diff --git a/.github/workflows/build-alpine-linux.yml b/.github/workflows/build-alpine-linux.yml index ce97f3c1092..c39962fa07f 100644 --- a/.github/workflows/build-alpine-linux.yml +++ b/.github/workflows/build-alpine-linux.yml @@ -96,6 +96,8 @@ jobs: --with-boot-jdk=${{ steps.bootjdk.outputs.path }} --with-zlib=system --with-jmod-compress=zip-1 + --with-external-symbols-in-bundles=none + --with-native-debug-symbols-level=1 ${{ inputs.extra-conf-options }} ${{ inputs.configure-arguments }} || ( echo "Dumping config.log:" && cat config.log && diff --git a/.github/workflows/build-cross-compile.yml b/.github/workflows/build-cross-compile.yml index 27ae0a6cb63..a0642d469aa 100644 --- a/.github/workflows/build-cross-compile.yml +++ b/.github/workflows/build-cross-compile.yml @@ -179,6 +179,8 @@ jobs: --openjdk-target=${{ matrix.gnu-arch }}-linux-gnu${{ matrix.gnu-abi}} --with-sysroot=sysroot --with-jmod-compress=zip-1 + --with-external-symbols-in-bundles=none + --with-native-debug-symbols-level=1 CC=${{ matrix.gnu-arch }}-linux-gnu${{ matrix.gnu-abi}}-gcc-${{ inputs.gcc-major-version }} CXX=${{ matrix.gnu-arch }}-linux-gnu${{ matrix.gnu-abi}}-g++-${{ inputs.gcc-major-version }} ${{ inputs.extra-conf-options }} ${{ inputs.configure-arguments }} || ( diff --git a/.github/workflows/build-linux.yml b/.github/workflows/build-linux.yml index 33e09585bf3..791b53a3f04 100644 --- a/.github/workflows/build-linux.yml +++ b/.github/workflows/build-linux.yml @@ -143,6 +143,8 @@ jobs: --with-gtest=${{ steps.gtest.outputs.path }} --with-zlib=system --with-jmod-compress=zip-1 + --with-external-symbols-in-bundles=none + --with-native-debug-symbols-level=1 ${{ inputs.extra-conf-options }} ${{ inputs.configure-arguments }} || ( echo "Dumping config.log:" && cat config.log && diff --git a/.github/workflows/build-macos.yml b/.github/workflows/build-macos.yml index dba47194c6d..484e616fad7 100644 --- a/.github/workflows/build-macos.yml +++ b/.github/workflows/build-macos.yml @@ -110,6 +110,8 @@ jobs: --with-gtest=${{ steps.gtest.outputs.path }} --with-zlib=system --with-jmod-compress=zip-1 + --with-external-symbols-in-bundles=none + --with-native-debug-symbols-level=1 ${{ inputs.extra-conf-options }} ${{ inputs.configure-arguments }} || ( echo "Dumping config.log:" && cat config.log && diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 6bcccf627ab..4dafc016a99 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -134,6 +134,7 @@ jobs: --with-gtest=${{ steps.gtest.outputs.path }} --with-msvc-toolset-version=${{ inputs.msvc-toolset-version }} --with-jmod-compress=zip-1 + --with-external-symbols-in-bundles=none ${{ inputs.extra-conf-options }} ${{ inputs.configure-arguments }} || ( echo "Dumping config.log:" && cat config.log && diff --git a/doc/testing.html b/doc/testing.html index 31f4fbd1778..195153c8612 100644 --- a/doc/testing.html +++ b/doc/testing.html @@ -72,6 +72,7 @@ id="toc-notes-for-specific-tests">Notes for Specific Tests
  • Non-US locale
  • PKCS11 Tests
  • +
  • SCTP Tests
  • Testing Ahead-of-time Optimizations
  • @@ -621,6 +622,21 @@ element of the appropriate @Artifact class. (See JTREG="JAVA_OPTIONS=-Djdk.test.lib.artifacts.nsslib-linux_aarch64=/path/to/NSS-libs"

    For more notes about the PKCS11 tests, please refer to test/jdk/sun/security/pkcs11/README.

    +

    SCTP Tests

    +

    The SCTP tests require the SCTP runtime library, which is often not +installed by default in popular Linux distributions. Without this +library, the SCTP tests will be skipped. If you want to enable the SCTP +tests, you should install the SCTP library before running the tests.

    +

    For distributions using the .deb packaging format and the apt tool +(such as Debian, Ubuntu, etc.), try this:

    +
    sudo apt install libsctp1
    +sudo modprobe sctp
    +lsmod | grep sctp
    +

    For distributions using the .rpm packaging format and the dnf tool +(such as Fedora, Red Hat, etc.), try this:

    +
    sudo dnf install -y lksctp-tools
    +sudo modprobe sctp
    +lsmod | grep sctp

    Testing Ahead-of-time Optimizations

    One way to improve test coverage of ahead-of-time (AOT) optimizations diff --git a/doc/testing.md b/doc/testing.md index b95f59de9fd..d0e54aab02b 100644 --- a/doc/testing.md +++ b/doc/testing.md @@ -640,6 +640,32 @@ $ make test TEST="jtreg:sun/security/pkcs11/Secmod/AddTrustedCert.java" \ For more notes about the PKCS11 tests, please refer to test/jdk/sun/security/pkcs11/README. + +### SCTP Tests + +The SCTP tests require the SCTP runtime library, which is often not installed +by default in popular Linux distributions. Without this library, the SCTP tests +will be skipped. If you want to enable the SCTP tests, you should install the +SCTP library before running the tests. + +For distributions using the .deb packaging format and the apt tool +(such as Debian, Ubuntu, etc.), try this: + +``` +sudo apt install libsctp1 +sudo modprobe sctp +lsmod | grep sctp +``` + +For distributions using the .rpm packaging format and the dnf tool +(such as Fedora, Red Hat, etc.), try this: + +``` +sudo dnf install -y lksctp-tools +sudo modprobe sctp +lsmod | grep sctp +``` + ### Testing Ahead-of-time Optimizations One way to improve test coverage of ahead-of-time (AOT) optimizations in diff --git a/make/RunTests.gmk b/make/RunTests.gmk index 946b1332edc..02ea632e3ec 100644 --- a/make/RunTests.gmk +++ b/make/RunTests.gmk @@ -1,5 +1,5 @@ # -# Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2016, 2026, 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 @@ -972,6 +972,10 @@ define SetupRunJtregTestBody JTREG_AUTO_PROBLEM_LISTS += ProblemList-enable-preview.txt endif + ifneq ($$(findstring -XX:+UseCompactObjectHeaders, $$(JTREG_ALL_OPTIONS)), ) + JTREG_AUTO_PROBLEM_LISTS += ProblemList-coh.txt + endif + ifneq ($$(JTREG_EXTRA_PROBLEM_LISTS), ) # Accept both absolute paths as well as relative to the current test root. diff --git a/make/autoconf/flags-cflags.m4 b/make/autoconf/flags-cflags.m4 index 6298bcae416..639c3852212 100644 --- a/make/autoconf/flags-cflags.m4 +++ b/make/autoconf/flags-cflags.m4 @@ -69,6 +69,19 @@ AC_DEFUN([FLAGS_SETUP_DEBUG_SYMBOLS], # Debug prefix mapping if supported by compiler DEBUG_PREFIX_CFLAGS= + UTIL_ARG_WITH(NAME: native-debug-symbols-level, TYPE: literal, + DEFAULT: [auto], VALID_VALUES: [auto 1 2 3], + CHECK_AVAILABLE: [ + if test x$TOOLCHAIN_TYPE = xmicrosoft; then + AVAILABLE=false + fi + ], + DESC: [set the native debug symbol level (GCC and Clang only)], + DEFAULT_DESC: [toolchain default], + IF_AUTO: [ + RESULT="" + ]) + # Debug symbols if test "x$TOOLCHAIN_TYPE" = xgcc; then if test "x$ALLOW_ABSOLUTE_PATHS_IN_OUTPUT" = "xfalse"; then @@ -93,8 +106,9 @@ AC_DEFUN([FLAGS_SETUP_DEBUG_SYMBOLS], ) fi - CFLAGS_DEBUG_SYMBOLS="-g -gdwarf-4" - ASFLAGS_DEBUG_SYMBOLS="-g" + # Debug info level should follow the debug format to be effective. + CFLAGS_DEBUG_SYMBOLS="-gdwarf-4 -g${NATIVE_DEBUG_SYMBOLS_LEVEL}" + ASFLAGS_DEBUG_SYMBOLS="-g${NATIVE_DEBUG_SYMBOLS_LEVEL}" elif test "x$TOOLCHAIN_TYPE" = xclang; then if test "x$ALLOW_ABSOLUTE_PATHS_IN_OUTPUT" = "xfalse"; then # Check if compiler supports -fdebug-prefix-map. If so, use that to make @@ -113,8 +127,9 @@ AC_DEFUN([FLAGS_SETUP_DEBUG_SYMBOLS], FLAGS_COMPILER_CHECK_ARGUMENTS(ARGUMENT: [${GDWARF_FLAGS}], IF_FALSE: [GDWARF_FLAGS=""]) - CFLAGS_DEBUG_SYMBOLS="-g ${GDWARF_FLAGS}" - ASFLAGS_DEBUG_SYMBOLS="-g" + # Debug info level should follow the debug format to be effective. + CFLAGS_DEBUG_SYMBOLS="${GDWARF_FLAGS} -g${NATIVE_DEBUG_SYMBOLS_LEVEL}" + ASFLAGS_DEBUG_SYMBOLS="-g${NATIVE_DEBUG_SYMBOLS_LEVEL}" elif test "x$TOOLCHAIN_TYPE" = xmicrosoft; then CFLAGS_DEBUG_SYMBOLS="-Z7" fi diff --git a/make/common/Utils.gmk b/make/common/Utils.gmk index c0ebabca3f7..b4bb949d3ee 100644 --- a/make/common/Utils.gmk +++ b/make/common/Utils.gmk @@ -114,7 +114,7 @@ EscapeDollar = $(subst $$,\$$,$(subst \$$,$$,$(strip $1))) ################################################################################ # This macro works just like EscapeDollar above, but for #. -EscapeHash = $(subst \#,\\\#,$(subst \\\#,\#,$(strip $1))) +EscapeHash = $(subst $(HASH),\$(HASH),$(subst \$(HASH),$(HASH),$(strip $1))) ################################################################################ # This macro translates $ into $$ to protect the string from make itself. diff --git a/make/hotspot/lib/CompileGtest.gmk b/make/hotspot/lib/CompileGtest.gmk index 60912992134..327014b1e9d 100644 --- a/make/hotspot/lib/CompileGtest.gmk +++ b/make/hotspot/lib/CompileGtest.gmk @@ -1,5 +1,5 @@ # -# Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2016, 2026, 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 @@ -61,7 +61,8 @@ $(eval $(call SetupJdkLibrary, BUILD_GTEST_LIBGTEST, \ INCLUDE_FILES := gtest-all.cc gmock-all.cc, \ DISABLED_WARNINGS_gcc := format-nonliteral maybe-uninitialized undef \ unused-result zero-as-null-pointer-constant, \ - DISABLED_WARNINGS_clang := format-nonliteral undef unused-result, \ + DISABLED_WARNINGS_clang := format-nonliteral undef unused-result \ + zero-as-null-pointer-constant, \ DISABLED_WARNINGS_microsoft := 4530, \ DEFAULT_CFLAGS := false, \ CFLAGS := $(JVM_CFLAGS) \ diff --git a/make/jdk/src/classes/build/tools/taglet/JSpec.java b/make/jdk/src/classes/build/tools/taglet/JSpec.java index 1c301e94a8a..7e1e0ca215e 100644 --- a/make/jdk/src/classes/build/tools/taglet/JSpec.java +++ b/make/jdk/src/classes/build/tools/taglet/JSpec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, 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 @@ -49,13 +49,15 @@ import static com.sun.source.doctree.DocTree.Kind.*; * The tags can be used as follows: * *

    - * @jls section-number description
    + * @jls chapter.section description
    + * @jls preview-feature-chapter.section description
      * 
    * * For example: * *
      * @jls 3.4 Line Terminators
    + * @jls primitive-types-in-patterns-instanceof-switch-5.7.1 Exact Testing Conversions
      * 
    * * will produce the following HTML, depending on the file containing @@ -64,10 +66,24 @@ import static com.sun.source.doctree.DocTree.Kind.*; *
    {@code
      * 
    See Java Language Specification: *
    3.4 Line terminators + *
    + * 5.7.1 Exact Testing Conversions + * PREVIEW * }
    * - * Copies of JLS and JVMS are expected to have been placed in the {@code specs} - * folder. These documents are not included in open-source repositories. + * In inline tags (note you need manual JLS/JVMS prefix): + *
    + * JLS {@jls 3.4}
    + * 
    + * + * produces (note the section sign and no trailing dot): + *
    + * JLS §3.4
    + * 
    + * + * Copies of JLS, JVMS, and preview JLS and JVMS changes are expected to have + * been placed in the {@code specs} folder. These documents are not included + * in open-source repositories. */ public class JSpec implements Taglet { @@ -87,9 +103,9 @@ public class JSpec implements Taglet { } } - private String tagName; - private String specTitle; - private String idPrefix; + private final String tagName; + private final String specTitle; + private final String idPrefix; JSpec(String tagName, String specTitle, String idPrefix) { this.tagName = tagName; @@ -98,7 +114,7 @@ public class JSpec implements Taglet { } // Note: Matches special cases like @jvms 6.5.checkcast - private static final Pattern TAG_PATTERN = Pattern.compile("(?s)(.+ )?(?[1-9][0-9]*)(?
    [0-9a-z_.]*)( .*)?$"); + private static final Pattern TAG_PATTERN = Pattern.compile("(?s)(.+ )?(?([a-z0-9]+-)+)?(?[1-9][0-9]*)(?
    [0-9a-z_.]*)( .*)?$"); /** * Returns the set of locations in which the tag may be used. @@ -157,19 +173,50 @@ public class JSpec implements Taglet { .trim(); Matcher m = TAG_PATTERN.matcher(tagText); if (m.find()) { + // preview-feature-4.6 is preview-feature-, 4, .6 + String preview = m.group("preview"); // null if no preview feature String chapter = m.group("chapter"); String section = m.group("section"); String rootParent = currentPath().replaceAll("[^/]+", ".."); - String url = String.format("%1$s/specs/%2$s/%2$s-%3$s.html#%2$s-%3$s%4$s", - rootParent, idPrefix, chapter, section); + String url = preview == null ? + String.format("%1$s/specs/%2$s/%2$s-%3$s.html#%2$s-%3$s%4$s", + rootParent, idPrefix, chapter, section) : + String.format("%1$s/specs/%5$s%2$s.html#%2$s-%3$s%4$s", + rootParent, idPrefix, chapter, section, preview); + + var literal = expand(contents).trim(); + var prefix = (preview == null ? "" : preview) + chapter + section; + if (literal.startsWith(prefix)) { + var hasFullTitle = literal.length() > prefix.length(); + if (hasFullTitle) { + // Drop the preview identifier + literal = chapter + section + literal.substring(prefix.length()); + } else { + // No section sign if the tag refers to a chapter, like {@jvms 4} + String sectionSign = section.isEmpty() ? "" : "§"; + // Change whole text to "§chapter.x" in inline tags. + literal = sectionSign + chapter + section; + } + } sb.append("") - .append(expand(contents)) + .append(literal) .append(""); + if (preview != null) { + // Add PREVIEW superscript that links to JLS/JVMS 1.5.1 + // "Restrictions on the Use of Preview Features" + // Similar to how APIs link to the Preview info box warning + var sectionLink = String.format("%1$s/specs/%2$s/%2$s-%3$s.html#%2$s-%3$s%4$s", + rootParent, idPrefix, "1", ".5.1"); + sb.append("PREVIEW"); + } + if (tag.getKind() == DocTree.Kind.UNKNOWN_BLOCK_TAG) { sb.append("
    "); } diff --git a/make/langtools/tools/propertiesparser/gen/ClassGenerator.java b/make/langtools/tools/propertiesparser/gen/ClassGenerator.java index 247537b4676..14e8c4fb00a 100644 --- a/make/langtools/tools/propertiesparser/gen/ClassGenerator.java +++ b/make/langtools/tools/propertiesparser/gen/ClassGenerator.java @@ -286,7 +286,7 @@ public class ClassGenerator { diagnosticFlags.isEmpty() ? StubKind.DIAGNOSTIC_FLAGS_EMPTY.format() : StubKind.DIAGNOSTIC_FLAGS_NON_EMPTY.format(diagnosticFlags), - StubKind.LINT_CATEGORY.format("\"" + lintCategory + "\""), + StubKind.LINT_CATEGORY.format(toLintFieldName(lintCategory)), "\"" + keyParts[0] + "\"", "\"" + Stream.of(keyParts).skip(2).collect(Collectors.joining(".")) + "\"", javadoc); @@ -314,7 +314,7 @@ public class ClassGenerator { diagnosticFlags.isEmpty() ? StubKind.DIAGNOSTIC_FLAGS_EMPTY.format() : StubKind.DIAGNOSTIC_FLAGS_NON_EMPTY.format(diagnosticFlags), - StubKind.LINT_CATEGORY.format("\"" + lintCategory + "\""), + StubKind.LINT_CATEGORY.format(toLintFieldName(lintCategory)), "\"" + keyParts[0] + "\"", "\"" + Stream.of(keyParts).skip(2).collect(Collectors.joining(".")) + "\"", argNames.stream().collect(Collectors.joining(", "))); @@ -329,6 +329,11 @@ public class ClassGenerator { } } + String toLintFieldName(String lintCategory) { + return lintCategory.toUpperCase() + .replaceAll("-", "_"); + } + /** * Form the name of a factory method/field given a resource key. */ diff --git a/make/langtools/tools/propertiesparser/resources/templates.properties b/make/langtools/tools/propertiesparser/resources/templates.properties index 81a9be2552c..f8ff07a878f 100644 --- a/make/langtools/tools/propertiesparser/resources/templates.properties +++ b/make/langtools/tools/propertiesparser/resources/templates.properties @@ -87,7 +87,7 @@ suppress.warnings=\ @SuppressWarnings("rawtypes")\n lint.category=\ - LintCategory.get({0}).get() + LintCategory.{0} diagnostic.flags.empty=\ EnumSet.noneOf(DiagnosticFlag.class) diff --git a/make/modules/jdk.jdwp.agent/Lib.gmk b/make/modules/jdk.jdwp.agent/Lib.gmk index 6ae053d4bec..ce58e145f59 100644 --- a/make/modules/jdk.jdwp.agent/Lib.gmk +++ b/make/modules/jdk.jdwp.agent/Lib.gmk @@ -54,21 +54,6 @@ $(eval $(call SetupJdkLibrary, BUILD_LIBJDWP, \ NAME := jdwp, \ OPTIMIZATION := LOW, \ CFLAGS := -DJDWP_LOGGING $(ICONV_CFLAGS), \ - DISABLED_WARNINGS_gcc_eventFilter.c := unused-variable, \ - DISABLED_WARNINGS_gcc_SDE.c := unused-function, \ - DISABLED_WARNINGS_gcc_threadControl.c := unused-but-set-variable \ - unused-variable, \ - DISABLED_WARNINGS_gcc_utf_util.c := unused-but-set-variable, \ - DISABLED_WARNINGS_clang_error_messages.c := format-nonliteral, \ - DISABLED_WARNINGS_clang_eventFilter.c := unused-variable, \ - DISABLED_WARNINGS_clang_EventRequestImpl.c := self-assign, \ - DISABLED_WARNINGS_clang_inStream.c := sometimes-uninitialized, \ - DISABLED_WARNINGS_clang_log_messages.c := format-nonliteral, \ - DISABLED_WARNINGS_clang_SDE.c := unused-function, \ - DISABLED_WARNINGS_clang_threadControl.c := unused-but-set-variable \ - unused-variable, \ - DISABLED_WARNINGS_clang_utf_util.c := unused-but-set-variable, \ - DISABLED_WARNINGS_microsoft_debugInit.c := 5287, \ LDFLAGS := $(ICONV_LDFLAGS), \ EXTRA_HEADER_DIRS := \ include \ diff --git a/make/modules/jdk.security.auth/Lib.gmk b/make/modules/jdk.security.auth/Lib.gmk index 9ead32dbe12..96d609f08d6 100644 --- a/make/modules/jdk.security.auth/Lib.gmk +++ b/make/modules/jdk.security.auth/Lib.gmk @@ -31,13 +31,14 @@ include LibCommon.gmk ## Build libjaas ################################################################################ -$(eval $(call SetupJdkLibrary, BUILD_LIBJAAS, \ - NAME := jaas, \ - OPTIMIZATION := LOW, \ - EXTRA_HEADER_DIRS := java.base:libjava, \ - LIBS_windows := advapi32.lib mpr.lib netapi32.lib user32.lib, \ -)) - -TARGETS += $(BUILD_LIBJAAS) +ifeq ($(call isTargetOs, windows), true) + $(eval $(call SetupJdkLibrary, BUILD_LIBJAAS, \ + NAME := jaas, \ + OPTIMIZATION := LOW, \ + EXTRA_HEADER_DIRS := java.base:libjava, \ + LIBS_windows := advapi32.lib mpr.lib netapi32.lib user32.lib, \ + )) + TARGETS += $(BUILD_LIBJAAS) +endif ################################################################################ diff --git a/src/demo/share/jfc/TableExample/OldJTable.java b/src/demo/share/jfc/TableExample/OldJTable.java deleted file mode 100644 index 8c77978fe8a..00000000000 --- a/src/demo/share/jfc/TableExample/OldJTable.java +++ /dev/null @@ -1,264 +0,0 @@ -/* - * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * - Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * - Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * - Neither the name of Oracle nor the names of its - * contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS - * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, - * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/* - * This source code is provided to illustrate the usage of a given feature - * or technique and has been deliberately simplified. Additional steps - * required for a production-quality application, such as security checks, - * input validation and proper error handling, might not be present in - * this sample code. - */ - - - -import java.util.EventObject; -import java.util.List; -import javax.swing.JTable; -import javax.swing.table.DefaultTableModel; -import javax.swing.table.TableCellEditor; -import javax.swing.table.TableCellRenderer; -import javax.swing.table.TableColumn; - - -/** - * The OldJTable is an unsupported class containing some methods that were - * deleted from the JTable between releases 0.6 and 0.7 - */ -@SuppressWarnings("serial") -public class OldJTable extends JTable -{ - /* - * A new convenience method returning the index of the column in the - * co-ordinate space of the view. - */ - public int getColumnIndex(Object identifier) { - return getColumnModel().getColumnIndex(identifier); - } - -// -// Methods deleted from the JTable because they only work with the -// DefaultTableModel. -// - - public TableColumn addColumn(Object columnIdentifier, int width) { - return addColumn(columnIdentifier, width, null, null, null); - } - - public TableColumn addColumn(Object columnIdentifier, List columnData) { - return addColumn(columnIdentifier, -1, null, null, columnData); - } - - // Override the new JTable implementation - it will not add a column to the - // DefaultTableModel. - public TableColumn addColumn(Object columnIdentifier, int width, - TableCellRenderer renderer, - TableCellEditor editor) { - return addColumn(columnIdentifier, width, renderer, editor, null); - } - - public TableColumn addColumn(Object columnIdentifier, int width, - TableCellRenderer renderer, - TableCellEditor editor, List columnData) { - checkDefaultTableModel(); - - // Set up the model side first - DefaultTableModel m = (DefaultTableModel)getModel(); - m.addColumn(columnIdentifier, columnData.toArray()); - - // The column will have been added to the end, so the index of the - // column in the model is the last element. - TableColumn newColumn = new TableColumn( - m.getColumnCount()-1, width, renderer, editor); - super.addColumn(newColumn); - return newColumn; - } - - // Not possilble to make this work the same way ... change it so that - // it does not delete columns from the model. - public void removeColumn(Object columnIdentifier) { - super.removeColumn(getColumn(columnIdentifier)); - } - - public void addRow(Object[] rowData) { - checkDefaultTableModel(); - ((DefaultTableModel)getModel()).addRow(rowData); - } - - public void addRow(List rowData) { - checkDefaultTableModel(); - ((DefaultTableModel)getModel()).addRow(rowData.toArray()); - } - - public void removeRow(int rowIndex) { - checkDefaultTableModel(); - ((DefaultTableModel)getModel()).removeRow(rowIndex); - } - - public void moveRow(int startIndex, int endIndex, int toIndex) { - checkDefaultTableModel(); - ((DefaultTableModel)getModel()).moveRow(startIndex, endIndex, toIndex); - } - - public void insertRow(int rowIndex, Object[] rowData) { - checkDefaultTableModel(); - ((DefaultTableModel)getModel()).insertRow(rowIndex, rowData); - } - - public void insertRow(int rowIndex, List rowData) { - checkDefaultTableModel(); - ((DefaultTableModel)getModel()).insertRow(rowIndex, rowData.toArray()); - } - - public void setNumRows(int newSize) { - checkDefaultTableModel(); - ((DefaultTableModel)getModel()).setNumRows(newSize); - } - - public void setDataVector(Object[][] newData, List columnIds) { - checkDefaultTableModel(); - ((DefaultTableModel)getModel()).setDataVector( - newData, columnIds.toArray()); - } - - public void setDataVector(Object[][] newData, Object[] columnIds) { - checkDefaultTableModel(); - ((DefaultTableModel)getModel()).setDataVector(newData, columnIds); - } - - protected void checkDefaultTableModel() { - if(!(dataModel instanceof DefaultTableModel)) - throw new InternalError("In order to use this method, the data model must be an instance of DefaultTableModel."); - } - -// -// Methods removed from JTable in the move from identifiers to ints. -// - - public Object getValueAt(Object columnIdentifier, int rowIndex) { - return super.getValueAt(rowIndex, getColumnIndex(columnIdentifier)); - } - - public boolean isCellEditable(Object columnIdentifier, int rowIndex) { - return super.isCellEditable(rowIndex, getColumnIndex(columnIdentifier)); - } - - public void setValueAt(Object aValue, Object columnIdentifier, int rowIndex) { - super.setValueAt(aValue, rowIndex, getColumnIndex(columnIdentifier)); - } - - public boolean editColumnRow(Object identifier, int row) { - return super.editCellAt(row, getColumnIndex(identifier)); - } - - public void moveColumn(Object columnIdentifier, Object targetColumnIdentifier) { - moveColumn(getColumnIndex(columnIdentifier), - getColumnIndex(targetColumnIdentifier)); - } - - public boolean isColumnSelected(Object identifier) { - return isColumnSelected(getColumnIndex(identifier)); - } - - public TableColumn addColumn(int modelColumn, int width) { - return addColumn(modelColumn, width, null, null); - } - - public TableColumn addColumn(int modelColumn) { - return addColumn(modelColumn, 75, null, null); - } - - /** - * Creates a new column with modelColumn, width, - * renderer, and editor and adds it to the end of - * the JTable's array of columns. This method also retrieves the - * name of the column using the model's getColumnName(modelColumn) - * method, and sets the both the header value and the identifier - * for this TableColumn accordingly. - *

    - * The modelColumn is the index of the column in the model which - * will supply the data for this column in the table. This, like the - * columnIdentifier in previous releases, does not change as the - * columns are moved in the view. - *

    - * For the rest of the JTable API, and all of its associated classes, - * columns are referred to in the co-ordinate system of the view, the - * index of the column in the model is kept inside the TableColumn - * and is used only to retrieve the information from the appropraite - * column in the model. - *

    - * - * @param modelColumn The index of the column in the model - * @param width The new column's width. Or -1 to use - * the default width - * @param renderer The renderer used with the new column. - * Or null to use the default renderer. - * @param editor The editor used with the new column. - * Or null to use the default editor. - */ - public TableColumn addColumn(int modelColumn, int width, - TableCellRenderer renderer, - TableCellEditor editor) { - TableColumn newColumn = new TableColumn( - modelColumn, width, renderer, editor); - addColumn(newColumn); - return newColumn; - } - -// -// Methods that had their arguments switched. -// - -// These won't work with the new table package. - -/* - public Object getValueAt(int columnIndex, int rowIndex) { - return super.getValueAt(rowIndex, columnIndex); - } - - public boolean isCellEditable(int columnIndex, int rowIndex) { - return super.isCellEditable(rowIndex, columnIndex); - } - - public void setValueAt(Object aValue, int columnIndex, int rowIndex) { - super.setValueAt(aValue, rowIndex, columnIndex); - } -*/ - - public boolean editColumnRow(int columnIndex, int rowIndex) { - return super.editCellAt(rowIndex, columnIndex); - } - - public boolean editColumnRow(int columnIndex, int rowIndex, EventObject e){ - return super.editCellAt(rowIndex, columnIndex, e); - } - - -} // End Of Class OldJTable diff --git a/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp index 37a6a130e0d..c0621cbd5c2 100644 --- a/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp @@ -1218,43 +1218,11 @@ void LIR_Assembler::emit_alloc_array(LIR_OpAllocArray* op) { __ bind(*op->stub()->continuation()); } -void LIR_Assembler::type_profile_helper(Register mdo, - ciMethodData *md, ciProfileData *data, - Register recv, Label* update_done) { +void LIR_Assembler::type_profile_helper(Register mdo, ciMethodData *md, + ciProfileData *data, Register recv) { - // Given a profile data offset, generate an Address which points to - // the corresponding slot in mdo->data(). - // Clobbers rscratch2. - auto slot_at = [=](ByteSize offset) -> Address { - return __ form_address(rscratch2, mdo, - md->byte_offset_of_slot(data, offset), - LogBytesPerWord); - }; - - for (uint i = 0; i < ReceiverTypeData::row_limit(); i++) { - Label next_test; - // See if the receiver is receiver[n]. - __ ldr(rscratch1, slot_at(ReceiverTypeData::receiver_offset(i))); - __ cmp(recv, rscratch1); - __ br(Assembler::NE, next_test); - __ addptr(slot_at(ReceiverTypeData::receiver_count_offset(i)), - DataLayout::counter_increment); - __ b(*update_done); - __ bind(next_test); - } - - // Didn't find receiver; find next empty slot and fill it in - for (uint i = 0; i < ReceiverTypeData::row_limit(); i++) { - Label next_test; - Address recv_addr(slot_at(ReceiverTypeData::receiver_offset(i))); - __ ldr(rscratch1, recv_addr); - __ cbnz(rscratch1, next_test); - __ str(recv, recv_addr); - __ mov(rscratch1, DataLayout::counter_increment); - __ str(rscratch1, slot_at(ReceiverTypeData::receiver_count_offset(i))); - __ b(*update_done); - __ bind(next_test); - } + int mdp_offset = md->byte_offset_of_slot(data, in_ByteSize(0)); + __ profile_receiver_type(recv, mdo, mdp_offset); } void LIR_Assembler::emit_typecheck_helper(LIR_OpTypeCheck *op, Label* success, Label* failure, Label* obj_is_null) { @@ -1316,14 +1284,9 @@ void LIR_Assembler::emit_typecheck_helper(LIR_OpTypeCheck *op, Label* success, L __ b(*obj_is_null); __ bind(not_null); - Label update_done; Register recv = k_RInfo; __ load_klass(recv, obj); - type_profile_helper(mdo, md, data, recv, &update_done); - Address counter_addr(mdo, md->byte_offset_of_slot(data, CounterData::count_offset())); - __ addptr(counter_addr, DataLayout::counter_increment); - - __ bind(update_done); + type_profile_helper(mdo, md, data, recv); } else { __ cbz(obj, *obj_is_null); } @@ -1430,13 +1393,9 @@ void LIR_Assembler::emit_opTypeCheck(LIR_OpTypeCheck* op) { __ b(done); __ bind(not_null); - Label update_done; Register recv = k_RInfo; __ load_klass(recv, value); - type_profile_helper(mdo, md, data, recv, &update_done); - Address counter_addr(mdo, md->byte_offset_of_slot(data, CounterData::count_offset())); - __ addptr(counter_addr, DataLayout::counter_increment); - __ bind(update_done); + type_profile_helper(mdo, md, data, recv); } else { __ cbz(value, done); } @@ -2540,13 +2499,9 @@ void LIR_Assembler::emit_profile_call(LIR_OpProfileCall* op) { if (C1OptimizeVirtualCallProfiling && known_klass != nullptr) { // We know the type that will be seen at this call site; we can // statically update the MethodData* rather than needing to do - // dynamic tests on the receiver type - - // NOTE: we should probably put a lock around this search to - // avoid collisions by concurrent compilations + // dynamic tests on the receiver type. ciVirtualCallData* vc_data = (ciVirtualCallData*) data; - uint i; - for (i = 0; i < VirtualCallData::row_limit(); i++) { + for (uint i = 0; i < VirtualCallData::row_limit(); i++) { ciKlass* receiver = vc_data->receiver(i); if (known_klass->equals(receiver)) { Address data_addr(mdo, md->byte_offset_of_slot(data, VirtualCallData::receiver_count_offset(i))); @@ -2554,36 +2509,13 @@ void LIR_Assembler::emit_profile_call(LIR_OpProfileCall* op) { return; } } - - // Receiver type not found in profile data; select an empty slot - - // Note that this is less efficient than it should be because it - // always does a write to the receiver part of the - // VirtualCallData rather than just the first time - for (i = 0; i < VirtualCallData::row_limit(); i++) { - ciKlass* receiver = vc_data->receiver(i); - if (receiver == nullptr) { - __ mov_metadata(rscratch1, known_klass->constant_encoding()); - Address recv_addr = - __ form_address(rscratch2, mdo, - md->byte_offset_of_slot(data, VirtualCallData::receiver_offset(i)), - LogBytesPerWord); - __ str(rscratch1, recv_addr); - Address data_addr(mdo, md->byte_offset_of_slot(data, VirtualCallData::receiver_count_offset(i))); - __ addptr(data_addr, DataLayout::counter_increment); - return; - } - } + // Receiver type is not found in profile data. + // Fall back to runtime helper to handle the rest at runtime. + __ mov_metadata(recv, known_klass->constant_encoding()); } else { __ load_klass(recv, recv); - Label update_done; - type_profile_helper(mdo, md, data, recv, &update_done); - // Receiver did not match any saved receiver and there is no empty row for it. - // Increment total counter to indicate polymorphic case. - __ addptr(counter_addr, DataLayout::counter_increment); - - __ bind(update_done); } + type_profile_helper(mdo, md, data, recv); } else { // Static call __ addptr(counter_addr, DataLayout::counter_increment); diff --git a/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.hpp b/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.hpp index 21916a5f7dd..5af06fc6a1c 100644 --- a/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.hpp @@ -50,9 +50,8 @@ friend class ArrayCopyStub; Address stack_slot_address(int index, uint shift, Register tmp, int adjust = 0); // Record the type of the receiver in ReceiverTypeData - void type_profile_helper(Register mdo, - ciMethodData *md, ciProfileData *data, - Register recv, Label* update_done); + void type_profile_helper(Register mdo, ciMethodData *md, + ciProfileData *data, Register recv); void add_debug_info_for_branch(address adr, CodeEmitInfo* info); void casw(Register addr, Register newval, Register cmpval); diff --git a/src/hotspot/cpu/aarch64/immediate_aarch64.cpp b/src/hotspot/cpu/aarch64/immediate_aarch64.cpp index 710a1f0677d..eb4cb23100c 100644 --- a/src/hotspot/cpu/aarch64/immediate_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/immediate_aarch64.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2014, 2020, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -23,13 +23,13 @@ * */ -#include // do not reorder -#include // do not reorder - +#include "cppstdlib/cstdlib.hpp" #include "immediate_aarch64.hpp" #include "metaprogramming/primitiveConversions.hpp" #include "utilities/globalDefinitions.hpp" +#include + // there are at most 2^13 possible logical immediate encodings // however, some combinations of immr and imms are invalid static const unsigned LI_TABLE_SIZE = (1 << 13); diff --git a/src/hotspot/cpu/aarch64/immediate_aarch64.hpp b/src/hotspot/cpu/aarch64/immediate_aarch64.hpp index 0cbdb562088..fb38995417b 100644 --- a/src/hotspot/cpu/aarch64/immediate_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/immediate_aarch64.hpp @@ -1,4 +1,5 @@ /* + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2014, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -22,10 +23,10 @@ * */ -#ifndef _IMMEDIATE_H -#define _IMMEDIATE_H +#ifndef CPU_AARCH64_IMMEDIATE_AARCH64_HPP +#define CPU_AARCH64_IMMEDIATE_AARCH64_HPP -#include +#include /* * functions to map backwards and forwards between logical or floating @@ -51,4 +52,4 @@ uint32_t encoding_for_logical_immediate(uint64_t immediate); uint64_t fp_immediate_for_encoding(uint32_t imm8, int is_dp); uint32_t encoding_for_fp_immediate(float immediate); -#endif // _IMMEDIATE_H +#endif // CPU_AARCH64_IMMEDIATE_AARCH64_HPP diff --git a/src/hotspot/cpu/aarch64/interp_masm_aarch64.cpp b/src/hotspot/cpu/aarch64/interp_masm_aarch64.cpp index 957c2aee1c1..2b506b241e0 100644 --- a/src/hotspot/cpu/aarch64/interp_masm_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/interp_masm_aarch64.cpp @@ -240,15 +240,14 @@ void InterpreterMacroAssembler::load_resolved_klass_at_offset( // Rsub_klass: subklass // // Kills: -// r2, r5 +// r2 void InterpreterMacroAssembler::gen_subtype_check(Register Rsub_klass, Label& ok_is_subtype) { assert(Rsub_klass != r0, "r0 holds superklass"); assert(Rsub_klass != r2, "r2 holds 2ndary super array length"); - assert(Rsub_klass != r5, "r5 holds 2ndary super array scan ptr"); // Profile the not-null value's klass. - profile_typecheck(r2, Rsub_klass, r5); // blows r2, reloads r5 + profile_typecheck(r2, Rsub_klass); // blows r2 // Do the check. check_klass_subtype(Rsub_klass, r0, r2, ok_is_subtype); // blows r2 @@ -991,7 +990,6 @@ void InterpreterMacroAssembler::profile_final_call(Register mdp) { void InterpreterMacroAssembler::profile_virtual_call(Register receiver, Register mdp, - Register reg2, bool receiver_can_be_null) { if (ProfileInterpreter) { Label profile_continue; @@ -1009,7 +1007,7 @@ void InterpreterMacroAssembler::profile_virtual_call(Register receiver, } // Record the receiver type. - record_klass_in_profile(receiver, mdp, reg2); + profile_receiver_type(receiver, mdp, 0); bind(skip_receiver_profile); // The method data pointer needs to be updated to reflect the new target. @@ -1018,131 +1016,6 @@ void InterpreterMacroAssembler::profile_virtual_call(Register receiver, } } -// This routine creates a state machine for updating the multi-row -// type profile at a virtual call site (or other type-sensitive bytecode). -// The machine visits each row (of receiver/count) until the receiver type -// is found, or until it runs out of rows. At the same time, it remembers -// the location of the first empty row. (An empty row records null for its -// receiver, and can be allocated for a newly-observed receiver type.) -// Because there are two degrees of freedom in the state, a simple linear -// search will not work; it must be a decision tree. Hence this helper -// function is recursive, to generate the required tree structured code. -// It's the interpreter, so we are trading off code space for speed. -// See below for example code. -void InterpreterMacroAssembler::record_klass_in_profile_helper( - Register receiver, Register mdp, - Register reg2, int start_row, - Label& done) { - if (TypeProfileWidth == 0) { - increment_mdp_data_at(mdp, in_bytes(CounterData::count_offset())); - } else { - record_item_in_profile_helper(receiver, mdp, reg2, 0, done, TypeProfileWidth, - &VirtualCallData::receiver_offset, &VirtualCallData::receiver_count_offset); - } -} - -void InterpreterMacroAssembler::record_item_in_profile_helper(Register item, Register mdp, - Register reg2, int start_row, Label& done, int total_rows, - OffsetFunction item_offset_fn, OffsetFunction item_count_offset_fn) { - int last_row = total_rows - 1; - assert(start_row <= last_row, "must be work left to do"); - // Test this row for both the item and for null. - // Take any of three different outcomes: - // 1. found item => increment count and goto done - // 2. found null => keep looking for case 1, maybe allocate this cell - // 3. found something else => keep looking for cases 1 and 2 - // Case 3 is handled by a recursive call. - for (int row = start_row; row <= last_row; row++) { - Label next_test; - bool test_for_null_also = (row == start_row); - - // See if the item is item[n]. - int item_offset = in_bytes(item_offset_fn(row)); - test_mdp_data_at(mdp, item_offset, item, - (test_for_null_also ? reg2 : noreg), - next_test); - // (Reg2 now contains the item from the CallData.) - - // The item is item[n]. Increment count[n]. - int count_offset = in_bytes(item_count_offset_fn(row)); - increment_mdp_data_at(mdp, count_offset); - b(done); - bind(next_test); - - if (test_for_null_also) { - Label found_null; - // Failed the equality check on item[n]... Test for null. - if (start_row == last_row) { - // The only thing left to do is handle the null case. - cbz(reg2, found_null); - // Item did not match any saved item and there is no empty row for it. - // Increment total counter to indicate polymorphic case. - increment_mdp_data_at(mdp, in_bytes(CounterData::count_offset())); - b(done); - bind(found_null); - break; - } - // Since null is rare, make it be the branch-taken case. - cbz(reg2, found_null); - - // Put all the "Case 3" tests here. - record_item_in_profile_helper(item, mdp, reg2, start_row + 1, done, total_rows, - item_offset_fn, item_count_offset_fn); - - // Found a null. Keep searching for a matching item, - // but remember that this is an empty (unused) slot. - bind(found_null); - } - } - - // In the fall-through case, we found no matching item, but we - // observed the item[start_row] is null. - - // Fill in the item field and increment the count. - int item_offset = in_bytes(item_offset_fn(start_row)); - set_mdp_data_at(mdp, item_offset, item); - int count_offset = in_bytes(item_count_offset_fn(start_row)); - mov(reg2, DataLayout::counter_increment); - set_mdp_data_at(mdp, count_offset, reg2); - if (start_row > 0) { - b(done); - } -} - -// Example state machine code for three profile rows: -// // main copy of decision tree, rooted at row[1] -// if (row[0].rec == rec) { row[0].incr(); goto done; } -// if (row[0].rec != nullptr) { -// // inner copy of decision tree, rooted at row[1] -// if (row[1].rec == rec) { row[1].incr(); goto done; } -// if (row[1].rec != nullptr) { -// // degenerate decision tree, rooted at row[2] -// if (row[2].rec == rec) { row[2].incr(); goto done; } -// if (row[2].rec != nullptr) { count.incr(); goto done; } // overflow -// row[2].init(rec); goto done; -// } else { -// // remember row[1] is empty -// if (row[2].rec == rec) { row[2].incr(); goto done; } -// row[1].init(rec); goto done; -// } -// } else { -// // remember row[0] is empty -// if (row[1].rec == rec) { row[1].incr(); goto done; } -// if (row[2].rec == rec) { row[2].incr(); goto done; } -// row[0].init(rec); goto done; -// } -// done: - -void InterpreterMacroAssembler::record_klass_in_profile(Register receiver, - Register mdp, Register reg2) { - assert(ProfileInterpreter, "must be profiling"); - Label done; - - record_klass_in_profile_helper(receiver, mdp, reg2, 0, done); - - bind (done); -} - void InterpreterMacroAssembler::profile_ret(Register return_bci, Register mdp) { if (ProfileInterpreter) { @@ -1200,7 +1073,7 @@ void InterpreterMacroAssembler::profile_null_seen(Register mdp) { } } -void InterpreterMacroAssembler::profile_typecheck(Register mdp, Register klass, Register reg2) { +void InterpreterMacroAssembler::profile_typecheck(Register mdp, Register klass) { if (ProfileInterpreter) { Label profile_continue; @@ -1213,7 +1086,7 @@ void InterpreterMacroAssembler::profile_typecheck(Register mdp, Register klass, mdp_delta = in_bytes(VirtualCallData::virtual_call_data_size()); // Record the object type. - record_klass_in_profile(klass, mdp, reg2); + profile_receiver_type(klass, mdp, 0); } update_mdp_by_constant(mdp, mdp_delta); diff --git a/src/hotspot/cpu/aarch64/interp_masm_aarch64.hpp b/src/hotspot/cpu/aarch64/interp_masm_aarch64.hpp index 2b230a3b73e..74d4430000d 100644 --- a/src/hotspot/cpu/aarch64/interp_masm_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/interp_masm_aarch64.hpp @@ -273,15 +273,6 @@ class InterpreterMacroAssembler: public MacroAssembler { Register test_value_out, Label& not_equal_continue); - void record_klass_in_profile(Register receiver, Register mdp, - Register reg2); - void record_klass_in_profile_helper(Register receiver, Register mdp, - Register reg2, int start_row, - Label& done); - void record_item_in_profile_helper(Register item, Register mdp, - Register reg2, int start_row, Label& done, int total_rows, - OffsetFunction item_offset_fn, OffsetFunction item_count_offset_fn); - void update_mdp_by_offset(Register mdp_in, int offset_of_offset); void update_mdp_by_offset(Register mdp_in, Register reg, int offset_of_disp); void update_mdp_by_constant(Register mdp_in, int constant); @@ -295,11 +286,10 @@ class InterpreterMacroAssembler: public MacroAssembler { void profile_call(Register mdp); void profile_final_call(Register mdp); void profile_virtual_call(Register receiver, Register mdp, - Register scratch2, bool receiver_can_be_null = false); void profile_ret(Register return_bci, Register mdp); void profile_null_seen(Register mdp); - void profile_typecheck(Register mdp, Register klass, Register scratch); + void profile_typecheck(Register mdp, Register klass); void profile_typecheck_failed(Register mdp); void profile_switch_default(Register mdp); void profile_switch_case(Register index_in_scratch, Register mdp, diff --git a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp index 2ccc755be3c..f8b5a6f825c 100644 --- a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp @@ -2118,6 +2118,161 @@ Address MacroAssembler::argument_address(RegisterOrConstant arg_slot, } } +// Handle the receiver type profile update given the "recv" klass. +// +// Normally updates the ReceiverData (RD) that starts at "mdp" + "mdp_offset". +// If there are no matching or claimable receiver entries in RD, updates +// the polymorphic counter. +// +// This code expected to run by either the interpreter or JIT-ed code, without +// extra synchronization. For safety, receiver cells are claimed atomically, which +// avoids grossly misrepresenting the profiles under concurrent updates. For speed, +// counter updates are not atomic. +// +void MacroAssembler::profile_receiver_type(Register recv, Register mdp, int mdp_offset) { + assert_different_registers(recv, mdp, rscratch1, rscratch2); + + int base_receiver_offset = in_bytes(ReceiverTypeData::receiver_offset(0)); + int end_receiver_offset = in_bytes(ReceiverTypeData::receiver_offset(ReceiverTypeData::row_limit())); + int poly_count_offset = in_bytes(CounterData::count_offset()); + int receiver_step = in_bytes(ReceiverTypeData::receiver_offset(1)) - base_receiver_offset; + int receiver_to_count_step = in_bytes(ReceiverTypeData::receiver_count_offset(0)) - base_receiver_offset; + + // Adjust for MDP offsets. + base_receiver_offset += mdp_offset; + end_receiver_offset += mdp_offset; + poly_count_offset += mdp_offset; + +#ifdef ASSERT + // We are about to walk the MDO slots without asking for offsets. + // Check that our math hits all the right spots. + for (uint c = 0; c < ReceiverTypeData::row_limit(); c++) { + int real_recv_offset = mdp_offset + in_bytes(ReceiverTypeData::receiver_offset(c)); + int real_count_offset = mdp_offset + in_bytes(ReceiverTypeData::receiver_count_offset(c)); + int offset = base_receiver_offset + receiver_step*c; + int count_offset = offset + receiver_to_count_step; + assert(offset == real_recv_offset, "receiver slot math"); + assert(count_offset == real_count_offset, "receiver count math"); + } + int real_poly_count_offset = mdp_offset + in_bytes(CounterData::count_offset()); + assert(poly_count_offset == real_poly_count_offset, "poly counter math"); +#endif + + // Corner case: no profile table. Increment poly counter and exit. + if (ReceiverTypeData::row_limit() == 0) { + increment(Address(mdp, poly_count_offset), DataLayout::counter_increment); + return; + } + + Register offset = rscratch2; + + Label L_loop_search_receiver, L_loop_search_empty; + Label L_restart, L_found_recv, L_found_empty, L_polymorphic, L_count_update; + + // The code here recognizes three major cases: + // A. Fastest: receiver found in the table + // B. Fast: no receiver in the table, and the table is full + // C. Slow: no receiver in the table, free slots in the table + // + // The case A performance is most important, as perfectly-behaved code would end up + // there, especially with larger TypeProfileWidth. The case B performance is + // important as well, this is where bulk of code would land for normally megamorphic + // cases. The case C performance is not essential, its job is to deal with installation + // races, we optimize for code density instead. Case C needs to make sure that receiver + // rows are only claimed once. This makes sure we never overwrite a row for another + // receiver and never duplicate the receivers in the list, making profile type-accurate. + // + // It is very tempting to handle these cases in a single loop, and claim the first slot + // without checking the rest of the table. But, profiling code should tolerate free slots + // in the table, as class unloading can clear them. After such cleanup, the receiver + // we need might be _after_ the free slot. Therefore, we need to let at least full scan + // to complete, before trying to install new slots. Splitting the code in several tight + // loops also helpfully optimizes for cases A and B. + // + // This code is effectively: + // + // restart: + // // Fastest: receiver is already installed + // for (i = 0; i < receiver_count(); i++) { + // if (receiver(i) == recv) goto found_recv(i); + // } + // + // // Fast: no receiver, but profile is full + // for (i = 0; i < receiver_count(); i++) { + // if (receiver(i) == null) goto found_null(i); + // } + // goto polymorphic + // + // // Slow: try to install receiver + // found_null(i): + // CAS(&receiver(i), null, recv); + // goto restart + // + // polymorphic: + // count++; + // return + // + // found_recv(i): + // *receiver_count(i)++ + // + + bind(L_restart); + + // Fastest: receiver is already installed + mov(offset, base_receiver_offset); + bind(L_loop_search_receiver); + ldr(rscratch1, Address(mdp, offset)); + cmp(rscratch1, recv); + br(Assembler::EQ, L_found_recv); + add(offset, offset, receiver_step); + sub(rscratch1, offset, end_receiver_offset); + cbnz(rscratch1, L_loop_search_receiver); + + // Fast: no receiver, but profile is full + mov(offset, base_receiver_offset); + bind(L_loop_search_empty); + ldr(rscratch1, Address(mdp, offset)); + cbz(rscratch1, L_found_empty); + add(offset, offset, receiver_step); + sub(rscratch1, offset, end_receiver_offset); + cbnz(rscratch1, L_loop_search_empty); + b(L_polymorphic); + + // Slow: try to install receiver + bind(L_found_empty); + + // Atomically swing receiver slot: null -> recv. + // + // The update uses CAS, which clobbers rscratch1. Therefore, rscratch2 + // is used to hold the destination address. This is safe because the + // offset is no longer needed after the address is computed. + + lea(rscratch2, Address(mdp, offset)); + cmpxchg(/*addr*/ rscratch2, /*expected*/ zr, /*new*/ recv, Assembler::xword, + /*acquire*/ false, /*release*/ false, /*weak*/ true, noreg); + + // CAS success means the slot now has the receiver we want. CAS failure means + // something had claimed the slot concurrently: it can be the same receiver we want, + // or something else. Since this is a slow path, we can optimize for code density, + // and just restart the search from the beginning. + b(L_restart); + + // Counter updates: + + // Increment polymorphic counter instead of receiver slot. + bind(L_polymorphic); + mov(offset, poly_count_offset); + b(L_count_update); + + // Found a receiver, convert its slot offset to corresponding count offset. + bind(L_found_recv); + add(offset, offset, receiver_to_count_step); + + bind(L_count_update); + increment(Address(mdp, offset), DataLayout::counter_increment); +} + + void MacroAssembler::call_VM_leaf_base(address entry_point, int number_of_arguments, Label *retaddr) { @@ -5606,12 +5761,11 @@ void MacroAssembler::adrp(Register reg1, const Address &dest, uint64_t &byte_off } void MacroAssembler::load_byte_map_base(Register reg) { - CardTable::CardValue* byte_map_base = - ((CardTableBarrierSet*)(BarrierSet::barrier_set()))->card_table()->byte_map_base(); + CardTableBarrierSet* ctbs = CardTableBarrierSet::barrier_set(); - // Strictly speaking the byte_map_base isn't an address at all, and it might + // Strictly speaking the card table base isn't an address at all, and it might // even be negative. It is thus materialised as a constant. - mov(reg, (uint64_t)byte_map_base); + mov(reg, (uint64_t)ctbs->card_table_base_const()); } void MacroAssembler::build_frame(int framesize) { @@ -5782,6 +5936,9 @@ address MacroAssembler::arrays_equals(Register a1, Register a2, Register tmp3, // return false; bind(A_IS_NOT_NULL); ldrw(cnt1, Address(a1, length_offset)); + ldrw(tmp5, Address(a2, length_offset)); + cmp(cnt1, tmp5); + br(NE, DONE); // If lengths differ, return false // Increase loop counter by diff between base- and actual start-offset. addw(cnt1, cnt1, extra_length); lea(a1, Address(a1, start_offset)); @@ -5848,6 +6005,9 @@ address MacroAssembler::arrays_equals(Register a1, Register a2, Register tmp3, cbz(a1, DONE); ldrw(cnt1, Address(a1, length_offset)); cbz(a2, DONE); + ldrw(tmp5, Address(a2, length_offset)); + cmp(cnt1, tmp5); + br(NE, DONE); // If lengths differ, return false // Increase loop counter by diff between base- and actual start-offset. addw(cnt1, cnt1, extra_length); @@ -6259,14 +6419,10 @@ void MacroAssembler::fill_words(Register base, Register cnt, Register value) // Intrinsic for // -// - sun.nio.cs.ISO_8859_1.Encoder#encodeISOArray0(byte[] sa, int sp, byte[] da, int dp, int len) -// Encodes char[] to byte[] in ISO-8859-1 -// -// - java.lang.StringCoding#encodeISOArray0(byte[] sa, int sp, byte[] da, int dp, int len) -// Encodes byte[] (containing UTF-16) to byte[] in ISO-8859-1 -// -// - java.lang.StringCoding#encodeAsciiArray0(char[] sa, int sp, byte[] da, int dp, int len) -// Encodes char[] to byte[] in ASCII +// - sun/nio/cs/ISO_8859_1$Encoder.implEncodeISOArray +// return the number of characters copied. +// - java/lang/StringUTF16.compress +// return index of non-latin1 character if copy fails, otherwise 'len'. // // This version always returns the number of characters copied, and does not // clobber the 'len' register. A successful copy will complete with the post- diff --git a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp index 4baa07d7d49..7eb6cea0614 100644 --- a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp @@ -1122,6 +1122,8 @@ public: Address argument_address(RegisterOrConstant arg_slot, int extra_slot_offset = 0); + void profile_receiver_type(Register recv, Register mdp, int mdp_offset); + void verify_sve_vector_length(Register tmp = rscratch1); void reinitialize_ptrue() { if (UseSVE > 0) { diff --git a/src/hotspot/cpu/aarch64/sharedRuntime_aarch64.cpp b/src/hotspot/cpu/aarch64/sharedRuntime_aarch64.cpp index 89ae6bc10e0..73b631029a0 100644 --- a/src/hotspot/cpu/aarch64/sharedRuntime_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/sharedRuntime_aarch64.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2014, 2021, Red Hat Inc. All rights reserved. * Copyright (c) 2021, Azul Systems, Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. @@ -722,22 +722,20 @@ void SharedRuntime::generate_i2c2i_adapters(MacroAssembler *masm, // Class initialization barrier for static methods entry_address[AdapterBlob::C2I_No_Clinit_Check] = nullptr; - if (VM_Version::supports_fast_class_init_checks()) { - Label L_skip_barrier; + assert(VM_Version::supports_fast_class_init_checks(), "sanity"); + Label L_skip_barrier; - { // Bypass the barrier for non-static methods - __ ldrh(rscratch1, Address(rmethod, Method::access_flags_offset())); - __ andsw(zr, rscratch1, JVM_ACC_STATIC); - __ br(Assembler::EQ, L_skip_barrier); // non-static - } + // Bypass the barrier for non-static methods + __ ldrh(rscratch1, Address(rmethod, Method::access_flags_offset())); + __ andsw(zr, rscratch1, JVM_ACC_STATIC); + __ br(Assembler::EQ, L_skip_barrier); // non-static - __ load_method_holder(rscratch2, rmethod); - __ clinit_barrier(rscratch2, rscratch1, &L_skip_barrier); - __ far_jump(RuntimeAddress(SharedRuntime::get_handle_wrong_method_stub())); + __ load_method_holder(rscratch2, rmethod); + __ clinit_barrier(rscratch2, rscratch1, &L_skip_barrier); + __ far_jump(RuntimeAddress(SharedRuntime::get_handle_wrong_method_stub())); - __ bind(L_skip_barrier); - entry_address[AdapterBlob::C2I_No_Clinit_Check] = __ pc(); - } + __ bind(L_skip_barrier); + entry_address[AdapterBlob::C2I_No_Clinit_Check] = __ pc(); BarrierSetAssembler* bs = BarrierSet::barrier_set()->barrier_set_assembler(); bs->c2i_entry_barrier(masm); @@ -1508,7 +1506,8 @@ nmethod* SharedRuntime::generate_native_wrapper(MacroAssembler* masm, // SVC, HVC, or SMC. Make it a NOP. __ nop(); - if (VM_Version::supports_fast_class_init_checks() && method->needs_clinit_barrier()) { + if (method->needs_clinit_barrier()) { + assert(VM_Version::supports_fast_class_init_checks(), "sanity"); Label L_skip_barrier; __ mov_metadata(rscratch2, method->method_holder()); // InstanceKlass* __ clinit_barrier(rscratch2, rscratch1, &L_skip_barrier); diff --git a/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp b/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp index 7e2f333ba40..db653bcf236 100644 --- a/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp @@ -6081,14 +6081,18 @@ class StubGenerator: public StubCodeGenerator { // static int implKyber12To16( // byte[] condensed, int index, short[] parsed, int parsedLength) {} // - // (parsedLength or (parsedLength - 48) must be divisible by 64.) + // we assume that parsed and condensed are allocated such that for + // n = (parsedLength + 63) / 64 + // n blocks of 96 bytes of input can be processed, i.e. + // index + n * 96 <= condensed.length and + // n * 64 <= parsed.length // // condensed (byte[]) = c_rarg0 // condensedIndex = c_rarg1 - // parsed (short[112 or 256]) = c_rarg2 - // parsedLength (112 or 256) = c_rarg3 + // parsed (short[]) = c_rarg2 + // parsedLength = c_rarg3 address generate_kyber12To16() { - Label L_F00, L_loop, L_end; + Label L_F00, L_loop; __ align(CodeEntryAlignment); StubId stub_id = StubId::stubgen_kyber12To16_id; @@ -6209,75 +6213,8 @@ class StubGenerator: public StubCodeGenerator { vs_st2_post(vs_front(vb), __ T8H, parsed); __ sub(parsedLength, parsedLength, 64); - __ cmp(parsedLength, (u1)64); - __ br(Assembler::GE, L_loop); - __ cbz(parsedLength, L_end); - - // if anything is left it should be a final 72 bytes of input - // i.e. a final 48 12-bit values. so we handle this by loading - // 48 bytes into all 16B lanes of front(vin) and only 24 - // bytes into the lower 8B lane of back(vin) - vs_ld3_post(vs_front(vin), __ T16B, condensed); - vs_ld3(vs_back(vin), __ T8B, condensed); - - // Expand vin[0] into va[0:1], and vin[1] into va[2:3] and va[4:5] - // n.b. target elements 2 and 3 of va duplicate elements 4 and - // 5 and target element 2 of vb duplicates element 4. - __ ushll(va[0], __ T8H, vin[0], __ T8B, 0); - __ ushll2(va[1], __ T8H, vin[0], __ T16B, 0); - __ ushll(va[2], __ T8H, vin[1], __ T8B, 0); - __ ushll2(va[3], __ T8H, vin[1], __ T16B, 0); - __ ushll(va[4], __ T8H, vin[1], __ T8B, 0); - __ ushll2(va[5], __ T8H, vin[1], __ T16B, 0); - - // This time expand just the lower 8 lanes - __ ushll(vb[0], __ T8H, vin[3], __ T8B, 0); - __ ushll(vb[2], __ T8H, vin[4], __ T8B, 0); - __ ushll(vb[4], __ T8H, vin[4], __ T8B, 0); - - // shift lo byte of copy 1 of the middle stripe into the high byte - __ shl(va[2], __ T8H, va[2], 8); - __ shl(va[3], __ T8H, va[3], 8); - __ shl(vb[2], __ T8H, vb[2], 8); - - // expand vin[2] into va[6:7] and lower 8 lanes of vin[5] into - // vb[6] pre-shifted by 4 to ensure top bits of the input 12-bit - // int are in bit positions [4..11]. - __ ushll(va[6], __ T8H, vin[2], __ T8B, 4); - __ ushll2(va[7], __ T8H, vin[2], __ T16B, 4); - __ ushll(vb[6], __ T8H, vin[5], __ T8B, 4); - - // mask hi 4 bits of each 1st 12-bit int in pair from copy1 and - // shift lo 4 bits of each 2nd 12-bit int in pair to bottom of - // copy2 - __ andr(va[2], __ T16B, va[2], v31); - __ andr(va[3], __ T16B, va[3], v31); - __ ushr(va[4], __ T8H, va[4], 4); - __ ushr(va[5], __ T8H, va[5], 4); - __ andr(vb[2], __ T16B, vb[2], v31); - __ ushr(vb[4], __ T8H, vb[4], 4); - - - - // sum hi 4 bits and lo 8 bits of each 1st 12-bit int in pair and - // hi 8 bits plus lo 4 bits of each 2nd 12-bit int in pair - - // n.b. ordering ensures: i) inputs are consumed before they are - // overwritten ii) order of 16-bit results across succsessive - // pairs of vectors in va and then lower half of vb reflects order - // of corresponding 12-bit inputs - __ addv(va[0], __ T8H, va[0], va[2]); - __ addv(va[2], __ T8H, va[1], va[3]); - __ addv(va[1], __ T8H, va[4], va[6]); - __ addv(va[3], __ T8H, va[5], va[7]); - __ addv(vb[0], __ T8H, vb[0], vb[2]); - __ addv(vb[1], __ T8H, vb[4], vb[6]); - - // store 48 results interleaved as shorts - vs_st2_post(vs_front(va), __ T8H, parsed); - vs_st2_post(vs_front(vs_front(vb)), __ T8H, parsed); - - __ BIND(L_end); + __ cmp(parsedLength, (u1)0); + __ br(Assembler::GT, L_loop); __ leave(); // required for proper stackwalking of RuntimeStub frame __ mov(r0, zr); // return 0 diff --git a/src/hotspot/cpu/aarch64/templateTable_aarch64.cpp b/src/hotspot/cpu/aarch64/templateTable_aarch64.cpp index cde142b39ac..5d4f7103a84 100644 --- a/src/hotspot/cpu/aarch64/templateTable_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/templateTable_aarch64.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2014, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -2290,7 +2290,8 @@ void TemplateTable::resolve_cache_and_index_for_method(int byte_no, __ subs(zr, temp, (int) code); // have we resolved this bytecode? // Class initialization barrier for static methods - if (VM_Version::supports_fast_class_init_checks() && bytecode() == Bytecodes::_invokestatic) { + if (bytecode() == Bytecodes::_invokestatic) { + assert(VM_Version::supports_fast_class_init_checks(), "sanity"); __ br(Assembler::NE, L_clinit_barrier_slow); __ ldr(temp, Address(Rcache, in_bytes(ResolvedMethodEntry::method_offset()))); __ load_method_holder(temp, temp); @@ -2340,8 +2341,8 @@ void TemplateTable::resolve_cache_and_index_for_field(int byte_no, __ subs(zr, temp, (int) code); // have we resolved this bytecode? // Class initialization barrier for static fields - if (VM_Version::supports_fast_class_init_checks() && - (bytecode() == Bytecodes::_getstatic || bytecode() == Bytecodes::_putstatic)) { + if (bytecode() == Bytecodes::_getstatic || bytecode() == Bytecodes::_putstatic) { + assert(VM_Version::supports_fast_class_init_checks(), "sanity"); const Register field_holder = temp; __ br(Assembler::NE, L_clinit_barrier_slow); @@ -3369,7 +3370,7 @@ void TemplateTable::invokevirtual_helper(Register index, __ load_klass(r0, recv); // profile this call - __ profile_virtual_call(r0, rlocals, r3); + __ profile_virtual_call(r0, rlocals); // get target Method & entry point __ lookup_virtual_method(r0, index, method); @@ -3499,7 +3500,7 @@ void TemplateTable::invokeinterface(int byte_no) { /*return_method=*/false); // profile this call - __ profile_virtual_call(r3, r13, r19); + __ profile_virtual_call(r3, r13); // Get declaring interface class from method, and itable index diff --git a/src/hotspot/cpu/arm/frame_arm.cpp b/src/hotspot/cpu/arm/frame_arm.cpp index 7a23296a3d4..f791fae7bd7 100644 --- a/src/hotspot/cpu/arm/frame_arm.cpp +++ b/src/hotspot/cpu/arm/frame_arm.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2026, 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 @@ -356,10 +356,10 @@ frame frame::sender_for_interpreter_frame(RegisterMap* map) const { bool frame::is_interpreted_frame_valid(JavaThread* thread) const { assert(is_interpreted_frame(), "Not an interpreted frame"); // These are reasonable sanity checks - if (fp() == 0 || (intptr_t(fp()) & (wordSize-1)) != 0) { + if (fp() == nullptr || (intptr_t(fp()) & (wordSize-1)) != 0) { return false; } - if (sp() == 0 || (intptr_t(sp()) & (wordSize-1)) != 0) { + if (sp() == nullptr || (intptr_t(sp()) & (wordSize-1)) != 0) { return false; } if (fp() + interpreter_frame_initial_sp_offset < sp()) { diff --git a/src/hotspot/cpu/arm/gc/shared/cardTableBarrierSetAssembler_arm.cpp b/src/hotspot/cpu/arm/gc/shared/cardTableBarrierSetAssembler_arm.cpp index 2427d46cafa..5d63035ac69 100644 --- a/src/hotspot/cpu/arm/gc/shared/cardTableBarrierSetAssembler_arm.cpp +++ b/src/hotspot/cpu/arm/gc/shared/cardTableBarrierSetAssembler_arm.cpp @@ -67,9 +67,7 @@ void CardTableBarrierSetAssembler::store_at(MacroAssembler* masm, DecoratorSet d void CardTableBarrierSetAssembler::gen_write_ref_array_post_barrier(MacroAssembler* masm, DecoratorSet decorators, Register addr, Register count, Register tmp) { BLOCK_COMMENT("CardTablePostBarrier"); - BarrierSet* bs = BarrierSet::barrier_set(); - CardTableBarrierSet* ctbs = barrier_set_cast(bs); - CardTable* ct = ctbs->card_table(); + CardTableBarrierSet* ctbs = CardTableBarrierSet::barrier_set(); Label L_cardtable_loop, L_done; @@ -83,7 +81,7 @@ void CardTableBarrierSetAssembler::gen_write_ref_array_post_barrier(MacroAssembl __ sub(count, count, addr); // nb of cards // warning: Rthread has not been preserved - __ mov_address(tmp, (address) ct->byte_map_base()); + __ mov_address(tmp, (address)ctbs->card_table_base_const()); __ add(addr,tmp, addr); Register zero = __ zero_register(tmp); @@ -122,8 +120,7 @@ void CardTableBarrierSetAssembler::store_check_part1(MacroAssembler* masm, Regis assert(bs->kind() == BarrierSet::CardTableBarrierSet, "Wrong barrier set kind"); - CardTableBarrierSet* ctbs = barrier_set_cast(bs); - CardTable* ct = ctbs->card_table(); + CardTableBarrierSet* ctbs = CardTableBarrierSet::barrier_set(); // Load card table base address. @@ -140,7 +137,7 @@ void CardTableBarrierSetAssembler::store_check_part1(MacroAssembler* masm, Regis Possible cause is a cache miss (card table base address resides in a rarely accessed area of thread descriptor). */ - __ mov_address(card_table_base, (address)ct->byte_map_base()); + __ mov_address(card_table_base, (address)ctbs->card_table_base_const()); } // The 2nd part of the store check. @@ -170,8 +167,8 @@ void CardTableBarrierSetAssembler::store_check_part2(MacroAssembler* masm, Regis void CardTableBarrierSetAssembler::set_card(MacroAssembler* masm, Register card_table_base, Address card_table_addr, Register tmp) { CardTableBarrierSet* ctbs = barrier_set_cast(BarrierSet::barrier_set()); - CardTable* ct = ctbs->card_table(); - if ((((uintptr_t)ct->byte_map_base() & 0xff) == 0)) { + + if ((((uintptr_t)ctbs->card_table_base_const() & 0xff) == 0)) { // Card table is aligned so the lowest byte of the table address base is zero. // This works only if the code is not saved for later use, possibly // in a context where the base would no longer be aligned. diff --git a/src/hotspot/cpu/arm/nativeInst_arm_32.cpp b/src/hotspot/cpu/arm/nativeInst_arm_32.cpp index 232294b246a..df780ac31a6 100644 --- a/src/hotspot/cpu/arm/nativeInst_arm_32.cpp +++ b/src/hotspot/cpu/arm/nativeInst_arm_32.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2026, 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 @@ -172,7 +172,7 @@ void NativeMovConstReg::set_data(intptr_t x, address pc) { address addr = oop_addr != nullptr ? (address)oop_addr : (address)metadata_addr; - if(pc == 0) { + if (pc == nullptr) { offset = addr - instruction_address() - 8; } else { offset = addr - pc - 8; @@ -228,7 +228,7 @@ void NativeMovConstReg::set_data(intptr_t x, address pc) { void NativeMovConstReg::set_pc_relative_offset(address addr, address pc) { int offset; - if (pc == 0) { + if (pc == nullptr) { offset = addr - instruction_address() - 8; } else { offset = addr - pc - 8; diff --git a/src/hotspot/cpu/arm/nativeInst_arm_32.hpp b/src/hotspot/cpu/arm/nativeInst_arm_32.hpp index 82385bf0244..2b52db89285 100644 --- a/src/hotspot/cpu/arm/nativeInst_arm_32.hpp +++ b/src/hotspot/cpu/arm/nativeInst_arm_32.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2026, 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 @@ -371,7 +371,7 @@ class NativeMovConstReg: public NativeInstruction { public: intptr_t data() const; - void set_data(intptr_t x, address pc = 0); + void set_data(intptr_t x, address pc = nullptr); bool is_pc_relative() { return !is_movw(); } diff --git a/src/hotspot/cpu/ppc/gc/shared/cardTableBarrierSetAssembler_ppc.cpp b/src/hotspot/cpu/ppc/gc/shared/cardTableBarrierSetAssembler_ppc.cpp index 7404f7e2e5c..297ce57a394 100644 --- a/src/hotspot/cpu/ppc/gc/shared/cardTableBarrierSetAssembler_ppc.cpp +++ b/src/hotspot/cpu/ppc/gc/shared/cardTableBarrierSetAssembler_ppc.cpp @@ -103,8 +103,7 @@ void CardTableBarrierSetAssembler::resolve_jobject(MacroAssembler* masm, Registe void CardTableBarrierSetAssembler::gen_write_ref_array_post_barrier(MacroAssembler* masm, DecoratorSet decorators, Register addr, Register count, Register preserve) { - CardTableBarrierSet* ctbs = barrier_set_cast(BarrierSet::barrier_set()); - CardTable* ct = ctbs->card_table(); + CardTableBarrierSet* ctbs = CardTableBarrierSet::barrier_set(); assert_different_registers(addr, count, R0); Label Lskip_loop, Lstore_loop; @@ -117,7 +116,7 @@ void CardTableBarrierSetAssembler::gen_write_ref_array_post_barrier(MacroAssembl __ srdi(addr, addr, CardTable::card_shift()); __ srdi(count, count, CardTable::card_shift()); __ subf(count, addr, count); - __ add_const_optimized(addr, addr, (address)ct->byte_map_base(), R0); + __ add_const_optimized(addr, addr, (address)ctbs->card_table_base_const(), R0); __ addi(count, count, 1); __ li(R0, 0); __ mtctr(count); @@ -140,8 +139,8 @@ void CardTableBarrierSetAssembler::card_table_write(MacroAssembler* masm, } void CardTableBarrierSetAssembler::card_write_barrier_post(MacroAssembler* masm, Register store_addr, Register tmp) { - CardTableBarrierSet* bs = barrier_set_cast(BarrierSet::barrier_set()); - card_table_write(masm, bs->card_table()->byte_map_base(), tmp, store_addr); + CardTableBarrierSet* bs = CardTableBarrierSet::barrier_set(); + card_table_write(masm, bs->card_table_base_const(), tmp, store_addr); } void CardTableBarrierSetAssembler::oop_store_at(MacroAssembler* masm, DecoratorSet decorators, BasicType type, diff --git a/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.cpp b/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.cpp index 1f1bc7622ed..9d143c14d27 100644 --- a/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.cpp +++ b/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.cpp @@ -771,9 +771,6 @@ void ShenandoahBarrierSetAssembler::cmpxchg_oop(MacroAssembler *masm, Register b void ShenandoahBarrierSetAssembler::gen_write_ref_array_post_barrier(MacroAssembler* masm, DecoratorSet decorators, Register addr, Register count, Register preserve) { assert(ShenandoahCardBarrier, "Should have been checked by caller"); - - ShenandoahBarrierSet* bs = ShenandoahBarrierSet::barrier_set(); - CardTable* ct = bs->card_table(); assert_different_registers(addr, count, R0); Label L_skip_loop, L_store_loop; diff --git a/src/hotspot/cpu/ppc/interp_masm_ppc_64.cpp b/src/hotspot/cpu/ppc/interp_masm_ppc_64.cpp index fc865be015e..f7bf457f72c 100644 --- a/src/hotspot/cpu/ppc/interp_masm_ppc_64.cpp +++ b/src/hotspot/cpu/ppc/interp_masm_ppc_64.cpp @@ -1109,11 +1109,11 @@ void InterpreterMacroAssembler::verify_method_data_pointer() { lhz(R11_scratch1, in_bytes(DataLayout::bci_offset()), R28_mdx); ld(R12_scratch2, in_bytes(Method::const_offset()), R19_method); addi(R11_scratch1, R11_scratch1, in_bytes(ConstMethod::codes_offset())); - add(R11_scratch1, R12_scratch2, R12_scratch2); + add(R11_scratch1, R11_scratch1, R12_scratch2); cmpd(CR0, R11_scratch1, R14_bcp); beq(CR0, verify_continue); - call_VM_leaf(CAST_FROM_FN_PTR(address, InterpreterRuntime::verify_mdp ), R19_method, R14_bcp, R28_mdx); + call_VM_leaf(CAST_FROM_FN_PTR(address, InterpreterRuntime::verify_mdp), R19_method, R14_bcp, R28_mdx); bind(verify_continue); #endif diff --git a/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp b/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp index 5649ead2ea8..809285afddb 100644 --- a/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp +++ b/src/hotspot/cpu/ppc/macroAssembler_ppc.cpp @@ -4535,7 +4535,7 @@ void MacroAssembler::push_cont_fastpath() { Label done; ld_ptr(R0, JavaThread::cont_fastpath_offset(), R16_thread); cmpld(CR0, R1_SP, R0); - ble(CR0, done); + ble(CR0, done); // if (SP <= _cont_fastpath) goto done; st_ptr(R1_SP, JavaThread::cont_fastpath_offset(), R16_thread); bind(done); } @@ -4546,7 +4546,7 @@ void MacroAssembler::pop_cont_fastpath() { Label done; ld_ptr(R0, JavaThread::cont_fastpath_offset(), R16_thread); cmpld(CR0, R1_SP, R0); - ble(CR0, done); + blt(CR0, done); // if (SP < _cont_fastpath) goto done; li(R0, 0); st_ptr(R0, JavaThread::cont_fastpath_offset(), R16_thread); bind(done); diff --git a/src/hotspot/cpu/ppc/sharedRuntime_ppc.cpp b/src/hotspot/cpu/ppc/sharedRuntime_ppc.cpp index 4e427ace404..4eb2028f529 100644 --- a/src/hotspot/cpu/ppc/sharedRuntime_ppc.cpp +++ b/src/hotspot/cpu/ppc/sharedRuntime_ppc.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2012, 2025 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -1237,26 +1237,24 @@ void SharedRuntime::generate_i2c2i_adapters(MacroAssembler *masm, // Class initialization barrier for static methods entry_address[AdapterBlob::C2I_No_Clinit_Check] = nullptr; - if (VM_Version::supports_fast_class_init_checks()) { - Label L_skip_barrier; + assert(VM_Version::supports_fast_class_init_checks(), "sanity"); + Label L_skip_barrier; - { // Bypass the barrier for non-static methods - __ lhz(R0, in_bytes(Method::access_flags_offset()), R19_method); - __ andi_(R0, R0, JVM_ACC_STATIC); - __ beq(CR0, L_skip_barrier); // non-static - } + // Bypass the barrier for non-static methods + __ lhz(R0, in_bytes(Method::access_flags_offset()), R19_method); + __ andi_(R0, R0, JVM_ACC_STATIC); + __ beq(CR0, L_skip_barrier); // non-static - Register klass = R11_scratch1; - __ load_method_holder(klass, R19_method); - __ clinit_barrier(klass, R16_thread, &L_skip_barrier /*L_fast_path*/); + Register klass = R11_scratch1; + __ load_method_holder(klass, R19_method); + __ clinit_barrier(klass, R16_thread, &L_skip_barrier /*L_fast_path*/); - __ load_const_optimized(klass, SharedRuntime::get_handle_wrong_method_stub(), R0); - __ mtctr(klass); - __ bctr(); + __ load_const_optimized(klass, SharedRuntime::get_handle_wrong_method_stub(), R0); + __ mtctr(klass); + __ bctr(); - __ bind(L_skip_barrier); - entry_address[AdapterBlob::C2I_No_Clinit_Check] = __ pc(); - } + __ bind(L_skip_barrier); + entry_address[AdapterBlob::C2I_No_Clinit_Check] = __ pc(); BarrierSetAssembler* bs = BarrierSet::barrier_set()->barrier_set_assembler(); bs->c2i_entry_barrier(masm, /* tmp register*/ ic_klass, /* tmp register*/ receiver_klass, /* tmp register*/ code); @@ -2210,7 +2208,8 @@ nmethod *SharedRuntime::generate_native_wrapper(MacroAssembler *masm, // -------------------------------------------------------------------------- vep_start_pc = (intptr_t)__ pc(); - if (VM_Version::supports_fast_class_init_checks() && method->needs_clinit_barrier()) { + if (method->needs_clinit_barrier()) { + assert(VM_Version::supports_fast_class_init_checks(), "sanity"); Label L_skip_barrier; Register klass = r_temp_1; // Notify OOP recorder (don't need the relocation) diff --git a/src/hotspot/cpu/ppc/templateTable_ppc_64.cpp b/src/hotspot/cpu/ppc/templateTable_ppc_64.cpp index 8d61ba1b2d7..8a3af748fa1 100644 --- a/src/hotspot/cpu/ppc/templateTable_ppc_64.cpp +++ b/src/hotspot/cpu/ppc/templateTable_ppc_64.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2013, 2025 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -2199,7 +2199,8 @@ void TemplateTable::resolve_cache_and_index_for_method(int byte_no, Register Rca __ isync(); // Order load wrt. succeeding loads. // Class initialization barrier for static methods - if (VM_Version::supports_fast_class_init_checks() && bytecode() == Bytecodes::_invokestatic) { + if (bytecode() == Bytecodes::_invokestatic) { + assert(VM_Version::supports_fast_class_init_checks(), "sanity"); const Register method = Rscratch; const Register klass = Rscratch; @@ -2244,8 +2245,8 @@ void TemplateTable::resolve_cache_and_index_for_field(int byte_no, Register Rcac __ isync(); // Order load wrt. succeeding loads. // Class initialization barrier for static fields - if (VM_Version::supports_fast_class_init_checks() && - (bytecode() == Bytecodes::_getstatic || bytecode() == Bytecodes::_putstatic)) { + if (bytecode() == Bytecodes::_getstatic || bytecode() == Bytecodes::_putstatic) { + assert(VM_Version::supports_fast_class_init_checks(), "sanity"); const Register field_holder = R4_ARG2; // InterpreterRuntime::resolve_get_put sets field_holder and finally release-stores put_code. diff --git a/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp b/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp index dcf20752a21..824ea872935 100644 --- a/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp +++ b/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp @@ -2813,14 +2813,10 @@ void C2_MacroAssembler::char_array_compress_v(Register src, Register dst, Regist // Intrinsic for // -// - sun.nio.cs.ISO_8859_1.Encoder#encodeISOArray0(byte[] sa, int sp, byte[] da, int dp, int len) -// Encodes char[] to byte[] in ISO-8859-1 -// -// - java.lang.StringCoding#encodeISOArray0(byte[] sa, int sp, byte[] da, int dp, int len) -// Encodes byte[] (containing UTF-16) to byte[] in ISO-8859-1 -// -// - java.lang.StringCoding#encodeAsciiArray0(char[] sa, int sp, byte[] da, int dp, int len) -// Encodes char[] to byte[] in ASCII +// - sun/nio/cs/ISO_8859_1$Encoder.implEncodeISOArray +// return the number of characters copied. +// - java/lang/StringUTF16.compress +// return index of non-latin1 character if copy fails, otherwise 'len'. // // This version always returns the number of characters copied. A successful // copy will complete with the post-condition: 'res' == 'len', while an diff --git a/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp b/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp index 43b17a13c20..fb30f64e9ed 100644 --- a/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp +++ b/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp @@ -5110,9 +5110,8 @@ void MacroAssembler::get_thread(Register thread) { } void MacroAssembler::load_byte_map_base(Register reg) { - CardTable::CardValue* byte_map_base = - ((CardTableBarrierSet*)(BarrierSet::barrier_set()))->card_table()->byte_map_base(); - mv(reg, (uint64_t)byte_map_base); + CardTableBarrierSet* ctbs = CardTableBarrierSet::barrier_set(); + mv(reg, (uint64_t)ctbs->card_table_base_const()); } void MacroAssembler::build_frame(int framesize) { diff --git a/src/hotspot/cpu/riscv/riscv_v.ad b/src/hotspot/cpu/riscv/riscv_v.ad index d162280106a..aeb8398f06f 100644 --- a/src/hotspot/cpu/riscv/riscv_v.ad +++ b/src/hotspot/cpu/riscv/riscv_v.ad @@ -114,12 +114,12 @@ source %{ case Op_VectorCastHF2F: case Op_VectorCastF2HF: case Op_AddVHF: + case Op_SubVHF: + case Op_MulVHF: case Op_DivVHF: case Op_MaxVHF: case Op_MinVHF: - case Op_MulVHF: case Op_SqrtVHF: - case Op_SubVHF: return UseZvfh; case Op_FmaVHF: return UseZvfh && UseFMA; @@ -147,13 +147,28 @@ source %{ if (!UseRVV) { return false; } + switch (opcode) { case Op_SelectFromTwoVector: // There is no masked version of selectFrom two vector, i.e. selectFrom(av, bv, mask) in vector API. return false; + // Currently, the masked versions of the following 8 Float16 operations are disabled. + // When the support for Float16 vector classes is added in VectorAPI and the masked + // Float16 IR can be generated, these masked operations will be enabled and relevant + // backend support added. + case Op_AddVHF: + case Op_SubVHF: + case Op_MulVHF: + case Op_DivVHF: + case Op_MaxVHF: + case Op_MinVHF: + case Op_SqrtVHF: + case Op_FmaVHF: + return false; default: break; } + return match_rule_supported_vector(opcode, vlen, bt); } diff --git a/src/hotspot/cpu/riscv/sharedRuntime_riscv.cpp b/src/hotspot/cpu/riscv/sharedRuntime_riscv.cpp index eeb6fad1b59..8c343f6ab2b 100644 --- a/src/hotspot/cpu/riscv/sharedRuntime_riscv.cpp +++ b/src/hotspot/cpu/riscv/sharedRuntime_riscv.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2014, 2020, Red Hat Inc. All rights reserved. * Copyright (c) 2020, 2023, Huawei Technologies Co., Ltd. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. @@ -213,7 +213,7 @@ void RegisterSaver::restore_live_registers(MacroAssembler* masm) { // Is vector's size (in bytes) bigger than a size saved by default? // riscv does not ovlerlay the floating-point registers on vector registers like aarch64. bool SharedRuntime::is_wide_vector(int size) { - return UseRVV; + return UseRVV && size > 0; } // --------------------------------------------------------------------------- @@ -637,22 +637,20 @@ void SharedRuntime::generate_i2c2i_adapters(MacroAssembler *masm, // Class initialization barrier for static methods entry_address[AdapterBlob::C2I_No_Clinit_Check] = nullptr; - if (VM_Version::supports_fast_class_init_checks()) { - Label L_skip_barrier; + assert(VM_Version::supports_fast_class_init_checks(), "sanity"); + Label L_skip_barrier; - { // Bypass the barrier for non-static methods - __ load_unsigned_short(t0, Address(xmethod, Method::access_flags_offset())); - __ test_bit(t1, t0, exact_log2(JVM_ACC_STATIC)); - __ beqz(t1, L_skip_barrier); // non-static - } + // Bypass the barrier for non-static methods + __ load_unsigned_short(t0, Address(xmethod, Method::access_flags_offset())); + __ test_bit(t1, t0, exact_log2(JVM_ACC_STATIC)); + __ beqz(t1, L_skip_barrier); // non-static - __ load_method_holder(t1, xmethod); - __ clinit_barrier(t1, t0, &L_skip_barrier); - __ far_jump(RuntimeAddress(SharedRuntime::get_handle_wrong_method_stub())); + __ load_method_holder(t1, xmethod); + __ clinit_barrier(t1, t0, &L_skip_barrier); + __ far_jump(RuntimeAddress(SharedRuntime::get_handle_wrong_method_stub())); - __ bind(L_skip_barrier); - entry_address[AdapterBlob::C2I_No_Clinit_Check] = __ pc(); - } + __ bind(L_skip_barrier); + entry_address[AdapterBlob::C2I_No_Clinit_Check] = __ pc(); BarrierSetAssembler* bs = BarrierSet::barrier_set()->barrier_set_assembler(); bs->c2i_entry_barrier(masm); @@ -1443,7 +1441,8 @@ nmethod* SharedRuntime::generate_native_wrapper(MacroAssembler* masm, __ nop(); // 4 bytes } - if (VM_Version::supports_fast_class_init_checks() && method->needs_clinit_barrier()) { + if (method->needs_clinit_barrier()) { + assert(VM_Version::supports_fast_class_init_checks(), "sanity"); Label L_skip_barrier; __ mov_metadata(t1, method->method_holder()); // InstanceKlass* __ clinit_barrier(t1, t0, &L_skip_barrier); diff --git a/src/hotspot/cpu/riscv/templateTable_riscv.cpp b/src/hotspot/cpu/riscv/templateTable_riscv.cpp index ca41583e4bc..5a3644f70bb 100644 --- a/src/hotspot/cpu/riscv/templateTable_riscv.cpp +++ b/src/hotspot/cpu/riscv/templateTable_riscv.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2014, Red Hat Inc. All rights reserved. * Copyright (c) 2020, 2023, Huawei Technologies Co., Ltd. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. @@ -2192,7 +2192,8 @@ void TemplateTable::resolve_cache_and_index_for_method(int byte_no, __ mv(t0, (int) code); // Class initialization barrier for static methods - if (VM_Version::supports_fast_class_init_checks() && bytecode() == Bytecodes::_invokestatic) { + if (bytecode() == Bytecodes::_invokestatic) { + assert(VM_Version::supports_fast_class_init_checks(), "sanity"); __ bne(temp, t0, L_clinit_barrier_slow); // have we resolved this bytecode? __ ld(temp, Address(Rcache, in_bytes(ResolvedMethodEntry::method_offset()))); __ load_method_holder(temp, temp); @@ -2243,8 +2244,8 @@ void TemplateTable::resolve_cache_and_index_for_field(int byte_no, __ mv(t0, (int) code); // have we resolved this bytecode? // Class initialization barrier for static fields - if (VM_Version::supports_fast_class_init_checks() && - (bytecode() == Bytecodes::_getstatic || bytecode() == Bytecodes::_putstatic)) { + if (bytecode() == Bytecodes::_getstatic || bytecode() == Bytecodes::_putstatic) { + assert(VM_Version::supports_fast_class_init_checks(), "sanity"); const Register field_holder = temp; __ bne(temp, t0, L_clinit_barrier_slow); diff --git a/src/hotspot/cpu/riscv/vm_version_riscv.cpp b/src/hotspot/cpu/riscv/vm_version_riscv.cpp index 22f19c4f5ea..36f0864da0b 100644 --- a/src/hotspot/cpu/riscv/vm_version_riscv.cpp +++ b/src/hotspot/cpu/riscv/vm_version_riscv.cpp @@ -167,11 +167,6 @@ void VM_Version::common_initialize() { (unaligned_scalar.value() == MISALIGNED_SCALAR_FAST)); } - if (FLAG_IS_DEFAULT(AlignVector)) { - FLAG_SET_DEFAULT(AlignVector, - unaligned_vector.value() != MISALIGNED_VECTOR_FAST); - } - #ifdef __riscv_ztso // Hotspot is compiled with TSO support, it will only run on hardware which // supports Ztso @@ -242,6 +237,11 @@ void VM_Version::c2_initialize() { } } + if (FLAG_IS_DEFAULT(AlignVector)) { + FLAG_SET_DEFAULT(AlignVector, + unaligned_vector.value() != MISALIGNED_VECTOR_FAST); + } + // NOTE: Make sure codes dependent on UseRVV are put after MaxVectorSize initialize, // as there are extra checks inside it which could disable UseRVV // in some situations. diff --git a/src/hotspot/cpu/s390/gc/shared/cardTableBarrierSetAssembler_s390.cpp b/src/hotspot/cpu/s390/gc/shared/cardTableBarrierSetAssembler_s390.cpp index a0da6ebe682..9bb7f63ff31 100644 --- a/src/hotspot/cpu/s390/gc/shared/cardTableBarrierSetAssembler_s390.cpp +++ b/src/hotspot/cpu/s390/gc/shared/cardTableBarrierSetAssembler_s390.cpp @@ -83,8 +83,7 @@ void CardTableBarrierSetAssembler::resolve_jobject(MacroAssembler* masm, Registe void CardTableBarrierSetAssembler::gen_write_ref_array_post_barrier(MacroAssembler* masm, DecoratorSet decorators, Register addr, Register count, bool do_return) { - CardTableBarrierSet* ctbs = barrier_set_cast(BarrierSet::barrier_set()); - CardTable* ct = ctbs->card_table(); + CardTableBarrierSet* ctbs = CardTableBarrierSet::barrier_set(); NearLabel doXC, done; assert_different_registers(Z_R0, Z_R1, addr, count); @@ -105,7 +104,7 @@ void CardTableBarrierSetAssembler::gen_write_ref_array_post_barrier(MacroAssembl __ add2reg_with_index(count, -BytesPerHeapOop, count, addr); // Get base address of card table. - __ load_const_optimized(Z_R1, (address)ct->byte_map_base()); + __ load_const_optimized(Z_R1, (address)ctbs->card_table_base_const()); // count = (count>>shift) - (addr>>shift) __ z_srlg(addr, addr, CardTable::card_shift()); @@ -179,13 +178,12 @@ void CardTableBarrierSetAssembler::gen_write_ref_array_post_barrier(MacroAssembl void CardTableBarrierSetAssembler::store_check(MacroAssembler* masm, Register store_addr, Register tmp) { // Does a store check for the oop in register obj. The content of // register obj is destroyed afterwards. - CardTableBarrierSet* ctbs = barrier_set_cast(BarrierSet::barrier_set()); - CardTable* ct = ctbs->card_table(); + CardTableBarrierSet* ctbs = CardTableBarrierSet::barrier_set(); assert_different_registers(store_addr, tmp); __ z_srlg(store_addr, store_addr, CardTable::card_shift()); - __ load_absolute_address(tmp, (address)ct->byte_map_base()); + __ load_absolute_address(tmp, (address)ctbs->card_table_base_const()); __ z_agr(store_addr, tmp); __ z_mvi(0, store_addr, CardTable::dirty_card_val()); } diff --git a/src/hotspot/cpu/s390/sharedRuntime_s390.cpp b/src/hotspot/cpu/s390/sharedRuntime_s390.cpp index 5b6f7dcd984..00a830a80cd 100644 --- a/src/hotspot/cpu/s390/sharedRuntime_s390.cpp +++ b/src/hotspot/cpu/s390/sharedRuntime_s390.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016, 2024 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -1567,7 +1567,8 @@ nmethod *SharedRuntime::generate_native_wrapper(MacroAssembler *masm, //--------------------------------------------------------------------- wrapper_VEPStart = __ offset(); - if (VM_Version::supports_fast_class_init_checks() && method->needs_clinit_barrier()) { + if (method->needs_clinit_barrier()) { + assert(VM_Version::supports_fast_class_init_checks(), "sanity"); Label L_skip_barrier; Register klass = Z_R1_scratch; // Notify OOP recorder (don't need the relocation) @@ -2378,24 +2379,22 @@ void SharedRuntime::generate_i2c2i_adapters(MacroAssembler *masm, // Class initialization barrier for static methods entry_address[AdapterBlob::C2I_No_Clinit_Check] = nullptr; - if (VM_Version::supports_fast_class_init_checks()) { - Label L_skip_barrier; + assert(VM_Version::supports_fast_class_init_checks(), "sanity"); + Label L_skip_barrier; - { // Bypass the barrier for non-static methods - __ testbit_ushort(Address(Z_method, Method::access_flags_offset()), JVM_ACC_STATIC_BIT); - __ z_bfalse(L_skip_barrier); // non-static - } + // Bypass the barrier for non-static methods + __ testbit_ushort(Address(Z_method, Method::access_flags_offset()), JVM_ACC_STATIC_BIT); + __ z_bfalse(L_skip_barrier); // non-static - Register klass = Z_R11; - __ load_method_holder(klass, Z_method); - __ clinit_barrier(klass, Z_thread, &L_skip_barrier /*L_fast_path*/); + Register klass = Z_R11; + __ load_method_holder(klass, Z_method); + __ clinit_barrier(klass, Z_thread, &L_skip_barrier /*L_fast_path*/); - __ load_const_optimized(klass, SharedRuntime::get_handle_wrong_method_stub()); - __ z_br(klass); + __ load_const_optimized(klass, SharedRuntime::get_handle_wrong_method_stub()); + __ z_br(klass); - __ bind(L_skip_barrier); - entry_address[AdapterBlob::C2I_No_Clinit_Check] = __ pc(); - } + __ bind(L_skip_barrier); + entry_address[AdapterBlob::C2I_No_Clinit_Check] = __ pc(); gen_c2i_adapter(masm, total_args_passed, comp_args_on_stack, sig_bt, regs, skip_fixup); return; diff --git a/src/hotspot/cpu/s390/templateTable_s390.cpp b/src/hotspot/cpu/s390/templateTable_s390.cpp index 4e8fdf275e4..647915ef4fa 100644 --- a/src/hotspot/cpu/s390/templateTable_s390.cpp +++ b/src/hotspot/cpu/s390/templateTable_s390.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016, 2024 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -2377,7 +2377,8 @@ void TemplateTable::resolve_cache_and_index_for_method(int byte_no, __ z_cli(Address(Rcache, bc_offset), code); // Class initialization barrier for static methods - if (VM_Version::supports_fast_class_init_checks() && bytecode() == Bytecodes::_invokestatic) { + if (bytecode() == Bytecodes::_invokestatic) { + assert(VM_Version::supports_fast_class_init_checks(), "sanity"); const Register method = Z_R1_scratch; const Register klass = Z_R1_scratch; __ z_brne(L_clinit_barrier_slow); @@ -2427,8 +2428,8 @@ void TemplateTable::resolve_cache_and_index_for_field(int byte_no, __ z_cli(Address(cache, code_offset), code); // Class initialization barrier for static fields - if (VM_Version::supports_fast_class_init_checks() && - (bytecode() == Bytecodes::_getstatic || bytecode() == Bytecodes::_putstatic)) { + if (bytecode() == Bytecodes::_getstatic || bytecode() == Bytecodes::_putstatic) { + assert(VM_Version::supports_fast_class_init_checks(), "sanity"); const Register field_holder = index; __ z_brne(L_clinit_barrier_slow); diff --git a/src/hotspot/cpu/x86/c1_LIRAssembler_x86.cpp b/src/hotspot/cpu/x86/c1_LIRAssembler_x86.cpp index a2ea7af606d..37ee9451405 100644 --- a/src/hotspot/cpu/x86/c1_LIRAssembler_x86.cpp +++ b/src/hotspot/cpu/x86/c1_LIRAssembler_x86.cpp @@ -1261,29 +1261,9 @@ void LIR_Assembler::emit_alloc_array(LIR_OpAllocArray* op) { void LIR_Assembler::type_profile_helper(Register mdo, ciMethodData *md, ciProfileData *data, - Register recv, Label* update_done) { - for (uint i = 0; i < ReceiverTypeData::row_limit(); i++) { - Label next_test; - // See if the receiver is receiver[n]. - __ cmpptr(recv, Address(mdo, md->byte_offset_of_slot(data, ReceiverTypeData::receiver_offset(i)))); - __ jccb(Assembler::notEqual, next_test); - Address data_addr(mdo, md->byte_offset_of_slot(data, ReceiverTypeData::receiver_count_offset(i))); - __ addptr(data_addr, DataLayout::counter_increment); - __ jmp(*update_done); - __ bind(next_test); - } - - // Didn't find receiver; find next empty slot and fill it in - for (uint i = 0; i < ReceiverTypeData::row_limit(); i++) { - Label next_test; - Address recv_addr(mdo, md->byte_offset_of_slot(data, ReceiverTypeData::receiver_offset(i))); - __ cmpptr(recv_addr, NULL_WORD); - __ jccb(Assembler::notEqual, next_test); - __ movptr(recv_addr, recv); - __ movptr(Address(mdo, md->byte_offset_of_slot(data, ReceiverTypeData::receiver_count_offset(i))), DataLayout::counter_increment); - __ jmp(*update_done); - __ bind(next_test); - } + Register recv) { + int mdp_offset = md->byte_offset_of_slot(data, in_ByteSize(0)); + __ profile_receiver_type(recv, mdo, mdp_offset); } void LIR_Assembler::emit_typecheck_helper(LIR_OpTypeCheck *op, Label* success, Label* failure, Label* obj_is_null) { @@ -1341,15 +1321,9 @@ void LIR_Assembler::emit_typecheck_helper(LIR_OpTypeCheck *op, Label* success, L __ jmp(*obj_is_null); __ bind(not_null); - Label update_done; Register recv = k_RInfo; __ load_klass(recv, obj, tmp_load_klass); - type_profile_helper(mdo, md, data, recv, &update_done); - - Address nonprofiled_receiver_count_addr(mdo, md->byte_offset_of_slot(data, CounterData::count_offset())); - __ addptr(nonprofiled_receiver_count_addr, DataLayout::counter_increment); - - __ bind(update_done); + type_profile_helper(mdo, md, data, recv); } else { __ jcc(Assembler::equal, *obj_is_null); } @@ -1461,14 +1435,9 @@ void LIR_Assembler::emit_opTypeCheck(LIR_OpTypeCheck* op) { __ jmp(done); __ bind(not_null); - Label update_done; Register recv = k_RInfo; __ load_klass(recv, value, tmp_load_klass); - type_profile_helper(mdo, md, data, recv, &update_done); - - Address counter_addr(mdo, md->byte_offset_of_slot(data, CounterData::count_offset())); - __ addptr(counter_addr, DataLayout::counter_increment); - __ bind(update_done); + type_profile_helper(mdo, md, data, recv); } else { __ jcc(Assembler::equal, done); } @@ -2791,13 +2760,9 @@ void LIR_Assembler::emit_profile_call(LIR_OpProfileCall* op) { if (C1OptimizeVirtualCallProfiling && known_klass != nullptr) { // We know the type that will be seen at this call site; we can // statically update the MethodData* rather than needing to do - // dynamic tests on the receiver type - - // NOTE: we should probably put a lock around this search to - // avoid collisions by concurrent compilations + // dynamic tests on the receiver type. ciVirtualCallData* vc_data = (ciVirtualCallData*) data; - uint i; - for (i = 0; i < VirtualCallData::row_limit(); i++) { + for (uint i = 0; i < VirtualCallData::row_limit(); i++) { ciKlass* receiver = vc_data->receiver(i); if (known_klass->equals(receiver)) { Address data_addr(mdo, md->byte_offset_of_slot(data, VirtualCallData::receiver_count_offset(i))); @@ -2805,32 +2770,13 @@ void LIR_Assembler::emit_profile_call(LIR_OpProfileCall* op) { return; } } - - // Receiver type not found in profile data; select an empty slot - - // Note that this is less efficient than it should be because it - // always does a write to the receiver part of the - // VirtualCallData rather than just the first time - for (i = 0; i < VirtualCallData::row_limit(); i++) { - ciKlass* receiver = vc_data->receiver(i); - if (receiver == nullptr) { - Address recv_addr(mdo, md->byte_offset_of_slot(data, VirtualCallData::receiver_offset(i))); - __ mov_metadata(recv_addr, known_klass->constant_encoding(), rscratch1); - Address data_addr(mdo, md->byte_offset_of_slot(data, VirtualCallData::receiver_count_offset(i))); - __ addptr(data_addr, DataLayout::counter_increment); - return; - } - } + // Receiver type is not found in profile data. + // Fall back to runtime helper to handle the rest at runtime. + __ mov_metadata(recv, known_klass->constant_encoding()); } else { __ load_klass(recv, recv, tmp_load_klass); - Label update_done; - type_profile_helper(mdo, md, data, recv, &update_done); - // Receiver did not match any saved receiver and there is no empty row for it. - // Increment total counter to indicate polymorphic case. - __ addptr(counter_addr, DataLayout::counter_increment); - - __ bind(update_done); } + type_profile_helper(mdo, md, data, recv); } else { // Static call __ addptr(counter_addr, DataLayout::counter_increment); diff --git a/src/hotspot/cpu/x86/c1_LIRAssembler_x86.hpp b/src/hotspot/cpu/x86/c1_LIRAssembler_x86.hpp index 7786c44b68f..c4a368b54d8 100644 --- a/src/hotspot/cpu/x86/c1_LIRAssembler_x86.hpp +++ b/src/hotspot/cpu/x86/c1_LIRAssembler_x86.hpp @@ -43,7 +43,7 @@ // Record the type of the receiver in ReceiverTypeData void type_profile_helper(Register mdo, ciMethodData *md, ciProfileData *data, - Register recv, Label* update_done); + Register recv); enum { _call_stub_size = 28, diff --git a/src/hotspot/cpu/x86/c2_stubGenerator_x86_64_string.cpp b/src/hotspot/cpu/x86/c2_stubGenerator_x86_64_string.cpp index 0951bda0d17..77a149addb5 100644 --- a/src/hotspot/cpu/x86/c2_stubGenerator_x86_64_string.cpp +++ b/src/hotspot/cpu/x86/c2_stubGenerator_x86_64_string.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, 2025, Intel Corporation. All rights reserved. + * Copyright (c) 2024, 2026, Intel Corporation. All rights reserved. * * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -1330,10 +1330,12 @@ static void big_case_loop_helper(bool sizeKnown, int size, Label &noMatch, Label // Clarification: The BYTE_K compare above compares haystack[(n-32):(n-1)]. We need to // compare haystack[(k-1):(k-1+31)]. Subtracting either index gives shift value of // (k + 31 - n): x = (k-1+31)-(n-1) = k-1+31-n+1 = k+31-n. + // When isU is set, similarly, shift is from haystack[(n-32):(n-1)] to [(k-2):(k-2+31)] + if (sizeKnown) { - __ movl(temp2, 31 + size); + __ movl(temp2, (isU ? 30 : 31) + size); } else { - __ movl(temp2, 31); + __ movl(temp2, isU ? 30 : 31); __ addl(temp2, needleLen); } __ subl(temp2, hsLength); diff --git a/src/hotspot/cpu/x86/gc/shared/cardTableBarrierSetAssembler_x86.cpp b/src/hotspot/cpu/x86/gc/shared/cardTableBarrierSetAssembler_x86.cpp index 2b91662ddb5..65e6b4e01fc 100644 --- a/src/hotspot/cpu/x86/gc/shared/cardTableBarrierSetAssembler_x86.cpp +++ b/src/hotspot/cpu/x86/gc/shared/cardTableBarrierSetAssembler_x86.cpp @@ -95,11 +95,7 @@ void CardTableBarrierSetAssembler::store_at(MacroAssembler* masm, DecoratorSet d void CardTableBarrierSetAssembler::gen_write_ref_array_post_barrier(MacroAssembler* masm, DecoratorSet decorators, Register addr, Register count, Register tmp) { - BarrierSet *bs = BarrierSet::barrier_set(); - CardTableBarrierSet* ctbs = barrier_set_cast(bs); - CardTable* ct = ctbs->card_table(); - intptr_t disp = (intptr_t) ct->byte_map_base(); - SHENANDOAHGC_ONLY(assert(!UseShenandoahGC, "Shenandoah byte_map_base is not constant.");) + CardTableBarrierSet* ctbs = CardTableBarrierSet::barrier_set(); Label L_loop, L_done; const Register end = count; @@ -115,7 +111,7 @@ void CardTableBarrierSetAssembler::gen_write_ref_array_post_barrier(MacroAssembl __ shrptr(end, CardTable::card_shift()); __ subptr(end, addr); // end --> cards count - __ mov64(tmp, disp); + __ mov64(tmp, (intptr_t)ctbs->card_table_base_const()); __ addptr(addr, tmp); __ BIND(L_loop); __ movb(Address(addr, count, Address::times_1), 0); @@ -128,10 +124,7 @@ __ BIND(L_done); void CardTableBarrierSetAssembler::store_check(MacroAssembler* masm, Register obj, Address dst) { // Does a store check for the oop in register obj. The content of // register obj is destroyed afterwards. - BarrierSet* bs = BarrierSet::barrier_set(); - - CardTableBarrierSet* ctbs = barrier_set_cast(bs); - CardTable* ct = ctbs->card_table(); + CardTableBarrierSet* ctbs = CardTableBarrierSet::barrier_set(); __ shrptr(obj, CardTable::card_shift()); @@ -142,7 +135,7 @@ void CardTableBarrierSetAssembler::store_check(MacroAssembler* masm, Register ob // So this essentially converts an address to a displacement and it will // never need to be relocated. On 64bit however the value may be too // large for a 32bit displacement. - intptr_t byte_map_base = (intptr_t)ct->byte_map_base(); + intptr_t byte_map_base = (intptr_t)ctbs->card_table_base_const(); if (__ is_simm32(byte_map_base)) { card_addr = Address(noreg, obj, Address::times_1, byte_map_base); } else { diff --git a/src/hotspot/cpu/x86/interp_masm_x86.cpp b/src/hotspot/cpu/x86/interp_masm_x86.cpp index 36959ddfe1d..b2ea4143ac4 100644 --- a/src/hotspot/cpu/x86/interp_masm_x86.cpp +++ b/src/hotspot/cpu/x86/interp_masm_x86.cpp @@ -580,17 +580,16 @@ void InterpreterMacroAssembler::load_resolved_klass_at_index(Register klass, // Rsub_klass: subklass // // Kills: -// rcx, rdi +// rcx void InterpreterMacroAssembler::gen_subtype_check(Register Rsub_klass, Label& ok_is_subtype) { assert(Rsub_klass != rax, "rax holds superklass"); assert(Rsub_klass != r14, "r14 holds locals"); assert(Rsub_klass != r13, "r13 holds bcp"); assert(Rsub_klass != rcx, "rcx holds 2ndary super array length"); - assert(Rsub_klass != rdi, "rdi holds 2ndary super array scan ptr"); // Profile the not-null value's klass. - profile_typecheck(rcx, Rsub_klass, rdi); // blows rcx, reloads rdi + profile_typecheck(rcx, Rsub_klass); // blows rcx // Do the check. check_klass_subtype(Rsub_klass, rax, rcx, ok_is_subtype); // blows rcx @@ -1394,7 +1393,6 @@ void InterpreterMacroAssembler::profile_final_call(Register mdp) { void InterpreterMacroAssembler::profile_virtual_call(Register receiver, Register mdp, - Register reg2, bool receiver_can_be_null) { if (ProfileInterpreter) { Label profile_continue; @@ -1414,7 +1412,7 @@ void InterpreterMacroAssembler::profile_virtual_call(Register receiver, } // Record the receiver type. - record_klass_in_profile(receiver, mdp, reg2, true); + profile_receiver_type(receiver, mdp, 0); bind(skip_receiver_profile); // The method data pointer needs to be updated to reflect the new target. @@ -1423,135 +1421,6 @@ void InterpreterMacroAssembler::profile_virtual_call(Register receiver, } } -// This routine creates a state machine for updating the multi-row -// type profile at a virtual call site (or other type-sensitive bytecode). -// The machine visits each row (of receiver/count) until the receiver type -// is found, or until it runs out of rows. At the same time, it remembers -// the location of the first empty row. (An empty row records null for its -// receiver, and can be allocated for a newly-observed receiver type.) -// Because there are two degrees of freedom in the state, a simple linear -// search will not work; it must be a decision tree. Hence this helper -// function is recursive, to generate the required tree structured code. -// It's the interpreter, so we are trading off code space for speed. -// See below for example code. -void InterpreterMacroAssembler::record_klass_in_profile_helper( - Register receiver, Register mdp, - Register reg2, int start_row, - Label& done, bool is_virtual_call) { - if (TypeProfileWidth == 0) { - increment_mdp_data_at(mdp, in_bytes(CounterData::count_offset())); - } else { - record_item_in_profile_helper(receiver, mdp, reg2, 0, done, TypeProfileWidth, - &VirtualCallData::receiver_offset, &VirtualCallData::receiver_count_offset); - } -} - -void InterpreterMacroAssembler::record_item_in_profile_helper(Register item, Register mdp, Register reg2, int start_row, - Label& done, int total_rows, - OffsetFunction item_offset_fn, - OffsetFunction item_count_offset_fn) { - int last_row = total_rows - 1; - assert(start_row <= last_row, "must be work left to do"); - // Test this row for both the item and for null. - // Take any of three different outcomes: - // 1. found item => increment count and goto done - // 2. found null => keep looking for case 1, maybe allocate this cell - // 3. found something else => keep looking for cases 1 and 2 - // Case 3 is handled by a recursive call. - for (int row = start_row; row <= last_row; row++) { - Label next_test; - bool test_for_null_also = (row == start_row); - - // See if the item is item[n]. - int item_offset = in_bytes(item_offset_fn(row)); - test_mdp_data_at(mdp, item_offset, item, - (test_for_null_also ? reg2 : noreg), - next_test); - // (Reg2 now contains the item from the CallData.) - - // The item is item[n]. Increment count[n]. - int count_offset = in_bytes(item_count_offset_fn(row)); - increment_mdp_data_at(mdp, count_offset); - jmp(done); - bind(next_test); - - if (test_for_null_also) { - // Failed the equality check on item[n]... Test for null. - testptr(reg2, reg2); - if (start_row == last_row) { - // The only thing left to do is handle the null case. - Label found_null; - jccb(Assembler::zero, found_null); - // Item did not match any saved item and there is no empty row for it. - // Increment total counter to indicate polymorphic case. - increment_mdp_data_at(mdp, in_bytes(CounterData::count_offset())); - jmp(done); - bind(found_null); - break; - } - Label found_null; - // Since null is rare, make it be the branch-taken case. - jcc(Assembler::zero, found_null); - - // Put all the "Case 3" tests here. - record_item_in_profile_helper(item, mdp, reg2, start_row + 1, done, total_rows, - item_offset_fn, item_count_offset_fn); - - // Found a null. Keep searching for a matching item, - // but remember that this is an empty (unused) slot. - bind(found_null); - } - } - - // In the fall-through case, we found no matching item, but we - // observed the item[start_row] is null. - - // Fill in the item field and increment the count. - int item_offset = in_bytes(item_offset_fn(start_row)); - set_mdp_data_at(mdp, item_offset, item); - int count_offset = in_bytes(item_count_offset_fn(start_row)); - movl(reg2, DataLayout::counter_increment); - set_mdp_data_at(mdp, count_offset, reg2); - if (start_row > 0) { - jmp(done); - } -} - -// Example state machine code for three profile rows: -// // main copy of decision tree, rooted at row[1] -// if (row[0].rec == rec) { row[0].incr(); goto done; } -// if (row[0].rec != nullptr) { -// // inner copy of decision tree, rooted at row[1] -// if (row[1].rec == rec) { row[1].incr(); goto done; } -// if (row[1].rec != nullptr) { -// // degenerate decision tree, rooted at row[2] -// if (row[2].rec == rec) { row[2].incr(); goto done; } -// if (row[2].rec != nullptr) { count.incr(); goto done; } // overflow -// row[2].init(rec); goto done; -// } else { -// // remember row[1] is empty -// if (row[2].rec == rec) { row[2].incr(); goto done; } -// row[1].init(rec); goto done; -// } -// } else { -// // remember row[0] is empty -// if (row[1].rec == rec) { row[1].incr(); goto done; } -// if (row[2].rec == rec) { row[2].incr(); goto done; } -// row[0].init(rec); goto done; -// } -// done: - -void InterpreterMacroAssembler::record_klass_in_profile(Register receiver, - Register mdp, Register reg2, - bool is_virtual_call) { - assert(ProfileInterpreter, "must be profiling"); - Label done; - - record_klass_in_profile_helper(receiver, mdp, reg2, 0, done, is_virtual_call); - - bind (done); -} - void InterpreterMacroAssembler::profile_ret(Register return_bci, Register mdp) { if (ProfileInterpreter) { @@ -1611,7 +1480,7 @@ void InterpreterMacroAssembler::profile_null_seen(Register mdp) { } -void InterpreterMacroAssembler::profile_typecheck(Register mdp, Register klass, Register reg2) { +void InterpreterMacroAssembler::profile_typecheck(Register mdp, Register klass) { if (ProfileInterpreter) { Label profile_continue; @@ -1624,7 +1493,7 @@ void InterpreterMacroAssembler::profile_typecheck(Register mdp, Register klass, mdp_delta = in_bytes(VirtualCallData::virtual_call_data_size()); // Record the object type. - record_klass_in_profile(klass, mdp, reg2, false); + profile_receiver_type(klass, mdp, 0); } update_mdp_by_constant(mdp, mdp_delta); diff --git a/src/hotspot/cpu/x86/interp_masm_x86.hpp b/src/hotspot/cpu/x86/interp_masm_x86.hpp index 1d2080cd542..4114028f78e 100644 --- a/src/hotspot/cpu/x86/interp_masm_x86.hpp +++ b/src/hotspot/cpu/x86/interp_masm_x86.hpp @@ -234,16 +234,6 @@ class InterpreterMacroAssembler: public MacroAssembler { Register test_value_out, Label& not_equal_continue); - void record_klass_in_profile(Register receiver, Register mdp, - Register reg2, bool is_virtual_call); - void record_klass_in_profile_helper(Register receiver, Register mdp, - Register reg2, int start_row, - Label& done, bool is_virtual_call); - void record_item_in_profile_helper(Register item, Register mdp, Register reg2, int start_row, - Label& done, int total_rows, - OffsetFunction item_offset_fn, - OffsetFunction item_count_offset_fn); - void update_mdp_by_offset(Register mdp_in, int offset_of_offset); void update_mdp_by_offset(Register mdp_in, Register reg, int offset_of_disp); void update_mdp_by_constant(Register mdp_in, int constant); @@ -254,11 +244,10 @@ class InterpreterMacroAssembler: public MacroAssembler { void profile_call(Register mdp); void profile_final_call(Register mdp); void profile_virtual_call(Register receiver, Register mdp, - Register scratch2, bool receiver_can_be_null = false); void profile_ret(Register return_bci, Register mdp); void profile_null_seen(Register mdp); - void profile_typecheck(Register mdp, Register klass, Register scratch); + void profile_typecheck(Register mdp, Register klass); void profile_switch_default(Register mdp); void profile_switch_case(Register index_in_scratch, Register mdp, diff --git a/src/hotspot/cpu/x86/macroAssembler_x86.cpp b/src/hotspot/cpu/x86/macroAssembler_x86.cpp index 48928b0db3f..7f7bb2c4c7f 100644 --- a/src/hotspot/cpu/x86/macroAssembler_x86.cpp +++ b/src/hotspot/cpu/x86/macroAssembler_x86.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, 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 @@ -4749,6 +4749,203 @@ Address MacroAssembler::argument_address(RegisterOrConstant arg_slot, return Address(rsp, scale_reg, scale_factor, offset); } +// Handle the receiver type profile update given the "recv" klass. +// +// Normally updates the ReceiverData (RD) that starts at "mdp" + "mdp_offset". +// If there are no matching or claimable receiver entries in RD, updates +// the polymorphic counter. +// +// This code expected to run by either the interpreter or JIT-ed code, without +// extra synchronization. For safety, receiver cells are claimed atomically, which +// avoids grossly misrepresenting the profiles under concurrent updates. For speed, +// counter updates are not atomic. +// +void MacroAssembler::profile_receiver_type(Register recv, Register mdp, int mdp_offset) { + int base_receiver_offset = in_bytes(ReceiverTypeData::receiver_offset(0)); + int end_receiver_offset = in_bytes(ReceiverTypeData::receiver_offset(ReceiverTypeData::row_limit())); + int poly_count_offset = in_bytes(CounterData::count_offset()); + int receiver_step = in_bytes(ReceiverTypeData::receiver_offset(1)) - base_receiver_offset; + int receiver_to_count_step = in_bytes(ReceiverTypeData::receiver_count_offset(0)) - base_receiver_offset; + + // Adjust for MDP offsets. Slots are pointer-sized, so is the global offset. + assert(is_aligned(mdp_offset, BytesPerWord), "sanity"); + base_receiver_offset += mdp_offset; + end_receiver_offset += mdp_offset; + poly_count_offset += mdp_offset; + + // Scale down to optimize encoding. Slots are pointer-sized. + assert(is_aligned(base_receiver_offset, BytesPerWord), "sanity"); + assert(is_aligned(end_receiver_offset, BytesPerWord), "sanity"); + assert(is_aligned(poly_count_offset, BytesPerWord), "sanity"); + assert(is_aligned(receiver_step, BytesPerWord), "sanity"); + assert(is_aligned(receiver_to_count_step, BytesPerWord), "sanity"); + base_receiver_offset >>= LogBytesPerWord; + end_receiver_offset >>= LogBytesPerWord; + poly_count_offset >>= LogBytesPerWord; + receiver_step >>= LogBytesPerWord; + receiver_to_count_step >>= LogBytesPerWord; + +#ifdef ASSERT + // We are about to walk the MDO slots without asking for offsets. + // Check that our math hits all the right spots. + for (uint c = 0; c < ReceiverTypeData::row_limit(); c++) { + int real_recv_offset = mdp_offset + in_bytes(ReceiverTypeData::receiver_offset(c)); + int real_count_offset = mdp_offset + in_bytes(ReceiverTypeData::receiver_count_offset(c)); + int offset = base_receiver_offset + receiver_step*c; + int count_offset = offset + receiver_to_count_step; + assert((offset << LogBytesPerWord) == real_recv_offset, "receiver slot math"); + assert((count_offset << LogBytesPerWord) == real_count_offset, "receiver count math"); + } + int real_poly_count_offset = mdp_offset + in_bytes(CounterData::count_offset()); + assert(poly_count_offset << LogBytesPerWord == real_poly_count_offset, "poly counter math"); +#endif + + // Corner case: no profile table. Increment poly counter and exit. + if (ReceiverTypeData::row_limit() == 0) { + addptr(Address(mdp, poly_count_offset, Address::times_ptr), DataLayout::counter_increment); + return; + } + + Register offset = rscratch1; + + Label L_loop_search_receiver, L_loop_search_empty; + Label L_restart, L_found_recv, L_found_empty, L_polymorphic, L_count_update; + + // The code here recognizes three major cases: + // A. Fastest: receiver found in the table + // B. Fast: no receiver in the table, and the table is full + // C. Slow: no receiver in the table, free slots in the table + // + // The case A performance is most important, as perfectly-behaved code would end up + // there, especially with larger TypeProfileWidth. The case B performance is + // important as well, this is where bulk of code would land for normally megamorphic + // cases. The case C performance is not essential, its job is to deal with installation + // races, we optimize for code density instead. Case C needs to make sure that receiver + // rows are only claimed once. This makes sure we never overwrite a row for another + // receiver and never duplicate the receivers in the list, making profile type-accurate. + // + // It is very tempting to handle these cases in a single loop, and claim the first slot + // without checking the rest of the table. But, profiling code should tolerate free slots + // in the table, as class unloading can clear them. After such cleanup, the receiver + // we need might be _after_ the free slot. Therefore, we need to let at least full scan + // to complete, before trying to install new slots. Splitting the code in several tight + // loops also helpfully optimizes for cases A and B. + // + // This code is effectively: + // + // restart: + // // Fastest: receiver is already installed + // for (i = 0; i < receiver_count(); i++) { + // if (receiver(i) == recv) goto found_recv(i); + // } + // + // // Fast: no receiver, but profile is full + // for (i = 0; i < receiver_count(); i++) { + // if (receiver(i) == null) goto found_null(i); + // } + // goto polymorphic + // + // // Slow: try to install receiver + // found_null(i): + // CAS(&receiver(i), null, recv); + // goto restart + // + // polymorphic: + // count++; + // return + // + // found_recv(i): + // *receiver_count(i)++ + // + + bind(L_restart); + + // Fastest: receiver is already installed + movptr(offset, base_receiver_offset); + bind(L_loop_search_receiver); + cmpptr(recv, Address(mdp, offset, Address::times_ptr)); + jccb(Assembler::equal, L_found_recv); + addptr(offset, receiver_step); + cmpptr(offset, end_receiver_offset); + jccb(Assembler::notEqual, L_loop_search_receiver); + + // Fast: no receiver, but profile is full + movptr(offset, base_receiver_offset); + bind(L_loop_search_empty); + cmpptr(Address(mdp, offset, Address::times_ptr), NULL_WORD); + jccb(Assembler::equal, L_found_empty); + addptr(offset, receiver_step); + cmpptr(offset, end_receiver_offset); + jccb(Assembler::notEqual, L_loop_search_empty); + jmpb(L_polymorphic); + + // Slow: try to install receiver + bind(L_found_empty); + + // Atomically swing receiver slot: null -> recv. + // + // The update code uses CAS, which wants RAX register specifically, *and* it needs + // other important registers untouched, as they form the address. Therefore, we need + // to shift any important registers from RAX into some other spare register. If we + // have a spare register, we are forced to save it on stack here. + + Register spare_reg = noreg; + Register shifted_mdp = mdp; + Register shifted_recv = recv; + if (recv == rax || mdp == rax) { + spare_reg = (recv != rbx && mdp != rbx) ? rbx : + (recv != rcx && mdp != rcx) ? rcx : + rdx; + assert_different_registers(mdp, recv, offset, spare_reg); + + push(spare_reg); + if (recv == rax) { + movptr(spare_reg, recv); + shifted_recv = spare_reg; + } else { + assert(mdp == rax, "Remaining case"); + movptr(spare_reg, mdp); + shifted_mdp = spare_reg; + } + } else { + push(rax); + } + + // None of the important registers are in RAX after this shuffle. + assert_different_registers(rax, shifted_mdp, shifted_recv, offset); + + xorptr(rax, rax); + cmpxchgptr(shifted_recv, Address(shifted_mdp, offset, Address::times_ptr)); + + // Unshift registers. + if (recv == rax || mdp == rax) { + movptr(rax, spare_reg); + pop(spare_reg); + } else { + pop(rax); + } + + // CAS success means the slot now has the receiver we want. CAS failure means + // something had claimed the slot concurrently: it can be the same receiver we want, + // or something else. Since this is a slow path, we can optimize for code density, + // and just restart the search from the beginning. + jmpb(L_restart); + + // Counter updates: + + // Increment polymorphic counter instead of receiver slot. + bind(L_polymorphic); + movptr(offset, poly_count_offset); + jmpb(L_count_update); + + // Found a receiver, convert its slot offset to corresponding count offset. + bind(L_found_recv); + addptr(offset, receiver_to_count_step); + + bind(L_count_update); + addptr(Address(mdp, offset, Address::times_ptr), DataLayout::counter_increment); +} + void MacroAssembler::_verify_oop_addr(Address addr, const char* s, const char* file, int line) { if (!VerifyOops) return; @@ -5889,7 +6086,7 @@ void MacroAssembler::generate_fill(BasicType t, bool aligned, vpbroadcastd(xtmp, xtmp, Assembler::AVX_512bit); subptr(count, 16 << shift); - jccb(Assembler::less, L_check_fill_32_bytes); + jcc(Assembler::less, L_check_fill_32_bytes); align(16); BIND(L_fill_64_bytes_loop_avx3); @@ -6054,46 +6251,32 @@ void MacroAssembler::evpbroadcast(BasicType type, XMMRegister dst, Register src, } } -// Encode given char[]/byte[] to byte[] in ISO_8859_1 or ASCII -// -// @IntrinsicCandidate -// int sun.nio.cs.ISO_8859_1.Encoder#encodeISOArray0( -// char[] sa, int sp, byte[] da, int dp, int len) { -// int i = 0; -// for (; i < len; i++) { -// char c = sa[sp++]; -// if (c > '\u00FF') -// break; -// da[dp++] = (byte) c; -// } -// return i; -// } -// -// @IntrinsicCandidate -// int java.lang.StringCoding.encodeISOArray0( -// byte[] sa, int sp, byte[] da, int dp, int len) { -// int i = 0; -// for (; i < len; i++) { -// char c = StringUTF16.getChar(sa, sp++); -// if (c > '\u00FF') -// break; -// da[dp++] = (byte) c; -// } -// return i; -// } -// -// @IntrinsicCandidate -// int java.lang.StringCoding.encodeAsciiArray0( -// char[] sa, int sp, byte[] da, int dp, int len) { -// int i = 0; -// for (; i < len; i++) { -// char c = sa[sp++]; -// if (c >= '\u0080') -// break; -// da[dp++] = (byte) c; -// } -// return i; -// } +// encode char[] to byte[] in ISO_8859_1 or ASCII + //@IntrinsicCandidate + //private static int implEncodeISOArray(byte[] sa, int sp, + //byte[] da, int dp, int len) { + // int i = 0; + // for (; i < len; i++) { + // char c = StringUTF16.getChar(sa, sp++); + // if (c > '\u00FF') + // break; + // da[dp++] = (byte)c; + // } + // return i; + //} + // + //@IntrinsicCandidate + //private static int implEncodeAsciiArray(char[] sa, int sp, + // byte[] da, int dp, int len) { + // int i = 0; + // for (; i < len; i++) { + // char c = sa[sp++]; + // if (c >= '\u0080') + // break; + // da[dp++] = (byte)c; + // } + // return i; + //} void MacroAssembler::encode_iso_array(Register src, Register dst, Register len, XMMRegister tmp1Reg, XMMRegister tmp2Reg, XMMRegister tmp3Reg, XMMRegister tmp4Reg, diff --git a/src/hotspot/cpu/x86/macroAssembler_x86.hpp b/src/hotspot/cpu/x86/macroAssembler_x86.hpp index 695eea6ad03..93e3529ac1e 100644 --- a/src/hotspot/cpu/x86/macroAssembler_x86.hpp +++ b/src/hotspot/cpu/x86/macroAssembler_x86.hpp @@ -668,6 +668,8 @@ public: // method handles (JSR 292) Address argument_address(RegisterOrConstant arg_slot, int extra_slot_offset = 0); + void profile_receiver_type(Register recv, Register mdp, int mdp_offset); + // Debugging // only if +VerifyOops diff --git a/src/hotspot/cpu/x86/register_x86.cpp b/src/hotspot/cpu/x86/register_x86.cpp index e6083429344..188af9264a8 100644 --- a/src/hotspot/cpu/x86/register_x86.cpp +++ b/src/hotspot/cpu/x86/register_x86.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, 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 @@ -32,14 +32,10 @@ const KRegister::KRegisterImpl all_KRegisterImpls [KRegister::number_ const char * Register::RegisterImpl::name() const { static const char *const names[number_of_registers] = { -#ifdef _LP64 "rax", "rcx", "rdx", "rbx", "rsp", "rbp", "rsi", "rdi", "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15", "r16", "r17", "r18", "r19", "r20", "r21", "r22", "r23", "r24", "r25", "r26", "r27", "r28", "r29", "r30", "r31" -#else - "eax", "ecx", "edx", "ebx", "esp", "ebp", "esi", "edi" -#endif // _LP64 }; return is_valid() ? names[encoding()] : "noreg"; } @@ -54,11 +50,9 @@ const char* FloatRegister::FloatRegisterImpl::name() const { const char* XMMRegister::XMMRegisterImpl::name() const { static const char *const names[number_of_registers] = { "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7" -#ifdef _LP64 ,"xmm8", "xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15" ,"xmm16", "xmm17", "xmm18", "xmm19", "xmm20", "xmm21", "xmm22", "xmm23" ,"xmm24", "xmm25", "xmm26", "xmm27", "xmm28", "xmm29", "xmm30", "xmm31" -#endif // _LP64 }; return is_valid() ? names[encoding()] : "xnoreg"; } diff --git a/src/hotspot/cpu/x86/register_x86.hpp b/src/hotspot/cpu/x86/register_x86.hpp index 0a8ecb7be26..5e0326b9aad 100644 --- a/src/hotspot/cpu/x86/register_x86.hpp +++ b/src/hotspot/cpu/x86/register_x86.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, 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 @@ class VMRegImpl; typedef VMRegImpl* VMReg; -// The implementation of integer registers for the x86/x64 architectures. +// The implementation of integer registers for the x64 architectures. class Register { private: int _encoding; @@ -44,11 +44,9 @@ private: public: inline friend constexpr Register as_Register(int encoding); - enum { - number_of_registers = LP64_ONLY( 32 ) NOT_LP64( 8 ), - number_of_byte_registers = LP64_ONLY( 32 ) NOT_LP64( 4 ), - max_slots_per_register = LP64_ONLY( 2 ) NOT_LP64( 1 ) - }; + static const int number_of_registers = 32; + static const int number_of_byte_registers = 32; + static const int max_slots_per_register = 2; class RegisterImpl: public AbstractRegisterImpl { friend class Register; @@ -79,11 +77,9 @@ public: // Actually available GP registers for use, depending on actual CPU capabilities and flags. static int available_gp_registers() { -#ifdef _LP64 if (!UseAPX) { return number_of_registers / 2; } -#endif // _LP64 return number_of_registers; } }; @@ -116,9 +112,8 @@ constexpr Register rsp = as_Register(4); constexpr Register rbp = as_Register(5); constexpr Register rsi = as_Register(6); constexpr Register rdi = as_Register(7); -#ifdef _LP64 -constexpr Register r8 = as_Register( 8); -constexpr Register r9 = as_Register( 9); +constexpr Register r8 = as_Register(8); +constexpr Register r9 = as_Register(9); constexpr Register r10 = as_Register(10); constexpr Register r11 = as_Register(11); constexpr Register r12 = as_Register(12); @@ -141,7 +136,6 @@ constexpr Register r28 = as_Register(28); constexpr Register r29 = as_Register(29); constexpr Register r30 = as_Register(30); constexpr Register r31 = as_Register(31); -#endif // _LP64 // The implementation of x87 floating point registers for the ia32 architecture. @@ -154,10 +148,8 @@ private: public: inline friend constexpr FloatRegister as_FloatRegister(int encoding); - enum { - number_of_registers = 8, - max_slots_per_register = 2 - }; + static const int number_of_registers = 8; + static const int max_slots_per_register = 2; class FloatRegisterImpl: public AbstractRegisterImpl { friend class FloatRegister; @@ -217,10 +209,8 @@ private: public: inline friend constexpr XMMRegister as_XMMRegister(int encoding); - enum { - number_of_registers = LP64_ONLY( 32 ) NOT_LP64( 8 ), - max_slots_per_register = LP64_ONLY( 16 ) NOT_LP64( 16 ) // 512-bit - }; + static const int number_of_registers = 32; + static const int max_slots_per_register = 16; // 512-bit class XMMRegisterImpl: public AbstractRegisterImpl { friend class XMMRegister; @@ -250,11 +240,9 @@ public: // Actually available XMM registers for use, depending on actual CPU capabilities and flags. static int available_xmm_registers() { -#ifdef _LP64 if (UseAVX < 3) { return number_of_registers / 2; } -#endif // _LP64 return number_of_registers; } }; @@ -287,7 +275,6 @@ constexpr XMMRegister xmm4 = as_XMMRegister( 4); constexpr XMMRegister xmm5 = as_XMMRegister( 5); constexpr XMMRegister xmm6 = as_XMMRegister( 6); constexpr XMMRegister xmm7 = as_XMMRegister( 7); -#ifdef _LP64 constexpr XMMRegister xmm8 = as_XMMRegister( 8); constexpr XMMRegister xmm9 = as_XMMRegister( 9); constexpr XMMRegister xmm10 = as_XMMRegister(10); @@ -312,7 +299,6 @@ constexpr XMMRegister xmm28 = as_XMMRegister(28); constexpr XMMRegister xmm29 = as_XMMRegister(29); constexpr XMMRegister xmm30 = as_XMMRegister(30); constexpr XMMRegister xmm31 = as_XMMRegister(31); -#endif // _LP64 // The implementation of AVX-512 opmask registers. @@ -394,25 +380,17 @@ constexpr KRegister k7 = as_KRegister(7); // Define a class that exports it. class ConcreteRegisterImpl : public AbstractRegisterImpl { public: - enum { - max_gpr = Register::number_of_registers * Register::max_slots_per_register, - max_fpr = max_gpr + FloatRegister::number_of_registers * FloatRegister::max_slots_per_register, - max_xmm = max_fpr + XMMRegister::number_of_registers * XMMRegister::max_slots_per_register, - max_kpr = max_xmm + KRegister::number_of_registers * KRegister::max_slots_per_register, + static const int max_gpr = Register::number_of_registers * Register::max_slots_per_register; + static const int max_fpr = max_gpr + FloatRegister::number_of_registers * FloatRegister::max_slots_per_register; + static const int max_xmm = max_fpr + XMMRegister::number_of_registers * XMMRegister::max_slots_per_register; + static const int max_kpr = max_xmm + KRegister::number_of_registers * KRegister::max_slots_per_register; // A big enough number for C2: all the registers plus flags // This number must be large enough to cover REG_COUNT (defined by c2) registers. // There is no requirement that any ordering here matches any ordering c2 gives // it's optoregs. - - // x86_32.ad defines additional dummy FILL0-FILL7 registers, in order to tally - // REG_COUNT (computed by ADLC based on the number of reg_defs seen in .ad files) - // with ConcreteRegisterImpl::number_of_registers additional count of 8 is being - // added for 32 bit jvm. - number_of_registers = max_kpr + // gpr/fpr/xmm/kpr - NOT_LP64( 8 + ) // FILL0-FILL7 in x86_32.ad - 1 // eflags - }; + static const int number_of_registers = max_kpr + // gpr/fpr/xmm/kpr + 1; // eflags }; template <> diff --git a/src/hotspot/cpu/x86/sharedRuntime_x86_64.cpp b/src/hotspot/cpu/x86/sharedRuntime_x86_64.cpp index 5a4a5b1809e..bbd43c1a0e8 100644 --- a/src/hotspot/cpu/x86/sharedRuntime_x86_64.cpp +++ b/src/hotspot/cpu/x86/sharedRuntime_x86_64.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, 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 @@ -1043,26 +1043,24 @@ void SharedRuntime::generate_i2c2i_adapters(MacroAssembler *masm, // Class initialization barrier for static methods entry_address[AdapterBlob::C2I_No_Clinit_Check] = nullptr; - if (VM_Version::supports_fast_class_init_checks()) { - Label L_skip_barrier; - Register method = rbx; + assert(VM_Version::supports_fast_class_init_checks(), "sanity"); + Label L_skip_barrier; + Register method = rbx; - { // Bypass the barrier for non-static methods - Register flags = rscratch1; - __ load_unsigned_short(flags, Address(method, Method::access_flags_offset())); - __ testl(flags, JVM_ACC_STATIC); - __ jcc(Assembler::zero, L_skip_barrier); // non-static - } + // Bypass the barrier for non-static methods + Register flags = rscratch1; + __ load_unsigned_short(flags, Address(method, Method::access_flags_offset())); + __ testl(flags, JVM_ACC_STATIC); + __ jcc(Assembler::zero, L_skip_barrier); // non-static - Register klass = rscratch1; - __ load_method_holder(klass, method); - __ clinit_barrier(klass, &L_skip_barrier /*L_fast_path*/); + Register klass = rscratch1; + __ load_method_holder(klass, method); + __ clinit_barrier(klass, &L_skip_barrier /*L_fast_path*/); - __ jump(RuntimeAddress(SharedRuntime::get_handle_wrong_method_stub())); // slow path + __ jump(RuntimeAddress(SharedRuntime::get_handle_wrong_method_stub())); // slow path - __ bind(L_skip_barrier); - entry_address[AdapterBlob::C2I_No_Clinit_Check] = __ pc(); - } + __ bind(L_skip_barrier); + entry_address[AdapterBlob::C2I_No_Clinit_Check] = __ pc(); BarrierSetAssembler* bs = BarrierSet::barrier_set()->barrier_set_assembler(); bs->c2i_entry_barrier(masm); @@ -1904,7 +1902,8 @@ nmethod* SharedRuntime::generate_native_wrapper(MacroAssembler* masm, int vep_offset = ((intptr_t)__ pc()) - start; - if (VM_Version::supports_fast_class_init_checks() && method->needs_clinit_barrier()) { + if (method->needs_clinit_barrier()) { + assert(VM_Version::supports_fast_class_init_checks(), "sanity"); Label L_skip_barrier; Register klass = r10; __ mov_metadata(klass, method->method_holder()); // InstanceKlass* @@ -3602,4 +3601,3 @@ RuntimeStub* SharedRuntime::generate_jfr_return_lease() { } #endif // INCLUDE_JFR - diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64_kyber.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64_kyber.cpp index 3e5593322d5..7d5dee6a5df 100644 --- a/src/hotspot/cpu/x86/stubGenerator_x86_64_kyber.cpp +++ b/src/hotspot/cpu/x86/stubGenerator_x86_64_kyber.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, 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 @@ -64,6 +64,39 @@ static address kyberAvx512ConstsAddr(int offset) { const Register scratch = r10; +ATTRIBUTE_ALIGNED(64) static const uint8_t kyberAvx512_12To16Dup[] = { +// 0 - 63 + 0, 1, 1, 2, 3, 4, 4, 5, 6, 7, 7, 8, 9, 10, 10, 11, 12, 13, 13, 14, 15, 16, + 16, 17, 18, 19, 19, 20, 21, 22, 22, 23, 24, 25, 25, 26, 27, 28, 28, 29, 30, + 31, 31, 32, 33, 34, 34, 35, 36, 37, 37, 38, 39, 40, 40, 41, 42, 43, 43, 44, + 45, 46, 46, 47 + }; + +static address kyberAvx512_12To16DupAddr() { + return (address) kyberAvx512_12To16Dup; +} + +ATTRIBUTE_ALIGNED(64) static const uint16_t kyberAvx512_12To16Shift[] = { +// 0 - 31 + 0, 4, 0, 4, 0, 4, 0, 4, 0, 4, 0, 4, 0, 4, 0, 4, 0, 4, 0, 4, 0, 4, 0, 4, 0, + 4, 0, 4, 0, 4, 0, 4 + }; + +static address kyberAvx512_12To16ShiftAddr() { + return (address) kyberAvx512_12To16Shift; +} + +ATTRIBUTE_ALIGNED(64) static const uint64_t kyberAvx512_12To16And[] = { +// 0 - 7 + 0x0FFF0FFF0FFF0FFF, 0x0FFF0FFF0FFF0FFF, 0x0FFF0FFF0FFF0FFF, + 0x0FFF0FFF0FFF0FFF, 0x0FFF0FFF0FFF0FFF, 0x0FFF0FFF0FFF0FFF, + 0x0FFF0FFF0FFF0FFF, 0x0FFF0FFF0FFF0FFF + }; + +static address kyberAvx512_12To16AndAddr() { + return (address) kyberAvx512_12To16And; +} + ATTRIBUTE_ALIGNED(64) static const uint16_t kyberAvx512NttPerms[] = { // 0 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, @@ -822,10 +855,65 @@ address generate_kyber12To16_avx512(StubGenerator *stubgen, const Register perms = r11; - Label Loop; + Label Loop, VBMILoop; __ addptr(condensed, condensedOffs); + if (VM_Version::supports_avx512_vbmi()) { + // mask load for the first 48 bytes of each vector + __ mov64(rax, 0x0000FFFFFFFFFFFF); + __ kmovql(k1, rax); + + __ lea(perms, ExternalAddress(kyberAvx512_12To16DupAddr())); + __ evmovdqub(xmm20, Address(perms), Assembler::AVX_512bit); + + __ lea(perms, ExternalAddress(kyberAvx512_12To16ShiftAddr())); + __ evmovdquw(xmm21, Address(perms), Assembler::AVX_512bit); + + __ lea(perms, ExternalAddress(kyberAvx512_12To16AndAddr())); + __ evmovdquq(xmm22, Address(perms), Assembler::AVX_512bit); + + __ align(OptoLoopAlignment); + __ BIND(VBMILoop); + + __ evmovdqub(xmm0, k1, Address(condensed, 0), false, + Assembler::AVX_512bit); + __ evmovdqub(xmm1, k1, Address(condensed, 48), false, + Assembler::AVX_512bit); + __ evmovdqub(xmm2, k1, Address(condensed, 96), false, + Assembler::AVX_512bit); + __ evmovdqub(xmm3, k1, Address(condensed, 144), false, + Assembler::AVX_512bit); + + __ evpermb(xmm4, k0, xmm20, xmm0, false, Assembler::AVX_512bit); + __ evpermb(xmm5, k0, xmm20, xmm1, false, Assembler::AVX_512bit); + __ evpermb(xmm6, k0, xmm20, xmm2, false, Assembler::AVX_512bit); + __ evpermb(xmm7, k0, xmm20, xmm3, false, Assembler::AVX_512bit); + + __ evpsrlvw(xmm4, xmm4, xmm21, Assembler::AVX_512bit); + __ evpsrlvw(xmm5, xmm5, xmm21, Assembler::AVX_512bit); + __ evpsrlvw(xmm6, xmm6, xmm21, Assembler::AVX_512bit); + __ evpsrlvw(xmm7, xmm7, xmm21, Assembler::AVX_512bit); + + __ evpandq(xmm0, xmm22, xmm4, Assembler::AVX_512bit); + __ evpandq(xmm1, xmm22, xmm5, Assembler::AVX_512bit); + __ evpandq(xmm2, xmm22, xmm6, Assembler::AVX_512bit); + __ evpandq(xmm3, xmm22, xmm7, Assembler::AVX_512bit); + + store4regs(parsed, 0, xmm0_3, _masm); + + __ addptr(condensed, 192); + __ addptr(parsed, 256); + __ subl(parsedLength, 128); + __ jcc(Assembler::greater, VBMILoop); + + __ leave(); // required for proper stackwalking of RuntimeStub frame + __ mov64(rax, 0); // return 0 + __ ret(0); + + return start; + } + __ lea(perms, ExternalAddress(kyberAvx512_12To16PermsAddr())); load4regs(xmm24_27, perms, 0, _masm); diff --git a/src/hotspot/cpu/x86/templateTable_x86.cpp b/src/hotspot/cpu/x86/templateTable_x86.cpp index 7065f653e19..db7749ec482 100644 --- a/src/hotspot/cpu/x86/templateTable_x86.cpp +++ b/src/hotspot/cpu/x86/templateTable_x86.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, 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 @@ -2216,7 +2216,8 @@ void TemplateTable::resolve_cache_and_index_for_method(int byte_no, __ cmpl(temp, code); // have we resolved this bytecode? // Class initialization barrier for static methods - if (VM_Version::supports_fast_class_init_checks() && bytecode() == Bytecodes::_invokestatic) { + if (bytecode() == Bytecodes::_invokestatic) { + assert(VM_Version::supports_fast_class_init_checks(), "sanity"); const Register method = temp; const Register klass = temp; @@ -2264,8 +2265,8 @@ void TemplateTable::resolve_cache_and_index_for_field(int byte_no, __ cmpl(temp, code); // have we resolved this bytecode? // Class initialization barrier for static fields - if (VM_Version::supports_fast_class_init_checks() && - (bytecode() == Bytecodes::_getstatic || bytecode() == Bytecodes::_putstatic)) { + if (bytecode() == Bytecodes::_getstatic || bytecode() == Bytecodes::_putstatic) { + assert(VM_Version::supports_fast_class_init_checks(), "sanity"); const Register field_holder = temp; __ jcc(Assembler::notEqual, L_clinit_barrier_slow); @@ -3266,7 +3267,7 @@ void TemplateTable::invokevirtual_helper(Register index, __ load_klass(rax, recv, rscratch1); // profile this call - __ profile_virtual_call(rax, rlocals, rdx); + __ profile_virtual_call(rax, rlocals); // get target Method* & entry point __ lookup_virtual_method(rax, index, method); @@ -3407,7 +3408,7 @@ void TemplateTable::invokeinterface(int byte_no) { // profile this call __ restore_bcp(); // rbcp was destroyed by receiver type check - __ profile_virtual_call(rdx, rbcp, rlocals); + __ profile_virtual_call(rdx, rbcp); // Get declaring interface class from method, and itable index __ load_method_holder(rax, rbx); diff --git a/src/hotspot/cpu/x86/vm_version_x86.cpp b/src/hotspot/cpu/x86/vm_version_x86.cpp index 747daefd51d..74df41f8682 100644 --- a/src/hotspot/cpu/x86/vm_version_x86.cpp +++ b/src/hotspot/cpu/x86/vm_version_x86.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, 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 @@ -143,7 +143,7 @@ class VM_Version_StubGenerator: public StubCodeGenerator { Label detect_486, cpu486, detect_586, std_cpuid1, std_cpuid4, std_cpuid24, std_cpuid29; Label sef_cpuid, sefsl1_cpuid, ext_cpuid, ext_cpuid1, ext_cpuid5, ext_cpuid7; - Label ext_cpuid8, done, wrapup, vector_save_restore, apx_save_restore_warning; + Label ext_cpuid8, done, wrapup, vector_save_restore, apx_save_restore_warning, apx_xstate; Label legacy_setup, save_restore_except, legacy_save_restore, start_simd_check; StubCodeMark mark(this, "VM_Version", "get_cpu_info_stub"); @@ -468,6 +468,20 @@ class VM_Version_StubGenerator: public StubCodeGenerator { __ movq(Address(rsi, 0), r16); __ movq(Address(rsi, 8), r31); + // + // Query CPUID 0xD.19 for APX XSAVE offset + // Extended State Enumeration Sub-leaf 19 (APX) + // EAX = size of APX state (should be 128) + // EBX = offset in standard XSAVE format + // + __ movl(rax, 0xD); + __ movl(rcx, 19); + __ cpuid(); + __ lea(rsi, Address(rbp, in_bytes(VM_Version::apx_xstate_size_offset()))); + __ movl(Address(rsi, 0), rax); + __ lea(rsi, Address(rbp, in_bytes(VM_Version::apx_xstate_offset_offset()))); + __ movl(Address(rsi, 0), rbx); + UseAPX = save_apx; __ bind(vector_save_restore); // @@ -921,8 +935,9 @@ void VM_Version::get_processor_features() { // Check if processor has Intel Ecore if (FLAG_IS_DEFAULT(EnableX86ECoreOpts) && is_intel() && is_intel_server_family() && - (_model == 0x97 || _model == 0xAA || _model == 0xAC || _model == 0xAF || - _model == 0xCC || _model == 0xDD)) { + (supports_hybrid() || + _model == 0xAF /* Xeon 6 E-cores (Sierra Forest) */ || + _model == 0xDD /* Xeon 6+ E-cores (Clearwater Forest) */ )) { FLAG_SET_DEFAULT(EnableX86ECoreOpts, true); } diff --git a/src/hotspot/cpu/x86/vm_version_x86.hpp b/src/hotspot/cpu/x86/vm_version_x86.hpp index cc93ee3564e..a3f2a801198 100644 --- a/src/hotspot/cpu/x86/vm_version_x86.hpp +++ b/src/hotspot/cpu/x86/vm_version_x86.hpp @@ -676,6 +676,10 @@ protected: // Space to save apx registers after signal handle jlong apx_save[2]; // Save r16 and r31 + // cpuid function 0xD, subleaf 19 (APX extended state) + uint32_t apx_xstate_size; // EAX: size of APX state (128) + uint32_t apx_xstate_offset; // EBX: offset in standard XSAVE area + VM_Features feature_flags() const; // Asserts @@ -739,6 +743,11 @@ public: static ByteSize ymm_save_offset() { return byte_offset_of(CpuidInfo, ymm_save); } static ByteSize zmm_save_offset() { return byte_offset_of(CpuidInfo, zmm_save); } static ByteSize apx_save_offset() { return byte_offset_of(CpuidInfo, apx_save); } + static ByteSize apx_xstate_offset_offset() { return byte_offset_of(CpuidInfo, apx_xstate_offset); } + static ByteSize apx_xstate_size_offset() { return byte_offset_of(CpuidInfo, apx_xstate_size); } + + static uint32_t apx_xstate_offset() { return _cpuid_info.apx_xstate_offset; } + static uint32_t apx_xstate_size() { return _cpuid_info.apx_xstate_size; } // The value used to check ymm register after signal handle static int ymm_test_value() { return 0xCAFEBABE; } diff --git a/src/hotspot/cpu/x86/vmreg_x86.cpp b/src/hotspot/cpu/x86/vmreg_x86.cpp index 44aee56ef15..45269e69fcb 100644 --- a/src/hotspot/cpu/x86/vmreg_x86.cpp +++ b/src/hotspot/cpu/x86/vmreg_x86.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2006, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2006, 2026, 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 @@ -32,9 +32,7 @@ void VMRegImpl::set_regName() { int i; for (i = 0; i < ConcreteRegisterImpl::max_gpr ; ) { regName[i++] = reg->name(); -#ifdef AMD64 regName[i++] = reg->name(); -#endif // AMD64 reg = reg->successor(); } diff --git a/src/hotspot/cpu/x86/vmreg_x86.hpp b/src/hotspot/cpu/x86/vmreg_x86.hpp index 6f7c7fafb32..032ce654f2f 100644 --- a/src/hotspot/cpu/x86/vmreg_x86.hpp +++ b/src/hotspot/cpu/x86/vmreg_x86.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2006, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2006, 2026, 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 @@ -52,14 +52,8 @@ inline bool is_KRegister() { } inline Register as_Register() { - - assert( is_Register(), "must be"); - // Yuk -#ifdef AMD64 + assert(is_Register(), "must be"); return ::as_Register(value() >> 1); -#else - return ::as_Register(value()); -#endif // AMD64 } inline FloatRegister as_FloatRegister() { @@ -82,9 +76,6 @@ inline KRegister as_KRegister() { inline bool is_concrete() { assert(is_reg(), "must be"); -#ifndef AMD64 - if (is_Register()) return true; -#endif // AMD64 // Do not use is_XMMRegister() here as it depends on the UseAVX setting. if (value() >= ConcreteRegisterImpl::max_fpr && value() < ConcreteRegisterImpl::max_xmm) { int base = value() - ConcreteRegisterImpl::max_fpr; diff --git a/src/hotspot/cpu/x86/vmreg_x86.inline.hpp b/src/hotspot/cpu/x86/vmreg_x86.inline.hpp index 1aeedc094fd..76d70900fe4 100644 --- a/src/hotspot/cpu/x86/vmreg_x86.inline.hpp +++ b/src/hotspot/cpu/x86/vmreg_x86.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2006, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2006, 2026, 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,7 +26,7 @@ #define CPU_X86_VMREG_X86_INLINE_HPP inline VMReg Register::RegisterImpl::as_VMReg() const { - return VMRegImpl::as_VMReg(encoding() LP64_ONLY( << 1 )); + return VMRegImpl::as_VMReg(encoding() << 1); } inline VMReg FloatRegister::FloatRegisterImpl::as_VMReg() const { diff --git a/src/hotspot/cpu/x86/x86.ad b/src/hotspot/cpu/x86/x86.ad index 5958db5d1eb..93b306c37d6 100644 --- a/src/hotspot/cpu/x86/x86.ad +++ b/src/hotspot/cpu/x86/x86.ad @@ -1,5 +1,5 @@ // -// Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. +// Copyright (c) 2011, 2026, 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 @@ -2633,17 +2633,19 @@ bool Matcher::supports_vector_calling_convention(void) { return EnableVectorSupport; } +static bool is_ndd_demotable_opr1(const MachNode* mdef) { + return ((mdef->flags() & Node::PD::Flag_ndd_demotable_opr1) != 0); +} + +static bool is_ndd_demotable_opr2(const MachNode* mdef) { + return ((mdef->flags() & Node::PD::Flag_ndd_demotable_opr2) != 0); +} + +#ifdef ASSERT static bool is_ndd_demotable(const MachNode* mdef) { - return ((mdef->flags() & Node::PD::Flag_ndd_demotable) != 0); -} - -static bool is_ndd_demotable_commutative(const MachNode* mdef) { - return ((mdef->flags() & Node::PD::Flag_ndd_demotable_commutative) != 0); -} - -static bool is_demotion_candidate(const MachNode* mdef) { - return (is_ndd_demotable(mdef) || is_ndd_demotable_commutative(mdef)); + return (is_ndd_demotable_opr1(mdef) || is_ndd_demotable_opr2(mdef)); } +#endif bool Matcher::is_register_biasing_candidate(const MachNode* mdef, int oper_index) { @@ -2653,8 +2655,8 @@ bool Matcher::is_register_biasing_candidate(const MachNode* mdef, if (mdef->num_opnds() <= oper_index || mdef->operand_index(oper_index) < 0 || mdef->in(mdef->operand_index(oper_index)) == nullptr) { - assert(oper_index != 1 || !is_demotion_candidate(mdef), "%s", mdef->Name()); - assert(oper_index != 2 || !is_ndd_demotable_commutative(mdef), "%s", mdef->Name()); + assert(oper_index != 1 || !is_ndd_demotable_opr1(mdef), "%s", mdef->Name()); + assert(oper_index != 2 || !is_ndd_demotable_opr2(mdef), "%s", mdef->Name()); return false; } @@ -2662,14 +2664,13 @@ bool Matcher::is_register_biasing_candidate(const MachNode* mdef, // address computation. Biasing def towards any address component will not // result in NDD demotion by assembler. if (mdef->operand_num_edges(oper_index) != 1) { - assert(!is_ndd_demotable(mdef), "%s", mdef->Name()); return false; } // Demotion candidate must be register mask compatible with definition. const RegMask& oper_mask = mdef->in_RegMask(mdef->operand_index(oper_index)); if (!oper_mask.overlap(mdef->out_RegMask())) { - assert(!is_demotion_candidate(mdef), "%s", mdef->Name()); + assert(!is_ndd_demotable(mdef), "%s", mdef->Name()); return false; } @@ -2681,12 +2682,12 @@ bool Matcher::is_register_biasing_candidate(const MachNode* mdef, // EVEX prefix with shorter REX/REX2 encoding. Demotion candidates // are decorated with a special flag by instruction selector. case 1: - return is_demotion_candidate(mdef); + return is_ndd_demotable_opr1(mdef); // Definition operand of commutative operation can be biased towards second // operand. case 2: - return is_ndd_demotable_commutative(mdef); + return is_ndd_demotable_opr2(mdef); // Current scheme only selects up to two biasing candidates default: @@ -2888,9 +2889,9 @@ public: Flag_clears_zero_flag = Node::_last_flag << 9, Flag_clears_overflow_flag = Node::_last_flag << 10, Flag_clears_sign_flag = Node::_last_flag << 11, - Flag_ndd_demotable = Node::_last_flag << 12, - Flag_ndd_demotable_commutative = Node::_last_flag << 13, - _last_flag = Flag_ndd_demotable_commutative + Flag_ndd_demotable_opr1 = Node::_last_flag << 12, + Flag_ndd_demotable_opr2 = Node::_last_flag << 13, + _last_flag = Flag_ndd_demotable_opr2 }; }; @@ -9872,7 +9873,7 @@ instruct addI_rReg_ndd(rRegI dst, rRegI src1, rRegI src2, rFlagsReg cr) predicate(UseAPX); match(Set dst (AddI src1 src2)); effect(KILL cr); - flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_carry_flag, PD::Flag_sets_parity_flag, PD::Flag_ndd_demotable_commutative); + flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_carry_flag, PD::Flag_sets_parity_flag, PD::Flag_ndd_demotable_opr1, PD::Flag_ndd_demotable_opr2); format %{ "eaddl $dst, $src1, $src2\t# int ndd" %} ins_encode %{ @@ -9900,7 +9901,7 @@ instruct addI_rReg_rReg_imm_ndd(rRegI dst, rRegI src1, immI src2, rFlagsReg cr) predicate(UseAPX); match(Set dst (AddI src1 src2)); effect(KILL cr); - flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_carry_flag, PD::Flag_sets_parity_flag, PD::Flag_ndd_demotable); + flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_carry_flag, PD::Flag_sets_parity_flag, PD::Flag_ndd_demotable_opr1); format %{ "eaddl $dst, $src1, $src2\t# int ndd" %} ins_encode %{ @@ -9943,7 +9944,7 @@ instruct addI_rReg_rReg_mem_ndd(rRegI dst, rRegI src1, memory src2, rFlagsReg cr predicate(UseAPX); match(Set dst (AddI src1 (LoadI src2))); effect(KILL cr); - flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_carry_flag, PD::Flag_sets_parity_flag, PD::Flag_ndd_demotable_commutative); + flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_carry_flag, PD::Flag_sets_parity_flag, PD::Flag_ndd_demotable_opr1, PD::Flag_ndd_demotable_opr2); ins_cost(150); format %{ "eaddl $dst, $src1, $src2\t# int ndd" %} @@ -10000,7 +10001,7 @@ instruct incI_rReg_ndd(rRegI dst, rRegI src, immI_1 val, rFlagsReg cr) predicate(UseAPX && UseIncDec); match(Set dst (AddI src val)); effect(KILL cr); - flag(PD::Flag_ndd_demotable); + flag(PD::Flag_ndd_demotable_opr1); format %{ "eincl $dst, $src\t# int ndd" %} ins_encode %{ @@ -10055,7 +10056,7 @@ instruct decI_rReg_ndd(rRegI dst, rRegI src, immI_M1 val, rFlagsReg cr) predicate(UseAPX && UseIncDec); match(Set dst (AddI src val)); effect(KILL cr); - flag(PD::Flag_ndd_demotable); + flag(PD::Flag_ndd_demotable_opr1); format %{ "edecl $dst, $src\t# int ndd" %} ins_encode %{ @@ -10162,7 +10163,7 @@ instruct addL_rReg_ndd(rRegL dst, rRegL src1, rRegL src2, rFlagsReg cr) predicate(UseAPX); match(Set dst (AddL src1 src2)); effect(KILL cr); - flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_carry_flag, PD::Flag_sets_parity_flag, PD::Flag_ndd_demotable_commutative); + flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_carry_flag, PD::Flag_sets_parity_flag, PD::Flag_ndd_demotable_opr1, PD::Flag_ndd_demotable_opr2); format %{ "eaddq $dst, $src1, $src2\t# long ndd" %} ins_encode %{ @@ -10190,7 +10191,7 @@ instruct addL_rReg_rReg_imm_ndd(rRegL dst, rRegL src1, immL32 src2, rFlagsReg cr predicate(UseAPX); match(Set dst (AddL src1 src2)); effect(KILL cr); - flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_carry_flag, PD::Flag_sets_parity_flag, PD::Flag_ndd_demotable); + flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_carry_flag, PD::Flag_sets_parity_flag, PD::Flag_ndd_demotable_opr1); format %{ "eaddq $dst, $src1, $src2\t# long ndd" %} ins_encode %{ @@ -10233,7 +10234,7 @@ instruct addL_rReg_rReg_mem_ndd(rRegL dst, rRegL src1, memory src2, rFlagsReg cr predicate(UseAPX); match(Set dst (AddL src1 (LoadL src2))); effect(KILL cr); - flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_carry_flag, PD::Flag_sets_parity_flag, PD::Flag_ndd_demotable_commutative); + flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_carry_flag, PD::Flag_sets_parity_flag, PD::Flag_ndd_demotable_opr1, PD::Flag_ndd_demotable_opr2); ins_cost(150); format %{ "eaddq $dst, $src1, $src2\t# long ndd" %} @@ -10289,7 +10290,7 @@ instruct incL_rReg_ndd(rRegL dst, rRegI src, immL1 val, rFlagsReg cr) predicate(UseAPX && UseIncDec); match(Set dst (AddL src val)); effect(KILL cr); - flag(PD::Flag_ndd_demotable); + flag(PD::Flag_ndd_demotable_opr1); format %{ "eincq $dst, $src\t# long ndd" %} ins_encode %{ @@ -10344,7 +10345,7 @@ instruct decL_rReg_ndd(rRegL dst, rRegL src, immL_M1 val, rFlagsReg cr) predicate(UseAPX && UseIncDec); match(Set dst (AddL src val)); effect(KILL cr); - flag(PD::Flag_ndd_demotable); + flag(PD::Flag_ndd_demotable_opr1); format %{ "edecq $dst, $src\t# long ndd" %} ins_encode %{ @@ -11059,7 +11060,7 @@ instruct subI_rReg_ndd(rRegI dst, rRegI src1, rRegI src2, rFlagsReg cr) predicate(UseAPX); match(Set dst (SubI src1 src2)); effect(KILL cr); - flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_carry_flag, PD::Flag_sets_parity_flag, PD::Flag_ndd_demotable); + flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_carry_flag, PD::Flag_sets_parity_flag, PD::Flag_ndd_demotable_opr1); format %{ "esubl $dst, $src1, $src2\t# int ndd" %} ins_encode %{ @@ -11073,7 +11074,7 @@ instruct subI_rReg_rReg_imm_ndd(rRegI dst, rRegI src1, immI src2, rFlagsReg cr) predicate(UseAPX); match(Set dst (SubI src1 src2)); effect(KILL cr); - flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_carry_flag, PD::Flag_sets_parity_flag, PD::Flag_ndd_demotable); + flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_carry_flag, PD::Flag_sets_parity_flag, PD::Flag_ndd_demotable_opr1); format %{ "esubl $dst, $src1, $src2\t# int ndd" %} ins_encode %{ @@ -11116,7 +11117,7 @@ instruct subI_rReg_rReg_mem_ndd(rRegI dst, rRegI src1, memory src2, rFlagsReg cr predicate(UseAPX); match(Set dst (SubI src1 (LoadI src2))); effect(KILL cr); - flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_carry_flag, PD::Flag_sets_parity_flag, PD::Flag_ndd_demotable); + flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_carry_flag, PD::Flag_sets_parity_flag, PD::Flag_ndd_demotable_opr1); ins_cost(150); format %{ "esubl $dst, $src1, $src2\t# int ndd" %} @@ -11174,7 +11175,7 @@ instruct subL_rReg_ndd(rRegL dst, rRegL src1, rRegL src2, rFlagsReg cr) predicate(UseAPX); match(Set dst (SubL src1 src2)); effect(KILL cr); - flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_carry_flag, PD::Flag_sets_parity_flag, PD::Flag_ndd_demotable); + flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_carry_flag, PD::Flag_sets_parity_flag, PD::Flag_ndd_demotable_opr1); format %{ "esubq $dst, $src1, $src2\t# long ndd" %} ins_encode %{ @@ -11188,7 +11189,7 @@ instruct subL_rReg_rReg_imm_ndd(rRegL dst, rRegL src1, immL32 src2, rFlagsReg cr predicate(UseAPX); match(Set dst (SubL src1 src2)); effect(KILL cr); - flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_carry_flag, PD::Flag_sets_parity_flag, PD::Flag_ndd_demotable); + flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_carry_flag, PD::Flag_sets_parity_flag, PD::Flag_ndd_demotable_opr1); format %{ "esubq $dst, $src1, $src2\t# long ndd" %} ins_encode %{ @@ -11231,7 +11232,7 @@ instruct subL_rReg_rReg_mem_ndd(rRegL dst, rRegL src1, memory src2, rFlagsReg cr predicate(UseAPX); match(Set dst (SubL src1 (LoadL src2))); effect(KILL cr); - flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_carry_flag, PD::Flag_sets_parity_flag, PD::Flag_ndd_demotable); + flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_carry_flag, PD::Flag_sets_parity_flag, PD::Flag_ndd_demotable_opr1); ins_cost(150); format %{ "esubq $dst, $src1, $src2\t# long ndd" %} @@ -11303,7 +11304,7 @@ instruct negI_rReg_ndd(rRegI dst, rRegI src, immI_0 zero, rFlagsReg cr) predicate(UseAPX); match(Set dst (SubI zero src)); effect(KILL cr); - flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_ndd_demotable); + flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_ndd_demotable_opr2); format %{ "enegl $dst, $src\t# int ndd" %} ins_encode %{ @@ -11331,7 +11332,7 @@ instruct negI_rReg_2_ndd(rRegI dst, rRegI src, rFlagsReg cr) predicate(UseAPX); match(Set dst (NegI src)); effect(KILL cr); - flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_ndd_demotable); + flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_ndd_demotable_opr1); format %{ "enegl $dst, $src\t# int ndd" %} ins_encode %{ @@ -11372,7 +11373,7 @@ instruct negL_rReg_ndd(rRegL dst, rRegL src, immL0 zero, rFlagsReg cr) predicate(UseAPX); match(Set dst (SubL zero src)); effect(KILL cr); - flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_ndd_demotable); + flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_ndd_demotable_opr2); format %{ "enegq $dst, $src\t# long ndd" %} ins_encode %{ @@ -11400,7 +11401,7 @@ instruct negL_rReg_2_ndd(rRegL dst, rRegL src, rFlagsReg cr) predicate(UseAPX); match(Set dst (NegL src)); effect(KILL cr); - flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_ndd_demotable); + flag(PD::Flag_sets_overflow_flag, PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_ndd_demotable_opr1); format %{ "enegq $dst, $src\t# long ndd" %} ins_encode %{ @@ -11445,7 +11446,7 @@ instruct mulI_rReg_ndd(rRegI dst, rRegI src1, rRegI src2, rFlagsReg cr) predicate(UseAPX); match(Set dst (MulI src1 src2)); effect(KILL cr); - flag(PD::Flag_ndd_demotable_commutative); + flag(PD::Flag_ndd_demotable_opr1, PD::Flag_ndd_demotable_opr2); ins_cost(300); format %{ "eimull $dst, $src1, $src2\t# int ndd" %} @@ -11487,7 +11488,7 @@ instruct mulI_rReg_rReg_mem_ndd(rRegI dst, rRegI src1, memory src2, rFlagsReg cr predicate(UseAPX); match(Set dst (MulI src1 (LoadI src2))); effect(KILL cr); - flag(PD::Flag_ndd_demotable); + flag(PD::Flag_ndd_demotable_opr1, PD::Flag_ndd_demotable_opr2); ins_cost(350); format %{ "eimull $dst, $src1, $src2\t# int ndd" %} @@ -11539,7 +11540,7 @@ instruct mulL_rReg_ndd(rRegL dst, rRegL src1, rRegL src2, rFlagsReg cr) predicate(UseAPX); match(Set dst (MulL src1 src2)); effect(KILL cr); - flag(PD::Flag_ndd_demotable_commutative); + flag(PD::Flag_ndd_demotable_opr1, PD::Flag_ndd_demotable_opr2); ins_cost(300); format %{ "eimulq $dst, $src1, $src2\t# long ndd" %} @@ -11581,7 +11582,7 @@ instruct mulL_rReg_rReg_mem_ndd(rRegL dst, rRegL src1, memory src2, rFlagsReg cr predicate(UseAPX); match(Set dst (MulL src1 (LoadL src2))); effect(KILL cr); - flag(PD::Flag_ndd_demotable_commutative); + flag(PD::Flag_ndd_demotable_opr1, PD::Flag_ndd_demotable_opr2); ins_cost(350); format %{ "eimulq $dst, $src1, $src2 \t# long" %} @@ -11856,7 +11857,7 @@ instruct salI_rReg_immI2_ndd(rRegI dst, rRegI src, immI2 shift, rFlagsReg cr) predicate(UseAPX); match(Set dst (LShiftI src shift)); effect(KILL cr); - flag(PD::Flag_ndd_demotable); + flag(PD::Flag_ndd_demotable_opr1); format %{ "esall $dst, $src, $shift\t# int(ndd)" %} ins_encode %{ @@ -11885,7 +11886,7 @@ instruct salI_rReg_imm_ndd(rRegI dst, rRegI src, immI8 shift, rFlagsReg cr) predicate(UseAPX); match(Set dst (LShiftI src shift)); effect(KILL cr); - flag(PD::Flag_ndd_demotable); + flag(PD::Flag_ndd_demotable_opr1); format %{ "esall $dst, $src, $shift\t# int (ndd)" %} ins_encode %{ @@ -11992,7 +11993,7 @@ instruct sarI_rReg_imm_ndd(rRegI dst, rRegI src, immI8 shift, rFlagsReg cr) predicate(UseAPX); match(Set dst (RShiftI src shift)); effect(KILL cr); - flag(PD::Flag_ndd_demotable); + flag(PD::Flag_ndd_demotable_opr1); format %{ "esarl $dst, $src, $shift\t# int (ndd)" %} ins_encode %{ @@ -12099,7 +12100,7 @@ instruct shrI_rReg_imm_ndd(rRegI dst, rRegI src, immI8 shift, rFlagsReg cr) predicate(UseAPX); match(Set dst (URShiftI src shift)); effect(KILL cr); - flag(PD::Flag_ndd_demotable); + flag(PD::Flag_ndd_demotable_opr1); format %{ "eshrl $dst, $src, $shift\t # int (ndd)" %} ins_encode %{ @@ -12207,7 +12208,7 @@ instruct salL_rReg_immI2_ndd(rRegL dst, rRegL src, immI2 shift, rFlagsReg cr) predicate(UseAPX); match(Set dst (LShiftL src shift)); effect(KILL cr); - flag(PD::Flag_ndd_demotable); + flag(PD::Flag_ndd_demotable_opr1); format %{ "esalq $dst, $src, $shift\t# long (ndd)" %} ins_encode %{ @@ -12236,7 +12237,7 @@ instruct salL_rReg_imm_ndd(rRegL dst, rRegL src, immI8 shift, rFlagsReg cr) predicate(UseAPX); match(Set dst (LShiftL src shift)); effect(KILL cr); - flag(PD::Flag_ndd_demotable); + flag(PD::Flag_ndd_demotable_opr1); format %{ "esalq $dst, $src, $shift\t# long (ndd)" %} ins_encode %{ @@ -12343,7 +12344,7 @@ instruct sarL_rReg_imm_ndd(rRegL dst, rRegL src, immI shift, rFlagsReg cr) predicate(UseAPX); match(Set dst (RShiftL src shift)); effect(KILL cr); - flag(PD::Flag_ndd_demotable); + flag(PD::Flag_ndd_demotable_opr1); format %{ "esarq $dst, $src, $shift\t# long (ndd)" %} ins_encode %{ @@ -12450,7 +12451,7 @@ instruct shrL_rReg_imm_ndd(rRegL dst, rRegL src, immI8 shift, rFlagsReg cr) predicate(UseAPX); match(Set dst (URShiftL src shift)); effect(KILL cr); - flag(PD::Flag_ndd_demotable); + flag(PD::Flag_ndd_demotable_opr1); format %{ "eshrq $dst, $src, $shift\t# long (ndd)" %} ins_encode %{ @@ -12622,7 +12623,7 @@ instruct rolI_rReg_Var_ndd(rRegI dst, rRegI src, rcx_RegI shift, rFlagsReg cr) predicate(UseAPX && n->bottom_type()->basic_type() == T_INT); match(Set dst (RotateLeft src shift)); effect(KILL cr); - flag(PD::Flag_ndd_demotable); + flag(PD::Flag_ndd_demotable_opr1); format %{ "eroll $dst, $src, $shift\t# rotate left (int ndd)" %} ins_encode %{ @@ -12687,7 +12688,7 @@ instruct rorI_rReg_Var_ndd(rRegI dst, rRegI src, rcx_RegI shift, rFlagsReg cr) predicate(UseAPX && n->bottom_type()->basic_type() == T_INT); match(Set dst (RotateRight src shift)); effect(KILL cr); - flag(PD::Flag_ndd_demotable); + flag(PD::Flag_ndd_demotable_opr1); format %{ "erorl $dst, $src, $shift\t# rotate right(int ndd)" %} ins_encode %{ @@ -12754,7 +12755,7 @@ instruct rolL_rReg_Var_ndd(rRegL dst, rRegL src, rcx_RegI shift, rFlagsReg cr) predicate(UseAPX && n->bottom_type()->basic_type() == T_LONG); match(Set dst (RotateLeft src shift)); effect(KILL cr); - flag(PD::Flag_ndd_demotable); + flag(PD::Flag_ndd_demotable_opr1); format %{ "erolq $dst, $src, $shift\t# rotate left(long ndd)" %} ins_encode %{ @@ -12819,7 +12820,7 @@ instruct rorL_rReg_Var_ndd(rRegL dst, rRegL src, rcx_RegI shift, rFlagsReg cr) predicate(UseAPX && n->bottom_type()->basic_type() == T_LONG); match(Set dst (RotateRight src shift)); effect(KILL cr); - flag(PD::Flag_ndd_demotable); + flag(PD::Flag_ndd_demotable_opr1); format %{ "erorq $dst, $src, $shift\t# rotate right(long ndd)" %} ins_encode %{ @@ -12897,7 +12898,7 @@ instruct andI_rReg_ndd(rRegI dst, rRegI src1, rRegI src2, rFlagsReg cr) predicate(UseAPX); match(Set dst (AndI src1 src2)); effect(KILL cr); - flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable_commutative); + flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable_opr1, PD::Flag_ndd_demotable_opr2); format %{ "eandl $dst, $src1, $src2\t# int ndd" %} ins_encode %{ @@ -12990,7 +12991,7 @@ instruct andI_rReg_rReg_imm_ndd(rRegI dst, rRegI src1, immI src2, rFlagsReg cr) predicate(UseAPX); match(Set dst (AndI src1 src2)); effect(KILL cr); - flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable); + flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable_opr1); format %{ "eandl $dst, $src1, $src2\t# int ndd" %} ins_encode %{ @@ -13034,7 +13035,7 @@ instruct andI_rReg_rReg_mem_ndd(rRegI dst, rRegI src1, memory src2, rFlagsReg cr predicate(UseAPX); match(Set dst (AndI src1 (LoadI src2))); effect(KILL cr); - flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable_commutative); + flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable_opr1, PD::Flag_ndd_demotable_opr2); ins_cost(150); format %{ "eandl $dst, $src1, $src2\t# int ndd" %} @@ -13234,7 +13235,7 @@ instruct orI_rReg_ndd(rRegI dst, rRegI src1, rRegI src2, rFlagsReg cr) predicate(UseAPX); match(Set dst (OrI src1 src2)); effect(KILL cr); - flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable_commutative); + flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable_opr1, PD::Flag_ndd_demotable_opr2); format %{ "eorl $dst, $src1, $src2\t# int ndd" %} ins_encode %{ @@ -13263,7 +13264,7 @@ instruct orI_rReg_rReg_imm_ndd(rRegI dst, rRegI src1, immI src2, rFlagsReg cr) predicate(UseAPX); match(Set dst (OrI src1 src2)); effect(KILL cr); - flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable); + flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable_opr1); format %{ "eorl $dst, $src1, $src2\t# int ndd" %} ins_encode %{ @@ -13277,7 +13278,7 @@ instruct orI_rReg_imm_rReg_ndd(rRegI dst, immI src1, rRegI src2, rFlagsReg cr) predicate(UseAPX); match(Set dst (OrI src1 src2)); effect(KILL cr); - flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable); + flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable_opr1); format %{ "eorl $dst, $src2, $src1\t# int ndd" %} ins_encode %{ @@ -13321,7 +13322,7 @@ instruct orI_rReg_rReg_mem_ndd(rRegI dst, rRegI src1, memory src2, rFlagsReg cr) predicate(UseAPX); match(Set dst (OrI src1 (LoadI src2))); effect(KILL cr); - flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable); + flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable_opr1, PD::Flag_ndd_demotable_opr2); ins_cost(150); format %{ "eorl $dst, $src1, $src2\t# int ndd" %} @@ -13397,7 +13398,7 @@ instruct xorI_rReg_ndd(rRegI dst, rRegI src1, rRegI src2, rFlagsReg cr) predicate(UseAPX); match(Set dst (XorI src1 src2)); effect(KILL cr); - flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable_commutative); + flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable_opr1, PD::Flag_ndd_demotable_opr2); format %{ "exorl $dst, $src1, $src2\t# int ndd" %} ins_encode %{ @@ -13423,7 +13424,7 @@ instruct xorI_rReg_im1_ndd(rRegI dst, rRegI src, immI_M1 imm) %{ match(Set dst (XorI src imm)); predicate(UseAPX); - flag(PD::Flag_ndd_demotable); + flag(PD::Flag_ndd_demotable_opr1); format %{ "enotl $dst, $src" %} ins_encode %{ @@ -13454,7 +13455,7 @@ instruct xorI_rReg_rReg_imm_ndd(rRegI dst, rRegI src1, immI src2, rFlagsReg cr) predicate(UseAPX && n->in(2)->bottom_type()->is_int()->get_con() != -1); match(Set dst (XorI src1 src2)); effect(KILL cr); - flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable); + flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable_opr1); format %{ "exorl $dst, $src1, $src2\t# int ndd" %} ins_encode %{ @@ -13500,7 +13501,7 @@ instruct xorI_rReg_rReg_mem_ndd(rRegI dst, rRegI src1, memory src2, rFlagsReg cr predicate(UseAPX); match(Set dst (XorI src1 (LoadI src2))); effect(KILL cr); - flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable); + flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable_opr1, PD::Flag_ndd_demotable_opr2); ins_cost(150); format %{ "exorl $dst, $src1, $src2\t# int ndd" %} @@ -13579,7 +13580,7 @@ instruct andL_rReg_ndd(rRegL dst, rRegL src1, rRegL src2, rFlagsReg cr) predicate(UseAPX); match(Set dst (AndL src1 src2)); effect(KILL cr); - flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable_commutative); + flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable_opr1, PD::Flag_ndd_demotable_opr2); format %{ "eandq $dst, $src1, $src2\t# long ndd" %} ins_encode %{ @@ -13635,7 +13636,7 @@ instruct andL_rReg_rReg_imm_ndd(rRegL dst, rRegL src1, immL32 src2, rFlagsReg cr predicate(UseAPX); match(Set dst (AndL src1 src2)); effect(KILL cr); - flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable); + flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable_opr1); format %{ "eandq $dst, $src1, $src2\t# long ndd" %} ins_encode %{ @@ -13679,7 +13680,7 @@ instruct andL_rReg_rReg_mem_ndd(rRegL dst, rRegL src1, memory src2, rFlagsReg cr predicate(UseAPX); match(Set dst (AndL src1 (LoadL src2))); effect(KILL cr); - flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable_commutative); + flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable_opr1, PD::Flag_ndd_demotable_opr2); ins_cost(150); format %{ "eandq $dst, $src1, $src2\t# long ndd" %} @@ -13882,7 +13883,7 @@ instruct orL_rReg_ndd(rRegL dst, rRegL src1, rRegL src2, rFlagsReg cr) predicate(UseAPX); match(Set dst (OrL src1 src2)); effect(KILL cr); - flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable_commutative); + flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable_opr1, PD::Flag_ndd_demotable_opr2); format %{ "eorq $dst, $src1, $src2\t# long ndd" %} ins_encode %{ @@ -13937,7 +13938,7 @@ instruct orL_rReg_rReg_imm_ndd(rRegL dst, rRegL src1, immL32 src2, rFlagsReg cr) predicate(UseAPX); match(Set dst (OrL src1 src2)); effect(KILL cr); - flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable); + flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable_opr1); format %{ "eorq $dst, $src1, $src2\t# long ndd" %} ins_encode %{ @@ -13951,7 +13952,7 @@ instruct orL_rReg_imm_rReg_ndd(rRegL dst, immL32 src1, rRegL src2, rFlagsReg cr) predicate(UseAPX); match(Set dst (OrL src1 src2)); effect(KILL cr); - flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable); + flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable_opr1); format %{ "eorq $dst, $src2, $src1\t# long ndd" %} ins_encode %{ @@ -13996,7 +13997,7 @@ instruct orL_rReg_rReg_mem_ndd(rRegL dst, rRegL src1, memory src2, rFlagsReg cr) predicate(UseAPX); match(Set dst (OrL src1 (LoadL src2))); effect(KILL cr); - flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable_commutative); + flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable_opr1, PD::Flag_ndd_demotable_opr2); ins_cost(150); format %{ "eorq $dst, $src1, $src2\t# long ndd" %} @@ -14075,7 +14076,7 @@ instruct xorL_rReg_ndd(rRegL dst, rRegL src1, rRegL src2, rFlagsReg cr) predicate(UseAPX); match(Set dst (XorL src1 src2)); effect(KILL cr); - flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable_commutative); + flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable_opr1, PD::Flag_ndd_demotable_opr2); format %{ "exorq $dst, $src1, $src2\t# long ndd" %} ins_encode %{ @@ -14101,7 +14102,7 @@ instruct xorL_rReg_im1_ndd(rRegL dst,rRegL src, immL_M1 imm) %{ predicate(UseAPX); match(Set dst (XorL src imm)); - flag(PD::Flag_ndd_demotable); + flag(PD::Flag_ndd_demotable_opr1); format %{ "enotq $dst, $src" %} ins_encode %{ @@ -14132,7 +14133,7 @@ instruct xorL_rReg_rReg_imm(rRegL dst, rRegL src1, immL32 src2, rFlagsReg cr) predicate(UseAPX && n->in(2)->bottom_type()->is_long()->get_con() != -1L); match(Set dst (XorL src1 src2)); effect(KILL cr); - flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable); + flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable_opr1); format %{ "exorq $dst, $src1, $src2\t# long ndd" %} ins_encode %{ @@ -14178,7 +14179,7 @@ instruct xorL_rReg_rReg_mem_ndd(rRegL dst, rRegL src1, memory src2, rFlagsReg cr predicate(UseAPX); match(Set dst (XorL src1 (LoadL src2))); effect(KILL cr); - flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable_commutative); + flag(PD::Flag_sets_sign_flag, PD::Flag_sets_zero_flag, PD::Flag_sets_parity_flag, PD::Flag_clears_overflow_flag, PD::Flag_clears_carry_flag, PD::Flag_ndd_demotable_opr1, PD::Flag_ndd_demotable_opr2); ins_cost(150); format %{ "exorq $dst, $src1, $src2\t# long ndd" %} @@ -16633,7 +16634,7 @@ instruct minI_rReg_ndd(rRegI dst, rRegI src1, rRegI src2) predicate(UseAPX); match(Set dst (MinI src1 src2)); effect(DEF dst, USE src1, USE src2); - flag(PD::Flag_ndd_demotable); + flag(PD::Flag_ndd_demotable_opr1); ins_cost(200); expand %{ @@ -16685,7 +16686,7 @@ instruct maxI_rReg_ndd(rRegI dst, rRegI src1, rRegI src2) predicate(UseAPX); match(Set dst (MaxI src1 src2)); effect(DEF dst, USE src1, USE src2); - flag(PD::Flag_ndd_demotable); + flag(PD::Flag_ndd_demotable_opr1); ins_cost(200); expand %{ diff --git a/src/hotspot/os/aix/libodm_aix.cpp b/src/hotspot/os/aix/libodm_aix.cpp index 035703a1771..38e8067181a 100644 --- a/src/hotspot/os/aix/libodm_aix.cpp +++ b/src/hotspot/os/aix/libodm_aix.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2015, 2019 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -23,15 +23,15 @@ * */ +#include "cppstdlib/cstdlib.hpp" #include "libodm_aix.hpp" #include "misc_aix.hpp" -#include -#include -#include #include "runtime/arguments.hpp" #include "runtime/os.hpp" #include "utilities/permitForbiddenFunctions.hpp" +#include +#include dynamicOdm::dynamicOdm() { const char* libodmname = "/usr/lib/libodm.a(shr_64.o)"; diff --git a/src/hotspot/os/aix/libperfstat_aix.hpp b/src/hotspot/os/aix/libperfstat_aix.hpp index 5a6ddfd8f4d..aff11cfb41f 100644 --- a/src/hotspot/os/aix/libperfstat_aix.hpp +++ b/src/hotspot/os/aix/libperfstat_aix.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2012, 2024 SAP SE. All rights reserved. * Copyright (c) 2022, IBM Corp. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. @@ -32,8 +32,9 @@ #ifndef OS_AIX_LIBPERFSTAT_AIX_HPP #define OS_AIX_LIBPERFSTAT_AIX_HPP +#include "cppstdlib/cstdlib.hpp" + #include -#include /////////////////////////////////////////////////////////////////////////////////////////////// // These are excerpts from the AIX 7.1 libperfstat.h - diff --git a/src/hotspot/os/aix/os_aix.cpp b/src/hotspot/os/aix/os_aix.cpp index f88729cdc66..d7c1911a914 100644 --- a/src/hotspot/os/aix/os_aix.cpp +++ b/src/hotspot/os/aix/os_aix.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2012, 2025 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -258,10 +258,18 @@ bool os::free_memory(physical_memory_size_type& value) { return Aix::available_memory(value); } +bool os::Machine::free_memory(physical_memory_size_type& value) { + return Aix::available_memory(value); +} + bool os::available_memory(physical_memory_size_type& value) { return Aix::available_memory(value); } +bool os::Machine::available_memory(physical_memory_size_type& value) { + return Aix::available_memory(value); +} + bool os::Aix::available_memory(physical_memory_size_type& value) { os::Aix::meminfo_t mi; if (os::Aix::get_meminfo(&mi)) { @@ -273,6 +281,10 @@ bool os::Aix::available_memory(physical_memory_size_type& value) { } bool os::total_swap_space(physical_memory_size_type& value) { + return Machine::total_swap_space(value); +} + +bool os::Machine::total_swap_space(physical_memory_size_type& value) { perfstat_memory_total_t memory_info; if (libperfstat::perfstat_memory_total(nullptr, &memory_info, sizeof(perfstat_memory_total_t), 1) == -1) { return false; @@ -282,6 +294,10 @@ bool os::total_swap_space(physical_memory_size_type& value) { } bool os::free_swap_space(physical_memory_size_type& value) { + return Machine::free_swap_space(value); +} + +bool os::Machine::free_swap_space(physical_memory_size_type& value) { perfstat_memory_total_t memory_info; if (libperfstat::perfstat_memory_total(nullptr, &memory_info, sizeof(perfstat_memory_total_t), 1) == -1) { return false; @@ -294,6 +310,10 @@ physical_memory_size_type os::physical_memory() { return Aix::physical_memory(); } +physical_memory_size_type os::Machine::physical_memory() { + return Aix::physical_memory(); +} + size_t os::rss() { return (size_t)0; } // Cpu architecture string @@ -2264,6 +2284,10 @@ int os::active_processor_count() { return ActiveProcessorCount; } + return Machine::active_processor_count(); +} + +int os::Machine::active_processor_count() { int online_cpus = ::sysconf(_SC_NPROCESSORS_ONLN); assert(online_cpus > 0 && online_cpus <= processor_count(), "sanity check"); return online_cpus; diff --git a/src/hotspot/os/aix/os_perf_aix.cpp b/src/hotspot/os/aix/os_perf_aix.cpp index 8444002b871..aa8819d035f 100644 --- a/src/hotspot/os/aix/os_perf_aix.cpp +++ b/src/hotspot/os/aix/os_perf_aix.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2022, 2024, IBM Corp. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -23,6 +23,7 @@ * */ +#include "cppstdlib/cstdlib.hpp" #include "jvm.h" #include "libperfstat_aix.hpp" #include "memory/allocation.inline.hpp" @@ -46,7 +47,6 @@ #include #include #include -#include #include typedef struct { diff --git a/src/hotspot/os/bsd/memMapPrinter_macosx.cpp b/src/hotspot/os/bsd/memMapPrinter_macosx.cpp index a7ddab04d85..30e258c9d2c 100644 --- a/src/hotspot/os/bsd/memMapPrinter_macosx.cpp +++ b/src/hotspot/os/bsd/memMapPrinter_macosx.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2023, 2024, Red Hat, Inc. and/or its affiliates. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -25,6 +25,7 @@ #if defined(__APPLE__) +#include "cppstdlib/cstdlib.hpp" #include "nmt/memMapPrinter.hpp" #include "runtime/os.hpp" #include "utilities/align.hpp" @@ -34,7 +35,6 @@ #include #include -#include #include #include @@ -132,7 +132,7 @@ public: static const char* tagToStr(uint32_t user_tag) { switch (user_tag) { case 0: - return 0; + return nullptr; X1(MALLOC, malloc); X1(MALLOC_SMALL, malloc_small); X1(MALLOC_LARGE, malloc_large); diff --git a/src/hotspot/os/bsd/os_bsd.cpp b/src/hotspot/os/bsd/os_bsd.cpp index 61de48bb7fa..0e21c2d1785 100644 --- a/src/hotspot/os/bsd/os_bsd.cpp +++ b/src/hotspot/os/bsd/os_bsd.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, 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 @@ -137,10 +137,18 @@ bool os::available_memory(physical_memory_size_type& value) { return Bsd::available_memory(value); } +bool os::Machine::available_memory(physical_memory_size_type& value) { + return Bsd::available_memory(value); +} + bool os::free_memory(physical_memory_size_type& value) { return Bsd::available_memory(value); } +bool os::Machine::free_memory(physical_memory_size_type& value) { + return Bsd::available_memory(value); +} + // Available here means free. Note that this number is of no much use. As an estimate // for future memory pressure it is far too conservative, since MacOS will use a lot // of unused memory for caches, and return it willingly in case of needs. @@ -181,6 +189,10 @@ void os::Bsd::print_uptime_info(outputStream* st) { } bool os::total_swap_space(physical_memory_size_type& value) { + return Machine::total_swap_space(value); +} + +bool os::Machine::total_swap_space(physical_memory_size_type& value) { #if defined(__APPLE__) struct xsw_usage vmusage; size_t size = sizeof(vmusage); @@ -195,6 +207,10 @@ bool os::total_swap_space(physical_memory_size_type& value) { } bool os::free_swap_space(physical_memory_size_type& value) { + return Machine::free_swap_space(value); +} + +bool os::Machine::free_swap_space(physical_memory_size_type& value) { #if defined(__APPLE__) struct xsw_usage vmusage; size_t size = sizeof(vmusage); @@ -212,6 +228,10 @@ physical_memory_size_type os::physical_memory() { return Bsd::physical_memory(); } +physical_memory_size_type os::Machine::physical_memory() { + return Bsd::physical_memory(); +} + size_t os::rss() { size_t rss = 0; #ifdef __APPLE__ @@ -608,7 +628,7 @@ static void *thread_native_entry(Thread *thread) { log_info(os, thread)("Thread finished (tid: %zu, pthread id: %zu).", os::current_thread_id(), (uintx) pthread_self()); - return 0; + return nullptr; } bool os::create_thread(Thread* thread, ThreadType thr_type, @@ -1400,7 +1420,7 @@ int os::get_loaded_modules_info(os::LoadedModulesCallbackFunc callback, void *pa #elif defined(__APPLE__) for (uint32_t i = 1; i < _dyld_image_count(); i++) { // Value for top_address is returned as 0 since we don't have any information about module size - if (callback(_dyld_get_image_name(i), (address)_dyld_get_image_header(i), (address)0, param)) { + if (callback(_dyld_get_image_name(i), (address)_dyld_get_image_header(i), nullptr, param)) { return 1; } } @@ -2189,6 +2209,10 @@ int os::active_processor_count() { return ActiveProcessorCount; } + return Machine::active_processor_count(); +} + +int os::Machine::active_processor_count() { return _processor_count; } diff --git a/src/hotspot/os/linux/cgroupSubsystem_linux.cpp b/src/hotspot/os/linux/cgroupSubsystem_linux.cpp index f9c6f794ebd..e49d070890e 100644 --- a/src/hotspot/os/linux/cgroupSubsystem_linux.cpp +++ b/src/hotspot/os/linux/cgroupSubsystem_linux.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, 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 @@ -631,22 +631,20 @@ void CgroupSubsystemFactory::cleanup(CgroupInfo* cg_infos) { * return: * true if there were no errors. false otherwise. */ -bool CgroupSubsystem::active_processor_count(int& value) { - int cpu_count; - int result = -1; - +bool CgroupSubsystem::active_processor_count(double& value) { // We use a cache with a timeout to avoid performing expensive // computations in the event this function is called frequently. // [See 8227006]. - CachingCgroupController* contrl = cpu_controller(); - CachedMetric* cpu_limit = contrl->metrics_cache(); + CachingCgroupController* contrl = cpu_controller(); + CachedMetric* cpu_limit = contrl->metrics_cache(); if (!cpu_limit->should_check_metric()) { - value = (int)cpu_limit->value(); - log_trace(os, container)("CgroupSubsystem::active_processor_count (cached): %d", value); + value = cpu_limit->value(); + log_trace(os, container)("CgroupSubsystem::active_processor_count (cached): %.2f", value); return true; } - cpu_count = os::Linux::active_processor_count(); + int cpu_count = os::Linux::active_processor_count(); + double result = -1; if (!CgroupUtil::processor_count(contrl->controller(), cpu_count, result)) { return false; } @@ -671,8 +669,8 @@ bool CgroupSubsystem::active_processor_count(int& value) { */ bool CgroupSubsystem::memory_limit_in_bytes(physical_memory_size_type upper_bound, physical_memory_size_type& value) { - CachingCgroupController* contrl = memory_controller(); - CachedMetric* memory_limit = contrl->metrics_cache(); + CachingCgroupController* contrl = memory_controller(); + CachedMetric* memory_limit = contrl->metrics_cache(); if (!memory_limit->should_check_metric()) { value = memory_limit->value(); return true; diff --git a/src/hotspot/os/linux/cgroupSubsystem_linux.hpp b/src/hotspot/os/linux/cgroupSubsystem_linux.hpp index 522b64f8816..d083a9985c2 100644 --- a/src/hotspot/os/linux/cgroupSubsystem_linux.hpp +++ b/src/hotspot/os/linux/cgroupSubsystem_linux.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, 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 @@ -181,20 +181,21 @@ class CgroupController: public CHeapObj { static bool limit_from_str(char* limit_str, physical_memory_size_type& value); }; +template class CachedMetric : public CHeapObj{ private: - volatile physical_memory_size_type _metric; + volatile MetricType _metric; volatile jlong _next_check_counter; public: CachedMetric() { - _metric = value_unlimited; + _metric = static_cast(value_unlimited); _next_check_counter = min_jlong; } bool should_check_metric() { return os::elapsed_counter() > _next_check_counter; } - physical_memory_size_type value() { return _metric; } - void set_value(physical_memory_size_type value, jlong timeout) { + MetricType value() { return _metric; } + void set_value(MetricType value, jlong timeout) { _metric = value; // Metric is unlikely to change, but we want to remain // responsive to configuration changes. A very short grace time @@ -205,19 +206,19 @@ class CachedMetric : public CHeapObj{ } }; -template +template class CachingCgroupController : public CHeapObj { private: T* _controller; - CachedMetric* _metrics_cache; + CachedMetric* _metrics_cache; public: CachingCgroupController(T* cont) { _controller = cont; - _metrics_cache = new CachedMetric(); + _metrics_cache = new CachedMetric(); } - CachedMetric* metrics_cache() { return _metrics_cache; } + CachedMetric* metrics_cache() { return _metrics_cache; } T* controller() { return _controller; } }; @@ -277,7 +278,7 @@ class CgroupMemoryController: public CHeapObj { class CgroupSubsystem: public CHeapObj { public: bool memory_limit_in_bytes(physical_memory_size_type upper_bound, physical_memory_size_type& value); - bool active_processor_count(int& value); + bool active_processor_count(double& value); virtual bool pids_max(uint64_t& value) = 0; virtual bool pids_current(uint64_t& value) = 0; @@ -286,8 +287,8 @@ class CgroupSubsystem: public CHeapObj { virtual char * cpu_cpuset_cpus() = 0; virtual char * cpu_cpuset_memory_nodes() = 0; virtual const char * container_type() = 0; - virtual CachingCgroupController* memory_controller() = 0; - virtual CachingCgroupController* cpu_controller() = 0; + virtual CachingCgroupController* memory_controller() = 0; + virtual CachingCgroupController* cpu_controller() = 0; virtual CgroupCpuacctController* cpuacct_controller() = 0; bool cpu_quota(int& value); diff --git a/src/hotspot/os/linux/cgroupUtil_linux.cpp b/src/hotspot/os/linux/cgroupUtil_linux.cpp index 7aa07d53148..570b335940b 100644 --- a/src/hotspot/os/linux/cgroupUtil_linux.cpp +++ b/src/hotspot/os/linux/cgroupUtil_linux.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2024, 2025, Red Hat, Inc. + * Copyright (c) 2026, 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,9 +26,8 @@ #include "cgroupUtil_linux.hpp" #include "os_linux.hpp" -bool CgroupUtil::processor_count(CgroupCpuController* cpu_ctrl, int upper_bound, int& value) { +bool CgroupUtil::processor_count(CgroupCpuController* cpu_ctrl, int upper_bound, double& value) { assert(upper_bound > 0, "upper bound of cpus must be positive"); - int limit_count = upper_bound; int quota = -1; int period = -1; if (!cpu_ctrl->cpu_quota(quota)) { @@ -37,20 +37,15 @@ bool CgroupUtil::processor_count(CgroupCpuController* cpu_ctrl, int upper_bound, return false; } int quota_count = 0; - int result = upper_bound; + double result = upper_bound; - if (quota > -1 && period > 0) { - quota_count = ceilf((float)quota / (float)period); - log_trace(os, container)("CPU Quota count based on quota/period: %d", quota_count); + if (quota > 0 && period > 0) { // Use quotas + double cpu_quota = static_cast(quota) / period; + log_trace(os, container)("CPU Quota based on quota/period: %.2f", cpu_quota); + result = MIN2(result, cpu_quota); } - // Use quotas - if (quota_count != 0) { - limit_count = quota_count; - } - - result = MIN2(upper_bound, limit_count); - log_trace(os, container)("OSContainer::active_processor_count: %d", result); + log_trace(os, container)("OSContainer::active_processor_count: %.2f", result); value = result; return true; } @@ -73,11 +68,11 @@ physical_memory_size_type CgroupUtil::get_updated_mem_limit(CgroupMemoryControll // Get an updated cpu limit. The return value is strictly less than or equal to the // passed in 'lowest' value. -int CgroupUtil::get_updated_cpu_limit(CgroupCpuController* cpu, +double CgroupUtil::get_updated_cpu_limit(CgroupCpuController* cpu, int lowest, int upper_bound) { assert(lowest > 0 && lowest <= upper_bound, "invariant"); - int cpu_limit_val = -1; + double cpu_limit_val = -1; if (CgroupUtil::processor_count(cpu, upper_bound, cpu_limit_val) && cpu_limit_val != upper_bound) { assert(cpu_limit_val <= upper_bound, "invariant"); if (lowest > cpu_limit_val) { @@ -172,7 +167,7 @@ void CgroupUtil::adjust_controller(CgroupCpuController* cpu) { assert(cg_path[0] == '/', "cgroup path must start with '/'"); int host_cpus = os::Linux::active_processor_count(); int lowest_limit = host_cpus; - int cpus = get_updated_cpu_limit(cpu, lowest_limit, host_cpus); + double cpus = get_updated_cpu_limit(cpu, lowest_limit, host_cpus); int orig_limit = lowest_limit != host_cpus ? lowest_limit : host_cpus; char* limit_cg_path = nullptr; while ((last_slash = strrchr(cg_path, '/')) != cg_path) { diff --git a/src/hotspot/os/linux/cgroupUtil_linux.hpp b/src/hotspot/os/linux/cgroupUtil_linux.hpp index d72bbd1cf1e..1fd2a7d872b 100644 --- a/src/hotspot/os/linux/cgroupUtil_linux.hpp +++ b/src/hotspot/os/linux/cgroupUtil_linux.hpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2024, Red Hat, Inc. + * Copyright (c) 2026, 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,7 +32,7 @@ class CgroupUtil: AllStatic { public: - static bool processor_count(CgroupCpuController* cpu, int upper_bound, int& value); + static bool processor_count(CgroupCpuController* cpu, int upper_bound, double& value); // Given a memory controller, adjust its path to a point in the hierarchy // that represents the closest memory limit. static void adjust_controller(CgroupMemoryController* m); @@ -42,9 +43,7 @@ class CgroupUtil: AllStatic { static physical_memory_size_type get_updated_mem_limit(CgroupMemoryController* m, physical_memory_size_type lowest, physical_memory_size_type upper_bound); - static int get_updated_cpu_limit(CgroupCpuController* c, - int lowest, - int upper_bound); + static double get_updated_cpu_limit(CgroupCpuController* c, int lowest, int upper_bound); }; #endif // CGROUP_UTIL_LINUX_HPP diff --git a/src/hotspot/os/linux/cgroupV1Subsystem_linux.cpp b/src/hotspot/os/linux/cgroupV1Subsystem_linux.cpp index 2df604083d2..c8f5a290c99 100644 --- a/src/hotspot/os/linux/cgroupV1Subsystem_linux.cpp +++ b/src/hotspot/os/linux/cgroupV1Subsystem_linux.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, 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 @@ -328,8 +328,8 @@ CgroupV1Subsystem::CgroupV1Subsystem(CgroupV1Controller* cpuset, _pids(pids) { CgroupUtil::adjust_controller(memory); CgroupUtil::adjust_controller(cpu); - _memory = new CachingCgroupController(memory); - _cpu = new CachingCgroupController(cpu); + _memory = new CachingCgroupController(memory); + _cpu = new CachingCgroupController(cpu); } bool CgroupV1Subsystem::is_containerized() { diff --git a/src/hotspot/os/linux/cgroupV1Subsystem_linux.hpp b/src/hotspot/os/linux/cgroupV1Subsystem_linux.hpp index f556bc57f26..af8d0efd378 100644 --- a/src/hotspot/os/linux/cgroupV1Subsystem_linux.hpp +++ b/src/hotspot/os/linux/cgroupV1Subsystem_linux.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, 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 @@ -214,15 +214,15 @@ class CgroupV1Subsystem: public CgroupSubsystem { const char * container_type() override { return "cgroupv1"; } - CachingCgroupController* memory_controller() override { return _memory; } - CachingCgroupController* cpu_controller() override { return _cpu; } + CachingCgroupController* memory_controller() override { return _memory; } + CachingCgroupController* cpu_controller() override { return _cpu; } CgroupCpuacctController* cpuacct_controller() override { return _cpuacct; } private: /* controllers */ - CachingCgroupController* _memory = nullptr; + CachingCgroupController* _memory = nullptr; CgroupV1Controller* _cpuset = nullptr; - CachingCgroupController* _cpu = nullptr; + CachingCgroupController* _cpu = nullptr; CgroupV1CpuacctController* _cpuacct = nullptr; CgroupV1Controller* _pids = nullptr; diff --git a/src/hotspot/os/linux/cgroupV2Subsystem_linux.cpp b/src/hotspot/os/linux/cgroupV2Subsystem_linux.cpp index c61d30e9236..30e1affc646 100644 --- a/src/hotspot/os/linux/cgroupV2Subsystem_linux.cpp +++ b/src/hotspot/os/linux/cgroupV2Subsystem_linux.cpp @@ -1,6 +1,6 @@ /* * Copyright (c) 2020, 2025, Red Hat Inc. - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, 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 @@ -156,8 +156,8 @@ CgroupV2Subsystem::CgroupV2Subsystem(CgroupV2MemoryController * memory, _unified(unified) { CgroupUtil::adjust_controller(memory); CgroupUtil::adjust_controller(cpu); - _memory = new CachingCgroupController(memory); - _cpu = new CachingCgroupController(cpu); + _memory = new CachingCgroupController(memory); + _cpu = new CachingCgroupController(cpu); _cpuacct = cpuacct; } diff --git a/src/hotspot/os/linux/cgroupV2Subsystem_linux.hpp b/src/hotspot/os/linux/cgroupV2Subsystem_linux.hpp index 39a4fabe9f6..998145c0ff9 100644 --- a/src/hotspot/os/linux/cgroupV2Subsystem_linux.hpp +++ b/src/hotspot/os/linux/cgroupV2Subsystem_linux.hpp @@ -1,6 +1,6 @@ /* * Copyright (c) 2020, 2024, Red Hat Inc. - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, 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 @@ -152,8 +152,8 @@ class CgroupV2Subsystem: public CgroupSubsystem { /* One unified controller */ CgroupV2Controller _unified; /* Caching wrappers for cpu/memory metrics */ - CachingCgroupController* _memory = nullptr; - CachingCgroupController* _cpu = nullptr; + CachingCgroupController* _memory = nullptr; + CachingCgroupController* _cpu = nullptr; CgroupCpuacctController* _cpuacct = nullptr; @@ -175,8 +175,8 @@ class CgroupV2Subsystem: public CgroupSubsystem { const char * container_type() override { return "cgroupv2"; } - CachingCgroupController* memory_controller() override { return _memory; } - CachingCgroupController* cpu_controller() override { return _cpu; } + CachingCgroupController* memory_controller() override { return _memory; } + CachingCgroupController* cpu_controller() override { return _cpu; } CgroupCpuacctController* cpuacct_controller() override { return _cpuacct; }; }; diff --git a/src/hotspot/os/linux/gc/z/zPhysicalMemoryBacking_linux.cpp b/src/hotspot/os/linux/gc/z/zPhysicalMemoryBacking_linux.cpp index 25ffd0b8078..28159ae4801 100644 --- a/src/hotspot/os/linux/gc/z/zPhysicalMemoryBacking_linux.cpp +++ b/src/hotspot/os/linux/gc/z/zPhysicalMemoryBacking_linux.cpp @@ -66,9 +66,6 @@ #endif // open(2) flags -#ifndef O_CLOEXEC -#define O_CLOEXEC 02000000 -#endif #ifndef O_TMPFILE #define O_TMPFILE (020000000 | O_DIRECTORY) #endif diff --git a/src/hotspot/os/linux/osContainer_linux.cpp b/src/hotspot/os/linux/osContainer_linux.cpp index 15a6403d07f..b46263efd99 100644 --- a/src/hotspot/os/linux/osContainer_linux.cpp +++ b/src/hotspot/os/linux/osContainer_linux.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, 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 @@ -86,8 +86,8 @@ void OSContainer::init() { // 2.) On a physical Linux system with a limit enforced by other means (like systemd slice) physical_memory_size_type mem_limit_val = value_unlimited; (void)memory_limit_in_bytes(mem_limit_val); // discard error and use default - int host_cpus = os::Linux::active_processor_count(); - int cpus = host_cpus; + double host_cpus = os::Linux::active_processor_count(); + double cpus = host_cpus; (void)active_processor_count(cpus); // discard error and use default any_mem_cpu_limit_present = mem_limit_val != value_unlimited || host_cpus != cpus; if (any_mem_cpu_limit_present) { @@ -127,8 +127,7 @@ bool OSContainer::available_memory_in_bytes(physical_memory_size_type& value) { return false; } -bool OSContainer::available_swap_in_bytes(physical_memory_size_type host_free_swap, - physical_memory_size_type& value) { +bool OSContainer::available_swap_in_bytes(physical_memory_size_type& value) { physical_memory_size_type mem_limit = 0; physical_memory_size_type mem_swap_limit = 0; if (memory_limit_in_bytes(mem_limit) && @@ -179,8 +178,7 @@ bool OSContainer::available_swap_in_bytes(physical_memory_size_type host_free_sw assert(num < 25, "buffer too small"); mem_limit_buf[num] = '\0'; log_trace(os,container)("OSContainer::available_swap_in_bytes: container_swap_limit=%s" - " container_mem_limit=%s, host_free_swap: " PHYS_MEM_TYPE_FORMAT, - mem_swap_buf, mem_limit_buf, host_free_swap); + " container_mem_limit=%s", mem_swap_buf, mem_limit_buf); } return false; } @@ -252,7 +250,7 @@ char * OSContainer::cpu_cpuset_memory_nodes() { return cgroup_subsystem->cpu_cpuset_memory_nodes(); } -bool OSContainer::active_processor_count(int& value) { +bool OSContainer::active_processor_count(double& value) { assert(cgroup_subsystem != nullptr, "cgroup subsystem not available"); return cgroup_subsystem->active_processor_count(value); } @@ -291,11 +289,13 @@ template struct metric_fmt; template<> struct metric_fmt { static constexpr const char* fmt = "%llu"; }; template<> struct metric_fmt { static constexpr const char* fmt = "%lu"; }; template<> struct metric_fmt { static constexpr const char* fmt = "%d"; }; +template<> struct metric_fmt { static constexpr const char* fmt = "%.2f"; }; template<> struct metric_fmt { static constexpr const char* fmt = "%s"; }; template void OSContainer::print_container_metric(outputStream*, const char*, unsigned long long int, const char*); template void OSContainer::print_container_metric(outputStream*, const char*, unsigned long int, const char*); template void OSContainer::print_container_metric(outputStream*, const char*, int, const char*); +template void OSContainer::print_container_metric(outputStream*, const char*, double, const char*); template void OSContainer::print_container_metric(outputStream*, const char*, const char*, const char*); template @@ -304,12 +304,13 @@ void OSContainer::print_container_metric(outputStream* st, const char* metrics, constexpr int longest_value = max_length - 11; // Max length - shortest "metric: " string ("cpu_quota: ") char value_str[longest_value + 1] = {}; os::snprintf_checked(value_str, longest_value, metric_fmt::fmt, value); - st->print("%s: %*s", metrics, max_length - static_cast(strlen(metrics)) - 2, value_str); // -2 for the ": " - if (unit[0] != '\0') { - st->print_cr(" %s", unit); - } else { - st->print_cr(""); - } + + const int pad_width = max_length - static_cast(strlen(metrics)) - 2; // -2 for the ": " + const char* unit_prefix = unit[0] != '\0' ? " " : ""; + + char line[128] = {}; + os::snprintf_checked(line, sizeof(line), "%s: %*s%s%s", metrics, pad_width, value_str, unit_prefix, unit); + st->print_cr("%s", line); } void OSContainer::print_container_helper(outputStream* st, MetricResult& res, const char* metrics) { diff --git a/src/hotspot/os/linux/osContainer_linux.hpp b/src/hotspot/os/linux/osContainer_linux.hpp index 11c3e086feb..96b59b98db8 100644 --- a/src/hotspot/os/linux/osContainer_linux.hpp +++ b/src/hotspot/os/linux/osContainer_linux.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, 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 @@ -72,8 +72,7 @@ class OSContainer: AllStatic { static const char * container_type(); static bool available_memory_in_bytes(physical_memory_size_type& value); - static bool available_swap_in_bytes(physical_memory_size_type host_free_swap, - physical_memory_size_type& value); + static bool available_swap_in_bytes(physical_memory_size_type& value); static bool memory_limit_in_bytes(physical_memory_size_type& value); static bool memory_and_swap_limit_in_bytes(physical_memory_size_type& value); static bool memory_and_swap_usage_in_bytes(physical_memory_size_type& value); @@ -84,7 +83,7 @@ class OSContainer: AllStatic { static bool rss_usage_in_bytes(physical_memory_size_type& value); static bool cache_usage_in_bytes(physical_memory_size_type& value); - static bool active_processor_count(int& value); + static bool active_processor_count(double& value); static char * cpu_cpuset_cpus(); static char * cpu_cpuset_memory_nodes(); diff --git a/src/hotspot/os/linux/os_linux.cpp b/src/hotspot/os/linux/os_linux.cpp index a28f9850495..7190845a8ba 100644 --- a/src/hotspot/os/linux/os_linux.cpp +++ b/src/hotspot/os/linux/os_linux.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2015, 2024 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -27,6 +27,7 @@ #include "code/vtableStubs.hpp" #include "compiler/compileBroker.hpp" #include "compiler/disassembler.hpp" +#include "cppstdlib/cstdlib.hpp" #include "hugepages.hpp" #include "interpreter/interpreter.hpp" #include "jvm.h" @@ -96,7 +97,6 @@ # include # include # include -# include # include # include # include @@ -211,15 +211,58 @@ static bool suppress_primordial_thread_resolution = false; // utility functions +bool os::is_containerized() { + return OSContainer::is_containerized(); +} + +bool os::Container::memory_limit(physical_memory_size_type& value) { + physical_memory_size_type result = 0; + if (OSContainer::memory_limit_in_bytes(result) && result != value_unlimited) { + value = result; + return true; + } + return false; +} + +bool os::Container::memory_soft_limit(physical_memory_size_type& value) { + physical_memory_size_type result = 0; + if (OSContainer::memory_soft_limit_in_bytes(result) && result != 0 && result != value_unlimited) { + value = result; + return true; + } + return false; +} + +bool os::Container::memory_throttle_limit(physical_memory_size_type& value) { + physical_memory_size_type result = 0; + if (OSContainer::memory_throttle_limit_in_bytes(result) && result != value_unlimited) { + value = result; + return true; + } + return false; +} + +bool os::Container::used_memory(physical_memory_size_type& value) { + return OSContainer::memory_usage_in_bytes(value); +} + bool os::available_memory(physical_memory_size_type& value) { - if (OSContainer::is_containerized() && OSContainer::available_memory_in_bytes(value)) { + if (is_containerized() && Container::available_memory(value)) { log_trace(os)("available container memory: " PHYS_MEM_TYPE_FORMAT, value); return true; } + return Machine::available_memory(value); +} + +bool os::Machine::available_memory(physical_memory_size_type& value) { return Linux::available_memory(value); } +bool os::Container::available_memory(physical_memory_size_type& value) { + return OSContainer::available_memory_in_bytes(value); +} + bool os::Linux::available_memory(physical_memory_size_type& value) { physical_memory_size_type avail_mem = 0; @@ -251,11 +294,15 @@ bool os::Linux::available_memory(physical_memory_size_type& value) { } bool os::free_memory(physical_memory_size_type& value) { - if (OSContainer::is_containerized() && OSContainer::available_memory_in_bytes(value)) { + if (is_containerized() && Container::available_memory(value)) { log_trace(os)("free container memory: " PHYS_MEM_TYPE_FORMAT, value); return true; } + return Machine::free_memory(value); +} + +bool os::Machine::free_memory(physical_memory_size_type& value) { return Linux::free_memory(value); } @@ -274,21 +321,30 @@ bool os::Linux::free_memory(physical_memory_size_type& value) { } bool os::total_swap_space(physical_memory_size_type& value) { - if (OSContainer::is_containerized()) { - physical_memory_size_type mem_swap_limit = value_unlimited; - physical_memory_size_type memory_limit = value_unlimited; - if (OSContainer::memory_and_swap_limit_in_bytes(mem_swap_limit) && - OSContainer::memory_limit_in_bytes(memory_limit)) { - if (memory_limit != value_unlimited && mem_swap_limit != value_unlimited && - mem_swap_limit >= memory_limit /* ensure swap is >= 0 */) { - value = mem_swap_limit - memory_limit; - return true; - } - } - } // fallback to the host swap space if the container returned unlimited + if (is_containerized() && Container::total_swap_space(value)) { + return true; + } // fallback to the host swap space if the container value fails + return Machine::total_swap_space(value); +} + +bool os::Machine::total_swap_space(physical_memory_size_type& value) { return Linux::host_swap(value); } +bool os::Container::total_swap_space(physical_memory_size_type& value) { + physical_memory_size_type mem_swap_limit = value_unlimited; + physical_memory_size_type memory_limit = value_unlimited; + if (OSContainer::memory_and_swap_limit_in_bytes(mem_swap_limit) && + OSContainer::memory_limit_in_bytes(memory_limit)) { + if (memory_limit != value_unlimited && mem_swap_limit != value_unlimited && + mem_swap_limit >= memory_limit /* ensure swap is >= 0 */) { + value = mem_swap_limit - memory_limit; + return true; + } + } + return false; +} + static bool host_free_swap_f(physical_memory_size_type& value) { struct sysinfo si; int ret = sysinfo(&si); @@ -309,32 +365,45 @@ bool os::free_swap_space(physical_memory_size_type& value) { return false; } physical_memory_size_type host_free_swap_val = MIN2(total_swap_space, host_free_swap); - if (OSContainer::is_containerized()) { - if (OSContainer::available_swap_in_bytes(host_free_swap_val, value)) { + if (is_containerized()) { + if (Container::free_swap_space(value)) { return true; } // Fall through to use host value log_trace(os,container)("os::free_swap_space: containerized value unavailable" " returning host value: " PHYS_MEM_TYPE_FORMAT, host_free_swap_val); } + value = host_free_swap_val; return true; } +bool os::Machine::free_swap_space(physical_memory_size_type& value) { + return host_free_swap_f(value); +} + +bool os::Container::free_swap_space(physical_memory_size_type& value) { + return OSContainer::available_swap_in_bytes(value); +} + physical_memory_size_type os::physical_memory() { - if (OSContainer::is_containerized()) { + if (is_containerized()) { physical_memory_size_type mem_limit = value_unlimited; - if (OSContainer::memory_limit_in_bytes(mem_limit) && mem_limit != value_unlimited) { + if (Container::memory_limit(mem_limit) && mem_limit != value_unlimited) { log_trace(os)("total container memory: " PHYS_MEM_TYPE_FORMAT, mem_limit); return mem_limit; } } - physical_memory_size_type phys_mem = Linux::physical_memory(); + physical_memory_size_type phys_mem = Machine::physical_memory(); log_trace(os)("total system memory: " PHYS_MEM_TYPE_FORMAT, phys_mem); return phys_mem; } +physical_memory_size_type os::Machine::physical_memory() { + return Linux::physical_memory(); +} + // Returns the resident set size (RSS) of the process. // Falls back to using VmRSS from /proc/self/status if /proc/self/smaps_rollup is unavailable. // Note: On kernels with memory cgroups or shared memory, VmRSS may underreport RSS. @@ -2439,20 +2508,21 @@ bool os::Linux::print_container_info(outputStream* st) { OSContainer::print_container_metric(st, "cpu_memory_nodes", p != nullptr ? p : "not supported"); free(p); - int i = -1; - bool supported = OSContainer::active_processor_count(i); + double cpus = -1; + bool supported = OSContainer::active_processor_count(cpus); if (supported) { - assert(i > 0, "must be"); + assert(cpus > 0, "must be"); if (ActiveProcessorCount > 0) { OSContainer::print_container_metric(st, "active_processor_count", ActiveProcessorCount, "(from -XX:ActiveProcessorCount)"); } else { - OSContainer::print_container_metric(st, "active_processor_count", i); + OSContainer::print_container_metric(st, "active_processor_count", cpus); } } else { OSContainer::print_container_metric(st, "active_processor_count", "not supported"); } + int i = -1; supported = OSContainer::cpu_quota(i); if (supported && i > 0) { OSContainer::print_container_metric(st, "cpu_quota", i); @@ -4737,15 +4807,26 @@ int os::active_processor_count() { return ActiveProcessorCount; } - int active_cpus = -1; - if (OSContainer::is_containerized() && OSContainer::active_processor_count(active_cpus)) { - log_trace(os)("active_processor_count: determined by OSContainer: %d", - active_cpus); - } else { - active_cpus = os::Linux::active_processor_count(); + if (is_containerized()) { + double cpu_quota; + if (Container::processor_count(cpu_quota)) { + int active_cpus = ceilf(cpu_quota); // Round fractional CPU quota up. + assert(active_cpus <= Machine::active_processor_count(), "must be"); + log_trace(os)("active_processor_count: determined by OSContainer: %d", + active_cpus); + return active_cpus; + } } - return active_cpus; + return Machine::active_processor_count(); +} + +int os::Machine::active_processor_count() { + return os::Linux::active_processor_count(); +} + +bool os::Container::processor_count(double& value) { + return OSContainer::active_processor_count(value); } static bool should_warn_invalid_processor_id() { @@ -4878,36 +4959,18 @@ int os::open(const char *path, int oflag, int mode) { // All file descriptors that are opened in the Java process and not // specifically destined for a subprocess should have the close-on-exec // flag set. If we don't set it, then careless 3rd party native code - // might fork and exec without closing all appropriate file descriptors, - // and this in turn might: - // - // - cause end-of-file to fail to be detected on some file - // descriptors, resulting in mysterious hangs, or - // - // - might cause an fopen in the subprocess to fail on a system - // suffering from bug 1085341. - // - // (Yes, the default setting of the close-on-exec flag is a Unix - // design flaw) - // - // See: - // 1085341: 32-bit stdio routines should support file descriptors >255 - // 4843136: (process) pipe file descriptor from Runtime.exec not being closed - // 6339493: (process) Runtime.exec does not close all file descriptors on Solaris 9 - // - // Modern Linux kernels (after 2.6.23 2007) support O_CLOEXEC with open(). - // O_CLOEXEC is preferable to using FD_CLOEXEC on an open file descriptor - // because it saves a system call and removes a small window where the flag - // is unset. On ancient Linux kernels the O_CLOEXEC flag will be ignored - // and we fall back to using FD_CLOEXEC (see below). -#ifdef O_CLOEXEC + // might fork and exec without closing all appropriate file descriptors. oflag |= O_CLOEXEC; -#endif int fd = ::open(path, oflag, mode); - if (fd == -1) return -1; + // No further checking is needed if open() returned an error or + // access mode is not read only. + if (fd == -1 || (oflag & O_ACCMODE) != O_RDONLY) { + return fd; + } - //If the open succeeded, the file might still be a directory + // If the open succeeded and is read only, the file might be a directory + // which the JVM doesn't allow to be read. { struct stat buf; int ret = ::fstat(fd, &buf); @@ -4925,21 +4988,6 @@ int os::open(const char *path, int oflag, int mode) { } } -#ifdef FD_CLOEXEC - // Validate that the use of the O_CLOEXEC flag on open above worked. - // With recent kernels, we will perform this check exactly once. - static sig_atomic_t O_CLOEXEC_is_known_to_work = 0; - if (!O_CLOEXEC_is_known_to_work) { - int flags = ::fcntl(fd, F_GETFD); - if (flags != -1) { - if ((flags & FD_CLOEXEC) != 0) - O_CLOEXEC_is_known_to_work = 1; - else - ::fcntl(fd, F_SETFD, flags | FD_CLOEXEC); - } - } -#endif - return fd; } diff --git a/src/hotspot/os/linux/os_perf_linux.cpp b/src/hotspot/os/linux/os_perf_linux.cpp index 5708194521f..9f91f3b4c0d 100644 --- a/src/hotspot/os/linux/os_perf_linux.cpp +++ b/src/hotspot/os/linux/os_perf_linux.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2026, 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,6 +22,7 @@ * */ +#include "cppstdlib/cstdlib.hpp" #include "jvm.h" #include "memory/allocation.inline.hpp" #include "os_linux.inline.hpp" @@ -40,7 +41,6 @@ #include #include #include -#include #include #include #include diff --git a/src/hotspot/os/posix/forbiddenFunctions_posix.hpp b/src/hotspot/os/posix/forbiddenFunctions_posix.hpp index 529cf22078e..1c91e9d2599 100644 --- a/src/hotspot/os/posix/forbiddenFunctions_posix.hpp +++ b/src/hotspot/os/posix/forbiddenFunctions_posix.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2026, 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 @@ -36,15 +36,12 @@ #include #endif +// POSIX puts _exit in . +FORBID_IMPORTED_NORETURN_C_FUNCTION(void _exit(int), /* not noexcept */, "use os::exit") + // If needed, add os::strndup and use that instead. FORBID_C_FUNCTION(char* strndup(const char*, size_t), noexcept, "don't use"); -// These are unimplementable for Windows, and they aren't useful for a -// POSIX implementation of NMT either. -// https://stackoverflow.com/questions/62962839/stdaligned-alloc-missing-from-visual-studio-2019 -FORBID_C_FUNCTION(int posix_memalign(void**, size_t, size_t), noexcept, "don't use"); -FORBID_C_FUNCTION(void* aligned_alloc(size_t, size_t), noexcept, "don't use"); - // realpath with a null second argument mallocs a string for the result. // With a non-null second argument, there is a risk of buffer overrun. PRAGMA_DIAG_PUSH diff --git a/src/hotspot/os/posix/os_posix.cpp b/src/hotspot/os/posix/os_posix.cpp index 07e1920c62d..5412e2bc92d 100644 --- a/src/hotspot/os/posix/os_posix.cpp +++ b/src/hotspot/os/posix/os_posix.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, 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 @@ -23,6 +23,7 @@ */ #include "classfile/classLoader.hpp" +#include "cppstdlib/cstdlib.hpp" #include "interpreter/interpreter.hpp" #include "jvm.h" #include "jvmtifiles/jvmti.h" @@ -457,12 +458,10 @@ char* os::map_memory_to_file(char* base, size_t size, int fd) { warning("Failed mmap to file. (%s)", os::strerror(errno)); return nullptr; } - if (base != nullptr && addr != base) { - if (!os::release_memory(addr, size)) { - warning("Could not release memory on unsuccessful file mapping"); - } - return nullptr; - } + + // The requested address should be the same as the returned address when using MAP_FIXED + // as per POSIX. + assert(base == nullptr || addr == base, "base should equal addr when using MAP_FIXED"); return addr; } diff --git a/src/hotspot/os/posix/perfMemory_posix.cpp b/src/hotspot/os/posix/perfMemory_posix.cpp index 39bfc72a486..ce9c2a4f031 100644 --- a/src/hotspot/os/posix/perfMemory_posix.cpp +++ b/src/hotspot/os/posix/perfMemory_posix.cpp @@ -1,6 +1,6 @@ /* - * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2012, 2021 SAP SE. All rights reserved. + * Copyright (c) 2001, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2026 SAP SE. 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 @@ -112,6 +112,10 @@ static void save_memory_to_file(char* addr, size_t size) { result = ::close(fd); if (result == OS_ERR) { warning("Could not close %s: %s\n", destfile, os::strerror(errno)); + } else { + if (!successful_write) { + remove(destfile); + } } } FREE_C_HEAP_ARRAY(char, destfile); @@ -946,9 +950,10 @@ static int create_sharedmem_file(const char* dirname, const char* filename, size if (result == -1 ) break; if (!os::write(fd, &zero_int, 1)) { if (errno == ENOSPC) { - warning("Insufficient space for shared memory file:\n %s\nTry using the -Djava.io.tmpdir= option to select an alternate temp location.\n", filename); + warning("Insufficient space for shared memory file: %s/%s\n", dirname, filename); } result = OS_ERR; + remove(filename); break; } } diff --git a/src/hotspot/os/posix/permitForbiddenFunctions_posix.hpp b/src/hotspot/os/posix/permitForbiddenFunctions_posix.hpp index 3ff8c383a31..b45cede2e5f 100644 --- a/src/hotspot/os/posix/permitForbiddenFunctions_posix.hpp +++ b/src/hotspot/os/posix/permitForbiddenFunctions_posix.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2026, 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,15 +25,20 @@ #ifndef OS_POSIX_PERMITFORBIDDENFUNCTIONS_POSIX_HPP #define OS_POSIX_PERMITFORBIDDENFUNCTIONS_POSIX_HPP +#include "cppstdlib/cstdlib.hpp" #include "utilities/compilerWarnings.hpp" #include "utilities/globalDefinitions.hpp" +#include + // Provide wrappers for some functions otherwise forbidden from use in HotSpot. // See forbiddenFunctions.hpp for details. namespace permit_forbidden_function { BEGIN_ALLOW_FORBIDDEN_FUNCTIONS +[[noreturn]] inline void _exit(int status) { ::_exit(status); } + // Used by the POSIX implementation of os::realpath. inline char* realpath(const char* path, char* resolved_path) { return ::realpath(path, resolved_path); diff --git a/src/hotspot/os/posix/signals_posix.cpp b/src/hotspot/os/posix/signals_posix.cpp index 85babea5603..203e13a46ac 100644 --- a/src/hotspot/os/posix/signals_posix.cpp +++ b/src/hotspot/os/posix/signals_posix.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, 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 @@ -951,6 +951,32 @@ struct enum_sigcode_desc_t { const char* s_desc; }; +#if defined(LINUX) +// Additional kernel si_code definitions that are only exported by +// more recent glibc distributions, so we have to hard-code the values. +#ifndef BUS_MCEERR_AR // glibc 2.17 +#define BUS_MCEERR_AR 4 +#define BUS_MCEERR_AO 5 +#endif + +#ifndef SEGV_PKUERR // glibc 2.27 +#define SEGV_PKUERR 4 +#endif + +#ifndef SYS_SECCOMP // glibc 2.28 +#define SYS_SECCOMP 1 +#endif + +#ifndef TRAP_BRANCH // glibc 2.30 +#define TRAP_BRANCH 3 +#endif + +#ifndef TRAP_HWBKPT // not glibc version specific - gdb related +#define TRAP_HWBKPT 4 +#endif + +#endif // LINUX + static bool get_signal_code_description(const siginfo_t* si, enum_sigcode_desc_t* out) { const struct { @@ -976,6 +1002,7 @@ static bool get_signal_code_description(const siginfo_t* si, enum_sigcode_desc_t { SIGSEGV, SEGV_ACCERR, "SEGV_ACCERR", "Invalid permissions for mapped object." }, #if defined(LINUX) { SIGSEGV, SEGV_BNDERR, "SEGV_BNDERR", "Failed address bound checks." }, + { SIGSEGV, SEGV_PKUERR, "SEGV_PKUERR", "Protection key checking failure." }, #endif #if defined(AIX) // no explanation found what keyerr would be @@ -984,8 +1011,18 @@ static bool get_signal_code_description(const siginfo_t* si, enum_sigcode_desc_t { SIGBUS, BUS_ADRALN, "BUS_ADRALN", "Invalid address alignment." }, { SIGBUS, BUS_ADRERR, "BUS_ADRERR", "Nonexistent physical address." }, { SIGBUS, BUS_OBJERR, "BUS_OBJERR", "Object-specific hardware error." }, +#if defined(LINUX) + { SIGBUS, BUS_MCEERR_AR,"BUS_MCEERR_AR","Hardware memory error consumed on a machine check: action required." }, + { SIGBUS, BUS_MCEERR_AO,"BUS_MCEERR_AO","Hardware memory error detected in process but not consumed: action optional." }, + + { SIGSYS, SYS_SECCOMP, "SYS_SECCOMP", "Secure computing (seccomp) filter failure." }, +#endif { SIGTRAP, TRAP_BRKPT, "TRAP_BRKPT", "Process breakpoint." }, { SIGTRAP, TRAP_TRACE, "TRAP_TRACE", "Process trace trap." }, +#if defined(LINUX) + { SIGTRAP, TRAP_BRANCH, "TRAP_BRANCH", "Process taken branch trap." }, + { SIGTRAP, TRAP_HWBKPT, "TRAP_HWBKPT", "Hardware breakpoint/watchpoint." }, +#endif { SIGCHLD, CLD_EXITED, "CLD_EXITED", "Child has exited." }, { SIGCHLD, CLD_KILLED, "CLD_KILLED", "Child has terminated abnormally and did not create a core file." }, { SIGCHLD, CLD_DUMPED, "CLD_DUMPED", "Child has terminated abnormally and created a core file." }, @@ -993,6 +1030,7 @@ static bool get_signal_code_description(const siginfo_t* si, enum_sigcode_desc_t { SIGCHLD, CLD_STOPPED, "CLD_STOPPED", "Child has stopped." }, { SIGCHLD, CLD_CONTINUED,"CLD_CONTINUED","Stopped child has continued." }, #ifdef SIGPOLL + { SIGPOLL, POLL_IN, "POLL_IN", "Data input available." }, { SIGPOLL, POLL_OUT, "POLL_OUT", "Output buffers available." }, { SIGPOLL, POLL_MSG, "POLL_MSG", "Input message available." }, { SIGPOLL, POLL_ERR, "POLL_ERR", "I/O error." }, diff --git a/src/hotspot/os/windows/os_windows.cpp b/src/hotspot/os/windows/os_windows.cpp index 0ac05e8a435..b0b7ae18106 100644 --- a/src/hotspot/os/windows/os_windows.cpp +++ b/src/hotspot/os/windows/os_windows.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, 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,6 +30,7 @@ #include "code/vtableStubs.hpp" #include "compiler/compileBroker.hpp" #include "compiler/disassembler.hpp" +#include "cppstdlib/cstdlib.hpp" #include "interpreter/interpreter.hpp" #include "jvm.h" #include "jvmtifiles/jvmti.h" @@ -838,10 +839,18 @@ bool os::available_memory(physical_memory_size_type& value) { return win32::available_memory(value); } +bool os::Machine::available_memory(physical_memory_size_type& value) { + return win32::available_memory(value); +} + bool os::free_memory(physical_memory_size_type& value) { return win32::available_memory(value); } +bool os::Machine::free_memory(physical_memory_size_type& value) { + return win32::available_memory(value); +} + bool os::win32::available_memory(physical_memory_size_type& value) { // Use GlobalMemoryStatusEx() because GlobalMemoryStatus() may return incorrect // value if total memory is larger than 4GB @@ -857,7 +866,11 @@ bool os::win32::available_memory(physical_memory_size_type& value) { } } -bool os::total_swap_space(physical_memory_size_type& value) { +bool os::total_swap_space(physical_memory_size_type& value) { + return Machine::total_swap_space(value); +} + +bool os::Machine::total_swap_space(physical_memory_size_type& value) { MEMORYSTATUSEX ms; ms.dwLength = sizeof(ms); BOOL res = GlobalMemoryStatusEx(&ms); @@ -871,6 +884,10 @@ bool os::total_swap_space(physical_memory_size_type& value) { } bool os::free_swap_space(physical_memory_size_type& value) { + return Machine::free_swap_space(value); +} + +bool os::Machine::free_swap_space(physical_memory_size_type& value) { MEMORYSTATUSEX ms; ms.dwLength = sizeof(ms); BOOL res = GlobalMemoryStatusEx(&ms); @@ -887,6 +904,10 @@ physical_memory_size_type os::physical_memory() { return win32::physical_memory(); } +physical_memory_size_type os::Machine::physical_memory() { + return win32::physical_memory(); +} + size_t os::rss() { size_t rss = 0; PROCESS_MEMORY_COUNTERS_EX pmex; @@ -910,6 +931,10 @@ int os::active_processor_count() { return ActiveProcessorCount; } + return Machine::active_processor_count(); +} + +int os::Machine::active_processor_count() { bool schedules_all_processor_groups = win32::is_windows_11_or_greater() || win32::is_windows_server_2022_or_greater(); if (UseAllWindowsProcessorGroups && !schedules_all_processor_groups && !win32::processor_group_warning_displayed()) { win32::set_processor_group_warning_displayed(true); diff --git a/src/hotspot/os/windows/permitForbiddenFunctions_windows.hpp b/src/hotspot/os/windows/permitForbiddenFunctions_windows.hpp index 99e77464fbd..fbd476ceb60 100644 --- a/src/hotspot/os/windows/permitForbiddenFunctions_windows.hpp +++ b/src/hotspot/os/windows/permitForbiddenFunctions_windows.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2026, 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,6 +25,7 @@ #ifndef OS_WINDOWS_PERMITFORBIDDENFUNCTIONS_WINDOWS_HPP #define OS_WINDOWS_PERMITFORBIDDENFUNCTIONS_WINDOWS_HPP +#include "cppstdlib/cstdlib.hpp" #include "utilities/compilerWarnings.hpp" #include "utilities/globalDefinitions.hpp" @@ -34,6 +35,8 @@ namespace permit_forbidden_function { BEGIN_ALLOW_FORBIDDEN_FUNCTIONS +[[noreturn]] inline void _exit(int status) { ::_exit(status); } + // Used by the Windows implementation of os::realpath. inline char* _fullpath(char* absPath, const char* relPath, size_t maxLength) { return ::_fullpath(absPath, relPath, maxLength); diff --git a/src/hotspot/os/windows/vmError_windows.cpp b/src/hotspot/os/windows/vmError_windows.cpp index 22df4d82cbf..66df38d2734 100644 --- a/src/hotspot/os/windows/vmError_windows.cpp +++ b/src/hotspot/os/windows/vmError_windows.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, 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,6 +24,7 @@ #include "cds/aotMetaspace.hpp" #include "cds/cdsConfig.hpp" +#include "cppstdlib/cstdlib.hpp" #include "runtime/arguments.hpp" #include "runtime/javaThread.hpp" #include "runtime/os.hpp" diff --git a/src/hotspot/os_cpu/aix_ppc/os_aix_ppc.cpp b/src/hotspot/os_cpu/aix_ppc/os_aix_ppc.cpp index afef21b091a..3ab81697280 100644 --- a/src/hotspot/os_cpu/aix_ppc/os_aix_ppc.cpp +++ b/src/hotspot/os_cpu/aix_ppc/os_aix_ppc.cpp @@ -1,6 +1,6 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2012, 2025 SAP SE. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2026 SAP SE. 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 @@ -412,12 +412,8 @@ run_stub: } void os::Aix::init_thread_fpu_state(void) { -#if !defined(USE_XLC_BUILTINS) // Disable FP exceptions. __asm__ __volatile__ ("mtfsfi 6,0"); -#else - __mtfsfi(6, 0); -#endif } //////////////////////////////////////////////////////////////////////////////// diff --git a/src/hotspot/os_cpu/aix_ppc/prefetch_aix_ppc.inline.hpp b/src/hotspot/os_cpu/aix_ppc/prefetch_aix_ppc.inline.hpp index 1f9917acae7..d9dac0e231f 100644 --- a/src/hotspot/os_cpu/aix_ppc/prefetch_aix_ppc.inline.hpp +++ b/src/hotspot/os_cpu/aix_ppc/prefetch_aix_ppc.inline.hpp @@ -1,6 +1,6 @@ /* - * Copyright (c) 1997, 2021, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2012, 2013 SAP SE. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2026 SAP SE. 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,33 +26,24 @@ #ifndef OS_CPU_AIX_PPC_PREFETCH_AIX_PPC_INLINE_HPP #define OS_CPU_AIX_PPC_PREFETCH_AIX_PPC_INLINE_HPP -#include "runtime/prefetch.hpp" - +// Included in runtime/prefetch.inline.hpp inline void Prefetch::read(const void *loc, intx interval) { -#if !defined(USE_XLC_BUILTINS) __asm__ __volatile__ ( " dcbt 0, %0 \n" : : /*%0*/"r" ( ((address)loc) +((long)interval) ) //: ); -#else - __dcbt(((address)loc) +((long)interval)); -#endif } inline void Prefetch::write(void *loc, intx interval) { -#if !defined(USE_XLC_BUILTINS) __asm__ __volatile__ ( " dcbtst 0, %0 \n" : : /*%0*/"r" ( ((address)loc) +((long)interval) ) //: ); -#else - __dcbtst( ((address)loc) +((long)interval) ); -#endif } #endif // OS_CPU_AIX_PPC_PREFETCH_AIX_PPC_INLINE_HPP diff --git a/src/hotspot/os_cpu/bsd_aarch64/os_bsd_aarch64.cpp b/src/hotspot/os_cpu/bsd_aarch64/os_bsd_aarch64.cpp index f6e2d39e315..62dba218b2f 100644 --- a/src/hotspot/os_cpu/bsd_aarch64/os_bsd_aarch64.cpp +++ b/src/hotspot/os_cpu/bsd_aarch64/os_bsd_aarch64.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2014, Red Hat Inc. All rights reserved. * Copyright (c) 2021, Azul Systems, Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. @@ -29,6 +29,7 @@ #include "classfile/vmSymbols.hpp" #include "code/codeCache.hpp" #include "code/vtableStubs.hpp" +#include "cppstdlib/cstdlib.hpp" #include "interpreter/interpreter.hpp" #include "jvm.h" #include "logging/log.hpp" @@ -63,7 +64,6 @@ # include # include # include -# include # include # include # include diff --git a/src/hotspot/os_cpu/bsd_aarch64/prefetch_bsd_aarch64.inline.hpp b/src/hotspot/os_cpu/bsd_aarch64/prefetch_bsd_aarch64.inline.hpp index 1bcbdcaf90d..185f9b54144 100644 --- a/src/hotspot/os_cpu/bsd_aarch64/prefetch_bsd_aarch64.inline.hpp +++ b/src/hotspot/os_cpu/bsd_aarch64/prefetch_bsd_aarch64.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2014, Red Hat Inc. All rights reserved. * Copyright (c) 2021, Azul Systems, Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. @@ -27,8 +27,7 @@ #ifndef OS_CPU_BSD_AARCH64_PREFETCH_BSD_AARCH64_INLINE_HPP #define OS_CPU_BSD_AARCH64_PREFETCH_BSD_AARCH64_INLINE_HPP -#include "runtime/prefetch.hpp" - +// Included in runtime/prefetch.inline.hpp inline void Prefetch::read (const void *loc, intx interval) { if (interval >= 0) diff --git a/src/hotspot/os_cpu/bsd_x86/os_bsd_x86.cpp b/src/hotspot/os_cpu/bsd_x86/os_bsd_x86.cpp index 420e7b4cd8d..f1c80594eaf 100644 --- a/src/hotspot/os_cpu/bsd_x86/os_bsd_x86.cpp +++ b/src/hotspot/os_cpu/bsd_x86/os_bsd_x86.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, 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,6 +26,7 @@ #include "classfile/vmSymbols.hpp" #include "code/codeCache.hpp" #include "code/vtableStubs.hpp" +#include "cppstdlib/cstdlib.hpp" #include "interpreter/interpreter.hpp" #include "jvm.h" #include "logging/log.hpp" @@ -58,7 +59,6 @@ # include # include # include -# include # include # include # include diff --git a/src/hotspot/os_cpu/bsd_x86/prefetch_bsd_x86.inline.hpp b/src/hotspot/os_cpu/bsd_x86/prefetch_bsd_x86.inline.hpp index 52cc405e211..464740920a1 100644 --- a/src/hotspot/os_cpu/bsd_x86/prefetch_bsd_x86.inline.hpp +++ b/src/hotspot/os_cpu/bsd_x86/prefetch_bsd_x86.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, 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,8 +25,7 @@ #ifndef OS_CPU_BSD_X86_PREFETCH_BSD_X86_INLINE_HPP #define OS_CPU_BSD_X86_PREFETCH_BSD_X86_INLINE_HPP -#include "runtime/prefetch.hpp" - +// Included in runtime/prefetch.inline.hpp inline void Prefetch::read (const void *loc, intx interval) { __asm__ ("prefetcht0 (%0,%1,1)" : : "r" (loc), "r" (interval)); diff --git a/src/hotspot/os_cpu/bsd_zero/prefetch_bsd_zero.inline.hpp b/src/hotspot/os_cpu/bsd_zero/prefetch_bsd_zero.inline.hpp index 220d6a08f68..5edf3744719 100644 --- a/src/hotspot/os_cpu/bsd_zero/prefetch_bsd_zero.inline.hpp +++ b/src/hotspot/os_cpu/bsd_zero/prefetch_bsd_zero.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright 2007, 2008 Red Hat, Inc. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,7 +26,7 @@ #ifndef OS_CPU_BSD_ZERO_PREFETCH_BSD_ZERO_INLINE_HPP #define OS_CPU_BSD_ZERO_PREFETCH_BSD_ZERO_INLINE_HPP -#include "runtime/prefetch.hpp" +// Included in runtime/prefetch.inline.hpp inline void Prefetch::read(const void* loc, intx interval) { } diff --git a/src/hotspot/os_cpu/linux_aarch64/os_linux_aarch64.cpp b/src/hotspot/os_cpu/linux_aarch64/os_linux_aarch64.cpp index e565f353382..da9e7e159f1 100644 --- a/src/hotspot/os_cpu/linux_aarch64/os_linux_aarch64.cpp +++ b/src/hotspot/os_cpu/linux_aarch64/os_linux_aarch64.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2014, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -28,6 +28,7 @@ #include "code/codeCache.hpp" #include "code/vtableStubs.hpp" #include "code/nativeInst.hpp" +#include "cppstdlib/cstdlib.hpp" #include "interpreter/interpreter.hpp" #include "jvm.h" #include "memory/allocation.inline.hpp" @@ -59,7 +60,6 @@ # include # include # include -# include # include # include # include diff --git a/src/hotspot/os_cpu/linux_aarch64/prefetch_linux_aarch64.inline.hpp b/src/hotspot/os_cpu/linux_aarch64/prefetch_linux_aarch64.inline.hpp index 168a680a404..580e9f4ffa2 100644 --- a/src/hotspot/os_cpu/linux_aarch64/prefetch_linux_aarch64.inline.hpp +++ b/src/hotspot/os_cpu/linux_aarch64/prefetch_linux_aarch64.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2014, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,8 +26,7 @@ #ifndef OS_CPU_LINUX_AARCH64_PREFETCH_LINUX_AARCH64_INLINE_HPP #define OS_CPU_LINUX_AARCH64_PREFETCH_LINUX_AARCH64_INLINE_HPP -#include "runtime/prefetch.hpp" - +// Included in runtime/prefetch.inline.hpp inline void Prefetch::read (const void *loc, intx interval) { if (interval >= 0) diff --git a/src/hotspot/os_cpu/linux_arm/javaThread_linux_arm.cpp b/src/hotspot/os_cpu/linux_arm/javaThread_linux_arm.cpp index 3dc0035ed87..d82b9d90417 100644 --- a/src/hotspot/os_cpu/linux_arm/javaThread_linux_arm.cpp +++ b/src/hotspot/os_cpu/linux_arm/javaThread_linux_arm.cpp @@ -43,7 +43,8 @@ void JavaThread::cache_global_variables() { BarrierSet* bs = BarrierSet::barrier_set(); if (bs->is_a(BarrierSet::CardTableBarrierSet)) { - _card_table_base = (address) (barrier_set_cast(bs)->card_table()->byte_map_base()); + CardTableBarrierSet* ctbs = CardTableBarrierSet::barrier_set(); + _card_table_base = (address)ctbs->card_table_base_const(); } else { _card_table_base = nullptr; } diff --git a/src/hotspot/os_cpu/linux_arm/os_linux_arm.cpp b/src/hotspot/os_cpu/linux_arm/os_linux_arm.cpp index f4196d89fe3..41a4dbea384 100644 --- a/src/hotspot/os_cpu/linux_arm/os_linux_arm.cpp +++ b/src/hotspot/os_cpu/linux_arm/os_linux_arm.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2026, 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,6 +25,7 @@ #include "asm/assembler.inline.hpp" #include "classfile/vmSymbols.hpp" #include "code/vtableStubs.hpp" +#include "cppstdlib/cstdlib.hpp" #include "interpreter/interpreter.hpp" #include "jvm.h" #include "memory/allocation.inline.hpp" @@ -57,7 +58,6 @@ # include # include # include -# include # include # include # include diff --git a/src/hotspot/os_cpu/linux_arm/prefetch_linux_arm.inline.hpp b/src/hotspot/os_cpu/linux_arm/prefetch_linux_arm.inline.hpp index dfbab55d9b9..a536c17fbe3 100644 --- a/src/hotspot/os_cpu/linux_arm/prefetch_linux_arm.inline.hpp +++ b/src/hotspot/os_cpu/linux_arm/prefetch_linux_arm.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2026, 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 @@ #ifndef OS_CPU_LINUX_ARM_PREFETCH_LINUX_ARM_INLINE_HPP #define OS_CPU_LINUX_ARM_PREFETCH_LINUX_ARM_INLINE_HPP -#include "runtime/prefetch.hpp" +// Included in runtime/prefetch.inline.hpp inline void Prefetch::read (const void *loc, intx interval) { #if defined(__ARM_ARCH_7A__) || defined(__ARM_ARCH_6__) || defined(__ARM_ARCH_5TE__) diff --git a/src/hotspot/os_cpu/linux_ppc/os_linux_ppc.cpp b/src/hotspot/os_cpu/linux_ppc/os_linux_ppc.cpp index 5909b5799b9..6854fb805a9 100644 --- a/src/hotspot/os_cpu/linux_ppc/os_linux_ppc.cpp +++ b/src/hotspot/os_cpu/linux_ppc/os_linux_ppc.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2012, 2025 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -28,6 +28,7 @@ #include "classfile/vmSymbols.hpp" #include "code/codeCache.hpp" #include "code/vtableStubs.hpp" +#include "cppstdlib/cstdlib.hpp" #include "interpreter/interpreter.hpp" #include "jvm.h" #include "memory/allocation.inline.hpp" @@ -62,7 +63,6 @@ # include # include # include -# include # include # include # include diff --git a/src/hotspot/os_cpu/linux_ppc/prefetch_linux_ppc.inline.hpp b/src/hotspot/os_cpu/linux_ppc/prefetch_linux_ppc.inline.hpp index 12c65e6bf00..c33624a07de 100644 --- a/src/hotspot/os_cpu/linux_ppc/prefetch_linux_ppc.inline.hpp +++ b/src/hotspot/os_cpu/linux_ppc/prefetch_linux_ppc.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2012, 2013 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,8 +26,7 @@ #ifndef OS_CPU_LINUX_PPC_PREFETCH_LINUX_PPC_INLINE_HPP #define OS_CPU_LINUX_PPC_PREFETCH_LINUX_PPC_INLINE_HPP -#include "runtime/prefetch.hpp" - +// Included in runtime/prefetch.inline.hpp inline void Prefetch::read(const void *loc, intx interval) { __asm__ __volatile__ ( diff --git a/src/hotspot/os_cpu/linux_riscv/os_linux_riscv.cpp b/src/hotspot/os_cpu/linux_riscv/os_linux_riscv.cpp index 7a5929b0f41..d4a306d35b1 100644 --- a/src/hotspot/os_cpu/linux_riscv/os_linux_riscv.cpp +++ b/src/hotspot/os_cpu/linux_riscv/os_linux_riscv.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2022, Huawei Technologies Co., Ltd. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -28,6 +28,7 @@ #include "code/codeCache.hpp" #include "code/nativeInst.hpp" #include "code/vtableStubs.hpp" +#include "cppstdlib/cstdlib.hpp" #include "interpreter/interpreter.hpp" #include "jvm.h" #include "memory/allocation.inline.hpp" @@ -61,7 +62,6 @@ # include # include # include -# include # include # include # include diff --git a/src/hotspot/os_cpu/linux_riscv/prefetch_linux_riscv.inline.hpp b/src/hotspot/os_cpu/linux_riscv/prefetch_linux_riscv.inline.hpp index a2dd79544c1..c17054e8a0c 100644 --- a/src/hotspot/os_cpu/linux_riscv/prefetch_linux_riscv.inline.hpp +++ b/src/hotspot/os_cpu/linux_riscv/prefetch_linux_riscv.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2021, Huawei Technologies Co., Ltd. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,7 +26,7 @@ #ifndef OS_CPU_LINUX_RISCV_VM_PREFETCH_LINUX_RISCV_INLINE_HPP #define OS_CPU_LINUX_RISCV_VM_PREFETCH_LINUX_RISCV_INLINE_HPP -#include "runtime/prefetch.hpp" +// Included in runtime/prefetch.inline.hpp inline void Prefetch::read (const void *loc, intx interval) { if (interval >= 0 && UseZicbop) { diff --git a/src/hotspot/os_cpu/linux_s390/os_linux_s390.cpp b/src/hotspot/os_cpu/linux_s390/os_linux_s390.cpp index 4e074512e34..749f6bec032 100644 --- a/src/hotspot/os_cpu/linux_s390/os_linux_s390.cpp +++ b/src/hotspot/os_cpu/linux_s390/os_linux_s390.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016, 2024 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -30,6 +30,7 @@ #include "code/nativeInst.hpp" #include "code/vtableStubs.hpp" #include "compiler/disassembler.hpp" +#include "cppstdlib/cstdlib.hpp" #include "interpreter/interpreter.hpp" #include "jvm.h" #include "memory/allocation.inline.hpp" @@ -62,7 +63,6 @@ # include # include # include -# include # include # include # include diff --git a/src/hotspot/os_cpu/linux_s390/prefetch_linux_s390.inline.hpp b/src/hotspot/os_cpu/linux_s390/prefetch_linux_s390.inline.hpp index ee55d01886f..56038714a9a 100644 --- a/src/hotspot/os_cpu/linux_s390/prefetch_linux_s390.inline.hpp +++ b/src/hotspot/os_cpu/linux_s390/prefetch_linux_s390.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,7 +26,7 @@ #ifndef OS_CPU_LINUX_S390_PREFETCH_LINUX_S390_INLINE_HPP #define OS_CPU_LINUX_S390_PREFETCH_LINUX_S390_INLINE_HPP -#include "runtime/prefetch.hpp" +// Included in runtime/prefetch.inline.hpp inline void Prefetch::read(const void* loc, intx interval) { // No prefetch instructions on z/Architecture -> implement trivially. diff --git a/src/hotspot/os_cpu/linux_x86/os_linux_x86.cpp b/src/hotspot/os_cpu/linux_x86/os_linux_x86.cpp index a7f4e5ef688..ee08738c678 100644 --- a/src/hotspot/os_cpu/linux_x86/os_linux_x86.cpp +++ b/src/hotspot/os_cpu/linux_x86/os_linux_x86.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, 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,6 +26,7 @@ #include "classfile/vmSymbols.hpp" #include "code/codeCache.hpp" #include "code/vtableStubs.hpp" +#include "cppstdlib/cstdlib.hpp" #include "interpreter/interpreter.hpp" #include "jvm.h" #include "logging/log.hpp" @@ -51,6 +52,7 @@ #include "utilities/debug.hpp" #include "utilities/events.hpp" #include "utilities/vmError.hpp" +#include "runtime/vm_version.hpp" // put OS-includes here # include @@ -59,7 +61,6 @@ # include # include # include -# include # include # include # include @@ -380,6 +381,43 @@ size_t os::Posix::default_stack_size(os::ThreadType thr_type) { ///////////////////////////////////////////////////////////////////////////// // helper functions for fatal error handler +// XSAVE constants - from Intel SDM Vol. 1, Chapter 13 +#define XSAVE_HDR_OFFSET 512 +#define XFEATURE_APX (1ULL << 19) + +// XSAVE header structure +// See: Intel SDM Vol. 1, Section 13.4.2 "XSAVE Header" +// Also: Linux kernel arch/x86/include/asm/fpu/types.h +struct xstate_header { + uint64_t xfeatures; + uint64_t xcomp_bv; + uint64_t reserved[6]; +}; + +// APX extended state - R16-R31 (16 x 64-bit registers) +// See: Intel APX Architecture Specification +struct apx_state { + uint64_t regs[16]; // r16-r31 +}; + +static apx_state* get_apx_state(const ucontext_t* uc) { + uint32_t offset = VM_Version::apx_xstate_offset(); + if (offset == 0 || uc->uc_mcontext.fpregs == nullptr) { + return nullptr; + } + + char* xsave = (char*)uc->uc_mcontext.fpregs; + xstate_header* hdr = (xstate_header*)(xsave + XSAVE_HDR_OFFSET); + + // Check if APX state is present in this context + if (!(hdr->xfeatures & XFEATURE_APX)) { + return nullptr; + } + + return (apx_state*)(xsave + offset); +} + + void os::print_context(outputStream *st, const void *context) { if (context == nullptr) return; @@ -406,6 +444,14 @@ void os::print_context(outputStream *st, const void *context) { st->print(", R14=" INTPTR_FORMAT, (intptr_t)uc->uc_mcontext.gregs[REG_R14]); st->print(", R15=" INTPTR_FORMAT, (intptr_t)uc->uc_mcontext.gregs[REG_R15]); st->cr(); + // Dump APX EGPRs (R16-R31) + apx_state* apx = UseAPX ? get_apx_state(uc) : nullptr; + if (apx != nullptr) { + for (int i = 0; i < 16; i++) { + st->print("%sR%d=" INTPTR_FORMAT, (i % 4 == 0) ? "" : ", ", 16 + i, (intptr_t)apx->regs[i]); + if (i % 4 == 3) st->cr(); + } + } st->print( "RIP=" INTPTR_FORMAT, (intptr_t)uc->uc_mcontext.gregs[REG_RIP]); st->print(", EFLAGS=" INTPTR_FORMAT, (intptr_t)uc->uc_mcontext.gregs[REG_EFL]); st->print(", CSGSFS=" INTPTR_FORMAT, (intptr_t)uc->uc_mcontext.gregs[REG_CSGSFS]); @@ -432,37 +478,50 @@ void os::print_context(outputStream *st, const void *context) { } void os::print_register_info(outputStream *st, const void *context, int& continuation) { - const int register_count = 16; + if (context == nullptr) { + return; + } + const ucontext_t *uc = (const ucontext_t*)context; + apx_state* apx = UseAPX ? get_apx_state(uc) : nullptr; + + const int register_count = 16 + (apx != nullptr ? 16 : 0); int n = continuation; assert(n >= 0 && n <= register_count, "Invalid continuation value"); - if (context == nullptr || n == register_count) { + if (n == register_count) { return; } - const ucontext_t *uc = (const ucontext_t*)context; while (n < register_count) { // Update continuation with next index before printing location continuation = n + 1; + + if (n < 16) { + // Standard registers (RAX-R15) # define CASE_PRINT_REG(n, str, id) case n: st->print(str); print_location(st, uc->uc_mcontext.gregs[REG_##id]); - switch (n) { - CASE_PRINT_REG( 0, "RAX=", RAX); break; - CASE_PRINT_REG( 1, "RBX=", RBX); break; - CASE_PRINT_REG( 2, "RCX=", RCX); break; - CASE_PRINT_REG( 3, "RDX=", RDX); break; - CASE_PRINT_REG( 4, "RSP=", RSP); break; - CASE_PRINT_REG( 5, "RBP=", RBP); break; - CASE_PRINT_REG( 6, "RSI=", RSI); break; - CASE_PRINT_REG( 7, "RDI=", RDI); break; - CASE_PRINT_REG( 8, "R8 =", R8); break; - CASE_PRINT_REG( 9, "R9 =", R9); break; - CASE_PRINT_REG(10, "R10=", R10); break; - CASE_PRINT_REG(11, "R11=", R11); break; - CASE_PRINT_REG(12, "R12=", R12); break; - CASE_PRINT_REG(13, "R13=", R13); break; - CASE_PRINT_REG(14, "R14=", R14); break; - CASE_PRINT_REG(15, "R15=", R15); break; - } + switch (n) { + CASE_PRINT_REG( 0, "RAX=", RAX); break; + CASE_PRINT_REG( 1, "RBX=", RBX); break; + CASE_PRINT_REG( 2, "RCX=", RCX); break; + CASE_PRINT_REG( 3, "RDX=", RDX); break; + CASE_PRINT_REG( 4, "RSP=", RSP); break; + CASE_PRINT_REG( 5, "RBP=", RBP); break; + CASE_PRINT_REG( 6, "RSI=", RSI); break; + CASE_PRINT_REG( 7, "RDI=", RDI); break; + CASE_PRINT_REG( 8, "R8 =", R8); break; + CASE_PRINT_REG( 9, "R9 =", R9); break; + CASE_PRINT_REG(10, "R10=", R10); break; + CASE_PRINT_REG(11, "R11=", R11); break; + CASE_PRINT_REG(12, "R12=", R12); break; + CASE_PRINT_REG(13, "R13=", R13); break; + CASE_PRINT_REG(14, "R14=", R14); break; + CASE_PRINT_REG(15, "R15=", R15); break; + } # undef CASE_PRINT_REG + } else { + // APX extended general purpose registers (R16-R31) + st->print("R%d=", n); + print_location(st, apx->regs[n - 16]); + } ++n; } } diff --git a/src/hotspot/os_cpu/linux_x86/prefetch_linux_x86.inline.hpp b/src/hotspot/os_cpu/linux_x86/prefetch_linux_x86.inline.hpp index bb4302e1ddb..aadd21bca40 100644 --- a/src/hotspot/os_cpu/linux_x86/prefetch_linux_x86.inline.hpp +++ b/src/hotspot/os_cpu/linux_x86/prefetch_linux_x86.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, 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,8 +25,7 @@ #ifndef OS_CPU_LINUX_X86_PREFETCH_LINUX_X86_INLINE_HPP #define OS_CPU_LINUX_X86_PREFETCH_LINUX_X86_INLINE_HPP -#include "runtime/prefetch.hpp" - +// Included in runtime/prefetch.inline.hpp inline void Prefetch::read (const void *loc, intx interval) { __asm__ ("prefetcht0 (%0,%1,1)" : : "r" (loc), "r" (interval)); diff --git a/src/hotspot/os_cpu/linux_zero/prefetch_linux_zero.inline.hpp b/src/hotspot/os_cpu/linux_zero/prefetch_linux_zero.inline.hpp index a510fa87834..3a34c311acd 100644 --- a/src/hotspot/os_cpu/linux_zero/prefetch_linux_zero.inline.hpp +++ b/src/hotspot/os_cpu/linux_zero/prefetch_linux_zero.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright 2007, 2008 Red Hat, Inc. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,7 +26,7 @@ #ifndef OS_CPU_LINUX_ZERO_PREFETCH_LINUX_ZERO_INLINE_HPP #define OS_CPU_LINUX_ZERO_PREFETCH_LINUX_ZERO_INLINE_HPP -#include "runtime/prefetch.hpp" +// Included in runtime/prefetch.inline.hpp inline void Prefetch::read(const void* loc, intx interval) { } diff --git a/src/hotspot/os_cpu/windows_aarch64/os_windows_aarch64.cpp b/src/hotspot/os_cpu/windows_aarch64/os_windows_aarch64.cpp index d99e0167cbd..2c2afb168fd 100644 --- a/src/hotspot/os_cpu/windows_aarch64/os_windows_aarch64.cpp +++ b/src/hotspot/os_cpu/windows_aarch64/os_windows_aarch64.cpp @@ -1,6 +1,6 @@ /* * Copyright (c) 2020, Microsoft Corporation. All rights reserved. - * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2026, 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,6 +28,7 @@ #include "code/codeCache.hpp" #include "code/vtableStubs.hpp" #include "code/nativeInst.hpp" +#include "cppstdlib/cstdlib.hpp" #include "interpreter/interpreter.hpp" #include "jvm.h" #include "memory/allocation.inline.hpp" @@ -54,7 +55,6 @@ # include # include # include -# include # include # include diff --git a/src/hotspot/os_cpu/windows_aarch64/prefetch_windows_aarch64.inline.hpp b/src/hotspot/os_cpu/windows_aarch64/prefetch_windows_aarch64.inline.hpp index df301ade92d..a360ee342be 100644 --- a/src/hotspot/os_cpu/windows_aarch64/prefetch_windows_aarch64.inline.hpp +++ b/src/hotspot/os_cpu/windows_aarch64/prefetch_windows_aarch64.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, Microsoft Corporation. All rights reserved. + * Copyright (c) 2020, 2026, Microsoft Corporation. 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,8 +25,7 @@ #ifndef OS_CPU_WINDOWS_AARCH64_PREFETCH_WINDOWS_AARCH64_INLINE_HPP #define OS_CPU_WINDOWS_AARCH64_PREFETCH_WINDOWS_AARCH64_INLINE_HPP -#include "runtime/prefetch.hpp" - +// Included in runtime/prefetch.inline.hpp inline void Prefetch::read (const void *loc, intx interval) { } diff --git a/src/hotspot/os_cpu/windows_x86/prefetch_windows_x86.inline.hpp b/src/hotspot/os_cpu/windows_x86/prefetch_windows_x86.inline.hpp index 996625cb0ad..645fbe99a22 100644 --- a/src/hotspot/os_cpu/windows_x86/prefetch_windows_x86.inline.hpp +++ b/src/hotspot/os_cpu/windows_x86/prefetch_windows_x86.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, 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 @@ #ifndef OS_CPU_WINDOWS_X86_PREFETCH_WINDOWS_X86_INLINE_HPP #define OS_CPU_WINDOWS_X86_PREFETCH_WINDOWS_X86_INLINE_HPP -#include "runtime/prefetch.hpp" +// Included in runtime/prefetch.inline.hpp inline void Prefetch::read (const void *loc, intx interval) {} inline void Prefetch::write(void *loc, intx interval) {} diff --git a/src/hotspot/share/c1/c1_LinearScan.hpp b/src/hotspot/share/c1/c1_LinearScan.hpp index 33cc0e5d17b..3873c371a47 100644 --- a/src/hotspot/share/c1/c1_LinearScan.hpp +++ b/src/hotspot/share/c1/c1_LinearScan.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, 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 @@ -884,7 +884,6 @@ class LinearScanStatistic : public StackObj { counter_throw, counter_unwind, counter_typecheck, - counter_fpu_stack, counter_misc_inst, counter_other_inst, blank_line_2, diff --git a/src/hotspot/share/cds/aotConstantPoolResolver.cpp b/src/hotspot/share/cds/aotConstantPoolResolver.cpp index c4bb26f6fb1..93145940955 100644 --- a/src/hotspot/share/cds/aotConstantPoolResolver.cpp +++ b/src/hotspot/share/cds/aotConstantPoolResolver.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2026, 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 @@ -116,6 +116,10 @@ bool AOTConstantPoolResolver::is_class_resolution_deterministic(InstanceKlass* c return false; } } else if (resolved_class->is_objArray_klass()) { + if (CDSConfig::is_dumping_dynamic_archive()) { + // This is difficult to handle. See JDK-8374639 + return false; + } Klass* elem = ObjArrayKlass::cast(resolved_class)->bottom_klass(); if (elem->is_instance_klass()) { return is_class_resolution_deterministic(cp_holder, InstanceKlass::cast(elem)); diff --git a/src/hotspot/share/cds/aotGrowableArray.cpp b/src/hotspot/share/cds/aotGrowableArray.cpp new file mode 100644 index 00000000000..ec63e7aa57f --- /dev/null +++ b/src/hotspot/share/cds/aotGrowableArray.cpp @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2026, 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. + * + */ + +#include "cds/aotGrowableArray.hpp" +#include "cds/aotMetaspace.hpp" +#include "memory/allocation.inline.hpp" +#include "utilities/growableArray.hpp" + +void AOTGrowableArrayHelper::deallocate(void* mem) { + if (!AOTMetaspace::in_aot_cache(mem)) { + GrowableArrayCHeapAllocator::deallocate(mem); + } +} diff --git a/src/hotspot/share/cds/aotGrowableArray.hpp b/src/hotspot/share/cds/aotGrowableArray.hpp new file mode 100644 index 00000000000..0a0c137ed07 --- /dev/null +++ b/src/hotspot/share/cds/aotGrowableArray.hpp @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2026, 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. + * + */ + +#ifndef SHARE_AOT_AOTGROWABLEARRAY_HPP +#define SHARE_AOT_AOTGROWABLEARRAY_HPP + +#include +#include + +class AOTGrowableArrayHelper { +public: + static void deallocate(void* mem); +}; + +// An AOTGrowableArray provides the same functionality as a GrowableArray that +// uses the C heap allocator. In addition, AOTGrowableArray can be iterated with +// MetaspaceClosure. This type should be used for growable arrays that need to be +// stored in the AOT cache. See ModuleEntry::_reads for an example. +template +class AOTGrowableArray : public GrowableArrayWithAllocator> { + friend class VMStructs; + friend class GrowableArrayWithAllocator; + + static E* allocate(int max, MemTag mem_tag) { + return (E*)GrowableArrayCHeapAllocator::allocate(max, sizeof(E), mem_tag); + } + + E* allocate() { + return allocate(this->_capacity, mtClass); + } + + void deallocate(E* mem) { +#if INCLUDE_CDS + AOTGrowableArrayHelper::deallocate(mem); +#else + GrowableArrayCHeapAllocator::deallocate(mem); +#endif + } + +public: + AOTGrowableArray(int initial_capacity, MemTag mem_tag) : + GrowableArrayWithAllocator( + allocate(initial_capacity, mem_tag), + initial_capacity) {} + + AOTGrowableArray() : AOTGrowableArray(0, mtClassShared) {} + + // methods required by MetaspaceClosure + void metaspace_pointers_do(MetaspaceClosure* it); + int size_in_heapwords() const { return (int)heap_word_size(sizeof(*this)); } + MetaspaceClosureType type() const { return MetaspaceClosureType::GrowableArrayType; } + static bool is_read_only_by_default() { return false; } +}; + +#endif // SHARE_AOT_AOTGROWABLEARRAY_HPP diff --git a/src/hotspot/share/gc/g1/g1ConcurrentMarkObjArrayProcessor.inline.hpp b/src/hotspot/share/cds/aotGrowableArray.inline.hpp similarity index 62% rename from src/hotspot/share/gc/g1/g1ConcurrentMarkObjArrayProcessor.inline.hpp rename to src/hotspot/share/cds/aotGrowableArray.inline.hpp index f6d47acdc01..8c6e8cb6503 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentMarkObjArrayProcessor.inline.hpp +++ b/src/hotspot/share/cds/aotGrowableArray.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2026, 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,17 +22,16 @@ * */ -#ifndef SHARE_GC_G1_G1CONCURRENTMARKOBJARRAYPROCESSOR_INLINE_HPP -#define SHARE_GC_G1_G1CONCURRENTMARKOBJARRAYPROCESSOR_INLINE_HPP +#ifndef SHARE_CDS_AOTGROWABLEARRAY_INLINE_HPP +#define SHARE_CDS_AOTGROWABLEARRAY_INLINE_HPP -#include "gc/g1/g1ConcurrentMarkObjArrayProcessor.hpp" +#include "cds/aotGrowableArray.hpp" -#include "gc/shared/gc_globals.hpp" -#include "oops/oop.inline.hpp" -#include "oops/oopsHierarchy.hpp" +#include "memory/metaspaceClosure.hpp" -inline bool G1CMObjArrayProcessor::should_be_sliced(oop obj) { - return obj->is_objArray() && ((objArrayOop)obj)->size() >= 2 * ObjArrayMarkingStride; +template +void AOTGrowableArray::metaspace_pointers_do(MetaspaceClosure* it) { + it->push_c_array(AOTGrowableArray::data_addr(), AOTGrowableArray::capacity()); } -#endif // SHARE_GC_G1_G1CONCURRENTMARKOBJARRAYPROCESSOR_INLINE_HPP +#endif // SHARE_CDS_AOTGROWABLEARRAY_INLINE_HPP diff --git a/src/hotspot/share/cds/aotMapLogger.cpp b/src/hotspot/share/cds/aotMapLogger.cpp index a252eae4b84..5e4e0956824 100644 --- a/src/hotspot/share/cds/aotMapLogger.cpp +++ b/src/hotspot/share/cds/aotMapLogger.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, 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 @@ -29,6 +29,8 @@ #include "cds/aotStreamedHeapWriter.hpp" #include "cds/cdsConfig.hpp" #include "cds/filemap.hpp" +#include "classfile/moduleEntry.hpp" +#include "classfile/packageEntry.hpp" #include "classfile/systemDictionaryShared.hpp" #include "classfile/vmClasses.hpp" #include "logging/log.hpp" @@ -141,7 +143,7 @@ public: info._buffered_addr = ref->obj(); info._requested_addr = ref->obj(); info._bytes = ref->size() * BytesPerWord; - info._type = ref->msotype(); + info._type = ref->type(); _objs.append(info); } @@ -214,7 +216,7 @@ void AOTMapLogger::dumptime_log_metaspace_region(const char* name, DumpRegion* r info._buffered_addr = src_info->buffered_addr(); info._requested_addr = info._buffered_addr + _buffer_to_requested_delta; info._bytes = src_info->size_in_bytes(); - info._type = src_info->msotype(); + info._type = src_info->type(); objs.append(info); } @@ -332,43 +334,52 @@ void AOTMapLogger::log_metaspace_objects_impl(address region_base, address regio address buffered_addr = info._buffered_addr; address requested_addr = info._requested_addr; int bytes = info._bytes; - MetaspaceObj::Type type = info._type; - const char* type_name = MetaspaceObj::type_name(type); + MetaspaceClosureType type = info._type; + const char* type_name = MetaspaceClosure::type_name(type); log_as_hex(last_obj_base, buffered_addr, last_obj_base + _buffer_to_requested_delta); switch (type) { - case MetaspaceObj::ClassType: + case MetaspaceClosureType::ClassType: log_klass((Klass*)src, requested_addr, type_name, bytes, current); break; - case MetaspaceObj::ConstantPoolType: + case MetaspaceClosureType::ConstantPoolType: log_constant_pool((ConstantPool*)src, requested_addr, type_name, bytes, current); break; - case MetaspaceObj::ConstantPoolCacheType: + case MetaspaceClosureType::ConstantPoolCacheType: log_constant_pool_cache((ConstantPoolCache*)src, requested_addr, type_name, bytes, current); break; - case MetaspaceObj::ConstMethodType: + case MetaspaceClosureType::ConstMethodType: log_const_method((ConstMethod*)src, requested_addr, type_name, bytes, current); break; - case MetaspaceObj::MethodType: + case MetaspaceClosureType::MethodType: log_method((Method*)src, requested_addr, type_name, bytes, current); break; - case MetaspaceObj::MethodCountersType: + case MetaspaceClosureType::MethodCountersType: log_method_counters((MethodCounters*)src, requested_addr, type_name, bytes, current); break; - case MetaspaceObj::MethodDataType: + case MetaspaceClosureType::MethodDataType: log_method_data((MethodData*)src, requested_addr, type_name, bytes, current); break; - case MetaspaceObj::SymbolType: + case MetaspaceClosureType::ModuleEntryType: + log_module_entry((ModuleEntry*)src, requested_addr, type_name, bytes, current); + break; + case MetaspaceClosureType::PackageEntryType: + log_package_entry((PackageEntry*)src, requested_addr, type_name, bytes, current); + break; + case MetaspaceClosureType::GrowableArrayType: + log_growable_array((GrowableArrayBase*)src, requested_addr, type_name, bytes, current); + break; + case MetaspaceClosureType::SymbolType: log_symbol((Symbol*)src, requested_addr, type_name, bytes, current); break; - case MetaspaceObj::KlassTrainingDataType: + case MetaspaceClosureType::KlassTrainingDataType: log_klass_training_data((KlassTrainingData*)src, requested_addr, type_name, bytes, current); break; - case MetaspaceObj::MethodTrainingDataType: + case MetaspaceClosureType::MethodTrainingDataType: log_method_training_data((MethodTrainingData*)src, requested_addr, type_name, bytes, current); break; - case MetaspaceObj::CompileTrainingDataType: + case MetaspaceClosureType::CompileTrainingDataType: log_compile_training_data((CompileTrainingData*)src, requested_addr, type_name, bytes, current); break; default: @@ -421,6 +432,27 @@ void AOTMapLogger::log_method_data(MethodData* md, address requested_addr, const log_debug(aot, map)(_LOG_PREFIX " %s", p2i(requested_addr), type_name, bytes, md->method()->external_name()); } +void AOTMapLogger::log_module_entry(ModuleEntry* mod, address requested_addr, const char* type_name, + int bytes, Thread* current) { + ResourceMark rm(current); + log_debug(aot, map)(_LOG_PREFIX " %s", p2i(requested_addr), type_name, bytes, + mod->name_as_C_string()); +} + +void AOTMapLogger::log_package_entry(PackageEntry* pkg, address requested_addr, const char* type_name, + int bytes, Thread* current) { + ResourceMark rm(current); + log_debug(aot, map)(_LOG_PREFIX " %s - %s", p2i(requested_addr), type_name, bytes, + pkg->module()->name_as_C_string(), pkg->name_as_C_string()); +} + +void AOTMapLogger::log_growable_array(GrowableArrayBase* arr, address requested_addr, const char* type_name, + int bytes, Thread* current) { + ResourceMark rm(current); + log_debug(aot, map)(_LOG_PREFIX " %d (%d)", p2i(requested_addr), type_name, bytes, + arr->length(), arr->capacity()); +} + void AOTMapLogger::log_klass(Klass* k, address requested_addr, const char* type_name, int bytes, Thread* current) { ResourceMark rm(current); diff --git a/src/hotspot/share/cds/aotMapLogger.hpp b/src/hotspot/share/cds/aotMapLogger.hpp index ba188514861..bf7ce0028b9 100644 --- a/src/hotspot/share/cds/aotMapLogger.hpp +++ b/src/hotspot/share/cds/aotMapLogger.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, 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,6 +28,7 @@ #include "cds/archiveBuilder.hpp" #include "memory/allocation.hpp" #include "memory/allStatic.hpp" +#include "memory/metaspaceClosureType.hpp" #include "oops/oopsHierarchy.hpp" #include "utilities/globalDefinitions.hpp" #include "utilities/growableArray.hpp" @@ -37,9 +38,13 @@ class ArchiveStreamedHeapInfo; class CompileTrainingData; class DumpRegion; class FileMapInfo; +class GrowableArrayBase; class KlassTrainingData; +class MethodCounters; class MethodTrainingData; +class ModuleEntry; class outputStream; +class PackageEntry; // Write detailed info to a mapfile to analyze contents of the AOT cache/CDS archive. // -Xlog:aot+map* can be used both when creating an AOT cache, or when using an AOT cache. @@ -62,7 +67,7 @@ class AOTMapLogger : AllStatic { address _buffered_addr; address _requested_addr; int _bytes; - MetaspaceObj::Type _type; + MetaspaceClosureType _type; }; public: @@ -142,6 +147,9 @@ private: Thread* current); static void log_klass(Klass* k, address requested_addr, const char* type_name, int bytes, Thread* current); static void log_method(Method* m, address requested_addr, const char* type_name, int bytes, Thread* current); + static void log_module_entry(ModuleEntry* mod, address requested_addr, const char* type_name, int bytes, Thread* current); + static void log_package_entry(PackageEntry* pkg, address requested_addr, const char* type_name, int bytes, Thread* current); + static void log_growable_array(GrowableArrayBase* arr, address requested_addr, const char* type_name, int bytes, Thread* current); static void log_symbol(Symbol* s, address requested_addr, const char* type_name, int bytes, Thread* current); static void log_klass_training_data(KlassTrainingData* ktd, address requested_addr, const char* type_name, int bytes, Thread* current); static void log_method_training_data(MethodTrainingData* mtd, address requested_addr, const char* type_name, int bytes, Thread* current); diff --git a/src/hotspot/share/cds/aotMappedHeapLoader.cpp b/src/hotspot/share/cds/aotMappedHeapLoader.cpp index 84051cbd9e5..210867be70c 100644 --- a/src/hotspot/share/cds/aotMappedHeapLoader.cpp +++ b/src/hotspot/share/cds/aotMappedHeapLoader.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, 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 @@ -360,10 +360,8 @@ bool AOTMappedHeapLoader::load_heap_region(FileMapInfo* mapinfo) { } objArrayOop AOTMappedHeapLoader::root_segment(int segment_idx) { - if (CDSConfig::is_dumping_heap()) { - assert(Thread::current() == (Thread*)VMThread::vm_thread(), "should be in vm thread"); - } else { - assert(CDSConfig::is_using_archive(), "must be"); + if (!CDSConfig::is_using_archive()) { + assert(CDSConfig::is_dumping_heap() && Thread::current() == (Thread*)VMThread::vm_thread(), "sanity"); } objArrayOop segment = (objArrayOop)_root_segments->at(segment_idx).resolve(); @@ -466,7 +464,9 @@ void AOTMappedHeapLoader::finish_initialization(FileMapInfo* info) { add_root_segment((objArrayOop)segment_oop); } - StringTable::load_shared_strings_array(); + if (CDSConfig::is_dumping_final_static_archive()) { + StringTable::move_shared_strings_into_runtime_table(); + } } } @@ -619,7 +619,7 @@ bool AOTMappedHeapLoader::map_heap_region_impl(FileMapInfo* info) { aot_log_info(aot)("Preferred address to map heap data (to avoid relocation) is " INTPTR_FORMAT, p2i(requested_start)); // allocate from java heap - HeapWord* start = G1CollectedHeap::heap()->alloc_archive_region(word_size, (HeapWord*)requested_start); + HeapWord* start = G1CollectedHeap::heap()->alloc_archive_region(word_size); if (start == nullptr) { AOTMetaspace::report_loading_error("UseSharedSpaces: Unable to allocate java heap region for archive heap."); return false; diff --git a/src/hotspot/share/cds/aotMappedHeapWriter.cpp b/src/hotspot/share/cds/aotMappedHeapWriter.cpp index 9c67b2e0e8d..e73b980614a 100644 --- a/src/hotspot/share/cds/aotMappedHeapWriter.cpp +++ b/src/hotspot/share/cds/aotMappedHeapWriter.cpp @@ -696,7 +696,7 @@ template void AOTMappedHeapWriter::relocate_field_in_buffer(T* fiel // We use zero-based, 0-shift encoding, so the narrowOop is just the lower // 32 bits of request_referent intptr_t addr = cast_from_oop(request_referent); - *((narrowOop*)field_addr_in_buffer) = checked_cast(addr); + *((narrowOop*)field_addr_in_buffer) = CompressedOops::narrow_oop_cast(addr); } else { store_requested_oop_in_buffer(field_addr_in_buffer, request_referent); } diff --git a/src/hotspot/share/cds/aotMetaspace.cpp b/src/hotspot/share/cds/aotMetaspace.cpp index 3824a2be3e2..894a35183ca 100644 --- a/src/hotspot/share/cds/aotMetaspace.cpp +++ b/src/hotspot/share/cds/aotMetaspace.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2026, 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 @@ -698,6 +698,9 @@ public: Universe::metaspace_pointers_do(it); vmSymbols::metaspace_pointers_do(it); TrainingData::iterate_roots(it); + if (CDSConfig::is_dumping_full_module_graph()) { + ClassLoaderDataShared::iterate_roots(it); + } // The above code should find all the symbols that are referenced by the // archived classes. We just need to add the extra symbols which @@ -795,6 +798,10 @@ void VM_PopulateDumpSharedSpace::doit() { _builder.make_klasses_shareable(); AOTMetaspace::make_method_handle_intrinsics_shareable(); + if (CDSConfig::is_dumping_full_module_graph()) { + ClassLoaderDataShared::remove_unshareable_info(); + } + dump_java_heap_objects(); dump_shared_symbol_table(_builder.symbols()); @@ -1097,7 +1104,12 @@ void AOTMetaspace::dump_static_archive_impl(StaticArchiveBuilder& builder, TRAPS #if INCLUDE_CDS_JAVA_HEAP if (CDSConfig::is_dumping_heap()) { - assert(CDSConfig::allow_only_single_java_thread(), "Required"); + if (!CDSConfig::is_dumping_preimage_static_archive()) { + // A single thread is required for Reference handling and deterministic CDS archive. + // Its's not required for dumping preimage, where References won't be archived and + // determinism is not needed. + assert(CDSConfig::allow_only_single_java_thread(), "Required"); + } if (!HeapShared::is_archived_boot_layer_available(THREAD)) { report_loading_error("archivedBootLayer not available, disabling full module graph"); CDSConfig::stop_dumping_full_module_graph(); @@ -1135,6 +1147,7 @@ void AOTMetaspace::dump_static_archive_impl(StaticArchiveBuilder& builder, TRAPS HeapShared::init_heap_writer(); if (CDSConfig::is_dumping_full_module_graph()) { ClassLoaderDataShared::ensure_module_entry_tables_exist(); + ClassLoaderDataShared::build_tables(CHECK); HeapShared::reset_archived_object_states(CHECK); } @@ -1154,12 +1167,6 @@ void AOTMetaspace::dump_static_archive_impl(StaticArchiveBuilder& builder, TRAPS // Perhaps there is a way to avoid hard-coding these names here. // See discussion in JDK-8342481. } - - if (HeapShared::is_writing_mapping_mode()) { - // Do this at the very end, when no Java code will be executed. Otherwise - // some new strings may be added to the intern table. - StringTable::allocate_shared_strings_array(CHECK); - } } else { log_info(aot)("Not dumping heap, reset CDSConfig::_is_using_optimized_module_handling"); CDSConfig::stop_using_optimized_module_handling(); @@ -2159,7 +2166,6 @@ void AOTMetaspace::initialize_shared_spaces() { intptr_t* buffer = (intptr_t*)dynamic_mapinfo->serialized_data(); ReadClosure rc(&buffer, (intptr_t)SharedBaseAddress); DynamicArchive::serialize(&rc); - DynamicArchive::setup_array_klasses(); } LogStreamHandle(Info, aot) lsh; diff --git a/src/hotspot/share/cds/aotReferenceObjSupport.cpp b/src/hotspot/share/cds/aotReferenceObjSupport.cpp index aa7cc875533..0c27c8ce5f0 100644 --- a/src/hotspot/share/cds/aotReferenceObjSupport.cpp +++ b/src/hotspot/share/cds/aotReferenceObjSupport.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, 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 @@ -177,12 +177,17 @@ void AOTReferenceObjSupport::init_keep_alive_objs_table() { // Returns true IFF obj is an instance of java.lang.ref.Reference. If so, perform extra eligibility checks. bool AOTReferenceObjSupport::check_if_ref_obj(oop obj) { - // We have a single Java thread. This means java.lang.ref.Reference$ReferenceHandler thread - // is not running. Otherwise the checks for next/discovered may not work. - precond(CDSConfig::allow_only_single_java_thread()); assert_at_safepoint(); // _keep_alive_objs_table uses raw oops if (obj->klass()->is_subclass_of(vmClasses::Reference_klass())) { + // The following check works only if the java.lang.ref.Reference$ReferenceHandler thread + // is not running. + // + // This code is called on every object found by AOTArtifactFinder. When dumping the + // preimage archive, AOTArtifactFinder should not find any Reference objects. + precond(!CDSConfig::is_dumping_preimage_static_archive()); + precond(CDSConfig::allow_only_single_java_thread()); + precond(AOTReferenceObjSupport::is_enabled()); precond(JavaClasses::is_supported_for_archiving(obj)); precond(_keep_alive_objs_table != nullptr); diff --git a/src/hotspot/share/cds/archiveBuilder.cpp b/src/hotspot/share/cds/archiveBuilder.cpp index 3c7f25a950b..9161980c4be 100644 --- a/src/hotspot/share/cds/archiveBuilder.cpp +++ b/src/hotspot/share/cds/archiveBuilder.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, 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 @@ -243,7 +243,7 @@ bool ArchiveBuilder::gather_klass_and_symbol(MetaspaceClosure::Ref* ref, bool re if (get_follow_mode(ref) != make_a_copy) { return false; } - if (ref->msotype() == MetaspaceObj::ClassType) { + if (ref->type() == MetaspaceClosureType::ClassType) { Klass* klass = (Klass*)ref->obj(); assert(klass->is_klass(), "must be"); if (!is_excluded(klass)) { @@ -252,7 +252,7 @@ bool ArchiveBuilder::gather_klass_and_symbol(MetaspaceClosure::Ref* ref, bool re assert(klass->is_instance_klass(), "must be"); } } - } else if (ref->msotype() == MetaspaceObj::SymbolType) { + } else if (ref->type() == MetaspaceClosureType::SymbolType) { // Make sure the symbol won't be GC'ed while we are dumping the archive. Symbol* sym = (Symbol*)ref->obj(); sym->increment_refcount(); @@ -271,11 +271,6 @@ void ArchiveBuilder::gather_klasses_and_symbols() { aot_log_info(aot)("Gathering classes and symbols ... "); GatherKlassesAndSymbols doit(this); iterate_roots(&doit); -#if INCLUDE_CDS_JAVA_HEAP - if (CDSConfig::is_dumping_full_module_graph()) { - ClassLoaderDataShared::iterate_symbols(&doit); - } -#endif doit.finish(); if (CDSConfig::is_dumping_static_archive()) { @@ -446,14 +441,14 @@ bool ArchiveBuilder::gather_one_source_obj(MetaspaceClosure::Ref* ref, bool read } #ifdef ASSERT - if (ref->msotype() == MetaspaceObj::MethodType) { + if (ref->type() == MetaspaceClosureType::MethodType) { Method* m = (Method*)ref->obj(); assert(!RegeneratedClasses::has_been_regenerated((address)m->method_holder()), "Should not archive methods in a class that has been regenerated"); } #endif - if (ref->msotype() == MetaspaceObj::MethodDataType) { + if (ref->type() == MetaspaceClosureType::MethodDataType) { MethodData* md = (MethodData*)ref->obj(); md->clean_method_data(false /* always_clean */); } @@ -554,16 +549,16 @@ ArchiveBuilder::FollowMode ArchiveBuilder::get_follow_mode(MetaspaceClosure::Ref if (CDSConfig::is_dumping_dynamic_archive() && AOTMetaspace::in_aot_cache(obj)) { // Don't dump existing shared metadata again. return point_to_it; - } else if (ref->msotype() == MetaspaceObj::MethodDataType || - ref->msotype() == MetaspaceObj::MethodCountersType || - ref->msotype() == MetaspaceObj::KlassTrainingDataType || - ref->msotype() == MetaspaceObj::MethodTrainingDataType || - ref->msotype() == MetaspaceObj::CompileTrainingDataType) { + } else if (ref->type() == MetaspaceClosureType::MethodDataType || + ref->type() == MetaspaceClosureType::MethodCountersType || + ref->type() == MetaspaceClosureType::KlassTrainingDataType || + ref->type() == MetaspaceClosureType::MethodTrainingDataType || + ref->type() == MetaspaceClosureType::CompileTrainingDataType) { return (TrainingData::need_data() || TrainingData::assembling_data()) ? make_a_copy : set_to_null; - } else if (ref->msotype() == MetaspaceObj::AdapterHandlerEntryType) { + } else if (ref->type() == MetaspaceClosureType::AdapterHandlerEntryType) { return CDSConfig::is_dumping_adapters() ? make_a_copy : set_to_null; } else { - if (ref->msotype() == MetaspaceObj::ClassType) { + if (ref->type() == MetaspaceClosureType::ClassType) { Klass* klass = (Klass*)ref->obj(); assert(klass->is_klass(), "must be"); if (RegeneratedClasses::has_been_regenerated(klass)) { @@ -571,7 +566,12 @@ ArchiveBuilder::FollowMode ArchiveBuilder::get_follow_mode(MetaspaceClosure::Ref } if (is_excluded(klass)) { ResourceMark rm; - log_debug(cds, dynamic)("Skipping class (excluded): %s", klass->external_name()); + aot_log_trace(aot)("pointer set to null: class (excluded): %s", klass->external_name()); + return set_to_null; + } + if (klass->is_array_klass() && CDSConfig::is_dumping_dynamic_archive()) { + ResourceMark rm; + aot_log_trace(aot)("pointer set to null: array class not supported in dynamic region: %s", klass->external_name()); return set_to_null; } } @@ -615,15 +615,6 @@ void ArchiveBuilder::dump_rw_metadata() { ResourceMark rm; aot_log_info(aot)("Allocating RW objects ... "); make_shallow_copies(&_rw_region, &_rw_src_objs); - -#if INCLUDE_CDS_JAVA_HEAP - if (CDSConfig::is_dumping_full_module_graph()) { - // Archive the ModuleEntry's and PackageEntry's of the 3 built-in loaders - char* start = rw_region()->top(); - ClassLoaderDataShared::allocate_archived_tables(); - alloc_stats()->record_modules(rw_region()->top() - start, /*read_only*/false); - } -#endif } void ArchiveBuilder::dump_ro_metadata() { @@ -632,15 +623,6 @@ void ArchiveBuilder::dump_ro_metadata() { start_dump_region(&_ro_region); make_shallow_copies(&_ro_region, &_ro_src_objs); - -#if INCLUDE_CDS_JAVA_HEAP - if (CDSConfig::is_dumping_full_module_graph()) { - char* start = ro_region()->top(); - ClassLoaderDataShared::init_archived_tables(); - alloc_stats()->record_modules(ro_region()->top() - start, /*read_only*/true); - } -#endif - RegeneratedClasses::record_regenerated_objects(); } @@ -658,7 +640,7 @@ void ArchiveBuilder::make_shallow_copy(DumpRegion *dump_region, SourceObjInfo* s size_t alignment = SharedSpaceObjectAlignment; // alignment for the dest pointer char* oldtop = dump_region->top(); - if (src_info->msotype() == MetaspaceObj::ClassType) { + if (src_info->type() == MetaspaceClosureType::ClassType) { // Allocate space for a pointer directly in front of the future InstanceKlass, so // we can do a quick lookup from InstanceKlass* -> RunTimeClassInfo* // without building another hashtable. See RunTimeClassInfo::get_for() @@ -674,7 +656,7 @@ void ArchiveBuilder::make_shallow_copy(DumpRegion *dump_region, SourceObjInfo* s alignment = nth_bit(ArchiveBuilder::precomputed_narrow_klass_shift()); } #endif - } else if (src_info->msotype() == MetaspaceObj::SymbolType) { + } else if (src_info->type() == MetaspaceClosureType::SymbolType) { // Symbols may be allocated by using AllocateHeap, so their sizes // may be less than size_in_bytes() indicates. bytes = ((Symbol*)src)->byte_size(); @@ -684,7 +666,7 @@ void ArchiveBuilder::make_shallow_copy(DumpRegion *dump_region, SourceObjInfo* s memcpy(dest, src, bytes); // Update the hash of buffered sorted symbols for static dump so that the symbols have deterministic contents - if (CDSConfig::is_dumping_static_archive() && (src_info->msotype() == MetaspaceObj::SymbolType)) { + if (CDSConfig::is_dumping_static_archive() && (src_info->type() == MetaspaceClosureType::SymbolType)) { Symbol* buffered_symbol = (Symbol*)dest; assert(((Symbol*)src)->is_permanent(), "archived symbols must be permanent"); buffered_symbol->update_identity_hash(); @@ -699,7 +681,7 @@ void ArchiveBuilder::make_shallow_copy(DumpRegion *dump_region, SourceObjInfo* s } } - intptr_t* archived_vtable = CppVtables::get_archived_vtable(src_info->msotype(), (address)dest); + intptr_t* archived_vtable = CppVtables::get_archived_vtable(src_info->type(), (address)dest); if (archived_vtable != nullptr) { *(address*)dest = (address)archived_vtable; ArchivePtrMarker::mark_pointer((address*)dest); @@ -709,7 +691,7 @@ void ArchiveBuilder::make_shallow_copy(DumpRegion *dump_region, SourceObjInfo* s src_info->set_buffered_addr((address)dest); char* newtop = dump_region->top(); - _alloc_stats.record(src_info->msotype(), int(newtop - oldtop), src_info->read_only()); + _alloc_stats.record(src_info->type(), int(newtop - oldtop), src_info->read_only()); DEBUG_ONLY(_alloc_stats.verify((int)dump_region->used(), src_info->read_only())); } @@ -984,8 +966,6 @@ void ArchiveBuilder::make_klasses_shareable() { #undef STATS_FORMAT #undef STATS_PARAMS - - DynamicArchive::make_array_klasses_shareable(); } void ArchiveBuilder::make_training_data_shareable() { @@ -994,15 +974,15 @@ void ArchiveBuilder::make_training_data_shareable() { return; } - if (info.msotype() == MetaspaceObj::KlassTrainingDataType || - info.msotype() == MetaspaceObj::MethodTrainingDataType || - info.msotype() == MetaspaceObj::CompileTrainingDataType) { + if (info.type() == MetaspaceClosureType::KlassTrainingDataType || + info.type() == MetaspaceClosureType::MethodTrainingDataType || + info.type() == MetaspaceClosureType::CompileTrainingDataType) { TrainingData* buffered_td = (TrainingData*)info.buffered_addr(); buffered_td->remove_unshareable_info(); - } else if (info.msotype() == MetaspaceObj::MethodDataType) { + } else if (info.type() == MetaspaceClosureType::MethodDataType) { MethodData* buffered_mdo = (MethodData*)info.buffered_addr(); buffered_mdo->remove_unshareable_info(); - } else if (info.msotype() == MetaspaceObj::MethodCountersType) { + } else if (info.type() == MetaspaceClosureType::MethodCountersType) { MethodCounters* buffered_mc = (MethodCounters*)info.buffered_addr(); buffered_mc->remove_unshareable_info(); } diff --git a/src/hotspot/share/cds/archiveBuilder.hpp b/src/hotspot/share/cds/archiveBuilder.hpp index 9a628439039..9de6c02edc5 100644 --- a/src/hotspot/share/cds/archiveBuilder.hpp +++ b/src/hotspot/share/cds/archiveBuilder.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, 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 @@ -134,13 +134,13 @@ private: int _size_in_bytes; int _id; // Each object has a unique serial ID, starting from zero. The ID is assigned // when the object is added into _source_objs. - MetaspaceObj::Type _msotype; + MetaspaceClosureType _type; address _source_addr; // The source object to be copied. address _buffered_addr; // The copy of this object insider the buffer. public: SourceObjInfo(MetaspaceClosure::Ref* ref, bool read_only, FollowMode follow_mode) : _ptrmap_start(0), _ptrmap_end(0), _read_only(read_only), _has_embedded_pointer(false), _follow_mode(follow_mode), - _size_in_bytes(ref->size() * BytesPerWord), _id(0), _msotype(ref->msotype()), + _size_in_bytes(ref->size() * BytesPerWord), _id(0), _type(ref->type()), _source_addr(ref->obj()) { if (follow_mode == point_to_it) { _buffered_addr = ref->obj(); @@ -155,7 +155,7 @@ private: SourceObjInfo(address src, SourceObjInfo* renegerated_obj_info) : _ptrmap_start(0), _ptrmap_end(0), _read_only(false), _follow_mode(renegerated_obj_info->_follow_mode), - _size_in_bytes(0), _msotype(renegerated_obj_info->_msotype), + _size_in_bytes(0), _type(renegerated_obj_info->_type), _source_addr(src), _buffered_addr(renegerated_obj_info->_buffered_addr) {} bool should_copy() const { return _follow_mode == make_a_copy; } @@ -182,7 +182,7 @@ private: } return _buffered_addr; } - MetaspaceObj::Type msotype() const { return _msotype; } + MetaspaceClosureType type() const { return _type; } FollowMode follow_mode() const { return _follow_mode; } }; diff --git a/src/hotspot/share/cds/cdsConfig.cpp b/src/hotspot/share/cds/cdsConfig.cpp index 5f6b568dd6e..f4ef3c66f7a 100644 --- a/src/hotspot/share/cds/cdsConfig.cpp +++ b/src/hotspot/share/cds/cdsConfig.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2026, 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 @@ -556,7 +556,9 @@ void CDSConfig::check_aotmode_record() { // At VM exit, the module graph may be contaminated with program states. // We will rebuild the module graph when dumping the CDS final image. - disable_heap_dumping(); + _is_using_optimized_module_handling = false; + _is_using_full_module_graph = false; + _is_dumping_full_module_graph = false; } void CDSConfig::check_aotmode_create() { @@ -582,6 +584,7 @@ void CDSConfig::check_aotmode_create() { substitute_aot_filename(FLAG_MEMBER_ENUM(AOTCache)); _is_dumping_final_static_archive = true; + _is_using_full_module_graph = false; UseSharedSpaces = true; RequireSharedSpaces = true; @@ -954,7 +957,9 @@ bool CDSConfig::are_vm_options_incompatible_with_dumping_heap() { } bool CDSConfig::is_dumping_heap() { - if (!(is_dumping_classic_static_archive() || is_dumping_final_static_archive()) + // Note: when dumping preimage static archive, only a very limited set of oops + // are dumped. + if (!is_dumping_static_archive() || are_vm_options_incompatible_with_dumping_heap() || _disable_heap_dumping) { return false; @@ -966,6 +971,26 @@ bool CDSConfig::is_loading_heap() { return HeapShared::is_archived_heap_in_use(); } +bool CDSConfig::is_dumping_klass_subgraphs() { + if (is_dumping_classic_static_archive() || is_dumping_final_static_archive()) { + // KlassSubGraphs (see heapShared.cpp) is a legacy mechanism for archiving oops. It + // has been superceded by AOT class linking. This feature is used only when + // AOT class linking is disabled. + // + // KlassSubGraphs are disabled in the preimage static archive, which contains a very + // limited set of oops. + return is_dumping_heap() && !is_dumping_aot_linked_classes(); + } else { + return false; + } +} + +bool CDSConfig::is_using_klass_subgraphs() { + return (is_loading_heap() && + !CDSConfig::is_using_aot_linked_classes() && + !CDSConfig::is_dumping_final_static_archive()); +} + bool CDSConfig::is_using_full_module_graph() { if (ClassLoaderDataShared::is_full_module_graph_loaded()) { return true; diff --git a/src/hotspot/share/cds/cdsConfig.hpp b/src/hotspot/share/cds/cdsConfig.hpp index 202904e8231..739dbb4937b 100644 --- a/src/hotspot/share/cds/cdsConfig.hpp +++ b/src/hotspot/share/cds/cdsConfig.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2026, 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 @@ -188,6 +188,9 @@ public: 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_dumping_klass_subgraphs() NOT_CDS_JAVA_HEAP_RETURN_(false); + static bool is_using_klass_subgraphs() 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/cppVtables.cpp b/src/hotspot/share/cds/cppVtables.cpp index f2862454286..da68fa70761 100644 --- a/src/hotspot/share/cds/cppVtables.cpp +++ b/src/hotspot/share/cds/cppVtables.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, 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,12 +22,14 @@ * */ +#include "cds/aotGrowableArray.hpp" #include "cds/aotMetaspace.hpp" #include "cds/archiveBuilder.hpp" #include "cds/archiveUtils.hpp" #include "cds/cdsConfig.hpp" #include "cds/cppVtables.hpp" #include "logging/log.hpp" +#include "memory/resourceArea.hpp" #include "oops/instanceClassLoaderKlass.hpp" #include "oops/instanceMirrorKlass.hpp" #include "oops/instanceRefKlass.hpp" @@ -53,6 +55,19 @@ // + at run time: we clone the actual contents of the vtables from libjvm.so // into our own tables. + +#ifndef PRODUCT + +// AOTGrowableArray has a vtable only when in non-product builds (due to +// the virtual printing functions in AnyObj). + +using GrowableArray_ModuleEntry_ptr = AOTGrowableArray; + +#define DEBUG_CPP_VTABLE_TYPES_DO(f) \ + f(GrowableArray_ModuleEntry_ptr) \ + +#endif + // Currently, the archive contains ONLY the following types of objects that have C++ vtables. #define CPP_VTABLE_TYPES_DO(f) \ f(ConstantPool) \ @@ -68,7 +83,8 @@ f(TypeArrayKlass) \ f(KlassTrainingData) \ f(MethodTrainingData) \ - f(CompileTrainingData) + f(CompileTrainingData) \ + NOT_PRODUCT(DEBUG_CPP_VTABLE_TYPES_DO(f)) class CppVtableInfo { intptr_t _vtable_size; @@ -86,7 +102,7 @@ public: } }; -static inline intptr_t* vtable_of(const Metadata* m) { +static inline intptr_t* vtable_of(const void* m) { return *((intptr_t**)m); } @@ -116,6 +132,7 @@ CppVtableInfo* CppVtableCloner::allocate_and_initialize(const char* name) { template void CppVtableCloner::initialize(const char* name, CppVtableInfo* info) { + ResourceMark rm; T tmp; // Allocate temporary dummy metadata object to get to the original vtable. int n = info->vtable_size(); intptr_t* srcvtable = vtable_of(&tmp); @@ -268,7 +285,7 @@ void CppVtables::serialize(SerializeClosure* soc) { } } -intptr_t* CppVtables::get_archived_vtable(MetaspaceObj::Type msotype, address obj) { +intptr_t* CppVtables::get_archived_vtable(MetaspaceClosureType type, address obj) { if (!_orig_cpp_vtptrs_inited) { CPP_VTABLE_TYPES_DO(INIT_ORIG_CPP_VTPTRS); _orig_cpp_vtptrs_inited = true; @@ -276,19 +293,23 @@ intptr_t* CppVtables::get_archived_vtable(MetaspaceObj::Type msotype, address ob assert(CDSConfig::is_dumping_archive(), "sanity"); int kind = -1; - switch (msotype) { - case MetaspaceObj::SymbolType: - case MetaspaceObj::TypeArrayU1Type: - case MetaspaceObj::TypeArrayU2Type: - case MetaspaceObj::TypeArrayU4Type: - case MetaspaceObj::TypeArrayU8Type: - case MetaspaceObj::TypeArrayOtherType: - case MetaspaceObj::ConstMethodType: - case MetaspaceObj::ConstantPoolCacheType: - case MetaspaceObj::AnnotationsType: - case MetaspaceObj::RecordComponentType: - case MetaspaceObj::AdapterHandlerEntryType: - case MetaspaceObj::AdapterFingerPrintType: + switch (type) { + case MetaspaceClosureType::SymbolType: + case MetaspaceClosureType::TypeArrayU1Type: + case MetaspaceClosureType::TypeArrayU2Type: + case MetaspaceClosureType::TypeArrayU4Type: + case MetaspaceClosureType::TypeArrayU8Type: + case MetaspaceClosureType::TypeArrayOtherType: + case MetaspaceClosureType::CArrayType: + case MetaspaceClosureType::ConstMethodType: + case MetaspaceClosureType::ConstantPoolCacheType: + case MetaspaceClosureType::AnnotationsType: + case MetaspaceClosureType::ModuleEntryType: + case MetaspaceClosureType::PackageEntryType: + case MetaspaceClosureType::RecordComponentType: + case MetaspaceClosureType::AdapterHandlerEntryType: + case MetaspaceClosureType::AdapterFingerPrintType: + PRODUCT_ONLY(case MetaspaceClosureType::GrowableArrayType:) // These have no vtables. break; default: diff --git a/src/hotspot/share/cds/cppVtables.hpp b/src/hotspot/share/cds/cppVtables.hpp index b40ca036023..9e28ba020ee 100644 --- a/src/hotspot/share/cds/cppVtables.hpp +++ b/src/hotspot/share/cds/cppVtables.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, 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,6 +27,7 @@ #include "memory/allocation.hpp" #include "memory/allStatic.hpp" +#include "memory/metaspaceClosureType.hpp" #include "utilities/globalDefinitions.hpp" class ArchiveBuilder; @@ -40,7 +41,7 @@ class CppVtables : AllStatic { public: static void dumptime_init(ArchiveBuilder* builder); static void zero_archived_vtables(); - static intptr_t* get_archived_vtable(MetaspaceObj::Type msotype, address obj); + static intptr_t* get_archived_vtable(MetaspaceClosureType type, address obj); static void serialize(SerializeClosure* sc); static bool is_valid_shared_method(const Method* m) NOT_CDS_RETURN_(false); static char* vtables_serialized_base() { return _vtables_serialized_base; } diff --git a/src/hotspot/share/cds/dumpAllocStats.hpp b/src/hotspot/share/cds/dumpAllocStats.hpp index 7d651320e6f..4553f0f6a01 100644 --- a/src/hotspot/share/cds/dumpAllocStats.hpp +++ b/src/hotspot/share/cds/dumpAllocStats.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, 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,32 +27,34 @@ #include "classfile/compactHashtable.hpp" #include "memory/allocation.hpp" +#include "memory/metaspaceClosureType.hpp" // This is for dumping detailed statistics for the allocations // in the shared spaces. class DumpAllocStats : public StackObj { public: - // Here's poor man's enum inheritance -#define SHAREDSPACE_OBJ_TYPES_DO(f) \ - METASPACE_OBJ_TYPES_DO(f) \ +#define DUMPED_OBJ_TYPES_DO(f) \ + METASPACE_CLOSURE_TYPES_DO(f) \ f(SymbolHashentry) \ f(SymbolBucket) \ f(StringHashentry) \ f(StringBucket) \ - f(ModulesNatives) \ f(CppVTables) \ f(Other) +#define DUMPED_TYPE_DECLARE(name) name ## Type, +#define DUMPED_TYPE_NAME_CASE(name) case name ## Type: return #name; + enum Type { // Types are MetaspaceObj::ClassType, MetaspaceObj::SymbolType, etc - SHAREDSPACE_OBJ_TYPES_DO(METASPACE_OBJ_TYPE_DECLARE) + DUMPED_OBJ_TYPES_DO(DUMPED_TYPE_DECLARE) _number_of_types }; static const char* type_name(Type type) { switch(type) { - SHAREDSPACE_OBJ_TYPES_DO(METASPACE_OBJ_TYPE_NAME_CASE) + DUMPED_OBJ_TYPES_DO(DUMPED_TYPE_NAME_CASE) default: ShouldNotReachHere(); return nullptr; @@ -101,16 +103,12 @@ public: CompactHashtableStats* symbol_stats() { return &_symbol_stats; } CompactHashtableStats* string_stats() { return &_string_stats; } - void record(MetaspaceObj::Type type, int byte_size, bool read_only) { - assert(int(type) >= 0 && type < MetaspaceObj::_number_of_types, "sanity"); + void record(MetaspaceClosureType type, int byte_size, bool read_only) { + int t = (int)type; + assert(t >= 0 && t < (int)MetaspaceClosureType::_number_of_types, "sanity"); int which = (read_only) ? RO : RW; - _counts[which][type] ++; - _bytes [which][type] += byte_size; - } - - void record_modules(int byte_size, bool read_only) { - int which = (read_only) ? RO : RW; - _bytes [which][ModulesNativesType] += byte_size; + _counts[which][t] ++; + _bytes [which][t] += byte_size; } void record_other_type(int byte_size, bool read_only) { diff --git a/src/hotspot/share/cds/dynamicArchive.cpp b/src/hotspot/share/cds/dynamicArchive.cpp index 8fae8dabf8c..d39cf3775e4 100644 --- a/src/hotspot/share/cds/dynamicArchive.cpp +++ b/src/hotspot/share/cds/dynamicArchive.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, 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 @@ -48,6 +48,7 @@ #include "logging/log.hpp" #include "memory/metaspaceClosure.hpp" #include "memory/resourceArea.hpp" +#include "oops/array.hpp" #include "oops/klass.inline.hpp" #include "runtime/arguments.hpp" #include "runtime/os.hpp" @@ -95,7 +96,6 @@ public: void sort_methods(InstanceKlass* ik) const; void remark_pointers_for_instance_klass(InstanceKlass* k, bool should_mark) const; void write_archive(char* serialized_data, AOTClassLocationConfig* cl_config); - void gather_array_klasses(); public: // Do this before and after the archive dump to see if any corruption @@ -132,7 +132,6 @@ public: init_header(); gather_source_objs(); - gather_array_klasses(); reserve_buffer(); log_info(cds, dynamic)("Copying %d klasses and %d symbols", @@ -159,7 +158,6 @@ public: ArchiveBuilder::OtherROAllocMark mark; SystemDictionaryShared::write_to_archive(false); cl_config = AOTClassLocationConfig::dumptime()->write_to_archive(); - DynamicArchive::dump_array_klasses(); serialized_data = ro_region()->top(); WriteClosure wc(ro_region()); @@ -175,8 +173,6 @@ public: write_archive(serialized_data, cl_config); release_header(); - DynamicArchive::post_dump(); - post_dump(); verify_universe("After CDS dynamic dump"); @@ -185,30 +181,6 @@ public: virtual void iterate_roots(MetaspaceClosure* it) { AOTArtifactFinder::all_cached_classes_do(it); SystemDictionaryShared::dumptime_classes_do(it); - iterate_primitive_array_klasses(it); - } - - void iterate_primitive_array_klasses(MetaspaceClosure* it) { - for (int i = T_BOOLEAN; i <= T_LONG; i++) { - assert(is_java_primitive((BasicType)i), "sanity"); - Klass* k = Universe::typeArrayKlass((BasicType)i); // this give you "[I", etc - assert(AOTMetaspace::in_aot_cache_static_region((void*)k), - "one-dimensional primitive array should be in static archive"); - ArrayKlass* ak = ArrayKlass::cast(k); - while (ak != nullptr && ak->in_aot_cache()) { - Klass* next_k = ak->array_klass_or_null(); - if (next_k != nullptr) { - ak = ArrayKlass::cast(next_k); - } else { - ak = nullptr; - } - } - if (ak != nullptr) { - assert(ak->dimension() > 1, "sanity"); - // this is the lowest dimension that's not in the static archive - it->push(&ak); - } - } } }; @@ -367,26 +339,6 @@ void DynamicArchiveBuilder::write_archive(char* serialized_data, AOTClassLocatio log_info(cds, dynamic)("%d klasses; %d symbols", klasses()->length(), symbols()->length()); } -void DynamicArchiveBuilder::gather_array_klasses() { - for (int i = 0; i < klasses()->length(); i++) { - if (klasses()->at(i)->is_objArray_klass()) { - ObjArrayKlass* oak = ObjArrayKlass::cast(klasses()->at(i)); - Klass* elem = oak->element_klass(); - if (AOTMetaspace::in_aot_cache_static_region(elem)) { - // Only capture the array klass whose element_klass is in the static archive. - // During run time, setup (see DynamicArchive::setup_array_klasses()) is needed - // so that the element_klass can find its array klasses from the dynamic archive. - DynamicArchive::append_array_klass(oak); - } else { - // The element_klass and its array klasses are in the same archive. - assert(!AOTMetaspace::in_aot_cache_static_region(oak), - "we should not gather klasses that are already in the static archive"); - } - } - } - log_debug(aot)("Total array klasses gathered for dynamic archive: %d", DynamicArchive::num_array_klasses()); -} - class VM_PopulateDynamicDumpSharedSpace: public VM_Heap_Sync_Operation { DynamicArchiveBuilder _builder; public: @@ -403,76 +355,9 @@ public: } }; -// _array_klasses and _dynamic_archive_array_klasses only hold the array klasses -// which have element klass in the static archive. -GrowableArray* DynamicArchive::_array_klasses = nullptr; -Array* DynamicArchive::_dynamic_archive_array_klasses = nullptr; - void DynamicArchive::serialize(SerializeClosure* soc) { SymbolTable::serialize_shared_table_header(soc, false); SystemDictionaryShared::serialize_dictionary_headers(soc, false); - soc->do_ptr(&_dynamic_archive_array_klasses); -} - -void DynamicArchive::append_array_klass(ObjArrayKlass* ak) { - if (_array_klasses == nullptr) { - _array_klasses = new (mtClassShared) GrowableArray(50, mtClassShared); - } - _array_klasses->append(ak); -} - -void DynamicArchive::dump_array_klasses() { - assert(CDSConfig::is_dumping_dynamic_archive(), "sanity"); - if (_array_klasses != nullptr) { - ArchiveBuilder* builder = ArchiveBuilder::current(); - int num_array_klasses = _array_klasses->length(); - _dynamic_archive_array_klasses = - ArchiveBuilder::new_ro_array(num_array_klasses); - for (int i = 0; i < num_array_klasses; i++) { - builder->write_pointer_in_buffer(_dynamic_archive_array_klasses->adr_at(i), _array_klasses->at(i)); - } - } -} - -void DynamicArchive::setup_array_klasses() { - if (_dynamic_archive_array_klasses != nullptr) { - for (int i = 0; i < _dynamic_archive_array_klasses->length(); i++) { - ObjArrayKlass* oak = _dynamic_archive_array_klasses->at(i); - Klass* elm = oak->element_klass(); - assert(AOTMetaspace::in_aot_cache_static_region((void*)elm), "must be"); - - if (elm->is_instance_klass()) { - assert(InstanceKlass::cast(elm)->array_klasses() == nullptr, "must be"); - InstanceKlass::cast(elm)->set_array_klasses(oak); - } else { - assert(elm->is_array_klass(), "sanity"); - assert(ArrayKlass::cast(elm)->higher_dimension() == nullptr, "must be"); - ArrayKlass::cast(elm)->set_higher_dimension(oak); - } - } - log_debug(aot)("Total array klasses read from dynamic archive: %d", _dynamic_archive_array_klasses->length()); - } -} - -void DynamicArchive::make_array_klasses_shareable() { - if (_array_klasses != nullptr) { - int num_array_klasses = _array_klasses->length(); - for (int i = 0; i < num_array_klasses; i++) { - ObjArrayKlass* k = ArchiveBuilder::current()->get_buffered_addr(_array_klasses->at(i)); - k->remove_unshareable_info(); - } - } -} - -void DynamicArchive::post_dump() { - if (_array_klasses != nullptr) { - delete _array_klasses; - _array_klasses = nullptr; - } -} - -int DynamicArchive::num_array_klasses() { - return _array_klasses != nullptr ? _array_klasses->length() : 0; } void DynamicArchive::dump_impl(bool jcmd_request, const char* archive_name, TRAPS) { diff --git a/src/hotspot/share/cds/dynamicArchive.hpp b/src/hotspot/share/cds/dynamicArchive.hpp index c42c4b7dfde..63a48e252ee 100644 --- a/src/hotspot/share/cds/dynamicArchive.hpp +++ b/src/hotspot/share/cds/dynamicArchive.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, 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,13 +26,8 @@ #define SHARE_CDS_DYNAMICARCHIVE_HPP #include "cds/filemap.hpp" -#include "classfile/compactHashtable.hpp" #include "memory/allStatic.hpp" -#include "memory/memRegion.hpp" -#include "oops/array.hpp" -#include "oops/oop.hpp" #include "utilities/exceptions.hpp" -#include "utilities/growableArray.hpp" #include "utilities/macros.hpp" #if INCLUDE_CDS @@ -59,22 +54,13 @@ public: }; class DynamicArchive : AllStatic { -private: - static GrowableArray* _array_klasses; - static Array* _dynamic_archive_array_klasses; public: static void dump_for_jcmd(const char* archive_name, TRAPS); static void dump_at_exit(JavaThread* current); static void dump_impl(bool jcmd_request, const char* archive_name, TRAPS); static bool is_mapped() { return FileMapInfo::dynamic_info() != nullptr; } static bool validate(FileMapInfo* dynamic_info); - static void dump_array_klasses(); - static void setup_array_klasses(); - static void append_array_klass(ObjArrayKlass* oak); static void serialize(SerializeClosure* soc); - static void make_array_klasses_shareable(); - static void post_dump(); - static int num_array_klasses(); }; #endif // INCLUDE_CDS #endif // SHARE_CDS_DYNAMICARCHIVE_HPP diff --git a/src/hotspot/share/cds/heapShared.cpp b/src/hotspot/share/cds/heapShared.cpp index fdc335f3799..143f9147853 100644 --- a/src/hotspot/share/cds/heapShared.cpp +++ b/src/hotspot/share/cds/heapShared.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, 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 @@ -210,7 +210,7 @@ static bool is_subgraph_root_class_of(ArchivableStaticFieldInfo fields[], Instan bool HeapShared::is_subgraph_root_class(InstanceKlass* ik) { assert(CDSConfig::is_dumping_heap(), "dump-time only"); - if (!CDSConfig::is_dumping_aot_linked_classes()) { + if (CDSConfig::is_dumping_klass_subgraphs()) { // 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); @@ -413,6 +413,8 @@ void HeapShared::materialize_thread_object() { void HeapShared::add_to_dumped_interned_strings(oop string) { assert(HeapShared::is_writing_mapping_mode(), "Only used by this mode"); AOTMappedHeapWriter::add_to_dumped_interned_strings(string); + bool success = archive_reachable_objects_from(1, _dump_time_special_subgraph, string); + assert(success, "shared strings array must not point to arrays or strings that are too large to archive"); } void HeapShared::finalize_initialization(FileMapInfo* static_mapinfo) { @@ -453,7 +455,6 @@ int HeapShared::append_root(oop obj) { oop HeapShared::get_root(int index, bool clear) { assert(index >= 0, "sanity"); - assert(!CDSConfig::is_dumping_heap() && CDSConfig::is_using_archive(), "runtime only"); assert(is_archived_heap_in_use(), "getting roots into heap that is not used"); oop result; @@ -598,8 +599,7 @@ public: void set_oop(MetaspaceObj* ptr, oop o) { MutexLocker ml(ScratchObjects_lock, Mutex::_no_safepoint_check_flag); OopHandle handle(Universe::vm_global(), o); - bool is_new = put(ptr, handle); - assert(is_new, "cannot set twice"); + put_when_absent(ptr, handle); } void remove_oop(MetaspaceObj* ptr) { MutexLocker ml(ScratchObjects_lock, Mutex::_no_safepoint_check_flag); @@ -612,6 +612,11 @@ public: }; void HeapShared::add_scratch_resolved_references(ConstantPool* src, objArrayOop dest) { + if (CDSConfig::is_dumping_preimage_static_archive() && scratch_resolved_references(src) != nullptr) { + // We are in AOT training run. The class has been redefined and we are giving it a new resolved_reference. + // Ignore it, as this class will be excluded from the AOT config. + return; + } if (SystemDictionaryShared::is_builtin_loader(src->pool_holder()->class_loader_data())) { _scratch_objects_table->set_oop(src, dest); } @@ -831,14 +836,6 @@ static objArrayOop get_archived_resolved_references(InstanceKlass* src_ik) { return nullptr; } -void HeapShared::archive_strings() { - assert(HeapShared::is_writing_mapping_mode(), "should not reach here"); - oop shared_strings_array = StringTable::init_shared_strings_array(); - bool success = archive_reachable_objects_from(1, _dump_time_special_subgraph, shared_strings_array); - assert(success, "shared strings array must not point to arrays or strings that are too large to archive"); - StringTable::set_shared_strings_array_index(append_root(shared_strings_array)); -} - int HeapShared::archive_exception_instance(oop exception) { bool success = archive_reachable_objects_from(1, _dump_time_special_subgraph, exception); assert(success, "sanity"); @@ -890,7 +887,7 @@ void HeapShared::start_scanning_for_oops() { void HeapShared::end_scanning_for_oops() { if (is_writing_mapping_mode()) { - archive_strings(); + StringTable::init_shared_table(); } delete_seen_objects_table(); } @@ -940,7 +937,7 @@ void HeapShared::scan_java_class(Klass* orig_k) { void HeapShared::archive_subgraphs() { assert(CDSConfig::is_dumping_heap(), "must be"); - if (!CDSConfig::is_dumping_aot_linked_classes()) { + if (CDSConfig::is_dumping_klass_subgraphs()) { archive_object_subgraphs(archive_subgraph_entry_fields, false /* is_full_module_graph */); if (CDSConfig::is_dumping_full_module_graph()) { @@ -948,10 +945,6 @@ void HeapShared::archive_subgraphs() { true /* is_full_module_graph */); } } - - if (CDSConfig::is_dumping_full_module_graph()) { - Modules::verify_archived_modules(); - } } // @@ -1302,10 +1295,7 @@ static void verify_the_heap(Klass* k, const char* which) { // this case, we will not load the ArchivedKlassSubGraphInfoRecord and will clear its roots. void HeapShared::resolve_classes(JavaThread* current) { assert(CDSConfig::is_using_archive(), "runtime only!"); - if (!is_archived_heap_in_use()) { - return; // nothing to do - } - if (!CDSConfig::is_using_aot_linked_classes()) { + if (CDSConfig::is_using_klass_subgraphs()) { resolve_classes_for_subgraphs(current, archive_subgraph_entry_fields); resolve_classes_for_subgraphs(current, fmg_archive_subgraph_entry_fields); } @@ -1395,7 +1385,7 @@ void HeapShared::init_classes_for_special_subgraph(Handle class_loader, TRAPS) { void HeapShared::initialize_from_archived_subgraph(JavaThread* current, Klass* k) { JavaThread* THREAD = current; - if (!is_archived_heap_in_use()) { + if (!CDSConfig::is_using_klass_subgraphs()) { return; // nothing to do } @@ -1871,7 +1861,7 @@ void HeapShared::archive_reachable_objects_from_static_field(InstanceKlass *k, const char* klass_name, int field_offset, const char* field_name) { - assert(CDSConfig::is_dumping_heap(), "dump time only"); + precond(CDSConfig::is_dumping_klass_subgraphs()); assert(k->defined_by_boot_loader(), "must be boot class"); oop m = k->java_mirror(); @@ -1922,7 +1912,7 @@ class VerifySharedOopClosure: public BasicOopIterateClosure { }; void HeapShared::verify_subgraph_from_static_field(InstanceKlass* k, int field_offset) { - assert(CDSConfig::is_dumping_heap(), "dump time only"); + precond(CDSConfig::is_dumping_klass_subgraphs()); assert(k->defined_by_boot_loader(), "must be boot class"); oop m = k->java_mirror(); @@ -2148,7 +2138,7 @@ 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(); - if (!CDSConfig::is_dumping_aot_linked_classes()) { + if (CDSConfig::is_dumping_klass_subgraphs()) { 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/hotspot/share/cds/heapShared.hpp b/src/hotspot/share/cds/heapShared.hpp index 118c60faa60..3c7068e96ab 100644 --- a/src/hotspot/share/cds/heapShared.hpp +++ b/src/hotspot/share/cds/heapShared.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, 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 @@ -478,7 +478,6 @@ private: static bool has_been_archived(oop orig_obj); static void prepare_resolved_references(); - static void archive_strings(); static void archive_subgraphs(); static void copy_java_mirror(oop orig_mirror, oop scratch_m); diff --git a/src/hotspot/share/ci/ciField.cpp b/src/hotspot/share/ci/ciField.cpp index 19e05784f4d..e0c818f02fc 100644 --- a/src/hotspot/share/ci/ciField.cpp +++ b/src/hotspot/share/ci/ciField.cpp @@ -216,6 +216,10 @@ ciField::ciField(fieldDescriptor *fd) : static bool trust_final_non_static_fields(ciInstanceKlass* holder) { if (holder == nullptr) return false; + if (holder->trust_final_fields()) { + // Explicit opt-in from system classes + return true; + } // Even if general trusting is disabled, trust system-built closures in these packages. if (holder->is_in_package("java/lang/invoke") || holder->is_in_package("sun/invoke") || holder->is_in_package("java/lang/reflect") || holder->is_in_package("jdk/internal/reflect") || @@ -230,14 +234,6 @@ static bool trust_final_non_static_fields(ciInstanceKlass* holder) { // Trust final fields in records if (holder->is_record()) return true; - // Trust Atomic*FieldUpdaters: they are very important for performance, and make up one - // more reason not to use Unsafe, if their final fields are trusted. See more in JDK-8140483. - if (holder->name() == ciSymbols::java_util_concurrent_atomic_AtomicIntegerFieldUpdater_Impl() || - holder->name() == ciSymbols::java_util_concurrent_atomic_AtomicLongFieldUpdater_CASUpdater() || - holder->name() == ciSymbols::java_util_concurrent_atomic_AtomicLongFieldUpdater_LockedUpdater() || - holder->name() == ciSymbols::java_util_concurrent_atomic_AtomicReferenceFieldUpdater_Impl()) { - return true; - } return TrustFinalNonStaticFields; } diff --git a/src/hotspot/share/ci/ciInstanceKlass.cpp b/src/hotspot/share/ci/ciInstanceKlass.cpp index 64b9acf9146..33bcabc4566 100644 --- a/src/hotspot/share/ci/ciInstanceKlass.cpp +++ b/src/hotspot/share/ci/ciInstanceKlass.cpp @@ -65,6 +65,7 @@ ciInstanceKlass::ciInstanceKlass(Klass* k) : _has_nonstatic_concrete_methods = ik->has_nonstatic_concrete_methods(); _is_hidden = ik->is_hidden(); _is_record = ik->is_record(); + _trust_final_fields = ik->trust_final_fields(); _nonstatic_fields = nullptr; // initialized lazily by compute_nonstatic_fields: _has_injected_fields = -1; _implementor = nullptr; // we will fill these lazily diff --git a/src/hotspot/share/ci/ciInstanceKlass.hpp b/src/hotspot/share/ci/ciInstanceKlass.hpp index a1b2d8dd12d..8ccf1fadfb7 100644 --- a/src/hotspot/share/ci/ciInstanceKlass.hpp +++ b/src/hotspot/share/ci/ciInstanceKlass.hpp @@ -59,6 +59,7 @@ private: bool _has_nonstatic_concrete_methods; bool _is_hidden; bool _is_record; + bool _trust_final_fields; bool _has_trusted_loader; ciFlags _flags; @@ -207,6 +208,10 @@ public: return _is_record; } + bool trust_final_fields() const { + return _trust_final_fields; + } + ciInstanceKlass* get_canonical_holder(int offset); ciField* get_field_by_offset(int field_offset, bool is_static); ciField* get_field_by_name(ciSymbol* name, ciSymbol* signature, bool is_static); diff --git a/src/hotspot/share/ci/ciUtilities.cpp b/src/hotspot/share/ci/ciUtilities.cpp index 0c5b4d9824f..011fa049275 100644 --- a/src/hotspot/share/ci/ciUtilities.cpp +++ b/src/hotspot/share/ci/ciUtilities.cpp @@ -42,10 +42,7 @@ const char* basictype_to_str(BasicType t) { // ------------------------------------------------------------------ // card_table_base -CardTable::CardValue* ci_card_table_address() { - BarrierSet* bs = BarrierSet::barrier_set(); - CardTableBarrierSet* ctbs = barrier_set_cast(bs); - CardTable* ct = ctbs->card_table(); - assert(!UseShenandoahGC, "Shenandoah byte_map_base is not constant."); - return ct->byte_map_base(); +CardTable::CardValue* ci_card_table_address_const() { + CardTableBarrierSet* ctbs = CardTableBarrierSet::barrier_set(); + return ctbs->card_table_base_const(); } diff --git a/src/hotspot/share/ci/ciUtilities.hpp b/src/hotspot/share/ci/ciUtilities.hpp index 75dbb03adf4..3333972d57d 100644 --- a/src/hotspot/share/ci/ciUtilities.hpp +++ b/src/hotspot/share/ci/ciUtilities.hpp @@ -51,9 +51,9 @@ inline const char* bool_to_str(bool b) { const char* basictype_to_str(BasicType t); -CardTable::CardValue* ci_card_table_address(); +CardTable::CardValue* ci_card_table_address_const(); template T ci_card_table_address_as() { - return reinterpret_cast(ci_card_table_address()); + return reinterpret_cast(ci_card_table_address_const()); } #endif // SHARE_CI_CIUTILITIES_HPP diff --git a/src/hotspot/share/classfile/classFileParser.cpp b/src/hotspot/share/classfile/classFileParser.cpp index c9d9d3632b5..817d0c64d11 100644 --- a/src/hotspot/share/classfile/classFileParser.cpp +++ b/src/hotspot/share/classfile/classFileParser.cpp @@ -943,6 +943,7 @@ public: _java_lang_Deprecated_for_removal, _jdk_internal_vm_annotation_AOTSafeClassInitializer, _method_AOTRuntimeSetup, + _jdk_internal_vm_annotation_TrustFinalFields, _annotation_LIMIT }; const Location _location; @@ -1878,6 +1879,11 @@ AnnotationCollector::annotation_index(const ClassLoaderData* loader_data, if (!privileged) break; // only allow in privileged code return _field_Stable; } + case VM_SYMBOL_ENUM_NAME(jdk_internal_vm_annotation_TrustFinalFields_signature): { + if (_location != _in_class) break; // only allow for classes + if (!privileged) break; // only allow in privileged code + return _jdk_internal_vm_annotation_TrustFinalFields; + } case VM_SYMBOL_ENUM_NAME(jdk_internal_vm_annotation_Contended_signature): { if (_location != _in_field && _location != _in_class) { break; // only allow for fields and classes @@ -1992,6 +1998,9 @@ void ClassFileParser::ClassAnnotationCollector::apply_to(InstanceKlass* ik) { if (has_annotation(_jdk_internal_vm_annotation_AOTSafeClassInitializer)) { ik->set_has_aot_safe_initializer(); } + if (has_annotation(_jdk_internal_vm_annotation_TrustFinalFields)) { + ik->set_trust_final_fields(true); + } } #define MAX_ARGS_SIZE 255 diff --git a/src/hotspot/share/classfile/classLoader.cpp b/src/hotspot/share/classfile/classLoader.cpp index 12fbda899b9..f631bfaa102 100644 --- a/src/hotspot/share/classfile/classLoader.cpp +++ b/src/hotspot/share/classfile/classLoader.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, 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 @@ -42,6 +42,7 @@ #include "classfile/vmClasses.hpp" #include "classfile/vmSymbols.hpp" #include "compiler/compileBroker.hpp" +#include "cppstdlib/cstdlib.hpp" #include "interpreter/bytecodeStream.hpp" #include "interpreter/oopMapCache.hpp" #include "jimage.hpp" @@ -81,7 +82,6 @@ #include "utilities/utf8.hpp" #include -#include // Entry point in java.dll for path canonicalization @@ -1418,6 +1418,10 @@ char* ClassLoader::lookup_vm_options() { jio_snprintf(modules_path, JVM_MAXPATHLEN, "%s%slib%smodules", Arguments::get_java_home(), fileSep, fileSep); JImage_file =(*JImageOpen)(modules_path, &error); if (JImage_file == nullptr) { + if (Arguments::has_jimage()) { + // The modules file exists but is unreadable or corrupt + vm_exit_during_initialization(err_msg("Unable to load %s", modules_path)); + } return nullptr; } diff --git a/src/hotspot/share/classfile/classLoaderDataShared.cpp b/src/hotspot/share/classfile/classLoaderDataShared.cpp index 7a7743edd03..d415fe64bac 100644 --- a/src/hotspot/share/classfile/classLoaderDataShared.cpp +++ b/src/hotspot/share/classfile/classLoaderDataShared.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, 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 @@ -33,6 +33,7 @@ #include "classfile/packageEntry.hpp" #include "classfile/systemDictionary.hpp" #include "logging/log.hpp" +#include "memory/metaspaceClosure.hpp" #include "runtime/handles.inline.hpp" #include "runtime/safepoint.hpp" @@ -56,9 +57,9 @@ class ArchivedClassLoaderData { public: ArchivedClassLoaderData() : _packages(nullptr), _modules(nullptr), _unnamed_module(nullptr) {} - void iterate_symbols(ClassLoaderData* loader_data, MetaspaceClosure* closure); - void allocate(ClassLoaderData* loader_data); - void init_archived_entries(ClassLoaderData* loader_data); + void iterate_roots(MetaspaceClosure* closure); + void build_tables(ClassLoaderData* loader_data, TRAPS); + void remove_unshareable_info(); ModuleEntry* unnamed_module() { return _unnamed_module; } @@ -80,17 +81,14 @@ static ModuleEntry* _archived_javabase_moduleEntry = nullptr; static int _platform_loader_root_index = -1; static int _system_loader_root_index = -1; -void ArchivedClassLoaderData::iterate_symbols(ClassLoaderData* loader_data, MetaspaceClosure* closure) { +void ArchivedClassLoaderData::iterate_roots(MetaspaceClosure* it) { assert(CDSConfig::is_dumping_full_module_graph(), "must be"); - assert_valid(loader_data); - if (loader_data != nullptr) { - loader_data->packages()->iterate_symbols(closure); - loader_data->modules() ->iterate_symbols(closure); - loader_data->unnamed_module()->iterate_symbols(closure); - } + it->push(&_packages); + it->push(&_modules); + it->push(&_unnamed_module); } -void ArchivedClassLoaderData::allocate(ClassLoaderData* loader_data) { +void ArchivedClassLoaderData::build_tables(ClassLoaderData* loader_data, TRAPS) { assert(CDSConfig::is_dumping_full_module_graph(), "must be"); assert_valid(loader_data); if (loader_data != nullptr) { @@ -98,19 +96,28 @@ void ArchivedClassLoaderData::allocate(ClassLoaderData* loader_data) { // address of the Symbols, which may be relocated at runtime due to ASLR. // So we store the packages/modules in Arrays. At runtime, we create // the hashtables using these arrays. - _packages = loader_data->packages()->allocate_archived_entries(); - _modules = loader_data->modules() ->allocate_archived_entries(); - _unnamed_module = loader_data->unnamed_module()->allocate_archived_entry(); + _packages = loader_data->packages()->build_aot_table(loader_data, CHECK); + _modules = loader_data->modules()->build_aot_table(loader_data, CHECK); + _unnamed_module = loader_data->unnamed_module(); } } -void ArchivedClassLoaderData::init_archived_entries(ClassLoaderData* loader_data) { - assert(CDSConfig::is_dumping_full_module_graph(), "must be"); - assert_valid(loader_data); - if (loader_data != nullptr) { - loader_data->packages()->init_archived_entries(_packages); - loader_data->modules() ->init_archived_entries(_modules); - _unnamed_module->init_as_archived_entry(); +void ArchivedClassLoaderData::remove_unshareable_info() { + if (_packages != nullptr) { + _packages = ArchiveBuilder::current()->get_buffered_addr(_packages); + for (int i = 0; i < _packages->length(); i++) { + _packages->at(i)->remove_unshareable_info(); + } + } + if (_modules != nullptr) { + _modules = ArchiveBuilder::current()->get_buffered_addr(_modules); + for (int i = 0; i < _modules->length(); i++) { + _modules->at(i)->remove_unshareable_info(); + } + } + if (_unnamed_module != nullptr) { + _unnamed_module = ArchiveBuilder::current()->get_buffered_addr(_unnamed_module); + _unnamed_module->remove_unshareable_info(); } } @@ -153,7 +160,6 @@ void ArchivedClassLoaderData::clear_archived_oops() { // ------------------------------ void ClassLoaderDataShared::load_archived_platform_and_system_class_loaders() { -#if INCLUDE_CDS_JAVA_HEAP // The streaming object loader prefers loading the class loader related objects before // the CLD constructor which has a NoSafepointVerifier. if (!HeapShared::is_loading_streaming_mode()) { @@ -178,7 +184,6 @@ void ClassLoaderDataShared::load_archived_platform_and_system_class_loaders() { if (system_loader_module_entry != nullptr) { system_loader_module_entry->preload_archived_oops(); } -#endif } static ClassLoaderData* null_class_loader_data() { @@ -210,28 +215,27 @@ void ClassLoaderDataShared::ensure_module_entry_table_exists(oop class_loader) { assert(met != nullptr, "sanity"); } -void ClassLoaderDataShared::iterate_symbols(MetaspaceClosure* closure) { +void ClassLoaderDataShared::build_tables(TRAPS) { assert(CDSConfig::is_dumping_full_module_graph(), "must be"); - _archived_boot_loader_data.iterate_symbols (null_class_loader_data(), closure); - _archived_platform_loader_data.iterate_symbols(java_platform_loader_data_or_null(), closure); - _archived_system_loader_data.iterate_symbols (java_system_loader_data_or_null(), closure); + _archived_boot_loader_data.build_tables(null_class_loader_data(), CHECK); + _archived_platform_loader_data.build_tables(java_platform_loader_data_or_null(), CHECK); + _archived_system_loader_data.build_tables(java_system_loader_data_or_null(), CHECK); } -void ClassLoaderDataShared::allocate_archived_tables() { +void ClassLoaderDataShared::iterate_roots(MetaspaceClosure* it) { assert(CDSConfig::is_dumping_full_module_graph(), "must be"); - _archived_boot_loader_data.allocate (null_class_loader_data()); - _archived_platform_loader_data.allocate(java_platform_loader_data_or_null()); - _archived_system_loader_data.allocate (java_system_loader_data_or_null()); + _archived_boot_loader_data.iterate_roots(it); + _archived_platform_loader_data.iterate_roots(it); + _archived_system_loader_data.iterate_roots(it); } -void ClassLoaderDataShared::init_archived_tables() { +void ClassLoaderDataShared::remove_unshareable_info() { assert(CDSConfig::is_dumping_full_module_graph(), "must be"); + _archived_boot_loader_data.remove_unshareable_info(); + _archived_platform_loader_data.remove_unshareable_info(); + _archived_system_loader_data.remove_unshareable_info(); - _archived_boot_loader_data.init_archived_entries (null_class_loader_data()); - _archived_platform_loader_data.init_archived_entries(java_platform_loader_data_or_null()); - _archived_system_loader_data.init_archived_entries (java_system_loader_data_or_null()); - - _archived_javabase_moduleEntry = ModuleEntry::get_archived_entry(ModuleEntryTable::javabase_moduleEntry()); + _archived_javabase_moduleEntry = ArchiveBuilder::current()->get_buffered_addr(ModuleEntryTable::javabase_moduleEntry()); _platform_loader_root_index = HeapShared::append_root(SystemDictionary::java_platform_loader()); _system_loader_root_index = HeapShared::append_root(SystemDictionary::java_system_loader()); @@ -271,7 +275,6 @@ ModuleEntry* ClassLoaderDataShared::archived_unnamed_module(ClassLoaderData* loa return archived_module; } - void ClassLoaderDataShared::clear_archived_oops() { assert(!CDSConfig::is_using_full_module_graph(), "must be"); _archived_boot_loader_data.clear_archived_oops(); diff --git a/src/hotspot/share/classfile/classLoaderDataShared.hpp b/src/hotspot/share/classfile/classLoaderDataShared.hpp index 39d0a89418f..2cf37310e50 100644 --- a/src/hotspot/share/classfile/classLoaderDataShared.hpp +++ b/src/hotspot/share/classfile/classLoaderDataShared.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, 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 @@ -40,11 +40,11 @@ class ClassLoaderDataShared : AllStatic { public: static void load_archived_platform_and_system_class_loaders() NOT_CDS_JAVA_HEAP_RETURN; static void restore_archived_modules_for_preloading_classes(JavaThread* current) NOT_CDS_JAVA_HEAP_RETURN; + static void build_tables(TRAPS) NOT_CDS_JAVA_HEAP_RETURN; + static void iterate_roots(MetaspaceClosure* closure) NOT_CDS_JAVA_HEAP_RETURN; + static void remove_unshareable_info() NOT_CDS_JAVA_HEAP_RETURN; #if INCLUDE_CDS_JAVA_HEAP static void ensure_module_entry_tables_exist(); - static void allocate_archived_tables(); - static void iterate_symbols(MetaspaceClosure* closure); - static void init_archived_tables(); static void serialize(SerializeClosure* f); static void clear_archived_oops(); static void restore_archived_entries_for_null_class_loader_data(); diff --git a/src/hotspot/share/classfile/javaClasses.cpp b/src/hotspot/share/classfile/javaClasses.cpp index 77a94c8afa5..b650bf8cfb8 100644 --- a/src/hotspot/share/classfile/javaClasses.cpp +++ b/src/hotspot/share/classfile/javaClasses.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, 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 @@ -1263,6 +1263,10 @@ bool java_lang_Class::restore_archived_mirror(Klass *k, "Restored %s archived mirror " PTR_FORMAT, k->external_name(), p2i(mirror())); } + if (CDSConfig::is_dumping_heap()) { + create_scratch_mirror(k, CHECK_(false)); + } + return true; } #endif // INCLUDE_CDS_JAVA_HEAP @@ -4297,10 +4301,6 @@ int jdk_internal_foreign_abi_NativeEntryPoint::_downcall_stub_address_offset; macro(_method_type_offset, k, "methodType", java_lang_invoke_MethodType_signature, false); \ macro(_downcall_stub_address_offset, k, "downcallStubAddress", long_signature, false); -bool jdk_internal_foreign_abi_NativeEntryPoint::is_instance(oop obj) { - return obj != nullptr && is_subclass(obj->klass()); -} - void jdk_internal_foreign_abi_NativeEntryPoint::compute_offsets() { InstanceKlass* k = vmClasses::NativeEntryPoint_klass(); NEP_FIELDS_DO(FIELD_COMPUTE_OFFSET); @@ -4337,10 +4337,6 @@ int jdk_internal_foreign_abi_ABIDescriptor::_scratch2_offset; macro(_scratch1_offset, k, "scratch1", jdk_internal_foreign_abi_VMStorage_signature, false); \ macro(_scratch2_offset, k, "scratch2", jdk_internal_foreign_abi_VMStorage_signature, false); -bool jdk_internal_foreign_abi_ABIDescriptor::is_instance(oop obj) { - return obj != nullptr && is_subclass(obj->klass()); -} - void jdk_internal_foreign_abi_ABIDescriptor::compute_offsets() { InstanceKlass* k = vmClasses::ABIDescriptor_klass(); ABIDescriptor_FIELDS_DO(FIELD_COMPUTE_OFFSET); diff --git a/src/hotspot/share/classfile/javaClasses.hpp b/src/hotspot/share/classfile/javaClasses.hpp index 33dc912404c..a8562a345c8 100644 --- a/src/hotspot/share/classfile/javaClasses.hpp +++ b/src/hotspot/share/classfile/javaClasses.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, 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 @@ -1179,13 +1179,6 @@ class jdk_internal_foreign_abi_NativeEntryPoint: AllStatic { static oop method_type(oop entry); static jlong downcall_stub_address(oop entry); - // Testers - static bool is_subclass(Klass* klass) { - return vmClasses::NativeEntryPoint_klass() != nullptr && - klass->is_subclass_of(vmClasses::NativeEntryPoint_klass()); - } - static bool is_instance(oop obj); - // Accessors for code generation: static int method_type_offset_in_bytes() { return _method_type_offset; } static int downcall_stub_address_offset_in_bytes() { return _downcall_stub_address_offset; } @@ -1216,13 +1209,6 @@ class jdk_internal_foreign_abi_ABIDescriptor: AllStatic { static jint shadowSpace(oop entry); static oop scratch1(oop entry); static oop scratch2(oop entry); - - // Testers - static bool is_subclass(Klass* klass) { - return vmClasses::ABIDescriptor_klass() != nullptr && - klass->is_subclass_of(vmClasses::ABIDescriptor_klass()); - } - static bool is_instance(oop obj); }; class jdk_internal_foreign_abi_VMStorage: AllStatic { diff --git a/src/hotspot/share/classfile/moduleEntry.cpp b/src/hotspot/share/classfile/moduleEntry.cpp index 5fb3d6f2d13..b5b8aa4ef55 100644 --- a/src/hotspot/share/classfile/moduleEntry.cpp +++ b/src/hotspot/share/classfile/moduleEntry.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2026, 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 @@ -23,6 +23,7 @@ */ #include "cds/aotClassLocation.hpp" +#include "cds/aotGrowableArray.inline.hpp" #include "cds/archiveBuilder.hpp" #include "cds/archiveUtils.hpp" #include "cds/cdsConfig.hpp" @@ -37,6 +38,7 @@ #include "jni.h" #include "logging/log.hpp" #include "logging/logStream.hpp" +#include "memory/metadataFactory.hpp" #include "memory/resourceArea.hpp" #include "memory/universe.hpp" #include "oops/oopHandle.inline.hpp" @@ -44,7 +46,6 @@ #include "runtime/handles.inline.hpp" #include "runtime/safepoint.hpp" #include "utilities/events.hpp" -#include "utilities/growableArray.hpp" #include "utilities/hashTable.hpp" #include "utilities/ostream.hpp" #include "utilities/quickSort.hpp" @@ -167,7 +168,7 @@ void ModuleEntry::add_read(ModuleEntry* m) { } else { if (reads() == nullptr) { // Lazily create a module's reads list - GrowableArray* new_reads = new (mtModule) GrowableArray(MODULE_READS_SIZE, mtModule); + AOTGrowableArray* new_reads = new (mtModule) AOTGrowableArray(MODULE_READS_SIZE, mtModule); set_reads(new_reads); } @@ -274,8 +275,7 @@ ModuleEntry::ModuleEntry(Handle module_handle, _has_default_read_edges(false), _must_walk_reads(false), _is_open(is_open), - _is_patched(false) - DEBUG_ONLY(COMMA _reads_is_archived(false)) { + _is_patched(false) { // Initialize fields specific to a ModuleEntry if (_name == nullptr) { @@ -394,7 +394,6 @@ ModuleEntryTable::~ModuleEntryTable() { ModuleEntryTableDeleter deleter; _table.unlink(&deleter); assert(_table.number_of_entries() == 0, "should have removed all entries"); - } void ModuleEntry::set_loader_data(ClassLoaderData* cld) { @@ -402,147 +401,51 @@ void ModuleEntry::set_loader_data(ClassLoaderData* cld) { _loader_data = cld; } +void ModuleEntry::metaspace_pointers_do(MetaspaceClosure* it) { + it->push(&_name); + it->push(&_reads); + it->push(&_version); + it->push(&_location); +} + #if INCLUDE_CDS_JAVA_HEAP -typedef HashTable< - const ModuleEntry*, - ModuleEntry*, - 557, // prime number - AnyObj::C_HEAP> ArchivedModuleEntries; -static ArchivedModuleEntries* _archive_modules_entries = nullptr; - -#ifndef PRODUCT -static int _num_archived_module_entries = 0; -static int _num_inited_module_entries = 0; -#endif - bool ModuleEntry::should_be_archived() const { return SystemDictionaryShared::is_builtin_loader(loader_data()); } -ModuleEntry* ModuleEntry::allocate_archived_entry() const { - precond(should_be_archived()); - precond(CDSConfig::is_dumping_full_module_graph()); - ModuleEntry* archived_entry = (ModuleEntry*)ArchiveBuilder::rw_region_alloc(sizeof(ModuleEntry)); - memcpy((void*)archived_entry, (void*)this, sizeof(ModuleEntry)); +void ModuleEntry::remove_unshareable_info() { + _archived_module_index = HeapShared::append_root(module_oop()); - archived_entry->_archived_module_index = HeapShared::append_root(module_oop()); - if (_archive_modules_entries == nullptr) { - _archive_modules_entries = new (mtClass)ArchivedModuleEntries(); - } - assert(_archive_modules_entries->get(this) == nullptr, "Each ModuleEntry must not be shared across ModuleEntryTables"); - _archive_modules_entries->put(this, archived_entry); - DEBUG_ONLY(_num_archived_module_entries++); - - if (CDSConfig::is_dumping_final_static_archive()) { - OopHandle null_handle; - archived_entry->_shared_pd = null_handle; - } else { - assert(archived_entry->shared_protection_domain() == nullptr, "never set during -Xshare:dump"); + if (_reads != nullptr) { + _reads->set_in_aot_cache(); } // Clear handles and restore at run time. Handles cannot be archived. + if (CDSConfig::is_dumping_final_static_archive()) { + OopHandle null_handle; + _shared_pd = null_handle; + } else { + assert(shared_protection_domain() == nullptr, "never set during -Xshare:dump"); + } + OopHandle null_handle; - archived_entry->_module_handle = null_handle; - - // For verify_archived_module_entries() - DEBUG_ONLY(_num_inited_module_entries++); - - if (log_is_enabled(Info, aot, module)) { - ResourceMark rm; - LogStream ls(Log(aot, module)::info()); - ls.print("Stored in archive: "); - archived_entry->print(&ls); - } - return archived_entry; -} - -bool ModuleEntry::has_been_archived() { - assert(!ArchiveBuilder::current()->is_in_buffer_space(this), "must be called on original ModuleEntry"); - return _archive_modules_entries->contains(this); -} - -ModuleEntry* ModuleEntry::get_archived_entry(ModuleEntry* orig_entry) { - ModuleEntry** ptr = _archive_modules_entries->get(orig_entry); - assert(ptr != nullptr && *ptr != nullptr, "must have been allocated"); - return *ptr; -} - -// This function is used to archive ModuleEntry::_reads and PackageEntry::_qualified_exports. -// GrowableArray cannot be directly archived, as it needs to be expandable at runtime. -// Write it out as an Array, and convert it back to GrowableArray at runtime. -Array* ModuleEntry::write_growable_array(GrowableArray* array) { - Array* archived_array = nullptr; - int length = (array == nullptr) ? 0 : array->length(); - if (length > 0) { - archived_array = ArchiveBuilder::new_ro_array(length); - for (int i = 0; i < length; i++) { - ModuleEntry* archived_entry = get_archived_entry(array->at(i)); - archived_array->at_put(i, archived_entry); - ArchivePtrMarker::mark_pointer((address*)archived_array->adr_at(i)); - } - } - - return archived_array; -} - -GrowableArray* ModuleEntry::restore_growable_array(Array* archived_array) { - GrowableArray* array = nullptr; - int length = (archived_array == nullptr) ? 0 : archived_array->length(); - if (length > 0) { - array = new (mtModule) GrowableArray(length, mtModule); - for (int i = 0; i < length; i++) { - ModuleEntry* archived_entry = archived_array->at(i); - array->append(archived_entry); - } - } - - return array; -} - -void ModuleEntry::iterate_symbols(MetaspaceClosure* closure) { - closure->push(&_name); - closure->push(&_version); - closure->push(&_location); -} - -void ModuleEntry::init_as_archived_entry() { - set_archived_reads(write_growable_array(reads())); + _module_handle = null_handle; _loader_data = nullptr; // re-init at runtime if (name() != nullptr) { - _shared_path_index = AOTClassLocationConfig::dumptime()->get_module_shared_path_index(_location); - _name = ArchiveBuilder::get_buffered_symbol(_name); - ArchivePtrMarker::mark_pointer((address*)&_name); + Symbol* src_location = ArchiveBuilder::current()->get_source_addr(_location); + _shared_path_index = AOTClassLocationConfig::dumptime()->get_module_shared_path_index(src_location); } else { // _shared_path_index is used only by SystemDictionary::is_shared_class_visible_impl() // for checking classes in named modules. _shared_path_index = -1; } - if (_version != nullptr) { - _version = ArchiveBuilder::get_buffered_symbol(_version); - } - if (_location != nullptr) { - _location = ArchiveBuilder::get_buffered_symbol(_location); - } JFR_ONLY(set_trace_id(0);) // re-init at runtime - - ArchivePtrMarker::mark_pointer((address*)&_reads); - ArchivePtrMarker::mark_pointer((address*)&_version); - ArchivePtrMarker::mark_pointer((address*)&_location); } -#ifndef PRODUCT -void ModuleEntry::verify_archived_module_entries() { - assert(_num_archived_module_entries == _num_inited_module_entries, - "%d ModuleEntries have been archived but %d of them have been properly initialized with archived java.lang.Module objects", - _num_archived_module_entries, _num_inited_module_entries); -} -#endif // PRODUCT - void ModuleEntry::load_from_archive(ClassLoaderData* loader_data) { assert(CDSConfig::is_using_archive(), "runtime only"); set_loader_data(loader_data); - set_reads(restore_growable_array(archived_reads())); JFR_ONLY(INIT_ID(this);) } @@ -581,38 +484,28 @@ static int compare_module_by_name(ModuleEntry* a, ModuleEntry* b) { return a->name()->fast_compare(b->name()); } -void ModuleEntryTable::iterate_symbols(MetaspaceClosure* closure) { - auto syms = [&] (const SymbolHandle& key, ModuleEntry*& m) { - m->iterate_symbols(closure); - }; - _table.iterate_all(syms); -} - -Array* ModuleEntryTable::allocate_archived_entries() { - Array* archived_modules = ArchiveBuilder::new_rw_array(_table.number_of_entries()); +Array* ModuleEntryTable::build_aot_table(ClassLoaderData* loader_data, TRAPS) { + Array* aot_table = + MetadataFactory::new_array(loader_data, _table.number_of_entries(), nullptr, CHECK_NULL); int n = 0; auto grab = [&] (const SymbolHandle& key, ModuleEntry*& m) { - archived_modules->at_put(n++, m); + m->pack_reads(); + aot_table->at_put(n++, m); + if (log_is_enabled(Info, aot, module)) { + ResourceMark rm; + LogStream ls(Log(aot, module)::info()); + ls.print("Stored in archive: "); + m->print(&ls); + } }; _table.iterate_all(grab); if (n > 1) { // Always allocate in the same order to produce deterministic archive. - QuickSort::sort(archived_modules->data(), n, compare_module_by_name); + QuickSort::sort(aot_table->data(), n, compare_module_by_name); } - for (int i = 0; i < n; i++) { - archived_modules->at_put(i, archived_modules->at(i)->allocate_archived_entry()); - ArchivePtrMarker::mark_pointer((address*)archived_modules->adr_at(i)); - } - return archived_modules; -} -void ModuleEntryTable::init_archived_entries(Array* archived_modules) { - assert(CDSConfig::is_dumping_full_module_graph(), "sanity"); - for (int i = 0; i < archived_modules->length(); i++) { - ModuleEntry* archived_entry = archived_modules->at(i); - archived_entry->init_as_archived_entry(); - } + return aot_table; } void ModuleEntryTable::load_archived_entries(ClassLoaderData* loader_data, diff --git a/src/hotspot/share/classfile/moduleEntry.hpp b/src/hotspot/share/classfile/moduleEntry.hpp index 2e1852c5369..1a0251a2c2a 100644 --- a/src/hotspot/share/classfile/moduleEntry.hpp +++ b/src/hotspot/share/classfile/moduleEntry.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2026, 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,9 @@ #ifndef SHARE_CLASSFILE_MODULEENTRY_HPP #define SHARE_CLASSFILE_MODULEENTRY_HPP +#include "cds/aotGrowableArray.hpp" #include "jni.h" +#include "memory/metaspaceClosureType.hpp" #include "oops/oopHandle.hpp" #include "oops/symbol.hpp" #include "oops/symbolHandle.hpp" @@ -68,11 +70,8 @@ private: // for shared classes from this module Symbol* _name; // name of this module ClassLoaderData* _loader_data; + AOTGrowableArray* _reads; // list of modules that are readable by this module - union { - GrowableArray* _reads; // list of modules that are readable by this module - Array* _archived_reads; // List of readable modules stored in the CDS archive - }; Symbol* _version; // module version number Symbol* _location; // module location CDS_ONLY(int _shared_path_index;) // >=0 if classes in this module are in CDS archive @@ -81,7 +80,6 @@ private: bool _must_walk_reads; // walk module's reads list at GC safepoints to purge out dead modules bool _is_open; // whether the packages in the module are all unqualifiedly exported bool _is_patched; // whether the module is patched via --patch-module - DEBUG_ONLY(bool _reads_is_archived); CDS_JAVA_HEAP_ONLY(int _archived_module_index;) JFR_ONLY(DEFINE_TRACE_ID_FIELD;) @@ -120,22 +118,18 @@ public: bool can_read(ModuleEntry* m) const; bool has_reads_list() const; - GrowableArray* reads() const { - assert(!_reads_is_archived, "sanity"); + AOTGrowableArray* reads() const { return _reads; } - void set_reads(GrowableArray* r) { + void set_reads(AOTGrowableArray* r) { _reads = r; - DEBUG_ONLY(_reads_is_archived = false); } - Array* archived_reads() const { - assert(_reads_is_archived, "sanity"); - return _archived_reads; - } - void set_archived_reads(Array* r) { - _archived_reads = r; - DEBUG_ONLY(_reads_is_archived = true); + void pack_reads() { + if (_reads != nullptr) { + _reads->shrink_to_fit(); + } } + void add_read(ModuleEntry* m); void set_read_walk_required(ClassLoaderData* m_loader_data); @@ -189,6 +183,13 @@ public: const char* name_as_C_string() const { return is_named() ? name()->as_C_string() : UNNAMED_MODULE; } + + // methods required by MetaspaceClosure + void metaspace_pointers_do(MetaspaceClosure* it); + int size_in_heapwords() const { return (int)heap_word_size(sizeof(ModuleEntry)); } + MetaspaceClosureType type() const { return MetaspaceClosureType::ModuleEntryType; } + static bool is_read_only_by_default() { return false; } + void print(outputStream* st = tty) const; void verify(); @@ -198,18 +199,11 @@ public: #if INCLUDE_CDS_JAVA_HEAP bool should_be_archived() const; - void iterate_symbols(MetaspaceClosure* closure); - ModuleEntry* allocate_archived_entry() const; - void init_as_archived_entry(); - static ModuleEntry* get_archived_entry(ModuleEntry* orig_entry); - bool has_been_archived(); - static Array* write_growable_array(GrowableArray* array); - static GrowableArray* restore_growable_array(Array* archived_array); + void remove_unshareable_info(); void load_from_archive(ClassLoaderData* loader_data); void preload_archived_oops(); void restore_archived_oops(ClassLoaderData* loader_data); void clear_archived_oops(); - static void verify_archived_module_entries() PRODUCT_RETURN; #endif }; @@ -275,9 +269,7 @@ public: void verify(); #if INCLUDE_CDS_JAVA_HEAP - void iterate_symbols(MetaspaceClosure* closure); - Array* allocate_archived_entries(); - void init_archived_entries(Array* archived_modules); + Array* build_aot_table(ClassLoaderData* loader_data, TRAPS); void load_archived_entries(ClassLoaderData* loader_data, Array* archived_modules); void restore_archived_oops(ClassLoaderData* loader_data, diff --git a/src/hotspot/share/classfile/modules.cpp b/src/hotspot/share/classfile/modules.cpp index baf2acfb78c..51d09d9c47f 100644 --- a/src/hotspot/share/classfile/modules.cpp +++ b/src/hotspot/share/classfile/modules.cpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. +* Copyright (c) 2016, 2026, 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 @@ -505,13 +505,10 @@ void Modules::check_archived_module_oop(oop orig_module_obj) { ClassLoaderData* loader_data = orig_module_ent->loader_data(); assert(loader_data->is_builtin_class_loader_data(), "must be"); - if (orig_module_ent->name() != nullptr) { - // For each named module, we archive both the java.lang.Module oop and the ModuleEntry. - assert(orig_module_ent->has_been_archived(), "sanity"); - } else { + precond(ArchiveBuilder::current()->has_been_archived(orig_module_ent)); + if (orig_module_ent->name() == nullptr) { // We always archive unnamed module oop for boot, platform, and system loaders. precond(orig_module_ent->should_be_archived()); - precond(orig_module_ent->has_been_archived()); if (loader_data->is_boot_class_loader_data()) { assert(!_seen_boot_unnamed_module, "only once"); @@ -529,10 +526,6 @@ void Modules::check_archived_module_oop(oop orig_module_obj) { } } -void Modules::verify_archived_modules() { - ModuleEntry::verify_archived_module_entries(); -} - class Modules::ArchivedProperty { const char* _prop; const bool _numbered; diff --git a/src/hotspot/share/classfile/modules.hpp b/src/hotspot/share/classfile/modules.hpp index 27a22c1017a..75857c8960c 100644 --- a/src/hotspot/share/classfile/modules.hpp +++ b/src/hotspot/share/classfile/modules.hpp @@ -1,5 +1,5 @@ /* -* Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. +* Copyright (c) 2016, 2026, 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 @@ -59,7 +59,6 @@ public: TRAPS) NOT_CDS_JAVA_HEAP_RETURN; static void init_archived_modules(JavaThread* current, Handle h_platform_loader, Handle h_system_loader) NOT_CDS_JAVA_HEAP_RETURN; - static void verify_archived_modules() NOT_CDS_JAVA_HEAP_RETURN; static void dump_archived_module_info() NOT_CDS_JAVA_HEAP_RETURN; static void serialize_archived_module_info(SerializeClosure* soc) NOT_CDS_JAVA_HEAP_RETURN; diff --git a/src/hotspot/share/classfile/packageEntry.cpp b/src/hotspot/share/classfile/packageEntry.cpp index ea2e6cd1def..3e61f2e3a3e 100644 --- a/src/hotspot/share/classfile/packageEntry.cpp +++ b/src/hotspot/share/classfile/packageEntry.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2026, 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,6 +22,8 @@ * */ +#include "cds/aotGrowableArray.inline.hpp" +#include "cds/aotMetaspace.hpp" #include "cds/archiveBuilder.hpp" #include "cds/archiveUtils.hpp" #include "cds/cdsConfig.hpp" @@ -31,13 +33,13 @@ #include "classfile/vmSymbols.hpp" #include "logging/log.hpp" #include "logging/logStream.hpp" +#include "memory/metadataFactory.hpp" #include "memory/resourceArea.hpp" #include "oops/array.hpp" #include "oops/symbol.hpp" #include "runtime/handles.inline.hpp" #include "runtime/java.hpp" #include "utilities/events.hpp" -#include "utilities/growableArray.hpp" #include "utilities/hashTable.hpp" #include "utilities/ostream.hpp" #include "utilities/quickSort.hpp" @@ -51,7 +53,7 @@ PackageEntry::PackageEntry(Symbol* name, ModuleEntry* module) : _qualified_exports(nullptr), _defined_by_cds_in_class_path(0) { - // name can't be null + // name can't be null -- a class in the default package gets a PackageEntry of nullptr. _name->increment_refcount(); JFR_ONLY(INIT_ID(this);) @@ -81,7 +83,7 @@ void PackageEntry::add_qexport(ModuleEntry* m) { if (!has_qual_exports_list()) { // Lazily create a package's qualified exports list. // Initial size is small, do not anticipate export lists to be large. - _qualified_exports = new (mtModule) GrowableArray(QUAL_EXP_SIZE, mtModule); + _qualified_exports = new (mtModule) AOTGrowableArray(QUAL_EXP_SIZE, mtModule); } // Determine, based on this newly established export to module m, @@ -183,12 +185,24 @@ void PackageEntry::purge_qualified_exports() { } void PackageEntry::delete_qualified_exports() { - if (_qualified_exports != nullptr) { + if (_qualified_exports != nullptr && !AOTMetaspace::in_aot_cache(_qualified_exports)) { delete _qualified_exports; } _qualified_exports = nullptr; } +void PackageEntry::pack_qualified_exports() { + if (_qualified_exports != nullptr) { + _qualified_exports->shrink_to_fit(); + } +} + +void PackageEntry::metaspace_pointers_do(MetaspaceClosure* it) { + it->push(&_name); + it->push(&_module); + it->push(&_qualified_exports); +} + PackageEntryTable::PackageEntryTable() { } PackageEntryTable::~PackageEntryTable() { @@ -212,66 +226,19 @@ PackageEntryTable::~PackageEntryTable() { } #if INCLUDE_CDS_JAVA_HEAP -typedef HashTable< - const PackageEntry*, - PackageEntry*, - 557, // prime number - AnyObj::C_HEAP> ArchivedPackageEntries; -static ArchivedPackageEntries* _archived_packages_entries = nullptr; - bool PackageEntry::should_be_archived() const { return module()->should_be_archived(); } -PackageEntry* PackageEntry::allocate_archived_entry() const { - precond(should_be_archived()); - PackageEntry* archived_entry = (PackageEntry*)ArchiveBuilder::rw_region_alloc(sizeof(PackageEntry)); - memcpy((void*)archived_entry, (void*)this, sizeof(PackageEntry)); - - if (_archived_packages_entries == nullptr) { - _archived_packages_entries = new (mtClass)ArchivedPackageEntries(); +void PackageEntry::remove_unshareable_info() { + if (_qualified_exports != nullptr) { + _qualified_exports->set_in_aot_cache(); } - assert(_archived_packages_entries->get(this) == nullptr, "Each PackageEntry must not be shared across PackageEntryTables"); - _archived_packages_entries->put(this, archived_entry); - - return archived_entry; -} - -PackageEntry* PackageEntry::get_archived_entry(PackageEntry* orig_entry) { - PackageEntry** ptr = _archived_packages_entries->get(orig_entry); - if (ptr != nullptr) { - return *ptr; - } else { - return nullptr; - } -} - -void PackageEntry::iterate_symbols(MetaspaceClosure* closure) { - closure->push(&_name); -} - -void PackageEntry::init_as_archived_entry() { - Array* archived_qualified_exports = ModuleEntry::write_growable_array(_qualified_exports); - - _name = ArchiveBuilder::get_buffered_symbol(_name); - _module = ModuleEntry::get_archived_entry(_module); - _qualified_exports = (GrowableArray*)archived_qualified_exports; _defined_by_cds_in_class_path = 0; JFR_ONLY(set_trace_id(0);) // re-init at runtime - - ArchivePtrMarker::mark_pointer((address*)&_name); - ArchivePtrMarker::mark_pointer((address*)&_module); - ArchivePtrMarker::mark_pointer((address*)&_qualified_exports); - - LogStreamHandle(Info, aot, package) st; - if (st.is_enabled()) { - st.print("archived "); - print(&st); - } } void PackageEntry::load_from_archive() { - _qualified_exports = ModuleEntry::restore_growable_array((Array*)_qualified_exports); JFR_ONLY(INIT_ID(this);) } @@ -280,14 +247,7 @@ static int compare_package_by_name(PackageEntry* a, PackageEntry* b) { return a->name()->fast_compare(b->name()); } -void PackageEntryTable::iterate_symbols(MetaspaceClosure* closure) { - auto syms = [&] (const SymbolHandle& key, PackageEntry*& p) { - p->iterate_symbols(closure); - }; - _table.iterate_all(syms); -} - -Array* PackageEntryTable::allocate_archived_entries() { +Array* PackageEntryTable::build_aot_table(ClassLoaderData* loader_data, TRAPS) { // First count the packages in named modules int n = 0; auto count = [&] (const SymbolHandle& key, PackageEntry*& p) { @@ -297,12 +257,19 @@ Array* PackageEntryTable::allocate_archived_entries() { }; _table.iterate_all(count); - Array* archived_packages = ArchiveBuilder::new_rw_array(n); + Array* archived_packages = MetadataFactory::new_array(loader_data, n, nullptr, CHECK_NULL); // reset n n = 0; auto grab = [&] (const SymbolHandle& key, PackageEntry*& p) { if (p->should_be_archived()) { + p->pack_qualified_exports(); archived_packages->at_put(n++, p); + + LogStreamHandle(Info, aot, package) st; + if (st.is_enabled()) { + st.print("archived "); + p->print(&st); + } } }; _table.iterate_all(grab); @@ -311,18 +278,8 @@ Array* PackageEntryTable::allocate_archived_entries() { // Always allocate in the same order to produce deterministic archive. QuickSort::sort(archived_packages->data(), n, compare_package_by_name); } - for (int i = 0; i < n; i++) { - archived_packages->at_put(i, archived_packages->at(i)->allocate_archived_entry()); - ArchivePtrMarker::mark_pointer((address*)archived_packages->adr_at(i)); - } - return archived_packages; -} -void PackageEntryTable::init_archived_entries(Array* archived_packages) { - for (int i = 0; i < archived_packages->length(); i++) { - PackageEntry* archived_entry = archived_packages->at(i); - archived_entry->init_as_archived_entry(); - } + return archived_packages; } void PackageEntryTable::load_archived_entries(Array* archived_packages) { diff --git a/src/hotspot/share/classfile/packageEntry.hpp b/src/hotspot/share/classfile/packageEntry.hpp index 6abf89dc60f..7b174a92287 100644 --- a/src/hotspot/share/classfile/packageEntry.hpp +++ b/src/hotspot/share/classfile/packageEntry.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2026, 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,9 @@ #ifndef SHARE_CLASSFILE_PACKAGEENTRY_HPP #define SHARE_CLASSFILE_PACKAGEENTRY_HPP +#include "cds/aotGrowableArray.hpp" #include "classfile/moduleEntry.hpp" +#include "memory/metaspaceClosureType.hpp" #include "oops/symbol.hpp" #include "oops/symbolHandle.hpp" #include "runtime/atomicAccess.hpp" @@ -114,7 +116,7 @@ private: bool _must_walk_exports; // Contains list of modules this package is qualifiedly exported to. Access // to this list is protected by the Module_lock. - GrowableArray* _qualified_exports; + AOTGrowableArray* _qualified_exports; JFR_ONLY(DEFINE_TRACE_ID_FIELD;) // Initial size of a package entry's list of qualified exports. @@ -205,14 +207,24 @@ public: void purge_qualified_exports(); void delete_qualified_exports(); + void pack_qualified_exports(); // used by AOT + + // methods required by MetaspaceClosure + void metaspace_pointers_do(MetaspaceClosure* it); + int size_in_heapwords() const { return (int)heap_word_size(sizeof(PackageEntry)); } + MetaspaceClosureType type() const { return MetaspaceClosureType::PackageEntryType; } + static bool is_read_only_by_default() { return false; } + void print(outputStream* st = tty); + char* name_as_C_string() const { + assert(_name != nullptr, "name can't be null"); + return name()->as_C_string(); + } + #if INCLUDE_CDS_JAVA_HEAP bool should_be_archived() const; - void iterate_symbols(MetaspaceClosure* closure); - PackageEntry* allocate_archived_entry() const; - void init_as_archived_entry(); - static PackageEntry* get_archived_entry(PackageEntry* orig_entry); + void remove_unshareable_info(); void load_from_archive(); #endif @@ -271,9 +283,7 @@ public: void print(outputStream* st = tty); #if INCLUDE_CDS_JAVA_HEAP - void iterate_symbols(MetaspaceClosure* closure); - Array* allocate_archived_entries(); - void init_archived_entries(Array* archived_packages); + Array* build_aot_table(ClassLoaderData* loader_data, TRAPS); void load_archived_entries(Array* archived_packages); #endif }; diff --git a/src/hotspot/share/classfile/stringTable.cpp b/src/hotspot/share/classfile/stringTable.cpp index c775014cfac..2b8b7780a41 100644 --- a/src/hotspot/share/classfile/stringTable.cpp +++ b/src/hotspot/share/classfile/stringTable.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, 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 @@ -74,24 +74,9 @@ const size_t REHASH_LEN = 100; const double CLEAN_DEAD_HIGH_WATER_MARK = 0.5; #if INCLUDE_CDS_JAVA_HEAP -bool StringTable::_is_two_dimensional_shared_strings_array = false; -OopHandle StringTable::_shared_strings_array; -int StringTable::_shared_strings_array_root_index; - inline oop StringTable::read_string_from_compact_hashtable(address base_address, u4 index) { assert(AOTMappedHeapLoader::is_in_use(), "sanity"); - objArrayOop array = (objArrayOop)(_shared_strings_array.resolve()); - oop s; - - if (!_is_two_dimensional_shared_strings_array) { - s = array->obj_at((int)index); - } else { - int primary_index = index >> _secondary_array_index_bits; - int secondary_index = index & _secondary_array_index_mask; - objArrayOop secondary = (objArrayOop)array->obj_at(primary_index); - s = secondary->obj_at(secondary_index); - } - + oop s = HeapShared::get_root((int)index, false); assert(java_lang_String::is_instance(s), "must be"); return s; } @@ -115,7 +100,6 @@ OopStorage* StringTable::_oop_storage; static size_t _current_size = 0; static volatile size_t _items_count = 0; -DEBUG_ONLY(static bool _disable_interning_during_cds_dump = false); volatile bool _alt_hash = false; @@ -317,12 +301,6 @@ void StringTable::create_table() { _oop_storage->register_num_dead_callback(&gc_notification); } -#if INCLUDE_CDS_JAVA_HEAP -void StringTable::load_shared_strings_array() { - _shared_strings_array = OopHandle(Universe::vm_global(), HeapShared::get_root(_shared_strings_array_root_index)); -} -#endif - void StringTable::item_added() { AtomicAccess::inc(&_items_count); } @@ -509,9 +487,6 @@ oop StringTable::intern(const char* utf8_string, TRAPS) { } oop StringTable::intern(const StringWrapper& name, TRAPS) { - assert(!AtomicAccess::load_acquire(&_disable_interning_during_cds_dump), - "All threads that may intern strings should have been stopped before CDS starts copying the interned string table"); - // shared table always uses java_lang_String::hash_code unsigned int hash = hash_wrapped_string(name); oop found_string = lookup_shared(name, hash); @@ -614,6 +589,10 @@ struct StringTableDeleteCheck : StackObj { }; void StringTable::clean_dead_entries(JavaThread* jt) { + // BulkDeleteTask::prepare() may take ConcurrentHashTableResize_lock (nosafepoint-2). + // When NativeHeapTrimmer is enabled, SuspendMark may take NativeHeapTrimmer::_lock (nosafepoint). + // Take SuspendMark first to keep lock order and avoid deadlock. + NativeHeapTrimmer::SuspendMark sm("stringtable"); StringTableHash::BulkDeleteTask bdt(_local_table); if (!bdt.prepare(jt)) { return; @@ -621,7 +600,6 @@ void StringTable::clean_dead_entries(JavaThread* jt) { StringTableDeleteCheck stdc; StringTableDoDelete stdd; - NativeHeapTrimmer::SuspendMark sm("stringtable"); { TraceTime timer("Clean", TRACETIME_LOG(Debug, stringtable, perf)); while(bdt.do_task(jt, stdc, stdd)) { @@ -954,118 +932,13 @@ oop StringTable::lookup_shared(const jchar* name, int len) { return _shared_table.lookup(wrapped_name, java_lang_String::hash_code(name, len), 0); } -// This is called BEFORE we enter the CDS safepoint. We can still allocate Java object arrays to -// be used by the shared strings table. -void StringTable::allocate_shared_strings_array(TRAPS) { - if (!CDSConfig::is_dumping_heap()) { - return; - } +void StringTable::init_shared_table() { + assert(SafepointSynchronize::is_at_safepoint(), "inside AOT safepoint"); + precond(CDSConfig::is_dumping_heap()); + assert(HeapShared::is_writing_mapping_mode(), "not used for streamed oops"); - assert(HeapShared::is_writing_mapping_mode(), "should not reach here"); - - CompileBroker::wait_for_no_active_tasks(); - - precond(CDSConfig::allow_only_single_java_thread()); - - // At this point, no more strings will be added: - // - There's only a single Java thread (this thread). It no longer executes Java bytecodes - // so JIT compilation will eventually stop. - // - CompileBroker has no more active tasks, so all JIT requests have been processed. - - // This flag will be cleared after intern table dumping has completed, so we can run the - // compiler again (for future AOT method compilation, etc). - DEBUG_ONLY(AtomicAccess::release_store(&_disable_interning_during_cds_dump, true)); - - if (items_count_acquire() > (size_t)max_jint) { - fatal("Too many strings to be archived: %zu", items_count_acquire()); - } - - int total = (int)items_count_acquire(); - size_t single_array_size = objArrayOopDesc::object_size(total); - - log_info(aot)("allocated string table for %d strings", total); - - if (!HeapShared::is_too_large_to_archive(single_array_size)) { - // The entire table can fit in a single array - objArrayOop array = oopFactory::new_objArray(vmClasses::Object_klass(), total, CHECK); - _shared_strings_array = OopHandle(Universe::vm_global(), array); - log_info(aot)("string table array (single level) length = %d", total); - } else { - // Split the table in two levels of arrays. - int primary_array_length = (total + _secondary_array_max_length - 1) / _secondary_array_max_length; - size_t primary_array_size = objArrayOopDesc::object_size(primary_array_length); - size_t secondary_array_size = objArrayOopDesc::object_size(_secondary_array_max_length); - - if (HeapShared::is_too_large_to_archive(secondary_array_size)) { - // This can only happen if you have an extremely large number of classes that - // refer to more than 16384 * 16384 = 26M interned strings! Not a practical concern - // but bail out for safety. - log_error(aot)("Too many strings to be archived: %zu", items_count_acquire()); - AOTMetaspace::unrecoverable_writing_error(); - } - - objArrayOop primary = oopFactory::new_objArray(vmClasses::Object_klass(), primary_array_length, CHECK); - objArrayHandle primaryHandle(THREAD, primary); - _shared_strings_array = OopHandle(Universe::vm_global(), primary); - - log_info(aot)("string table array (primary) length = %d", primary_array_length); - for (int i = 0; i < primary_array_length; i++) { - int len; - if (total > _secondary_array_max_length) { - len = _secondary_array_max_length; - } else { - len = total; - } - total -= len; - - objArrayOop secondary = oopFactory::new_objArray(vmClasses::Object_klass(), len, CHECK); - primaryHandle()->obj_at_put(i, secondary); - - log_info(aot)("string table array (secondary)[%d] length = %d", i, len); - assert(!HeapShared::is_too_large_to_archive(secondary), "sanity"); - } - - assert(total == 0, "must be"); - _is_two_dimensional_shared_strings_array = true; - } -} - -#ifndef PRODUCT -void StringTable::verify_secondary_array_index_bits() { - assert(HeapShared::is_writing_mapping_mode(), "should not reach here"); - int max; - for (max = 1; ; max++) { - size_t next_size = objArrayOopDesc::object_size(1 << (max + 1)); - if (HeapShared::is_too_large_to_archive(next_size)) { - break; - } - } - // Currently max is 17 for +UseCompressedOops, 16 for -UseCompressedOops. - // When we add support for Shenandoah (which has a smaller mininum region size than G1), - // max will become 15/14. - // - // We use _secondary_array_index_bits==14 as that will be the eventual value, and will - // make testing easier. - assert(_secondary_array_index_bits <= max, - "_secondary_array_index_bits (%d) must be smaller than max possible value (%d)", - _secondary_array_index_bits, max); -} -#endif // PRODUCT - -// This is called AFTER we enter the CDS safepoint. -// -// For each shared string: -// [1] Store it into _shared_strings_array. Encode its position as a 32-bit index. -// [2] Store the index and hashcode into _shared_table. -oop StringTable::init_shared_strings_array() { - assert(CDSConfig::is_dumping_heap(), "must be"); - assert(HeapShared::is_writing_mapping_mode(), "should not reach here"); - objArrayOop array = (objArrayOop)(_shared_strings_array.resolve()); - - verify_secondary_array_index_bits(); - - int index = 0; - auto copy_into_array = [&] (WeakHandle* val) { + int n = 0; + auto copy_into_aot_heap = [&] (WeakHandle* val) { oop string = val->peek(); if (string != nullptr && !HeapShared::is_string_too_large_to_archive(string)) { // If string is too large, don't put it into the string table. @@ -1074,53 +947,34 @@ oop StringTable::init_shared_strings_array() { // - If there's a reference to it, we will report an error inside HeapShared.cpp and // dumping will fail. HeapShared::add_to_dumped_interned_strings(string); - if (!_is_two_dimensional_shared_strings_array) { - assert(index < array->length(), "no strings should have been added"); - array->obj_at_put(index, string); - } else { - int primary_index = index >> _secondary_array_index_bits; - int secondary_index = index & _secondary_array_index_mask; - - assert(primary_index < array->length(), "no strings should have been added"); - objArrayOop secondary = (objArrayOop)array->obj_at(primary_index); - - assert(secondary != nullptr && secondary->is_objArray(), "must be"); - assert(secondary_index < secondary->length(), "no strings should have been added"); - secondary->obj_at_put(secondary_index, string); - } - index ++; } + n++; return true; }; - _local_table->do_safepoint_scan(copy_into_array); - log_info(aot)("Archived %d interned strings", index); - return array; + _local_table->do_safepoint_scan(copy_into_aot_heap); + log_info(aot)("Archived %d interned strings", n); }; void StringTable::write_shared_table() { + assert(SafepointSynchronize::is_at_safepoint(), "inside AOT safepoint"); + precond(CDSConfig::is_dumping_heap()); + assert(HeapShared::is_writing_mapping_mode(), "not used for streamed oops"); + _shared_table.reset(); CompactHashtableWriter writer((int)items_count_acquire(), ArchiveBuilder::string_stats()); - int index = 0; auto copy_into_shared_table = [&] (WeakHandle* val) { oop string = val->peek(); if (string != nullptr && !HeapShared::is_string_too_large_to_archive(string)) { unsigned int hash = java_lang_String::hash_code(string); - writer.add(hash, index); - index ++; + int root_id = HeapShared::append_root(string); + writer.add(hash, root_id); } return true; }; _local_table->do_safepoint_scan(copy_into_shared_table); writer.dump(&_shared_table, "string"); - - DEBUG_ONLY(AtomicAccess::release_store(&_disable_interning_during_cds_dump, false)); -} - -void StringTable::set_shared_strings_array_index(int root_index) { - assert(HeapShared::is_writing_mapping_mode(), "should not reach here"); - _shared_strings_array_root_index = root_index; } void StringTable::serialize_shared_table_header(SerializeClosure* soc) { @@ -1132,8 +986,27 @@ void StringTable::serialize_shared_table_header(SerializeClosure* soc) { } else if (!AOTMappedHeapLoader::is_in_use()) { _shared_table.reset(); } +} - soc->do_bool(&_is_two_dimensional_shared_strings_array); - soc->do_int(&_shared_strings_array_root_index); +void StringTable::move_shared_strings_into_runtime_table() { + precond(CDSConfig::is_dumping_final_static_archive()); + JavaThread* THREAD = JavaThread::current(); + HandleMark hm(THREAD); + + int n = 0; + _shared_table.iterate_all([&](oop string) { + int length = java_lang_String::length(string); + Handle h_string (THREAD, string); + StringWrapper name(h_string, length); + unsigned int hash = hash_wrapped_string(name); + + assert(!_alt_hash, "too early"); + oop interned = do_intern(name, hash, THREAD); + assert(string == interned, "must be"); + n++; + }); + + _shared_table.reset(); + log_info(aot)("Moved %d interned strings to runtime table", n); } #endif //INCLUDE_CDS_JAVA_HEAP diff --git a/src/hotspot/share/classfile/stringTable.hpp b/src/hotspot/share/classfile/stringTable.hpp index 839e9d9053d..0024a45a2f2 100644 --- a/src/hotspot/share/classfile/stringTable.hpp +++ b/src/hotspot/share/classfile/stringTable.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, 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 @@ -109,49 +109,17 @@ public: static bool needs_rehashing() { return _needs_rehashing; } static inline void update_needs_rehash(bool rehash); - // Sharing -#if INCLUDE_CDS_JAVA_HEAP - static inline oop read_string_from_compact_hashtable(address base_address, u4 index); - + // AOT support + static inline oop read_string_from_compact_hashtable(address base_address, u4 index) NOT_CDS_JAVA_HEAP_RETURN_(nullptr); private: - static bool _is_two_dimensional_shared_strings_array; - static OopHandle _shared_strings_array; - static int _shared_strings_array_root_index; - - // All the shared strings are referenced through _shared_strings_array to keep them alive. - // Each shared string is stored as a 32-bit index in ::_shared_table. The index - // is interpreted in two ways: - // - // [1] _is_two_dimensional_shared_strings_array = false: _shared_strings_array is an Object[]. - // Each shared string is stored as _shared_strings_array[index] - // - // [2] _is_two_dimensional_shared_strings_array = true: _shared_strings_array is an Object[][] - // This happens when there are too many elements in the shared table. We store them - // using two levels of objArrays, such that none of the arrays are too big for - // AOTMappedHeapWriter::is_too_large_to_archive(). In this case, the index is splited into two - // parts. Each shared string is stored as _shared_strings_array[primary_index][secondary_index]: - // - // [bits 31 .. 14][ bits 13 .. 0 ] - // primary_index secondary_index - const static int _secondary_array_index_bits = 14; - const static int _secondary_array_max_length = 1 << _secondary_array_index_bits; - const static int _secondary_array_index_mask = _secondary_array_max_length - 1; - - // make sure _secondary_array_index_bits is not too big - static void verify_secondary_array_index_bits() PRODUCT_RETURN; -#endif // INCLUDE_CDS_JAVA_HEAP - - private: static oop lookup_shared(const StringWrapper& name, unsigned int hash) NOT_CDS_JAVA_HEAP_RETURN_(nullptr); - public: +public: static oop lookup_shared(const jchar* name, int len) NOT_CDS_JAVA_HEAP_RETURN_(nullptr); static size_t shared_entry_count() NOT_CDS_JAVA_HEAP_RETURN_(0); - static void allocate_shared_strings_array(TRAPS) NOT_CDS_JAVA_HEAP_RETURN; - static void load_shared_strings_array() NOT_CDS_JAVA_HEAP_RETURN; - static oop init_shared_strings_array() NOT_CDS_JAVA_HEAP_RETURN_(nullptr); + static void init_shared_table() NOT_CDS_JAVA_HEAP_RETURN; static void write_shared_table() NOT_CDS_JAVA_HEAP_RETURN; - static void set_shared_strings_array_index(int root_index) NOT_CDS_JAVA_HEAP_RETURN; static void serialize_shared_table_header(SerializeClosure* soc) NOT_CDS_JAVA_HEAP_RETURN; + static void move_shared_strings_into_runtime_table(); // Jcmd static void dump(outputStream* st, bool verbose=false); diff --git a/src/hotspot/share/classfile/symbolTable.cpp b/src/hotspot/share/classfile/symbolTable.cpp index ec639a2b4d3..c49aa10fa0d 100644 --- a/src/hotspot/share/classfile/symbolTable.cpp +++ b/src/hotspot/share/classfile/symbolTable.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, 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 @@ -763,6 +763,10 @@ struct SymbolTableDeleteCheck : StackObj { }; void SymbolTable::clean_dead_entries(JavaThread* jt) { + // BulkDeleteTask::prepare() may take ConcurrentHashTableResize_lock (nosafepoint-2). + // When NativeHeapTrimmer is enabled, SuspendMark may take NativeHeapTrimmer::_lock (nosafepoint). + // Take SuspendMark first to keep lock order and avoid deadlock. + NativeHeapTrimmer::SuspendMark sm("symboltable"); SymbolTableHash::BulkDeleteTask bdt(_local_table); if (!bdt.prepare(jt)) { return; @@ -770,7 +774,6 @@ void SymbolTable::clean_dead_entries(JavaThread* jt) { SymbolTableDeleteCheck stdc; SymbolTableDoDelete stdd; - NativeHeapTrimmer::SuspendMark sm("symboltable"); { TraceTime timer("Clean", TRACETIME_LOG(Debug, symboltable, perf)); while (bdt.do_task(jt, stdc, stdd)) { diff --git a/src/hotspot/share/classfile/vmIntrinsics.hpp b/src/hotspot/share/classfile/vmIntrinsics.hpp index 6f9c2326a45..07fa294e8e1 100644 --- a/src/hotspot/share/classfile/vmIntrinsics.hpp +++ b/src/hotspot/share/classfile/vmIntrinsics.hpp @@ -415,18 +415,18 @@ class methodHandle; \ do_class(java_lang_StringCoding, "java/lang/StringCoding") \ do_intrinsic(_countPositives, java_lang_StringCoding, countPositives_name, countPositives_signature, F_S) \ - do_name( countPositives_name, "countPositives0") \ + do_name( countPositives_name, "countPositives") \ do_signature(countPositives_signature, "([BII)I") \ \ do_class(sun_nio_cs_iso8859_1_Encoder, "sun/nio/cs/ISO_8859_1$Encoder") \ do_intrinsic(_encodeISOArray, sun_nio_cs_iso8859_1_Encoder, encodeISOArray_name, encodeISOArray_signature, F_S) \ - do_name( encodeISOArray_name, "encodeISOArray0") \ + do_name( encodeISOArray_name, "implEncodeISOArray") \ do_signature(encodeISOArray_signature, "([CI[BII)I") \ \ do_intrinsic(_encodeByteISOArray, java_lang_StringCoding, encodeISOArray_name, indexOfI_signature, F_S) \ \ do_intrinsic(_encodeAsciiArray, java_lang_StringCoding, encodeAsciiArray_name, encodeISOArray_signature, F_S) \ - do_name( encodeAsciiArray_name, "encodeAsciiArray0") \ + do_name( encodeAsciiArray_name, "implEncodeAsciiArray") \ \ do_class(java_math_BigInteger, "java/math/BigInteger") \ do_intrinsic(_multiplyToLen, java_math_BigInteger, multiplyToLen_name, multiplyToLen_signature, F_S) \ diff --git a/src/hotspot/share/classfile/vmSymbols.hpp b/src/hotspot/share/classfile/vmSymbols.hpp index 8388b98faae..79646f24d0e 100644 --- a/src/hotspot/share/classfile/vmSymbols.hpp +++ b/src/hotspot/share/classfile/vmSymbols.hpp @@ -245,10 +245,6 @@ class SerializeClosure; \ /* Concurrency support */ \ template(java_util_concurrent_locks_AbstractOwnableSynchronizer, "java/util/concurrent/locks/AbstractOwnableSynchronizer") \ - template(java_util_concurrent_atomic_AtomicIntegerFieldUpdater_Impl, "java/util/concurrent/atomic/AtomicIntegerFieldUpdater$AtomicIntegerFieldUpdaterImpl") \ - template(java_util_concurrent_atomic_AtomicLongFieldUpdater_CASUpdater, "java/util/concurrent/atomic/AtomicLongFieldUpdater$CASUpdater") \ - template(java_util_concurrent_atomic_AtomicLongFieldUpdater_LockedUpdater, "java/util/concurrent/atomic/AtomicLongFieldUpdater$LockedUpdater") \ - template(java_util_concurrent_atomic_AtomicReferenceFieldUpdater_Impl, "java/util/concurrent/atomic/AtomicReferenceFieldUpdater$AtomicReferenceFieldUpdaterImpl") \ template(jdk_internal_vm_annotation_Contended_signature, "Ljdk/internal/vm/annotation/Contended;") \ template(jdk_internal_vm_annotation_ReservedStackAccess_signature, "Ljdk/internal/vm/annotation/ReservedStackAccess;") \ template(jdk_internal_ValueBased_signature, "Ljdk/internal/ValueBased;") \ @@ -302,6 +298,7 @@ class SerializeClosure; template(jdk_internal_misc_Scoped_signature, "Ljdk/internal/misc/ScopedMemoryAccess$Scoped;") \ template(jdk_internal_vm_annotation_IntrinsicCandidate_signature, "Ljdk/internal/vm/annotation/IntrinsicCandidate;") \ template(jdk_internal_vm_annotation_Stable_signature, "Ljdk/internal/vm/annotation/Stable;") \ + template(jdk_internal_vm_annotation_TrustFinalFields_signature, "Ljdk/internal/vm/annotation/TrustFinalFields;") \ \ template(jdk_internal_vm_annotation_ChangesCurrentThread_signature, "Ljdk/internal/vm/annotation/ChangesCurrentThread;") \ template(jdk_internal_vm_annotation_JvmtiHideEvents_signature, "Ljdk/internal/vm/annotation/JvmtiHideEvents;") \ diff --git a/src/hotspot/share/code/aotCodeCache.cpp b/src/hotspot/share/code/aotCodeCache.cpp index 0314c5227d2..f51c068f1e7 100644 --- a/src/hotspot/share/code/aotCodeCache.cpp +++ b/src/hotspot/share/code/aotCodeCache.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2026, 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 @@ -1371,6 +1371,7 @@ void AOTCodeAddressTable::init_extrs() { SET_ADDRESS(_extrs, ShenandoahRuntime::load_reference_barrier_phantom_narrow); #endif #if INCLUDE_ZGC + SET_ADDRESS(_extrs, ZBarrierSetRuntime::load_barrier_on_oop_field_preloaded_addr()); SET_ADDRESS(_extrs, ZBarrierSetRuntime::load_barrier_on_phantom_oop_field_preloaded_addr()); #if defined(AMD64) SET_ADDRESS(_extrs, &ZPointerLoadShift); diff --git a/src/hotspot/share/code/dependencies.cpp b/src/hotspot/share/code/dependencies.cpp index d90695739a1..dbfe1cd884e 100644 --- a/src/hotspot/share/code/dependencies.cpp +++ b/src/hotspot/share/code/dependencies.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, 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 @@ -1124,7 +1124,7 @@ class AbstractClassHierarchyWalker { Klass* find_witness(InstanceKlass* context_type, KlassDepChange* changes = nullptr); static void init(); - static void print_statistics(); + NOT_PRODUCT(static void print_statistics();) }; PerfCounter* AbstractClassHierarchyWalker::_perf_find_witness_anywhere_calls_count = nullptr; @@ -2277,6 +2277,7 @@ bool KlassDepChange::involves_context(Klass* k) { return is_contained; } +#ifndef PRODUCT void Dependencies::print_statistics() { AbstractClassHierarchyWalker::print_statistics(); } @@ -2302,6 +2303,7 @@ void AbstractClassHierarchyWalker::print_statistics() { } } } +#endif CallSiteDepChange::CallSiteDepChange(Handle call_site, Handle method_handle) : _call_site(call_site), diff --git a/src/hotspot/share/code/dependencies.hpp b/src/hotspot/share/code/dependencies.hpp index d11c51a66dc..582a08183f9 100644 --- a/src/hotspot/share/code/dependencies.hpp +++ b/src/hotspot/share/code/dependencies.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, 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 @@ -649,7 +649,7 @@ class Dependencies: public ResourceObj { }; friend class Dependencies::DepStream; - static void print_statistics(); + NOT_PRODUCT(static void print_statistics();) }; diff --git a/src/hotspot/share/compiler/compileBroker.cpp b/src/hotspot/share/compiler/compileBroker.cpp index 574f4d6543b..7b236ed3589 100644 --- a/src/hotspot/share/compiler/compileBroker.cpp +++ b/src/hotspot/share/compiler/compileBroker.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, 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 @@ -2346,12 +2346,18 @@ void CompileBroker::invoke_compiler_on_method(CompileTask* task) { /* Repeat compilation without installing code for profiling purposes */ int repeat_compilation_count = directive->RepeatCompilationOption; - while (repeat_compilation_count > 0) { - ResourceMark rm(thread); - task->print_ul("NO CODE INSTALLED"); - thread->timeout()->reset(); - comp->compile_method(&ci_env, target, osr_bci, false, directive); - repeat_compilation_count--; + if (repeat_compilation_count > 0) { + CHeapStringHolder failure_reason; + failure_reason.set(ci_env._failure_reason.get()); + while (repeat_compilation_count > 0) { + ResourceMark rm(thread); + task->print_ul("NO CODE INSTALLED"); + thread->timeout()->reset(); + ci_env._failure_reason.clear(); + comp->compile_method(&ci_env, target, osr_bci, false, directive); + repeat_compilation_count--; + } + ci_env._failure_reason.set(failure_reason.get()); } } diff --git a/src/hotspot/share/compiler/disassembler.cpp b/src/hotspot/share/compiler/disassembler.cpp index c79c15e0f32..2c1ef235e07 100644 --- a/src/hotspot/share/compiler/disassembler.cpp +++ b/src/hotspot/share/compiler/disassembler.cpp @@ -607,10 +607,27 @@ void decode_env::print_address(address adr) { return; } + address card_table_base = nullptr; BarrierSet* bs = BarrierSet::barrier_set(); - if (bs->is_a(BarrierSet::CardTableBarrierSet) && - adr == ci_card_table_address_as

    ()) { - st->print("word_map_base"); +#if INCLUDE_G1GC + if (bs->is_a(BarrierSet::G1BarrierSet)) { + G1BarrierSet* g1bs = barrier_set_cast(bs); + card_table_base = g1bs->card_table()->byte_map_base(); + } else +#endif +#if INCLUDE_SHENANDOAHGC + if (bs->is_a(BarrierSet::ShenandoahBarrierSet)) { + ShenandoahBarrierSet* sbs = barrier_set_cast(bs); + if (sbs->card_table() != nullptr) { + card_table_base = sbs->card_table()->byte_map_base(); + } + } else +#endif + if (bs->is_a(BarrierSet::CardTableBarrierSet)) { + card_table_base = ci_card_table_address_as
    (); + } + if (card_table_base != nullptr && adr == card_table_base) { + st->print("card_table_base"); if (WizardMode) st->print(" " INTPTR_FORMAT, p2i(adr)); return; } diff --git a/src/hotspot/share/cppstdlib/cstdlib.hpp b/src/hotspot/share/cppstdlib/cstdlib.hpp new file mode 100644 index 00000000000..2b58e737882 --- /dev/null +++ b/src/hotspot/share/cppstdlib/cstdlib.hpp @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2025, 2026, 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. + * + */ + +#ifndef SHARE_CPPSTDLIB_CSTDLIB_HPP +#define SHARE_CPPSTDLIB_CSTDLIB_HPP + +#include "utilities/compilerWarnings.hpp" + +// HotSpot usage for : +// +// Some functions are explicitly forbidden below. That may not be a complete +// list of all the functions we should forbid. +// +// We assume provides definitions in the global namespace, in +// addition to providing them in the std namespace. We prefer to use the names +// in the global namespace. + +BEGIN_ALLOW_FORBIDDEN_FUNCTIONS +#include "utilities/vmassert_uninstall.hpp" + +#include + +#include "utilities/vmassert_reinstall.hpp" // don't reorder +END_ALLOW_FORBIDDEN_FUNCTIONS + +// AIX may define malloc and calloc as macros when certain other features are +// present, causing us all sorts of grief. +// https://www.ibm.com/docs/en/openxl-c-and-cpp-aix/17.1.4?topic=compilers-memory-allocation +// Replace the macro definitions with something we can work with. +// AIX 7.3 no longer uses macro renaming when building with clang, instead +// using the same asm replacement approach as used below. This workaround can +// be removed once earlier versions are no longer supported as build platforms. +#if defined(AIX) && (defined(__VEC__) || defined(__AIXVEC)) +#if defined(malloc) || defined(calloc) +#if !defined(malloc) || !defined(calloc) +#error "Inconsistent alloc macro mappings, expected both to be mapped." +#endif +// Remove the macros. +#undef malloc +#undef calloc +// Implement the mapping using gcc/clang asm name mapping. +extern "C" { +extern void* malloc(size_t) noexcept asm("vec_malloc"); +extern void* calloc(size_t, size_t) noexcept asm("vec_calloc"); +} // extern "C" +// Because the macros are in place when brings names into the std +// namespace, macro replacement causes the expanded names to be added instead +// of the intended names. We can't remove std::vec_malloc and std::vec_calloc, +// but we do add the standard names in case someone uses them. +namespace std { +using ::malloc; +using ::calloc; +} // namespace std +#endif // Macro definition for malloc or calloc +#endif // AIX altivec allocator support + +// Prefer os:: variants of these. +FORBID_IMPORTED_NORETURN_C_FUNCTION(void exit(int), noexcept, "use os::exit") +FORBID_IMPORTED_NORETURN_C_FUNCTION(void _Exit(int), noexcept, "use os::exit") + +// Windows puts _exit in . POSIX puts it in . +// We can't forbid it here when using clang if it's not in - see +// the clang definition for FORBIDDEN_FUNCTION_NORETURN_ATTRIBUTE. +#ifdef _WINDOWS +FORBID_IMPORTED_NORETURN_C_FUNCTION(void _exit(int), /* not noexcept */, "use os::exit") +#endif // _WINDOWS + +// These functions return raw C-heap pointers or, in case of free(), take raw +// C-heap pointers. We generally want allocation to be done through NMT, using +// os::malloc and friends. +FORBID_IMPORTED_C_FUNCTION(void* malloc(size_t), noexcept, "use os::malloc"); +FORBID_IMPORTED_C_FUNCTION(void free(void*), noexcept, "use os::free"); +FORBID_IMPORTED_C_FUNCTION(void* calloc(size_t, size_t), noexcept, "use os::malloc and zero out manually"); +FORBID_IMPORTED_C_FUNCTION(void* realloc(void*, size_t), noexcept, "use os::realloc"); + +// These are not provided (and are unimplementable?) by Windows. +// https://stackoverflow.com/questions/62962839/stdaligned-alloc-missing-from-visual-studio-2019 +// They also aren't useful for a POSIX implementation of NMT. +#ifndef _WINDOWS +FORBID_C_FUNCTION(void* aligned_alloc(size_t, size_t), noexcept, "don't use"); +FORBID_C_FUNCTION(int posix_memalign(void**, size_t, size_t), noexcept, "don't use"); +#endif // !_WINDOWS + +#endif // SHARE_CPPSTDLIB_CSTDLIB_HPP diff --git a/src/hotspot/share/gc/epsilon/epsilonHeap.cpp b/src/hotspot/share/gc/epsilon/epsilonHeap.cpp index e5ae673ef0c..59ab69b2427 100644 --- a/src/hotspot/share/gc/epsilon/epsilonHeap.cpp +++ b/src/hotspot/share/gc/epsilon/epsilonHeap.cpp @@ -62,8 +62,6 @@ jint EpsilonHeap::initialize() { // Enable monitoring _monitoring_support = new EpsilonMonitoringSupport(this); - _last_counter_update = 0; - _last_heap_print = 0; // Install barrier set BarrierSet::set_barrier_set(new EpsilonBarrierSet()); @@ -77,6 +75,7 @@ jint EpsilonHeap::initialize() { void EpsilonHeap::initialize_serviceability() { _pool = new EpsilonMemoryPool(this); _memory_manager.add_pool(_pool); + _monitoring_support->mark_ready(); } GrowableArray EpsilonHeap::memory_managers() { @@ -101,7 +100,7 @@ EpsilonHeap* EpsilonHeap::heap() { return named_heap(CollectedHeap::Epsilon); } -HeapWord* EpsilonHeap::allocate_work(size_t size, bool verbose) { +HeapWord* EpsilonHeap::allocate_work(size_t size) { assert(is_object_aligned(size), "Allocation size should be aligned: %zu", size); HeapWord* res = nullptr; @@ -151,19 +150,23 @@ HeapWord* EpsilonHeap::allocate_work(size_t size, bool verbose) { size_t used = _space->used(); - // Allocation successful, update counters - if (verbose) { - size_t last = _last_counter_update; - if ((used - last >= _step_counter_update) && AtomicAccess::cmpxchg(&_last_counter_update, last, used) == last) { + // Allocation successful, update counters and print status. + // At this point, some diagnostic subsystems might not yet be initialized. + // We pretend the printout happened either way. This keeps allocation path + // from obsessively checking the subsystems' status on every allocation. + size_t last_counter = _last_counter_update.load_relaxed(); + if ((used - last_counter >= _step_counter_update) && + _last_counter_update.compare_set(last_counter, used)) { + if (_monitoring_support->is_ready()) { _monitoring_support->update_counters(); } } - // ...and print the occupancy line, if needed - if (verbose) { - size_t last = _last_heap_print; - if ((used - last >= _step_heap_print) && AtomicAccess::cmpxchg(&_last_heap_print, last, used) == last) { - print_heap_info(used); + size_t last_heap = _last_heap_print.load_relaxed(); + if ((used - last_heap >= _step_heap_print) && + _last_heap_print.compare_set(last_heap, used)) { + print_heap_info(used); + if (Metaspace::initialized()) { print_metaspace_info(); } } @@ -265,8 +268,7 @@ HeapWord* EpsilonHeap::mem_allocate(size_t size) { } HeapWord* EpsilonHeap::allocate_loaded_archive_space(size_t size) { - // Cannot use verbose=true because Metaspace is not initialized - return allocate_work(size, /* verbose = */false); + return allocate_work(size); } void EpsilonHeap::collect(GCCause::Cause cause) { diff --git a/src/hotspot/share/gc/epsilon/epsilonHeap.hpp b/src/hotspot/share/gc/epsilon/epsilonHeap.hpp index 4f812bde8b3..8d7aa7960fd 100644 --- a/src/hotspot/share/gc/epsilon/epsilonHeap.hpp +++ b/src/hotspot/share/gc/epsilon/epsilonHeap.hpp @@ -31,6 +31,7 @@ #include "gc/shared/collectedHeap.hpp" #include "gc/shared/space.hpp" #include "memory/virtualspace.hpp" +#include "runtime/atomic.hpp" #include "services/memoryManager.hpp" class EpsilonHeap : public CollectedHeap { @@ -45,8 +46,8 @@ private: size_t _step_counter_update; size_t _step_heap_print; int64_t _decay_time_ns; - volatile size_t _last_counter_update; - volatile size_t _last_heap_print; + Atomic _last_counter_update; + Atomic _last_heap_print; void print_tracing_info() const override; void stop() override {}; @@ -83,7 +84,7 @@ public: bool requires_barriers(stackChunkOop obj) const override { return false; } // Allocation - HeapWord* allocate_work(size_t size, bool verbose = true); + HeapWord* allocate_work(size_t size); HeapWord* mem_allocate(size_t size) override; HeapWord* allocate_new_tlab(size_t min_size, size_t requested_size, diff --git a/src/hotspot/share/gc/epsilon/epsilonMonitoringSupport.cpp b/src/hotspot/share/gc/epsilon/epsilonMonitoringSupport.cpp index 51d0a8356d2..213fc18b8ff 100644 --- a/src/hotspot/share/gc/epsilon/epsilonMonitoringSupport.cpp +++ b/src/hotspot/share/gc/epsilon/epsilonMonitoringSupport.cpp @@ -99,6 +99,7 @@ EpsilonMonitoringSupport::EpsilonMonitoringSupport(EpsilonHeap* heap) { } void EpsilonMonitoringSupport::update_counters() { + assert(is_ready(), "Must be ready"); MemoryService::track_memory_usage(); if (UsePerfData) { @@ -110,3 +111,11 @@ void EpsilonMonitoringSupport::update_counters() { MetaspaceCounters::update_performance_counters(); } } + +bool EpsilonMonitoringSupport::is_ready() { + return _ready.load_acquire(); +} + +void EpsilonMonitoringSupport::mark_ready() { + _ready.release_store(true); +} diff --git a/src/hotspot/share/gc/epsilon/epsilonMonitoringSupport.hpp b/src/hotspot/share/gc/epsilon/epsilonMonitoringSupport.hpp index 67a60d92778..9dc52c2a659 100644 --- a/src/hotspot/share/gc/epsilon/epsilonMonitoringSupport.hpp +++ b/src/hotspot/share/gc/epsilon/epsilonMonitoringSupport.hpp @@ -26,6 +26,7 @@ #define SHARE_GC_EPSILON_EPSILONMONITORINGSUPPORT_HPP #include "memory/allocation.hpp" +#include "runtime/atomic.hpp" class EpsilonGenerationCounters; class EpsilonSpaceCounters; @@ -35,9 +36,12 @@ class EpsilonMonitoringSupport : public CHeapObj { private: EpsilonGenerationCounters* _heap_counters; EpsilonSpaceCounters* _space_counters; + Atomic _ready; public: EpsilonMonitoringSupport(EpsilonHeap* heap); + bool is_ready(); + void mark_ready(); void update_counters(); }; diff --git a/src/hotspot/share/gc/g1/g1Arguments.cpp b/src/hotspot/share/gc/g1/g1Arguments.cpp index 2be0e008c22..ffb06a7d822 100644 --- a/src/hotspot/share/gc/g1/g1Arguments.cpp +++ b/src/hotspot/share/gc/g1/g1Arguments.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2017, Red Hat, Inc. and/or its affiliates. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -36,6 +36,7 @@ #include "gc/shared/fullGCForwarding.hpp" #include "gc/shared/gcArguments.hpp" #include "gc/shared/workerPolicy.hpp" +#include "runtime/flags/jvmFlagLimit.hpp" #include "runtime/globals.hpp" #include "runtime/globals_extension.hpp" #include "runtime/java.hpp" @@ -190,7 +191,8 @@ void G1Arguments::initialize() { } FLAG_SET_DEFAULT(G1ConcRefinementThreads, 0); } else if (FLAG_IS_DEFAULT(G1ConcRefinementThreads)) { - FLAG_SET_ERGO(G1ConcRefinementThreads, ParallelGCThreads); + const JVMTypedFlagLimit* conc_refinement_threads_limits = JVMFlagLimit::get_range_at(FLAG_MEMBER_ENUM(G1ConcRefinementThreads))->cast(); + FLAG_SET_ERGO(G1ConcRefinementThreads, MIN2(ParallelGCThreads, conc_refinement_threads_limits->max())); } if (FLAG_IS_DEFAULT(ConcGCThreads) || ConcGCThreads == 0) { @@ -207,6 +209,17 @@ void G1Arguments::initialize() { FLAG_SET_DEFAULT(GCTimeRatio, 24); } + // Do not interfere with GC-Pressure driven heap resizing unless the user + // explicitly sets otherwise. G1 heap sizing should be free to grow or shrink + // the heap based on GC pressure, rather than being forced to satisfy + // MinHeapFreeRatio or MaxHeapFreeRatio defaults that the user did not set. + if (FLAG_IS_DEFAULT(MinHeapFreeRatio)) { + FLAG_SET_DEFAULT(MinHeapFreeRatio, 0); + } + if (FLAG_IS_DEFAULT(MaxHeapFreeRatio)) { + FLAG_SET_DEFAULT(MaxHeapFreeRatio, 100); + } + // Below, we might need to calculate the pause time interval based on // the pause target. When we do so we are going to give G1 maximum // flexibility and allow it to do pauses when it needs to. So, we'll diff --git a/src/hotspot/share/gc/g1/g1BarrierSet.inline.hpp b/src/hotspot/share/gc/g1/g1BarrierSet.inline.hpp index ee2c1450d9b..794e5db0634 100644 --- a/src/hotspot/share/gc/g1/g1BarrierSet.inline.hpp +++ b/src/hotspot/share/gc/g1/g1BarrierSet.inline.hpp @@ -70,7 +70,11 @@ inline void G1BarrierSet::write_ref_field_pre(T* field) { template inline void G1BarrierSet::write_ref_field_post(T* field) { - volatile CardValue* byte = _card_table->byte_for(field); + // Make sure that the card table reference is read only once. Otherwise the compiler + // might reload that value in the two accesses below, that could cause writes to + // the wrong card table. + CardTable* card_table = AtomicAccess::load(&_card_table); + CardValue* byte = card_table->byte_for(field); if (*byte == G1CardTable::clean_card_val()) { *byte = G1CardTable::dirty_card_val(); } diff --git a/src/hotspot/share/gc/g1/g1BatchedTask.cpp b/src/hotspot/share/gc/g1/g1BatchedTask.cpp index 57558301541..1f082153476 100644 --- a/src/hotspot/share/gc/g1/g1BatchedTask.cpp +++ b/src/hotspot/share/gc/g1/g1BatchedTask.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, 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,7 +26,6 @@ #include "gc/g1/g1BatchedTask.hpp" #include "gc/g1/g1CollectedHeap.inline.hpp" #include "gc/g1/g1GCParPhaseTimesTracker.hpp" -#include "runtime/atomicAccess.hpp" #include "utilities/growableArray.hpp" void G1AbstractSubTask::record_work_item(uint worker_id, uint index, size_t count) { @@ -40,7 +39,7 @@ const char* G1AbstractSubTask::name() const { } bool G1BatchedTask::try_claim_serial_task(int& task) { - task = AtomicAccess::fetch_then_add(&_num_serial_tasks_done, 1); + task = _num_serial_tasks_done.fetch_then_add(1); return task < _serial_tasks.length(); } @@ -96,8 +95,8 @@ void G1BatchedTask::work(uint worker_id) { } G1BatchedTask::~G1BatchedTask() { - assert(AtomicAccess::load(&_num_serial_tasks_done) >= _serial_tasks.length(), - "Only %d tasks of %d claimed", AtomicAccess::load(&_num_serial_tasks_done), _serial_tasks.length()); + assert(_num_serial_tasks_done.load_relaxed() >= _serial_tasks.length(), + "Only %d tasks of %d claimed", _num_serial_tasks_done.load_relaxed(), _serial_tasks.length()); for (G1AbstractSubTask* task : _parallel_tasks) { delete task; diff --git a/src/hotspot/share/gc/g1/g1BatchedTask.hpp b/src/hotspot/share/gc/g1/g1BatchedTask.hpp index 020fda634e4..a6d2ef923c0 100644 --- a/src/hotspot/share/gc/g1/g1BatchedTask.hpp +++ b/src/hotspot/share/gc/g1/g1BatchedTask.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, 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,6 +28,7 @@ #include "gc/g1/g1GCPhaseTimes.hpp" #include "gc/shared/workerThread.hpp" #include "memory/allocation.hpp" +#include "runtime/atomic.hpp" template class GrowableArrayCHeap; @@ -120,7 +121,7 @@ public: // 5) ~T() // class G1BatchedTask : public WorkerTask { - volatile int _num_serial_tasks_done; + Atomic _num_serial_tasks_done; G1GCPhaseTimes* _phase_times; bool try_claim_serial_task(int& task); diff --git a/src/hotspot/share/gc/g1/g1CardSet.cpp b/src/hotspot/share/gc/g1/g1CardSet.cpp index 80cf5fea76a..60ad63e812c 100644 --- a/src/hotspot/share/gc/g1/g1CardSet.cpp +++ b/src/hotspot/share/gc/g1/g1CardSet.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, 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 @@ -29,7 +29,6 @@ #include "gc/shared/gcLogPrecious.hpp" #include "gc/shared/gcTraceTime.inline.hpp" #include "memory/allocation.inline.hpp" -#include "runtime/atomicAccess.hpp" #include "runtime/globals_extension.hpp" #include "runtime/java.hpp" #include "utilities/bitMap.inline.hpp" @@ -192,32 +191,32 @@ const char* G1CardSetConfiguration::mem_object_type_name_str(uint index) { void G1CardSetCoarsenStats::reset() { STATIC_ASSERT(ARRAY_SIZE(_coarsen_from) == ARRAY_SIZE(_coarsen_collision)); for (uint i = 0; i < ARRAY_SIZE(_coarsen_from); i++) { - _coarsen_from[i] = 0; - _coarsen_collision[i] = 0; + _coarsen_from[i].store_relaxed(0); + _coarsen_collision[i].store_relaxed(0); } } void G1CardSetCoarsenStats::set(G1CardSetCoarsenStats& other) { STATIC_ASSERT(ARRAY_SIZE(_coarsen_from) == ARRAY_SIZE(_coarsen_collision)); for (uint i = 0; i < ARRAY_SIZE(_coarsen_from); i++) { - _coarsen_from[i] = other._coarsen_from[i]; - _coarsen_collision[i] = other._coarsen_collision[i]; + _coarsen_from[i].store_relaxed(other._coarsen_from[i].load_relaxed()); + _coarsen_collision[i].store_relaxed(other._coarsen_collision[i].load_relaxed()); } } void G1CardSetCoarsenStats::subtract_from(G1CardSetCoarsenStats& other) { STATIC_ASSERT(ARRAY_SIZE(_coarsen_from) == ARRAY_SIZE(_coarsen_collision)); for (uint i = 0; i < ARRAY_SIZE(_coarsen_from); i++) { - _coarsen_from[i] = other._coarsen_from[i] - _coarsen_from[i]; - _coarsen_collision[i] = other._coarsen_collision[i] - _coarsen_collision[i]; + _coarsen_from[i].store_relaxed(other._coarsen_from[i].load_relaxed() - _coarsen_from[i].load_relaxed()); + _coarsen_collision[i].store_relaxed(other._coarsen_collision[i].load_relaxed() - _coarsen_collision[i].load_relaxed()); } } void G1CardSetCoarsenStats::record_coarsening(uint tag, bool collision) { assert(tag < ARRAY_SIZE(_coarsen_from), "tag %u out of bounds", tag); - AtomicAccess::inc(&_coarsen_from[tag], memory_order_relaxed); + _coarsen_from[tag].add_then_fetch(1u, memory_order_relaxed); if (collision) { - AtomicAccess::inc(&_coarsen_collision[tag], memory_order_relaxed); + _coarsen_collision[tag].add_then_fetch(1u, memory_order_relaxed); } } @@ -228,13 +227,13 @@ void G1CardSetCoarsenStats::print_on(outputStream* out) { "Inline->AoC %zu (%zu) " "AoC->BitMap %zu (%zu) " "BitMap->Full %zu (%zu) ", - _coarsen_from[0], _coarsen_collision[0], - _coarsen_from[1], _coarsen_collision[1], + _coarsen_from[0].load_relaxed(), _coarsen_collision[0].load_relaxed(), + _coarsen_from[1].load_relaxed(), _coarsen_collision[1].load_relaxed(), // There is no BitMap at the first level so we can't . - _coarsen_from[3], _coarsen_collision[3], - _coarsen_from[4], _coarsen_collision[4], - _coarsen_from[5], _coarsen_collision[5], - _coarsen_from[6], _coarsen_collision[6] + _coarsen_from[3].load_relaxed(), _coarsen_collision[3].load_relaxed(), + _coarsen_from[4].load_relaxed(), _coarsen_collision[4].load_relaxed(), + _coarsen_from[5].load_relaxed(), _coarsen_collision[5].load_relaxed(), + _coarsen_from[6].load_relaxed(), _coarsen_collision[6].load_relaxed() ); } @@ -248,7 +247,7 @@ class G1CardSetHashTable : public CHeapObj { // the per region cardsets. const static uint GroupBucketClaimSize = 4; // Did we insert at least one card in the table? - bool volatile _inserted_card; + Atomic _inserted_card; G1CardSetMemoryManager* _mm; CardSetHash _table; @@ -311,10 +310,10 @@ public: G1CardSetHashTableValue value(region_idx, G1CardSetInlinePtr()); bool inserted = _table.insert_get(Thread::current(), lookup, value, found, should_grow); - if (!_inserted_card && inserted) { + if (!_inserted_card.load_relaxed() && inserted) { // It does not matter to us who is setting the flag so a regular atomic store // is sufficient. - AtomicAccess::store(&_inserted_card, true); + _inserted_card.store_relaxed(true); } return found.value(); @@ -343,9 +342,9 @@ public: } void reset() { - if (AtomicAccess::load(&_inserted_card)) { + if (_inserted_card.load_relaxed()) { _table.unsafe_reset(InitialLogTableSize); - AtomicAccess::store(&_inserted_card, false); + _inserted_card.store_relaxed(false); } } @@ -455,14 +454,14 @@ void G1CardSet::free_mem_object(ContainerPtr container) { _mm->free(container_type_to_mem_object_type(type), value); } -G1CardSet::ContainerPtr G1CardSet::acquire_container(ContainerPtr volatile* container_addr) { +G1CardSet::ContainerPtr G1CardSet::acquire_container(Atomic* container_addr) { // Update reference counts under RCU critical section to avoid a // use-after-cleapup bug where we increment a reference count for // an object whose memory has already been cleaned up and reused. GlobalCounter::CriticalSection cs(Thread::current()); while (true) { // Get ContainerPtr and increment refcount atomically wrt to memory reuse. - ContainerPtr container = AtomicAccess::load_acquire(container_addr); + ContainerPtr container = container_addr->load_acquire(); uint cs_type = container_type(container); if (container == FullCardSet || cs_type == ContainerInlinePtr) { return container; @@ -503,15 +502,15 @@ class G1ReleaseCardsets : public StackObj { G1CardSet* _card_set; using ContainerPtr = G1CardSet::ContainerPtr; - void coarsen_to_full(ContainerPtr* container_addr) { + void coarsen_to_full(Atomic* container_addr) { while (true) { - ContainerPtr cur_container = AtomicAccess::load_acquire(container_addr); + ContainerPtr cur_container = container_addr->load_acquire(); uint cs_type = G1CardSet::container_type(cur_container); if (cur_container == G1CardSet::FullCardSet) { return; } - ContainerPtr old_value = AtomicAccess::cmpxchg(container_addr, cur_container, G1CardSet::FullCardSet); + ContainerPtr old_value = container_addr->compare_exchange(cur_container, G1CardSet::FullCardSet); if (old_value == cur_container) { _card_set->release_and_maybe_free_container(cur_container); @@ -523,7 +522,7 @@ class G1ReleaseCardsets : public StackObj { public: explicit G1ReleaseCardsets(G1CardSet* card_set) : _card_set(card_set) { } - void operator ()(ContainerPtr* container_addr) { + void operator ()(Atomic* container_addr) { coarsen_to_full(container_addr); } }; @@ -544,10 +543,10 @@ G1AddCardResult G1CardSet::add_to_howl(ContainerPtr parent_container, ContainerPtr container; uint bucket = _config->howl_bucket_index(card_in_region); - ContainerPtr volatile* bucket_entry = howl->container_addr(bucket); + Atomic* bucket_entry = howl->container_addr(bucket); while (true) { - if (AtomicAccess::load(&howl->_num_entries) >= _config->cards_in_howl_threshold()) { + if (howl->_num_entries.load_relaxed() >= _config->cards_in_howl_threshold()) { return Overflow; } @@ -571,7 +570,7 @@ G1AddCardResult G1CardSet::add_to_howl(ContainerPtr parent_container, } if (increment_total && add_result == Added) { - AtomicAccess::inc(&howl->_num_entries, memory_order_relaxed); + howl->_num_entries.add_then_fetch(1u, memory_order_relaxed); } if (to_transfer != nullptr) { @@ -588,7 +587,7 @@ G1AddCardResult G1CardSet::add_to_bitmap(ContainerPtr container, uint card_in_re return bitmap->add(card_offset, _config->cards_in_howl_bitmap_threshold(), _config->max_cards_in_howl_bitmap()); } -G1AddCardResult G1CardSet::add_to_inline_ptr(ContainerPtr volatile* container_addr, ContainerPtr container, uint card_in_region) { +G1AddCardResult G1CardSet::add_to_inline_ptr(Atomic* container_addr, ContainerPtr container, uint card_in_region) { G1CardSetInlinePtr value(container_addr, container); return value.add(card_in_region, _config->inline_ptr_bits_per_card(), _config->max_cards_in_inline_ptr()); } @@ -610,7 +609,7 @@ G1CardSet::ContainerPtr G1CardSet::create_coarsened_array_of_cards(uint card_in_ return new_container; } -bool G1CardSet::coarsen_container(ContainerPtr volatile* container_addr, +bool G1CardSet::coarsen_container(Atomic* container_addr, ContainerPtr cur_container, uint card_in_region, bool within_howl) { @@ -640,7 +639,7 @@ bool G1CardSet::coarsen_container(ContainerPtr volatile* container_addr, ShouldNotReachHere(); } - ContainerPtr old_value = AtomicAccess::cmpxchg(container_addr, cur_container, new_container); // Memory order? + ContainerPtr old_value = container_addr->compare_exchange(cur_container, new_container); // Memory order? if (old_value == cur_container) { // Success. Indicate that the cards from the current card set must be transferred // by this caller. @@ -687,7 +686,7 @@ void G1CardSet::transfer_cards(G1CardSetHashTableValue* table_entry, ContainerPt assert(container_type(source_container) == ContainerHowl, "must be"); // Need to correct for that the Full remembered set occupies more cards than the // AoCS before. - AtomicAccess::add(&_num_occupied, _config->max_cards_in_region() - table_entry->_num_occupied, memory_order_relaxed); + _num_occupied.add_then_fetch(_config->max_cards_in_region() - table_entry->_num_occupied.load_relaxed(), memory_order_relaxed); } } @@ -713,18 +712,18 @@ void G1CardSet::transfer_cards_in_howl(ContainerPtr parent_container, diff -= 1; G1CardSetHowl* howling_array = container_ptr(parent_container); - AtomicAccess::add(&howling_array->_num_entries, diff, memory_order_relaxed); + howling_array->_num_entries.add_then_fetch(diff, memory_order_relaxed); G1CardSetHashTableValue* table_entry = get_container(card_region); assert(table_entry != nullptr, "Table entry not found for transferred cards"); - AtomicAccess::add(&table_entry->_num_occupied, diff, memory_order_relaxed); + table_entry->_num_occupied.add_then_fetch(diff, memory_order_relaxed); - AtomicAccess::add(&_num_occupied, diff, memory_order_relaxed); + _num_occupied.add_then_fetch(diff, memory_order_relaxed); } } -G1AddCardResult G1CardSet::add_to_container(ContainerPtr volatile* container_addr, +G1AddCardResult G1CardSet::add_to_container(Atomic* container_addr, ContainerPtr container, uint card_region, uint card_in_region, @@ -827,8 +826,8 @@ G1AddCardResult G1CardSet::add_card(uint card_region, uint card_in_region, bool } if (increment_total && add_result == Added) { - AtomicAccess::inc(&table_entry->_num_occupied, memory_order_relaxed); - AtomicAccess::inc(&_num_occupied, memory_order_relaxed); + table_entry->_num_occupied.add_then_fetch(1u, memory_order_relaxed); + _num_occupied.add_then_fetch(1u, memory_order_relaxed); } if (should_grow_table) { _table->grow(); @@ -853,7 +852,7 @@ bool G1CardSet::contains_card(uint card_region, uint card_in_region) { return false; } - ContainerPtr container = table_entry->_container; + ContainerPtr container = table_entry->_container.load_relaxed(); if (container == FullCardSet) { // contains_card() is not a performance critical method so we do not hide that // case in the switch below. @@ -889,7 +888,7 @@ void G1CardSet::print_info(outputStream* st, uintptr_t card) { return; } - ContainerPtr container = table_entry->_container; + ContainerPtr container = table_entry->_container.load_relaxed(); if (container == FullCardSet) { st->print("FULL card set)"); return; @@ -940,7 +939,7 @@ void G1CardSet::iterate_cards_during_transfer(ContainerPtr const container, Card void G1CardSet::iterate_containers(ContainerPtrClosure* cl, bool at_safepoint) { auto do_value = [&] (G1CardSetHashTableValue* value) { - cl->do_containerptr(value->_region_idx, value->_num_occupied, value->_container); + cl->do_containerptr(value->_region_idx, value->_num_occupied.load_relaxed(), value->_container.load_relaxed()); return true; }; @@ -1001,11 +1000,11 @@ bool G1CardSet::occupancy_less_or_equal_to(size_t limit) const { } bool G1CardSet::is_empty() const { - return _num_occupied == 0; + return _num_occupied.load_relaxed() == 0; } size_t G1CardSet::occupied() const { - return _num_occupied; + return _num_occupied.load_relaxed(); } size_t G1CardSet::num_containers() { @@ -1024,10 +1023,6 @@ size_t G1CardSet::num_containers() { return cl._count; } -G1CardSetCoarsenStats G1CardSet::coarsen_stats() { - return _coarsen_stats; -} - void G1CardSet::print_coarsen_stats(outputStream* out) { _last_coarsen_stats.subtract_from(_coarsen_stats); @@ -1055,7 +1050,7 @@ size_t G1CardSet::static_mem_size() { void G1CardSet::clear() { _table->reset(); - _num_occupied = 0; + _num_occupied.store_relaxed(0); _mm->flush(); } diff --git a/src/hotspot/share/gc/g1/g1CardSet.hpp b/src/hotspot/share/gc/g1/g1CardSet.hpp index 9cefc4b1c22..64ddf0ca6a4 100644 --- a/src/hotspot/share/gc/g1/g1CardSet.hpp +++ b/src/hotspot/share/gc/g1/g1CardSet.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, 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,6 +27,7 @@ #include "memory/allocation.hpp" #include "memory/memRegion.hpp" +#include "runtime/atomic.hpp" #include "utilities/concurrentHashTable.hpp" class G1CardSetAllocOptions; @@ -154,8 +155,8 @@ public: private: // Indices are "from" indices. - size_t _coarsen_from[NumCoarsenCategories]; - size_t _coarsen_collision[NumCoarsenCategories]; + Atomic _coarsen_from[NumCoarsenCategories]; + Atomic _coarsen_collision[NumCoarsenCategories]; public: G1CardSetCoarsenStats() { reset(); } @@ -271,11 +272,11 @@ private: // Total number of cards in this card set. This is a best-effort value, i.e. there may // be (slightly) more cards in the card set than this value in reality. - size_t _num_occupied; + Atomic _num_occupied; ContainerPtr make_container_ptr(void* value, uintptr_t type); - ContainerPtr acquire_container(ContainerPtr volatile* container_addr); + ContainerPtr acquire_container(Atomic* container_addr); // Returns true if the card set container should be released bool release_container(ContainerPtr container); // Release card set and free if needed. @@ -288,7 +289,7 @@ private: // coarsen_container does not transfer cards from cur_container // to the new container. Transfer is achieved by transfer_cards. // Returns true if this was the thread that coarsened the container (and added the card). - bool coarsen_container(ContainerPtr volatile* container_addr, + bool coarsen_container(Atomic* container_addr, ContainerPtr cur_container, uint card_in_region, bool within_howl = false); @@ -300,9 +301,9 @@ private: void transfer_cards(G1CardSetHashTableValue* table_entry, ContainerPtr source_container, uint card_region); void transfer_cards_in_howl(ContainerPtr parent_container, ContainerPtr source_container, uint card_region); - G1AddCardResult add_to_container(ContainerPtr volatile* container_addr, ContainerPtr container, uint card_region, uint card, bool increment_total = true); + G1AddCardResult add_to_container(Atomic* container_addr, ContainerPtr container, uint card_region, uint card, bool increment_total = true); - G1AddCardResult add_to_inline_ptr(ContainerPtr volatile* container_addr, ContainerPtr container, uint card_in_region); + G1AddCardResult add_to_inline_ptr(Atomic* container_addr, ContainerPtr container, uint card_in_region); G1AddCardResult add_to_array(ContainerPtr container, uint card_in_region); G1AddCardResult add_to_bitmap(ContainerPtr container, uint card_in_region); G1AddCardResult add_to_howl(ContainerPtr parent_container, uint card_region, uint card_in_region, bool increment_total = true); @@ -366,7 +367,6 @@ public: size_t num_containers(); - static G1CardSetCoarsenStats coarsen_stats(); static void print_coarsen_stats(outputStream* out); // Returns size of the actual remembered set containers in bytes. @@ -412,8 +412,15 @@ public: using ContainerPtr = G1CardSet::ContainerPtr; const uint _region_idx; - uint volatile _num_occupied; - ContainerPtr volatile _container; + Atomic _num_occupied; + Atomic _container; + + // Copy constructor needed for use in ConcurrentHashTable. + G1CardSetHashTableValue(const G1CardSetHashTableValue& other) : + _region_idx(other._region_idx), + _num_occupied(other._num_occupied.load_relaxed()), + _container(other._container.load_relaxed()) + { } G1CardSetHashTableValue(uint region_idx, ContainerPtr container) : _region_idx(region_idx), _num_occupied(0), _container(container) { } }; diff --git a/src/hotspot/share/gc/g1/g1CardSetContainers.hpp b/src/hotspot/share/gc/g1/g1CardSetContainers.hpp index 72c7795be2e..78551479e06 100644 --- a/src/hotspot/share/gc/g1/g1CardSetContainers.hpp +++ b/src/hotspot/share/gc/g1/g1CardSetContainers.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2026, 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 @@ #include "gc/g1/g1CardSet.hpp" #include "memory/allocation.hpp" -#include "runtime/atomicAccess.hpp" +#include "runtime/atomic.hpp" #include "utilities/bitMap.hpp" #include "utilities/globalDefinitions.hpp" @@ -67,7 +67,7 @@ class G1CardSetInlinePtr : public StackObj { using ContainerPtr = G1CardSet::ContainerPtr; - ContainerPtr volatile * _value_addr; + Atomic* _value_addr; ContainerPtr _value; static const uint SizeFieldLen = 3; @@ -103,7 +103,7 @@ public: explicit G1CardSetInlinePtr(ContainerPtr value) : G1CardSetInlinePtr(nullptr, value) {} - G1CardSetInlinePtr(ContainerPtr volatile* value_addr, ContainerPtr value) : _value_addr(value_addr), _value(value) { + G1CardSetInlinePtr(Atomic* value_addr, ContainerPtr value) : _value_addr(value_addr), _value(value) { assert(G1CardSet::container_type(_value) == G1CardSet::ContainerInlinePtr, "Value " PTR_FORMAT " is not a valid G1CardSetInlinePtr.", p2i(_value)); } @@ -145,13 +145,13 @@ public: // All but inline pointers are of this kind. For those, card entries are stored // directly in the ContainerPtr of the ConcurrentHashTable node. class G1CardSetContainer { - uintptr_t _ref_count; + Atomic _ref_count; protected: ~G1CardSetContainer() = default; public: G1CardSetContainer() : _ref_count(3) { } - uintptr_t refcount() const { return AtomicAccess::load_acquire(&_ref_count); } + uintptr_t refcount() const { return _ref_count.load_acquire(); } bool try_increment_refcount(); @@ -172,7 +172,7 @@ public: using ContainerPtr = G1CardSet::ContainerPtr; private: EntryCountType _size; - EntryCountType volatile _num_entries; + Atomic _num_entries; // VLA implementation. EntryDataType _data[1]; @@ -180,10 +180,10 @@ private: static const EntryCountType EntryMask = LockBitMask - 1; class G1CardSetArrayLocker : public StackObj { - EntryCountType volatile* _num_entries_addr; + Atomic* _num_entries_addr; EntryCountType _local_num_entries; public: - G1CardSetArrayLocker(EntryCountType volatile* value); + G1CardSetArrayLocker(Atomic* value); EntryCountType num_entries() const { return _local_num_entries; } void inc_num_entries() { @@ -192,7 +192,7 @@ private: } ~G1CardSetArrayLocker() { - AtomicAccess::release_store(_num_entries_addr, _local_num_entries); + _num_entries_addr->release_store(_local_num_entries); } }; @@ -213,7 +213,7 @@ public: template void iterate(CardVisitor& found); - size_t num_entries() const { return _num_entries & EntryMask; } + size_t num_entries() const { return _num_entries.load_relaxed() & EntryMask; } static size_t header_size_in_bytes(); @@ -223,7 +223,7 @@ public: }; class G1CardSetBitMap : public G1CardSetContainer { - size_t _num_bits_set; + Atomic _num_bits_set; BitMap::bm_word_t _bits[1]; public: @@ -236,7 +236,7 @@ public: return bm.at(card_idx); } - uint num_bits_set() const { return (uint)_num_bits_set; } + uint num_bits_set() const { return (uint)_num_bits_set.load_relaxed(); } template void iterate(CardVisitor& found, size_t const size_in_bits, uint offset); @@ -255,10 +255,10 @@ class G1CardSetHowl : public G1CardSetContainer { public: typedef uint EntryCountType; using ContainerPtr = G1CardSet::ContainerPtr; - EntryCountType volatile _num_entries; + Atomic _num_entries; private: // VLA implementation. - ContainerPtr _buckets[1]; + Atomic _buckets[1]; // Do not add class member variables beyond this point. // Iterates over the given ContainerPtr with at index in this Howl card set, @@ -268,14 +268,14 @@ private: ContainerPtr at(EntryCountType index) const; - ContainerPtr const* buckets() const; + Atomic const* buckets() const; public: G1CardSetHowl(EntryCountType card_in_region, G1CardSetConfiguration* config); - ContainerPtr const* container_addr(EntryCountType index) const; + Atomic const* container_addr(EntryCountType index) const; - ContainerPtr* container_addr(EntryCountType index); + Atomic* container_addr(EntryCountType index); bool contains(uint card_idx, G1CardSetConfiguration* config); // Iterates over all ContainerPtrs in this Howl card set, applying a CardOrRangeVisitor diff --git a/src/hotspot/share/gc/g1/g1CardSetContainers.inline.hpp b/src/hotspot/share/gc/g1/g1CardSetContainers.inline.hpp index 1958309f517..3c6fb9d1a02 100644 --- a/src/hotspot/share/gc/g1/g1CardSetContainers.inline.hpp +++ b/src/hotspot/share/gc/g1/g1CardSetContainers.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, 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 @@ -67,7 +67,7 @@ inline G1AddCardResult G1CardSetInlinePtr::add(uint card_idx, uint bits_per_card return Overflow; } ContainerPtr new_value = merge(_value, card_idx, num_cards, bits_per_card); - ContainerPtr old_value = AtomicAccess::cmpxchg(_value_addr, _value, new_value, memory_order_relaxed); + ContainerPtr old_value = _value_addr->compare_exchange(_value, new_value, memory_order_relaxed); if (_value == old_value) { return Added; } @@ -126,7 +126,7 @@ inline bool G1CardSetContainer::try_increment_refcount() { } uintptr_t new_value = old_value + 2; - uintptr_t ref_count = AtomicAccess::cmpxchg(&_ref_count, old_value, new_value); + uintptr_t ref_count = _ref_count.compare_exchange(old_value, new_value); if (ref_count == old_value) { return true; } @@ -137,7 +137,7 @@ inline bool G1CardSetContainer::try_increment_refcount() { inline uintptr_t G1CardSetContainer::decrement_refcount() { uintptr_t old_value = refcount(); assert((old_value & 0x1) != 0 && old_value >= 3, "precondition"); - return AtomicAccess::sub(&_ref_count, 2u); + return _ref_count.sub_then_fetch(2u); } inline G1CardSetArray::G1CardSetArray(uint card_in_region, EntryCountType num_cards) : @@ -149,14 +149,13 @@ inline G1CardSetArray::G1CardSetArray(uint card_in_region, EntryCountType num_ca *entry_addr(0) = checked_cast(card_in_region); } -inline G1CardSetArray::G1CardSetArrayLocker::G1CardSetArrayLocker(EntryCountType volatile* num_entries_addr) : +inline G1CardSetArray::G1CardSetArrayLocker::G1CardSetArrayLocker(Atomic* num_entries_addr) : _num_entries_addr(num_entries_addr) { SpinYield s; - EntryCountType num_entries = AtomicAccess::load(_num_entries_addr) & EntryMask; + EntryCountType num_entries = _num_entries_addr->load_relaxed() & EntryMask; while (true) { - EntryCountType old_value = AtomicAccess::cmpxchg(_num_entries_addr, - num_entries, - (EntryCountType)(num_entries | LockBitMask)); + EntryCountType old_value = _num_entries_addr->compare_exchange(num_entries, + (EntryCountType)(num_entries | LockBitMask)); if (old_value == num_entries) { // Succeeded locking the array. _local_num_entries = num_entries; @@ -174,7 +173,7 @@ inline G1CardSetArray::EntryDataType const* G1CardSetArray::base_addr() const { } inline G1CardSetArray::EntryDataType const* G1CardSetArray::entry_addr(EntryCountType index) const { - assert(index < _num_entries, "precondition"); + assert(index < _num_entries.load_relaxed(), "precondition"); return base_addr() + index; } @@ -189,7 +188,7 @@ inline G1CardSetArray::EntryDataType G1CardSetArray::at(EntryCountType index) co inline G1AddCardResult G1CardSetArray::add(uint card_idx) { assert(card_idx < (1u << (sizeof(EntryDataType) * BitsPerByte)), "Card index %u does not fit allowed card value range.", card_idx); - EntryCountType num_entries = AtomicAccess::load_acquire(&_num_entries) & EntryMask; + EntryCountType num_entries = _num_entries.load_acquire() & EntryMask; EntryCountType idx = 0; for (; idx < num_entries; idx++) { if (at(idx) == card_idx) { @@ -223,7 +222,7 @@ inline G1AddCardResult G1CardSetArray::add(uint card_idx) { } inline bool G1CardSetArray::contains(uint card_idx) { - EntryCountType num_entries = AtomicAccess::load_acquire(&_num_entries) & EntryMask; + EntryCountType num_entries = _num_entries.load_acquire() & EntryMask; for (EntryCountType idx = 0; idx < num_entries; idx++) { if (at(idx) == card_idx) { @@ -235,7 +234,7 @@ inline bool G1CardSetArray::contains(uint card_idx) { template void G1CardSetArray::iterate(CardVisitor& found) { - EntryCountType num_entries = AtomicAccess::load_acquire(&_num_entries) & EntryMask; + EntryCountType num_entries = _num_entries.load_acquire() & EntryMask; for (EntryCountType idx = 0; idx < num_entries; idx++) { found(at(idx)); } @@ -256,11 +255,11 @@ inline G1CardSetBitMap::G1CardSetBitMap(uint card_in_region, uint size_in_bits) inline G1AddCardResult G1CardSetBitMap::add(uint card_idx, size_t threshold, size_t size_in_bits) { BitMapView bm(_bits, size_in_bits); - if (_num_bits_set >= threshold) { + if (_num_bits_set.load_relaxed() >= threshold) { return bm.at(card_idx) ? Found : Overflow; } if (bm.par_set_bit(card_idx)) { - AtomicAccess::inc(&_num_bits_set, memory_order_relaxed); + _num_bits_set.add_then_fetch(1u, memory_order_relaxed); return Added; } return Found; @@ -276,22 +275,22 @@ inline size_t G1CardSetBitMap::header_size_in_bytes() { return offset_of(G1CardSetBitMap, _bits); } -inline G1CardSetHowl::ContainerPtr const* G1CardSetHowl::container_addr(EntryCountType index) const { - assert(index < _num_entries, "precondition"); +inline Atomic const* G1CardSetHowl::container_addr(EntryCountType index) const { + assert(index < _num_entries.load_relaxed(), "precondition"); return buckets() + index; } -inline G1CardSetHowl::ContainerPtr* G1CardSetHowl::container_addr(EntryCountType index) { - return const_cast(const_cast(this)->container_addr(index)); +inline Atomic* G1CardSetHowl::container_addr(EntryCountType index) { + return const_cast*>(const_cast(this)->container_addr(index)); } inline G1CardSetHowl::ContainerPtr G1CardSetHowl::at(EntryCountType index) const { - return *container_addr(index); + return (*container_addr(index)).load_relaxed(); } -inline G1CardSetHowl::ContainerPtr const* G1CardSetHowl::buckets() const { +inline Atomic const* G1CardSetHowl::buckets() const { const void* ptr = reinterpret_cast(this) + header_size_in_bytes(); - return reinterpret_cast(ptr); + return reinterpret_cast const*>(ptr); } inline G1CardSetHowl::G1CardSetHowl(EntryCountType card_in_region, G1CardSetConfiguration* config) : @@ -300,7 +299,7 @@ inline G1CardSetHowl::G1CardSetHowl(EntryCountType card_in_region, G1CardSetConf EntryCountType num_buckets = config->num_buckets_in_howl(); EntryCountType bucket = config->howl_bucket_index(card_in_region); for (uint i = 0; i < num_buckets; ++i) { - *container_addr(i) = G1CardSetInlinePtr(); + container_addr(i)->store_relaxed(G1CardSetInlinePtr()); if (i == bucket) { G1CardSetInlinePtr value(container_addr(i), at(i)); value.add(card_in_region, config->inline_ptr_bits_per_card(), config->max_cards_in_inline_ptr()); @@ -310,8 +309,8 @@ inline G1CardSetHowl::G1CardSetHowl(EntryCountType card_in_region, G1CardSetConf inline bool G1CardSetHowl::contains(uint card_idx, G1CardSetConfiguration* config) { EntryCountType bucket = config->howl_bucket_index(card_idx); - ContainerPtr* array_entry = container_addr(bucket); - ContainerPtr container = AtomicAccess::load_acquire(array_entry); + Atomic* array_entry = container_addr(bucket); + ContainerPtr container = array_entry->load_acquire(); switch (G1CardSet::container_type(container)) { case G1CardSet::ContainerArrayOfCards: { diff --git a/src/hotspot/share/gc/g1/g1CardSetMemory.cpp b/src/hotspot/share/gc/g1/g1CardSetMemory.cpp index d13a6fe2dca..60602ef942b 100644 --- a/src/hotspot/share/gc/g1/g1CardSetMemory.cpp +++ b/src/hotspot/share/gc/g1/g1CardSetMemory.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, 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,7 +26,6 @@ #include "gc/g1/g1CardSetContainers.inline.hpp" #include "gc/g1/g1CardSetMemory.inline.hpp" #include "gc/g1/g1MonotonicArena.inline.hpp" -#include "runtime/atomicAccess.hpp" #include "utilities/ostream.hpp" G1CardSetAllocator::G1CardSetAllocator(const char* name, diff --git a/src/hotspot/share/gc/g1/g1CardTableClaimTable.cpp b/src/hotspot/share/gc/g1/g1CardTableClaimTable.cpp index e0cadbdd907..d8cabaa00a4 100644 --- a/src/hotspot/share/gc/g1/g1CardTableClaimTable.cpp +++ b/src/hotspot/share/gc/g1/g1CardTableClaimTable.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, 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 @@ -44,20 +44,20 @@ G1CardTableClaimTable::~G1CardTableClaimTable() { void G1CardTableClaimTable::initialize(uint max_reserved_regions) { assert(_card_claims == nullptr, "Must not be initialized twice"); - _card_claims = NEW_C_HEAP_ARRAY(uint, max_reserved_regions, mtGC); + _card_claims = NEW_C_HEAP_ARRAY(Atomic, max_reserved_regions, mtGC); _max_reserved_regions = max_reserved_regions; reset_all_to_unclaimed(); } void G1CardTableClaimTable::reset_all_to_unclaimed() { for (uint i = 0; i < _max_reserved_regions; i++) { - _card_claims[i] = 0; + _card_claims[i].store_relaxed(0); } } void G1CardTableClaimTable::reset_all_to_claimed() { for (uint i = 0; i < _max_reserved_regions; i++) { - _card_claims[i] = (uint)G1HeapRegion::CardsPerRegion; + _card_claims[i].store_relaxed((uint)G1HeapRegion::CardsPerRegion); } } diff --git a/src/hotspot/share/gc/g1/g1CardTableClaimTable.hpp b/src/hotspot/share/gc/g1/g1CardTableClaimTable.hpp index 4f524b83f97..822ef45c722 100644 --- a/src/hotspot/share/gc/g1/g1CardTableClaimTable.hpp +++ b/src/hotspot/share/gc/g1/g1CardTableClaimTable.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, 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,6 +27,7 @@ #include "gc/g1/g1CardTable.hpp" #include "memory/allocation.hpp" +#include "runtime/atomic.hpp" class G1HeapRegionClosure; @@ -45,7 +46,7 @@ class G1CardTableClaimTable : public CHeapObj { // Card table iteration claim values for every heap region, from 0 (completely unclaimed) // to (>=) G1HeapRegion::CardsPerRegion (completely claimed). - uint volatile* _card_claims; + Atomic* _card_claims; uint _cards_per_chunk; // For conversion between card index and chunk index. diff --git a/src/hotspot/share/gc/g1/g1CardTableClaimTable.inline.hpp b/src/hotspot/share/gc/g1/g1CardTableClaimTable.inline.hpp index d682f0d17ae..35b2484982c 100644 --- a/src/hotspot/share/gc/g1/g1CardTableClaimTable.inline.hpp +++ b/src/hotspot/share/gc/g1/g1CardTableClaimTable.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, 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 @@ -29,26 +29,25 @@ #include "gc/g1/g1CollectedHeap.inline.hpp" #include "gc/g1/g1HeapRegion.inline.hpp" -#include "runtime/atomicAccess.hpp" bool G1CardTableClaimTable::has_unclaimed_cards(uint region) { assert(region < _max_reserved_regions, "Tried to access invalid region %u", region); - return AtomicAccess::load(&_card_claims[region]) < G1HeapRegion::CardsPerRegion; + return _card_claims[region].load_relaxed() < G1HeapRegion::CardsPerRegion; } void G1CardTableClaimTable::reset_to_unclaimed(uint region) { assert(region < _max_reserved_regions, "Tried to access invalid region %u", region); - AtomicAccess::store(&_card_claims[region], 0u); + _card_claims[region].store_relaxed(0u); } uint G1CardTableClaimTable::claim_cards(uint region, uint increment) { assert(region < _max_reserved_regions, "Tried to access invalid region %u", region); - return AtomicAccess::fetch_then_add(&_card_claims[region], increment, memory_order_relaxed); + return _card_claims[region].fetch_then_add(increment, memory_order_relaxed); } uint G1CardTableClaimTable::claim_chunk(uint region) { assert(region < _max_reserved_regions, "Tried to access invalid region %u", region); - return AtomicAccess::fetch_then_add(&_card_claims[region], cards_per_chunk(), memory_order_relaxed); + return _card_claims[region].fetch_then_add(cards_per_chunk(), memory_order_relaxed); } uint G1CardTableClaimTable::claim_all_cards(uint region) { diff --git a/src/hotspot/share/gc/g1/g1CodeRootSet.cpp b/src/hotspot/share/gc/g1/g1CodeRootSet.cpp index 60ad3a2af32..ca4487876b9 100644 --- a/src/hotspot/share/gc/g1/g1CodeRootSet.cpp +++ b/src/hotspot/share/gc/g1/g1CodeRootSet.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2026, 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 @@ #include "gc/g1/g1HeapRegion.hpp" #include "memory/allocation.hpp" #include "oops/oop.inline.hpp" -#include "runtime/atomicAccess.hpp" +#include "runtime/atomic.hpp" #include "utilities/concurrentHashTable.inline.hpp" #include "utilities/concurrentHashTableTasks.inline.hpp" @@ -60,7 +60,7 @@ class G1CodeRootSetHashTable : public CHeapObj { HashTable _table; HashTableScanTask _table_scanner; - size_t volatile _num_entries; + Atomic _num_entries; bool is_empty() const { return number_of_entries() == 0; } @@ -120,7 +120,7 @@ public: bool grow_hint = false; bool inserted = _table.insert(Thread::current(), lookup, method, &grow_hint); if (inserted) { - AtomicAccess::inc(&_num_entries); + _num_entries.add_then_fetch(1u); } if (grow_hint) { _table.grow(Thread::current()); @@ -131,7 +131,7 @@ public: HashTableLookUp lookup(method); bool removed = _table.remove(Thread::current(), lookup); if (removed) { - AtomicAccess::dec(&_num_entries); + _num_entries.sub_then_fetch(1u); } return removed; } @@ -182,7 +182,7 @@ public: guarantee(succeeded, "unable to clean table"); if (num_deleted != 0) { - size_t current_size = AtomicAccess::sub(&_num_entries, num_deleted); + size_t current_size = _num_entries.sub_then_fetch(num_deleted); shrink_to_match(current_size); } } @@ -226,7 +226,7 @@ public: size_t mem_size() { return sizeof(*this) + _table.get_mem_size(Thread::current()); } - size_t number_of_entries() const { return AtomicAccess::load(&_num_entries); } + size_t number_of_entries() const { return _num_entries.load_relaxed(); } }; uintx G1CodeRootSetHashTable::HashTableLookUp::get_hash() const { diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp index 061241c24e2..8f83a653885 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, 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 @@ -103,7 +103,6 @@ #include "oops/access.inline.hpp" #include "oops/compressedOops.inline.hpp" #include "oops/oop.inline.hpp" -#include "runtime/atomicAccess.hpp" #include "runtime/cpuTimeCounters.hpp" #include "runtime/handles.inline.hpp" #include "runtime/init.hpp" @@ -526,7 +525,7 @@ void G1CollectedHeap::iterate_regions_in_range(MemRegion range, const Func& func } } -HeapWord* G1CollectedHeap::alloc_archive_region(size_t word_size, HeapWord* preferred_addr) { +HeapWord* G1CollectedHeap::alloc_archive_region(size_t word_size) { assert(!is_init_completed(), "Expect to be called at JVM init time"); MutexLocker x(Heap_lock); @@ -686,8 +685,9 @@ HeapWord* G1CollectedHeap::attempt_allocation_humongous(size_t word_size) { // the check before we do the actual allocation. The reason for doing it // before the allocation is that we avoid having to keep track of the newly // allocated memory while we do a GC. - if (policy()->need_to_start_conc_mark("concurrent humongous allocation", - word_size)) { + // Only try that if we can actually perform a GC. + if (is_init_completed() && + policy()->need_to_start_conc_mark("concurrent humongous allocation", word_size)) { try_collect(word_size, GCCause::_g1_humongous_allocation, collection_counters(this)); } diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.hpp b/src/hotspot/share/gc/g1/g1CollectedHeap.hpp index aff7166d391..1f900c76851 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.hpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, 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 @@ -54,6 +54,7 @@ #include "memory/allocation.hpp" #include "memory/iterator.hpp" #include "memory/memRegion.hpp" +#include "runtime/atomic.hpp" #include "runtime/mutexLocker.hpp" #include "runtime/threadSMR.hpp" #include "utilities/bitMap.hpp" @@ -124,7 +125,7 @@ class G1JavaThreadsListClaimer : public StackObj { ThreadsListHandle _list; uint _claim_step; - volatile uint _cur_claim; + Atomic _cur_claim; // Attempts to claim _claim_step JavaThreads, returning an array of claimed // JavaThread* with count elements. Returns null (and a zero count) if there @@ -735,12 +736,10 @@ public: void iterate_regions_in_range(MemRegion range, const Func& func); // Commit the required number of G1 region(s) according to the size requested - // and mark them as 'old' region(s). Preferred address is treated as a hint for - // the location of the archive space in the heap. The returned address may or may - // not be same as the preferred address. + // and mark them as 'old' region(s). // This API is only used for allocating heap space for the archived heap objects // in the CDS archive. - HeapWord* alloc_archive_region(size_t word_size, HeapWord* preferred_addr); + HeapWord* alloc_archive_region(size_t word_size); // Populate the G1BlockOffsetTable for archived regions with the given // memory range. @@ -1267,7 +1266,6 @@ public: bool is_marked(oop obj) const; - inline static bool is_obj_filler(const oop obj); // Determine if an object is dead, given the object and also // the region to which the object belongs. inline bool is_obj_dead(const oop obj, const G1HeapRegion* hr) const; diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.inline.hpp b/src/hotspot/share/gc/g1/g1CollectedHeap.inline.hpp index abd61e72d57..8782b65b6f9 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.inline.hpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, 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,7 @@ #include "gc/g1/g1CollectorState.hpp" #include "gc/g1/g1ConcurrentMark.inline.hpp" #include "gc/g1/g1EvacFailureRegions.hpp" +#include "gc/g1/g1EvacStats.inline.hpp" #include "gc/g1/g1HeapRegion.inline.hpp" #include "gc/g1/g1HeapRegionManager.inline.hpp" #include "gc/g1/g1HeapRegionRemSet.hpp" @@ -38,10 +39,10 @@ #include "gc/g1/g1Policy.hpp" #include "gc/g1/g1RegionPinCache.inline.hpp" #include "gc/g1/g1RemSet.hpp" +#include "gc/shared/collectedHeap.inline.hpp" #include "gc/shared/markBitMap.inline.hpp" #include "gc/shared/taskqueue.inline.hpp" #include "oops/stackChunkOop.hpp" -#include "runtime/atomicAccess.hpp" #include "runtime/threadSMR.inline.hpp" #include "utilities/bitMap.inline.hpp" @@ -53,10 +54,10 @@ inline bool G1STWIsAliveClosure::do_object_b(oop p) { inline JavaThread* const* G1JavaThreadsListClaimer::claim(uint& count) { count = 0; - if (AtomicAccess::load(&_cur_claim) >= _list.length()) { + if (_cur_claim.load_relaxed() >= _list.length()) { return nullptr; } - uint claim = AtomicAccess::fetch_then_add(&_cur_claim, _claim_step); + uint claim = _cur_claim.fetch_then_add(_claim_step); if (claim >= _list.length()) { return nullptr; } @@ -230,16 +231,11 @@ inline bool G1CollectedHeap::requires_barriers(stackChunkOop obj) const { return !heap_region_containing(obj)->is_young(); // is_in_young does an unnecessary null check } -inline bool G1CollectedHeap::is_obj_filler(const oop obj) { - Klass* k = obj->klass_without_asserts(); - return k == Universe::fillerArrayKlass() || k == vmClasses::FillerObject_klass(); -} - inline bool G1CollectedHeap::is_obj_dead(const oop obj, const G1HeapRegion* hr) const { assert(!hr->is_free(), "looking up obj " PTR_FORMAT " in Free region %u", p2i(obj), hr->hrm_index()); if (hr->is_in_parsable_area(obj)) { // This object is in the parsable part of the heap, live unless scrubbed. - return is_obj_filler(obj); + return is_filler_object(obj); } else { // From Remark until a region has been concurrently scrubbed, parts of the // region is not guaranteed to be parsable. Use the bitmap for liveness. diff --git a/src/hotspot/share/gc/g1/g1CollectionSetChooser.cpp b/src/hotspot/share/gc/g1/g1CollectionSetChooser.cpp index 954ca40a77f..e7bab32129e 100644 --- a/src/hotspot/share/gc/g1/g1CollectionSetChooser.cpp +++ b/src/hotspot/share/gc/g1/g1CollectionSetChooser.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, 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 @@ #include "gc/g1/g1CollectionSetChooser.hpp" #include "gc/g1/g1HeapRegionRemSet.inline.hpp" #include "gc/shared/space.hpp" -#include "runtime/atomicAccess.hpp" +#include "runtime/atomic.hpp" #include "utilities/quickSort.hpp" // Determine collection set candidates (from marking): For all regions determine @@ -50,7 +50,7 @@ class G1BuildCandidateRegionsTask : public WorkerTask { G1HeapRegion** _data; - uint volatile _cur_claim_idx; + Atomic _cur_claim_idx; static int compare_region_gc_efficiency(G1HeapRegion** rr1, G1HeapRegion** rr2) { G1HeapRegion* r1 = *rr1; @@ -105,7 +105,7 @@ class G1BuildCandidateRegionsTask : public WorkerTask { // Claim a new chunk, returning its bounds [from, to[. void claim_chunk(uint& from, uint& to) { - uint result = AtomicAccess::add(&_cur_claim_idx, _chunk_size); + uint result = _cur_claim_idx.add_then_fetch(_chunk_size); assert(_max_size > result - 1, "Array too small, is %u should be %u with chunk size %u.", _max_size, result, _chunk_size); @@ -121,14 +121,15 @@ class G1BuildCandidateRegionsTask : public WorkerTask { } void sort_by_gc_efficiency() { - if (_cur_claim_idx == 0) { + uint length = _cur_claim_idx.load_relaxed(); + if (length == 0) { return; } - for (uint i = _cur_claim_idx; i < _max_size; i++) { + for (uint i = length; i < _max_size; i++) { assert(_data[i] == nullptr, "must be"); } - qsort(_data, _cur_claim_idx, sizeof(_data[0]), (_sort_Fn)compare_region_gc_efficiency); - for (uint i = _cur_claim_idx; i < _max_size; i++) { + qsort(_data, length, sizeof(_data[0]), (_sort_Fn)compare_region_gc_efficiency); + for (uint i = length; i < _max_size; i++) { assert(_data[i] == nullptr, "must be"); } } @@ -202,13 +203,13 @@ class G1BuildCandidateRegionsTask : public WorkerTask { G1CollectedHeap* _g1h; G1HeapRegionClaimer _hrclaimer; - uint volatile _num_regions_added; + Atomic _num_regions_added; G1BuildCandidateArray _result; void update_totals(uint num_regions) { if (num_regions > 0) { - AtomicAccess::add(&_num_regions_added, num_regions); + _num_regions_added.add_then_fetch(num_regions); } } @@ -220,7 +221,7 @@ class G1BuildCandidateRegionsTask : public WorkerTask { void prune(G1HeapRegion** data) { G1Policy* p = G1CollectedHeap::heap()->policy(); - uint num_candidates = AtomicAccess::load(&_num_regions_added); + uint num_candidates = _num_regions_added.load_relaxed(); uint min_old_cset_length = p->calc_min_old_cset_length(num_candidates); uint num_pruned = 0; @@ -253,7 +254,7 @@ class G1BuildCandidateRegionsTask : public WorkerTask { wasted_bytes, allowed_waste); - AtomicAccess::sub(&_num_regions_added, num_pruned, memory_order_relaxed); + _num_regions_added.sub_then_fetch(num_pruned, memory_order_relaxed); } public: @@ -274,7 +275,7 @@ public: _result.sort_by_gc_efficiency(); prune(_result.array()); candidates->set_candidates_from_marking(_result.array(), - _num_regions_added); + _num_regions_added.load_relaxed()); } }; diff --git a/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp b/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp index 456d543fa10..2bbfb5032b3 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, 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 @@ -51,6 +51,9 @@ #include "gc/shared/gcTimer.hpp" #include "gc/shared/gcTraceTime.inline.hpp" #include "gc/shared/gcVMOperations.hpp" +#include "gc/shared/partialArraySplitter.inline.hpp" +#include "gc/shared/partialArrayState.hpp" +#include "gc/shared/partialArrayTaskStats.hpp" #include "gc/shared/referencePolicy.hpp" #include "gc/shared/suspendibleThreadSet.hpp" #include "gc/shared/taskqueue.inline.hpp" @@ -67,7 +70,6 @@ #include "nmt/memTracker.hpp" #include "oops/access.inline.hpp" #include "oops/oop.inline.hpp" -#include "runtime/atomicAccess.hpp" #include "runtime/globals_extension.hpp" #include "runtime/handles.inline.hpp" #include "runtime/java.hpp" @@ -76,6 +78,7 @@ #include "runtime/prefetch.inline.hpp" #include "runtime/threads.hpp" #include "utilities/align.hpp" +#include "utilities/checkedCast.hpp" #include "utilities/formatBuffer.hpp" #include "utilities/growableArray.hpp" #include "utilities/powerOfTwo.hpp" @@ -99,7 +102,7 @@ bool G1CMBitMapClosure::do_addr(HeapWord* const addr) { // We move that task's local finger along. _task->move_finger_to(addr); - _task->scan_task_entry(G1TaskQueueEntry::from_oop(cast_to_oop(addr))); + _task->process_entry(G1TaskQueueEntry(cast_to_oop(addr)), false /* stolen */); // we only partially drain the local queue and global stack _task->drain_local_queue(true); _task->drain_global_stack(true); @@ -148,25 +151,25 @@ bool G1CMMarkStack::initialize() { } G1CMMarkStack::TaskQueueEntryChunk* G1CMMarkStack::ChunkAllocator::allocate_new_chunk() { - if (_size >= _max_capacity) { + if (_size.load_relaxed() >= _max_capacity) { return nullptr; } - size_t cur_idx = AtomicAccess::fetch_then_add(&_size, 1u); + size_t cur_idx = _size.fetch_then_add(1u); if (cur_idx >= _max_capacity) { return nullptr; } size_t bucket = get_bucket(cur_idx); - if (AtomicAccess::load_acquire(&_buckets[bucket]) == nullptr) { + if (_buckets[bucket].load_acquire() == nullptr) { if (!_should_grow) { // Prefer to restart the CM. return nullptr; } MutexLocker x(G1MarkStackChunkList_lock, Mutex::_no_safepoint_check_flag); - if (AtomicAccess::load_acquire(&_buckets[bucket]) == nullptr) { + if (_buckets[bucket].load_acquire() == nullptr) { size_t desired_capacity = bucket_size(bucket) * 2; if (!try_expand_to(desired_capacity)) { return nullptr; @@ -175,7 +178,7 @@ G1CMMarkStack::TaskQueueEntryChunk* G1CMMarkStack::ChunkAllocator::allocate_new_ } size_t bucket_idx = get_bucket_index(cur_idx); - TaskQueueEntryChunk* result = ::new (&_buckets[bucket][bucket_idx]) TaskQueueEntryChunk; + TaskQueueEntryChunk* result = ::new (&_buckets[bucket].load_relaxed()[bucket_idx]) TaskQueueEntryChunk; result->next = nullptr; return result; } @@ -197,10 +200,10 @@ bool G1CMMarkStack::ChunkAllocator::initialize(size_t initial_capacity, size_t m _max_capacity = max_capacity; _num_buckets = get_bucket(_max_capacity) + 1; - _buckets = NEW_C_HEAP_ARRAY(TaskQueueEntryChunk*, _num_buckets, mtGC); + _buckets = NEW_C_HEAP_ARRAY(Atomic, _num_buckets, mtGC); for (size_t i = 0; i < _num_buckets; i++) { - _buckets[i] = nullptr; + _buckets[i].store_relaxed(nullptr); } size_t new_capacity = bucket_size(0); @@ -240,9 +243,9 @@ G1CMMarkStack::ChunkAllocator::~ChunkAllocator() { } for (size_t i = 0; i < _num_buckets; i++) { - if (_buckets[i] != nullptr) { - MmapArrayAllocator::free(_buckets[i], bucket_size(i)); - _buckets[i] = nullptr; + if (_buckets[i].load_relaxed() != nullptr) { + MmapArrayAllocator::free(_buckets[i].load_relaxed(), bucket_size(i)); + _buckets[i].store_relaxed(nullptr); } } @@ -259,7 +262,7 @@ bool G1CMMarkStack::ChunkAllocator::reserve(size_t new_capacity) { // and the new capacity (new_capacity). This step ensures that there are no gaps in the // array and that the capacity accurately reflects the reserved memory. for (; i <= highest_bucket; i++) { - if (AtomicAccess::load_acquire(&_buckets[i]) != nullptr) { + if (_buckets[i].load_acquire() != nullptr) { continue; // Skip over already allocated buckets. } @@ -279,7 +282,7 @@ bool G1CMMarkStack::ChunkAllocator::reserve(size_t new_capacity) { return false; } _capacity += bucket_capacity; - AtomicAccess::release_store(&_buckets[i], bucket_base); + _buckets[i].release_store(bucket_base); } return true; } @@ -288,9 +291,9 @@ void G1CMMarkStack::expand() { _chunk_allocator.try_expand(); } -void G1CMMarkStack::add_chunk_to_list(TaskQueueEntryChunk* volatile* list, TaskQueueEntryChunk* elem) { - elem->next = *list; - *list = elem; +void G1CMMarkStack::add_chunk_to_list(Atomic* list, TaskQueueEntryChunk* elem) { + elem->next = list->load_relaxed(); + list->store_relaxed(elem); } void G1CMMarkStack::add_chunk_to_chunk_list(TaskQueueEntryChunk* elem) { @@ -304,10 +307,10 @@ void G1CMMarkStack::add_chunk_to_free_list(TaskQueueEntryChunk* elem) { add_chunk_to_list(&_free_list, elem); } -G1CMMarkStack::TaskQueueEntryChunk* G1CMMarkStack::remove_chunk_from_list(TaskQueueEntryChunk* volatile* list) { - TaskQueueEntryChunk* result = *list; +G1CMMarkStack::TaskQueueEntryChunk* G1CMMarkStack::remove_chunk_from_list(Atomic* list) { + TaskQueueEntryChunk* result = list->load_relaxed(); if (result != nullptr) { - *list = (*list)->next; + list->store_relaxed(list->load_relaxed()->next); } return result; } @@ -361,8 +364,8 @@ bool G1CMMarkStack::par_pop_chunk(G1TaskQueueEntry* ptr_arr) { void G1CMMarkStack::set_empty() { _chunks_in_chunk_list = 0; - _chunk_list = nullptr; - _free_list = nullptr; + _chunk_list.store_relaxed(nullptr); + _free_list.store_relaxed(nullptr); _chunk_allocator.reset(); } @@ -490,6 +493,7 @@ G1ConcurrentMark::G1ConcurrentMark(G1CollectedHeap* g1h, _task_queues(new G1CMTaskQueueSet(_max_num_tasks)), _terminator(_max_num_tasks, _task_queues), + _partial_array_state_manager(new PartialArrayStateManager(_max_num_tasks)), _first_overflow_barrier_sync(), _second_overflow_barrier_sync(), @@ -556,6 +560,10 @@ G1ConcurrentMark::G1ConcurrentMark(G1CollectedHeap* g1h, reset_at_marking_complete(); } +PartialArrayStateManager* G1ConcurrentMark::partial_array_state_manager() const { + return _partial_array_state_manager; +} + void G1ConcurrentMark::reset() { _has_aborted = false; @@ -650,7 +658,26 @@ void G1ConcurrentMark::set_concurrency_and_phase(uint active_tasks, bool concurr } } +#if TASKQUEUE_STATS +void G1ConcurrentMark::print_and_reset_taskqueue_stats() { + + _task_queues->print_and_reset_taskqueue_stats("G1ConcurrentMark Oop Queue"); + + auto get_pa_stats = [&](uint i) { + return _tasks[i]->partial_array_task_stats(); + }; + + PartialArrayTaskStats::log_set(_max_num_tasks, get_pa_stats, + "G1ConcurrentMark Partial Array Task Stats"); + + for (uint i = 0; i < _max_num_tasks; ++i) { + get_pa_stats(i)->reset(); + } +} +#endif + void G1ConcurrentMark::reset_at_marking_complete() { + TASKQUEUE_STATS_ONLY(print_and_reset_taskqueue_stats()); // We set the global marking state to some default values when we're // not doing marking. reset_marking_for_restart(); @@ -804,11 +831,25 @@ void G1ConcurrentMark::cleanup_for_next_mark() { clear_bitmap(_concurrent_workers, true); + reset_partial_array_state_manager(); + // Repeat the asserts from above. guarantee(cm_thread()->in_progress(), "invariant"); guarantee(!_g1h->collector_state()->mark_or_rebuild_in_progress(), "invariant"); } +void G1ConcurrentMark::reset_partial_array_state_manager() { + for (uint i = 0; i < _max_num_tasks; ++i) { + _tasks[i]->unregister_partial_array_splitter(); + } + + partial_array_state_manager()->reset(); + + for (uint i = 0; i < _max_num_tasks; ++i) { + _tasks[i]->register_partial_array_splitter(); + } +} + void G1ConcurrentMark::clear_bitmap(WorkerThreads* workers) { assert_at_safepoint_on_vm_thread(); // To avoid fragmentation the full collection requesting to clear the bitmap @@ -1789,17 +1830,18 @@ public: { } void operator()(G1TaskQueueEntry task_entry) const { - if (task_entry.is_array_slice()) { - guarantee(_g1h->is_in_reserved(task_entry.slice()), "Slice " PTR_FORMAT " must be in heap.", p2i(task_entry.slice())); + if (task_entry.is_partial_array_state()) { + oop obj = task_entry.to_partial_array_state()->source(); + guarantee(_g1h->is_in_reserved(obj), "Partial Array " PTR_FORMAT " must be in heap.", p2i(obj)); return; } - guarantee(oopDesc::is_oop(task_entry.obj()), + guarantee(oopDesc::is_oop(task_entry.to_oop()), "Non-oop " PTR_FORMAT ", phase: %s, info: %d", - p2i(task_entry.obj()), _phase, _info); - G1HeapRegion* r = _g1h->heap_region_containing(task_entry.obj()); + p2i(task_entry.to_oop()), _phase, _info); + G1HeapRegion* r = _g1h->heap_region_containing(task_entry.to_oop()); guarantee(!(r->in_collection_set() || r->has_index_in_opt_cset()), "obj " PTR_FORMAT " from %s (%d) in region %u in (optional) collection set", - p2i(task_entry.obj()), _phase, _info, r->hrm_index()); + p2i(task_entry.to_oop()), _phase, _info, r->hrm_index()); } }; @@ -2055,6 +2097,17 @@ void G1CMTask::reset(G1CMBitMap* mark_bitmap) { _mark_stats_cache.reset(); } +void G1CMTask::register_partial_array_splitter() { + + ::new (&_partial_array_splitter) PartialArraySplitter(_cm->partial_array_state_manager(), + _cm->max_num_tasks(), + ObjArrayMarkingStride); +} + +void G1CMTask::unregister_partial_array_splitter() { + _partial_array_splitter.~PartialArraySplitter(); +} + bool G1CMTask::should_exit_termination() { if (!regular_clock_call()) { return true; @@ -2185,7 +2238,7 @@ bool G1CMTask::get_entries_from_global_stack() { if (task_entry.is_null()) { break; } - assert(task_entry.is_array_slice() || oopDesc::is_oop(task_entry.obj()), "Element " PTR_FORMAT " must be an array slice or oop", p2i(task_entry.obj())); + assert(task_entry.is_partial_array_state() || oopDesc::is_oop(task_entry.to_oop()), "Element " PTR_FORMAT " must be an array slice or oop", p2i(task_entry.to_oop())); bool success = _task_queue->push(task_entry); // We only call this when the local queue is empty or under a // given target limit. So, we do not expect this push to fail. @@ -2216,7 +2269,7 @@ void G1CMTask::drain_local_queue(bool partially) { G1TaskQueueEntry entry; bool ret = _task_queue->pop_local(entry); while (ret) { - scan_task_entry(entry); + process_entry(entry, false /* stolen */); if (_task_queue->size() <= target_size || has_aborted()) { ret = false; } else { @@ -2226,6 +2279,37 @@ void G1CMTask::drain_local_queue(bool partially) { } } +size_t G1CMTask::start_partial_array_processing(oop obj) { + assert(should_be_sliced(obj), "Must be an array object %d and large %zu", obj->is_objArray(), obj->size()); + + objArrayOop obj_array = objArrayOop(obj); + size_t array_length = obj_array->length(); + + size_t initial_chunk_size = _partial_array_splitter.start(_task_queue, obj_array, nullptr, array_length); + + // Mark objArray klass metadata + if (_cm_oop_closure->do_metadata()) { + _cm_oop_closure->do_klass(obj_array->klass()); + } + + process_array_chunk(obj_array, 0, initial_chunk_size); + + // Include object header size + return objArrayOopDesc::object_size(checked_cast(initial_chunk_size)); +} + +size_t G1CMTask::process_partial_array(const G1TaskQueueEntry& task, bool stolen) { + PartialArrayState* state = task.to_partial_array_state(); + // Access state before release by claim(). + objArrayOop obj = objArrayOop(state->source()); + + PartialArraySplitter::Claim claim = + _partial_array_splitter.claim(state, _task_queue, stolen); + + process_array_chunk(obj, claim._start, claim._end); + return heap_word_size((claim._end - claim._start) * heapOopSize); +} + void G1CMTask::drain_global_stack(bool partially) { if (has_aborted()) { return; @@ -2430,7 +2514,7 @@ void G1CMTask::attempt_stealing() { while (!has_aborted()) { G1TaskQueueEntry entry; if (_cm->try_stealing(_worker_id, entry)) { - scan_task_entry(entry); + process_entry(entry, true /* stolen */); // And since we're towards the end, let's totally drain the // local queue and global stack. @@ -2759,12 +2843,12 @@ G1CMTask::G1CMTask(uint worker_id, G1ConcurrentMark* cm, G1CMTaskQueue* task_queue, G1RegionMarkStats* mark_stats) : - _objArray_processor(this), _worker_id(worker_id), _g1h(G1CollectedHeap::heap()), _cm(cm), _mark_bitmap(nullptr), _task_queue(task_queue), + _partial_array_splitter(_cm->partial_array_state_manager(), _cm->max_num_tasks(), ObjArrayMarkingStride), _mark_stats_cache(mark_stats, G1RegionMarkStatsCache::RegionMarkStatsCacheSize), _calls(0), _time_target_ms(0.0), diff --git a/src/hotspot/share/gc/g1/g1ConcurrentMark.hpp b/src/hotspot/share/gc/g1/g1ConcurrentMark.hpp index 752082ce629..836d7793f81 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentMark.hpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentMark.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, 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,20 @@ #define SHARE_GC_G1_G1CONCURRENTMARK_HPP #include "gc/g1/g1ConcurrentMarkBitMap.hpp" -#include "gc/g1/g1ConcurrentMarkObjArrayProcessor.hpp" #include "gc/g1/g1HeapRegionSet.hpp" #include "gc/g1/g1HeapVerifier.hpp" #include "gc/g1/g1RegionMarkStatsCache.hpp" #include "gc/shared/gcCause.hpp" +#include "gc/shared/partialArraySplitter.hpp" +#include "gc/shared/partialArrayState.hpp" +#include "gc/shared/partialArrayTaskStats.hpp" #include "gc/shared/taskqueue.hpp" #include "gc/shared/taskTerminator.hpp" #include "gc/shared/verifyOption.hpp" #include "gc/shared/workerThread.hpp" #include "gc/shared/workerUtils.hpp" #include "memory/allocation.hpp" +#include "runtime/atomic.hpp" #include "utilities/compilerWarnings.hpp" #include "utilities/numberSeq.hpp" @@ -53,41 +56,7 @@ class G1RegionToSpaceMapper; class G1SurvivorRegions; class ThreadClosure; -// This is a container class for either an oop or a continuation address for -// mark stack entries. Both are pushed onto the mark stack. -class G1TaskQueueEntry { -private: - void* _holder; - - static const uintptr_t ArraySliceBit = 1; - - G1TaskQueueEntry(oop obj) : _holder(obj) { - assert(_holder != nullptr, "Not allowed to set null task queue element"); - } - G1TaskQueueEntry(HeapWord* addr) : _holder((void*)((uintptr_t)addr | ArraySliceBit)) { } -public: - - G1TaskQueueEntry() : _holder(nullptr) { } - // Trivially copyable, for use in GenericTaskQueue. - - static G1TaskQueueEntry from_slice(HeapWord* what) { return G1TaskQueueEntry(what); } - static G1TaskQueueEntry from_oop(oop obj) { return G1TaskQueueEntry(obj); } - - oop obj() const { - assert(!is_array_slice(), "Trying to read array slice " PTR_FORMAT " as oop", p2i(_holder)); - return cast_to_oop(_holder); - } - - HeapWord* slice() const { - assert(is_array_slice(), "Trying to read oop " PTR_FORMAT " as array slice", p2i(_holder)); - return (HeapWord*)((uintptr_t)_holder & ~ArraySliceBit); - } - - bool is_oop() const { return !is_array_slice(); } - bool is_array_slice() const { return ((uintptr_t)_holder & ArraySliceBit) != 0; } - bool is_null() const { return _holder == nullptr; } -}; - +typedef ScannerTask G1TaskQueueEntry; typedef GenericTaskQueue G1CMTaskQueue; typedef GenericTaskQueueSet G1CMTaskQueueSet; @@ -172,9 +141,9 @@ private: size_t _capacity; size_t _num_buckets; bool _should_grow; - TaskQueueEntryChunk* volatile* _buckets; + Atomic* _buckets; char _pad0[DEFAULT_PADDING_SIZE]; - volatile size_t _size; + Atomic _size; char _pad4[DEFAULT_PADDING_SIZE - sizeof(size_t)]; size_t bucket_size(size_t bucket) { @@ -212,7 +181,7 @@ private: bool initialize(size_t initial_capacity, size_t max_capacity); void reset() { - _size = 0; + _size.store_relaxed(0); _should_grow = false; } @@ -241,17 +210,17 @@ private: ChunkAllocator _chunk_allocator; char _pad0[DEFAULT_PADDING_SIZE]; - TaskQueueEntryChunk* volatile _free_list; // Linked list of free chunks that can be allocated by users. + Atomic _free_list; // Linked list of free chunks that can be allocated by users. char _pad1[DEFAULT_PADDING_SIZE - sizeof(TaskQueueEntryChunk*)]; - TaskQueueEntryChunk* volatile _chunk_list; // List of chunks currently containing data. + Atomic _chunk_list; // List of chunks currently containing data. volatile size_t _chunks_in_chunk_list; char _pad2[DEFAULT_PADDING_SIZE - sizeof(TaskQueueEntryChunk*) - sizeof(size_t)]; // Atomically add the given chunk to the list. - void add_chunk_to_list(TaskQueueEntryChunk* volatile* list, TaskQueueEntryChunk* elem); + void add_chunk_to_list(Atomic* list, TaskQueueEntryChunk* elem); // Atomically remove and return a chunk from the given list. Returns null if the // list is empty. - TaskQueueEntryChunk* remove_chunk_from_list(TaskQueueEntryChunk* volatile* list); + TaskQueueEntryChunk* remove_chunk_from_list(Atomic* list); void add_chunk_to_chunk_list(TaskQueueEntryChunk* elem); void add_chunk_to_free_list(TaskQueueEntryChunk* elem); @@ -283,7 +252,7 @@ private: // Return whether the chunk list is empty. Racy due to unsynchronized access to // _chunk_list. - bool is_empty() const { return _chunk_list == nullptr; } + bool is_empty() const { return _chunk_list.load_relaxed() == nullptr; } size_t capacity() const { return _chunk_allocator.capacity(); } @@ -411,6 +380,8 @@ class G1ConcurrentMark : public CHeapObj { G1CMTaskQueueSet* _task_queues; // Task queue set TaskTerminator _terminator; // For termination + PartialArrayStateManager* _partial_array_state_manager; + // Two sync barriers that are used to synchronize tasks when an // overflow occurs. The algorithm is the following. All tasks enter // the first one to ensure that they have all stopped manipulating @@ -488,6 +459,8 @@ class G1ConcurrentMark : public CHeapObj { // Prints all gathered CM-related statistics void print_stats(); + void print_and_reset_taskqueue_stats(); + HeapWord* finger() { return _finger; } bool concurrent() { return _concurrent; } uint active_tasks() { return _num_active_tasks; } @@ -556,14 +529,14 @@ public: // mark_in_bitmap call. Updates various statistics data. void add_to_liveness(uint worker_id, oop const obj, size_t size); // Did the last marking find a live object between bottom and TAMS? - bool contains_live_object(uint region) const { return _region_mark_stats[region]._live_words != 0; } + bool contains_live_object(uint region) const { return _region_mark_stats[region].live_words() != 0; } // Live bytes in the given region as determined by concurrent marking, i.e. the amount of // live bytes between bottom and TAMS. - size_t live_bytes(uint region) const { return _region_mark_stats[region]._live_words * HeapWordSize; } + size_t live_bytes(uint region) const { return _region_mark_stats[region].live_words() * HeapWordSize; } // Set live bytes for concurrent marking. - void set_live_bytes(uint region, size_t live_bytes) { _region_mark_stats[region]._live_words = live_bytes / HeapWordSize; } + void set_live_bytes(uint region, size_t live_bytes) { _region_mark_stats[region]._live_words.store_relaxed(live_bytes / HeapWordSize); } // Approximate number of incoming references found during marking. - size_t incoming_refs(uint region) const { return _region_mark_stats[region]._incoming_refs; } + size_t incoming_refs(uint region) const { return _region_mark_stats[region].incoming_refs(); } // Update the TAMS for the given region to the current top. inline void update_top_at_mark_start(G1HeapRegion* r); @@ -582,6 +555,8 @@ public: uint worker_id_offset() const { return _worker_id_offset; } + uint max_num_tasks() const {return _max_num_tasks; } + // Clear statistics gathered during the concurrent cycle for the given region after // it has been reclaimed. void clear_statistics(G1HeapRegion* r); @@ -631,6 +606,8 @@ public: // Calculates the number of concurrent GC threads to be used in the marking phase. uint calc_active_marking_workers(); + PartialArrayStateManager* partial_array_state_manager() const; + // Resets the global marking data structures, as well as the // task local ones; should be called during concurrent start. void reset(); @@ -642,6 +619,10 @@ public: // to be called concurrently to the mutator. It will yield to safepoint requests. void cleanup_for_next_mark(); + // Recycle the memory that has been requested by allocators associated with + // this manager. + void reset_partial_array_state_manager(); + // Clear the next marking bitmap during safepoint. void clear_bitmap(WorkerThreads* workers); @@ -732,14 +713,13 @@ private: refs_reached_period = 1024, }; - G1CMObjArrayProcessor _objArray_processor; - uint _worker_id; G1CollectedHeap* _g1h; G1ConcurrentMark* _cm; G1CMBitMap* _mark_bitmap; // the task queue of this task G1CMTaskQueue* _task_queue; + PartialArraySplitter _partial_array_splitter; G1RegionMarkStatsCache _mark_stats_cache; // Number of calls to this task @@ -850,13 +830,24 @@ private: // mark bitmap scan, and so needs to be pushed onto the mark stack. bool is_below_finger(oop obj, HeapWord* global_finger) const; - template void process_grey_task_entry(G1TaskQueueEntry task_entry); + template void process_grey_task_entry(G1TaskQueueEntry task_entry, bool stolen); + + static bool should_be_sliced(oop obj); + // Start processing the given objArrayOop by first pushing its continuations and + // then scanning the first chunk including the header. + size_t start_partial_array_processing(oop obj); + // Process the given continuation. Returns the number of words scanned. + size_t process_partial_array(const G1TaskQueueEntry& task, bool stolen); + // Apply the closure to the given range of elements in the objArray. + inline void process_array_chunk(objArrayOop obj, size_t start, size_t end); public: - // Apply the closure on the given area of the objArray. Return the number of words - // scanned. - inline size_t scan_objArray(objArrayOop obj, MemRegion mr); // Resets the task; should be called right at the beginning of a marking phase. void reset(G1CMBitMap* mark_bitmap); + // Register/unregister Partial Array Splitter Allocator with the PartialArrayStateManager. + // This allows us to discard memory arenas used for partial object array states at the end + // of a concurrent mark cycle. + void register_partial_array_splitter(); + void unregister_partial_array_splitter(); // Clears all the fields that correspond to a claimed region. void clear_region_fields(); @@ -912,7 +903,7 @@ public: inline bool deal_with_reference(T* p); // Scans an object and visits its children. - inline void scan_task_entry(G1TaskQueueEntry task_entry); + inline void process_entry(G1TaskQueueEntry task_entry, bool stolen); // Pushes an object on the local queue. inline void push(G1TaskQueueEntry task_entry); @@ -957,6 +948,11 @@ public: Pair flush_mark_stats_cache(); // Prints statistics associated with this task void print_stats(); +#if TASKQUEUE_STATS + PartialArrayTaskStats* partial_array_task_stats() { + return _partial_array_splitter.stats(); + } +#endif }; // Class that's used to to print out per-region liveness diff --git a/src/hotspot/share/gc/g1/g1ConcurrentMark.inline.hpp b/src/hotspot/share/gc/g1/g1ConcurrentMark.inline.hpp index 6f71012ff7c..2f4824e4cae 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentMark.inline.hpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentMark.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, 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 @@ -29,7 +29,6 @@ #include "gc/g1/g1CollectedHeap.inline.hpp" #include "gc/g1/g1ConcurrentMarkBitMap.inline.hpp" -#include "gc/g1/g1ConcurrentMarkObjArrayProcessor.inline.hpp" #include "gc/g1/g1HeapRegion.hpp" #include "gc/g1/g1HeapRegionRemSet.inline.hpp" #include "gc/g1/g1OopClosures.inline.hpp" @@ -39,6 +38,7 @@ #include "gc/shared/suspendibleThreadSet.hpp" #include "gc/shared/taskqueue.inline.hpp" #include "utilities/bitMap.inline.hpp" +#include "utilities/checkedCast.hpp" inline bool G1CMIsAliveClosure::do_object_b(oop obj) { // Check whether the passed in object is null. During discovery the referent @@ -90,7 +90,7 @@ inline void G1CMMarkStack::iterate(Fn fn) const { size_t num_chunks = 0; - TaskQueueEntryChunk* cur = _chunk_list; + TaskQueueEntryChunk* cur = _chunk_list.load_relaxed(); while (cur != nullptr) { guarantee(num_chunks <= _chunks_in_chunk_list, "Found %zu oop chunks which is more than there should be", num_chunks); @@ -107,13 +107,15 @@ inline void G1CMMarkStack::iterate(Fn fn) const { #endif // It scans an object and visits its children. -inline void G1CMTask::scan_task_entry(G1TaskQueueEntry task_entry) { process_grey_task_entry(task_entry); } +inline void G1CMTask::process_entry(G1TaskQueueEntry task_entry, bool stolen) { + process_grey_task_entry(task_entry, stolen); +} inline void G1CMTask::push(G1TaskQueueEntry task_entry) { - assert(task_entry.is_array_slice() || _g1h->is_in_reserved(task_entry.obj()), "invariant"); - assert(task_entry.is_array_slice() || !_g1h->is_on_master_free_list( - _g1h->heap_region_containing(task_entry.obj())), "invariant"); - assert(task_entry.is_array_slice() || _mark_bitmap->is_marked(cast_from_oop(task_entry.obj())), "invariant"); + assert(task_entry.is_partial_array_state() || _g1h->is_in_reserved(task_entry.to_oop()), "invariant"); + assert(task_entry.is_partial_array_state() || !_g1h->is_on_master_free_list( + _g1h->heap_region_containing(task_entry.to_oop())), "invariant"); + assert(task_entry.is_partial_array_state() || _mark_bitmap->is_marked(cast_from_oop(task_entry.to_oop())), "invariant"); if (!_task_queue->push(task_entry)) { // The local task queue looks full. We need to push some entries @@ -159,29 +161,34 @@ inline bool G1CMTask::is_below_finger(oop obj, HeapWord* global_finger) const { } template -inline void G1CMTask::process_grey_task_entry(G1TaskQueueEntry task_entry) { - assert(scan || (task_entry.is_oop() && task_entry.obj()->is_typeArray()), "Skipping scan of grey non-typeArray"); - assert(task_entry.is_array_slice() || _mark_bitmap->is_marked(cast_from_oop(task_entry.obj())), +inline void G1CMTask::process_grey_task_entry(G1TaskQueueEntry task_entry, bool stolen) { + assert(scan || (!task_entry.is_partial_array_state() && task_entry.to_oop()->is_typeArray()), "Skipping scan of grey non-typeArray"); + assert(task_entry.is_partial_array_state() || _mark_bitmap->is_marked(cast_from_oop(task_entry.to_oop())), "Any stolen object should be a slice or marked"); if (scan) { - if (task_entry.is_array_slice()) { - _words_scanned += _objArray_processor.process_slice(task_entry.slice()); + if (task_entry.is_partial_array_state()) { + _words_scanned += process_partial_array(task_entry, stolen); } else { - oop obj = task_entry.obj(); - if (G1CMObjArrayProcessor::should_be_sliced(obj)) { - _words_scanned += _objArray_processor.process_obj(obj); + oop obj = task_entry.to_oop(); + if (should_be_sliced(obj)) { + _words_scanned += start_partial_array_processing(obj); } else { - _words_scanned += obj->oop_iterate_size(_cm_oop_closure);; + _words_scanned += obj->oop_iterate_size(_cm_oop_closure); } } } check_limits(); } -inline size_t G1CMTask::scan_objArray(objArrayOop obj, MemRegion mr) { - obj->oop_iterate(_cm_oop_closure, mr); - return mr.word_size(); +inline bool G1CMTask::should_be_sliced(oop obj) { + return obj->is_objArray() && ((objArrayOop)obj)->length() >= (int)ObjArrayMarkingStride; +} + +inline void G1CMTask::process_array_chunk(objArrayOop obj, size_t start, size_t end) { + obj->oop_iterate_elements_range(_cm_oop_closure, + checked_cast(start), + checked_cast(end)); } inline void G1ConcurrentMark::update_top_at_mark_start(G1HeapRegion* r) { @@ -265,7 +272,7 @@ inline bool G1CMTask::make_reference_grey(oop obj) { // be pushed on the stack. So, some duplicate work, but no // correctness problems. if (is_below_finger(obj, global_finger)) { - G1TaskQueueEntry entry = G1TaskQueueEntry::from_oop(obj); + G1TaskQueueEntry entry(obj); if (obj->is_typeArray()) { // Immediately process arrays of primitive types, rather // than pushing on the mark stack. This keeps us from @@ -277,7 +284,7 @@ inline bool G1CMTask::make_reference_grey(oop obj) { // by only doing a bookkeeping update and avoiding the // actual scan of the object - a typeArray contains no // references, and the metadata is built-in. - process_grey_task_entry(entry); + process_grey_task_entry(entry, false /* stolen */); } else { push(entry); } diff --git a/src/hotspot/share/gc/g1/g1ConcurrentMarkObjArrayProcessor.cpp b/src/hotspot/share/gc/g1/g1ConcurrentMarkObjArrayProcessor.cpp deleted file mode 100644 index 7f62e5527d5..00000000000 --- a/src/hotspot/share/gc/g1/g1ConcurrentMarkObjArrayProcessor.cpp +++ /dev/null @@ -1,80 +0,0 @@ -/* - * 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 - * 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. - * - */ - -#include "gc/g1/g1CollectedHeap.inline.hpp" -#include "gc/g1/g1ConcurrentMark.inline.hpp" -#include "gc/g1/g1ConcurrentMarkObjArrayProcessor.inline.hpp" -#include "gc/g1/g1HeapRegion.inline.hpp" -#include "gc/shared/gc_globals.hpp" -#include "memory/memRegion.hpp" -#include "utilities/globalDefinitions.hpp" - -void G1CMObjArrayProcessor::push_array_slice(HeapWord* what) { - _task->push(G1TaskQueueEntry::from_slice(what)); -} - -size_t G1CMObjArrayProcessor::process_array_slice(objArrayOop obj, HeapWord* start_from, size_t remaining) { - size_t words_to_scan = MIN2(remaining, (size_t)ObjArrayMarkingStride); - - if (remaining > ObjArrayMarkingStride) { - push_array_slice(start_from + ObjArrayMarkingStride); - } - - // Then process current area. - MemRegion mr(start_from, words_to_scan); - return _task->scan_objArray(obj, mr); -} - -size_t G1CMObjArrayProcessor::process_obj(oop obj) { - assert(should_be_sliced(obj), "Must be an array object %d and large %zu", obj->is_objArray(), obj->size()); - - return process_array_slice(objArrayOop(obj), cast_from_oop(obj), objArrayOop(obj)->size()); -} - -size_t G1CMObjArrayProcessor::process_slice(HeapWord* slice) { - - // Find the start address of the objArrayOop. - // Shortcut the BOT access if the given address is from a humongous object. The BOT - // slide is fast enough for "smaller" objects in non-humongous regions, but is slower - // than directly using heap region table. - G1CollectedHeap* g1h = G1CollectedHeap::heap(); - G1HeapRegion* r = g1h->heap_region_containing(slice); - - HeapWord* const start_address = r->is_humongous() ? - r->humongous_start_region()->bottom() : - r->block_start(slice); - - assert(cast_to_oop(start_address)->is_objArray(), "Address " PTR_FORMAT " does not refer to an object array ", p2i(start_address)); - assert(start_address < slice, - "Object start address " PTR_FORMAT " must be smaller than decoded address " PTR_FORMAT, - p2i(start_address), - p2i(slice)); - - objArrayOop objArray = objArrayOop(cast_to_oop(start_address)); - - size_t already_scanned = pointer_delta(slice, start_address); - size_t remaining = objArray->size() - already_scanned; - - return process_array_slice(objArray, slice, remaining); -} diff --git a/src/hotspot/share/gc/g1/g1ConcurrentMarkObjArrayProcessor.hpp b/src/hotspot/share/gc/g1/g1ConcurrentMarkObjArrayProcessor.hpp deleted file mode 100644 index c2737dbbda6..00000000000 --- a/src/hotspot/share/gc/g1/g1ConcurrentMarkObjArrayProcessor.hpp +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) 2016, 2019, 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. - * - */ - -#ifndef SHARE_GC_G1_G1CONCURRENTMARKOBJARRAYPROCESSOR_HPP -#define SHARE_GC_G1_G1CONCURRENTMARKOBJARRAYPROCESSOR_HPP - -#include "oops/oopsHierarchy.hpp" - -class G1CMTask; - -// Helper class to mark through large objArrays during marking in an efficient way. -// Instead of pushing large object arrays, we push continuations onto the -// mark stack. These continuations are identified by having their LSB set. -// This allows incremental processing of large objects. -class G1CMObjArrayProcessor { -private: - // Reference to the task for doing the actual work. - G1CMTask* _task; - - // Push the continuation at the given address onto the mark stack. - void push_array_slice(HeapWord* addr); - - // Process (apply the closure) on the given continuation of the given objArray. - size_t process_array_slice(objArrayOop const obj, HeapWord* start_from, size_t remaining); -public: - static bool should_be_sliced(oop obj); - - G1CMObjArrayProcessor(G1CMTask* task) : _task(task) { - } - - // Process the given continuation. Returns the number of words scanned. - size_t process_slice(HeapWord* slice); - // Start processing the given objArrayOop by scanning the header and pushing its - // continuation. - size_t process_obj(oop obj); -}; - -#endif // SHARE_GC_G1_G1CONCURRENTMARKOBJARRAYPROCESSOR_HPP diff --git a/src/hotspot/share/gc/g1/g1ConcurrentMarkRemarkTasks.cpp b/src/hotspot/share/gc/g1/g1ConcurrentMarkRemarkTasks.cpp index fdef4214622..4eb11f6d8f6 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentMarkRemarkTasks.cpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentMarkRemarkTasks.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, 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,7 +30,6 @@ #include "gc/g1/g1HeapRegionPrinter.hpp" #include "gc/g1/g1RemSetTrackingPolicy.hpp" #include "logging/log.hpp" -#include "runtime/atomicAccess.hpp" #include "runtime/mutexLocker.hpp" struct G1UpdateRegionLivenessAndSelectForRebuildTask::G1OnRegionClosure : public G1HeapRegionClosure { @@ -154,7 +153,7 @@ void G1UpdateRegionLivenessAndSelectForRebuildTask::work(uint worker_id) { G1OnRegionClosure on_region_cl(_g1h, _cm, &local_cleanup_list); _g1h->heap_region_par_iterate_from_worker_offset(&on_region_cl, &_hrclaimer, worker_id); - AtomicAccess::add(&_total_selected_for_rebuild, on_region_cl._num_selected_for_rebuild); + _total_selected_for_rebuild.add_then_fetch(on_region_cl._num_selected_for_rebuild); // Update the old/humongous region sets _g1h->remove_from_old_gen_sets(on_region_cl._num_old_regions_removed, diff --git a/src/hotspot/share/gc/g1/g1ConcurrentMarkRemarkTasks.hpp b/src/hotspot/share/gc/g1/g1ConcurrentMarkRemarkTasks.hpp index 161f0b4b9f5..a256693ff1d 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentMarkRemarkTasks.hpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentMarkRemarkTasks.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, 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 @@ -29,6 +29,7 @@ #include "gc/g1/g1HeapRegionManager.hpp" #include "gc/g1/g1HeapRegionSet.hpp" #include "gc/shared/workerThread.hpp" +#include "runtime/atomic.hpp" class G1CollectedHeap; class G1ConcurrentMark; @@ -41,7 +42,7 @@ class G1UpdateRegionLivenessAndSelectForRebuildTask : public WorkerTask { G1ConcurrentMark* _cm; G1HeapRegionClaimer _hrclaimer; - uint volatile _total_selected_for_rebuild; + Atomic _total_selected_for_rebuild; // Reclaimed empty regions G1FreeRegionList _cleanup_list; @@ -57,7 +58,9 @@ public: void work(uint worker_id) override; - uint total_selected_for_rebuild() const { return _total_selected_for_rebuild; } + uint total_selected_for_rebuild() const { + return _total_selected_for_rebuild.load_relaxed(); + } static uint desired_num_workers(uint num_regions); }; diff --git a/src/hotspot/share/gc/g1/g1ConcurrentRefine.cpp b/src/hotspot/share/gc/g1/g1ConcurrentRefine.cpp index ed6a9ad4292..8546e6e2d64 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentRefine.cpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentRefine.cpp @@ -28,6 +28,7 @@ #include "gc/g1/g1CollectedHeap.inline.hpp" #include "gc/g1/g1CollectionSet.hpp" #include "gc/g1/g1ConcurrentRefine.hpp" +#include "gc/g1/g1ConcurrentRefineStats.inline.hpp" #include "gc/g1/g1ConcurrentRefineSweepTask.hpp" #include "gc/g1/g1ConcurrentRefineThread.hpp" #include "gc/g1/g1HeapRegion.inline.hpp" diff --git a/src/hotspot/share/gc/g1/g1ConcurrentRefineStats.cpp b/src/hotspot/share/gc/g1/g1ConcurrentRefineStats.cpp index 83a09c55a3f..5160d5ed036 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentRefineStats.cpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentRefineStats.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, 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,7 +22,7 @@ * */ -#include "gc/g1/g1ConcurrentRefineStats.hpp" +#include "gc/g1/g1ConcurrentRefineStats.inline.hpp" #include "runtime/atomicAccess.hpp" #include "runtime/timer.hpp" @@ -39,19 +39,27 @@ G1ConcurrentRefineStats::G1ConcurrentRefineStats() : {} void G1ConcurrentRefineStats::add_atomic(G1ConcurrentRefineStats* other) { - AtomicAccess::add(&_sweep_duration, other->_sweep_duration, memory_order_relaxed); - AtomicAccess::add(&_yield_during_sweep_duration, other->_yield_during_sweep_duration, memory_order_relaxed); + _sweep_duration.add_then_fetch(other->_sweep_duration.load_relaxed(), memory_order_relaxed); + _yield_during_sweep_duration.add_then_fetch(other->yield_during_sweep_duration(), memory_order_relaxed); - AtomicAccess::add(&_cards_scanned, other->_cards_scanned, memory_order_relaxed); - AtomicAccess::add(&_cards_clean, other->_cards_clean, memory_order_relaxed); - AtomicAccess::add(&_cards_not_parsable, other->_cards_not_parsable, memory_order_relaxed); - AtomicAccess::add(&_cards_already_refer_to_cset, other->_cards_already_refer_to_cset, memory_order_relaxed); - AtomicAccess::add(&_cards_refer_to_cset, other->_cards_refer_to_cset, memory_order_relaxed); - AtomicAccess::add(&_cards_no_cross_region, other->_cards_no_cross_region, memory_order_relaxed); + _cards_scanned.add_then_fetch(other->cards_scanned(), memory_order_relaxed); + _cards_clean.add_then_fetch(other->cards_clean(), memory_order_relaxed); + _cards_not_parsable.add_then_fetch(other->cards_not_parsable(), memory_order_relaxed); + _cards_already_refer_to_cset.add_then_fetch(other->cards_already_refer_to_cset(), memory_order_relaxed); + _cards_refer_to_cset.add_then_fetch(other->cards_refer_to_cset(), memory_order_relaxed); + _cards_no_cross_region.add_then_fetch(other->cards_no_cross_region(), memory_order_relaxed); - AtomicAccess::add(&_refine_duration, other->_refine_duration, memory_order_relaxed); + _refine_duration.add_then_fetch(other->refine_duration(), memory_order_relaxed); } void G1ConcurrentRefineStats::reset() { - *this = G1ConcurrentRefineStats(); + _sweep_duration.store_relaxed(0); + _yield_during_sweep_duration.store_relaxed(0); + _cards_scanned.store_relaxed(0); + _cards_clean.store_relaxed(0); + _cards_not_parsable.store_relaxed(0); + _cards_already_refer_to_cset.store_relaxed(0); + _cards_refer_to_cset.store_relaxed(0); + _cards_no_cross_region.store_relaxed(0); + _refine_duration.store_relaxed(0); } diff --git a/src/hotspot/share/gc/g1/g1ConcurrentRefineStats.hpp b/src/hotspot/share/gc/g1/g1ConcurrentRefineStats.hpp index ce22f4317df..5f57c56ba6c 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentRefineStats.hpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentRefineStats.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, 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,61 +26,61 @@ #define SHARE_GC_G1_G1CONCURRENTREFINESTATS_HPP #include "memory/allocation.hpp" +#include "runtime/atomic.hpp" #include "utilities/globalDefinitions.hpp" -#include "utilities/ticks.hpp" // Collection of statistics for concurrent refinement processing. // Used for collecting per-thread statistics and for summaries over a // collection of threads. class G1ConcurrentRefineStats : public CHeapObj { - jlong _sweep_duration; // Time spent sweeping the table finding non-clean cards - // and refining them. - jlong _yield_during_sweep_duration; // Time spent yielding during the sweep (not doing the sweep). + Atomic _sweep_duration; // Time spent sweeping the table finding non-clean cards + // and refining them. + Atomic _yield_during_sweep_duration; // Time spent yielding during the sweep (not doing the sweep). - size_t _cards_scanned; // Total number of cards scanned. - size_t _cards_clean; // Number of cards found clean. - size_t _cards_not_parsable; // Number of cards we could not parse and left unrefined. - size_t _cards_already_refer_to_cset;// Number of cards marked found to be already young. - size_t _cards_refer_to_cset; // Number of dirty cards that were recently found to contain a to-cset reference. - size_t _cards_no_cross_region; // Number of dirty cards that were dirtied, but then cleaned again by the mutator. + Atomic _cards_scanned; // Total number of cards scanned. + Atomic _cards_clean; // Number of cards found clean. + Atomic _cards_not_parsable; // Number of cards we could not parse and left unrefined. + Atomic _cards_already_refer_to_cset;// Number of cards marked found to be already young. + Atomic _cards_refer_to_cset; // Number of dirty cards that were recently found to contain a to-cset reference. + Atomic _cards_no_cross_region; // Number of dirty cards that were dirtied, but then cleaned again by the mutator. - jlong _refine_duration; // Time spent during actual refinement. + Atomic _refine_duration; // Time spent during actual refinement. public: G1ConcurrentRefineStats(); // Time spent performing sweeping the refinement table (includes actual refinement, // but not yield time). - jlong sweep_duration() const { return _sweep_duration - _yield_during_sweep_duration; } - jlong yield_during_sweep_duration() const { return _yield_during_sweep_duration; } - jlong refine_duration() const { return _refine_duration; } + inline jlong sweep_duration() const; + inline jlong yield_during_sweep_duration() const; + inline jlong refine_duration() const; // Number of refined cards. - size_t refined_cards() const { return cards_not_clean(); } + inline size_t refined_cards() const; - size_t cards_scanned() const { return _cards_scanned; } - size_t cards_clean() const { return _cards_clean; } - size_t cards_not_clean() const { return _cards_scanned - _cards_clean; } - size_t cards_not_parsable() const { return _cards_not_parsable; } - size_t cards_already_refer_to_cset() const { return _cards_already_refer_to_cset; } - size_t cards_refer_to_cset() const { return _cards_refer_to_cset; } - size_t cards_no_cross_region() const { return _cards_no_cross_region; } + inline size_t cards_scanned() const; + inline size_t cards_clean() const; + inline size_t cards_not_clean() const; + inline size_t cards_not_parsable() const; + inline size_t cards_already_refer_to_cset() const; + inline size_t cards_refer_to_cset() const; + inline size_t cards_no_cross_region() const; // Number of cards that were marked dirty and in need of refinement. This includes cards recently // found to refer to the collection set as they originally were dirty. - size_t cards_pending() const { return cards_not_clean() - _cards_already_refer_to_cset; } + inline size_t cards_pending() const; - size_t cards_to_cset() const { return _cards_already_refer_to_cset + _cards_refer_to_cset; } + inline size_t cards_to_cset() const; - void inc_sweep_time(jlong t) { _sweep_duration += t; } - void inc_yield_during_sweep_duration(jlong t) { _yield_during_sweep_duration += t; } - void inc_refine_duration(jlong t) { _refine_duration += t; } + inline void inc_sweep_time(jlong t); + inline void inc_yield_during_sweep_duration(jlong t); + inline void inc_refine_duration(jlong t); - void inc_cards_scanned(size_t increment) { _cards_scanned += increment; } - void inc_cards_clean(size_t increment) { _cards_clean += increment; } - void inc_cards_not_parsable() { _cards_not_parsable++; } - void inc_cards_already_refer_to_cset() { _cards_already_refer_to_cset++; } - void inc_cards_refer_to_cset() { _cards_refer_to_cset++; } - void inc_cards_no_cross_region() { _cards_no_cross_region++; } + inline void inc_cards_scanned(size_t increment); + inline void inc_cards_clean(size_t increment); + inline void inc_cards_not_parsable(); + inline void inc_cards_already_refer_to_cset(); + inline void inc_cards_refer_to_cset(); + inline void inc_cards_no_cross_region(); void add_atomic(G1ConcurrentRefineStats* other); diff --git a/src/hotspot/share/gc/g1/g1ConcurrentRefineStats.inline.hpp b/src/hotspot/share/gc/g1/g1ConcurrentRefineStats.inline.hpp new file mode 100644 index 00000000000..e1a296c6494 --- /dev/null +++ b/src/hotspot/share/gc/g1/g1ConcurrentRefineStats.inline.hpp @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2026, 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. + * + */ + +#ifndef SHARE_GC_G1_G1CONCURRENTREFINESTATS_INLINE_HPP +#define SHARE_GC_G1_G1CONCURRENTREFINESTATS_INLINE_HPP + +#include "gc/g1/g1ConcurrentRefineStats.hpp" + +inline jlong G1ConcurrentRefineStats::sweep_duration() const { + return _sweep_duration.load_relaxed() - yield_during_sweep_duration(); +} + +inline jlong G1ConcurrentRefineStats::yield_during_sweep_duration() const { + return _yield_during_sweep_duration.load_relaxed(); +} + +inline jlong G1ConcurrentRefineStats::refine_duration() const { + return _refine_duration.load_relaxed(); +} + +inline size_t G1ConcurrentRefineStats::refined_cards() const { + return cards_not_clean(); +} + +inline size_t G1ConcurrentRefineStats::cards_scanned() const { + return _cards_scanned.load_relaxed(); +} + +inline size_t G1ConcurrentRefineStats::cards_clean() const { + return _cards_clean.load_relaxed(); +} + +inline size_t G1ConcurrentRefineStats::cards_not_clean() const { + return cards_scanned() - cards_clean(); +} + +inline size_t G1ConcurrentRefineStats::cards_not_parsable() const { + return _cards_not_parsable.load_relaxed(); +} + +inline size_t G1ConcurrentRefineStats::cards_already_refer_to_cset() const { + return _cards_already_refer_to_cset.load_relaxed(); +} + +inline size_t G1ConcurrentRefineStats::cards_refer_to_cset() const { + return _cards_refer_to_cset.load_relaxed(); +} + +inline size_t G1ConcurrentRefineStats::cards_no_cross_region() const { + return _cards_no_cross_region.load_relaxed(); +} + +inline size_t G1ConcurrentRefineStats::cards_pending() const { + return cards_not_clean() - cards_already_refer_to_cset(); +} + +inline size_t G1ConcurrentRefineStats::cards_to_cset() const { + return cards_already_refer_to_cset() + cards_refer_to_cset(); +} + +inline void G1ConcurrentRefineStats::inc_sweep_time(jlong t) { + _sweep_duration.store_relaxed(_sweep_duration.load_relaxed() + t); +} + +inline void G1ConcurrentRefineStats::inc_yield_during_sweep_duration(jlong t) { + _yield_during_sweep_duration.store_relaxed(yield_during_sweep_duration() + t); +} + +inline void G1ConcurrentRefineStats::inc_refine_duration(jlong t) { + _refine_duration.store_relaxed(refine_duration() + t); +} + +inline void G1ConcurrentRefineStats::inc_cards_scanned(size_t increment) { + _cards_scanned.store_relaxed(cards_scanned() + increment); +} + +inline void G1ConcurrentRefineStats::inc_cards_clean(size_t increment) { + _cards_clean.store_relaxed(cards_clean() + increment); +} + +inline void G1ConcurrentRefineStats::inc_cards_not_parsable() { + _cards_not_parsable.store_relaxed(cards_not_parsable() + 1); +} + +inline void G1ConcurrentRefineStats::inc_cards_already_refer_to_cset() { + _cards_already_refer_to_cset.store_relaxed(cards_already_refer_to_cset() + 1); +} + +inline void G1ConcurrentRefineStats::inc_cards_refer_to_cset() { + _cards_refer_to_cset.store_relaxed(cards_refer_to_cset() + 1); +} + +inline void G1ConcurrentRefineStats::inc_cards_no_cross_region() { + _cards_no_cross_region.store_relaxed(cards_no_cross_region() + 1); +} + +#endif // SHARE_GC_G1_G1CONCURRENTREFINESTATS_INLINE_HPP diff --git a/src/hotspot/share/gc/g1/g1ConcurrentRefineSweepTask.cpp b/src/hotspot/share/gc/g1/g1ConcurrentRefineSweepTask.cpp index ca5bc9ebe5f..ce944f2254d 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentRefineSweepTask.cpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentRefineSweepTask.cpp @@ -24,6 +24,7 @@ #include "gc/g1/g1CardTableClaimTable.inline.hpp" #include "gc/g1/g1CollectedHeap.inline.hpp" +#include "gc/g1/g1ConcurrentRefineStats.inline.hpp" #include "gc/g1/g1ConcurrentRefineSweepTask.hpp" class G1RefineRegionClosure : public G1HeapRegionClosure { diff --git a/src/hotspot/share/gc/g1/g1ConcurrentRefineSweepTask.hpp b/src/hotspot/share/gc/g1/g1ConcurrentRefineSweepTask.hpp index bf24c5ae850..827b9a3c402 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentRefineSweepTask.hpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentRefineSweepTask.hpp @@ -25,10 +25,10 @@ #ifndef SHARE_GC_G1_G1CONCURRENTREFINESWEEPTASK_HPP #define SHARE_GC_G1_G1CONCURRENTREFINESWEEPTASK_HPP -#include "gc/g1/g1ConcurrentRefineStats.hpp" #include "gc/shared/workerThread.hpp" class G1CardTableClaimTable; +class G1ConcurrentRefineStats; class G1ConcurrentRefineSweepTask : public WorkerTask { G1CardTableClaimTable* _scan_state; diff --git a/src/hotspot/share/gc/g1/g1ConcurrentRefineThread.cpp b/src/hotspot/share/gc/g1/g1ConcurrentRefineThread.cpp index eccfe466d48..6b51e5eef62 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentRefineThread.cpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentRefineThread.cpp @@ -26,7 +26,7 @@ #include "gc/g1/g1CardTableClaimTable.inline.hpp" #include "gc/g1/g1CollectedHeap.inline.hpp" #include "gc/g1/g1ConcurrentRefine.hpp" -#include "gc/g1/g1ConcurrentRefineStats.hpp" +#include "gc/g1/g1ConcurrentRefineStats.inline.hpp" #include "gc/g1/g1ConcurrentRefineSweepTask.hpp" #include "gc/g1/g1ConcurrentRefineThread.hpp" #include "gc/shared/gcTraceTime.inline.hpp" diff --git a/src/hotspot/share/gc/g1/g1ConcurrentRefineThread.hpp b/src/hotspot/share/gc/g1/g1ConcurrentRefineThread.hpp index 7cdc001d348..2ecbdc668eb 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentRefineThread.hpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentRefineThread.hpp @@ -25,7 +25,6 @@ #ifndef SHARE_GC_G1_G1CONCURRENTREFINETHREAD_HPP #define SHARE_GC_G1_G1CONCURRENTREFINETHREAD_HPP -#include "gc/g1/g1ConcurrentRefineStats.hpp" #include "gc/shared/concurrentGCThread.hpp" #include "runtime/mutex.hpp" #include "utilities/globalDefinitions.hpp" diff --git a/src/hotspot/share/gc/g1/g1EvacFailureRegions.cpp b/src/hotspot/share/gc/g1/g1EvacFailureRegions.cpp index ffcb5a0022f..37553e2aa56 100644 --- a/src/hotspot/share/gc/g1/g1EvacFailureRegions.cpp +++ b/src/hotspot/share/gc/g1/g1EvacFailureRegions.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2021, 2022, Huawei Technologies Co., Ltd. All rights reserved. + * Copyright (c) 2026, 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 +29,6 @@ #include "gc/g1/g1EvacFailureRegions.inline.hpp" #include "gc/g1/g1HeapRegion.hpp" #include "memory/allocation.hpp" -#include "runtime/atomicAccess.hpp" #include "utilities/bitMap.inline.hpp" G1EvacFailureRegions::G1EvacFailureRegions() : @@ -43,7 +43,7 @@ G1EvacFailureRegions::~G1EvacFailureRegions() { } void G1EvacFailureRegions::pre_collection(uint max_regions) { - AtomicAccess::store(&_num_regions_evac_failed, 0u); + _num_regions_evac_failed.store_relaxed(0u); _regions_evac_failed.resize(max_regions); _regions_pinned.resize(max_regions); _regions_alloc_failed.resize(max_regions); @@ -69,6 +69,6 @@ void G1EvacFailureRegions::par_iterate(G1HeapRegionClosure* closure, G1CollectedHeap::heap()->par_iterate_regions_array(closure, hrclaimer, _evac_failed_regions, - AtomicAccess::load(&_num_regions_evac_failed), + num_regions_evac_failed(), worker_id); } diff --git a/src/hotspot/share/gc/g1/g1EvacFailureRegions.hpp b/src/hotspot/share/gc/g1/g1EvacFailureRegions.hpp index 9d29957b782..f752a3f8ab7 100644 --- a/src/hotspot/share/gc/g1/g1EvacFailureRegions.hpp +++ b/src/hotspot/share/gc/g1/g1EvacFailureRegions.hpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2021, 2022, Huawei Technologies Co., Ltd. All rights reserved. + * Copyright (c) 2026, 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,6 +26,7 @@ #ifndef SHARE_GC_G1_G1EVACFAILUREREGIONS_HPP #define SHARE_GC_G1_G1EVACFAILUREREGIONS_HPP +#include "runtime/atomic.hpp" #include "utilities/bitMap.hpp" class G1AbstractSubTask; @@ -53,14 +55,14 @@ class G1EvacFailureRegions { // Evacuation failed regions (indexes) in the current collection. uint* _evac_failed_regions; // Number of regions evacuation failed in the current collection. - volatile uint _num_regions_evac_failed; + Atomic _num_regions_evac_failed; public: G1EvacFailureRegions(); ~G1EvacFailureRegions(); uint get_region_idx(uint idx) const { - assert(idx < _num_regions_evac_failed, "precondition"); + assert(idx < _num_regions_evac_failed.load_relaxed(), "precondition"); return _evac_failed_regions[idx]; } diff --git a/src/hotspot/share/gc/g1/g1EvacFailureRegions.inline.hpp b/src/hotspot/share/gc/g1/g1EvacFailureRegions.inline.hpp index 6eec9b63e6b..fb456475b56 100644 --- a/src/hotspot/share/gc/g1/g1EvacFailureRegions.inline.hpp +++ b/src/hotspot/share/gc/g1/g1EvacFailureRegions.inline.hpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2021, 2022, Huawei Technologies Co., Ltd. All rights reserved. + * Copyright (c) 2026, 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 @@ -29,10 +30,9 @@ #include "gc/g1/g1CollectedHeap.inline.hpp" #include "gc/g1/g1GCPhaseTimes.hpp" -#include "runtime/atomicAccess.hpp" uint G1EvacFailureRegions::num_regions_evac_failed() const { - return AtomicAccess::load(&_num_regions_evac_failed); + return _num_regions_evac_failed.load_relaxed(); } bool G1EvacFailureRegions::has_regions_evac_failed() const { @@ -57,7 +57,7 @@ bool G1EvacFailureRegions::record(uint worker_id, uint region_idx, bool cause_pi bool success = _regions_evac_failed.par_set_bit(region_idx, memory_order_relaxed); if (success) { - size_t offset = AtomicAccess::fetch_then_add(&_num_regions_evac_failed, 1u); + size_t offset = _num_regions_evac_failed.fetch_then_add(1u); _evac_failed_regions[offset] = region_idx; G1CollectedHeap* g1h = G1CollectedHeap::heap(); diff --git a/src/hotspot/share/gc/g1/g1EvacStats.cpp b/src/hotspot/share/gc/g1/g1EvacStats.cpp index 049175a4ecc..1d54b184e64 100644 --- a/src/hotspot/share/gc/g1/g1EvacStats.cpp +++ b/src/hotspot/share/gc/g1/g1EvacStats.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, 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,13 +22,24 @@ * */ -#include "gc/g1/g1EvacStats.hpp" +#include "gc/g1/g1EvacStats.inline.hpp" #include "gc/shared/gc_globals.hpp" #include "gc/shared/gcId.hpp" #include "logging/log.hpp" #include "memory/allocation.inline.hpp" #include "runtime/globals.hpp" +void G1EvacStats::reset() { + PLABStats::reset(); + _region_end_waste.store_relaxed(0); + _regions_filled.store_relaxed(0); + _num_plab_filled.store_relaxed(0); + _direct_allocated.store_relaxed(0); + _num_direct_allocated.store_relaxed(0); + _failure_used.store_relaxed(0); + _failure_waste.store_relaxed(0); +} + void G1EvacStats::log_plab_allocation() { log_debug(gc, plab)("%s PLAB allocation: " "allocated: %zuB, " @@ -51,13 +62,13 @@ void G1EvacStats::log_plab_allocation() { "failure used: %zuB, " "failure wasted: %zuB", _description, - _region_end_waste * HeapWordSize, - _regions_filled, - _num_plab_filled, - _direct_allocated * HeapWordSize, - _num_direct_allocated, - _failure_used * HeapWordSize, - _failure_waste * HeapWordSize); + region_end_waste() * HeapWordSize, + regions_filled(), + num_plab_filled(), + direct_allocated() * HeapWordSize, + num_direct_allocated(), + failure_used() * HeapWordSize, + failure_waste() * HeapWordSize); } void G1EvacStats::log_sizing(size_t calculated_words, size_t net_desired_words) { @@ -109,7 +120,7 @@ size_t G1EvacStats::compute_desired_plab_size() const { // threads do not allocate anything but a few rather large objects. In this // degenerate case the PLAB size would simply quickly tend to minimum PLAB size, // which is an okay reaction. - size_t const used_for_waste_calculation = used() > _region_end_waste ? used() - _region_end_waste : 0; + size_t const used_for_waste_calculation = used() > region_end_waste() ? used() - region_end_waste() : 0; size_t const total_waste_allowed = used_for_waste_calculation * TargetPLABWastePct; return (size_t)((double)total_waste_allowed / (100 - G1LastPLABAverageOccupancy)); diff --git a/src/hotspot/share/gc/g1/g1EvacStats.hpp b/src/hotspot/share/gc/g1/g1EvacStats.hpp index e6eb80442d6..b250d4580b5 100644 --- a/src/hotspot/share/gc/g1/g1EvacStats.hpp +++ b/src/hotspot/share/gc/g1/g1EvacStats.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, 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,6 +27,7 @@ #include "gc/shared/gcUtil.hpp" #include "gc/shared/plab.hpp" +#include "runtime/atomic.hpp" // Records various memory allocation statistics gathered during evacuation. All sizes // are in HeapWords. @@ -36,30 +37,21 @@ class G1EvacStats : public PLABStats { AdaptiveWeightedAverage _net_plab_size_filter; // Integrator with decay - size_t _region_end_waste; // Number of words wasted due to skipping to the next region. - uint _regions_filled; // Number of regions filled completely. - size_t _num_plab_filled; // Number of PLABs filled and retired. - size_t _direct_allocated; // Number of words allocated directly into the regions. - size_t _num_direct_allocated; // Number of direct allocation attempts. + Atomic _region_end_waste; // Number of words wasted due to skipping to the next region. + Atomic _regions_filled; // Number of regions filled completely. + Atomic _num_plab_filled; // Number of PLABs filled and retired. + Atomic _direct_allocated; // Number of words allocated directly into the regions. + Atomic _num_direct_allocated; // Number of direct allocation attempts. // Number of words in live objects remaining in regions that ultimately suffered an // evacuation failure. This is used in the regions when the regions are made old regions. - size_t _failure_used; + Atomic _failure_used; // Number of words wasted in regions which failed evacuation. This is the sum of space // for objects successfully copied out of the regions (now dead space) plus waste at the // end of regions. - size_t _failure_waste; + Atomic _failure_waste; - virtual void reset() { - PLABStats::reset(); - _region_end_waste = 0; - _regions_filled = 0; - _num_plab_filled = 0; - _direct_allocated = 0; - _num_direct_allocated = 0; - _failure_used = 0; - _failure_waste = 0; - } + virtual void reset(); void log_plab_allocation(); void log_sizing(size_t calculated_words, size_t net_desired_words); @@ -77,16 +69,16 @@ public: // Should be called at the end of a GC pause. void adjust_desired_plab_size(); - uint regions_filled() const { return _regions_filled; } - size_t num_plab_filled() const { return _num_plab_filled; } - size_t region_end_waste() const { return _region_end_waste; } - size_t direct_allocated() const { return _direct_allocated; } - size_t num_direct_allocated() const { return _num_direct_allocated; } + uint regions_filled() const; + size_t num_plab_filled() const; + size_t region_end_waste() const; + size_t direct_allocated() const; + size_t num_direct_allocated() const; // Amount of space in heapwords used in the failing regions when an evacuation failure happens. - size_t failure_used() const { return _failure_used; } + size_t failure_used() const; // Amount of space in heapwords wasted (unused) in the failing regions when an evacuation failure happens. - size_t failure_waste() const { return _failure_waste; } + size_t failure_waste() const; inline void add_num_plab_filled(size_t value); inline void add_direct_allocated(size_t value); diff --git a/src/hotspot/share/gc/g1/g1EvacStats.inline.hpp b/src/hotspot/share/gc/g1/g1EvacStats.inline.hpp index c90598a30cb..2bd3b37719a 100644 --- a/src/hotspot/share/gc/g1/g1EvacStats.inline.hpp +++ b/src/hotspot/share/gc/g1/g1EvacStats.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, 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,28 +27,54 @@ #include "gc/g1/g1EvacStats.hpp" -#include "runtime/atomicAccess.hpp" +inline uint G1EvacStats::regions_filled() const { + return _regions_filled.load_relaxed(); +} + +inline size_t G1EvacStats::num_plab_filled() const { + return _num_plab_filled.load_relaxed(); +} + +inline size_t G1EvacStats::region_end_waste() const { + return _region_end_waste.load_relaxed(); +} + +inline size_t G1EvacStats::direct_allocated() const { + return _direct_allocated.load_relaxed(); +} + +inline size_t G1EvacStats::num_direct_allocated() const { + return _num_direct_allocated.load_relaxed(); +} + +inline size_t G1EvacStats::failure_used() const { + return _failure_used.load_relaxed(); +} + +inline size_t G1EvacStats::failure_waste() const { + return _failure_waste.load_relaxed(); +} inline void G1EvacStats::add_direct_allocated(size_t value) { - AtomicAccess::add(&_direct_allocated, value, memory_order_relaxed); + _direct_allocated.add_then_fetch(value, memory_order_relaxed); } inline void G1EvacStats::add_num_plab_filled(size_t value) { - AtomicAccess::add(&_num_plab_filled, value, memory_order_relaxed); + _num_plab_filled.add_then_fetch(value, memory_order_relaxed); } inline void G1EvacStats::add_num_direct_allocated(size_t value) { - AtomicAccess::add(&_num_direct_allocated, value, memory_order_relaxed); + _num_direct_allocated.add_then_fetch(value, memory_order_relaxed); } inline void G1EvacStats::add_region_end_waste(size_t value) { - AtomicAccess::add(&_region_end_waste, value, memory_order_relaxed); - AtomicAccess::inc(&_regions_filled, memory_order_relaxed); + _region_end_waste.add_then_fetch(value, memory_order_relaxed); + _regions_filled.add_then_fetch(1u, memory_order_relaxed); } inline void G1EvacStats::add_failure_used_and_waste(size_t used, size_t waste) { - AtomicAccess::add(&_failure_used, used, memory_order_relaxed); - AtomicAccess::add(&_failure_waste, waste, memory_order_relaxed); + _failure_used.add_then_fetch(used, memory_order_relaxed); + _failure_waste.add_then_fetch(waste, memory_order_relaxed); } #endif // SHARE_GC_G1_G1EVACSTATS_INLINE_HPP diff --git a/src/hotspot/share/gc/g1/g1FullCollector.cpp b/src/hotspot/share/gc/g1/g1FullCollector.cpp index 5ca5dcef001..6c8cc7028cc 100644 --- a/src/hotspot/share/gc/g1/g1FullCollector.cpp +++ b/src/hotspot/share/gc/g1/g1FullCollector.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, 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 @@ -116,8 +116,8 @@ G1FullCollector::G1FullCollector(G1CollectedHeap* heap, _num_workers(calc_active_workers()), _has_compaction_targets(false), _has_humongous(false), - _oop_queue_set(_num_workers), - _array_queue_set(_num_workers), + _marking_task_queues(_num_workers), + _partial_array_state_manager(nullptr), _preserved_marks_set(true), _serial_compaction_point(this, nullptr), _humongous_compaction_point(this, nullptr), @@ -134,32 +134,40 @@ G1FullCollector::G1FullCollector(G1CollectedHeap* heap, _compaction_points = NEW_C_HEAP_ARRAY(G1FullGCCompactionPoint*, _num_workers, mtGC); _live_stats = NEW_C_HEAP_ARRAY(G1RegionMarkStats, _heap->max_num_regions(), mtGC); - _compaction_tops = NEW_C_HEAP_ARRAY(HeapWord*, _heap->max_num_regions(), mtGC); + _compaction_tops = NEW_C_HEAP_ARRAY(Atomic, _heap->max_num_regions(), mtGC); for (uint j = 0; j < heap->max_num_regions(); j++) { _live_stats[j].clear(); - _compaction_tops[j] = nullptr; + ::new (&_compaction_tops[j]) Atomic{}; } + _partial_array_state_manager = new PartialArrayStateManager(_num_workers); + for (uint i = 0; i < _num_workers; i++) { _markers[i] = new G1FullGCMarker(this, i, _live_stats); _compaction_points[i] = new G1FullGCCompactionPoint(this, _preserved_marks_set.get(i)); - _oop_queue_set.register_queue(i, marker(i)->oop_stack()); - _array_queue_set.register_queue(i, marker(i)->objarray_stack()); + _marking_task_queues.register_queue(i, marker(i)->task_queue()); } + _serial_compaction_point.set_preserved_stack(_preserved_marks_set.get(0)); _humongous_compaction_point.set_preserved_stack(_preserved_marks_set.get(0)); _region_attr_table.initialize(heap->reserved(), G1HeapRegion::GrainBytes); } +PartialArrayStateManager* G1FullCollector::partial_array_state_manager() const { + return _partial_array_state_manager; +} + G1FullCollector::~G1FullCollector() { for (uint i = 0; i < _num_workers; i++) { delete _markers[i]; delete _compaction_points[i]; } + delete _partial_array_state_manager; + FREE_C_HEAP_ARRAY(G1FullGCMarker*, _markers); FREE_C_HEAP_ARRAY(G1FullGCCompactionPoint*, _compaction_points); - FREE_C_HEAP_ARRAY(HeapWord*, _compaction_tops); + FREE_C_HEAP_ARRAY(Atomic, _compaction_tops); FREE_C_HEAP_ARRAY(G1RegionMarkStats, _live_stats); } @@ -279,8 +287,8 @@ public: uint index = (_tm == RefProcThreadModel::Single) ? 0 : worker_id; G1FullKeepAliveClosure keep_alive(_collector.marker(index)); BarrierEnqueueDiscoveredFieldClosure enqueue; - G1FollowStackClosure* complete_gc = _collector.marker(index)->stack_closure(); - _rp_task->rp_work(worker_id, &is_alive, &keep_alive, &enqueue, complete_gc); + G1MarkStackClosure* complete_marking = _collector.marker(index)->stack_closure(); + _rp_task->rp_work(worker_id, &is_alive, &keep_alive, &enqueue, complete_marking); } }; @@ -302,7 +310,7 @@ void G1FullCollector::phase1_mark_live_objects() { const ReferenceProcessorStats& stats = reference_processor()->process_discovered_references(task, _heap->workers(), pt); scope()->tracer()->report_gc_reference_stats(stats); pt.print_all_references(); - assert(marker(0)->oop_stack()->is_empty(), "Should be no oops on the stack"); + assert(marker(0)->task_queue()->is_empty(), "Should be no oops on the stack"); } { @@ -328,8 +336,7 @@ void G1FullCollector::phase1_mark_live_objects() { scope()->tracer()->report_object_count_after_gc(&_is_alive, _heap->workers()); } #if TASKQUEUE_STATS - oop_queue_set()->print_and_reset_taskqueue_stats("Oop Queue"); - array_queue_set()->print_and_reset_taskqueue_stats("ObjArrayOop Queue"); + marking_task_queues()->print_and_reset_taskqueue_stats("Marking Task Queue"); #endif } diff --git a/src/hotspot/share/gc/g1/g1FullCollector.hpp b/src/hotspot/share/gc/g1/g1FullCollector.hpp index ed8225fc004..7e455b07013 100644 --- a/src/hotspot/share/gc/g1/g1FullCollector.hpp +++ b/src/hotspot/share/gc/g1/g1FullCollector.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, 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 @@ -79,8 +79,8 @@ class G1FullCollector : StackObj { bool _has_humongous; G1FullGCMarker** _markers; G1FullGCCompactionPoint** _compaction_points; - OopQueueSet _oop_queue_set; - ObjArrayTaskQueueSet _array_queue_set; + G1MarkTasksQueueSet _marking_task_queues; + PartialArrayStateManager* _partial_array_state_manager; PreservedMarksSet _preserved_marks_set; G1FullGCCompactionPoint _serial_compaction_point; G1FullGCCompactionPoint _humongous_compaction_point; @@ -96,7 +96,7 @@ class G1FullCollector : StackObj { G1FullGCHeapRegionAttr _region_attr_table; - HeapWord* volatile* _compaction_tops; + Atomic* _compaction_tops; public: G1FullCollector(G1CollectedHeap* heap, @@ -113,8 +113,7 @@ public: uint workers() { return _num_workers; } G1FullGCMarker* marker(uint id) { return _markers[id]; } G1FullGCCompactionPoint* compaction_point(uint id) { return _compaction_points[id]; } - OopQueueSet* oop_queue_set() { return &_oop_queue_set; } - ObjArrayTaskQueueSet* array_queue_set() { return &_array_queue_set; } + G1MarkTasksQueueSet* marking_task_queues() { return &_marking_task_queues; } PreservedMarksSet* preserved_mark_set() { return &_preserved_marks_set; } G1FullGCCompactionPoint* serial_compaction_point() { return &_serial_compaction_point; } G1FullGCCompactionPoint* humongous_compaction_point() { return &_humongous_compaction_point; } @@ -122,9 +121,11 @@ public: ReferenceProcessor* reference_processor(); size_t live_words(uint region_index) const { assert(region_index < _heap->max_num_regions(), "sanity"); - return _live_stats[region_index]._live_words; + return _live_stats[region_index].live_words(); } + PartialArrayStateManager* partial_array_state_manager() const; + void before_marking_update_attribute_table(G1HeapRegion* hr); inline bool is_compacting(oop obj) const; diff --git a/src/hotspot/share/gc/g1/g1FullCollector.inline.hpp b/src/hotspot/share/gc/g1/g1FullCollector.inline.hpp index b52f3d79604..0c201f0e43f 100644 --- a/src/hotspot/share/gc/g1/g1FullCollector.inline.hpp +++ b/src/hotspot/share/gc/g1/g1FullCollector.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, 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 @@ -63,11 +63,11 @@ void G1FullCollector::update_from_skip_compacting_to_compacting(uint region_idx) } void G1FullCollector::set_compaction_top(G1HeapRegion* r, HeapWord* value) { - AtomicAccess::store(&_compaction_tops[r->hrm_index()], value); + _compaction_tops[r->hrm_index()].store_relaxed(value); } HeapWord* G1FullCollector::compaction_top(G1HeapRegion* r) const { - return AtomicAccess::load(&_compaction_tops[r->hrm_index()]); + return _compaction_tops[r->hrm_index()].load_relaxed(); } void G1FullCollector::set_has_compaction_targets() { diff --git a/src/hotspot/share/gc/g1/g1FullGCAdjustTask.cpp b/src/hotspot/share/gc/g1/g1FullGCAdjustTask.cpp index 83c846e84d4..c02b028112b 100644 --- a/src/hotspot/share/gc/g1/g1FullGCAdjustTask.cpp +++ b/src/hotspot/share/gc/g1/g1FullGCAdjustTask.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, 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,6 @@ #include "gc/shared/weakProcessor.inline.hpp" #include "logging/log.hpp" #include "memory/iterator.inline.hpp" -#include "runtime/atomicAccess.hpp" class G1AdjustLiveClosure : public StackObj { G1AdjustClosure* _adjust_closure; diff --git a/src/hotspot/share/gc/g1/g1FullGCMarkTask.cpp b/src/hotspot/share/gc/g1/g1FullGCMarkTask.cpp index 52b0d04a500..f14e1108db8 100644 --- a/src/hotspot/share/gc/g1/g1FullGCMarkTask.cpp +++ b/src/hotspot/share/gc/g1/g1FullGCMarkTask.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, 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 @@ G1FullGCMarkTask::G1FullGCMarkTask(G1FullCollector* collector) : G1FullGCTask("G1 Parallel Marking Task", collector), _root_processor(G1CollectedHeap::heap(), collector->workers()), - _terminator(collector->workers(), collector->array_queue_set()) { + _terminator(collector->workers(), collector->marking_task_queues()) { } void G1FullGCMarkTask::work(uint worker_id) { @@ -54,10 +54,9 @@ void G1FullGCMarkTask::work(uint worker_id) { } // Mark stack is populated, now process and drain it. - marker->complete_marking(collector()->oop_queue_set(), collector()->array_queue_set(), &_terminator); + marker->complete_marking(collector()->marking_task_queues(), &_terminator); // This is the point where the entire marking should have completed. - assert(marker->oop_stack()->is_empty(), "Marking should have completed"); - assert(marker->objarray_stack()->is_empty(), "Array marking should have completed"); + assert(marker->task_queue()->is_empty(), "Marking should have completed"); log_task("Marking task", worker_id, start); } diff --git a/src/hotspot/share/gc/g1/g1FullGCMarker.cpp b/src/hotspot/share/gc/g1/g1FullGCMarker.cpp index aa8f12a2d1b..2b0b78ac1ce 100644 --- a/src/hotspot/share/gc/g1/g1FullGCMarker.cpp +++ b/src/hotspot/share/gc/g1/g1FullGCMarker.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, 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,6 +25,8 @@ #include "classfile/classLoaderData.hpp" #include "classfile/classLoaderDataGraph.hpp" #include "gc/g1/g1FullGCMarker.inline.hpp" +#include "gc/shared/partialArraySplitter.inline.hpp" +#include "gc/shared/partialArrayState.hpp" #include "gc/shared/referenceProcessor.hpp" #include "gc/shared/taskTerminator.hpp" #include "gc/shared/verifyOption.hpp" @@ -36,8 +38,8 @@ G1FullGCMarker::G1FullGCMarker(G1FullCollector* collector, _collector(collector), _worker_id(worker_id), _bitmap(collector->mark_bitmap()), - _oop_stack(), - _objarray_stack(), + _task_queue(), + _partial_array_splitter(collector->partial_array_state_manager(), collector->workers(), ObjArrayMarkingStride), _mark_closure(worker_id, this, ClassLoaderData::_claim_stw_fullgc_mark, G1CollectedHeap::heap()->ref_processor_stw()), _stack_closure(this), _cld_closure(mark_closure(), ClassLoaderData::_claim_stw_fullgc_mark), @@ -47,24 +49,36 @@ G1FullGCMarker::G1FullGCMarker(G1FullCollector* collector, } G1FullGCMarker::~G1FullGCMarker() { - assert(is_empty(), "Must be empty at this point"); + assert(is_task_queue_empty(), "Must be empty at this point"); } -void G1FullGCMarker::complete_marking(OopQueueSet* oop_stacks, - ObjArrayTaskQueueSet* array_stacks, +void G1FullGCMarker::process_partial_array(PartialArrayState* state, bool stolen) { + // Access state before release by claim(). + objArrayOop obj_array = objArrayOop(state->source()); + PartialArraySplitter::Claim claim = + _partial_array_splitter.claim(state, task_queue(), stolen); + process_array_chunk(obj_array, claim._start, claim._end); +} + +void G1FullGCMarker::start_partial_array_processing(objArrayOop obj) { + mark_closure()->do_klass(obj->klass()); + // Don't push empty arrays to avoid unnecessary work. + size_t array_length = obj->length(); + if (array_length > 0) { + size_t initial_chunk_size = _partial_array_splitter.start(task_queue(), obj, nullptr, array_length); + process_array_chunk(obj, 0, initial_chunk_size); + } +} + +void G1FullGCMarker::complete_marking(G1ScannerTasksQueueSet* task_queues, TaskTerminator* terminator) { do { - follow_marking_stacks(); - ObjArrayTask steal_array; - if (array_stacks->steal(_worker_id, steal_array)) { - follow_array_chunk(objArrayOop(steal_array.obj()), steal_array.index()); - } else { - oop steal_oop; - if (oop_stacks->steal(_worker_id, steal_oop)) { - follow_object(steal_oop); - } + process_marking_stacks(); + ScannerTask stolen_task; + if (task_queues->steal(_worker_id, stolen_task)) { + dispatch_task(stolen_task, true); } - } while (!is_empty() || !terminator->offer_termination()); + } while (!is_task_queue_empty() || !terminator->offer_termination()); } void G1FullGCMarker::flush_mark_stats_cache() { diff --git a/src/hotspot/share/gc/g1/g1FullGCMarker.hpp b/src/hotspot/share/gc/g1/g1FullGCMarker.hpp index b1b750eae90..5973cc841c5 100644 --- a/src/hotspot/share/gc/g1/g1FullGCMarker.hpp +++ b/src/hotspot/share/gc/g1/g1FullGCMarker.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, 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,6 +28,8 @@ #include "gc/g1/g1FullGCOopClosures.hpp" #include "gc/g1/g1OopClosures.hpp" #include "gc/g1/g1RegionMarkStatsCache.hpp" +#include "gc/shared/partialArraySplitter.hpp" +#include "gc/shared/partialArrayState.hpp" #include "gc/shared/stringdedup/stringDedup.hpp" #include "gc/shared/taskqueue.hpp" #include "memory/iterator.hpp" @@ -38,16 +40,15 @@ #include "utilities/growableArray.hpp" #include "utilities/stack.hpp" -typedef OverflowTaskQueue OopQueue; -typedef OverflowTaskQueue ObjArrayTaskQueue; -typedef GenericTaskQueueSet OopQueueSet; -typedef GenericTaskQueueSet ObjArrayTaskQueueSet; class G1CMBitMap; class G1FullCollector; class TaskTerminator; +typedef OverflowTaskQueue G1MarkTasksQueue; +typedef GenericTaskQueueSet G1MarkTasksQueueSet; + class G1FullGCMarker : public CHeapObj { G1FullCollector* _collector; @@ -56,56 +57,50 @@ class G1FullGCMarker : public CHeapObj { G1CMBitMap* _bitmap; // Mark stack - OopQueue _oop_stack; - ObjArrayTaskQueue _objarray_stack; + G1MarkTasksQueue _task_queue; + PartialArraySplitter _partial_array_splitter; // Marking closures G1MarkAndPushClosure _mark_closure; - G1FollowStackClosure _stack_closure; + G1MarkStackClosure _stack_closure; CLDToOopClosure _cld_closure; StringDedup::Requests _string_dedup_requests; G1RegionMarkStatsCache _mark_stats_cache; - inline bool is_empty(); - inline void push_objarray(oop obj, size_t index); + inline bool is_task_queue_empty(); inline bool mark_object(oop obj); // Marking helpers - inline void follow_object(oop obj); - inline void follow_array(objArrayOop array); - inline void follow_array_chunk(objArrayOop array, int index); + inline void process_array_chunk(objArrayOop obj, size_t start, size_t end); + inline void dispatch_task(const ScannerTask& task, bool stolen); + // Start processing the given objArrayOop by first pushing its continuations and + // then scanning the first chunk. + void start_partial_array_processing(objArrayOop obj); + // Process the given continuation. + void process_partial_array(PartialArrayState* state, bool stolen); inline void publish_and_drain_oop_tasks(); - // Try to publish all contents from the objArray task queue overflow stack to - // the shared objArray stack. - // Returns true and a valid task if there has not been enough space in the shared - // objArray stack, otherwise returns false and the task is invalid. - inline bool publish_or_pop_objarray_tasks(ObjArrayTask& task); - public: G1FullGCMarker(G1FullCollector* collector, uint worker_id, G1RegionMarkStats* mark_stats); ~G1FullGCMarker(); - // Stack getters - OopQueue* oop_stack() { return &_oop_stack; } - ObjArrayTaskQueue* objarray_stack() { return &_objarray_stack; } + G1MarkTasksQueue* task_queue() { return &_task_queue; } // Marking entry points template inline void mark_and_push(T* p); - inline void follow_marking_stacks(); - void complete_marking(OopQueueSet* oop_stacks, - ObjArrayTaskQueueSet* array_stacks, + inline void process_marking_stacks(); + void complete_marking(G1MarkTasksQueueSet* task_queues, TaskTerminator* terminator); // Closure getters CLDToOopClosure* cld_closure() { return &_cld_closure; } G1MarkAndPushClosure* mark_closure() { return &_mark_closure; } - G1FollowStackClosure* stack_closure() { return &_stack_closure; } + G1MarkStackClosure* stack_closure() { return &_stack_closure; } // Flush live bytes to regions void flush_mark_stats_cache(); diff --git a/src/hotspot/share/gc/g1/g1FullGCMarker.inline.hpp b/src/hotspot/share/gc/g1/g1FullGCMarker.inline.hpp index 6cbfe2674e8..a6f45abe005 100644 --- a/src/hotspot/share/gc/g1/g1FullGCMarker.inline.hpp +++ b/src/hotspot/share/gc/g1/g1FullGCMarker.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, 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 @@ -42,6 +42,7 @@ #include "oops/access.inline.hpp" #include "oops/compressedOops.inline.hpp" #include "oops/oop.inline.hpp" +#include "utilities/checkedCast.hpp" #include "utilities/debug.hpp" inline bool G1FullGCMarker::mark_object(oop obj) { @@ -71,94 +72,55 @@ template inline void G1FullGCMarker::mark_and_push(T* p) { if (!CompressedOops::is_null(heap_oop)) { oop obj = CompressedOops::decode_not_null(heap_oop); if (mark_object(obj)) { - _oop_stack.push(obj); + _task_queue.push(ScannerTask(obj)); } assert(_bitmap->is_marked(obj), "Must be marked"); } } -inline bool G1FullGCMarker::is_empty() { - return _oop_stack.is_empty() && _objarray_stack.is_empty(); +inline bool G1FullGCMarker::is_task_queue_empty() { + return _task_queue.is_empty(); } -inline void G1FullGCMarker::push_objarray(oop obj, size_t index) { - ObjArrayTask task(obj, index); - assert(task.is_valid(), "bad ObjArrayTask"); - _objarray_stack.push(task); +inline void G1FullGCMarker::process_array_chunk(objArrayOop obj, size_t start, size_t end) { + obj->oop_iterate_elements_range(mark_closure(), + checked_cast(start), + checked_cast(end)); } -inline void G1FullGCMarker::follow_array(objArrayOop array) { - mark_closure()->do_klass(array->klass()); - // Don't push empty arrays to avoid unnecessary work. - if (array->length() > 0) { - push_objarray(array, 0); - } -} - -void G1FullGCMarker::follow_array_chunk(objArrayOop array, int index) { - const int len = array->length(); - const int beg_index = index; - assert(beg_index < len || len == 0, "index too large"); - - const int stride = MIN2(len - beg_index, (int) ObjArrayMarkingStride); - const int end_index = beg_index + stride; - - // Push the continuation first to allow more efficient work stealing. - if (end_index < len) { - push_objarray(array, end_index); - } - - array->oop_iterate_range(mark_closure(), beg_index, end_index); -} - -inline void G1FullGCMarker::follow_object(oop obj) { - assert(_bitmap->is_marked(obj), "should be marked"); - if (obj->is_objArray()) { - // Handle object arrays explicitly to allow them to - // be split into chunks if needed. - follow_array((objArrayOop)obj); +inline void G1FullGCMarker::dispatch_task(const ScannerTask& task, bool stolen) { + if (task.is_partial_array_state()) { + assert(_bitmap->is_marked(task.to_partial_array_state()->source()), "should be marked"); + process_partial_array(task.to_partial_array_state(), stolen); } else { - obj->oop_iterate(mark_closure()); + oop obj = task.to_oop(); + assert(_bitmap->is_marked(obj), "should be marked"); + if (obj->is_objArray()) { + // Handle object arrays explicitly to allow them to + // be split into chunks if needed. + start_partial_array_processing((objArrayOop)obj); + } else { + obj->oop_iterate(mark_closure()); + } } } inline void G1FullGCMarker::publish_and_drain_oop_tasks() { - oop obj; - while (_oop_stack.pop_overflow(obj)) { - if (!_oop_stack.try_push_to_taskqueue(obj)) { - assert(_bitmap->is_marked(obj), "must be marked"); - follow_object(obj); + ScannerTask task; + while (_task_queue.pop_overflow(task)) { + if (!_task_queue.try_push_to_taskqueue(task)) { + dispatch_task(task, false); } } - while (_oop_stack.pop_local(obj)) { - assert(_bitmap->is_marked(obj), "must be marked"); - follow_object(obj); + while (_task_queue.pop_local(task)) { + dispatch_task(task, false); } } -inline bool G1FullGCMarker::publish_or_pop_objarray_tasks(ObjArrayTask& task) { - // It is desirable to move as much as possible work from the overflow queue to - // the shared queue as quickly as possible. - while (_objarray_stack.pop_overflow(task)) { - if (!_objarray_stack.try_push_to_taskqueue(task)) { - return true; - } - } - return false; -} - -void G1FullGCMarker::follow_marking_stacks() { +void G1FullGCMarker::process_marking_stacks() { do { - // First, drain regular oop stack. publish_and_drain_oop_tasks(); - - // Then process ObjArrays one at a time to avoid marking stack bloat. - ObjArrayTask task; - if (publish_or_pop_objarray_tasks(task) || - _objarray_stack.pop_local(task)) { - follow_array_chunk(objArrayOop(task.obj()), task.index()); - } - } while (!is_empty()); + } while (!is_task_queue_empty()); } #endif // SHARE_GC_G1_G1FULLGCMARKER_INLINE_HPP diff --git a/src/hotspot/share/gc/g1/g1FullGCOopClosures.cpp b/src/hotspot/share/gc/g1/g1FullGCOopClosures.cpp index d9cf64a3655..273508ea9e3 100644 --- a/src/hotspot/share/gc/g1/g1FullGCOopClosures.cpp +++ b/src/hotspot/share/gc/g1/g1FullGCOopClosures.cpp @@ -35,7 +35,7 @@ G1IsAliveClosure::G1IsAliveClosure(G1FullCollector* collector) : G1IsAliveClosure(collector, collector->mark_bitmap()) { } -void G1FollowStackClosure::do_void() { _marker->follow_marking_stacks(); } +void G1MarkStackClosure::do_void() { _marker->process_marking_stacks(); } void G1FullKeepAliveClosure::do_oop(oop* p) { do_oop_work(p); } void G1FullKeepAliveClosure::do_oop(narrowOop* p) { do_oop_work(p); } diff --git a/src/hotspot/share/gc/g1/g1FullGCOopClosures.hpp b/src/hotspot/share/gc/g1/g1FullGCOopClosures.hpp index 388f8032de4..08ed5f982e1 100644 --- a/src/hotspot/share/gc/g1/g1FullGCOopClosures.hpp +++ b/src/hotspot/share/gc/g1/g1FullGCOopClosures.hpp @@ -86,11 +86,11 @@ public: virtual ReferenceIterationMode reference_iteration_mode() { return DO_FIELDS; } }; -class G1FollowStackClosure: public VoidClosure { +class G1MarkStackClosure: public VoidClosure { G1FullGCMarker* _marker; public: - G1FollowStackClosure(G1FullGCMarker* marker) : _marker(marker) {} + G1MarkStackClosure(G1FullGCMarker* marker) : _marker(marker) {} virtual void do_void(); }; diff --git a/src/hotspot/share/gc/g1/g1HeapRegionRemSet.cpp b/src/hotspot/share/gc/g1/g1HeapRegionRemSet.cpp index fae73a2c6bf..13c7a6a8d3e 100644 --- a/src/hotspot/share/gc/g1/g1HeapRegionRemSet.cpp +++ b/src/hotspot/share/gc/g1/g1HeapRegionRemSet.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, 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,7 +31,6 @@ #include "memory/allocation.hpp" #include "memory/padded.inline.hpp" #include "oops/oop.inline.hpp" -#include "runtime/atomicAccess.hpp" #include "runtime/globals_extension.hpp" #include "runtime/java.hpp" #include "runtime/mutexLocker.hpp" diff --git a/src/hotspot/share/gc/g1/g1HeapRegionRemSet.hpp b/src/hotspot/share/gc/g1/g1HeapRegionRemSet.hpp index 0f78e0d6271..950098c706e 100644 --- a/src/hotspot/share/gc/g1/g1HeapRegionRemSet.hpp +++ b/src/hotspot/share/gc/g1/g1HeapRegionRemSet.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, 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,7 +30,6 @@ #include "gc/g1/g1CodeRootSet.hpp" #include "gc/g1/g1CollectionSetCandidates.hpp" #include "gc/g1/g1FromCardCache.hpp" -#include "runtime/atomicAccess.hpp" #include "runtime/mutexLocker.hpp" #include "runtime/safepoint.hpp" #include "utilities/bitMap.hpp" @@ -123,9 +122,6 @@ public: static void initialize(MemRegion reserved); - // Coarsening statistics since VM start. - static G1CardSetCoarsenStats coarsen_stats() { return G1CardSet::coarsen_stats(); } - inline uintptr_t to_card(OopOrNarrowOopStar from) const; private: diff --git a/src/hotspot/share/gc/g1/g1HeapRegionRemSet.inline.hpp b/src/hotspot/share/gc/g1/g1HeapRegionRemSet.inline.hpp index fbd529cb1d3..f621b1318c1 100644 --- a/src/hotspot/share/gc/g1/g1HeapRegionRemSet.inline.hpp +++ b/src/hotspot/share/gc/g1/g1HeapRegionRemSet.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, 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,7 +30,6 @@ #include "gc/g1/g1CardSet.inline.hpp" #include "gc/g1/g1CollectedHeap.inline.hpp" #include "gc/g1/g1HeapRegion.inline.hpp" -#include "runtime/atomicAccess.hpp" #include "utilities/bitMap.inline.hpp" void G1HeapRegionRemSet::set_state_untracked() { diff --git a/src/hotspot/share/gc/g1/g1MonotonicArena.cpp b/src/hotspot/share/gc/g1/g1MonotonicArena.cpp index a9c6462680f..3f97870a67f 100644 --- a/src/hotspot/share/gc/g1/g1MonotonicArena.cpp +++ b/src/hotspot/share/gc/g1/g1MonotonicArena.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2026, 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,7 +24,6 @@ #include "gc/g1/g1MonotonicArena.inline.hpp" #include "memory/allocation.hpp" -#include "runtime/atomicAccess.hpp" #include "runtime/vmOperations.hpp" #include "utilities/globalCounter.inline.hpp" @@ -61,13 +60,13 @@ void G1MonotonicArena::SegmentFreeList::bulk_add(Segment& first, size_t num, size_t mem_size) { _list.prepend(first, last); - AtomicAccess::add(&_num_segments, num, memory_order_relaxed); - AtomicAccess::add(&_mem_size, mem_size, memory_order_relaxed); + _num_segments.add_then_fetch(num, memory_order_relaxed); + _mem_size.add_then_fetch(mem_size, memory_order_relaxed); } void G1MonotonicArena::SegmentFreeList::print_on(outputStream* out, const char* prefix) { out->print_cr("%s: segments %zu size %zu", - prefix, AtomicAccess::load(&_num_segments), AtomicAccess::load(&_mem_size)); + prefix, _num_segments.load_relaxed(), _mem_size.load_relaxed()); } G1MonotonicArena::Segment* G1MonotonicArena::SegmentFreeList::get_all(size_t& num_segments, @@ -75,12 +74,12 @@ G1MonotonicArena::Segment* G1MonotonicArena::SegmentFreeList::get_all(size_t& nu GlobalCounter::CriticalSection cs(Thread::current()); Segment* result = _list.pop_all(); - num_segments = AtomicAccess::load(&_num_segments); - mem_size = AtomicAccess::load(&_mem_size); + num_segments = _num_segments.load_relaxed(); + mem_size = _mem_size.load_relaxed(); if (result != nullptr) { - AtomicAccess::sub(&_num_segments, num_segments, memory_order_relaxed); - AtomicAccess::sub(&_mem_size, mem_size, memory_order_relaxed); + _num_segments.sub_then_fetch(num_segments, memory_order_relaxed); + _mem_size.sub_then_fetch(mem_size, memory_order_relaxed); } return result; } @@ -96,8 +95,8 @@ void G1MonotonicArena::SegmentFreeList::free_all() { Segment::delete_segment(cur); } - AtomicAccess::sub(&_num_segments, num_freed, memory_order_relaxed); - AtomicAccess::sub(&_mem_size, mem_size_freed, memory_order_relaxed); + _num_segments.sub_then_fetch(num_freed, memory_order_relaxed); + _mem_size.sub_then_fetch(mem_size_freed, memory_order_relaxed); } G1MonotonicArena::Segment* G1MonotonicArena::new_segment(Segment* const prev) { @@ -115,7 +114,7 @@ G1MonotonicArena::Segment* G1MonotonicArena::new_segment(Segment* const prev) { } // Install it as current allocation segment. - Segment* old = AtomicAccess::cmpxchg(&_first, prev, next); + Segment* old = _first.compare_exchange(prev, next); if (old != prev) { // Somebody else installed the segment, use that one. Segment::delete_segment(next); @@ -126,9 +125,9 @@ G1MonotonicArena::Segment* G1MonotonicArena::new_segment(Segment* const prev) { _last = next; } // Successfully installed the segment into the list. - AtomicAccess::inc(&_num_segments, memory_order_relaxed); - AtomicAccess::add(&_mem_size, next->mem_size(), memory_order_relaxed); - AtomicAccess::add(&_num_total_slots, next->num_slots(), memory_order_relaxed); + _num_segments.add_then_fetch(1u, memory_order_relaxed); + _mem_size.add_then_fetch(next->mem_size(), memory_order_relaxed); + _num_total_slots.add_then_fetch(next->num_slots(), memory_order_relaxed); return next; } } @@ -155,7 +154,7 @@ uint G1MonotonicArena::slot_size() const { } void G1MonotonicArena::drop_all() { - Segment* cur = AtomicAccess::load_acquire(&_first); + Segment* cur = _first.load_acquire(); if (cur != nullptr) { assert(_last != nullptr, "If there is at least one segment, there must be a last one."); @@ -175,25 +174,25 @@ void G1MonotonicArena::drop_all() { cur = next; } #endif - assert(num_segments == _num_segments, "Segment count inconsistent %u %u", num_segments, _num_segments); - assert(mem_size == _mem_size, "Memory size inconsistent"); + assert(num_segments == _num_segments.load_relaxed(), "Segment count inconsistent %u %u", num_segments, _num_segments.load_relaxed()); + assert(mem_size == _mem_size.load_relaxed(), "Memory size inconsistent"); assert(last == _last, "Inconsistent last segment"); - _segment_free_list->bulk_add(*first, *_last, _num_segments, _mem_size); + _segment_free_list->bulk_add(*first, *_last, _num_segments.load_relaxed(), _mem_size.load_relaxed()); } - _first = nullptr; + _first.store_relaxed(nullptr); _last = nullptr; - _num_segments = 0; - _mem_size = 0; - _num_total_slots = 0; - _num_allocated_slots = 0; + _num_segments.store_relaxed(0); + _mem_size.store_relaxed(0); + _num_total_slots.store_relaxed(0); + _num_allocated_slots.store_relaxed(0); } void* G1MonotonicArena::allocate() { assert(slot_size() > 0, "instance size not set."); - Segment* cur = AtomicAccess::load_acquire(&_first); + Segment* cur = _first.load_acquire(); if (cur == nullptr) { cur = new_segment(cur); } @@ -201,7 +200,7 @@ void* G1MonotonicArena::allocate() { while (true) { void* slot = cur->allocate_slot(); if (slot != nullptr) { - AtomicAccess::inc(&_num_allocated_slots, memory_order_relaxed); + _num_allocated_slots.add_then_fetch(1u, memory_order_relaxed); guarantee(is_aligned(slot, _alloc_options->slot_alignment()), "result " PTR_FORMAT " not aligned at %u", p2i(slot), _alloc_options->slot_alignment()); return slot; @@ -213,7 +212,7 @@ void* G1MonotonicArena::allocate() { } uint G1MonotonicArena::num_segments() const { - return AtomicAccess::load(&_num_segments); + return _num_segments.load_relaxed(); } #ifdef ASSERT @@ -238,7 +237,7 @@ uint G1MonotonicArena::calculate_length() const { template void G1MonotonicArena::iterate_segments(SegmentClosure& closure) const { - Segment* cur = AtomicAccess::load_acquire(&_first); + Segment* cur = _first.load_acquire(); assert((cur != nullptr) == (_last != nullptr), "If there is at least one segment, there must be a last one"); diff --git a/src/hotspot/share/gc/g1/g1MonotonicArena.hpp b/src/hotspot/share/gc/g1/g1MonotonicArena.hpp index 211820c5254..d8e658b5a64 100644 --- a/src/hotspot/share/gc/g1/g1MonotonicArena.hpp +++ b/src/hotspot/share/gc/g1/g1MonotonicArena.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2021, 2022, Huawei Technologies Co., Ltd. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -28,6 +28,7 @@ #include "gc/shared/freeListAllocator.hpp" #include "nmt/memTag.hpp" +#include "runtime/atomic.hpp" #include "utilities/globalDefinitions.hpp" #include "utilities/lockFreeStack.hpp" @@ -65,27 +66,27 @@ private: // AllocOptions provides parameters for Segment sizing and expansion. const AllocOptions* _alloc_options; - Segment* volatile _first; // The (start of the) list of all segments. - Segment* _last; // The last segment of the list of all segments. - volatile uint _num_segments; // Number of assigned segments to this allocator. - volatile size_t _mem_size; // Memory used by all segments. + Atomic _first; // The (start of the) list of all segments. + Segment* _last; // The last segment of the list of all segments. + Atomic _num_segments; // Number of assigned segments to this allocator. + Atomic _mem_size; // Memory used by all segments. SegmentFreeList* _segment_free_list; // The global free segment list to preferentially // get new segments from. - volatile uint _num_total_slots; // Number of slots available in all segments (allocated + not yet used). - volatile uint _num_allocated_slots; // Number of total slots allocated ever (including free and pending). + Atomic _num_total_slots; // Number of slots available in all segments (allocated + not yet used). + Atomic _num_allocated_slots; // Number of total slots allocated ever (including free and pending). inline Segment* new_segment(Segment* const prev); DEBUG_ONLY(uint calculate_length() const;) public: - const Segment* first_segment() const { return AtomicAccess::load(&_first); } + const Segment* first_segment() const { return _first.load_relaxed(); } - uint num_total_slots() const { return AtomicAccess::load(&_num_total_slots); } + uint num_total_slots() const { return _num_total_slots.load_relaxed(); } uint num_allocated_slots() const { - uint allocated = AtomicAccess::load(&_num_allocated_slots); + uint allocated = _num_allocated_slots.load_relaxed(); assert(calculate_length() == allocated, "Must be"); return allocated; } @@ -116,11 +117,11 @@ static constexpr uint SegmentPayloadMaxAlignment = 8; class alignas(SegmentPayloadMaxAlignment) G1MonotonicArena::Segment { const uint _slot_size; const uint _num_slots; - Segment* volatile _next; + Atomic _next; // Index into the next free slot to allocate into. Full if equal (or larger) // to _num_slots (can be larger because we atomically increment this value and // check only afterwards if the allocation has been successful). - uint volatile _next_allocate; + Atomic _next_allocate; const MemTag _mem_tag; static size_t header_size() { return align_up(sizeof(Segment), SegmentPayloadMaxAlignment); } @@ -139,21 +140,21 @@ class alignas(SegmentPayloadMaxAlignment) G1MonotonicArena::Segment { Segment(uint slot_size, uint num_slots, Segment* next, MemTag mem_tag); ~Segment() = default; public: - Segment* volatile* next_addr() { return &_next; } + Atomic* next_addr() { return &_next; } void* allocate_slot(); uint num_slots() const { return _num_slots; } - Segment* next() const { return _next; } + Segment* next() const { return _next.load_relaxed(); } void set_next(Segment* next) { assert(next != this, " loop condition"); - _next = next; + _next.store_relaxed(next); } void reset(Segment* next) { - _next_allocate = 0; + _next_allocate.store_relaxed(0); assert(next != this, " loop condition"); set_next(next); memset(payload(0), 0, payload_size()); @@ -166,7 +167,7 @@ public: uint length() const { // _next_allocate might grow larger than _num_slots in multi-thread environments // due to races. - return MIN2(_next_allocate, _num_slots); + return MIN2(_next_allocate.load_relaxed(), _num_slots); } static size_t size_in_bytes(uint slot_size, uint num_slots) { @@ -176,7 +177,7 @@ public: static Segment* create_segment(uint slot_size, uint num_slots, Segment* next, MemTag mem_tag); static void delete_segment(Segment* segment); - bool is_full() const { return _next_allocate >= _num_slots; } + bool is_full() const { return _next_allocate.load_relaxed() >= _num_slots; } }; static_assert(alignof(G1MonotonicArena::Segment) >= SegmentPayloadMaxAlignment, "assert alignment of Segment (and indirectly its payload)"); @@ -186,15 +187,15 @@ static_assert(alignof(G1MonotonicArena::Segment) >= SegmentPayloadMaxAlignment, // performed by multiple threads concurrently. // Counts and memory usage are current on a best-effort basis if accessed concurrently. class G1MonotonicArena::SegmentFreeList { - static Segment* volatile* next_ptr(Segment& segment) { + static Atomic* next_ptr(Segment& segment) { return segment.next_addr(); } using SegmentStack = LockFreeStack; SegmentStack _list; - volatile size_t _num_segments; - volatile size_t _mem_size; + Atomic _num_segments; + Atomic _mem_size; public: SegmentFreeList() : _list(), _num_segments(0), _mem_size(0) { } @@ -210,8 +211,8 @@ public: void print_on(outputStream* out, const char* prefix = ""); - size_t num_segments() const { return AtomicAccess::load(&_num_segments); } - size_t mem_size() const { return AtomicAccess::load(&_mem_size); } + size_t num_segments() const { return _num_segments.load_relaxed(); } + size_t mem_size() const { return _mem_size.load_relaxed(); } }; // Configuration for G1MonotonicArena, e.g slot size, slot number of next Segment. diff --git a/src/hotspot/share/gc/g1/g1MonotonicArena.inline.hpp b/src/hotspot/share/gc/g1/g1MonotonicArena.inline.hpp index dd9ccae1849..cf1b35ccead 100644 --- a/src/hotspot/share/gc/g1/g1MonotonicArena.inline.hpp +++ b/src/hotspot/share/gc/g1/g1MonotonicArena.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2021, 2022, Huawei Technologies Co., Ltd. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -28,14 +28,13 @@ #include "gc/g1/g1MonotonicArena.hpp" -#include "runtime/atomicAccess.hpp" #include "utilities/globalCounter.inline.hpp" inline void* G1MonotonicArena::Segment::allocate_slot() { - if (_next_allocate >= _num_slots) { + if (_next_allocate.load_relaxed() >= _num_slots) { return nullptr; } - uint result = AtomicAccess::fetch_then_add(&_next_allocate, 1u, memory_order_relaxed); + uint result = _next_allocate.fetch_then_add(1u, memory_order_relaxed); if (result >= _num_slots) { return nullptr; } @@ -48,8 +47,8 @@ inline G1MonotonicArena::Segment* G1MonotonicArena::SegmentFreeList::get() { Segment* result = _list.pop(); if (result != nullptr) { - AtomicAccess::dec(&_num_segments, memory_order_relaxed); - AtomicAccess::sub(&_mem_size, result->mem_size(), memory_order_relaxed); + _num_segments.sub_then_fetch(1u, memory_order_relaxed); + _mem_size.sub_then_fetch(result->mem_size(), memory_order_relaxed); } return result; } diff --git a/src/hotspot/share/gc/g1/g1PageBasedVirtualSpace.cpp b/src/hotspot/share/gc/g1/g1PageBasedVirtualSpace.cpp index d7e0c6e394f..529ef62b44d 100644 --- a/src/hotspot/share/gc/g1/g1PageBasedVirtualSpace.cpp +++ b/src/hotspot/share/gc/g1/g1PageBasedVirtualSpace.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2026, 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,6 @@ #include "nmt/memTracker.hpp" #include "oops/markWord.hpp" #include "oops/oop.inline.hpp" -#include "runtime/atomicAccess.hpp" #include "runtime/os.hpp" #include "utilities/align.hpp" #include "utilities/bitMap.inline.hpp" diff --git a/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp b/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp index 80e5fd44fcd..75a8ef1a336 100644 --- a/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp +++ b/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2026, 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 @@ -42,7 +42,6 @@ #include "memory/allocation.inline.hpp" #include "oops/access.inline.hpp" #include "oops/oop.inline.hpp" -#include "runtime/atomicAccess.hpp" #include "runtime/mutexLocker.hpp" #include "runtime/prefetch.inline.hpp" #include "utilities/globalDefinitions.hpp" @@ -238,9 +237,9 @@ void G1ParScanThreadState::do_partial_array(PartialArrayState* state, bool stole G1HeapRegionAttr dest_attr = _g1h->region_attr(to_array); G1SkipCardMarkSetter x(&_scanner, dest_attr.is_new_survivor()); // Process claimed task. - to_array->oop_iterate_range(&_scanner, - checked_cast(claim._start), - checked_cast(claim._end)); + to_array->oop_iterate_elements_range(&_scanner, + checked_cast(claim._start), + checked_cast(claim._end)); } MAYBE_INLINE_EVACUATION @@ -260,7 +259,7 @@ void G1ParScanThreadState::start_partial_objarray(oop from_obj, // Process the initial chunk. No need to process the type in the // klass, as it will already be handled by processing the built-in // module. - to_array->oop_iterate_range(&_scanner, 0, checked_cast(initial_chunk_size)); + to_array->oop_iterate_elements_range(&_scanner, 0, checked_cast(initial_chunk_size)); } MAYBE_INLINE_EVACUATION diff --git a/src/hotspot/share/gc/g1/g1ParallelCleaning.cpp b/src/hotspot/share/gc/g1/g1ParallelCleaning.cpp index 8d5e2a3239c..e3eabff5a50 100644 --- a/src/hotspot/share/gc/g1/g1ParallelCleaning.cpp +++ b/src/hotspot/share/gc/g1/g1ParallelCleaning.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, 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,7 +24,6 @@ #include "gc/g1/g1ParallelCleaning.hpp" -#include "runtime/atomicAccess.hpp" #if INCLUDE_JVMCI #include "jvmci/jvmci.hpp" #endif @@ -35,11 +34,11 @@ JVMCICleaningTask::JVMCICleaningTask() : } bool JVMCICleaningTask::claim_cleaning_task() { - if (AtomicAccess::load(&_cleaning_claimed)) { + if (_cleaning_claimed.load_relaxed()) { return false; } - return !AtomicAccess::cmpxchg(&_cleaning_claimed, false, true); + return _cleaning_claimed.compare_set(false, true); } void JVMCICleaningTask::work(bool unloading_occurred) { diff --git a/src/hotspot/share/gc/g1/g1ParallelCleaning.hpp b/src/hotspot/share/gc/g1/g1ParallelCleaning.hpp index d8725cb110d..815b0883e16 100644 --- a/src/hotspot/share/gc/g1/g1ParallelCleaning.hpp +++ b/src/hotspot/share/gc/g1/g1ParallelCleaning.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, 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,10 +26,13 @@ #define SHARE_GC_G1_G1PARALLELCLEANING_HPP #include "gc/shared/parallelCleaning.hpp" +#if INCLUDE_JVMCI +#include "runtime/atomic.hpp" +#endif #if INCLUDE_JVMCI class JVMCICleaningTask : public StackObj { - volatile bool _cleaning_claimed; + Atomic _cleaning_claimed; public: JVMCICleaningTask(); diff --git a/src/hotspot/share/gc/g1/g1Policy.cpp b/src/hotspot/share/gc/g1/g1Policy.cpp index 6eef6cbfa87..1d0b29c303e 100644 --- a/src/hotspot/share/gc/g1/g1Policy.cpp +++ b/src/hotspot/share/gc/g1/g1Policy.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, 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 @@ -32,7 +32,7 @@ #include "gc/g1/g1ConcurrentMark.hpp" #include "gc/g1/g1ConcurrentMarkThread.inline.hpp" #include "gc/g1/g1ConcurrentRefine.hpp" -#include "gc/g1/g1ConcurrentRefineStats.hpp" +#include "gc/g1/g1ConcurrentRefineStats.inline.hpp" #include "gc/g1/g1GCPhaseTimes.hpp" #include "gc/g1/g1HeapRegion.inline.hpp" #include "gc/g1/g1HeapRegionRemSet.inline.hpp" @@ -203,8 +203,8 @@ void G1Policy::update_young_length_bounds(size_t pending_cards, size_t card_rs_l // allocation. // That is "fine" - at most this will schedule a GC (hopefully only a little) too // early or too late. - AtomicAccess::store(&_young_list_desired_length, new_young_list_desired_length); - AtomicAccess::store(&_young_list_target_length, new_young_list_target_length); + _young_list_desired_length.store_relaxed(new_young_list_desired_length); + _young_list_target_length.store_relaxed(new_young_list_target_length); } // Calculates desired young gen length. It is calculated from: @@ -943,7 +943,7 @@ void G1Policy::record_young_collection_end(bool concurrent_operation_is_full_mar phase_times()->sum_thread_work_items(G1GCPhaseTimes::MergePSS, G1GCPhaseTimes::MergePSSToYoungGenCards)); } - record_pause(this_pause, start_time_sec, end_time_sec, allocation_failure); + record_pause(this_pause, start_time_sec, end_time_sec); if (G1GCPauseTypeHelper::is_last_young_pause(this_pause)) { assert(!G1GCPauseTypeHelper::is_concurrent_start_pause(this_pause), @@ -1389,16 +1389,13 @@ void G1Policy::update_gc_pause_time_ratios(G1GCPauseType gc_type, double start_t void G1Policy::record_pause(G1GCPauseType gc_type, double start, - double end, - bool allocation_failure) { + double end) { // Manage the MMU tracker. For some reason it ignores Full GCs. if (gc_type != G1GCPauseType::FullGC) { _mmu_tracker->add_pause(start, end); } - if (!allocation_failure) { - update_gc_pause_time_ratios(gc_type, start, end); - } + update_gc_pause_time_ratios(gc_type, start, end); update_time_to_mixed_tracking(gc_type, start, end); diff --git a/src/hotspot/share/gc/g1/g1Policy.hpp b/src/hotspot/share/gc/g1/g1Policy.hpp index 72fdc6deb5b..9513c79869e 100644 --- a/src/hotspot/share/gc/g1/g1Policy.hpp +++ b/src/hotspot/share/gc/g1/g1Policy.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2026, 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 @@ -35,7 +35,7 @@ #include "gc/g1/g1RemSetTrackingPolicy.hpp" #include "gc/g1/g1YoungGenSizer.hpp" #include "gc/shared/gcCause.hpp" -#include "runtime/atomicAccess.hpp" +#include "runtime/atomic.hpp" #include "utilities/pair.hpp" #include "utilities/ticks.hpp" @@ -81,12 +81,9 @@ class G1Policy: public CHeapObj { // Desired young gen length without taking actually available free regions into // account. - volatile uint _young_list_desired_length; + Atomic _young_list_desired_length; // Actual target length given available free memory. - volatile uint _young_list_target_length; - // The max number of regions we can extend the eden by while the GC - // locker is active. This should be >= _young_list_target_length; - volatile uint _young_list_max_length; + Atomic _young_list_target_length; // The survivor rate groups below must be initialized after the predictor because they // indirectly use it through the "this" object passed to their constructor. @@ -275,8 +272,7 @@ private: // Record the given STW pause with the given start and end times (in s). void record_pause(G1GCPauseType gc_type, double start, - double end, - bool allocation_failure = false); + double end); void update_gc_pause_time_ratios(G1GCPauseType gc_type, double start_sec, double end_sec); @@ -363,8 +359,8 @@ public: // This must be called at the very beginning of an evacuation pause. void decide_on_concurrent_start_pause(); - uint young_list_desired_length() const { return AtomicAccess::load(&_young_list_desired_length); } - uint young_list_target_length() const { return AtomicAccess::load(&_young_list_target_length); } + uint young_list_desired_length() const { return _young_list_desired_length.load_relaxed(); } + uint young_list_target_length() const { return _young_list_target_length.load_relaxed(); } bool should_allocate_mutator_region() const; bool should_expand_on_mutator_allocation() const; diff --git a/src/hotspot/share/gc/g1/g1RegionMarkStatsCache.hpp b/src/hotspot/share/gc/g1/g1RegionMarkStatsCache.hpp index 3c1a6ed4667..4dcdd33846e 100644 --- a/src/hotspot/share/gc/g1/g1RegionMarkStatsCache.hpp +++ b/src/hotspot/share/gc/g1/g1RegionMarkStatsCache.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, 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,6 +27,7 @@ #include "memory/allocation.hpp" #include "oops/oop.hpp" +#include "runtime/atomic.hpp" #include "utilities/debug.hpp" #include "utilities/globalDefinitions.hpp" #include "utilities/pair.hpp" @@ -40,20 +41,23 @@ // * the number of incoming references found during marking. This is an approximate // value because we do not mark through all objects. struct G1RegionMarkStats { - size_t _live_words; - size_t _incoming_refs; + Atomic _live_words; + Atomic _incoming_refs; // Clear all members. void clear() { - _live_words = 0; - _incoming_refs = 0; + _live_words.store_relaxed(0); + _incoming_refs.store_relaxed(0); } // Clear all members after a marking overflow. Only needs to clear the number of // incoming references as all objects will be rescanned, while the live words are // gathered whenever a thread can mark an object, which is synchronized. void clear_during_overflow() { - _incoming_refs = 0; + _incoming_refs.store_relaxed(0); } + + size_t live_words() const { return _live_words.load_relaxed(); } + size_t incoming_refs() const { return _incoming_refs.load_relaxed(); } }; // Per-marking thread cache for the region mark statistics. @@ -112,12 +116,16 @@ public: void add_live_words(oop obj); void add_live_words(uint region_idx, size_t live_words) { G1RegionMarkStatsCacheEntry* const cur = find_for_add(region_idx); - cur->_stats._live_words += live_words; + // This method is only ever called single-threaded, so we do not need atomic + // update here. + cur->_stats._live_words.store_relaxed(cur->_stats.live_words() + live_words); } void inc_incoming_refs(uint region_idx) { G1RegionMarkStatsCacheEntry* const cur = find_for_add(region_idx); - cur->_stats._incoming_refs++; + // This method is only ever called single-threaded, so we do not need atomic + // update here. + cur->_stats._incoming_refs.store_relaxed(cur->_stats.incoming_refs() + 1u); } void reset(uint region_idx) { diff --git a/src/hotspot/share/gc/g1/g1RegionMarkStatsCache.inline.hpp b/src/hotspot/share/gc/g1/g1RegionMarkStatsCache.inline.hpp index 6b0ebb34e0d..71cd33e71ae 100644 --- a/src/hotspot/share/gc/g1/g1RegionMarkStatsCache.inline.hpp +++ b/src/hotspot/share/gc/g1/g1RegionMarkStatsCache.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, 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,8 +27,6 @@ #include "gc/g1/g1RegionMarkStatsCache.hpp" -#include "runtime/atomicAccess.hpp" - inline G1RegionMarkStatsCache::G1RegionMarkStatsCacheEntry* G1RegionMarkStatsCache::find_for_add(uint region_idx) { uint const cache_idx = hash(region_idx); @@ -46,12 +44,12 @@ inline G1RegionMarkStatsCache::G1RegionMarkStatsCacheEntry* G1RegionMarkStatsCac inline void G1RegionMarkStatsCache::evict(uint idx) { G1RegionMarkStatsCacheEntry* cur = &_cache[idx]; - if (cur->_stats._live_words != 0) { - AtomicAccess::add(&_target[cur->_region_idx]._live_words, cur->_stats._live_words); + if (cur->_stats.live_words() != 0) { + _target[cur->_region_idx]._live_words.add_then_fetch(cur->_stats.live_words()); } - if (cur->_stats._incoming_refs != 0) { - AtomicAccess::add(&_target[cur->_region_idx]._incoming_refs, cur->_stats._incoming_refs); + if (cur->_stats.incoming_refs() != 0) { + _target[cur->_region_idx]._incoming_refs.add_then_fetch(cur->_stats.incoming_refs()); } cur->clear(); diff --git a/src/hotspot/share/gc/g1/g1RemSet.cpp b/src/hotspot/share/gc/g1/g1RemSet.cpp index c7724de280f..0c9a0fad8f2 100644 --- a/src/hotspot/share/gc/g1/g1RemSet.cpp +++ b/src/hotspot/share/gc/g1/g1RemSet.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, 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 @@ -50,7 +50,7 @@ #include "memory/resourceArea.hpp" #include "oops/access.inline.hpp" #include "oops/oop.inline.hpp" -#include "runtime/atomicAccess.hpp" +#include "runtime/atomic.hpp" #include "runtime/os.hpp" #include "utilities/align.hpp" #include "utilities/globalDefinitions.hpp" @@ -107,46 +107,48 @@ class G1RemSetScanState : public CHeapObj { // Set of (unique) regions that can be added to concurrently. class G1DirtyRegions : public CHeapObj { uint* _buffer; - uint _cur_idx; + Atomic _cur_idx; size_t _max_reserved_regions; - bool* _contains; + Atomic* _contains; public: G1DirtyRegions(size_t max_reserved_regions) : _buffer(NEW_C_HEAP_ARRAY(uint, max_reserved_regions, mtGC)), _cur_idx(0), _max_reserved_regions(max_reserved_regions), - _contains(NEW_C_HEAP_ARRAY(bool, max_reserved_regions, mtGC)) { + _contains(NEW_C_HEAP_ARRAY(Atomic, max_reserved_regions, mtGC)) { reset(); } ~G1DirtyRegions() { FREE_C_HEAP_ARRAY(uint, _buffer); - FREE_C_HEAP_ARRAY(bool, _contains); + FREE_C_HEAP_ARRAY(Atomic, _contains); } void reset() { - _cur_idx = 0; - ::memset(_contains, false, _max_reserved_regions * sizeof(bool)); + _cur_idx.store_relaxed(0); + for (uint i = 0; i < _max_reserved_regions; i++) { + _contains[i].store_relaxed(false); + } } - uint size() const { return _cur_idx; } + uint size() const { return _cur_idx.load_relaxed(); } uint at(uint idx) const { - assert(idx < _cur_idx, "Index %u beyond valid regions", idx); + assert(idx < size(), "Index %u beyond valid regions", idx); return _buffer[idx]; } void add_dirty_region(uint region) { - if (_contains[region]) { + if (_contains[region].load_relaxed()) { return; } - bool marked_as_dirty = AtomicAccess::cmpxchg(&_contains[region], false, true) == false; + bool marked_as_dirty = _contains[region].compare_set(false, true); if (marked_as_dirty) { - uint allocated = AtomicAccess::fetch_then_add(&_cur_idx, 1u); + uint allocated = _cur_idx.fetch_then_add(1u); _buffer[allocated] = region; } } @@ -155,9 +157,11 @@ class G1RemSetScanState : public CHeapObj { void merge(const G1DirtyRegions* other) { for (uint i = 0; i < other->size(); i++) { uint region = other->at(i); - if (!_contains[region]) { - _buffer[_cur_idx++] = region; - _contains[region] = true; + if (!_contains[region].load_relaxed()) { + uint cur = _cur_idx.load_relaxed(); + _buffer[cur] = region; + _cur_idx.store_relaxed(cur + 1); + _contains[region].store_relaxed(true); } } } @@ -173,7 +177,7 @@ class G1RemSetScanState : public CHeapObj { class G1ClearCardTableTask : public G1AbstractSubTask { G1CollectedHeap* _g1h; G1DirtyRegions* _regions; - uint volatile _cur_dirty_regions; + Atomic _cur_dirty_regions; G1RemSetScanState* _scan_state; @@ -210,8 +214,9 @@ class G1ClearCardTableTask : public G1AbstractSubTask { void do_work(uint worker_id) override { const uint num_regions_per_worker = num_cards_per_worker / (uint)G1HeapRegion::CardsPerRegion; - while (_cur_dirty_regions < _regions->size()) { - uint next = AtomicAccess::fetch_then_add(&_cur_dirty_regions, num_regions_per_worker); + uint cur = _cur_dirty_regions.load_relaxed(); + while (cur < _regions->size()) { + uint next = _cur_dirty_regions.fetch_then_add(num_regions_per_worker); uint max = MIN2(next + num_regions_per_worker, _regions->size()); for (uint i = next; i < max; i++) { @@ -226,6 +231,7 @@ class G1ClearCardTableTask : public G1AbstractSubTask { // old regions use it for old->collection set candidates, so they should not be cleared // either. } + cur = max; } } }; @@ -1115,7 +1121,7 @@ class G1MergeHeapRootsTask : public WorkerTask { bool _initial_evacuation; - volatile bool _fast_reclaim_handled; + Atomic _fast_reclaim_handled; public: G1MergeHeapRootsTask(G1RemSetScanState* scan_state, uint num_workers, bool initial_evacuation) : @@ -1143,8 +1149,8 @@ public: // 1. eager-reclaim candidates if (_initial_evacuation && g1h->has_humongous_reclaim_candidates() && - !_fast_reclaim_handled && - !AtomicAccess::cmpxchg(&_fast_reclaim_handled, false, true)) { + !_fast_reclaim_handled.load_relaxed() && + _fast_reclaim_handled.compare_set(false, true)) { G1GCParPhaseTimesTracker subphase_x(p, G1GCPhaseTimes::MergeER, worker_id); diff --git a/src/hotspot/share/gc/g1/g1YoungCollector.cpp b/src/hotspot/share/gc/g1/g1YoungCollector.cpp index fa5c617f1db..36cc44a8b7c 100644 --- a/src/hotspot/share/gc/g1/g1YoungCollector.cpp +++ b/src/hotspot/share/gc/g1/g1YoungCollector.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, 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 @@ -58,6 +58,7 @@ #include "gc/shared/workerThread.hpp" #include "jfr/jfrEvents.hpp" #include "memory/resourceArea.hpp" +#include "runtime/atomic.hpp" #include "runtime/threads.hpp" #include "utilities/ticks.hpp" @@ -459,8 +460,8 @@ class G1PrepareEvacuationTask : public WorkerTask { G1CollectedHeap* _g1h; G1HeapRegionClaimer _claimer; - volatile uint _humongous_total; - volatile uint _humongous_candidates; + Atomic _humongous_total; + Atomic _humongous_candidates; G1MonotonicArenaMemoryStats _all_card_set_stats; @@ -481,19 +482,19 @@ public: } void add_humongous_candidates(uint candidates) { - AtomicAccess::add(&_humongous_candidates, candidates); + _humongous_candidates.add_then_fetch(candidates); } void add_humongous_total(uint total) { - AtomicAccess::add(&_humongous_total, total); + _humongous_total.add_then_fetch(total); } uint humongous_candidates() { - return _humongous_candidates; + return _humongous_candidates.load_relaxed(); } uint humongous_total() { - return _humongous_total; + return _humongous_total.load_relaxed(); } const G1MonotonicArenaMemoryStats all_card_set_stats() const { @@ -698,7 +699,7 @@ protected: virtual void evacuate_live_objects(G1ParScanThreadState* pss, uint worker_id) = 0; private: - volatile bool _pinned_regions_recorded; + Atomic _pinned_regions_recorded; public: G1EvacuateRegionsBaseTask(const char* name, @@ -722,7 +723,7 @@ public: G1ParScanThreadState* pss = _per_thread_states->state_for_worker(worker_id); pss->set_ref_discoverer(_g1h->ref_processor_stw()); - if (!AtomicAccess::cmpxchg(&_pinned_regions_recorded, false, true)) { + if (_pinned_regions_recorded.compare_set(false, true)) { record_pinned_regions(pss, worker_id); } scan_roots(pss, worker_id); diff --git a/src/hotspot/share/gc/g1/g1YoungGCPostEvacuateTasks.cpp b/src/hotspot/share/gc/g1/g1YoungGCPostEvacuateTasks.cpp index d875d9c8b8a..46d12df575c 100644 --- a/src/hotspot/share/gc/g1/g1YoungGCPostEvacuateTasks.cpp +++ b/src/hotspot/share/gc/g1/g1YoungGCPostEvacuateTasks.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, 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 @@ -46,7 +46,8 @@ #include "oops/access.inline.hpp" #include "oops/compressedOops.inline.hpp" #include "oops/oop.inline.hpp" -#include "runtime/prefetch.hpp" +#include "runtime/atomic.hpp" +#include "runtime/prefetch.inline.hpp" #include "runtime/threads.hpp" #include "runtime/threadSMR.hpp" #include "utilities/bitMap.inline.hpp" @@ -759,7 +760,7 @@ class G1PostEvacuateCollectionSetCleanupTask2::FreeCollectionSetTask : public G1 const size_t* _surviving_young_words; uint _active_workers; G1EvacFailureRegions* _evac_failure_regions; - volatile uint _num_retained_regions; + Atomic _num_retained_regions; FreeCSetStats* worker_stats(uint worker) { return &_worker_stats[worker]; @@ -794,7 +795,7 @@ public: virtual ~FreeCollectionSetTask() { Ticks serial_time = Ticks::now(); - bool has_new_retained_regions = AtomicAccess::load(&_num_retained_regions) != 0; + bool has_new_retained_regions = _num_retained_regions.load_relaxed() != 0; if (has_new_retained_regions) { G1CollectionSetCandidates* candidates = _g1h->collection_set()->candidates(); candidates->sort_by_efficiency(); @@ -829,7 +830,7 @@ public: // Report per-region type timings. cl.report_timing(); - AtomicAccess::add(&_num_retained_regions, cl.num_retained_regions(), memory_order_relaxed); + _num_retained_regions.add_then_fetch(cl.num_retained_regions(), memory_order_relaxed); } }; diff --git a/src/hotspot/share/gc/g1/g1YoungGCPreEvacuateTasks.cpp b/src/hotspot/share/gc/g1/g1YoungGCPreEvacuateTasks.cpp index b11213ddeb3..c0870b7a726 100644 --- a/src/hotspot/share/gc/g1/g1YoungGCPreEvacuateTasks.cpp +++ b/src/hotspot/share/gc/g1/g1YoungGCPreEvacuateTasks.cpp @@ -23,7 +23,6 @@ */ #include "gc/g1/g1CollectedHeap.inline.hpp" -#include "gc/g1/g1ConcurrentRefineStats.hpp" #include "gc/g1/g1RegionPinCache.inline.hpp" #include "gc/g1/g1ThreadLocalData.hpp" #include "gc/g1/g1YoungGCPreEvacuateTasks.hpp" diff --git a/src/hotspot/share/gc/parallel/psParallelCompact.cpp b/src/hotspot/share/gc/parallel/psParallelCompact.cpp index b1b07b4bc5c..bab72296d4c 100644 --- a/src/hotspot/share/gc/parallel/psParallelCompact.cpp +++ b/src/hotspot/share/gc/parallel/psParallelCompact.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, 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 @@ -44,6 +44,7 @@ #include "gc/parallel/psStringDedup.hpp" #include "gc/parallel/psYoungGen.hpp" #include "gc/shared/classUnloadingContext.hpp" +#include "gc/shared/collectedHeap.inline.hpp" #include "gc/shared/fullGCForwarding.inline.hpp" #include "gc/shared/gcCause.hpp" #include "gc/shared/gcHeapSummary.hpp" @@ -932,6 +933,17 @@ void PSParallelCompact::summary_phase(bool should_do_max_compaction) } } +void PSParallelCompact::report_object_count_after_gc() { + GCTraceTime(Debug, gc, phases) tm("Report Object Count", &_gc_timer); + // The heap is compacted, all objects are iterable. However there may be + // filler objects in the heap which we should ignore. + class SkipFillerObjectClosure : public BoolObjectClosure { + public: + bool do_object_b(oop obj) override { return !CollectedHeap::is_filler_object(obj); } + } cl; + _gc_tracer.report_object_count_after_gc(&cl, &ParallelScavengeHeap::heap()->workers()); +} + bool PSParallelCompact::invoke(bool clear_all_soft_refs, bool should_do_max_compaction) { assert(SafepointSynchronize::is_at_safepoint(), "should be at safepoint"); assert(Thread::current() == (Thread*)VMThread::vm_thread(), @@ -1027,6 +1039,8 @@ bool PSParallelCompact::invoke(bool clear_all_soft_refs, bool should_do_max_comp heap->print_heap_change(pre_gc_values); + report_object_count_after_gc(); + // Track memory usage and detect low memory MemoryService::track_memory_usage(); heap->update_counters(); @@ -1274,10 +1288,6 @@ void PSParallelCompact::marking_phase(ParallelOldTracer *gc_tracer) { } } - { - GCTraceTime(Debug, gc, phases) tm("Report Object Count", &_gc_timer); - _gc_tracer.report_object_count_after_gc(is_alive_closure(), &ParallelScavengeHeap::heap()->workers()); - } #if TASKQUEUE_STATS ParCompactionManager::print_and_reset_taskqueue_stats(); #endif @@ -1835,8 +1845,7 @@ void PSParallelCompact::verify_filler_in_dense_prefix() { oop obj = cast_to_oop(cur_addr); oopDesc::verify(obj); if (!mark_bitmap()->is_marked(cur_addr)) { - Klass* k = cast_to_oop(cur_addr)->klass(); - assert(k == Universe::fillerArrayKlass() || k == vmClasses::FillerObject_klass(), "inv"); + assert(CollectedHeap::is_filler_object(cast_to_oop(cur_addr)), "inv"); } cur_addr += obj->size(); } diff --git a/src/hotspot/share/gc/parallel/psParallelCompact.hpp b/src/hotspot/share/gc/parallel/psParallelCompact.hpp index 2297d720b35..4ac9395d727 100644 --- a/src/hotspot/share/gc/parallel/psParallelCompact.hpp +++ b/src/hotspot/share/gc/parallel/psParallelCompact.hpp @@ -749,6 +749,7 @@ private: // Move objects to new locations. static void compact(); + static void report_object_count_after_gc(); // Add available regions to the stack and draining tasks to the task queue. static void prepare_region_draining_tasks(uint parallel_gc_threads); diff --git a/src/hotspot/share/gc/serial/cardTableRS.cpp b/src/hotspot/share/gc/serial/cardTableRS.cpp index 80985424a62..a53ab066387 100644 --- a/src/hotspot/share/gc/serial/cardTableRS.cpp +++ b/src/hotspot/share/gc/serial/cardTableRS.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, 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,6 +28,7 @@ #include "gc/serial/serialHeap.inline.hpp" #include "gc/shared/space.hpp" #include "memory/iterator.inline.hpp" +#include "runtime/prefetch.inline.hpp" #include "utilities/align.hpp" void CardTableRS::scan_old_to_young_refs(TenuredGeneration* tg, HeapWord* saved_top) { diff --git a/src/hotspot/share/gc/serial/generation.hpp b/src/hotspot/share/gc/serial/generation.hpp index 8c9da3b42b7..ddfd4028a7d 100644 --- a/src/hotspot/share/gc/serial/generation.hpp +++ b/src/hotspot/share/gc/serial/generation.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, 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,6 @@ #include "memory/virtualspace.hpp" #include "runtime/mutex.hpp" #include "runtime/perfData.hpp" -#include "runtime/prefetch.inline.hpp" // A Generation models a heap area for similarly-aged objects. // It will contain one ore more spaces holding the actual objects. diff --git a/src/hotspot/share/gc/serial/serialFullGC.cpp b/src/hotspot/share/gc/serial/serialFullGC.cpp index 546084e38dd..0c8ca51fc99 100644 --- a/src/hotspot/share/gc/serial/serialFullGC.cpp +++ b/src/hotspot/share/gc/serial/serialFullGC.cpp @@ -412,7 +412,7 @@ void SerialFullGC::follow_array_chunk(objArrayOop array, int index) { const int stride = MIN2(len - beg_index, (int) ObjArrayMarkingStride); const int end_index = beg_index + stride; - array->oop_iterate_range(&mark_and_push_closure, beg_index, end_index); + array->oop_iterate_elements_range(&mark_and_push_closure, beg_index, end_index); if (end_index < len) { SerialFullGC::push_objarray(array, end_index); // Push the continuation. diff --git a/src/hotspot/share/gc/serial/serialHeap.cpp b/src/hotspot/share/gc/serial/serialHeap.cpp index 08d730bf877..8eafdfdcc82 100644 --- a/src/hotspot/share/gc/serial/serialHeap.cpp +++ b/src/hotspot/share/gc/serial/serialHeap.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, 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 @@ -70,6 +70,7 @@ #include "runtime/init.hpp" #include "runtime/java.hpp" #include "runtime/mutexLocker.hpp" +#include "runtime/prefetch.inline.hpp" #include "runtime/threads.hpp" #include "runtime/vmThread.hpp" #include "services/memoryManager.hpp" diff --git a/src/hotspot/share/gc/shared/barrierSetNMethod.cpp b/src/hotspot/share/gc/shared/barrierSetNMethod.cpp index cb5d6b5a886..ab94bae079a 100644 --- a/src/hotspot/share/gc/shared/barrierSetNMethod.cpp +++ b/src/hotspot/share/gc/shared/barrierSetNMethod.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, 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 @@ -33,6 +33,7 @@ #include "memory/universe.hpp" #include "oops/access.inline.hpp" #include "oops/method.inline.hpp" +#include "runtime/atomic.hpp" #include "runtime/frame.inline.hpp" #include "runtime/javaThread.hpp" #include "runtime/threads.hpp" @@ -196,8 +197,8 @@ int BarrierSetNMethod::nmethod_stub_entry_barrier(address* return_address_ptr) { // Diagnostic option to force deoptimization 1 in 10 times. It is otherwise // a very rare event. if (DeoptimizeNMethodBarriersALot && !nm->is_osr_method()) { - static volatile uint32_t counter=0; - if (AtomicAccess::add(&counter, 1u) % 10 == 0) { + static Atomic counter{0}; + if (counter.add_then_fetch(1u) % 10 == 0) { may_enter = false; } } diff --git a/src/hotspot/share/gc/shared/c1/cardTableBarrierSetC1.cpp b/src/hotspot/share/gc/shared/c1/cardTableBarrierSetC1.cpp index ebc1c1c7fb7..914358760aa 100644 --- a/src/hotspot/share/gc/shared/c1/cardTableBarrierSetC1.cpp +++ b/src/hotspot/share/gc/shared/c1/cardTableBarrierSetC1.cpp @@ -104,11 +104,8 @@ void CardTableBarrierSetC1::post_barrier(LIRAccess& access, LIR_Opr addr, LIR_Op return; } - BarrierSet* bs = BarrierSet::barrier_set(); - CardTableBarrierSet* ctbs = barrier_set_cast(bs); - CardTable* ct = ctbs->card_table(); - LIR_Const* card_table_base = new LIR_Const(ct->byte_map_base()); - SHENANDOAHGC_ONLY(assert(!UseShenandoahGC, "Shenandoah byte_map_base is not constant.");) + CardTableBarrierSet* ctbs = barrier_set_cast(BarrierSet::barrier_set()); + LIR_Const* card_table_base = new LIR_Const(ctbs->card_table_base_const()); if (addr->is_address()) { LIR_Address* address = addr->as_address_ptr(); diff --git a/src/hotspot/share/gc/shared/c2/barrierSetC2.cpp b/src/hotspot/share/gc/shared/c2/barrierSetC2.cpp index c4eefee5f65..53577bad1d8 100644 --- a/src/hotspot/share/gc/shared/c2/barrierSetC2.cpp +++ b/src/hotspot/share/gc/shared/c2/barrierSetC2.cpp @@ -771,7 +771,7 @@ Node* BarrierSetC2::obj_allocate(PhaseMacroExpand* macro, Node* mem, Node* toobi // this will require extensive changes to the loop optimization in order to // prevent a degradation of the optimization. // See comment in memnode.hpp, around line 227 in class LoadPNode. - Node* tlab_end = macro->make_load(toobig_false, mem, tlab_end_adr, 0, TypeRawPtr::BOTTOM, T_ADDRESS); + Node* tlab_end = macro->make_load_raw(toobig_false, mem, tlab_end_adr, 0, TypeRawPtr::BOTTOM, T_ADDRESS); // Load the TLAB top. Node* old_tlab_top = new LoadPNode(toobig_false, mem, tlab_top_adr, TypeRawPtr::BOTTOM, TypeRawPtr::BOTTOM, MemNode::unordered); diff --git a/src/hotspot/share/gc/shared/c2/cardTableBarrierSetC2.cpp b/src/hotspot/share/gc/shared/c2/cardTableBarrierSetC2.cpp index fada2672e9f..42af77ebdf4 100644 --- a/src/hotspot/share/gc/shared/c2/cardTableBarrierSetC2.cpp +++ b/src/hotspot/share/gc/shared/c2/cardTableBarrierSetC2.cpp @@ -116,7 +116,7 @@ Node* CardTableBarrierSetC2::atomic_xchg_at_resolved(C2AtomicParseAccess& access Node* CardTableBarrierSetC2::byte_map_base_node(GraphKit* kit) const { // Get base of card map - CardTable::CardValue* card_table_base = ci_card_table_address(); + CardTable::CardValue* card_table_base = ci_card_table_address_const(); if (card_table_base != nullptr) { return kit->makecon(TypeRawPtr::make((address)card_table_base)); } else { diff --git a/src/hotspot/share/gc/shared/cardTableBarrierSet.hpp b/src/hotspot/share/gc/shared/cardTableBarrierSet.hpp index faeb007c77d..c298bfcd0c2 100644 --- a/src/hotspot/share/gc/shared/cardTableBarrierSet.hpp +++ b/src/hotspot/share/gc/shared/cardTableBarrierSet.hpp @@ -27,6 +27,7 @@ #include "gc/shared/barrierSet.hpp" #include "gc/shared/cardTable.hpp" +#include "gc/shared/gc_globals.hpp" #include "memory/memRegion.hpp" #include "utilities/align.hpp" @@ -60,6 +61,10 @@ public: CardTableBarrierSet(CardTable* card_table); virtual ~CardTableBarrierSet(); + inline static CardTableBarrierSet* barrier_set() { + return barrier_set_cast(BarrierSet::barrier_set()); + } + template inline void write_ref_field_pre(T* addr) {} @@ -86,6 +91,10 @@ public: inline void write_ref_array(HeapWord* start, size_t count); CardTable* card_table() const { return _card_table; } + CardValue* card_table_base_const() const { + assert(UseSerialGC || UseParallelGC, "Only these GCs have constant card table base"); + return _card_table->byte_map_base(); + } virtual void on_slowpath_allocation_exit(JavaThread* thread, oop new_obj); diff --git a/src/hotspot/share/gc/shared/collectedHeap.hpp b/src/hotspot/share/gc/shared/collectedHeap.hpp index 6f335b1cdf4..363ccf321b2 100644 --- a/src/hotspot/share/gc/shared/collectedHeap.hpp +++ b/src/hotspot/share/gc/shared/collectedHeap.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, 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 @@ -309,6 +309,8 @@ protected: fill_with_object(start, pointer_delta(end, start), zap); } + inline static bool is_filler_object(oop obj); + virtual void fill_with_dummy_object(HeapWord* start, HeapWord* end, bool zap); static size_t min_dummy_object_size() { return oopDesc::header_size(); diff --git a/src/hotspot/share/gc/shared/collectedHeap.inline.hpp b/src/hotspot/share/gc/shared/collectedHeap.inline.hpp index c9d84f54449..194c1fe0bf2 100644 --- a/src/hotspot/share/gc/shared/collectedHeap.inline.hpp +++ b/src/hotspot/share/gc/shared/collectedHeap.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, 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,9 @@ #include "gc/shared/collectedHeap.hpp" +#include "classfile/vmClasses.hpp" #include "gc/shared/memAllocator.hpp" +#include "memory/universe.hpp" #include "oops/oop.inline.hpp" #include "utilities/align.hpp" @@ -50,4 +52,9 @@ inline void CollectedHeap::add_vmthread_cpu_time(jlong time) { _vmthread_cpu_time += time; } +inline bool CollectedHeap::is_filler_object(oop obj) { + Klass* k = obj->klass_without_asserts(); + return k == Universe::fillerArrayKlass() || k == vmClasses::FillerObject_klass(); +} + #endif // SHARE_GC_SHARED_COLLECTEDHEAP_INLINE_HPP diff --git a/src/hotspot/share/gc/shared/concurrentGCThread.cpp b/src/hotspot/share/gc/shared/concurrentGCThread.cpp index ac281f82f8a..ed6c1b4d283 100644 --- a/src/hotspot/share/gc/shared/concurrentGCThread.cpp +++ b/src/hotspot/share/gc/shared/concurrentGCThread.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, 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 @@ -23,7 +23,7 @@ */ #include "gc/shared/concurrentGCThread.hpp" -#include "runtime/atomicAccess.hpp" +#include "runtime/atomic.hpp" #include "runtime/init.hpp" #include "runtime/jniHandles.hpp" #include "runtime/mutexLocker.hpp" @@ -48,7 +48,7 @@ void ConcurrentGCThread::run() { // Signal thread has terminated MonitorLocker ml(Terminator_lock); - AtomicAccess::release_store(&_has_terminated, true); + _has_terminated.release_store(true); ml.notify_all(); } @@ -57,21 +57,21 @@ void ConcurrentGCThread::stop() { assert(!has_terminated(), "Invalid state"); // Signal thread to terminate - AtomicAccess::release_store_fence(&_should_terminate, true); + _should_terminate.release_store_fence(true); stop_service(); // Wait for thread to terminate MonitorLocker ml(Terminator_lock); - while (!_has_terminated) { + while (!_has_terminated.load_relaxed()) { ml.wait(); } } bool ConcurrentGCThread::should_terminate() const { - return AtomicAccess::load_acquire(&_should_terminate); + return _should_terminate.load_acquire(); } bool ConcurrentGCThread::has_terminated() const { - return AtomicAccess::load_acquire(&_has_terminated); + return _has_terminated.load_acquire(); } diff --git a/src/hotspot/share/gc/shared/concurrentGCThread.hpp b/src/hotspot/share/gc/shared/concurrentGCThread.hpp index 630abeaeb9f..0c764546045 100644 --- a/src/hotspot/share/gc/shared/concurrentGCThread.hpp +++ b/src/hotspot/share/gc/shared/concurrentGCThread.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, 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,14 +25,15 @@ #ifndef SHARE_GC_SHARED_CONCURRENTGCTHREAD_HPP #define SHARE_GC_SHARED_CONCURRENTGCTHREAD_HPP +#include "runtime/atomic.hpp" #include "runtime/javaThread.hpp" #include "runtime/nonJavaThread.hpp" #include "utilities/debug.hpp" class ConcurrentGCThread: public NamedThread { private: - volatile bool _should_terminate; - volatile bool _has_terminated; + Atomic _should_terminate; + Atomic _has_terminated; protected: void create_and_start(ThreadPriority prio = NearMaxPriority); diff --git a/src/hotspot/share/gc/shared/gcLocker.cpp b/src/hotspot/share/gc/shared/gcLocker.cpp index 01d17b1117d..898588e6b06 100644 --- a/src/hotspot/share/gc/shared/gcLocker.cpp +++ b/src/hotspot/share/gc/shared/gcLocker.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, 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 @@ #include "logging/log.hpp" #include "memory/resourceArea.hpp" #include "memory/universe.hpp" -#include "runtime/atomicAccess.hpp" +#include "runtime/atomic.hpp" #include "runtime/interfaceSupport.inline.hpp" #include "runtime/javaThread.inline.hpp" #include "runtime/safepoint.hpp" @@ -60,16 +60,13 @@ public: }; Monitor* GCLocker::_lock; -volatile bool GCLocker::_is_gc_request_pending; +Atomic GCLocker::_is_gc_request_pending{false}; -DEBUG_ONLY(uint64_t GCLocker::_verify_in_cr_count;) +DEBUG_ONLY(Atomic GCLocker::_verify_in_cr_count{0};) void GCLocker::initialize() { assert(JNICritical_lock != nullptr, "inv"); _lock = JNICritical_lock; - _is_gc_request_pending = false; - - DEBUG_ONLY(_verify_in_cr_count = 0;) } bool GCLocker::is_active() { @@ -84,11 +81,11 @@ bool GCLocker::is_active() { void GCLocker::block() { // _lock is held from the beginning of block() to the end of of unblock(). _lock->lock(); - assert(AtomicAccess::load(&_is_gc_request_pending) == false, "precondition"); + assert(_is_gc_request_pending.load_relaxed() == false, "precondition"); GCLockerTimingDebugLogger logger("Thread blocked to start GC."); - AtomicAccess::store(&_is_gc_request_pending, true); + _is_gc_request_pending.store_relaxed(true); // The _is_gc_request_pending and _jni_active_critical (inside // in_critical_atomic()) variables form a Dekker duality. On the GC side, the @@ -112,14 +109,14 @@ void GCLocker::block() { #ifdef ASSERT // Matching the storestore in GCLocker::exit. OrderAccess::loadload(); - assert(AtomicAccess::load(&_verify_in_cr_count) == 0, "inv"); + assert(_verify_in_cr_count.load_relaxed() == 0, "inv"); #endif } void GCLocker::unblock() { - assert(AtomicAccess::load(&_is_gc_request_pending) == true, "precondition"); + assert(_is_gc_request_pending.load_relaxed() == true, "precondition"); - AtomicAccess::store(&_is_gc_request_pending, false); + _is_gc_request_pending.store_relaxed(false); _lock->unlock(); } @@ -139,7 +136,7 @@ void GCLocker::enter_slow(JavaThread* current_thread) { // Same as fast path. OrderAccess::fence(); - if (!AtomicAccess::load(&_is_gc_request_pending)) { + if (!_is_gc_request_pending.load_relaxed()) { return; } diff --git a/src/hotspot/share/gc/shared/gcLocker.hpp b/src/hotspot/share/gc/shared/gcLocker.hpp index c0e28b66539..be3c0c3593e 100644 --- a/src/hotspot/share/gc/shared/gcLocker.hpp +++ b/src/hotspot/share/gc/shared/gcLocker.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, 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,6 +27,7 @@ #include "gc/shared/gcCause.hpp" #include "memory/allStatic.hpp" +#include "runtime/atomic.hpp" #include "runtime/mutex.hpp" // GCLocker provides synchronization between the garbage collector (GC) and @@ -43,11 +44,11 @@ class GCLocker: public AllStatic { static Monitor* _lock; - static volatile bool _is_gc_request_pending; + static Atomic _is_gc_request_pending; #ifdef ASSERT // Debug-only: to track the number of java threads in critical-region. - static uint64_t _verify_in_cr_count; + static Atomic _verify_in_cr_count; #endif static void enter_slow(JavaThread* current_thread); diff --git a/src/hotspot/share/gc/shared/gcLocker.inline.hpp b/src/hotspot/share/gc/shared/gcLocker.inline.hpp index 050b9570280..135bd9eab62 100644 --- a/src/hotspot/share/gc/shared/gcLocker.inline.hpp +++ b/src/hotspot/share/gc/shared/gcLocker.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, 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 @@ -38,13 +38,13 @@ void GCLocker::enter(JavaThread* current_thread) { // Matching the fence in GCLocker::block. OrderAccess::fence(); - if (AtomicAccess::load(&_is_gc_request_pending)) { + if (_is_gc_request_pending.load_relaxed()) { current_thread->exit_critical(); // slow-path enter_slow(current_thread); } - DEBUG_ONLY(AtomicAccess::add(&_verify_in_cr_count, (uint64_t)1);) + DEBUG_ONLY(_verify_in_cr_count.add_then_fetch(1u);) } else { current_thread->enter_critical(); } @@ -55,7 +55,7 @@ void GCLocker::exit(JavaThread* current_thread) { #ifdef ASSERT if (current_thread->in_last_critical()) { - AtomicAccess::add(&_verify_in_cr_count, (uint64_t)-1); + _verify_in_cr_count.sub_then_fetch(1u); // Matching the loadload in GCLocker::block. OrderAccess::storestore(); } diff --git a/src/hotspot/share/gc/shared/gc_globals.hpp b/src/hotspot/share/gc/shared/gc_globals.hpp index d08e95378f7..6aa1fcf066b 100644 --- a/src/hotspot/share/gc/shared/gc_globals.hpp +++ b/src/hotspot/share/gc/shared/gc_globals.hpp @@ -261,6 +261,7 @@ develop(uintx, ObjArrayMarkingStride, 2048, \ "Number of object array elements to push onto the marking stack " \ "before pushing a continuation entry") \ + range(1, INT_MAX/2) \ \ product_pd(bool, NeverActAsServerClassMachine, \ "(Deprecated) Never act like a server-class machine") \ diff --git a/src/hotspot/share/gc/shared/oopStorage.cpp b/src/hotspot/share/gc/shared/oopStorage.cpp index a1cc3ffa553..21e63f6fc32 100644 --- a/src/hotspot/share/gc/shared/oopStorage.cpp +++ b/src/hotspot/share/gc/shared/oopStorage.cpp @@ -700,7 +700,7 @@ void OopStorage::Block::release_entries(uintx releasing, OopStorage* owner) { // then someone else has made such a claim and the deferred update has not // yet been processed and will include our change, so we don't need to do // anything further. - if (_deferred_updates_next.compare_exchange(nullptr, this) == nullptr) { + if (_deferred_updates_next.compare_set(nullptr, this)) { // Successfully claimed. Push, with self-loop for end-of-list. Block* head = owner->_deferred_updates.load_relaxed(); while (true) { diff --git a/src/hotspot/share/gc/shared/oopStorageSetParState.inline.hpp b/src/hotspot/share/gc/shared/oopStorageSetParState.inline.hpp index 8e220e745e5..8341a3b20c4 100644 --- a/src/hotspot/share/gc/shared/oopStorageSetParState.inline.hpp +++ b/src/hotspot/share/gc/shared/oopStorageSetParState.inline.hpp @@ -31,7 +31,6 @@ #include "gc/shared/oopStorageSet.hpp" #include "memory/iterator.hpp" #include "oops/access.inline.hpp" -#include "runtime/atomicAccess.hpp" #include "utilities/debug.hpp" template diff --git a/src/hotspot/share/gc/shared/partialArrayState.cpp b/src/hotspot/share/gc/shared/partialArrayState.cpp index 6f714d48a35..aadbc46b7c1 100644 --- a/src/hotspot/share/gc/shared/partialArrayState.cpp +++ b/src/hotspot/share/gc/shared/partialArrayState.cpp @@ -28,7 +28,6 @@ #include "memory/arena.hpp" #include "nmt/memTag.hpp" #include "oops/oopsHierarchy.hpp" -#include "runtime/atomicAccess.hpp" #include "runtime/orderAccess.hpp" #include "utilities/debug.hpp" #include "utilities/globalDefinitions.hpp" diff --git a/src/hotspot/share/gc/shared/partialArrayTaskStepper.inline.hpp b/src/hotspot/share/gc/shared/partialArrayTaskStepper.inline.hpp index aaa86e2de16..6946f7c69ff 100644 --- a/src/hotspot/share/gc/shared/partialArrayTaskStepper.inline.hpp +++ b/src/hotspot/share/gc/shared/partialArrayTaskStepper.inline.hpp @@ -28,7 +28,6 @@ #include "gc/shared/partialArrayTaskStepper.hpp" #include "gc/shared/partialArrayState.hpp" -#include "runtime/atomicAccess.hpp" #include "utilities/checkedCast.hpp" #include "utilities/debug.hpp" diff --git a/src/hotspot/share/gc/shared/preservedMarks.cpp b/src/hotspot/share/gc/shared/preservedMarks.cpp index 1c9f1c82e6f..605b7afe072 100644 --- a/src/hotspot/share/gc/shared/preservedMarks.cpp +++ b/src/hotspot/share/gc/shared/preservedMarks.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2026, 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 @@ -29,7 +29,7 @@ #include "memory/allocation.inline.hpp" #include "memory/resourceArea.hpp" #include "oops/oop.inline.hpp" -#include "runtime/atomicAccess.hpp" +#include "runtime/atomic.hpp" #include "utilities/macros.hpp" void PreservedMarks::restore() { @@ -55,15 +55,6 @@ void PreservedMarks::adjust_during_full_gc() { } } -void PreservedMarks::restore_and_increment(volatile size_t* const total_size_addr) { - const size_t stack_size = size(); - restore(); - // Only do the atomic add if the size is > 0. - if (stack_size > 0) { - AtomicAccess::add(total_size_addr, stack_size); - } -} - #ifndef PRODUCT void PreservedMarks::assert_empty() { assert(_stack.is_empty(), "stack expected to be empty, size = %zu", @@ -93,7 +84,7 @@ void PreservedMarksSet::init(uint num) { class RestorePreservedMarksTask : public WorkerTask { PreservedMarksSet* const _preserved_marks_set; SequentialSubTasksDone _sub_tasks; - volatile size_t _total_size; + Atomic _total_size; #ifdef ASSERT size_t _total_size_before; #endif // ASSERT @@ -102,7 +93,12 @@ public: void work(uint worker_id) override { uint task_id = 0; while (_sub_tasks.try_claim_task(task_id)) { - _preserved_marks_set->get(task_id)->restore_and_increment(&_total_size); + PreservedMarks* next = _preserved_marks_set->get(task_id); + size_t num_restored = next->size(); + next->restore(); + if (num_restored > 0) { + _total_size.add_then_fetch(num_restored); + } } } @@ -121,9 +117,11 @@ public: } ~RestorePreservedMarksTask() { - assert(_total_size == _total_size_before, "total_size = %zu before = %zu", _total_size, _total_size_before); - size_t mem_size = _total_size * (sizeof(oop) + sizeof(markWord)); - log_trace(gc)("Restored %zu marks, occupying %zu %s", _total_size, + size_t local_total_size = _total_size.load_relaxed(); + + assert(local_total_size == _total_size_before, "total_size = %zu before = %zu", local_total_size, _total_size_before); + size_t mem_size = local_total_size * (sizeof(oop) + sizeof(markWord)); + log_trace(gc)("Restored %zu marks, occupying %zu %s", local_total_size, byte_size_in_proper_unit(mem_size), proper_unit_for_byte_size(mem_size)); } diff --git a/src/hotspot/share/gc/shared/preservedMarks.hpp b/src/hotspot/share/gc/shared/preservedMarks.hpp index 10f75116524..3bbbd335011 100644 --- a/src/hotspot/share/gc/shared/preservedMarks.hpp +++ b/src/hotspot/share/gc/shared/preservedMarks.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2026, 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 @@ -59,8 +59,7 @@ public: size_t size() const { return _stack.size(); } inline void push_if_necessary(oop obj, markWord m); inline void push_always(oop obj, markWord m); - // Iterate over the stack, restore all preserved marks, and - // reclaim the memory taken up by the stack segments. + // Restore all preserved marks, and reclaim the memory taken up by the stack segments. void restore(); // Adjust the preserved mark according to its @@ -71,8 +70,6 @@ public: // to their forwarding location stored in the mark. void adjust_during_full_gc(); - void restore_and_increment(volatile size_t* const _total_size_addr); - // Assert the stack is empty and has no cached segments. void assert_empty() PRODUCT_RETURN; diff --git a/src/hotspot/share/gc/shared/pretouchTask.cpp b/src/hotspot/share/gc/shared/pretouchTask.cpp index cc84c8c449d..58a3a2693ed 100644 --- a/src/hotspot/share/gc/shared/pretouchTask.cpp +++ b/src/hotspot/share/gc/shared/pretouchTask.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, 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 @@ #include "gc/shared/gc_globals.hpp" #include "gc/shared/pretouchTask.hpp" #include "logging/log.hpp" -#include "runtime/atomicAccess.hpp" +#include "runtime/atomic.hpp" #include "runtime/globals.hpp" #include "runtime/os.hpp" #include "utilities/align.hpp" @@ -52,11 +52,11 @@ size_t PretouchTask::chunk_size() { void PretouchTask::work(uint worker_id) { while (true) { - char* cur_start = AtomicAccess::load(&_cur_addr); + char* cur_start = _cur_addr.load_relaxed(); char* cur_end = cur_start + MIN2(_chunk_size, pointer_delta(_end_addr, cur_start, 1)); if (cur_start >= cur_end) { break; - } else if (cur_start == AtomicAccess::cmpxchg(&_cur_addr, cur_start, cur_end)) { + } else if (_cur_addr.compare_set(cur_start, cur_end)) { os::pretouch_memory(cur_start, cur_end, _page_size); } // Else attempt to claim chunk failed, so try again. } diff --git a/src/hotspot/share/gc/shared/pretouchTask.hpp b/src/hotspot/share/gc/shared/pretouchTask.hpp index 7c66c8b717c..6355d61edf7 100644 --- a/src/hotspot/share/gc/shared/pretouchTask.hpp +++ b/src/hotspot/share/gc/shared/pretouchTask.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, 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,9 +26,11 @@ #define SHARE_GC_SHARED_PRETOUCH_HPP #include "gc/shared/workerThread.hpp" +#include "runtime/atomic.hpp" +#include "utilities/globalDefinitions.hpp" class PretouchTask : public WorkerTask { - char* volatile _cur_addr; + Atomic _cur_addr; char* const _end_addr; size_t _page_size; size_t _chunk_size; diff --git a/src/hotspot/share/gc/shared/referenceProcessorPhaseTimes.cpp b/src/hotspot/share/gc/shared/referenceProcessorPhaseTimes.cpp index df7d8f7b38d..0371ed2c73b 100644 --- a/src/hotspot/share/gc/shared/referenceProcessorPhaseTimes.cpp +++ b/src/hotspot/share/gc/shared/referenceProcessorPhaseTimes.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, 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,7 +30,6 @@ #include "logging/logStream.hpp" #include "memory/allocation.inline.hpp" #include "memory/resourceArea.hpp" -#include "runtime/atomicAccess.hpp" #define ASSERT_REF_TYPE(ref_type) assert((ref_type) >= REF_SOFT && (ref_type) <= REF_PHANTOM, \ "Invariant (%d)", (int)ref_type) @@ -196,7 +195,7 @@ void ReferenceProcessorPhaseTimes::reset() { _soft_weak_final_refs_phase_worker_time_sec->reset(); for (int i = 0; i < number_of_subclasses_of_ref; i++) { - _ref_dropped[i] = 0; + _ref_dropped[i].store_relaxed(0); _ref_discovered[i] = 0; } @@ -214,7 +213,7 @@ ReferenceProcessorPhaseTimes::~ReferenceProcessorPhaseTimes() { void ReferenceProcessorPhaseTimes::add_ref_dropped(ReferenceType ref_type, size_t count) { ASSERT_REF_TYPE(ref_type); - AtomicAccess::add(&_ref_dropped[ref_type_2_index(ref_type)], count, memory_order_relaxed); + _ref_dropped[ref_type_2_index(ref_type)].add_then_fetch(count, memory_order_relaxed); } void ReferenceProcessorPhaseTimes::set_ref_discovered(ReferenceType ref_type, size_t count) { @@ -271,7 +270,7 @@ void ReferenceProcessorPhaseTimes::print_reference(ReferenceType ref_type, uint int const ref_type_index = ref_type_2_index(ref_type); size_t discovered = _ref_discovered[ref_type_index]; - size_t dropped = _ref_dropped[ref_type_index]; + size_t dropped = _ref_dropped[ref_type_index].load_relaxed(); assert(discovered >= dropped, "invariant"); size_t processed = discovered - dropped; diff --git a/src/hotspot/share/gc/shared/referenceProcessorPhaseTimes.hpp b/src/hotspot/share/gc/shared/referenceProcessorPhaseTimes.hpp index 16691452ef4..82d26902bce 100644 --- a/src/hotspot/share/gc/shared/referenceProcessorPhaseTimes.hpp +++ b/src/hotspot/share/gc/shared/referenceProcessorPhaseTimes.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, 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,6 +30,7 @@ #include "gc/shared/workerDataArray.hpp" #include "memory/allocation.hpp" #include "memory/referenceType.hpp" +#include "runtime/atomic.hpp" #include "utilities/ticks.hpp" class DiscoveredList; @@ -52,7 +53,7 @@ class ReferenceProcessorPhaseTimes : public CHeapObj { // Total spent time for reference processing. double _total_time_ms; - size_t _ref_dropped[number_of_subclasses_of_ref]; + Atomic _ref_dropped[number_of_subclasses_of_ref]; size_t _ref_discovered[number_of_subclasses_of_ref]; bool _processing_is_mt; diff --git a/src/hotspot/share/gc/shared/suspendibleThreadSet.cpp b/src/hotspot/share/gc/shared/suspendibleThreadSet.cpp index 83783b31ad9..834a3d04c00 100644 --- a/src/hotspot/share/gc/shared/suspendibleThreadSet.cpp +++ b/src/hotspot/share/gc/shared/suspendibleThreadSet.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2026, 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,7 +30,7 @@ uint SuspendibleThreadSet::_nthreads = 0; uint SuspendibleThreadSet::_nthreads_stopped = 0; -volatile bool SuspendibleThreadSet::_suspend_all = false; +Atomic SuspendibleThreadSet::_suspend_all{false}; double SuspendibleThreadSet::_suspend_all_start = 0.0; static Semaphore* _synchronize_wakeup = nullptr; @@ -96,7 +96,7 @@ void SuspendibleThreadSet::synchronize() { { MonitorLocker ml(STS_lock, Mutex::_no_safepoint_check_flag); assert(!should_yield(), "Only one at a time"); - AtomicAccess::store(&_suspend_all, true); + _suspend_all.store_relaxed(true); if (is_synchronized()) { return; } @@ -127,6 +127,6 @@ void SuspendibleThreadSet::desynchronize() { MonitorLocker ml(STS_lock, Mutex::_no_safepoint_check_flag); assert(should_yield(), "STS not synchronizing"); assert(is_synchronized(), "STS not synchronized"); - AtomicAccess::store(&_suspend_all, false); + _suspend_all.store_relaxed(false); ml.notify_all(); } diff --git a/src/hotspot/share/gc/shared/suspendibleThreadSet.hpp b/src/hotspot/share/gc/shared/suspendibleThreadSet.hpp index 38568c015bc..72fca5302e7 100644 --- a/src/hotspot/share/gc/shared/suspendibleThreadSet.hpp +++ b/src/hotspot/share/gc/shared/suspendibleThreadSet.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2026, 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,7 +26,7 @@ #define SHARE_GC_SHARED_SUSPENDIBLETHREADSET_HPP #include "memory/allocation.hpp" -#include "runtime/atomicAccess.hpp" +#include "runtime/atomic.hpp" // A SuspendibleThreadSet is a set of threads that can be suspended. // A thread can join and later leave the set, and periodically yield. @@ -43,7 +43,7 @@ class SuspendibleThreadSet : public AllStatic { private: static uint _nthreads; static uint _nthreads_stopped; - static volatile bool _suspend_all; + static Atomic _suspend_all; static double _suspend_all_start; static bool is_synchronized(); @@ -59,7 +59,7 @@ private: public: // Returns true if an suspension is in progress. - static bool should_yield() { return AtomicAccess::load(&_suspend_all); } + static bool should_yield() { return _suspend_all.load_relaxed(); } // Suspends the current thread if a suspension is in progress. static void yield() { diff --git a/src/hotspot/share/gc/shared/taskqueue.cpp b/src/hotspot/share/gc/shared/taskqueue.cpp index f75dc4c2923..58af1793a48 100644 --- a/src/hotspot/share/gc/shared/taskqueue.cpp +++ b/src/hotspot/share/gc/shared/taskqueue.cpp @@ -25,7 +25,6 @@ #include "gc/shared/taskqueue.hpp" #include "logging/log.hpp" #include "oops/oop.inline.hpp" -#include "runtime/atomicAccess.hpp" #include "runtime/javaThread.hpp" #include "runtime/os.hpp" #include "utilities/debug.hpp" diff --git a/src/hotspot/share/gc/shared/taskqueue.hpp b/src/hotspot/share/gc/shared/taskqueue.hpp index 3a751852ab6..5c2fe4e5178 100644 --- a/src/hotspot/share/gc/shared/taskqueue.hpp +++ b/src/hotspot/share/gc/shared/taskqueue.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, 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 @@ -183,8 +183,8 @@ protected: _age.store_relaxed(new_age); } - Age cmpxchg_age(Age old_age, Age new_age) { - return _age.compare_exchange(old_age, new_age); + bool par_set_age(Age old_age, Age new_age) { + return _age.compare_set(old_age, new_age); } idx_t age_top_relaxed() const { @@ -345,7 +345,7 @@ protected: using TaskQueueSuper::age_relaxed; using TaskQueueSuper::set_age_relaxed; - using TaskQueueSuper::cmpxchg_age; + using TaskQueueSuper::par_set_age; using TaskQueueSuper::age_top_relaxed; using TaskQueueSuper::increment_index; @@ -641,6 +641,10 @@ public: return (raw_value() & PartialArrayTag) != 0; } + bool is_null() const { + return _p == nullptr; + } + oop* to_oop_ptr() const { return static_cast(decode(OopTag)); } diff --git a/src/hotspot/share/gc/shared/taskqueue.inline.hpp b/src/hotspot/share/gc/shared/taskqueue.inline.hpp index f115d94740b..e77645f4fcf 100644 --- a/src/hotspot/share/gc/shared/taskqueue.inline.hpp +++ b/src/hotspot/share/gc/shared/taskqueue.inline.hpp @@ -32,7 +32,6 @@ #include "memory/allocation.inline.hpp" #include "memory/resourceArea.hpp" #include "oops/oop.inline.hpp" -#include "runtime/atomicAccess.hpp" #include "runtime/orderAccess.hpp" #include "utilities/debug.hpp" #include "utilities/ostream.hpp" @@ -170,8 +169,7 @@ bool GenericTaskQueue::pop_local_slow(uint localBot, Age oldAge) { if (localBot == oldAge.top()) { // No competing pop_global has yet incremented "top"; we'll try to // install new_age, thus claiming the element. - Age tempAge = cmpxchg_age(oldAge, newAge); - if (tempAge == oldAge) { + if (par_set_age(oldAge, newAge)) { // We win. assert_not_underflow(localBot, age_top_relaxed()); TASKQUEUE_STATS_ONLY(stats.record_pop_slow()); @@ -283,12 +281,12 @@ typename GenericTaskQueue::PopResult GenericTaskQueue::pop_g idx_t new_top = increment_index(oldAge.top()); idx_t new_tag = oldAge.tag() + ((new_top == 0) ? 1 : 0); Age newAge(new_top, new_tag); - Age resAge = cmpxchg_age(oldAge, newAge); + bool result = par_set_age(oldAge, newAge); // Note that using "bottom" here might fail, since a pop_local might // have decremented it. assert_not_underflow(localBot, newAge.top()); - return resAge == oldAge ? PopResult::Success : PopResult::Contended; + return result ? PopResult::Success : PopResult::Contended; } inline int randomParkAndMiller(int *seed0) { diff --git a/src/hotspot/share/gc/shared/workerThread.cpp b/src/hotspot/share/gc/shared/workerThread.cpp index 3a999da59dc..2f6f003608f 100644 --- a/src/hotspot/share/gc/shared/workerThread.cpp +++ b/src/hotspot/share/gc/shared/workerThread.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, 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,7 +26,6 @@ #include "gc/shared/workerThread.hpp" #include "logging/log.hpp" #include "memory/iterator.hpp" -#include "runtime/atomicAccess.hpp" #include "runtime/init.hpp" #include "runtime/java.hpp" #include "runtime/os.hpp" @@ -42,7 +41,7 @@ WorkerTaskDispatcher::WorkerTaskDispatcher() : void WorkerTaskDispatcher::coordinator_distribute_task(WorkerTask* task, uint num_workers) { // No workers are allowed to read the state variables until they have been signaled. _task = task; - _not_finished = num_workers; + _not_finished.store_relaxed(num_workers); // Dispatch 'num_workers' number of tasks. _start_semaphore.signal(num_workers); @@ -51,9 +50,12 @@ void WorkerTaskDispatcher::coordinator_distribute_task(WorkerTask* task, uint nu _end_semaphore.wait(); // No workers are allowed to read the state variables after the coordinator has been signaled. - assert(_not_finished == 0, "%d not finished workers?", _not_finished); +#ifdef ASSERT + uint not_finished = _not_finished.load_relaxed(); + assert(not_finished == 0, "%u not finished workers?", not_finished); +#endif // ASSERT _task = nullptr; - _started = 0; + _started.store_relaxed(0); } void WorkerTaskDispatcher::worker_run_task() { @@ -61,7 +63,7 @@ void WorkerTaskDispatcher::worker_run_task() { _start_semaphore.wait(); // Get and set worker id. - const uint worker_id = AtomicAccess::fetch_then_add(&_started, 1u); + const uint worker_id = _started.fetch_then_add(1u); WorkerThread::set_worker_id(worker_id); // Run task. @@ -70,7 +72,7 @@ void WorkerTaskDispatcher::worker_run_task() { // Mark that the worker is done with the task. // The worker is not allowed to read the state variables after this line. - const uint not_finished = AtomicAccess::sub(&_not_finished, 1u); + const uint not_finished = _not_finished.sub_then_fetch(1u); // The last worker signals to the coordinator that all work is completed. if (not_finished == 0) { @@ -93,8 +95,22 @@ void WorkerThreads::initialize_workers() { } } +bool WorkerThreads::allow_inject_creation_failure() const { + if (!is_init_completed()) { + // Never allow creation failures during VM init + return false; + } + + if (_created_workers == 0) { + // Never allow creation failures of the first worker, it will cause the VM to exit + return false; + } + + return true; +} + WorkerThread* WorkerThreads::create_worker(uint name_suffix) { - if (is_init_completed() && InjectGCWorkerCreationFailure) { + if (InjectGCWorkerCreationFailure && allow_inject_creation_failure()) { return nullptr; } diff --git a/src/hotspot/share/gc/shared/workerThread.hpp b/src/hotspot/share/gc/shared/workerThread.hpp index d4e92797039..003ce8a2959 100644 --- a/src/hotspot/share/gc/shared/workerThread.hpp +++ b/src/hotspot/share/gc/shared/workerThread.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2002, 2026, 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,6 +27,7 @@ #include "gc/shared/gcId.hpp" #include "memory/allocation.hpp" +#include "runtime/atomic.hpp" #include "runtime/nonJavaThread.hpp" #include "runtime/semaphore.hpp" #include "utilities/debug.hpp" @@ -58,8 +59,8 @@ class WorkerTaskDispatcher { // The task currently being dispatched to the WorkerThreads. WorkerTask* _task; - volatile uint _started; - volatile uint _not_finished; + Atomic _started; + Atomic _not_finished; // Semaphore used to start the WorkerThreads. Semaphore _start_semaphore; @@ -103,6 +104,7 @@ public: WorkerThreads(const char* name, uint max_workers); void initialize_workers(); + bool allow_inject_creation_failure() const; uint max_workers() const { return _max_workers; } uint created_workers() const { return _created_workers; } diff --git a/src/hotspot/share/gc/shared/workerUtils.cpp b/src/hotspot/share/gc/shared/workerUtils.cpp index 422d513a5cd..1826b9d7df8 100644 --- a/src/hotspot/share/gc/shared/workerUtils.cpp +++ b/src/hotspot/share/gc/shared/workerUtils.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, 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,8 +22,9 @@ * */ +#include "cppstdlib/new.hpp" #include "gc/shared/workerUtils.hpp" -#include "runtime/atomicAccess.hpp" +#include "runtime/atomic.hpp" #include "runtime/mutexLocker.hpp" // *** WorkerThreadsBarrierSync @@ -80,21 +81,21 @@ void WorkerThreadsBarrierSync::abort() { SubTasksDone::SubTasksDone(uint n) : _tasks(nullptr), _n_tasks(n) { - _tasks = NEW_C_HEAP_ARRAY(bool, n, mtInternal); + _tasks = NEW_C_HEAP_ARRAY(Atomic, n, mtInternal); for (uint i = 0; i < _n_tasks; i++) { - _tasks[i] = false; + ::new (&_tasks[i]) Atomic(false); } } #ifdef ASSERT void SubTasksDone::all_tasks_claimed_impl(uint skipped[], size_t skipped_size) { - if (AtomicAccess::cmpxchg(&_verification_done, false, true)) { + if (!_verification_done.compare_set(false, true)) { // another thread has done the verification return; } // all non-skipped tasks are claimed for (uint i = 0; i < _n_tasks; ++i) { - if (!_tasks[i]) { + if (!_tasks[i].load_relaxed()) { auto is_skipped = false; for (size_t j = 0; j < skipped_size; ++j) { if (i == skipped[j]) { @@ -109,27 +110,27 @@ void SubTasksDone::all_tasks_claimed_impl(uint skipped[], size_t skipped_size) { for (size_t i = 0; i < skipped_size; ++i) { auto task_index = skipped[i]; assert(task_index < _n_tasks, "Array in range."); - assert(!_tasks[task_index], "%d is both claimed and skipped.", task_index); + assert(!_tasks[task_index].load_relaxed(), "%d is both claimed and skipped.", task_index); } } #endif bool SubTasksDone::try_claim_task(uint t) { assert(t < _n_tasks, "bad task id."); - return !_tasks[t] && !AtomicAccess::cmpxchg(&_tasks[t], false, true); + return !_tasks[t].load_relaxed() && _tasks[t].compare_set(false, true); } SubTasksDone::~SubTasksDone() { - assert(_verification_done, "all_tasks_claimed must have been called."); - FREE_C_HEAP_ARRAY(bool, _tasks); + assert(_verification_done.load_relaxed(), "all_tasks_claimed must have been called."); + FREE_C_HEAP_ARRAY(Atomic, _tasks); } // *** SequentialSubTasksDone bool SequentialSubTasksDone::try_claim_task(uint& t) { - t = _num_claimed; + t = _num_claimed.load_relaxed(); if (t < _num_tasks) { - t = AtomicAccess::add(&_num_claimed, 1u) - 1; + t = _num_claimed.fetch_then_add(1u); } return t < _num_tasks; } diff --git a/src/hotspot/share/gc/shared/workerUtils.hpp b/src/hotspot/share/gc/shared/workerUtils.hpp index 5f771a57837..7bf9d7d7656 100644 --- a/src/hotspot/share/gc/shared/workerUtils.hpp +++ b/src/hotspot/share/gc/shared/workerUtils.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2002, 2026, 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,6 +28,7 @@ #include "cppstdlib/type_traits.hpp" #include "memory/allocation.hpp" #include "metaprogramming/enableIf.hpp" +#include "runtime/atomic.hpp" #include "runtime/mutex.hpp" #include "utilities/debug.hpp" #include "utilities/globalDefinitions.hpp" @@ -79,11 +80,11 @@ public: // enumeration type. class SubTasksDone: public CHeapObj { - volatile bool* _tasks; + Atomic* _tasks; uint _n_tasks; // make sure verification logic is run exactly once to avoid duplicate assertion failures - DEBUG_ONLY(volatile bool _verification_done = false;) + DEBUG_ONLY(Atomic _verification_done;) void all_tasks_claimed_impl(uint skipped[], size_t skipped_size) NOT_DEBUG_RETURN; NONCOPYABLE(SubTasksDone); @@ -127,7 +128,7 @@ public: class SequentialSubTasksDone : public CHeapObj { uint _num_tasks; // Total number of tasks available. - volatile uint _num_claimed; // Number of tasks claimed. + Atomic _num_claimed; // Number of tasks claimed. NONCOPYABLE(SequentialSubTasksDone); @@ -135,7 +136,8 @@ public: SequentialSubTasksDone(uint num_tasks) : _num_tasks(num_tasks), _num_claimed(0) { } ~SequentialSubTasksDone() { // Claiming may try to claim more tasks than there are. - assert(_num_claimed >= _num_tasks, "Claimed %u tasks of %u", _num_claimed, _num_tasks); + assert(_num_claimed.load_relaxed() >= _num_tasks, + "Claimed %u tasks of %u", _num_claimed.load_relaxed(), _num_tasks); } // Attempt to claim the next unclaimed task in the sequence, diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahAdaptiveHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahAdaptiveHeuristics.cpp index 41535f302d7..46d9f19d35f 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahAdaptiveHeuristics.cpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahAdaptiveHeuristics.cpp @@ -68,9 +68,9 @@ ShenandoahAdaptiveHeuristics::ShenandoahAdaptiveHeuristics(ShenandoahSpaceInfo* ShenandoahAdaptiveHeuristics::~ShenandoahAdaptiveHeuristics() {} -void ShenandoahAdaptiveHeuristics::choose_collection_set_from_regiondata(ShenandoahCollectionSet* cset, - RegionData* data, size_t size, - size_t actual_free) { +size_t ShenandoahAdaptiveHeuristics::choose_collection_set_from_regiondata(ShenandoahCollectionSet* cset, + RegionData* data, size_t size, + size_t actual_free) { size_t garbage_threshold = ShenandoahHeapRegion::region_size_bytes() * ShenandoahGarbageThreshold / 100; // The logic for cset selection in adaptive is as follows: @@ -124,6 +124,7 @@ void ShenandoahAdaptiveHeuristics::choose_collection_set_from_regiondata(Shenand cur_garbage = new_garbage; } } + return 0; } void ShenandoahAdaptiveHeuristics::record_cycle_start() { @@ -183,8 +184,8 @@ void ShenandoahAdaptiveHeuristics::record_success_concurrent() { } } -void ShenandoahAdaptiveHeuristics::record_success_degenerated() { - ShenandoahHeuristics::record_success_degenerated(); +void ShenandoahAdaptiveHeuristics::record_degenerated() { + ShenandoahHeuristics::record_degenerated(); // Adjust both trigger's parameters in the case of a degenerated GC because // either of them should have triggered earlier to avoid this case. adjust_margin_of_error(DEGENERATE_PENALTY_SD); diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahAdaptiveHeuristics.hpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahAdaptiveHeuristics.hpp index 66bfc3375a3..c4fdf819391 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahAdaptiveHeuristics.hpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahAdaptiveHeuristics.hpp @@ -108,13 +108,13 @@ public: virtual ~ShenandoahAdaptiveHeuristics(); - virtual void choose_collection_set_from_regiondata(ShenandoahCollectionSet* cset, - RegionData* data, size_t size, - size_t actual_free) override; + virtual size_t choose_collection_set_from_regiondata(ShenandoahCollectionSet* cset, + RegionData* data, size_t size, + size_t actual_free) override; virtual void record_cycle_start() override; virtual void record_success_concurrent() override; - virtual void record_success_degenerated() override; + virtual void record_degenerated() override; virtual void record_success_full() override; virtual bool should_start_gc() override; diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahAggressiveHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahAggressiveHeuristics.cpp index a833e39631c..990b59ec853 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahAggressiveHeuristics.cpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahAggressiveHeuristics.cpp @@ -39,15 +39,16 @@ ShenandoahAggressiveHeuristics::ShenandoahAggressiveHeuristics(ShenandoahSpaceIn SHENANDOAH_ERGO_ENABLE_FLAG(ShenandoahEvacReserveOverflow); } -void ShenandoahAggressiveHeuristics::choose_collection_set_from_regiondata(ShenandoahCollectionSet* cset, - RegionData* data, size_t size, - size_t free) { +size_t ShenandoahAggressiveHeuristics::choose_collection_set_from_regiondata(ShenandoahCollectionSet* cset, + RegionData* data, size_t size, + size_t free) { for (size_t idx = 0; idx < size; idx++) { ShenandoahHeapRegion* r = data[idx].get_region(); if (r->garbage() > 0) { cset->add_region(r); } } + return 0; } bool ShenandoahAggressiveHeuristics::should_start_gc() { diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahAggressiveHeuristics.hpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahAggressiveHeuristics.hpp index 5075258f1ce..25c8635489f 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahAggressiveHeuristics.hpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahAggressiveHeuristics.hpp @@ -35,9 +35,9 @@ class ShenandoahAggressiveHeuristics : public ShenandoahHeuristics { public: ShenandoahAggressiveHeuristics(ShenandoahSpaceInfo* space_info); - virtual void choose_collection_set_from_regiondata(ShenandoahCollectionSet* cset, - RegionData* data, size_t size, - size_t free); + virtual size_t choose_collection_set_from_regiondata(ShenandoahCollectionSet* cset, + RegionData* data, size_t size, + size_t free); virtual bool should_start_gc(); diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahCompactHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahCompactHeuristics.cpp index 28673b28612..09a8394a4b1 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahCompactHeuristics.cpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahCompactHeuristics.cpp @@ -76,9 +76,9 @@ bool ShenandoahCompactHeuristics::should_start_gc() { return ShenandoahHeuristics::should_start_gc(); } -void ShenandoahCompactHeuristics::choose_collection_set_from_regiondata(ShenandoahCollectionSet* cset, - RegionData* data, size_t size, - size_t actual_free) { +size_t ShenandoahCompactHeuristics::choose_collection_set_from_regiondata(ShenandoahCollectionSet* cset, + RegionData* data, size_t size, + size_t actual_free) { // Do not select too large CSet that would overflow the available free space size_t max_cset = actual_free * 3 / 4; @@ -97,4 +97,5 @@ void ShenandoahCompactHeuristics::choose_collection_set_from_regiondata(Shenando cset->add_region(r); } } + return 0; } diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahCompactHeuristics.hpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahCompactHeuristics.hpp index 21ec99eabc0..4988d5d495d 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahCompactHeuristics.hpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahCompactHeuristics.hpp @@ -37,9 +37,9 @@ public: virtual bool should_start_gc(); - virtual void choose_collection_set_from_regiondata(ShenandoahCollectionSet* cset, - RegionData* data, size_t size, - size_t actual_free); + virtual size_t choose_collection_set_from_regiondata(ShenandoahCollectionSet* cset, + RegionData* data, size_t size, + size_t actual_free); virtual const char* name() { return "Compact"; } virtual bool is_diagnostic() { return false; } diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.cpp index b14d72f249b..ee315ce5c7e 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.cpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.cpp @@ -37,7 +37,7 @@ ShenandoahGenerationalHeuristics::ShenandoahGenerationalHeuristics(ShenandoahGen : ShenandoahAdaptiveHeuristics(generation), _generation(generation) { } -void ShenandoahGenerationalHeuristics::choose_collection_set(ShenandoahCollectionSet* collection_set) { +size_t ShenandoahGenerationalHeuristics::choose_collection_set(ShenandoahCollectionSet* collection_set) { assert(collection_set->is_empty(), "Must be empty"); auto heap = ShenandoahGenerationalHeap::heap(); @@ -168,16 +168,12 @@ void ShenandoahGenerationalHeuristics::choose_collection_set(ShenandoahCollectio byte_size_in_proper_unit(total_garbage), proper_unit_for_byte_size(total_garbage)); size_t immediate_percent = (total_garbage == 0) ? 0 : (immediate_garbage * 100 / total_garbage); - bool doing_promote_in_place = (humongous_regions_promoted + regular_regions_promoted_in_place > 0); - if (doing_promote_in_place || (preselected_candidates > 0) || (immediate_percent <= ShenandoahImmediateThreshold)) { - // Only young collections need to prime the collection set. - if (_generation->is_young()) { - heap->old_generation()->heuristics()->prime_collection_set(collection_set); - } + size_t add_regions_to_old = 0; + if (doing_promote_in_place || (preselected_candidates > 0) || (immediate_percent <= ShenandoahImmediateThreshold)) { // Call the subclasses to add young-gen regions into the collection set. - choose_collection_set_from_regiondata(collection_set, candidates, cand_idx, immediate_garbage + free); + add_regions_to_old = choose_collection_set_from_regiondata(collection_set, candidates, cand_idx, immediate_garbage + free); } if (collection_set->has_old_regions()) { @@ -194,6 +190,7 @@ void ShenandoahGenerationalHeuristics::choose_collection_set(ShenandoahCollectio regular_regions_promoted_free, immediate_regions, immediate_garbage); + return add_regions_to_old; } @@ -210,13 +207,6 @@ size_t ShenandoahGenerationalHeuristics::add_preselected_regions_to_collection_s assert(ShenandoahGenerationalHeap::heap()->is_tenurable(r), "Preselected regions must have tenure age"); // Entire region will be promoted, This region does not impact young-gen or old-gen evacuation reserve. // This region has been pre-selected and its impact on promotion reserve is already accounted for. - - // r->used() is r->garbage() + r->get_live_data_bytes() - // Since all live data in this region is being evacuated from young-gen, it is as if this memory - // is garbage insofar as young-gen is concerned. Counting this as garbage reduces the need to - // reclaim highly utilized young-gen regions just for the sake of finding min_garbage to reclaim - // within young-gen memory. - cur_young_garbage += r->garbage(); cset->add_region(r); } diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.hpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.hpp index 31c016bb4b7..9b4c93af9b4 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.hpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.hpp @@ -44,7 +44,7 @@ class ShenandoahGenerationalHeuristics : public ShenandoahAdaptiveHeuristics { public: explicit ShenandoahGenerationalHeuristics(ShenandoahGeneration* generation); - void choose_collection_set(ShenandoahCollectionSet* collection_set) override; + size_t choose_collection_set(ShenandoahCollectionSet* collection_set) override; protected: ShenandoahGeneration* _generation; diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.cpp index 51805b205dd..f47371c14d5 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.cpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.cpp @@ -24,6 +24,7 @@ */ #include "gc/shenandoah/heuristics/shenandoahGlobalHeuristics.hpp" +#include "gc/shenandoah/shenandoahAsserts.hpp" #include "gc/shenandoah/shenandoahCollectorPolicy.hpp" #include "gc/shenandoah/shenandoahGenerationalHeap.inline.hpp" #include "gc/shenandoah/shenandoahGlobalGeneration.hpp" @@ -35,13 +36,14 @@ ShenandoahGlobalHeuristics::ShenandoahGlobalHeuristics(ShenandoahGlobalGeneratio } -void ShenandoahGlobalHeuristics::choose_collection_set_from_regiondata(ShenandoahCollectionSet* cset, - RegionData* data, size_t size, - size_t actual_free) { +size_t ShenandoahGlobalHeuristics::choose_collection_set_from_regiondata(ShenandoahCollectionSet* cset, + RegionData* data, size_t size, + size_t actual_free) { // Better select garbage-first regions QuickSort::sort(data, (int) size, compare_by_garbage); choose_global_collection_set(cset, data, size, actual_free, 0 /* cur_young_garbage */); + return 0; } @@ -49,94 +51,212 @@ void ShenandoahGlobalHeuristics::choose_global_collection_set(ShenandoahCollecti const ShenandoahHeuristics::RegionData* data, size_t size, size_t actual_free, size_t cur_young_garbage) const { + shenandoah_assert_heaplocked_or_safepoint(); auto heap = ShenandoahGenerationalHeap::heap(); + auto free_set = heap->free_set(); size_t region_size_bytes = ShenandoahHeapRegion::region_size_bytes(); size_t capacity = heap->soft_max_capacity(); + size_t garbage_threshold = region_size_bytes * ShenandoahGarbageThreshold / 100; size_t ignore_threshold = region_size_bytes * ShenandoahIgnoreGarbageThreshold / 100; size_t young_evac_reserve = heap->young_generation()->get_evacuation_reserve(); + size_t original_young_evac_reserve = young_evac_reserve; size_t old_evac_reserve = heap->old_generation()->get_evacuation_reserve(); - size_t max_young_cset = (size_t) (young_evac_reserve / ShenandoahEvacWaste); - size_t young_cur_cset = 0; - size_t max_old_cset = (size_t) (old_evac_reserve / ShenandoahOldEvacWaste); - size_t old_cur_cset = 0; + size_t old_promo_reserve = heap->old_generation()->get_promoted_reserve(); - // Figure out how many unaffiliated young regions are dedicated to mutator and to evacuator. Allow the young - // collector's unaffiliated regions to be transferred to old-gen if old-gen has more easily reclaimed garbage - // than young-gen. At the end of this cycle, any excess regions remaining in old-gen will be transferred back - // to young. Do not transfer the mutator's unaffiliated regions to old-gen. Those must remain available - // to the mutator as it needs to be able to consume this memory during concurrent GC. - - size_t unaffiliated_young_regions = heap->young_generation()->free_unaffiliated_regions(); + size_t unaffiliated_young_regions = free_set->collector_unaffiliated_regions(); size_t unaffiliated_young_memory = unaffiliated_young_regions * region_size_bytes; + size_t unaffiliated_old_regions = free_set->old_collector_unaffiliated_regions(); + size_t unaffiliated_old_memory = unaffiliated_old_regions * region_size_bytes; - if (unaffiliated_young_memory > max_young_cset) { - size_t unaffiliated_mutator_memory = unaffiliated_young_memory - max_young_cset; - unaffiliated_young_memory -= unaffiliated_mutator_memory; - unaffiliated_young_regions = unaffiliated_young_memory / region_size_bytes; // round down - unaffiliated_young_memory = unaffiliated_young_regions * region_size_bytes; + // Figure out how many unaffiliated regions are dedicated to Collector and OldCollector reserves. Let these + // be shuffled between young and old generations in order to expedite evacuation of whichever regions have the + // most garbage, regardless of whether these garbage-first regions reside in young or old generation. + // Excess reserves will be transferred back to the mutator after collection set has been chosen. At the end + // of evacuation, any reserves not consumed by evacuation will also be transferred to the mutator free set. + + // Truncate reserves to only target unaffiliated memory + size_t shared_reserve_regions = 0; + if (young_evac_reserve > unaffiliated_young_memory) { + shared_reserve_regions += unaffiliated_young_regions; + } else { + size_t delta_regions = young_evac_reserve / region_size_bytes; + shared_reserve_regions += delta_regions; } + young_evac_reserve = 0; + size_t total_old_reserve = old_evac_reserve + old_promo_reserve; + if (total_old_reserve > unaffiliated_old_memory) { + // Give all the unaffiliated memory to the shared reserves. Leave the rest for promo reserve. + shared_reserve_regions += unaffiliated_old_regions; + old_promo_reserve = total_old_reserve - unaffiliated_old_memory; + } else { + size_t delta_regions = old_evac_reserve / region_size_bytes; + shared_reserve_regions += delta_regions; + } + old_evac_reserve = 0; + assert(shared_reserve_regions <= + (heap->young_generation()->free_unaffiliated_regions() + heap->old_generation()->free_unaffiliated_regions()), + "simple math"); - // We'll affiliate these unaffiliated regions with either old or young, depending on need. - max_young_cset -= unaffiliated_young_memory; + size_t shared_reserves = shared_reserve_regions * region_size_bytes; + size_t committed_from_shared_reserves = 0; - // Keep track of how many regions we plan to transfer from young to old. - size_t regions_transferred_to_old = 0; + size_t promo_bytes = 0; + size_t old_evac_bytes = 0; + size_t young_evac_bytes = 0; - size_t free_target = (capacity * ShenandoahMinFreeThreshold) / 100 + max_young_cset; + size_t consumed_by_promo = 0; // promo_bytes * ShenandoahPromoEvacWaste + size_t consumed_by_old_evac = 0; // old_evac_bytes * ShenandoahOldEvacWaste + size_t consumed_by_young_evac = 0; // young_evac_bytes * ShenandoahEvacWaste + + // Of the memory reclaimed by GC, some of this will need to be reserved for the next GC collection. Use the current + // young reserve as an approximation of the future Collector reserve requirement. Try to end with at least + // (capacity * ShenandoahMinFreeThreshold) / 100 bytes available to the mutator. + size_t free_target = (capacity * ShenandoahMinFreeThreshold) / 100 + original_young_evac_reserve; size_t min_garbage = (free_target > actual_free) ? (free_target - actual_free) : 0; - log_info(gc, ergo)("Adaptive CSet Selection for GLOBAL. Max Young Evacuation: %zu" - "%s, Max Old Evacuation: %zu%s, Max Either Evacuation: %zu%s, Actual Free: %zu%s.", - byte_size_in_proper_unit(max_young_cset), proper_unit_for_byte_size(max_young_cset), - byte_size_in_proper_unit(max_old_cset), proper_unit_for_byte_size(max_old_cset), - byte_size_in_proper_unit(unaffiliated_young_memory), proper_unit_for_byte_size(unaffiliated_young_memory), - byte_size_in_proper_unit(actual_free), proper_unit_for_byte_size(actual_free)); + size_t aged_regions_promoted = 0; + size_t young_regions_evacuated = 0; + size_t old_regions_evacuated = 0; + log_info(gc, ergo)("Adaptive CSet Selection for GLOBAL. Discretionary evacuation budget (for either old or young): %zu%s" + ", Actual Free: %zu%s.", + byte_size_in_proper_unit(shared_reserves), proper_unit_for_byte_size(shared_reserves), + byte_size_in_proper_unit(actual_free), proper_unit_for_byte_size(actual_free)); + + size_t cur_garbage = cur_young_garbage; for (size_t idx = 0; idx < size; idx++) { ShenandoahHeapRegion* r = data[idx].get_region(); assert(!cset->is_preselected(r->index()), "There should be no preselected regions during GLOBAL GC"); bool add_region = false; - if (r->is_old() || heap->is_tenurable(r)) { - size_t new_cset = old_cur_cset + r->get_live_data_bytes(); - if ((r->garbage() > garbage_threshold)) { - while ((new_cset > max_old_cset) && (unaffiliated_young_regions > 0)) { - unaffiliated_young_regions--; - regions_transferred_to_old++; - max_old_cset += region_size_bytes / ShenandoahOldEvacWaste; + size_t region_garbage = r->garbage(); + size_t new_garbage = cur_garbage + region_garbage; + bool add_regardless = (region_garbage > ignore_threshold) && (new_garbage < min_garbage); + size_t live_bytes = r->get_live_data_bytes(); + if (add_regardless || (region_garbage >= garbage_threshold)) { + if (r->is_old()) { + size_t anticipated_consumption = (size_t) (live_bytes * ShenandoahOldEvacWaste); + size_t new_old_consumption = consumed_by_old_evac + anticipated_consumption; + size_t new_old_evac_reserve = old_evac_reserve; + size_t proposed_old_region_expansion = 0; + while ((new_old_consumption > new_old_evac_reserve) && (committed_from_shared_reserves < shared_reserves)) { + committed_from_shared_reserves += region_size_bytes; + proposed_old_region_expansion++; + new_old_evac_reserve += region_size_bytes; } - } - if ((new_cset <= max_old_cset) && (r->garbage() > garbage_threshold)) { - add_region = true; - old_cur_cset = new_cset; - } - } else { - assert(r->is_young() && !heap->is_tenurable(r), "DeMorgan's law (assuming r->is_affiliated)"); - size_t new_cset = young_cur_cset + r->get_live_data_bytes(); - size_t region_garbage = r->garbage(); - size_t new_garbage = cur_young_garbage + region_garbage; - bool add_regardless = (region_garbage > ignore_threshold) && (new_garbage < min_garbage); - - if (add_regardless || (r->garbage() > garbage_threshold)) { - while ((new_cset > max_young_cset) && (unaffiliated_young_regions > 0)) { - unaffiliated_young_regions--; - max_young_cset += region_size_bytes / ShenandoahEvacWaste; + // If this region has free memory and we choose to place it in the collection set, its free memory is no longer + // available to hold promotion results. So we behave as if its free memory is consumed within the promotion reserve. + size_t anticipated_loss_from_promo_reserve = r->free(); + size_t new_promo_consumption = consumed_by_promo + anticipated_loss_from_promo_reserve; + size_t new_promo_reserve = old_promo_reserve; + while ((new_promo_consumption > new_promo_reserve) && (committed_from_shared_reserves < shared_reserves)) { + committed_from_shared_reserves += region_size_bytes; + proposed_old_region_expansion++; + new_promo_reserve += region_size_bytes; + } + if ((new_old_consumption <= new_old_evac_reserve) && (new_promo_consumption <= new_promo_reserve)) { + add_region = true; + old_evac_reserve = new_old_evac_reserve; + old_promo_reserve = new_promo_reserve; + old_evac_bytes += live_bytes; + consumed_by_old_evac = new_old_consumption; + consumed_by_promo = new_promo_consumption; + cur_garbage = new_garbage; + old_regions_evacuated++; + } else { + // We failed to sufficiently expand old so unwind proposed expansion + committed_from_shared_reserves -= proposed_old_region_expansion * region_size_bytes; + } + } else if (heap->is_tenurable(r)) { + size_t anticipated_consumption = (size_t) (live_bytes * ShenandoahPromoEvacWaste); + size_t new_promo_consumption = consumed_by_promo + anticipated_consumption; + size_t new_promo_reserve = old_promo_reserve; + size_t proposed_old_region_expansion = 0; + while ((new_promo_consumption > new_promo_reserve) && (committed_from_shared_reserves < shared_reserves)) { + committed_from_shared_reserves += region_size_bytes; + proposed_old_region_expansion++; + new_promo_reserve += region_size_bytes; + } + if (new_promo_consumption <= new_promo_reserve) { + add_region = true; + old_promo_reserve = new_promo_reserve; + promo_bytes += live_bytes; + consumed_by_promo = new_promo_consumption; + cur_garbage = new_garbage; + aged_regions_promoted++; + } else { + // We failed to sufficiently expand old so unwind proposed expansion + committed_from_shared_reserves -= proposed_old_region_expansion * region_size_bytes; + } + } else { + assert(r->is_young() && !heap->is_tenurable(r), "DeMorgan's law (assuming r->is_affiliated)"); + size_t anticipated_consumption = (size_t) (live_bytes * ShenandoahEvacWaste); + size_t new_young_evac_consumption = consumed_by_young_evac + anticipated_consumption; + size_t new_young_evac_reserve = young_evac_reserve; + size_t proposed_young_region_expansion = 0; + while ((new_young_evac_consumption > new_young_evac_reserve) && (committed_from_shared_reserves < shared_reserves)) { + committed_from_shared_reserves += region_size_bytes; + proposed_young_region_expansion++; + new_young_evac_reserve += region_size_bytes; + } + if (new_young_evac_consumption <= new_young_evac_reserve) { + add_region = true; + young_evac_reserve = new_young_evac_reserve; + young_evac_bytes += live_bytes; + consumed_by_young_evac = new_young_evac_consumption; + cur_garbage = new_garbage; + young_regions_evacuated++; + } else { + // We failed to sufficiently expand old so unwind proposed expansion + committed_from_shared_reserves -= proposed_young_region_expansion * region_size_bytes; } - } - if ((new_cset <= max_young_cset) && (add_regardless || (region_garbage > garbage_threshold))) { - add_region = true; - young_cur_cset = new_cset; - cur_young_garbage = new_garbage; } } if (add_region) { cset->add_region(r); } } - if (regions_transferred_to_old > 0) { - assert(young_evac_reserve > regions_transferred_to_old * region_size_bytes, "young reserve cannot be negative"); - heap->young_generation()->set_evacuation_reserve(young_evac_reserve - regions_transferred_to_old * region_size_bytes); - heap->old_generation()->set_evacuation_reserve(old_evac_reserve + regions_transferred_to_old * region_size_bytes); + + if (committed_from_shared_reserves < shared_reserves) { + // Give all the rest to promotion + old_promo_reserve += (shared_reserves - committed_from_shared_reserves); + // dead code: committed_from_shared_reserves = shared_reserves; } + + // Consider the effects of round-off: + // 1. We know that the sum over each evacuation mutiplied by Evacuation Waste is <= total evacuation reserve + // 2. However, the reserve for each individual evacuation may be rounded down. In the worst case, we will be over budget + // by the number of regions evacuated, since each region's reserve might be under-estimated by at most 1 + // 3. Likewise, if we take the sum of bytes evacuated and multiply this by the Evacuation Waste and then round down + // to nearest integer, the calculated reserve will underestimate the true reserve needs by at most 1. + // 4. This explains the adjustments to subtotals in the assert statements below. + assert(young_evac_bytes * ShenandoahEvacWaste <= young_evac_reserve + young_regions_evacuated, + "budget: %zu <= %zu", (size_t) (young_evac_bytes * ShenandoahEvacWaste), young_evac_reserve); + assert(old_evac_bytes * ShenandoahOldEvacWaste <= old_evac_reserve + old_regions_evacuated, + "budget: %zu <= %zu", (size_t) (old_evac_bytes * ShenandoahOldEvacWaste), old_evac_reserve); + assert(promo_bytes * ShenandoahPromoEvacWaste <= old_promo_reserve + aged_regions_promoted, + "budget: %zu <= %zu", (size_t) (promo_bytes * ShenandoahPromoEvacWaste), old_promo_reserve); + assert(young_evac_reserve + old_evac_reserve + old_promo_reserve <= + heap->young_generation()->get_evacuation_reserve() + heap->old_generation()->get_evacuation_reserve() + + heap->old_generation()->get_promoted_reserve(), "Exceeded budget"); + + if (heap->young_generation()->get_evacuation_reserve() < young_evac_reserve) { + size_t delta_bytes = young_evac_reserve - heap->young_generation()->get_evacuation_reserve(); + size_t delta_regions = delta_bytes / region_size_bytes; + size_t regions_to_transfer = MIN2(unaffiliated_old_regions, delta_regions); + log_info(gc)("Global GC moves %zu unaffiliated regions from old collector to young collector reserves", regions_to_transfer); + ssize_t negated_regions = -regions_to_transfer; + heap->free_set()->move_unaffiliated_regions_from_collector_to_old_collector(negated_regions); + } else if (heap->young_generation()->get_evacuation_reserve() > young_evac_reserve) { + size_t delta_bytes = heap->young_generation()->get_evacuation_reserve() - young_evac_reserve; + size_t delta_regions = delta_bytes / region_size_bytes; + size_t regions_to_transfer = MIN2(unaffiliated_young_regions, delta_regions); + log_info(gc)("Global GC moves %zu unaffiliated regions from young collector to old collector reserves", regions_to_transfer); + heap->free_set()->move_unaffiliated_regions_from_collector_to_old_collector(regions_to_transfer); + } + + heap->young_generation()->set_evacuation_reserve(young_evac_reserve); + heap->old_generation()->set_evacuation_reserve(old_evac_reserve); + heap->old_generation()->set_promoted_reserve(old_promo_reserve); } diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.hpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.hpp index 1f95f75c521..e0513f60da9 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.hpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.hpp @@ -39,9 +39,9 @@ class ShenandoahGlobalHeuristics : public ShenandoahGenerationalHeuristics { public: ShenandoahGlobalHeuristics(ShenandoahGlobalGeneration* generation); - void choose_collection_set_from_regiondata(ShenandoahCollectionSet* cset, - RegionData* data, size_t size, - size_t actual_free) override; + size_t choose_collection_set_from_regiondata(ShenandoahCollectionSet* cset, + RegionData* data, size_t size, + size_t actual_free) override; private: void choose_global_collection_set(ShenandoahCollectionSet* cset, diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.cpp index 478c5696188..aeb64b6f1df 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.cpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.cpp @@ -72,7 +72,7 @@ ShenandoahHeuristics::~ShenandoahHeuristics() { FREE_C_HEAP_ARRAY(RegionGarbage, _region_data); } -void ShenandoahHeuristics::choose_collection_set(ShenandoahCollectionSet* collection_set) { +size_t ShenandoahHeuristics::choose_collection_set(ShenandoahCollectionSet* collection_set) { ShenandoahHeap* heap = ShenandoahHeap::heap(); assert(collection_set->is_empty(), "Must be empty"); @@ -153,8 +153,8 @@ void ShenandoahHeuristics::choose_collection_set(ShenandoahCollectionSet* collec if (immediate_percent <= ShenandoahImmediateThreshold) { choose_collection_set_from_regiondata(collection_set, candidates, cand_idx, immediate_garbage + free); } - collection_set->summarize(total_garbage, immediate_garbage, immediate_regions); + return 0; } void ShenandoahHeuristics::record_cycle_start() { @@ -243,7 +243,7 @@ void ShenandoahHeuristics::record_success_concurrent() { adjust_penalty(Concurrent_Adjust); } -void ShenandoahHeuristics::record_success_degenerated() { +void ShenandoahHeuristics::record_degenerated() { adjust_penalty(Degenerated_Penalty); } diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.hpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.hpp index 3cd2cb1d171..ae34a9743a9 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.hpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.hpp @@ -129,6 +129,13 @@ protected: #endif } + inline void update_livedata(size_t live) { + _region_union._live_data = live; +#ifdef ASSERT + _union_tag = is_live_data; +#endif + } + inline ShenandoahHeapRegion* get_region() const { assert(_union_tag != is_uninitialized, "Cannot fetch region from uninitialized RegionData"); return _region; @@ -176,9 +183,12 @@ protected: static int compare_by_garbage(RegionData a, RegionData b); - virtual void choose_collection_set_from_regiondata(ShenandoahCollectionSet* set, - RegionData* data, size_t data_size, - size_t free) = 0; + // This is a helper function to choose_collection_set(), returning the number of regions that need to be transferred to + // the old reserve from the young reserve in order to effectively evacuate the chosen collection set. In non-generational + // mode, the return value is 0. + virtual size_t choose_collection_set_from_regiondata(ShenandoahCollectionSet* set, + RegionData* data, size_t data_size, + size_t free) = 0; void adjust_penalty(intx step); @@ -218,7 +228,7 @@ public: virtual void record_success_concurrent(); - virtual void record_success_degenerated(); + virtual void record_degenerated(); virtual void record_success_full(); @@ -226,7 +236,9 @@ public: virtual void record_requested_gc(); - virtual void choose_collection_set(ShenandoahCollectionSet* collection_set); + // Choose the collection set, returning the number of regions that need to be transferred to the old reserve from the young + // reserve in order to effectively evacuate the chosen collection set. In non-generational mode, the return value is 0. + virtual size_t choose_collection_set(ShenandoahCollectionSet* collection_set); virtual bool can_unload_classes(); diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.cpp index 8bf068df0a8..f47d0cbe819 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.cpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.cpp @@ -26,9 +26,11 @@ #include "gc/shenandoah/heuristics/shenandoahOldHeuristics.hpp" #include "gc/shenandoah/shenandoahCollectionSet.hpp" #include "gc/shenandoah/shenandoahCollectorPolicy.hpp" +#include "gc/shenandoah/shenandoahFreeSet.hpp" #include "gc/shenandoah/shenandoahGenerationalHeap.hpp" #include "gc/shenandoah/shenandoahHeapRegion.inline.hpp" #include "gc/shenandoah/shenandoahOldGeneration.hpp" +#include "gc/shenandoah/shenandoahYoungGeneration.hpp" #include "logging/log.hpp" #include "utilities/quickSort.hpp" @@ -77,18 +79,31 @@ ShenandoahOldHeuristics::ShenandoahOldHeuristics(ShenandoahOldGeneration* genera } bool ShenandoahOldHeuristics::prime_collection_set(ShenandoahCollectionSet* collection_set) { - if (unprocessed_old_collection_candidates() == 0) { - return false; - } + _mixed_evac_cset = collection_set; + _included_old_regions = 0; + _evacuated_old_bytes = 0; + _collected_old_bytes = 0; if (_old_generation->is_preparing_for_mark()) { // We have unprocessed old collection candidates, but the heuristic has given up on evacuating them. // This is most likely because they were _all_ pinned at the time of the last mixed evacuation (and // this in turn is most likely because there are just one or two candidate regions remaining). - log_info(gc, ergo)("Remaining " UINT32_FORMAT " old regions are being coalesced and filled", unprocessed_old_collection_candidates()); + log_info(gc, ergo)("Remaining " UINT32_FORMAT + " old regions are being coalesced and filled", unprocessed_old_collection_candidates()); return false; } + // Between consecutive mixed-evacuation cycles, the live data within each candidate region may change due to + // promotions and old-gen evacuations. Re-sort the candidate regions in order to first evacuate regions that have + // the smallest amount of live data. These are easiest to evacuate with least effort. Doing these first allows + // us to more quickly replenish free memory with empty regions. + for (uint i = _next_old_collection_candidate; i < _last_old_collection_candidate; i++) { + ShenandoahHeapRegion* r = _region_data[i].get_region(); + _region_data[i].update_livedata(r->get_mixed_candidate_live_data_bytes()); + } + QuickSort::sort(_region_data + _next_old_collection_candidate, unprocessed_old_collection_candidates(), + compare_by_live); + _first_pinned_candidate = NOT_FOUND; uint included_old_regions = 0; @@ -100,150 +115,44 @@ bool ShenandoahOldHeuristics::prime_collection_set(ShenandoahCollectionSet* coll // of memory that can still be evacuated. We address this by reducing the evacuation budget by the amount // of live memory in that region and by the amount of unallocated memory in that region if the evacuation // budget is constrained by availability of free memory. - const size_t old_evacuation_reserve = _old_generation->get_evacuation_reserve(); - const size_t old_evacuation_budget = (size_t) ((double) old_evacuation_reserve / ShenandoahOldEvacWaste); - size_t unfragmented_available = _old_generation->free_unaffiliated_regions() * ShenandoahHeapRegion::region_size_bytes(); - size_t fragmented_available; - size_t excess_fragmented_available; + _old_evacuation_reserve = _old_generation->get_evacuation_reserve(); + _old_evacuation_budget = (size_t) ((double) _old_evacuation_reserve / ShenandoahOldEvacWaste); - if (unfragmented_available > old_evacuation_budget) { - unfragmented_available = old_evacuation_budget; - fragmented_available = 0; - excess_fragmented_available = 0; + // fragmented_available is the amount of memory within partially consumed old regions that may be required to + // hold the results of old evacuations. If all of the memory required by the old evacuation reserve is available + // in unfragmented regions (unaffiliated old regions), then fragmented_available is zero because we do not need + // to evacuate into the existing partially consumed old regions. + + // if fragmented_available is non-zero, excess_fragmented_old_budget represents the amount of fragmented memory + // that is available within old, but is not required to hold the resuilts of old evacuation. As old-gen regions + // are added into the collection set, their free memory is subtracted from excess_fragmented_old_budget until the + // excess is exhausted. For old-gen regions subsequently added to the collection set, their free memory is + // subtracted from fragmented_available and from the old_evacuation_budget (since the budget decreases when this + // fragmented_available memory decreases). After fragmented_available has been exhausted, any further old regions + // selected for the cset do not further decrease the old_evacuation_budget because all further evacuation is targeted + // to unfragmented regions. + + size_t unaffiliated_available = _old_generation->free_unaffiliated_regions() * ShenandoahHeapRegion::region_size_bytes(); + if (unaffiliated_available > _old_evacuation_reserve) { + _unspent_unfragmented_old_budget = _old_evacuation_budget; + _unspent_fragmented_old_budget = 0; + _excess_fragmented_old_budget = 0; } else { - assert(_old_generation->available() >= old_evacuation_budget, "Cannot budget more than is available"); - fragmented_available = _old_generation->available() - unfragmented_available; - assert(fragmented_available + unfragmented_available >= old_evacuation_budget, "Budgets do not add up"); - if (fragmented_available + unfragmented_available > old_evacuation_budget) { - excess_fragmented_available = (fragmented_available + unfragmented_available) - old_evacuation_budget; - fragmented_available -= excess_fragmented_available; + assert(_old_generation->available() >= _old_evacuation_reserve, "Cannot reserve more than is available"); + size_t affiliated_available = _old_generation->available() - unaffiliated_available; + assert(affiliated_available + unaffiliated_available >= _old_evacuation_reserve, "Budgets do not add up"); + if (affiliated_available + unaffiliated_available > _old_evacuation_reserve) { + _excess_fragmented_old_budget = (affiliated_available + unaffiliated_available) - _old_evacuation_reserve; + affiliated_available -= _excess_fragmented_old_budget; } + _unspent_fragmented_old_budget = (size_t) ((double) affiliated_available / ShenandoahOldEvacWaste); + _unspent_unfragmented_old_budget = (size_t) ((double) unaffiliated_available / ShenandoahOldEvacWaste); } - size_t remaining_old_evacuation_budget = old_evacuation_budget; - log_debug(gc)("Choose old regions for mixed collection: old evacuation budget: %zu%s, candidates: %u", - byte_size_in_proper_unit(old_evacuation_budget), proper_unit_for_byte_size(old_evacuation_budget), + log_debug(gc)("Choose old regions for mixed collection: old evacuation budget: " PROPERFMT ", candidates: %u", + PROPERFMTARGS(_old_evacuation_budget), unprocessed_old_collection_candidates()); - - size_t lost_evacuation_capacity = 0; - - // The number of old-gen regions that were selected as candidates for collection at the end of the most recent old-gen - // concurrent marking phase and have not yet been collected is represented by unprocessed_old_collection_candidates(). - // Candidate regions are ordered according to increasing amount of live data. If there is not sufficient room to - // evacuate region N, then there is no need to even consider evacuating region N+1. - while (unprocessed_old_collection_candidates() > 0) { - // Old collection candidates are sorted in order of decreasing garbage contained therein. - ShenandoahHeapRegion* r = next_old_collection_candidate(); - if (r == nullptr) { - break; - } - assert(r->is_regular(), "There should be no humongous regions in the set of mixed-evac candidates"); - - // If region r is evacuated to fragmented memory (to free memory within a partially used region), then we need - // to decrease the capacity of the fragmented memory by the scaled loss. - - const size_t live_data_for_evacuation = r->get_live_data_bytes(); - size_t lost_available = r->free(); - - if ((lost_available > 0) && (excess_fragmented_available > 0)) { - if (lost_available < excess_fragmented_available) { - excess_fragmented_available -= lost_available; - lost_evacuation_capacity -= lost_available; - lost_available = 0; - } else { - lost_available -= excess_fragmented_available; - lost_evacuation_capacity -= excess_fragmented_available; - excess_fragmented_available = 0; - } - } - size_t scaled_loss = (size_t) ((double) lost_available / ShenandoahOldEvacWaste); - if ((lost_available > 0) && (fragmented_available > 0)) { - if (scaled_loss + live_data_for_evacuation < fragmented_available) { - fragmented_available -= scaled_loss; - scaled_loss = 0; - } else { - // We will have to allocate this region's evacuation memory from unfragmented memory, so don't bother - // to decrement scaled_loss - } - } - if (scaled_loss > 0) { - // We were not able to account for the lost free memory within fragmented memory, so we need to take this - // allocation out of unfragmented memory. Unfragmented memory does not need to account for loss of free. - if (live_data_for_evacuation > unfragmented_available) { - // There is no room to evacuate this region or any that come after it in within the candidates array. - log_debug(gc, cset)("Not enough unfragmented memory (%zu) to hold evacuees (%zu) from region: (%zu)", - unfragmented_available, live_data_for_evacuation, r->index()); - break; - } else { - unfragmented_available -= live_data_for_evacuation; - } - } else { - // Since scaled_loss == 0, we have accounted for the loss of free memory, so we can allocate from either - // fragmented or unfragmented available memory. Use up the fragmented memory budget first. - size_t evacuation_need = live_data_for_evacuation; - - if (evacuation_need > fragmented_available) { - evacuation_need -= fragmented_available; - fragmented_available = 0; - } else { - fragmented_available -= evacuation_need; - evacuation_need = 0; - } - if (evacuation_need > unfragmented_available) { - // There is no room to evacuate this region or any that come after it in within the candidates array. - log_debug(gc, cset)("Not enough unfragmented memory (%zu) to hold evacuees (%zu) from region: (%zu)", - unfragmented_available, live_data_for_evacuation, r->index()); - break; - } else { - unfragmented_available -= evacuation_need; - // dead code: evacuation_need == 0; - } - } - collection_set->add_region(r); - included_old_regions++; - evacuated_old_bytes += live_data_for_evacuation; - collected_old_bytes += r->garbage(); - consume_old_collection_candidate(); - } - - if (_first_pinned_candidate != NOT_FOUND) { - // Need to deal with pinned regions - slide_pinned_regions_to_front(); - } - decrease_unprocessed_old_collection_candidates_live_memory(evacuated_old_bytes); - if (included_old_regions > 0) { - log_info(gc, ergo)("Old-gen piggyback evac (" UINT32_FORMAT " regions, evacuating " PROPERFMT ", reclaiming: " PROPERFMT ")", - included_old_regions, PROPERFMTARGS(evacuated_old_bytes), PROPERFMTARGS(collected_old_bytes)); - } - - if (unprocessed_old_collection_candidates() == 0) { - // We have added the last of our collection candidates to a mixed collection. - // Any triggers that occurred during mixed evacuations may no longer be valid. They can retrigger if appropriate. - clear_triggers(); - - _old_generation->complete_mixed_evacuations(); - } else if (included_old_regions == 0) { - // We have candidates, but none were included for evacuation - are they all pinned? - // or did we just not have enough room for any of them in this collection set? - // We don't want a region with a stuck pin to prevent subsequent old collections, so - // if they are all pinned we transition to a state that will allow us to make these uncollected - // (pinned) regions parsable. - if (all_candidates_are_pinned()) { - log_info(gc, ergo)("All candidate regions " UINT32_FORMAT " are pinned", unprocessed_old_collection_candidates()); - _old_generation->abandon_mixed_evacuations(); - } else { - log_info(gc, ergo)("No regions selected for mixed collection. " - "Old evacuation budget: " PROPERFMT ", Remaining evacuation budget: " PROPERFMT - ", Lost capacity: " PROPERFMT - ", Next candidate: " UINT32_FORMAT ", Last candidate: " UINT32_FORMAT, - PROPERFMTARGS(old_evacuation_reserve), - PROPERFMTARGS(remaining_old_evacuation_budget), - PROPERFMTARGS(lost_evacuation_capacity), - _next_old_collection_candidate, _last_old_collection_candidate); - } - } - - return (included_old_regions > 0); + return add_old_regions_to_cset(); } bool ShenandoahOldHeuristics::all_candidates_are_pinned() { @@ -317,6 +226,187 @@ void ShenandoahOldHeuristics::slide_pinned_regions_to_front() { _next_old_collection_candidate = write_index + 1; } +bool ShenandoahOldHeuristics::add_old_regions_to_cset() { + if (unprocessed_old_collection_candidates() == 0) { + return false; + } + _first_pinned_candidate = NOT_FOUND; + + // The number of old-gen regions that were selected as candidates for collection at the end of the most recent old-gen + // concurrent marking phase and have not yet been collected is represented by unprocessed_old_collection_candidates(). + // Candidate regions are ordered according to increasing amount of live data. If there is not sufficient room to + // evacuate region N, then there is no need to even consider evacuating region N+1. + while (unprocessed_old_collection_candidates() > 0) { + // Old collection candidates are sorted in order of decreasing garbage contained therein. + ShenandoahHeapRegion* r = next_old_collection_candidate(); + if (r == nullptr) { + break; + } + assert(r->is_regular(), "There should be no humongous regions in the set of mixed-evac candidates"); + + // If region r is evacuated to fragmented memory (to free memory within a partially used region), then we need + // to decrease the capacity of the fragmented memory by the scaled loss. + + const size_t live_data_for_evacuation = r->get_live_data_bytes(); + size_t lost_available = r->free(); + + ssize_t fragmented_delta = 0; + ssize_t unfragmented_delta = 0; + ssize_t excess_delta = 0; + + // We must decrease our mixed-evacuation budgets proportional to the lost available memory. This memory that is no + // longer available was likely "promised" to promotions, so we must decrease our mixed evacuations now. + // (e.g. if we loose 14 bytes of available old memory, we must decrease the evacuation budget by 10 bytes.) + size_t scaled_loss = (size_t) (((double) lost_available) / ShenandoahOldEvacWaste); + if (lost_available > 0) { + // We need to subtract lost_available from our working evacuation budgets + if (scaled_loss < _excess_fragmented_old_budget) { + excess_delta -= scaled_loss; + _excess_fragmented_old_budget -= scaled_loss; + } else { + excess_delta -= _excess_fragmented_old_budget; + _excess_fragmented_old_budget = 0; + } + + if (scaled_loss < _unspent_fragmented_old_budget) { + _unspent_fragmented_old_budget -= scaled_loss; + fragmented_delta = -scaled_loss; + scaled_loss = 0; + } else { + scaled_loss -= _unspent_fragmented_old_budget; + fragmented_delta = -_unspent_fragmented_old_budget; + _unspent_fragmented_old_budget = 0; + } + + if (scaled_loss < _unspent_unfragmented_old_budget) { + _unspent_unfragmented_old_budget -= scaled_loss; + unfragmented_delta = -scaled_loss; + scaled_loss = 0; + } else { + scaled_loss -= _unspent_unfragmented_old_budget; + fragmented_delta = -_unspent_unfragmented_old_budget; + _unspent_unfragmented_old_budget = 0; + } + } + + // Allocate replica from unfragmented memory if that exists + size_t evacuation_need = live_data_for_evacuation; + if (evacuation_need < _unspent_unfragmented_old_budget) { + _unspent_unfragmented_old_budget -= evacuation_need; + } else { + if (_unspent_unfragmented_old_budget > 0) { + evacuation_need -= _unspent_unfragmented_old_budget; + unfragmented_delta -= _unspent_unfragmented_old_budget; + _unspent_unfragmented_old_budget = 0; + } + // Take the remaining allocation out of fragmented available + if (_unspent_fragmented_old_budget > evacuation_need) { + _unspent_fragmented_old_budget -= evacuation_need; + } else { + // We cannot add this region into the collection set. We're done. Undo the adjustments to available. + _unspent_fragmented_old_budget -= fragmented_delta; + _unspent_unfragmented_old_budget -= unfragmented_delta; + _excess_fragmented_old_budget -= excess_delta; + break; + } + } + _mixed_evac_cset->add_region(r); + _included_old_regions++; + _evacuated_old_bytes += live_data_for_evacuation; + _collected_old_bytes += r->garbage(); + consume_old_collection_candidate(); + } + return true; +} + +bool ShenandoahOldHeuristics::finalize_mixed_evacs() { + if (_first_pinned_candidate != NOT_FOUND) { + // Need to deal with pinned regions + slide_pinned_regions_to_front(); + } + decrease_unprocessed_old_collection_candidates_live_memory(_evacuated_old_bytes); + if (_included_old_regions > 0) { + log_info(gc)("Old-gen mixed evac (%zu regions, evacuating %zu%s, reclaiming: %zu%s)", + _included_old_regions, + byte_size_in_proper_unit(_evacuated_old_bytes), proper_unit_for_byte_size(_evacuated_old_bytes), + byte_size_in_proper_unit(_collected_old_bytes), proper_unit_for_byte_size(_collected_old_bytes)); + } + + if (unprocessed_old_collection_candidates() == 0) { + // We have added the last of our collection candidates to a mixed collection. + // Any triggers that occurred during mixed evacuations may no longer be valid. They can retrigger if appropriate. + clear_triggers(); + _old_generation->complete_mixed_evacuations(); + } else if (_included_old_regions == 0) { + // We have candidates, but none were included for evacuation - are they all pinned? + // or did we just not have enough room for any of them in this collection set? + // We don't want a region with a stuck pin to prevent subsequent old collections, so + // if they are all pinned we transition to a state that will allow us to make these uncollected + // (pinned) regions parsable. + if (all_candidates_are_pinned()) { + log_info(gc)("All candidate regions " UINT32_FORMAT " are pinned", unprocessed_old_collection_candidates()); + _old_generation->abandon_mixed_evacuations(); + } else { + log_info(gc)("No regions selected for mixed collection. " + "Old evacuation budget: " PROPERFMT ", Next candidate: " UINT32_FORMAT ", Last candidate: " UINT32_FORMAT, + PROPERFMTARGS(_old_evacuation_reserve), + _next_old_collection_candidate, _last_old_collection_candidate); + } + } + return (_included_old_regions > 0); +} + +bool ShenandoahOldHeuristics::top_off_collection_set(size_t &add_regions_to_old) { + if (unprocessed_old_collection_candidates() == 0) { + add_regions_to_old = 0; + return false; + } else { + ShenandoahYoungGeneration* young_generation = _heap->young_generation(); + size_t young_unaffiliated_regions = young_generation->free_unaffiliated_regions(); + size_t max_young_cset = young_generation->get_evacuation_reserve(); + + // We have budgeted to assure the live_bytes_in_tenurable_regions() get evacuated into old generation. Young reserves + // only for untenurable region evacuations. + size_t planned_young_evac = _mixed_evac_cset->get_live_bytes_in_untenurable_regions(); + size_t consumed_from_young_cset = (size_t) (planned_young_evac * ShenandoahEvacWaste); + + size_t region_size_bytes = ShenandoahHeapRegion::region_size_bytes(); + size_t regions_required_for_collector_reserve = (consumed_from_young_cset + region_size_bytes - 1) / region_size_bytes; + + assert(consumed_from_young_cset <= max_young_cset, "sanity"); + assert(max_young_cset <= young_unaffiliated_regions * region_size_bytes, "sanity"); + + size_t regions_for_old_expansion; + if (consumed_from_young_cset < max_young_cset) { + size_t excess_young_reserves = max_young_cset - consumed_from_young_cset; + // We can only transfer empty regions from young to old. Furthermore, we must be careful to assure that the young + // Collector reserve that remains after transfer is comprised entirely of empty (unaffiliated) regions. + size_t consumed_unaffiliated_regions = (consumed_from_young_cset + region_size_bytes - 1) / region_size_bytes; + size_t available_unaffiliated_regions = ((young_unaffiliated_regions > consumed_unaffiliated_regions)? + young_unaffiliated_regions - consumed_unaffiliated_regions: 0); + regions_for_old_expansion = MIN2(available_unaffiliated_regions, excess_young_reserves / region_size_bytes); + } else { + regions_for_old_expansion = 0; + } + if (regions_for_old_expansion > 0) { + log_info(gc)("Augmenting old-gen evacuation budget from unexpended young-generation reserve by %zu regions", + regions_for_old_expansion); + add_regions_to_old = regions_for_old_expansion; + size_t budget_supplement = region_size_bytes * regions_for_old_expansion; + size_t supplement_without_waste = (size_t) (((double) budget_supplement) / ShenandoahOldEvacWaste); + _old_evacuation_budget += supplement_without_waste; + _unspent_unfragmented_old_budget += supplement_without_waste; + _old_generation->augment_evacuation_reserve(budget_supplement); + young_generation->set_evacuation_reserve(max_young_cset - budget_supplement); + + return add_old_regions_to_cset(); + } else { + add_regions_to_old = 0; + return false; + } + } +} + void ShenandoahOldHeuristics::prepare_for_old_collections() { ShenandoahHeap* heap = ShenandoahHeap::heap(); @@ -325,7 +415,6 @@ void ShenandoahOldHeuristics::prepare_for_old_collections() { size_t immediate_garbage = 0; size_t immediate_regions = 0; size_t live_data = 0; - RegionData* candidates = _region_data; for (size_t i = 0; i < num_regions; i++) { ShenandoahHeapRegion* region = heap->get_region(i); @@ -344,10 +433,10 @@ void ShenandoahOldHeuristics::prepare_for_old_collections() { // 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. - // Pinned regions cannot be evacuated, but we are not actually choosing candidates - // for the collection set here. That happens later during the next young GC cycle, - // by which time, the pinned region may no longer be pinned. + // Only place regular or pinned regions with live data into the candidate set. + // Pinned regions cannot be evacuated, but we are not actually choosing candidates + // for the collection set here. That happens later during the next young GC cycle, + // by which time, the pinned region may no longer be pinned. if (!region->has_live()) { assert(!region->is_pinned(), "Pinned region should have live (pinned) objects."); region->make_trash_immediate(); @@ -414,6 +503,8 @@ void ShenandoahOldHeuristics::prepare_for_old_collections() { ShenandoahHeapRegion* r = candidates[i].get_region(); size_t region_garbage = r->garbage(); size_t region_free = r->free(); + + r->capture_mixed_candidate_garbage(); candidates_garbage += region_garbage; unfragmented += region_free; } @@ -456,6 +547,8 @@ void ShenandoahOldHeuristics::prepare_for_old_collections() { r->index(), ShenandoahHeapRegion::region_state_to_string(r->state())); const size_t region_garbage = r->garbage(); const size_t region_free = r->free(); + + r->capture_mixed_candidate_garbage(); candidates_garbage += region_garbage; unfragmented += region_free; defrag_count++; @@ -546,6 +639,7 @@ unsigned int ShenandoahOldHeuristics::get_coalesce_and_fill_candidates(Shenandoa void ShenandoahOldHeuristics::abandon_collection_candidates() { _last_old_collection_candidate = 0; _next_old_collection_candidate = 0; + _live_bytes_in_unprocessed_candidates = 0; _last_old_region = 0; } @@ -766,10 +860,10 @@ void ShenandoahOldHeuristics::record_success_concurrent() { this->ShenandoahHeuristics::record_success_concurrent(); } -void ShenandoahOldHeuristics::record_success_degenerated() { +void ShenandoahOldHeuristics::record_degenerated() { // Forget any triggers that occurred while OLD GC was ongoing. If we really need to start another, it will retrigger. clear_triggers(); - this->ShenandoahHeuristics::record_success_degenerated(); + this->ShenandoahHeuristics::record_degenerated(); } void ShenandoahOldHeuristics::record_success_full() { @@ -790,8 +884,9 @@ bool ShenandoahOldHeuristics::is_experimental() { return true; } -void ShenandoahOldHeuristics::choose_collection_set_from_regiondata(ShenandoahCollectionSet* set, - ShenandoahHeuristics::RegionData* data, - size_t data_size, size_t free) { +size_t ShenandoahOldHeuristics::choose_collection_set_from_regiondata(ShenandoahCollectionSet* set, + ShenandoahHeuristics::RegionData* data, + size_t data_size, size_t free) { ShouldNotReachHere(); + return 0; } diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.hpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.hpp index 288d3d68e56..97a5b1ebf24 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.hpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahOldHeuristics.hpp @@ -102,6 +102,30 @@ private: size_t _fragmentation_first_old_region; size_t _fragmentation_last_old_region; + // State variables involved in construction of a mixed-evacuation collection set. These variables are initialized + // when client code invokes prime_collection_set(). They are consulted, and sometimes modified, when client code + // calls top_off_collection_set() to possibly expand the number of old-gen regions in a mixed evacuation cset, and by + // finalize_mixed_evacs(), which prepares the way for mixed evacuations to begin. + ShenandoahCollectionSet* _mixed_evac_cset; + size_t _evacuated_old_bytes; + size_t _collected_old_bytes; + size_t _included_old_regions; + size_t _old_evacuation_reserve; + size_t _old_evacuation_budget; + + // This represents the amount of memory that can be evacuated from old into initially empty regions during a mixed evacuation. + // This is the total amount of unfragmented free memory in old divided by ShenandoahOldEvacWaste. + size_t _unspent_unfragmented_old_budget; + + // This represents the amount of memory that can be evacuated from old into initially non-empty regions during a mixed + // evacuation. This is the total amount of initially fragmented free memory in old divided by ShenandoahOldEvacWaste. + size_t _unspent_fragmented_old_budget; + + // If there is more available memory in old than is required by the intended mixed evacuation, the amount of excess + // memory is represented by _excess_fragmented_old. To convert this value into a promotion budget, multiply by + // ShenandoahOldEvacWaste and divide by ShenandoahPromoWaste. + size_t _excess_fragmented_old_budget; + // The value of command-line argument ShenandoahOldGarbageThreshold represents the percent of garbage that must // be present within an old-generation region before that region is considered a good candidate for inclusion in // the collection set under normal circumstances. For our purposes, normal circustances are when the memory consumed @@ -131,7 +155,15 @@ private: void set_trigger_if_old_is_overgrown(); protected: - void choose_collection_set_from_regiondata(ShenandoahCollectionSet* set, RegionData* data, size_t data_size, size_t free) override; + size_t + choose_collection_set_from_regiondata(ShenandoahCollectionSet* set, RegionData* data, size_t data_size, size_t free) override; + + // This internal helper routine adds as many mixed evacuation candidate regions as fit within the old-gen evacuation budget + // to the collection set. This may be called twice to prepare for any given mixed evacuation cycle, the first time with + // a conservative old evacuation budget, and the second time with a larger more aggressive old evacuation budget. Returns + // true iff we need to finalize mixed evacs. (If no regions are added to the collection set, there is no need to finalize + // mixed evacuations.) + bool add_old_regions_to_cset(); public: explicit ShenandoahOldHeuristics(ShenandoahOldGeneration* generation, ShenandoahGenerationalHeap* gen_heap); @@ -139,8 +171,22 @@ public: // Prepare for evacuation of old-gen regions by capturing the mark results of a recently completed concurrent mark pass. void prepare_for_old_collections(); - // Return true iff the collection set is primed with at least one old-gen region. - bool prime_collection_set(ShenandoahCollectionSet* set); + // Initialize instance variables to support the preparation of a mixed-evacuation collection set. Adds as many + // old candidate regions into the collection set as can fit within the iniital conservative old evacuation budget. + // Returns true iff we need to finalize mixed evacs. + bool prime_collection_set(ShenandoahCollectionSet* collection_set); + + // If young evacuation did not consume all of its available evacuation reserve, add as many additional mixed- + // evacuation candidate regions into the collection set as will fit within this excess repurposed reserved. + // Returns true iff we need to finalize mixed evacs. Upon return, the var parameter regions_to_xfer holds the + // number of regions to transfer from young to old. + bool top_off_collection_set(size_t &add_regions_to_old); + + // Having added all eligible mixed-evacuation candidates to the collection set, this function updates the total count + // of how much old-gen memory remains to be evacuated and adjusts the representation of old-gen regions that remain to + // be evacuated, giving special attention to regions that are currently pinned. It outputs relevant log messages and + // returns true iff the collection set holds at least one unpinned mixed evacuation candidate. + bool finalize_mixed_evacs(); // How many old-collection candidates have not yet been processed? uint unprocessed_old_collection_candidates() const; @@ -201,7 +247,7 @@ public: void record_success_concurrent() override; - void record_success_degenerated() override; + void record_degenerated() override; void record_success_full() override; diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahPassiveHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahPassiveHeuristics.cpp index b5e9cc433ea..d4a38278161 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahPassiveHeuristics.cpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahPassiveHeuristics.cpp @@ -50,9 +50,9 @@ bool ShenandoahPassiveHeuristics::should_degenerate_cycle() { return ShenandoahDegeneratedGC; } -void ShenandoahPassiveHeuristics::choose_collection_set_from_regiondata(ShenandoahCollectionSet* cset, - RegionData* data, size_t size, - size_t actual_free) { +size_t ShenandoahPassiveHeuristics::choose_collection_set_from_regiondata(ShenandoahCollectionSet* cset, + RegionData* data, size_t size, + size_t actual_free) { assert(ShenandoahDegeneratedGC, "This path is only taken for Degenerated GC"); // Do not select too large CSet that would overflow the available free space. @@ -76,4 +76,5 @@ void ShenandoahPassiveHeuristics::choose_collection_set_from_regiondata(Shenando cset->add_region(r); } } + return 0; } diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahPassiveHeuristics.hpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahPassiveHeuristics.hpp index be4e91b1800..7a64fad7cc9 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahPassiveHeuristics.hpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahPassiveHeuristics.hpp @@ -46,9 +46,9 @@ public: virtual bool should_degenerate_cycle(); - virtual void choose_collection_set_from_regiondata(ShenandoahCollectionSet* set, - RegionData* data, size_t data_size, - size_t free); + virtual size_t choose_collection_set_from_regiondata(ShenandoahCollectionSet* set, + RegionData* data, size_t data_size, + size_t free); virtual const char* name() { return "Passive"; } virtual bool is_diagnostic() { return true; } diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahStaticHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahStaticHeuristics.cpp index d4d66fef6a1..3843e434781 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahStaticHeuristics.cpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahStaticHeuristics.cpp @@ -59,9 +59,9 @@ bool ShenandoahStaticHeuristics::should_start_gc() { return ShenandoahHeuristics::should_start_gc(); } -void ShenandoahStaticHeuristics::choose_collection_set_from_regiondata(ShenandoahCollectionSet* cset, - RegionData* data, size_t size, - size_t free) { +size_t ShenandoahStaticHeuristics::choose_collection_set_from_regiondata(ShenandoahCollectionSet* cset, + RegionData* data, size_t size, + size_t free) { size_t threshold = ShenandoahHeapRegion::region_size_bytes() * ShenandoahGarbageThreshold / 100; for (size_t idx = 0; idx < size; idx++) { @@ -70,4 +70,5 @@ void ShenandoahStaticHeuristics::choose_collection_set_from_regiondata(Shenandoa cset->add_region(r); } } + return 0; } diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahStaticHeuristics.hpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahStaticHeuristics.hpp index 24cb5547921..27dc3c8e0ae 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahStaticHeuristics.hpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahStaticHeuristics.hpp @@ -40,9 +40,9 @@ public: virtual bool should_start_gc(); - virtual void choose_collection_set_from_regiondata(ShenandoahCollectionSet* cset, - RegionData* data, size_t size, - size_t free); + virtual size_t choose_collection_set_from_regiondata(ShenandoahCollectionSet* cset, + RegionData* data, size_t size, + size_t free); virtual const char* name() { return "Static"; } virtual bool is_diagnostic() { return false; } diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahYoungHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahYoungHeuristics.cpp index 15d1058d7cd..01c3873df72 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahYoungHeuristics.cpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahYoungHeuristics.cpp @@ -33,11 +33,11 @@ #include "utilities/quickSort.hpp" ShenandoahYoungHeuristics::ShenandoahYoungHeuristics(ShenandoahYoungGeneration* generation) - : ShenandoahGenerationalHeuristics(generation) { + : ShenandoahGenerationalHeuristics(generation) { } -void ShenandoahYoungHeuristics::choose_collection_set_from_regiondata(ShenandoahCollectionSet* cset, +size_t ShenandoahYoungHeuristics::choose_collection_set_from_regiondata(ShenandoahCollectionSet* cset, RegionData* data, size_t size, size_t actual_free) { // See comments in ShenandoahAdaptiveHeuristics::choose_collection_set_from_regiondata(): @@ -48,6 +48,8 @@ void ShenandoahYoungHeuristics::choose_collection_set_from_regiondata(Shenandoah // array before younger regions that typically contain more garbage. This is one reason why, // for example, we continue examining regions even after rejecting a region that has // more live data than we can evacuate. + ShenandoahGenerationalHeap* heap = ShenandoahGenerationalHeap::heap(); + bool need_to_finalize_mixed = heap->old_generation()->heuristics()->prime_collection_set(cset); // Better select garbage-first regions QuickSort::sort(data, (int) size, compare_by_garbage); @@ -55,6 +57,17 @@ void ShenandoahYoungHeuristics::choose_collection_set_from_regiondata(Shenandoah size_t cur_young_garbage = add_preselected_regions_to_collection_set(cset, data, size); choose_young_collection_set(cset, data, size, actual_free, cur_young_garbage); + + // Especially when young-gen trigger is expedited in order to finish mixed evacuations, there may not be + // enough consolidated garbage to make effective use of young-gen evacuation reserve. If there is still + // young-gen reserve available following selection of the young-gen collection set, see if we can use + // this memory to expand the old-gen evacuation collection set. + size_t add_regions_to_old; + need_to_finalize_mixed |= heap->old_generation()->heuristics()->top_off_collection_set(add_regions_to_old); + if (need_to_finalize_mixed) { + heap->old_generation()->heuristics()->finalize_mixed_evacs(); + } + return add_regions_to_old; } void ShenandoahYoungHeuristics::choose_young_collection_set(ShenandoahCollectionSet* cset, diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahYoungHeuristics.hpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahYoungHeuristics.hpp index b9d64059680..85587887663 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahYoungHeuristics.hpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahYoungHeuristics.hpp @@ -38,9 +38,9 @@ public: explicit ShenandoahYoungHeuristics(ShenandoahYoungGeneration* generation); - void choose_collection_set_from_regiondata(ShenandoahCollectionSet* cset, - RegionData* data, size_t size, - size_t actual_free) override; + size_t choose_collection_set_from_regiondata(ShenandoahCollectionSet* cset, + RegionData* data, size_t size, + size_t actual_free) override; bool should_start_gc() override; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.cpp b/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.cpp index e58a7f40796..c1c6b876d90 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.cpp @@ -50,6 +50,8 @@ ShenandoahCollectionSet::ShenandoahCollectionSet(ShenandoahHeap* heap, ReservedS _region_count(0), _old_garbage(0), _preselected_regions(nullptr), + _young_available_bytes_collected(0), + _old_available_bytes_collected(0), _current_index(0) { // The collection set map is reserved to cover the entire heap *and* zero addresses. @@ -104,6 +106,7 @@ void ShenandoahCollectionSet::add_region(ShenandoahHeapRegion* r) { } } else if (r->is_old()) { _old_bytes_to_evacuate += live; + _old_available_bytes_collected += free; _old_garbage += garbage; } @@ -140,6 +143,7 @@ void ShenandoahCollectionSet::clear() { _old_bytes_to_evacuate = 0; _young_available_bytes_collected = 0; + _old_available_bytes_collected = 0; _has_old_regions = false; } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.hpp b/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.hpp index a1b77baa2d3..c99271de1fb 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahCollectionSet.hpp @@ -75,6 +75,10 @@ private: // should be subtracted from what's available. size_t _young_available_bytes_collected; + // When a region having memory available to be allocated is added to the collection set, the region's available memory + // should be subtracted from what's available. + size_t _old_available_bytes_collected; + shenandoah_padding(0); volatile size_t _current_index; shenandoah_padding(1); @@ -121,6 +125,9 @@ public: // Returns the amount of free bytes in young regions in the collection set. size_t get_young_available_bytes_collected() const { return _young_available_bytes_collected; } + // Returns the amount of free bytes in old regions in the collection set. + size_t get_old_available_bytes_collected() const { return _old_available_bytes_collected; } + // Returns the amount of garbage in old regions in the collection set. inline size_t get_old_garbage() const; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp index cee8727a3f4..364279deafe 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp @@ -204,9 +204,8 @@ bool ShenandoahConcurrentGC::collect(GCCause::Cause cause) { return false; } - entry_concurrent_update_refs_prepare(heap); - // Perform update-refs phase. + entry_concurrent_update_refs_prepare(heap); if (ShenandoahVerify) { vmop_entry_init_update_refs(); } @@ -227,6 +226,7 @@ bool ShenandoahConcurrentGC::collect(GCCause::Cause cause) { // Update references freed up collection set, kick the cleanup to reclaim the space. entry_cleanup_complete(); } else { + _abbreviated = true; if (!entry_final_roots()) { assert(_degen_point != _degenerated_unset, "Need to know where to start degenerated cycle"); return false; @@ -235,7 +235,6 @@ bool ShenandoahConcurrentGC::collect(GCCause::Cause cause) { if (VerifyAfterGC) { vmop_entry_verify_final_roots(); } - _abbreviated = true; } // We defer generation resizing actions until after cset regions have been recycled. We do this even following an @@ -282,7 +281,6 @@ bool ShenandoahConcurrentGC::complete_abbreviated_cycle() { return true; } - void ShenandoahConcurrentGC::vmop_entry_init_mark() { ShenandoahHeap* const heap = ShenandoahHeap::heap(); TraceCollectorStats tcs(heap->monitoring_support()->stw_collection_counters()); @@ -536,6 +534,12 @@ void ShenandoahConcurrentGC::entry_cleanup_early() { // This phase does not use workers, no need for setup heap->try_inject_alloc_failure(); op_cleanup_early(); + if (!heap->is_evacuation_in_progress()) { + // This is an abbreviated cycle. Rebuild the freeset in order to establish reserves for the next GC cycle. Doing + // the rebuild ASAP also expedites availability of immediate trash, reducing the likelihood that we will degenerate + // during promote-in-place processing. + heap->rebuild_free_set(true /*concurrent*/); + } } void ShenandoahConcurrentGC::entry_evacuate() { diff --git a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp index 590e7831f07..bc11659c5e5 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp @@ -44,7 +44,7 @@ ShenandoahControlThread::ShenandoahControlThread() : ShenandoahController(), _requested_gc_cause(GCCause::_no_gc), _degen_point(ShenandoahGC::_degenerated_outside_cycle), - _control_lock(Mutex::nosafepoint - 2, "ShenandoahGCRequest_lock", true) { + _control_lock(CONTROL_LOCK_RANK, "ShenandoahControl_lock", true) { set_name("Shenandoah Control Thread"); create_and_start(); } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahController.hpp b/src/hotspot/share/gc/shenandoah/shenandoahController.hpp index a6a699fac3b..b8ff4df4771 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahController.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahController.hpp @@ -42,6 +42,9 @@ private: shenandoah_padding(1); protected: + const Mutex::Rank WAITERS_LOCK_RANK = Mutex::safepoint - 5; + const Mutex::Rank CONTROL_LOCK_RANK = Mutex::nosafepoint - 2; + // While we could have a single lock for these, it may risk unblocking // GC waiters when alloc failure GC cycle finishes. We want instead // to make complete explicit cycle for demanding customers. @@ -54,8 +57,8 @@ protected: public: ShenandoahController(): _gc_id(0), - _alloc_failure_waiters_lock(Mutex::safepoint-2, "ShenandoahAllocFailureGC_lock", true), - _gc_waiters_lock(Mutex::safepoint-2, "ShenandoahRequestedGC_lock", true) + _alloc_failure_waiters_lock(WAITERS_LOCK_RANK, "ShenandoahAllocFailureWaiters_lock", true), + _gc_waiters_lock(WAITERS_LOCK_RANK, "ShenandoahGCWaiters_lock", true) { } // Request a collection cycle. This handles "explicit" gc requests diff --git a/src/hotspot/share/gc/shenandoah/shenandoahDegeneratedGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahDegeneratedGC.cpp index cd079d29afe..333bdbc6e72 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahDegeneratedGC.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahDegeneratedGC.cpp @@ -313,8 +313,12 @@ void ShenandoahDegenGC::op_degenerated() { policy->record_degenerated(_generation->is_young(), _abbreviated, progress); if (progress) { heap->notify_gc_progress(); + _generation->heuristics()->record_degenerated(); } else if (!heap->mode()->is_generational() || policy->generational_should_upgrade_degenerated_gc()) { + // Upgrade to full GC, register full-GC impact on heuristics. op_degenerated_futile(); + } else { + _generation->heuristics()->record_degenerated(); } } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahFreeSet.cpp b/src/hotspot/share/gc/shenandoah/shenandoahFreeSet.cpp index c03e66e28da..c4fe9103fcb 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahFreeSet.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahFreeSet.cpp @@ -326,7 +326,7 @@ void ShenandoahRegionPartitions::initialize_old_collector() { } void ShenandoahRegionPartitions::make_all_regions_unavailable() { - shenandoah_assert_heaplocked(); + shenandoah_assert_heaplocked_or_safepoint(); for (size_t partition_id = 0; partition_id < IntNumPartitions; partition_id++) { _membership[partition_id].clear_all(); _leftmosts[partition_id] = _max; @@ -338,7 +338,7 @@ void ShenandoahRegionPartitions::make_all_regions_unavailable() { _empty_region_counts[partition_id] = 0; _used[partition_id] = 0; _humongous_waste[partition_id] = 0; - _available[partition_id] = FreeSetUnderConstruction; + _available[partition_id] = 0; } } @@ -439,6 +439,13 @@ void ShenandoahRegionPartitions::set_capacity_of(ShenandoahFreeSetPartitionId wh _available[int(which_partition)] = value - _used[int(which_partition)]; } +void ShenandoahRegionPartitions::set_used_by(ShenandoahFreeSetPartitionId which_partition, size_t value) { + shenandoah_assert_heaplocked(); + assert (which_partition < NumPartitions, "selected free set must be valid"); + _used[int(which_partition)] = value; + _available[int(which_partition)] = _capacity[int(which_partition)] - value; +} + void ShenandoahRegionPartitions::increase_capacity(ShenandoahFreeSetPartitionId which_partition, size_t bytes) { shenandoah_assert_heaplocked(); @@ -900,7 +907,7 @@ idx_t ShenandoahRegionPartitions::rightmost_empty(ShenandoahFreeSetPartitionId w #ifdef ASSERT -void ShenandoahRegionPartitions::assert_bounds(bool validate_totals) { +void ShenandoahRegionPartitions::assert_bounds() { size_t capacities[UIntNumPartitions]; size_t used[UIntNumPartitions]; @@ -936,7 +943,7 @@ void ShenandoahRegionPartitions::assert_bounds(bool validate_totals) { switch (partition) { case ShenandoahFreeSetPartitionId::NotFree: { - assert(!validate_totals || (capacity != _region_size_bytes), "Should not be retired if empty"); + assert(capacity != _region_size_bytes, "Should not be retired if empty"); ShenandoahHeapRegion* r = ShenandoahHeap::heap()->get_region(i); if (r->is_humongous()) { if (r->is_old()) { @@ -976,12 +983,12 @@ void ShenandoahRegionPartitions::assert_bounds(bool validate_totals) { case ShenandoahFreeSetPartitionId::Collector: case ShenandoahFreeSetPartitionId::OldCollector: { + ShenandoahHeapRegion* r = ShenandoahHeap::heap()->get_region(i); assert(capacity > 0, "free regions must have allocation capacity"); bool is_empty = (capacity == _region_size_bytes); regions[int(partition)]++; used[int(partition)] += _region_size_bytes - capacity; capacities[int(partition)] += _region_size_bytes; - if (i < leftmosts[int(partition)]) { leftmosts[int(partition)] = i; } @@ -1020,20 +1027,20 @@ void ShenandoahRegionPartitions::assert_bounds(bool validate_totals) { idx_t beg_off = leftmosts[int(ShenandoahFreeSetPartitionId::Mutator)]; idx_t end_off = rightmosts[int(ShenandoahFreeSetPartitionId::Mutator)]; assert (beg_off >= leftmost(ShenandoahFreeSetPartitionId::Mutator), - "Mutator free regions before the leftmost: %zd, bound %zd", + "Mutator free region before the leftmost: %zd, bound %zd", beg_off, leftmost(ShenandoahFreeSetPartitionId::Mutator)); assert (end_off <= rightmost(ShenandoahFreeSetPartitionId::Mutator), - "Mutator free regions past the rightmost: %zd, bound %zd", + "Mutator free region past the rightmost: %zd, bound %zd", end_off, rightmost(ShenandoahFreeSetPartitionId::Mutator)); beg_off = empty_leftmosts[int(ShenandoahFreeSetPartitionId::Mutator)]; end_off = empty_rightmosts[int(ShenandoahFreeSetPartitionId::Mutator)]; - assert (beg_off >= leftmost_empty(ShenandoahFreeSetPartitionId::Mutator), - "Mutator free empty regions before the leftmost: %zd, bound %zd", - beg_off, leftmost_empty(ShenandoahFreeSetPartitionId::Mutator)); - assert (end_off <= rightmost_empty(ShenandoahFreeSetPartitionId::Mutator), - "Mutator free empty regions past the rightmost: %zd, bound %zd", - end_off, rightmost_empty(ShenandoahFreeSetPartitionId::Mutator)); + assert (beg_off >= _leftmosts_empty[int(ShenandoahFreeSetPartitionId::Mutator)], + "free empty region (%zd) before the leftmost bound %zd", + beg_off, _leftmosts_empty[int(ShenandoahFreeSetPartitionId::Mutator)]); + assert (end_off <= _rightmosts_empty[int(ShenandoahFreeSetPartitionId::Mutator)], + "free empty region (%zd) past the rightmost bound %zd", + end_off, _rightmosts_empty[int(ShenandoahFreeSetPartitionId::Mutator)]); // Performance invariants. Failing these would not break the free partition, but performance would suffer. assert (leftmost(ShenandoahFreeSetPartitionId::Collector) <= _max, "leftmost in bounds: %zd < %zd", @@ -1053,20 +1060,20 @@ void ShenandoahRegionPartitions::assert_bounds(bool validate_totals) { beg_off = leftmosts[int(ShenandoahFreeSetPartitionId::Collector)]; end_off = rightmosts[int(ShenandoahFreeSetPartitionId::Collector)]; assert (beg_off >= leftmost(ShenandoahFreeSetPartitionId::Collector), - "Collector free regions before the leftmost: %zd, bound %zd", + "Collector free region before the leftmost: %zd, bound %zd", beg_off, leftmost(ShenandoahFreeSetPartitionId::Collector)); assert (end_off <= rightmost(ShenandoahFreeSetPartitionId::Collector), - "Collector free regions past the rightmost: %zd, bound %zd", + "Collector free region past the rightmost: %zd, bound %zd", end_off, rightmost(ShenandoahFreeSetPartitionId::Collector)); beg_off = empty_leftmosts[int(ShenandoahFreeSetPartitionId::Collector)]; end_off = empty_rightmosts[int(ShenandoahFreeSetPartitionId::Collector)]; assert (beg_off >= _leftmosts_empty[int(ShenandoahFreeSetPartitionId::Collector)], - "Collector free empty regions before the leftmost: %zd, bound %zd", - beg_off, leftmost_empty(ShenandoahFreeSetPartitionId::Collector)); + "Collector free empty region before the leftmost: %zd, bound %zd", + beg_off, _leftmosts_empty[int(ShenandoahFreeSetPartitionId::Collector)]); assert (end_off <= _rightmosts_empty[int(ShenandoahFreeSetPartitionId::Collector)], - "Collector free empty regions past the rightmost: %zd, bound %zd", - end_off, rightmost_empty(ShenandoahFreeSetPartitionId::Collector)); + "Collector free empty region past the rightmost: %zd, bound %zd", + end_off, _rightmosts_empty[int(ShenandoahFreeSetPartitionId::Collector)]); // Performance invariants. Failing these would not break the free partition, but performance would suffer. assert (leftmost(ShenandoahFreeSetPartitionId::OldCollector) <= _max, "OldCollector leftmost in bounds: %zd < %zd", @@ -1083,106 +1090,109 @@ void ShenandoahRegionPartitions::assert_bounds(bool validate_totals) { ShenandoahFreeSetPartitionId::OldCollector), "OldCollector rightmost region should be free: %zd", rightmost(ShenandoahFreeSetPartitionId::OldCollector)); + // Concurrent recycling of trash recycles a region (changing its state from is_trash to is_empty without the heap lock), + // If OldCollector partition is empty, leftmosts will both equal max, rightmosts will both equal zero. // Likewise for empty region partitions. beg_off = leftmosts[int(ShenandoahFreeSetPartitionId::OldCollector)]; end_off = rightmosts[int(ShenandoahFreeSetPartitionId::OldCollector)]; - assert (beg_off >= leftmost(ShenandoahFreeSetPartitionId::OldCollector), - "OldCollector free regions before the leftmost: %zd, bound %zd", + assert (beg_off >= leftmost(ShenandoahFreeSetPartitionId::OldCollector), "free regions before the leftmost: %zd, bound %zd", beg_off, leftmost(ShenandoahFreeSetPartitionId::OldCollector)); - assert (end_off <= rightmost(ShenandoahFreeSetPartitionId::OldCollector), - "OldCollector free regions past the rightmost: %zd, bound %zd", + assert (end_off <= rightmost(ShenandoahFreeSetPartitionId::OldCollector), "free regions past the rightmost: %zd, bound %zd", end_off, rightmost(ShenandoahFreeSetPartitionId::OldCollector)); beg_off = empty_leftmosts[int(ShenandoahFreeSetPartitionId::OldCollector)]; end_off = empty_rightmosts[int(ShenandoahFreeSetPartitionId::OldCollector)]; assert (beg_off >= _leftmosts_empty[int(ShenandoahFreeSetPartitionId::OldCollector)], - "OldCollector free empty regions before the leftmost: %zd, bound %zd", - beg_off, leftmost_empty(ShenandoahFreeSetPartitionId::OldCollector)); + "free empty region (%zd) before the leftmost bound %zd, region %s trash", + beg_off, _leftmosts_empty[int(ShenandoahFreeSetPartitionId::OldCollector)], + ((beg_off >= _max)? "out of bounds is not": + (ShenandoahHeap::heap()->get_region(_leftmosts_empty[int(ShenandoahFreeSetPartitionId::OldCollector)])->is_trash()? + "is": "is not"))); assert (end_off <= _rightmosts_empty[int(ShenandoahFreeSetPartitionId::OldCollector)], - "OldCollector free empty regions past the rightmost: %zd, bound %zd", - end_off, rightmost_empty(ShenandoahFreeSetPartitionId::OldCollector)); + "free empty region (%zd) past the rightmost bound %zd, region %s trash", + end_off, _rightmosts_empty[int(ShenandoahFreeSetPartitionId::OldCollector)], + ((end_off < 0)? "out of bounds is not" : + (ShenandoahHeap::heap()->get_region(_rightmosts_empty[int(ShenandoahFreeSetPartitionId::OldCollector)])->is_trash()? + "is": "is not"))); - if (validate_totals) { - // young_retired_regions need to be added to either Mutator or Collector partitions, 100% used. - // Give enough of young_retired_regions, young_retired_capacity, young_retired_user - // to the Mutator partition to top it off so that it matches the running totals. - // - // Give any remnants to the Collector partition. After topping off the Collector partition, its values - // should also match running totals. + // young_retired_regions need to be added to either Mutator or Collector partitions, 100% used. + // Give enough of young_retired_regions, young_retired_capacity, young_retired_user + // to the Mutator partition to top it off so that it matches the running totals. + // + // Give any remnants to the Collector partition. After topping off the Collector partition, its values + // should also match running totals. + assert(young_retired_regions * _region_size_bytes == young_retired_capacity, "sanity"); + assert(young_retired_capacity == young_retired_used, "sanity"); - assert(young_retired_regions * _region_size_bytes == young_retired_capacity, "sanity"); - assert(young_retired_capacity == young_retired_used, "sanity"); + assert(capacities[int(ShenandoahFreeSetPartitionId::OldCollector)] + == _capacity[int(ShenandoahFreeSetPartitionId::OldCollector)], "Old collector capacities must match (%zu != %zu)", + capacities[int(ShenandoahFreeSetPartitionId::OldCollector)], + _capacity[int(ShenandoahFreeSetPartitionId::OldCollector)]); + assert(used[int(ShenandoahFreeSetPartitionId::OldCollector)] + == _used[int(ShenandoahFreeSetPartitionId::OldCollector)], "Old collector used must match"); + assert(regions[int(ShenandoahFreeSetPartitionId::OldCollector)] + == _capacity[int(ShenandoahFreeSetPartitionId::OldCollector)] / _region_size_bytes, "Old collector regions must match"); + assert(_capacity[int(ShenandoahFreeSetPartitionId::OldCollector)] + >= _used[int(ShenandoahFreeSetPartitionId::OldCollector)], "Old Collector capacity must be >= used"); + assert(_available[int(ShenandoahFreeSetPartitionId::OldCollector)] == + (_capacity[int(ShenandoahFreeSetPartitionId::OldCollector)] - _used[int(ShenandoahFreeSetPartitionId::OldCollector)]), + "Old Collector available must equal capacity minus used"); + assert(_humongous_waste[int(ShenandoahFreeSetPartitionId::OldCollector)] == + humongous_waste[int(ShenandoahFreeSetPartitionId::OldCollector)], "Old Collector humongous waste must match"); + assert(_capacity[int(ShenandoahFreeSetPartitionId::Mutator)] >= capacities[int(ShenandoahFreeSetPartitionId::Mutator)], + "Capacity total must be >= counted tally"); + size_t mutator_capacity_shortfall = + _capacity[int(ShenandoahFreeSetPartitionId::Mutator)] - capacities[int(ShenandoahFreeSetPartitionId::Mutator)]; + assert(mutator_capacity_shortfall <= young_retired_capacity, "sanity"); + capacities[int(ShenandoahFreeSetPartitionId::Mutator)] += mutator_capacity_shortfall; + young_retired_capacity -= mutator_capacity_shortfall; + capacities[int(ShenandoahFreeSetPartitionId::Collector)] += young_retired_capacity; - assert(capacities[int(ShenandoahFreeSetPartitionId::OldCollector)] - == _capacity[int(ShenandoahFreeSetPartitionId::OldCollector)], "Old collector capacities must match"); - assert(used[int(ShenandoahFreeSetPartitionId::OldCollector)] - == _used[int(ShenandoahFreeSetPartitionId::OldCollector)], "Old collector used must match"); - assert(regions[int(ShenandoahFreeSetPartitionId::OldCollector)] - == _capacity[int(ShenandoahFreeSetPartitionId::OldCollector)] / _region_size_bytes, "Old collector regions must match"); - assert(_capacity[int(ShenandoahFreeSetPartitionId::OldCollector)] - >= _used[int(ShenandoahFreeSetPartitionId::OldCollector)], "Old Collector capacity must be >= used"); - assert(_available[int(ShenandoahFreeSetPartitionId::OldCollector)] == - (_capacity[int(ShenandoahFreeSetPartitionId::OldCollector)] - _used[int(ShenandoahFreeSetPartitionId::OldCollector)]), - "Old Collector available must equal capacity minus used"); - assert(_humongous_waste[int(ShenandoahFreeSetPartitionId::OldCollector)] == - humongous_waste[int(ShenandoahFreeSetPartitionId::OldCollector)], "Old Collector humongous waste must match"); + assert(_used[int(ShenandoahFreeSetPartitionId::Mutator)] >= used[int(ShenandoahFreeSetPartitionId::Mutator)], + "Used total must be >= counted tally"); + size_t mutator_used_shortfall = + _used[int(ShenandoahFreeSetPartitionId::Mutator)] - used[int(ShenandoahFreeSetPartitionId::Mutator)]; + assert(mutator_used_shortfall <= young_retired_used, "sanity"); + used[int(ShenandoahFreeSetPartitionId::Mutator)] += mutator_used_shortfall; + young_retired_used -= mutator_used_shortfall; + used[int(ShenandoahFreeSetPartitionId::Collector)] += young_retired_used; - assert(_capacity[int(ShenandoahFreeSetPartitionId::Mutator)] >= capacities[int(ShenandoahFreeSetPartitionId::Mutator)], - "Capacity total must be >= counted tally"); - size_t mutator_capacity_shortfall = - _capacity[int(ShenandoahFreeSetPartitionId::Mutator)] - capacities[int(ShenandoahFreeSetPartitionId::Mutator)]; - assert(mutator_capacity_shortfall <= young_retired_capacity, "sanity"); - capacities[int(ShenandoahFreeSetPartitionId::Mutator)] += mutator_capacity_shortfall; - young_retired_capacity -= mutator_capacity_shortfall; - capacities[int(ShenandoahFreeSetPartitionId::Collector)] += young_retired_capacity; + assert(_capacity[int(ShenandoahFreeSetPartitionId::Mutator)] / _region_size_bytes + >= regions[int(ShenandoahFreeSetPartitionId::Mutator)], "Region total must be >= counted tally"); + size_t mutator_regions_shortfall = (_capacity[int(ShenandoahFreeSetPartitionId::Mutator)] / _region_size_bytes + - regions[int(ShenandoahFreeSetPartitionId::Mutator)]); + assert(mutator_regions_shortfall <= young_retired_regions, "sanity"); + regions[int(ShenandoahFreeSetPartitionId::Mutator)] += mutator_regions_shortfall; + young_retired_regions -= mutator_regions_shortfall; + regions[int(ShenandoahFreeSetPartitionId::Collector)] += young_retired_regions; + assert(capacities[int(ShenandoahFreeSetPartitionId::Collector)] == _capacity[int(ShenandoahFreeSetPartitionId::Collector)], + "Collector capacities must match"); + assert(used[int(ShenandoahFreeSetPartitionId::Collector)] == _used[int(ShenandoahFreeSetPartitionId::Collector)], + "Collector used must match"); + assert(regions[int(ShenandoahFreeSetPartitionId::Collector)] + == _capacity[int(ShenandoahFreeSetPartitionId::Collector)] / _region_size_bytes, "Collector regions must match"); + assert(_capacity[int(ShenandoahFreeSetPartitionId::Collector)] >= _used[int(ShenandoahFreeSetPartitionId::Collector)], + "Collector Capacity must be >= used"); + assert(_available[int(ShenandoahFreeSetPartitionId::Collector)] == + (_capacity[int(ShenandoahFreeSetPartitionId::Collector)] - _used[int(ShenandoahFreeSetPartitionId::Collector)]), + "Collector Available must equal capacity minus used"); - assert(_used[int(ShenandoahFreeSetPartitionId::Mutator)] >= used[int(ShenandoahFreeSetPartitionId::Mutator)], - "Used total must be >= counted tally"); - size_t mutator_used_shortfall = - _used[int(ShenandoahFreeSetPartitionId::Mutator)] - used[int(ShenandoahFreeSetPartitionId::Mutator)]; - assert(mutator_used_shortfall <= young_retired_used, "sanity"); - used[int(ShenandoahFreeSetPartitionId::Mutator)] += mutator_used_shortfall; - young_retired_used -= mutator_used_shortfall; - used[int(ShenandoahFreeSetPartitionId::Collector)] += young_retired_used; - - assert(_capacity[int(ShenandoahFreeSetPartitionId::Mutator)] / _region_size_bytes - >= regions[int(ShenandoahFreeSetPartitionId::Mutator)], "Region total must be >= counted tally"); - size_t mutator_regions_shortfall = (_capacity[int(ShenandoahFreeSetPartitionId::Mutator)] / _region_size_bytes - - regions[int(ShenandoahFreeSetPartitionId::Mutator)]); - assert(mutator_regions_shortfall <= young_retired_regions, "sanity"); - regions[int(ShenandoahFreeSetPartitionId::Mutator)] += mutator_regions_shortfall; - young_retired_regions -= mutator_regions_shortfall; - regions[int(ShenandoahFreeSetPartitionId::Collector)] += young_retired_regions; - - assert(capacities[int(ShenandoahFreeSetPartitionId::Collector)] == _capacity[int(ShenandoahFreeSetPartitionId::Collector)], - "Collector capacities must match"); - assert(used[int(ShenandoahFreeSetPartitionId::Collector)] == _used[int(ShenandoahFreeSetPartitionId::Collector)], - "Collector used must match"); - assert(regions[int(ShenandoahFreeSetPartitionId::Collector)] - == _capacity[int(ShenandoahFreeSetPartitionId::Collector)] / _region_size_bytes, "Collector regions must match"); - assert(_capacity[int(ShenandoahFreeSetPartitionId::Collector)] >= _used[int(ShenandoahFreeSetPartitionId::Collector)], - "Collector Capacity must be >= used"); - assert(_available[int(ShenandoahFreeSetPartitionId::Collector)] == - (_capacity[int(ShenandoahFreeSetPartitionId::Collector)] - _used[int(ShenandoahFreeSetPartitionId::Collector)]), - "Collector Available must equal capacity minus used"); - - assert(capacities[int(ShenandoahFreeSetPartitionId::Mutator)] == _capacity[int(ShenandoahFreeSetPartitionId::Mutator)], - "Mutator capacities must match"); - assert(used[int(ShenandoahFreeSetPartitionId::Mutator)] == _used[int(ShenandoahFreeSetPartitionId::Mutator)], - "Mutator used must match"); - assert(regions[int(ShenandoahFreeSetPartitionId::Mutator)] - == _capacity[int(ShenandoahFreeSetPartitionId::Mutator)] / _region_size_bytes, "Mutator regions must match"); - assert(_capacity[int(ShenandoahFreeSetPartitionId::Mutator)] >= _used[int(ShenandoahFreeSetPartitionId::Mutator)], - "Mutator capacity must be >= used"); - assert(_available[int(ShenandoahFreeSetPartitionId::Mutator)] == - (_capacity[int(ShenandoahFreeSetPartitionId::Mutator)] - _used[int(ShenandoahFreeSetPartitionId::Mutator)]), - "Mutator available must equal capacity minus used"); - assert(_humongous_waste[int(ShenandoahFreeSetPartitionId::Mutator)] == young_humongous_waste, - "Mutator humongous waste must match"); - } + assert(capacities[int(ShenandoahFreeSetPartitionId::Mutator)] == _capacity[int(ShenandoahFreeSetPartitionId::Mutator)], + "Mutator capacities must match"); + assert(used[int(ShenandoahFreeSetPartitionId::Mutator)] == _used[int(ShenandoahFreeSetPartitionId::Mutator)], + "Mutator used must match"); + assert(regions[int(ShenandoahFreeSetPartitionId::Mutator)] + == _capacity[int(ShenandoahFreeSetPartitionId::Mutator)] / _region_size_bytes, "Mutator regions must match"); + assert(_capacity[int(ShenandoahFreeSetPartitionId::Mutator)] >= _used[int(ShenandoahFreeSetPartitionId::Mutator)], + "Mutator capacity must be >= used"); + assert(_available[int(ShenandoahFreeSetPartitionId::Mutator)] == + (_capacity[int(ShenandoahFreeSetPartitionId::Mutator)] - _used[int(ShenandoahFreeSetPartitionId::Mutator)]), + "Mutator available must equal capacity minus used"); + assert(_humongous_waste[int(ShenandoahFreeSetPartitionId::Mutator)] == young_humongous_waste, + "Mutator humongous waste must match"); } #endif @@ -1206,6 +1216,36 @@ ShenandoahFreeSet::ShenandoahFreeSet(ShenandoahHeap* heap, size_t max_regions) : clear_internal(); } +void ShenandoahFreeSet::move_unaffiliated_regions_from_collector_to_old_collector(ssize_t count) { + shenandoah_assert_heaplocked(); + size_t region_size_bytes = ShenandoahHeapRegion::region_size_bytes(); + + size_t old_capacity = _partitions.get_capacity(ShenandoahFreeSetPartitionId::OldCollector); + size_t collector_capacity = _partitions.get_capacity(ShenandoahFreeSetPartitionId::Collector); + if (count > 0) { + size_t ucount = count; + size_t bytes_moved = ucount * region_size_bytes; + assert(collector_capacity >= bytes_moved, "Cannot transfer"); + assert(_partitions.get_empty_region_counts(ShenandoahFreeSetPartitionId::Collector) >= ucount, + "Cannot transfer %zu of %zu", ucount, _partitions.get_empty_region_counts(ShenandoahFreeSetPartitionId::Collector)); + _partitions.decrease_empty_region_counts(ShenandoahFreeSetPartitionId::Collector, ucount); + _partitions.set_capacity_of(ShenandoahFreeSetPartitionId::Collector, collector_capacity - bytes_moved); + _partitions.set_capacity_of(ShenandoahFreeSetPartitionId::OldCollector, old_capacity + bytes_moved); + _partitions.increase_empty_region_counts(ShenandoahFreeSetPartitionId::OldCollector, ucount); + } else if (count < 0) { + size_t ucount = -count; + size_t bytes_moved = ucount * region_size_bytes; + assert(old_capacity >= bytes_moved, "Cannot transfer"); + assert(_partitions.get_empty_region_counts(ShenandoahFreeSetPartitionId::OldCollector) >= ucount, + "Cannot transfer %zu of %zu", ucount, _partitions.get_empty_region_counts(ShenandoahFreeSetPartitionId::OldCollector)); + _partitions.decrease_empty_region_counts(ShenandoahFreeSetPartitionId::OldCollector, ucount); + _partitions.set_capacity_of(ShenandoahFreeSetPartitionId::OldCollector, old_capacity - bytes_moved); + _partitions.set_capacity_of(ShenandoahFreeSetPartitionId::Collector, collector_capacity + bytes_moved); + _partitions.increase_empty_region_counts(ShenandoahFreeSetPartitionId::Collector, ucount); + } + // else, do nothing +} + // was pip_pad_bytes void ShenandoahFreeSet::add_promoted_in_place_region_to_old_collector(ShenandoahHeapRegion* region) { shenandoah_assert_heaplocked(); @@ -1261,7 +1301,7 @@ void ShenandoahFreeSet::add_promoted_in_place_region_to_old_collector(Shenandoah /* CollectorSizeChanged */ true, /* OldCollectorSizeChanged */ true, /* AffiliatedChangesAreYoungNeutral */ false, /* AffiliatedChangesAreGlobalNeutral */ true, /* UnaffiliatedChangesAreYoungNeutral */ true>(); - _partitions.assert_bounds(true); + _partitions.assert_bounds(); } template @@ -1496,9 +1536,12 @@ HeapWord* ShenandoahFreeSet::try_allocate_in(ShenandoahHeapRegion* r, Shenandoah return nullptr; } HeapWord* result = nullptr; + // We must call try_recycle_under_lock() even if !r->is_trash(). The reason is that if r is being recycled at this + // moment by a GC worker thread, it may appear to be not trash even though it has not yet been fully recycled. If + // we proceed without waiting for the worker to finish recycling the region, the worker thread may overwrite the + // region's affiliation with FREE after we set the region's affiliation to req.afiliation() below r->try_recycle_under_lock(); in_new_region = r->is_empty(); - if (in_new_region) { log_debug(gc, free)("Using new region (%zu) for %s (" PTR_FORMAT ").", r->index(), req.type_string(), p2i(&req)); @@ -1668,7 +1711,7 @@ HeapWord* ShenandoahFreeSet::try_allocate_in(ShenandoahHeapRegion* r, Shenandoah default: assert(false, "won't happen"); } - _partitions.assert_bounds(true); + _partitions.assert_bounds(); return result; } @@ -1799,6 +1842,7 @@ HeapWord* ShenandoahFreeSet::allocate_contiguous(ShenandoahAllocRequest& req, bo increase_bytes_allocated(waste_bytes); } } + _partitions.increase_used(ShenandoahFreeSetPartitionId::Mutator, total_used); increase_bytes_allocated(total_used); req.set_actual_size(words_size); @@ -1819,14 +1863,16 @@ HeapWord* ShenandoahFreeSet::allocate_contiguous(ShenandoahAllocRequest& req, bo /* CollectorSizeChanged */ false, /* OldCollectorSizeChanged */ false, /* AffiliatedChangesAreYoungNeutral */ false, /* AffiliatedChangesAreGlobalNeutral */ false, /* UnaffiliatedChangesAreYoungNeutral */ false>(); - _partitions.assert_bounds(true); + _partitions.assert_bounds(); return _heap->get_region(beg)->bottom(); } class ShenandoahRecycleTrashedRegionClosure final : public ShenandoahHeapRegionClosure { public: void heap_region_do(ShenandoahHeapRegion* r) { - r->try_recycle(); + if (r->is_trash()) { + r->try_recycle(); + } } bool is_thread_safe() { @@ -1861,7 +1907,7 @@ bool ShenandoahFreeSet::transfer_one_region_from_mutator_to_old_collector(size_t /* CollectorSizeChanged */ false, /* OldCollectorSizeChanged */ true, /* AffiliatedChangesAreYoungNeutral */ true, /* AffiliatedChangesAreGlobalNeutral */ true, /* UnaffiliatedChangesAreYoungNeutral */ false>(); - _partitions.assert_bounds(true); + _partitions.assert_bounds(); return true; } else { return false; @@ -1914,7 +1960,7 @@ bool ShenandoahFreeSet::flip_to_old_gc(ShenandoahHeapRegion* r) { /* CollectorSizeChanged */ false, /* OldCollectorSizeChanged */ true, /* AffiliatedChangesAreYoungNeutral */ true, /* AffiliatedChangesAreGlobalNeutral */ true, /* UnaffiliatedChangesAreYoungNeutral */ false>(); - _partitions.assert_bounds(true); + _partitions.assert_bounds(); // 4. Do not adjust capacities for generations, we just swapped the regions that have already // been accounted for. However, we should adjust the evacuation reserves as those may have changed. shenandoah_assert_heaplocked(); @@ -1945,7 +1991,7 @@ void ShenandoahFreeSet::flip_to_gc(ShenandoahHeapRegion* r) { /* CollectorSizeChanged */ true, /* OldCollectorSizeChanged */ false, /* AffiliatedChangesAreYoungNeutral */ true, /* AffiliatedChangesAreGlobalNeutral */ true, /* UnaffiliatedChangesAreYoungNeutral */ true>(); - _partitions.assert_bounds(true); + _partitions.assert_bounds(); // We do not ensure that the region is no longer trash, relying on try_allocate_in(), which always comes next, // to recycle trash before attempting to allocate anything in the region. } @@ -2025,16 +2071,23 @@ void ShenandoahFreeSet::find_regions_with_alloc_capacity(size_t &young_trashed_r for (size_t idx = 0; idx < num_regions; idx++) { ShenandoahHeapRegion* region = _heap->get_region(idx); if (region->is_trash()) { - // Trashed regions represent immediate garbage identified by final mark and regions that had been in the collection - // partition but have not yet been "cleaned up" following update refs. + // Trashed regions represent regions that had been in the collection set (or may have been identified as immediate garbage) + // but have not yet been "cleaned up". The cset regions are not "trashed" until we have finished update refs. if (region->is_old()) { + // We're going to place this region into the Mutator set. We increment old_trashed_regions because this count represents + // regions that the old generation is entitled to without any transfer from young. We do not place this region into + // the OldCollector partition at this time. Instead, we let reserve_regions() decide whether to place this region + // into the OldCollector partition. Deferring the decision allows reserve_regions() to more effectively pack the + // OldCollector regions into high-address memory. We do not adjust capacities of old and young generations at this + // time. At the end of finish_rebuild(), the capacities are adjusted based on the results of reserve_regions(). old_trashed_regions++; } else { assert(region->is_young(), "Trashed region should be old or young"); young_trashed_regions++; } } else if (region->is_old()) { - // count both humongous and regular regions, but don't count trash (cset) regions. + // We count humongous and regular regions as "old regions". We do not count trashed regions that are old. Those + // are counted (above) as old_trashed_regions. old_region_count++; if (first_old_region > idx) { first_old_region = idx; @@ -2048,7 +2101,7 @@ void ShenandoahFreeSet::find_regions_with_alloc_capacity(size_t &young_trashed_r size_t ac = alloc_capacity(region); if (ac >= PLAB::min_size() * HeapWordSize) { if (region->is_trash() || !region->is_old()) { - // Both young and old collected regions (trashed) are placed into the Mutator set + // Both young and old (possibly immediately) collected regions (trashed) are placed into the Mutator set _partitions.raw_assign_membership(idx, ShenandoahFreeSetPartitionId::Mutator); if (idx < mutator_leftmost) { mutator_leftmost = idx; @@ -2111,10 +2164,19 @@ void ShenandoahFreeSet::find_regions_with_alloc_capacity(size_t &young_trashed_r assert(_partitions.membership(idx) == ShenandoahFreeSetPartitionId::NotFree, "Region should have been retired"); size_t humongous_waste_bytes = 0; if (region->is_humongous_start()) { - oop obj = cast_to_oop(region->bottom()); - size_t byte_size = obj->size() * HeapWordSize; - size_t region_span = ShenandoahHeapRegion::required_regions(byte_size); - humongous_waste_bytes = region_span * ShenandoahHeapRegion::region_size_bytes() - byte_size; + // Since rebuild does not necessarily happen at a safepoint, a newly allocated humongous object may not have been + // fully initialized. Therefore, we cannot safely consult its header. + ShenandoahHeapRegion* last_of_humongous_continuation = region; + size_t next_idx; + for (next_idx = idx + 1; next_idx < num_regions; next_idx++) { + ShenandoahHeapRegion* humongous_cont_candidate = _heap->get_region(next_idx); + if (!humongous_cont_candidate->is_humongous_continuation()) { + break; + } + last_of_humongous_continuation = humongous_cont_candidate; + } + // For humongous regions, used() is established while holding the global heap lock so it is reliable here + humongous_waste_bytes = ShenandoahHeapRegion::region_size_bytes() - last_of_humongous_continuation->used(); } if (region->is_old()) { old_collector_used += region_size_bytes; @@ -2183,7 +2245,7 @@ void ShenandoahFreeSet::find_regions_with_alloc_capacity(size_t &young_trashed_r /* CollectorSizeChanged */ true, /* OldCollectorSizeChanged */ true, /* AffiliatedChangesAreYoungNeutral */ false, /* AffiliatedChangesAreGlobalNeutral */ false, /* UnaffiliatedChangesAreYoungNeutral */ false>(); - _partitions.assert_bounds(true); + _partitions.assert_bounds(); #ifdef ASSERT if (_heap->mode()->is_generational()) { assert(young_affiliated_regions() == _heap->young_generation()->get_affiliated_region_count(), "sanity"); @@ -2221,7 +2283,7 @@ void ShenandoahFreeSet::transfer_humongous_regions_from_mutator_to_old_collector /* CollectorSizeChanged */ false, /* OldCollectorSizeChanged */ true, /* AffiliatedChangesAreYoungNeutral */ false, /* AffiliatedChangesAreGlobalNeutral */ true, /* UnaffiliatedChangesAreYoungNeutral */ true>(); - _partitions.assert_bounds(true); + _partitions.assert_bounds(); // global_used is unaffected by this transfer // No need to adjust ranges because humongous regions are not allocatable @@ -2303,7 +2365,7 @@ void ShenandoahFreeSet::transfer_empty_regions_from_to(ShenandoahFreeSetPartitio /* UnaffiliatedChangesAreYoungNeutral */ true>(); } } - _partitions.assert_bounds(true); + _partitions.assert_bounds(); } // Returns number of regions transferred, adds transferred bytes to var argument bytes_transferred @@ -2370,7 +2432,7 @@ size_t ShenandoahFreeSet::transfer_empty_regions_from_collector_set_to_mutator_s /* AffiliatedChangesAreYoungNeutral */ true, /* AffiliatedChangesAreGlobalNeutral */ true, /* UnaffiliatedChangesAreYoungNeutral */ true>(); } - _partitions.assert_bounds(true); + _partitions.assert_bounds(); return transferred_regions; } @@ -2445,7 +2507,7 @@ transfer_non_empty_regions_from_collector_set_to_mutator_set(ShenandoahFreeSetPa /* AffiliatedChangesAreYoungNeutral */ true, /* AffiliatedChangesAreGlobalNeutral */ true, /* UnaffiliatedChangesAreYoungNeutral */ true>(); } - _partitions.assert_bounds(true); + _partitions.assert_bounds(); return transferred_regions; } @@ -2495,6 +2557,10 @@ void ShenandoahFreeSet::move_regions_from_collector_to_mutator(size_t max_xfer_r void ShenandoahFreeSet::prepare_to_rebuild(size_t &young_trashed_regions, size_t &old_trashed_regions, size_t &first_old_region, size_t &last_old_region, size_t &old_region_count) { shenandoah_assert_heaplocked(); + assert(rebuild_lock() != nullptr, "sanity"); + rebuild_lock()->lock(false); + // This resets all state information, removing all regions from all sets. + clear(); log_debug(gc, free)("Rebuilding FreeSet"); // This places regions that have alloc_capacity into the old_collector set if they identify as is_old() or the @@ -2503,14 +2569,13 @@ void ShenandoahFreeSet::prepare_to_rebuild(size_t &young_trashed_regions, size_t first_old_region, last_old_region, old_region_count); } -void ShenandoahFreeSet::finish_rebuild(size_t young_trashed_regions, size_t old_trashed_regions, size_t old_region_count, - bool have_evacuation_reserves) { + +void ShenandoahFreeSet::finish_rebuild(size_t young_cset_regions, size_t old_cset_regions, size_t old_region_count) { shenandoah_assert_heaplocked(); size_t young_reserve(0), old_reserve(0); if (_heap->mode()->is_generational()) { - compute_young_and_old_reserves(young_trashed_regions, old_trashed_regions, have_evacuation_reserves, - young_reserve, old_reserve); + compute_young_and_old_reserves(young_cset_regions, old_cset_regions, young_reserve, old_reserve); } else { young_reserve = (_heap->max_capacity() / 100) * ShenandoahEvacReserve; old_reserve = 0; @@ -2524,8 +2589,44 @@ void ShenandoahFreeSet::finish_rebuild(size_t young_trashed_regions, size_t old_ _total_young_regions = _heap->num_regions() - old_region_count; _total_global_regions = _heap->num_regions(); establish_old_collector_alloc_bias(); - _partitions.assert_bounds(true); + + // Release the rebuild lock now. What remains in this function is read-only + rebuild_lock()->unlock(); + _partitions.assert_bounds(); log_status(); + if (_heap->mode()->is_generational()) { + // Clear the region balance until it is adjusted in preparation for a subsequent GC cycle. + _heap->old_generation()->set_region_balance(0); + } +} + + +// Reduce old reserve (when there are insufficient resources to satisfy the original request). +void ShenandoahFreeSet::reduce_old_reserve(size_t adjusted_old_reserve, size_t requested_old_reserve) { + ShenandoahOldGeneration* const old_generation = _heap->old_generation(); + size_t requested_promoted_reserve = old_generation->get_promoted_reserve(); + size_t requested_old_evac_reserve = old_generation->get_evacuation_reserve(); + assert(adjusted_old_reserve < requested_old_reserve, "Only allow reduction"); + assert(requested_promoted_reserve + requested_old_evac_reserve >= adjusted_old_reserve, "Sanity"); + size_t delta = requested_old_reserve - adjusted_old_reserve; + + if (requested_promoted_reserve >= delta) { + requested_promoted_reserve -= delta; + old_generation->set_promoted_reserve(requested_promoted_reserve); + } else { + delta -= requested_promoted_reserve; + requested_promoted_reserve = 0; + requested_old_evac_reserve -= delta; + old_generation->set_promoted_reserve(requested_promoted_reserve); + old_generation->set_evacuation_reserve(requested_old_evac_reserve); + } +} + +// Reduce young reserve (when there are insufficient resources to satisfy the original request). +void ShenandoahFreeSet::reduce_young_reserve(size_t adjusted_young_reserve, size_t requested_young_reserve) { + ShenandoahYoungGeneration* const young_generation = _heap->young_generation(); + assert(adjusted_young_reserve < requested_young_reserve, "Only allow reduction"); + young_generation->set_evacuation_reserve(adjusted_young_reserve); } /** @@ -2542,7 +2643,6 @@ void ShenandoahFreeSet::finish_rebuild(size_t young_trashed_regions, size_t old_ * this value should computed by ShenandoahGenerationalHeap::compute_old_generation_balance(). */ void ShenandoahFreeSet::compute_young_and_old_reserves(size_t young_trashed_regions, size_t old_trashed_regions, - bool have_evacuation_reserves, size_t& young_reserve_result, size_t& old_reserve_result) const { shenandoah_assert_generational(); shenandoah_assert_heaplocked(); @@ -2559,6 +2659,15 @@ void ShenandoahFreeSet::compute_young_and_old_reserves(size_t young_trashed_regi old_available += old_trashed_regions * region_size_bytes; young_unaffiliated_regions += young_trashed_regions; + assert(young_capacity >= young_generation->used(), + "Young capacity (%zu) must exceed used (%zu)", young_capacity, young_generation->used()); + + size_t young_available = young_capacity - young_generation->used(); + young_available += young_trashed_regions * region_size_bytes; + + assert(young_available >= young_unaffiliated_regions * region_size_bytes, "sanity"); + assert(old_available >= old_unaffiliated_regions * region_size_bytes, "sanity"); + // Consult old-region balance to make adjustments to current generation capacities and availability. // The generation region transfers take place after we rebuild. old_region_balance represents number of regions // to transfer from old to young. @@ -2578,6 +2687,7 @@ void ShenandoahFreeSet::compute_young_and_old_reserves(size_t young_trashed_regi ssize_t xfer_bytes = old_region_balance * checked_cast(region_size_bytes); old_available -= xfer_bytes; old_unaffiliated_regions -= old_region_balance; + young_available += xfer_bytes; young_capacity += xfer_bytes; young_unaffiliated_regions += old_region_balance; } @@ -2586,41 +2696,22 @@ void ShenandoahFreeSet::compute_young_and_old_reserves(size_t young_trashed_regi // promotions and evacuations. The partition between which old memory is reserved for evacuation and // which is reserved for promotion is enforced using thread-local variables that prescribe intentions for // each PLAB's available memory. - if (have_evacuation_reserves) { - // We are rebuilding at the end of final mark, having already established evacuation budgets for this GC pass. - const size_t promoted_reserve = old_generation->get_promoted_reserve(); - const size_t old_evac_reserve = old_generation->get_evacuation_reserve(); - young_reserve_result = young_generation->get_evacuation_reserve(); - old_reserve_result = promoted_reserve + old_evac_reserve; - if (old_reserve_result > old_available) { - // Try to transfer memory from young to old. - size_t old_deficit = old_reserve_result - old_available; - size_t old_region_deficit = (old_deficit + region_size_bytes - 1) / region_size_bytes; - if (young_unaffiliated_regions < old_region_deficit) { - old_region_deficit = young_unaffiliated_regions; - } - young_unaffiliated_regions -= old_region_deficit; - old_unaffiliated_regions += old_region_deficit; - old_region_balance -= old_region_deficit; - old_generation->set_region_balance(old_region_balance); - } - } else { - // We are rebuilding at end of GC, so we set aside budgets specified on command line (or defaults) - young_reserve_result = (young_capacity * ShenandoahEvacReserve) / 100; - // The auto-sizer has already made old-gen large enough to hold all anticipated evacuations and promotions. - // Affiliated old-gen regions are already in the OldCollector free set. Add in the relevant number of - // unaffiliated regions. - old_reserve_result = old_available; - } + const size_t promoted_reserve = old_generation->get_promoted_reserve(); + const size_t old_evac_reserve = old_generation->get_evacuation_reserve(); + young_reserve_result = young_generation->get_evacuation_reserve(); + old_reserve_result = promoted_reserve + old_evac_reserve; + assert(old_reserve_result + young_reserve_result <= old_available + young_available, + "Cannot reserve (%zu + %zu + %zu) more than is available: %zu + %zu", + promoted_reserve, old_evac_reserve, young_reserve_result, old_available, young_available); // Old available regions that have less than PLAB::min_size() of available memory are not placed into the OldCollector // free set. Because of this, old_available may not have enough memory to represent the intended reserve. Adjust // the reserve downward to account for this possibility. This loss is part of the reason why the original budget // was adjusted with ShenandoahOldEvacWaste and ShenandoahOldPromoWaste multipliers. if (old_reserve_result > - _partitions.capacity_of(ShenandoahFreeSetPartitionId::OldCollector) + old_unaffiliated_regions * region_size_bytes) { + _partitions.available_in(ShenandoahFreeSetPartitionId::OldCollector) + old_unaffiliated_regions * region_size_bytes) { old_reserve_result = - _partitions.capacity_of(ShenandoahFreeSetPartitionId::OldCollector) + old_unaffiliated_regions * region_size_bytes; + _partitions.available_in(ShenandoahFreeSetPartitionId::OldCollector) + old_unaffiliated_regions * region_size_bytes; } if (young_reserve_result > young_unaffiliated_regions * region_size_bytes) { @@ -2784,19 +2875,17 @@ void ShenandoahFreeSet::reserve_regions(size_t to_reserve, size_t to_reserve_old ShenandoahFreeSetPartitionId p = _partitions.membership(idx); size_t ac = alloc_capacity(r); assert(ac != region_size_bytes, "Empty regions should be in Mutator partion at entry to reserve_regions"); - if (p == ShenandoahFreeSetPartitionId::Collector) { - if (ac != region_size_bytes) { - young_used_regions++; - young_used_bytes = region_size_bytes - ac; - } - // else, unaffiliated region has no used - } else if (p == ShenandoahFreeSetPartitionId::OldCollector) { - if (ac != region_size_bytes) { - old_used_regions++; - old_used_bytes = region_size_bytes - ac; - } - // else, unaffiliated region has no used - } else if (p == ShenandoahFreeSetPartitionId::NotFree) { + assert(p != ShenandoahFreeSetPartitionId::Collector, "Collector regions must be converted from Mutator regions"); + if (p == ShenandoahFreeSetPartitionId::OldCollector) { + assert(!r->is_empty(), "Empty regions should be in Mutator partition at entry to reserve_regions"); + old_used_regions++; + old_used_bytes = region_size_bytes - ac; + // This region is within the range for OldCollector partition, as established by find_regions_with_alloc_capacity() + assert((_partitions.leftmost(ShenandoahFreeSetPartitionId::OldCollector) <= idx) && + (_partitions.rightmost(ShenandoahFreeSetPartitionId::OldCollector) >= idx), + "find_regions_with_alloc_capacity() should have established this is in range"); + } else { + assert(p == ShenandoahFreeSetPartitionId::NotFree, "sanity"); // This region has been retired if (r->is_old()) { old_used_regions++; @@ -2806,21 +2895,6 @@ void ShenandoahFreeSet::reserve_regions(size_t to_reserve, size_t to_reserve_old young_used_regions++; young_used_bytes += region_size_bytes - ac; } - } else { - assert(p == ShenandoahFreeSetPartitionId::OldCollector, "Not mutator and not NotFree, so must be OldCollector"); - assert(!r->is_empty(), "Empty regions should be in Mutator partition at entry to reserve_regions"); - if (idx < old_collector_low_idx) { - old_collector_low_idx = idx; - } - if (idx > old_collector_high_idx) { - old_collector_high_idx = idx; - } - if (idx < old_collector_empty_low_idx) { - old_collector_empty_low_idx = idx; - } - if (idx > old_collector_empty_high_idx) { - old_collector_empty_high_idx = idx; - } } } } @@ -2849,14 +2923,14 @@ void ShenandoahFreeSet::reserve_regions(size_t to_reserve, size_t to_reserve_old _partitions.increase_used(ShenandoahFreeSetPartitionId::OldCollector, used_to_old_collector); } - _partitions.expand_interval_if_range_modifies_either_boundary(ShenandoahFreeSetPartitionId::Collector, - collector_low_idx, collector_high_idx, - collector_empty_low_idx, collector_empty_high_idx); + _partitions.establish_interval(ShenandoahFreeSetPartitionId::Mutator, + mutator_low_idx, mutator_high_idx, mutator_empty_low_idx, mutator_empty_high_idx); + _partitions.establish_interval(ShenandoahFreeSetPartitionId::Collector, + collector_low_idx, collector_high_idx, collector_empty_low_idx, collector_empty_high_idx); + _partitions.expand_interval_if_range_modifies_either_boundary(ShenandoahFreeSetPartitionId::OldCollector, old_collector_low_idx, old_collector_high_idx, old_collector_empty_low_idx, old_collector_empty_high_idx); - _partitions.establish_interval(ShenandoahFreeSetPartitionId::Mutator, - mutator_low_idx, mutator_high_idx, mutator_empty_low_idx, mutator_empty_high_idx); recompute_total_used(); @@ -2865,17 +2939,22 @@ void ShenandoahFreeSet::reserve_regions(size_t to_reserve, size_t to_reserve_old /* CollectorSizeChanged */ true, /* OldCollectorSizeChanged */ true, /* AffiliatedChangesAreYoungNeutral */ false, /* AffiliatedChangesAreGlobalNeutral */ false, /* UnaffiliatedChangesAreYoungNeutral */ false>(); - _partitions.assert_bounds(true); + _partitions.assert_bounds(); if (LogTarget(Info, gc, free)::is_enabled()) { size_t old_reserve = _partitions.available_in(ShenandoahFreeSetPartitionId::OldCollector); if (old_reserve < to_reserve_old) { log_info(gc, free)("Wanted " PROPERFMT " for old reserve, but only reserved: " PROPERFMT, PROPERFMTARGS(to_reserve_old), PROPERFMTARGS(old_reserve)); + assert(_heap->mode()->is_generational(), "to_old_reserve > 0 implies generational mode"); + reduce_old_reserve(old_reserve, to_reserve_old); } size_t reserve = _partitions.available_in(ShenandoahFreeSetPartitionId::Collector); if (reserve < to_reserve) { + if (_heap->mode()->is_generational()) { + reduce_young_reserve(reserve, to_reserve); + } log_info(gc, free)("Wanted " PROPERFMT " for young reserve, but only reserved: " PROPERFMT, - PROPERFMTARGS(to_reserve), PROPERFMTARGS(reserve)); + PROPERFMTARGS(to_reserve), PROPERFMTARGS(reserve)); } } } @@ -3058,7 +3137,7 @@ void ShenandoahFreeSet::log_status() { size_t max_humongous = max_contig * ShenandoahHeapRegion::region_size_bytes(); // capacity() is capacity of mutator // used() is used of mutator - size_t free = capacity() - used(); + size_t free = capacity_holding_lock() - used_holding_lock(); // Since certain regions that belonged to the Mutator free partition at the time of most recent rebuild may have been // retired, the sum of used and capacities within regions that are still in the Mutator free partition may not match // my internally tracked values of used() and free(). diff --git a/src/hotspot/share/gc/shenandoah/shenandoahFreeSet.hpp b/src/hotspot/share/gc/shenandoah/shenandoahFreeSet.hpp index 95f9fbe6856..4e0aea80a9b 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahFreeSet.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahFreeSet.hpp @@ -28,9 +28,13 @@ #include "gc/shenandoah/shenandoahHeap.hpp" #include "gc/shenandoah/shenandoahHeapRegionSet.hpp" +#include "gc/shenandoah/shenandoahLock.hpp" #include "gc/shenandoah/shenandoahSimpleBitMap.hpp" #include "logging/logStream.hpp" +typedef ShenandoahLock ShenandoahRebuildLock; +typedef ShenandoahLocker ShenandoahRebuildLocker; + // Each ShenandoahHeapRegion is associated with a ShenandoahFreeSetPartitionId. enum class ShenandoahFreeSetPartitionId : uint8_t { Mutator, // Region is in the Mutator free set: available memory is available to mutators. @@ -139,8 +143,6 @@ public: ShenandoahRegionPartitions(size_t max_regions, ShenandoahFreeSet* free_set); ~ShenandoahRegionPartitions() {} - static const size_t FreeSetUnderConstruction = SIZE_MAX; - inline idx_t max() const { return _max; } // At initialization, reset OldCollector tallies @@ -222,6 +224,10 @@ public: void transfer_used_capacity_from_to(ShenandoahFreeSetPartitionId from_partition, ShenandoahFreeSetPartitionId to_partition, size_t regions); + // For recycled region r in the OldCollector partition but possibly not within the interval for empty OldCollector regions, + // expand the empty interval to include this region. + inline void adjust_interval_for_recycled_old_region_under_lock(ShenandoahHeapRegion* r); + const char* partition_membership_name(idx_t idx) const; // Return the index of the next available region >= start_index, or maximum_regions if not found. @@ -352,6 +358,16 @@ public: return _available[int(which_partition)]; } + // Return available_in assuming caller does not hold the heap lock but does hold the rebuild_lock. + // The returned value may be "slightly stale" because we do not assure that every fetch of this value + // sees the most recent update of this value. Requiring the caller to hold the rebuild_lock assures + // that we don't see "bogus" values that are "worse than stale". During rebuild of the freeset, the + // value of _available is not reliable. + inline size_t available_in_locked_for_rebuild(ShenandoahFreeSetPartitionId which_partition) const { + assert (which_partition < NumPartitions, "selected free set must be valid"); + return _available[int(which_partition)]; + } + // Returns bytes of humongous waste inline size_t humongous_waste(ShenandoahFreeSetPartitionId which_partition) const { assert (which_partition < NumPartitions, "selected free set must be valid"); @@ -359,31 +375,9 @@ public: return _humongous_waste[int(which_partition)]; } - // Return available_in assuming caller does not hold the heap lock. In production builds, available is - // returned without acquiring the lock. In debug builds, the global heap lock is acquired in order to - // enforce a consistency assert. - inline size_t available_in_not_locked(ShenandoahFreeSetPartitionId which_partition) const { - assert (which_partition < NumPartitions, "selected free set must be valid"); - shenandoah_assert_not_heaplocked(); -#ifdef ASSERT - ShenandoahHeapLocker locker(ShenandoahHeap::heap()->lock()); - assert((_available[int(which_partition)] == FreeSetUnderConstruction) || - (_available[int(which_partition)] == _capacity[int(which_partition)] - _used[int(which_partition)]), - "Expect available (%zu) equals capacity (%zu) - used (%zu) for partition %s", - _available[int(which_partition)], _capacity[int(which_partition)], _used[int(which_partition)], - partition_membership_name(idx_t(which_partition))); -#endif - return _available[int(which_partition)]; - } - inline void set_capacity_of(ShenandoahFreeSetPartitionId which_partition, size_t value); - inline void set_used_by(ShenandoahFreeSetPartitionId which_partition, size_t value) { - shenandoah_assert_heaplocked(); - assert (which_partition < NumPartitions, "selected free set must be valid"); - _used[int(which_partition)] = value; - _available[int(which_partition)] = _capacity[int(which_partition)] - value; - } + inline void set_used_by(ShenandoahFreeSetPartitionId which_partition, size_t value); inline size_t count(ShenandoahFreeSetPartitionId which_partition) const { return _region_counts[int(which_partition)]; } @@ -407,7 +401,7 @@ public: // idx >= leftmost && // idx <= rightmost // } - void assert_bounds(bool validate_totals) NOT_DEBUG_RETURN; + void assert_bounds() NOT_DEBUG_RETURN; }; // Publicly, ShenandoahFreeSet represents memory that is available to mutator threads. The public capacity(), used(), @@ -440,6 +434,15 @@ private: ShenandoahHeap* const _heap; ShenandoahRegionPartitions _partitions; + // This locks the rebuild process (in combination with the global heap lock). Whenever we rebuild the free set, + // we first acquire the global heap lock and then we acquire this _rebuild_lock in a nested context. Threads that + // need to check available, acquire only the _rebuild_lock to make sure that they are not obtaining the value of + // available for a partially reconstructed free-set. + // + // Note that there is rank ordering of nested locks to prevent deadlock. All threads that need to acquire both + // locks will acquire them in the same order: first the global heap lock and then the rebuild lock. + ShenandoahRebuildLock _rebuild_lock; + size_t _total_humongous_waste; HeapWord* allocate_aligned_plab(size_t size, ShenandoahAllocRequest& req, ShenandoahHeapRegion* r); @@ -630,15 +633,21 @@ private: void establish_old_collector_alloc_bias(); size_t get_usable_free_words(size_t free_bytes) const; + void reduce_young_reserve(size_t adjusted_young_reserve, size_t requested_young_reserve); + void reduce_old_reserve(size_t adjusted_old_reserve, size_t requested_old_reserve); + void log_freeset_stats(ShenandoahFreeSetPartitionId partition_id, LogStream& ls); + // log status, assuming lock has already been acquired by the caller. void log_status(); public: - static const size_t FreeSetUnderConstruction = ShenandoahRegionPartitions::FreeSetUnderConstruction; - ShenandoahFreeSet(ShenandoahHeap* heap, size_t max_regions); + ShenandoahRebuildLock* rebuild_lock() { + return &_rebuild_lock; + } + inline size_t max_regions() const { return _partitions.max(); } ShenandoahFreeSetPartitionId membership(size_t index) const { return _partitions.membership(index); } inline void shrink_interval_if_range_modifies_either_boundary(ShenandoahFreeSetPartitionId partition, @@ -679,35 +688,46 @@ public: return _total_global_used; } - size_t global_unaffiliated_regions() { + // A negative argument results in moving from old_collector to collector + void move_unaffiliated_regions_from_collector_to_old_collector(ssize_t regions); + + inline size_t global_unaffiliated_regions() { return _global_unaffiliated_regions; } - size_t young_unaffiliated_regions() { + inline size_t young_unaffiliated_regions() { return _young_unaffiliated_regions; } - size_t old_unaffiliated_regions() { + inline size_t collector_unaffiliated_regions() { + return _partitions.get_empty_region_counts(ShenandoahFreeSetPartitionId::Collector); + } + + inline size_t old_collector_unaffiliated_regions() { return _partitions.get_empty_region_counts(ShenandoahFreeSetPartitionId::OldCollector); } - size_t young_affiliated_regions() { + inline size_t old_unaffiliated_regions() { + return _partitions.get_empty_region_counts(ShenandoahFreeSetPartitionId::OldCollector); + } + + inline size_t young_affiliated_regions() { return _young_affiliated_regions; } - size_t old_affiliated_regions() { + inline size_t old_affiliated_regions() { return _old_affiliated_regions; } - size_t global_affiliated_regions() { + inline size_t global_affiliated_regions() { return _global_affiliated_regions; } - size_t total_young_regions() { + inline size_t total_young_regions() { return _total_young_regions; } - size_t total_old_regions() { + inline size_t total_old_regions() { return _partitions.get_capacity(ShenandoahFreeSetPartitionId::OldCollector) / ShenandoahHeapRegion::region_size_bytes(); } @@ -719,36 +739,27 @@ public: // Examine the existing free set representation, capturing the current state into var arguments: // - // young_cset_regions is the number of regions currently in the young cset if we are starting to evacuate, or zero - // old_cset_regions is the number of regions currently in the old cset if we are starting a mixed evacuation, or zero + // young_trashed_regions is the number of trashed regions (immediate garbage at final mark, cset regions after update refs) + // old_trashed_regions is the number of trashed regions + // (immediate garbage at final old mark, cset regions after update refs for mixed evac) // first_old_region is the index of the first region that is part of the OldCollector set // last_old_region is the index of the last region that is part of the OldCollector set // old_region_count is the number of regions in the OldCollector set that have memory available to be allocated - void prepare_to_rebuild(size_t &young_cset_regions, size_t &old_cset_regions, + void prepare_to_rebuild(size_t &young_trashed_regions, size_t &old_trashed_regions, size_t &first_old_region, size_t &last_old_region, size_t &old_region_count); // At the end of final mark, but before we begin evacuating, heuristics calculate how much memory is required to - // hold the results of evacuating to young-gen and to old-gen, and have_evacuation_reserves should be true. - // These quantities, stored as reserves for their respective generations, are consulted prior to rebuilding - // the free set (ShenandoahFreeSet) in preparation for evacuation. When the free set is rebuilt, we make sure - // to reserve sufficient memory in the collector and old_collector sets to hold evacuations. + // hold the results of evacuating to young-gen and to old-gen. These quantities, stored in reserves for their + // respective generations, are consulted prior to rebuilding the free set (ShenandoahFreeSet) in preparation for + // evacuation. When the free set is rebuilt, we make sure to reserve sufficient memory in the collector and + // old_collector sets to hold evacuations. Likewise, at the end of update refs, we rebuild the free set in order + // to set aside reserves to be consumed during the next GC cycle. // - // We also rebuild the free set at the end of GC, as we prepare to idle GC until the next trigger. In this case, - // have_evacuation_reserves is false because we don't yet know how much memory will need to be evacuated in the - // next GC cycle. When have_evacuation_reserves is false, the free set rebuild operation reserves for the collector - // and old_collector sets based on alternative mechanisms, such as ShenandoahEvacReserve, ShenandoahOldEvacReserve, and - // ShenandoahOldCompactionReserve. In a future planned enhancement, the reserve for old_collector set when the - // evacuation reserves are unknown, is based in part on anticipated promotion as determined by analysis of live data - // found during the previous GC pass which is one less than the current tenure age. - // - // young_cset_regions is the number of regions currently in the young cset if we are starting to evacuate, or zero - // old_cset_regions is the number of regions currently in the old cset if we are starting a mixed evacuation, or zero + // young_trashed_regions is the number of trashed regions (immediate garbage at final mark, cset regions after update refs) + // old_trashed_regions is the number of trashed regions + // (immediate garbage at final old mark, cset regions after update refs for mixed evac) // num_old_regions is the number of old-gen regions that have available memory for further allocations (excluding old cset) - // have_evacuation_reserves is true iff the desired values of young-gen and old-gen evacuation reserves and old-gen - // promotion reserve have been precomputed (and can be obtained by invoking - // ->get_evacuation_reserve() or old_gen->get_promoted_reserve() - void finish_rebuild(size_t young_cset_regions, size_t old_cset_regions, size_t num_old_regions, - bool have_evacuation_reserves = false); + void finish_rebuild(size_t young_trashed_regions, size_t old_trashed_regions, size_t num_old_regions); // When a region is promoted in place, we add the region's available memory if it is greater than plab_min_size() // into the old collector partition by invoking this method. @@ -776,13 +787,42 @@ public: // adjusts available with respect to lock holders. However, sequential calls to these three functions may produce // inconsistent data: available may not equal capacity - used because the intermediate states of any "atomic" // locked action can be seen by these unlocked functions. - inline size_t capacity() const { return _partitions.capacity_of(ShenandoahFreeSetPartitionId::Mutator); } - inline size_t used() const { return _partitions.used_by(ShenandoahFreeSetPartitionId::Mutator); } - inline size_t available() const { return _partitions.available_in_not_locked(ShenandoahFreeSetPartitionId::Mutator); } + inline size_t capacity_holding_lock() const { + shenandoah_assert_heaplocked(); + return _partitions.capacity_of(ShenandoahFreeSetPartitionId::Mutator); + } + inline size_t capacity_not_holding_lock() { + shenandoah_assert_not_heaplocked(); + ShenandoahRebuildLocker locker(rebuild_lock()); + return _partitions.capacity_of(ShenandoahFreeSetPartitionId::Mutator); + } + inline size_t used_holding_lock() const { + shenandoah_assert_heaplocked(); + return _partitions.used_by(ShenandoahFreeSetPartitionId::Mutator); + } + inline size_t used_not_holding_lock() { + shenandoah_assert_not_heaplocked(); + ShenandoahRebuildLocker locker(rebuild_lock()); + return _partitions.used_by(ShenandoahFreeSetPartitionId::Mutator); + } + inline size_t available() { + shenandoah_assert_not_heaplocked(); + ShenandoahRebuildLocker locker(rebuild_lock()); + return _partitions.available_in_locked_for_rebuild(ShenandoahFreeSetPartitionId::Mutator); + } + + // Use this version of available() if the heap lock is held. + inline size_t available_locked() const { + return _partitions.available_in(ShenandoahFreeSetPartitionId::Mutator); + } inline size_t total_humongous_waste() const { return _total_humongous_waste; } - inline size_t humongous_waste_in_mutator() const { return _partitions.humongous_waste(ShenandoahFreeSetPartitionId::Mutator); } - inline size_t humongous_waste_in_old() const { return _partitions.humongous_waste(ShenandoahFreeSetPartitionId::OldCollector); } + inline size_t humongous_waste_in_mutator() const { + return _partitions.humongous_waste(ShenandoahFreeSetPartitionId::Mutator); + } + inline size_t humongous_waste_in_old() const { + return _partitions.humongous_waste(ShenandoahFreeSetPartitionId::OldCollector); + } void decrease_humongous_waste_for_regular_bypass(ShenandoahHeapRegion* r, size_t waste); @@ -848,7 +888,7 @@ public: // Reserve space for evacuations, with regions reserved for old evacuations placed to the right // of regions reserved of young evacuations. - void compute_young_and_old_reserves(size_t young_cset_regions, size_t old_cset_regions, bool have_evacuation_reserves, + void compute_young_and_old_reserves(size_t young_cset_regions, size_t old_cset_regions, size_t &young_reserve_result, size_t &old_reserve_result) const; }; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahFullGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahFullGC.cpp index 027d7e02268..3c92750cc0c 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahFullGC.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahFullGC.cpp @@ -522,6 +522,7 @@ public: void heap_region_do(ShenandoahHeapRegion* r) override { if (r->is_trash()) { r->try_recycle_under_lock(); + // No need to adjust_interval_for_recycled_old_region. That will be taken care of during freeset rebuild. } if (r->is_cset()) { // Leave affiliation unchanged @@ -966,6 +967,7 @@ public: if (r->is_trash()) { live = 0; r->try_recycle_under_lock(); + // No need to adjust_interval_for_recycled_old_region. That will be taken care of during freeset rebuild. } else { if (r->is_old()) { ShenandoahGenerationalFullGC::account_for_region(r, _old_regions, _old_usage, _old_humongous_waste); @@ -1113,18 +1115,17 @@ void ShenandoahFullGC::phase5_epilog() { ShenandoahPostCompactClosure post_compact; heap->heap_region_iterate(&post_compact); heap->collection_set()->clear(); - size_t young_cset_regions, old_cset_regions; - size_t first_old, last_old, num_old; - heap->free_set()->prepare_to_rebuild(young_cset_regions, old_cset_regions, first_old, last_old, num_old); - - // We also do not expand old generation size following Full GC because we have scrambled age populations and - // no longer have objects separated by age into distinct regions. - if (heap->mode()->is_generational()) { - ShenandoahGenerationalFullGC::compute_balances(); + { + ShenandoahFreeSet* free_set = heap->free_set(); + size_t young_trashed_regions, old_trashed_regions, first_old, last_old, num_old; + free_set->prepare_to_rebuild(young_trashed_regions, old_trashed_regions, first_old, last_old, num_old); + // We also do not expand old generation size following Full GC because we have scrambled age populations and + // no longer have objects separated by age into distinct regions. + if (heap->mode()->is_generational()) { + ShenandoahGenerationalFullGC::compute_balances(); + } + free_set->finish_rebuild(young_trashed_regions, old_trashed_regions, num_old); } - - heap->free_set()->finish_rebuild(young_cset_regions, old_cset_regions, num_old); - // Set mark incomplete because the marking bitmaps have been reset except pinned regions. _generation->set_mark_incomplete(); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp index d74ee872cd1..cdc7e1a328a 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp @@ -250,6 +250,7 @@ void ShenandoahGeneration::compute_evacuation_budgets(ShenandoahHeap* const heap ShenandoahOldGeneration* const old_generation = heap->old_generation(); ShenandoahYoungGeneration* const young_generation = heap->young_generation(); + const size_t region_size_bytes = ShenandoahHeapRegion::region_size_bytes(); // During initialization and phase changes, it is more likely that fewer objects die young and old-gen // memory is not yet full (or is in the process of being replaced). During these times especially, it @@ -263,15 +264,15 @@ void ShenandoahGeneration::compute_evacuation_budgets(ShenandoahHeap* const heap // First priority is to reclaim the easy garbage out of young-gen. - // maximum_young_evacuation_reserve is upper bound on memory to be evacuated out of young - const size_t maximum_young_evacuation_reserve = (young_generation->max_capacity() * ShenandoahEvacReserve) / 100; - size_t young_evacuation_reserve = MIN2(maximum_young_evacuation_reserve, young_generation->available_with_reserve()); + // maximum_young_evacuation_reserve is upper bound on memory to be evacuated into young Collector Reserve. This is + // bounded at the end of previous GC cycle, based on available memory and balancing of evacuation to old and young. + size_t maximum_young_evacuation_reserve = young_generation->get_evacuation_reserve(); // maximum_old_evacuation_reserve is an upper bound on memory evacuated from old and evacuated to old (promoted), // clamped by the old generation space available. // // Here's the algebra. - // Let SOEP = ShenandoahOldEvacRatioPercent, + // Let SOEP = ShenandoahOldEvacPercent, // OE = old evac, // YE = young evac, and // TE = total evac = OE + YE @@ -283,12 +284,14 @@ void ShenandoahGeneration::compute_evacuation_budgets(ShenandoahHeap* const heap // => OE = YE*SOEP/(100-SOEP) // We have to be careful in the event that SOEP is set to 100 by the user. - assert(ShenandoahOldEvacRatioPercent <= 100, "Error"); + assert(ShenandoahOldEvacPercent <= 100, "Error"); const size_t old_available = old_generation->available(); - const size_t maximum_old_evacuation_reserve = (ShenandoahOldEvacRatioPercent == 100) ? - old_available : MIN2((maximum_young_evacuation_reserve * ShenandoahOldEvacRatioPercent) / (100 - ShenandoahOldEvacRatioPercent), + const size_t maximum_old_evacuation_reserve = (ShenandoahOldEvacPercent == 100) ? + old_available : MIN2((maximum_young_evacuation_reserve * ShenandoahOldEvacPercent) / (100 - ShenandoahOldEvacPercent), old_available); + // In some cases, maximum_old_reserve < old_available (when limited by ShenandoahOldEvacPercent) + // This limit affects mixed evacuations, but does not affect promotions. // Second priority is to reclaim garbage out of old-gen if there are old-gen collection candidates. Third priority // is to promote as much as we have room to promote. However, if old-gen memory is in short supply, this means young @@ -305,10 +308,8 @@ void ShenandoahGeneration::compute_evacuation_budgets(ShenandoahHeap* const heap // evacuation and update-refs, we give emphasis to reclaiming garbage first, wherever that garbage is found. // Global GC will adjust generation sizes to accommodate the collection set it chooses. - // Set old_promo_reserve to enforce that no regions are preselected for promotion. Such regions typically - // have relatively high memory utilization. We still call select_aged_regions() because this will prepare for - // promotions in place, if relevant. - old_promo_reserve = 0; + // Use remnant of old_available to hold promotions. + old_promo_reserve = old_available - maximum_old_evacuation_reserve; // Dedicate all available old memory to old_evacuation reserve. This may be small, because old-gen is only // expanded based on an existing mixed evacuation workload at the end of the previous GC cycle. We'll expand @@ -319,43 +320,48 @@ void ShenandoahGeneration::compute_evacuation_budgets(ShenandoahHeap* const heap // mixed evacuation, reserve all of this memory for compaction of old-gen and do not promote. Prioritize compaction // over promotion in order to defragment OLD so that it will be better prepared to efficiently receive promoted memory. old_evacuation_reserve = maximum_old_evacuation_reserve; - old_promo_reserve = 0; + old_promo_reserve = old_available - maximum_old_evacuation_reserve; } else { // Make all old-evacuation memory for promotion, but if we can't use it all for promotion, we'll allow some evacuation. - old_evacuation_reserve = 0; + old_evacuation_reserve = old_available - maximum_old_evacuation_reserve; old_promo_reserve = maximum_old_evacuation_reserve; } assert(old_evacuation_reserve <= old_available, "Error"); + // We see too many old-evacuation failures if we force ourselves to evacuate into regions that are not initially empty. // So we limit the old-evacuation reserve to unfragmented memory. Even so, old-evacuation is free to fill in nooks and // crannies within existing partially used regions and it generally tries to do so. - const size_t old_free_unfragmented = old_generation->free_unaffiliated_regions() * ShenandoahHeapRegion::region_size_bytes(); + const size_t old_free_unfragmented = old_generation->free_unaffiliated_regions() * region_size_bytes; if (old_evacuation_reserve > old_free_unfragmented) { const size_t delta = old_evacuation_reserve - old_free_unfragmented; old_evacuation_reserve -= delta; - // Let promo consume fragments of old-gen memory if not global - if (!is_global()) { - old_promo_reserve += delta; - } + // Let promo consume fragments of old-gen memory + old_promo_reserve += delta; } - // Preselect regions for promotion by evacuation (obtaining the live data to seed promoted_reserve), - // and identify regions that will promote in place. These use the tenuring threshold. - const size_t consumed_by_advance_promotion = select_aged_regions(old_promo_reserve); - assert(consumed_by_advance_promotion <= maximum_old_evacuation_reserve, "Cannot promote more than available old-gen memory"); + // If is_global(), we let garbage-first heuristic determine cset membership. Otherwise, we give priority + // to tenurable regions by preselecting regions for promotion by evacuation (obtaining the live data to seed promoted_reserve). + // This also identifies regions that will be promoted in place. These use the tenuring threshold. + const size_t consumed_by_advance_promotion = select_aged_regions(is_global()? 0: old_promo_reserve); + assert(consumed_by_advance_promotion <= old_promo_reserve, "Do not promote more than budgeted"); + + // The young evacuation reserve can be no larger than young_unaffiliated. Planning to evacuate into partially consumed + // young regions is doomed to failure if any of those partially consumed regions is selected for the collection set. + size_t young_unaffiliated = young_generation->free_unaffiliated_regions() * region_size_bytes; // If any regions have been selected for promotion in place, this has the effect of decreasing available within mutator // and collector partitions, due to padding of remnant memory within each promoted in place region. This will affect // young_evacuation_reserve but not old_evacuation_reserve or consumed_by_advance_promotion. So recompute. - young_evacuation_reserve = MIN2(young_evacuation_reserve, young_generation->available_with_reserve()); + size_t young_evacuation_reserve = MIN2(maximum_young_evacuation_reserve, young_unaffiliated); // Note that unused old_promo_reserve might not be entirely consumed_by_advance_promotion. Do not transfer this // to old_evacuation_reserve because this memory is likely very fragmented, and we do not want to increase the likelihood - // of old evacuation failure. + // of old evacuation failure. Leave this memory in the promoted reserve as it may be targeted by opportunistic + // promotions (found during evacuation of young regions). young_generation->set_evacuation_reserve(young_evacuation_reserve); old_generation->set_evacuation_reserve(old_evacuation_reserve); - old_generation->set_promoted_reserve(consumed_by_advance_promotion); + old_generation->set_promoted_reserve(old_promo_reserve); // There is no need to expand OLD because all memory used here was set aside at end of previous GC, except in the // case of a GLOBAL gc. During choose_collection_set() of GLOBAL, old will be expanded on demand. @@ -363,8 +369,8 @@ void ShenandoahGeneration::compute_evacuation_budgets(ShenandoahHeap* const heap // Having chosen the collection set, adjust the budgets for generational mode based on its composition. Note // that young_generation->available() now knows about recently discovered immediate garbage. -// -void ShenandoahGeneration::adjust_evacuation_budgets(ShenandoahHeap* const heap, ShenandoahCollectionSet* const collection_set) { +void ShenandoahGeneration::adjust_evacuation_budgets(ShenandoahHeap* const heap, + ShenandoahCollectionSet* const collection_set, size_t add_regions_to_old) { shenandoah_assert_generational(); // We may find that old_evacuation_reserve and/or loaned_for_young_evacuation are not fully consumed, in which case we may // be able to increase regions_available_to_loan @@ -398,7 +404,8 @@ void ShenandoahGeneration::adjust_evacuation_budgets(ShenandoahHeap* const heap, // Leave old_evac_reserve as previously configured } else if (old_evacuated_committed < old_evacuation_reserve) { // This happens if the old-gen collection consumes less than full budget. - log_debug(gc, cset)("Shrinking old evac reserve to match old_evac_commited: " PROPERFMT, PROPERFMTARGS(old_evacuated_committed)); + log_debug(gc, cset)("Shrinking old evac reserve to match old_evac_commited: " PROPERFMT, + PROPERFMTARGS(old_evacuated_committed)); old_evacuation_reserve = old_evacuated_committed; old_generation->set_evacuation_reserve(old_evacuation_reserve); } @@ -409,11 +416,17 @@ void ShenandoahGeneration::adjust_evacuation_budgets(ShenandoahHeap* const heap, size_t young_evacuated = collection_set->get_live_bytes_in_untenurable_regions(); size_t young_evacuated_reserve_used = (size_t) (ShenandoahEvacWaste * double(young_evacuated)); - size_t total_young_available = young_generation->available_with_reserve(); - assert(young_evacuated_reserve_used <= total_young_available, "Cannot evacuate more than is available in young"); + size_t total_young_available = young_generation->available_with_reserve() - add_regions_to_old * region_size_bytes;; + assert(young_evacuated_reserve_used <= total_young_available, "Cannot evacuate (%zu) more than is available in young (%zu)", + young_evacuated_reserve_used, total_young_available); young_generation->set_evacuation_reserve(young_evacuated_reserve_used); - size_t old_available = old_generation->available(); + // We have not yet rebuilt the free set. Some of the memory that is thought to be avaiable within old may no + // longer be available if that memory had been free within regions that were selected for the collection set. + // Make the necessary adjustments to old_available. + size_t old_available = + old_generation->available() + add_regions_to_old * region_size_bytes - collection_set->get_old_available_bytes_collected(); + // Now that we've established the collection set, we know how much memory is really required by old-gen for evacuation // and promotion reserves. Try shrinking OLD now in case that gives us a bit more runway for mutator allocations during // evac and update phases. @@ -422,21 +435,27 @@ void ShenandoahGeneration::adjust_evacuation_budgets(ShenandoahHeap* const heap, if (old_available < old_consumed) { // This can happen due to round-off errors when adding the results of truncated integer arithmetic. // We've already truncated old_evacuated_committed. Truncate young_advance_promoted_reserve_used here. + assert(young_advance_promoted_reserve_used <= (33 * (old_available - old_evacuated_committed)) / 32, "Round-off errors should be less than 3.125%%, committed: %zu, reserved: %zu", young_advance_promoted_reserve_used, old_available - old_evacuated_committed); - young_advance_promoted_reserve_used = old_available - old_evacuated_committed; + if (old_available > old_evacuated_committed) { + young_advance_promoted_reserve_used = old_available - old_evacuated_committed; + } else { + young_advance_promoted_reserve_used = 0; + old_evacuated_committed = old_available; + } + // TODO: reserve for full promotion reserve, not just for advance (preselected) promotion old_consumed = old_evacuated_committed + young_advance_promoted_reserve_used; } assert(old_available >= old_consumed, "Cannot consume (%zu) more than is available (%zu)", old_consumed, old_available); size_t excess_old = old_available - old_consumed; - size_t unaffiliated_old_regions = old_generation->free_unaffiliated_regions(); + size_t unaffiliated_old_regions = old_generation->free_unaffiliated_regions() + add_regions_to_old; size_t unaffiliated_old = unaffiliated_old_regions * region_size_bytes; - assert(old_available >= unaffiliated_old, - "Unaffiliated old (%zu is %zu * %zu) is a subset of old available (%zu)", - unaffiliated_old, unaffiliated_old_regions, region_size_bytes, old_available); + assert(unaffiliated_old >= old_evacuated_committed, "Do not evacuate (%zu) more than unaffiliated old (%zu)", + old_evacuated_committed, unaffiliated_old); // Make sure old_evac_committed is unaffiliated if (old_evacuated_committed > 0) { @@ -454,20 +473,22 @@ void ShenandoahGeneration::adjust_evacuation_budgets(ShenandoahHeap* const heap, } // If we find that OLD has excess regions, give them back to YOUNG now to reduce likelihood we run out of allocation - // runway during evacuation and update-refs. - size_t regions_to_xfer = 0; + // runway during evacuation and update-refs. We may make further adjustments to balance. + ssize_t add_regions_to_young = 0; if (excess_old > unaffiliated_old) { // we can give back unaffiliated_old (all of unaffiliated is excess) if (unaffiliated_old_regions > 0) { - regions_to_xfer = unaffiliated_old_regions; + add_regions_to_young = unaffiliated_old_regions; } } else if (unaffiliated_old_regions > 0) { // excess_old < unaffiliated old: we can give back MIN(excess_old/region_size_bytes, unaffiliated_old_regions) size_t excess_regions = excess_old / region_size_bytes; - regions_to_xfer = MIN2(excess_regions, unaffiliated_old_regions); + add_regions_to_young = MIN2(excess_regions, unaffiliated_old_regions); } - if (regions_to_xfer > 0) { - excess_old -= regions_to_xfer * region_size_bytes; + + if (add_regions_to_young > 0) { + assert(excess_old >= add_regions_to_young * region_size_bytes, "Cannot xfer more than excess old"); + excess_old -= add_regions_to_young * region_size_bytes; log_debug(gc, ergo)("Before start of evacuation, total_promotion reserve is young_advance_promoted_reserve: %zu " "plus excess: old: %zu", young_advance_promoted_reserve_used, excess_old); } @@ -475,6 +496,7 @@ void ShenandoahGeneration::adjust_evacuation_budgets(ShenandoahHeap* const heap, // Add in the excess_old memory to hold unanticipated promotions, if any. If there are more unanticipated // promotions than fit in reserved memory, they will be deferred until a future GC pass. size_t total_promotion_reserve = young_advance_promoted_reserve_used + excess_old; + old_generation->set_promoted_reserve(total_promotion_reserve); old_generation->reset_promoted_expended(); } @@ -782,17 +804,13 @@ void ShenandoahGeneration::prepare_regions_and_collection_set(bool concurrent) { ShenandoahCollectionSetPreselector preselector(collection_set, heap->num_regions()); // Find the amount that will be promoted, regions that will be promoted in - // place, and preselect older regions that will be promoted by evacuation. + // place, and preselected older regions that will be promoted by evacuation. compute_evacuation_budgets(heap); - // Choose the collection set, including the regions preselected above for - // promotion into the old generation. - _heuristics->choose_collection_set(collection_set); - if (!collection_set->is_empty()) { - // only make use of evacuation budgets when we are evacuating - adjust_evacuation_budgets(heap, collection_set); - } - + // Choose the collection set, including the regions preselected above for promotion into the old generation. + size_t add_regions_to_old = _heuristics->choose_collection_set(collection_set); + // Even if collection_set->is_empty(), we want to adjust budgets, making reserves available to mutator. + adjust_evacuation_budgets(heap, collection_set, add_regions_to_old); if (is_global()) { // We have just chosen a collection set for a global cycle. The mark bitmap covering old regions is complete, so // the remembered set scan can use that to avoid walking into garbage. When the next old mark begins, we will @@ -815,19 +833,17 @@ void ShenandoahGeneration::prepare_regions_and_collection_set(bool concurrent) { ShenandoahGCPhase phase(concurrent ? ShenandoahPhaseTimings::final_rebuild_freeset : ShenandoahPhaseTimings::degen_gc_final_rebuild_freeset); ShenandoahHeapLocker locker(heap->lock()); - size_t young_cset_regions, old_cset_regions; - - // We are preparing for evacuation. At this time, we ignore cset region tallies. - size_t first_old, last_old, num_old; - _free_set->prepare_to_rebuild(young_cset_regions, old_cset_regions, first_old, last_old, num_old); + // We are preparing for evacuation. + size_t young_trashed_regions, old_trashed_regions, first_old, last_old, num_old; + _free_set->prepare_to_rebuild(young_trashed_regions, old_trashed_regions, first_old, last_old, num_old); if (heap->mode()->is_generational()) { ShenandoahGenerationalHeap* gen_heap = ShenandoahGenerationalHeap::heap(); - gen_heap->compute_old_generation_balance(young_cset_regions, old_cset_regions); + size_t allocation_runway = + gen_heap->young_generation()->heuristics()->bytes_of_allocation_runway_before_gc_trigger(young_trashed_regions); + gen_heap->compute_old_generation_balance(allocation_runway, old_trashed_regions, young_trashed_regions); } - - // Free set construction uses reserve quantities, because they are known to be valid here - _free_set->finish_rebuild(young_cset_regions, old_cset_regions, num_old, true); + _free_set->finish_rebuild(young_trashed_regions, old_trashed_regions, num_old); } } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.hpp b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.hpp index 06cf132f946..d49e3bed5f8 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.hpp @@ -63,9 +63,10 @@ private: // Compute evacuation budgets prior to choosing collection set. void compute_evacuation_budgets(ShenandoahHeap* heap); - // Adjust evacuation budgets after choosing collection set. + // Adjust evacuation budgets after choosing collection set. The argument regions_to_xfer represents regions to be + // transfered to old based on decisions made in top_off_collection_set() void adjust_evacuation_budgets(ShenandoahHeap* heap, - ShenandoahCollectionSet* collection_set); + ShenandoahCollectionSet* collection_set, size_t regions_to_xfer); // Preselect for possible inclusion into the collection set exactly the most // garbage-dense regions, including those that satisfy criteria 1 & 2 below, @@ -144,6 +145,22 @@ private: virtual void prepare_gc(); // Called during final mark, chooses collection set, rebuilds free set. + // Upon return from prepare_regions_and_collection_set(), certain parameters have been established to govern the + // evacuation efforts that are about to begin. In particular: + // + // old_generation->get_promoted_reserve() represents the amount of memory within old-gen's available memory that has + // been set aside to hold objects promoted from young-gen memory. This represents an estimated percentage + // of the live young-gen memory within the collection set. If there is more data ready to be promoted than + // can fit within this reserve, the promotion of some objects will be deferred until a subsequent evacuation + // pass. + // + // old_generation->get_evacuation_reserve() represents the amount of memory within old-gen's available memory that has been + // set aside to hold objects evacuated from the old-gen collection set. + // + // young_generation->get_evacuation_reserve() represents the amount of memory within young-gen's available memory that has + // been set aside to hold objects evacuated from the young-gen collection set. Conservatively, this value + // equals the entire amount of live young-gen memory within the collection set, even though some of this memory + // will likely be promoted. virtual void prepare_regions_and_collection_set(bool concurrent); // Cancel marking (used by Full collect and when cancelling cycle). diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp index ece4150f577..3b57190cc75 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp @@ -47,7 +47,7 @@ #include "utilities/events.hpp" ShenandoahGenerationalControlThread::ShenandoahGenerationalControlThread() : - _control_lock(Mutex::nosafepoint - 2, "ShenandoahGCRequest_lock", true), + _control_lock(CONTROL_LOCK_RANK, "ShenandoahGCRequest_lock", true), _requested_gc_cause(GCCause::_no_gc), _requested_generation(nullptr), _gc_mode(none), @@ -61,7 +61,12 @@ ShenandoahGenerationalControlThread::ShenandoahGenerationalControlThread() : void ShenandoahGenerationalControlThread::run_service() { + // This is the only instance of request. It is important that request.generation + // does not change between a concurrent cycle failure and the start of a degenerated + // cycle. We initialize it with the young generation to handle the pathological case + // where the very first cycle is degenerated (some tests exercise this path). ShenandoahGCRequest request; + request.generation = _heap->young_generation(); while (!should_terminate()) { // Figure out if we have pending requests. @@ -77,12 +82,10 @@ void ShenandoahGenerationalControlThread::run_service() { // If the cycle was cancelled, continue the next iteration to deal with it. Otherwise, // if there was no other cycle requested, cleanup and wait for the next request. - if (!_heap->cancelled_gc()) { - MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag); - if (_requested_gc_cause == GCCause::_no_gc) { - set_gc_mode(ml, none); - ml.wait(); - } + MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag); + if (_requested_gc_cause == GCCause::_no_gc) { + set_gc_mode(ml, none); + ml.wait(); } } @@ -96,8 +99,7 @@ void ShenandoahGenerationalControlThread::stop_service() { log_debug(gc, thread)("Stopping control thread"); MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag); _heap->cancel_gc(GCCause::_shenandoah_stop_vm); - _requested_gc_cause = GCCause::_shenandoah_stop_vm; - notify_cancellation(ml, GCCause::_shenandoah_stop_vm); + notify_control_thread(ml, GCCause::_shenandoah_stop_vm); // We can't wait here because it may interfere with the active cycle's ability // to reach a safepoint (this runs on a java thread). } @@ -105,29 +107,39 @@ void ShenandoahGenerationalControlThread::stop_service() { void ShenandoahGenerationalControlThread::check_for_request(ShenandoahGCRequest& request) { // Hold the lock while we read request cause and generation MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag); - if (_heap->cancelled_gc()) { - // The previous request was cancelled. Either it was cancelled for an allocation - // failure (degenerated cycle), or old marking was cancelled to run a young collection. - // In either case, the correct generation for the next cycle can be determined by - // the cancellation cause. - request.cause = _heap->clear_cancellation(GCCause::_shenandoah_concurrent_gc); - if (request.cause == GCCause::_shenandoah_concurrent_gc) { + + log_debug(gc, thread)("cancelled cause: %s, requested cause: %s", + GCCause::to_string(_heap->cancelled_cause()), GCCause::to_string(_requested_gc_cause)); + + request.cause = _requested_gc_cause; + if (ShenandoahCollectorPolicy::is_allocation_failure(request.cause)) { + if (_degen_point == ShenandoahGC::_degenerated_unset) { request.generation = _heap->young_generation(); + _degen_point = ShenandoahGC::_degenerated_outside_cycle; + } else { + assert(request.generation != nullptr, "Must know which generation to use for degenerated cycle"); } } else { - request.cause = _requested_gc_cause; + if (request.cause == GCCause::_shenandoah_concurrent_gc) { + // This is a regulator request. It is also possible that the regulator "canceled" an old mark, + // so we can clear that here. This clear operation will only clear the cancellation if it is + // a regulator request. + _heap->clear_cancellation(GCCause::_shenandoah_concurrent_gc); + } request.generation = _requested_generation; - - // Only clear these if we made a request from them. In the case of a cancelled gc, - // we do not want to inadvertently lose this pending request. - _requested_gc_cause = GCCause::_no_gc; - _requested_generation = nullptr; } + log_debug(gc, thread)("request.cause: %s, request.generation: %s", + GCCause::to_string(request.cause), request.generation == nullptr ? "None" : request.generation->name()); + + _requested_gc_cause = GCCause::_no_gc; + _requested_generation = nullptr; + if (request.cause == GCCause::_no_gc || request.cause == GCCause::_shenandoah_stop_vm) { return; } + assert(request.generation != nullptr, "request.generation cannot be null, cause is: %s", GCCause::to_string(request.cause)); GCMode mode; if (ShenandoahCollectorPolicy::is_allocation_failure(request.cause)) { mode = prepare_for_allocation_failure_gc(request); @@ -140,11 +152,9 @@ void ShenandoahGenerationalControlThread::check_for_request(ShenandoahGCRequest& } ShenandoahGenerationalControlThread::GCMode ShenandoahGenerationalControlThread::prepare_for_allocation_failure_gc(ShenandoahGCRequest &request) { - - if (_degen_point == ShenandoahGC::_degenerated_unset) { - _degen_point = ShenandoahGC::_degenerated_outside_cycle; - request.generation = _heap->young_generation(); - } else if (request.generation->is_old()) { + // Important: not all paths update the request.generation. This is intentional. + // A degenerated cycle must use the same generation carried over from the previous request. + if (request.generation->is_old()) { // This means we degenerated during the young bootstrap for the old generation // cycle. The following degenerated cycle should therefore also be young. request.generation = _heap->young_generation(); @@ -588,6 +598,8 @@ bool ShenandoahGenerationalControlThread::check_cancellation_or_degen(Shenandoah if (ShenandoahCollectorPolicy::is_allocation_failure(_heap->cancelled_cause())) { assert(_degen_point == ShenandoahGC::_degenerated_unset, "Should not be set yet: %s", ShenandoahGC::degen_point_to_string(_degen_point)); + MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag); + _requested_gc_cause = _heap->cancelled_cause(); _degen_point = point; log_debug(gc, thread)("Cancellation detected:, reason: %s, degen point: %s", GCCause::to_string(_heap->cancelled_cause()), @@ -633,9 +645,7 @@ void ShenandoahGenerationalControlThread::service_stw_degenerated_cycle(const Sh void ShenandoahGenerationalControlThread::request_gc(GCCause::Cause cause) { if (ShenandoahCollectorPolicy::is_allocation_failure(cause)) { - // GC should already be cancelled. Here we are just notifying the control thread to - // wake up and handle the cancellation request, so we don't need to set _requested_gc_cause. - notify_cancellation(cause); + notify_control_thread(cause); } else if (ShenandoahCollectorPolicy::should_handle_requested_gc(cause)) { handle_requested_gc(cause); } @@ -653,7 +663,8 @@ bool ShenandoahGenerationalControlThread::request_concurrent_gc(ShenandoahGenera MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag); if (gc_mode() == servicing_old) { if (!preempt_old_marking(generation)) { - log_debug(gc, thread)("Cannot start young, old collection is not preemptible"); + // Global should be able to cause old collection to be abandoned + log_debug(gc, thread)("Cannot start %s, old collection is not preemptible", generation->name()); return false; } @@ -661,7 +672,7 @@ bool ShenandoahGenerationalControlThread::request_concurrent_gc(ShenandoahGenera log_info(gc)("Preempting old generation mark to allow %s GC", generation->name()); while (gc_mode() == servicing_old) { ShenandoahHeap::heap()->cancel_gc(GCCause::_shenandoah_concurrent_gc); - notify_cancellation(ml, GCCause::_shenandoah_concurrent_gc); + notify_control_thread(ml, GCCause::_shenandoah_concurrent_gc, generation); ml.wait(); } return true; @@ -695,21 +706,34 @@ void ShenandoahGenerationalControlThread::notify_control_thread(GCCause::Cause c void ShenandoahGenerationalControlThread::notify_control_thread(MonitorLocker& ml, GCCause::Cause cause, ShenandoahGeneration* generation) { assert(_control_lock.is_locked(), "Request lock must be held here"); - log_debug(gc, thread)("Notify control (%s): %s, %s", gc_mode_name(gc_mode()), GCCause::to_string(cause), generation->name()); - _requested_gc_cause = cause; - _requested_generation = generation; - ml.notify(); + if (ShenandoahCollectorPolicy::is_allocation_failure(_requested_gc_cause)) { + // We have already observed a request to handle an allocation failure. We cannot allow + // another request (System.gc or regulator) to subvert the degenerated cycle. + log_debug(gc, thread)("Not overwriting gc cause %s with %s", GCCause::to_string(_requested_gc_cause), GCCause::to_string(cause)); + } else { + log_debug(gc, thread)("Notify control (%s): %s, %s", gc_mode_name(gc_mode()), GCCause::to_string(cause), generation->name()); + _requested_gc_cause = cause; + _requested_generation = generation; + ml.notify(); + } } -void ShenandoahGenerationalControlThread::notify_cancellation(GCCause::Cause cause) { +void ShenandoahGenerationalControlThread::notify_control_thread(GCCause::Cause cause) { MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag); - notify_cancellation(ml, cause); + notify_control_thread(ml, cause); } -void ShenandoahGenerationalControlThread::notify_cancellation(MonitorLocker& ml, GCCause::Cause cause) { - assert(_heap->cancelled_gc(), "GC should already be cancelled"); - log_debug(gc,thread)("Notify control (%s): %s", gc_mode_name(gc_mode()), GCCause::to_string(cause)); - ml.notify(); +void ShenandoahGenerationalControlThread::notify_control_thread(MonitorLocker& ml, GCCause::Cause cause) { + assert(_control_lock.is_locked(), "Request lock must be held here"); + if (ShenandoahCollectorPolicy::is_allocation_failure(_requested_gc_cause)) { + // We have already observed a request to handle an allocation failure. We cannot allow + // another request (System.gc or regulator) to subvert the degenerated cycle. + log_debug(gc, thread)("Not overwriting gc cause %s with %s", GCCause::to_string(_requested_gc_cause), GCCause::to_string(cause)); + } else { + log_debug(gc, thread)("Notify control (%s): %s", gc_mode_name(gc_mode()), GCCause::to_string(cause)); + _requested_gc_cause = cause; + ml.notify(); + } } bool ShenandoahGenerationalControlThread::preempt_old_marking(ShenandoahGeneration* generation) { diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.hpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.hpp index b7dbedd5e84..13e69d25268 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.hpp @@ -135,16 +135,13 @@ private: // Return printable name for the given gc mode. static const char* gc_mode_name(GCMode mode); - // Takes the request lock and updates the requested cause and generation, then notifies the control thread. - // The overloaded variant should be used when the _control_lock is already held. + // These notify the control thread after updating _requested_gc_cause and (optionally) _requested_generation. + // Updating the requested generation is not necessary for allocation failures nor when stopping the thread. + void notify_control_thread(GCCause::Cause cause); + void notify_control_thread(MonitorLocker& ml, GCCause::Cause cause); void notify_control_thread(GCCause::Cause cause, ShenandoahGeneration* generation); void notify_control_thread(MonitorLocker& ml, GCCause::Cause cause, ShenandoahGeneration* generation); - // Notifies the control thread, but does not update the requested cause or generation. - // The overloaded variant should be used when the _control_lock is already held. - void notify_cancellation(GCCause::Cause cause); - void notify_cancellation(MonitorLocker& ml, GCCause::Cause cause); - // Configure the heap to age objects and regions if the aging period has elapsed. void maybe_set_aging_cycle(); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalFullGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalFullGC.cpp index 78672ee10a5..1b11c696d18 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalFullGC.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalFullGC.cpp @@ -55,9 +55,6 @@ void ShenandoahGenerationalFullGC::prepare() { // Since we may arrive here from degenerated GC failure of either young or old, establish generation as GLOBAL. heap->set_active_generation(heap->global_generation()); - // No need for old_gen->increase_used() as this was done when plabs were allocated. - heap->reset_generation_reserves(); - // Full GC supersedes any marking or coalescing in old generation. heap->old_generation()->cancel_gc(); } @@ -156,8 +153,11 @@ void ShenandoahGenerationalFullGC::compute_balances() { // In case this Full GC resulted from degeneration, clear the tally on anticipated promotion. heap->old_generation()->set_promotion_potential(0); - // Invoke this in case we are able to transfer memory from OLD to YOUNG. - heap->compute_old_generation_balance(0, 0); + + // Invoke this in case we are able to transfer memory from OLD to YOUNG + size_t allocation_runway = + heap->young_generation()->heuristics()->bytes_of_allocation_runway_before_gc_trigger(0L); + heap->compute_old_generation_balance(allocation_runway, 0, 0); } ShenandoahPrepareForGenerationalCompactionObjectClosure::ShenandoahPrepareForGenerationalCompactionObjectClosure(PreservedMarks* preserved_marks, diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp index f887cc9064e..36ea0b9e497 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp @@ -104,17 +104,6 @@ void ShenandoahGenerationalHeap::initialize_heuristics() { // Initialize global generation and heuristics even in generational mode. ShenandoahHeap::initialize_heuristics(); - // Max capacity is the maximum _allowed_ capacity. That is, the maximum allowed capacity - // for old would be total heap - minimum capacity of young. This means the sum of the maximum - // allowed for old and young could exceed the total heap size. It remains the case that the - // _actual_ capacity of young + old = total. - size_t region_count = num_regions(); - size_t max_young_regions = MAX2((region_count * ShenandoahMaxYoungPercentage) / 100, (size_t) 1U); - size_t initial_capacity_young = max_young_regions * ShenandoahHeapRegion::region_size_bytes(); - size_t max_capacity_young = initial_capacity_young; - size_t initial_capacity_old = max_capacity() - max_capacity_young; - size_t max_capacity_old = max_capacity() - initial_capacity_young; - _young_generation = new ShenandoahYoungGeneration(max_workers()); _old_generation = new ShenandoahOldGeneration(max_workers()); _young_generation->initialize_heuristics(mode()); @@ -310,9 +299,9 @@ oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, uint alloc_from_lab = false; } // else, we leave copy equal to nullptr, signaling a promotion failure below if appropriate. - // We choose not to promote objects smaller than PLAB::min_size() by way of shared allocations, as this is too + // We choose not to promote objects smaller than size_threshold by way of shared allocations as this is too // costly. Instead, we'll simply "evacuate" to young-gen memory (using a GCLAB) and will promote in a future - // evacuation pass. This condition is denoted by: is_promotion && has_plab && (size <= PLAB::min_size()) + // evacuation pass. This condition is denoted by: is_promotion && has_plab && (size <= size_threshhold). } #ifdef ASSERT } @@ -587,19 +576,18 @@ void ShenandoahGenerationalHeap::retire_plab(PLAB* plab) { // Make sure old-generation is large enough, but no larger than is necessary, to hold mixed evacuations // and promotions, if we anticipate either. Any deficit is provided by the young generation, subject to -// xfer_limit, and any surplus is transferred to the young generation. -// -// xfer_limit is the maximum we're able to transfer from young to old based on either: -// 1. an assumption that we will be able to replenish memory "borrowed" from young at the end of collection, or -// 2. there is sufficient excess in the allocation runway during GC idle cycles -void ShenandoahGenerationalHeap::compute_old_generation_balance(size_t old_xfer_limit, size_t old_cset_regions) { - +// mutator_xfer_limit, and any surplus is transferred to the young generation. mutator_xfer_limit is +// the maximum we're able to transfer from young to old. This is called at the end of GC, as we prepare +// for the idle span that precedes the next GC. +void ShenandoahGenerationalHeap::compute_old_generation_balance(size_t mutator_xfer_limit, + size_t old_trashed_regions, size_t young_trashed_regions) { + shenandoah_assert_heaplocked(); // We can limit the old reserve to the size of anticipated promotions: // max_old_reserve is an upper bound on memory evacuated from old and promoted to old, // clamped by the old generation space available. // // Here's the algebra. - // Let SOEP = ShenandoahOldEvacRatioPercent, + // Let SOEP = ShenandoahOldEvacPercent, // OE = old evac, // YE = young evac, and // TE = total evac = OE + YE @@ -611,81 +599,171 @@ void ShenandoahGenerationalHeap::compute_old_generation_balance(size_t old_xfer_ // => OE = YE*SOEP/(100-SOEP) // We have to be careful in the event that SOEP is set to 100 by the user. - assert(ShenandoahOldEvacRatioPercent <= 100, "Error"); - const size_t old_available = old_generation()->available(); - // The free set will reserve this amount of memory to hold young evacuations - const size_t young_reserve = (young_generation()->max_capacity() * ShenandoahEvacReserve) / 100; - - // In the case that ShenandoahOldEvacRatioPercent equals 100, max_old_reserve is limited only by xfer_limit. - - const double bound_on_old_reserve = old_available + old_xfer_limit + young_reserve; - const double max_old_reserve = ((ShenandoahOldEvacRatioPercent == 100)? bound_on_old_reserve: - MIN2(double(young_reserve * ShenandoahOldEvacRatioPercent) - / double(100 - ShenandoahOldEvacRatioPercent), bound_on_old_reserve)); - + assert(ShenandoahOldEvacPercent <= 100, "Error"); const size_t region_size_bytes = ShenandoahHeapRegion::region_size_bytes(); + ShenandoahOldGeneration* old_gen = old_generation(); + size_t old_capacity = old_gen->max_capacity(); + size_t old_usage = old_gen->used(); // includes humongous waste + size_t old_available = ((old_capacity >= old_usage)? old_capacity - old_usage: 0) + old_trashed_regions * region_size_bytes; + + ShenandoahYoungGeneration* young_gen = young_generation(); + size_t young_capacity = young_gen->max_capacity(); + size_t young_usage = young_gen->used(); // includes humongous waste + size_t young_available = ((young_capacity >= young_usage)? young_capacity - young_usage: 0); + size_t freeset_available = free_set()->available_locked(); + if (young_available > freeset_available) { + young_available = freeset_available; + } + young_available += young_trashed_regions * region_size_bytes; + + // The free set will reserve this amount of memory to hold young evacuations (initialized to the ideal reserve) + size_t young_reserve = (young_generation()->max_capacity() * ShenandoahEvacReserve) / 100; + + // If ShenandoahOldEvacPercent equals 100, max_old_reserve is limited only by mutator_xfer_limit and young_reserve + const size_t bound_on_old_reserve = ((old_available + mutator_xfer_limit + young_reserve) * ShenandoahOldEvacPercent) / 100; + size_t proposed_max_old = ((ShenandoahOldEvacPercent == 100)? + bound_on_old_reserve: + MIN2((young_reserve * ShenandoahOldEvacPercent) / (100 - ShenandoahOldEvacPercent), + bound_on_old_reserve)); + if (young_reserve > young_available) { + young_reserve = young_available; + } + // Decide how much old space we should reserve for a mixed collection - double reserve_for_mixed = 0; - if (old_generation()->has_unprocessed_collection_candidates()) { + size_t reserve_for_mixed = 0; + const size_t old_fragmented_available = + old_available - (old_generation()->free_unaffiliated_regions() + old_trashed_regions) * region_size_bytes; + + if (old_fragmented_available > proposed_max_old) { + // After we've promoted regions in place, there may be an abundance of old-fragmented available memory, + // even more than the desired percentage for old reserve. We cannot transfer these fragmented regions back + // to young. Instead we make the best of the situation by using this fragmented memory for both promotions + // and evacuations. + proposed_max_old = old_fragmented_available; + } + size_t reserve_for_promo = old_fragmented_available; + const size_t max_old_reserve = proposed_max_old; + const size_t mixed_candidate_live_memory = old_generation()->unprocessed_collection_candidates_live_memory(); + const bool doing_mixed = (mixed_candidate_live_memory > 0); + if (doing_mixed) { // We want this much memory to be unfragmented in order to reliably evacuate old. This is conservative because we // may not evacuate the entirety of unprocessed candidates in a single mixed evacuation. - const double max_evac_need = - (double(old_generation()->unprocessed_collection_candidates_live_memory()) * ShenandoahOldEvacWaste); + const size_t max_evac_need = (size_t) (mixed_candidate_live_memory * ShenandoahOldEvacWaste); assert(old_available >= old_generation()->free_unaffiliated_regions() * region_size_bytes, "Unaffiliated available must be less than total available"); - const double old_fragmented_available = - double(old_available - old_generation()->free_unaffiliated_regions() * region_size_bytes); - reserve_for_mixed = max_evac_need + old_fragmented_available; - if (reserve_for_mixed > max_old_reserve) { - reserve_for_mixed = max_old_reserve; + + // We prefer to evacuate all of mixed into unfragmented memory, and will expand old in order to do so, unless + // we already have too much fragmented available memory in old. + reserve_for_mixed = max_evac_need; + if (reserve_for_mixed + reserve_for_promo > max_old_reserve) { + // In this case, we'll allow old-evac to target some of the fragmented old memory. + size_t excess_reserves = (reserve_for_mixed + reserve_for_promo) - max_old_reserve; + if (reserve_for_promo > excess_reserves) { + reserve_for_promo -= excess_reserves; + } else { + excess_reserves -= reserve_for_promo; + reserve_for_promo = 0; + reserve_for_mixed -= excess_reserves; + } } } - // Decide how much space we should reserve for promotions from young - size_t reserve_for_promo = 0; + // Decide how much additional space we should reserve for promotions from young. We give priority to mixed evacations + // over promotions. const size_t promo_load = old_generation()->get_promotion_potential(); const bool doing_promotions = promo_load > 0; if (doing_promotions) { - // We're promoting and have a bound on the maximum amount that can be promoted - assert(max_old_reserve >= reserve_for_mixed, "Sanity"); - const size_t available_for_promotions = max_old_reserve - reserve_for_mixed; - reserve_for_promo = MIN2((size_t)(promo_load * ShenandoahPromoEvacWaste), available_for_promotions); + // We've already set aside all of the fragmented available memory within old-gen to represent old objects + // to be promoted from young generation. promo_load represents the memory that we anticipate to be promoted + // from regions that have reached tenure age. In the ideal, we will always use fragmented old-gen memory + // to hold individually promoted objects and will use unfragmented old-gen memory to represent the old-gen + // evacuation workloa. + + // We're promoting and have an estimate of memory to be promoted from aged regions + assert(max_old_reserve >= (reserve_for_mixed + reserve_for_promo), "Sanity"); + const size_t available_for_additional_promotions = max_old_reserve - (reserve_for_mixed + reserve_for_promo); + size_t promo_need = (size_t)(promo_load * ShenandoahPromoEvacWaste); + if (promo_need > reserve_for_promo) { + reserve_for_promo += MIN2(promo_need - reserve_for_promo, available_for_additional_promotions); + } + // We've already reserved all the memory required for the promo_load, and possibly more. The excess + // can be consumed by objects promoted from regions that have not yet reached tenure age. } - // This is the total old we want to ideally reserve - const size_t old_reserve = reserve_for_mixed + reserve_for_promo; - assert(old_reserve <= max_old_reserve, "cannot reserve more than max for old evacuations"); + // This is the total old we want to reserve (initialized to the ideal reserve) + size_t old_reserve = reserve_for_mixed + reserve_for_promo; // We now check if the old generation is running a surplus or a deficit. - const size_t max_old_available = old_generation()->available() + old_cset_regions * region_size_bytes; - if (max_old_available >= old_reserve) { - // We are running a surplus, so the old region surplus can go to young - const size_t old_surplus = (max_old_available - old_reserve) / region_size_bytes; - const size_t unaffiliated_old_regions = old_generation()->free_unaffiliated_regions() + old_cset_regions; - const size_t old_region_surplus = MIN2(old_surplus, unaffiliated_old_regions); - old_generation()->set_region_balance(checked_cast(old_region_surplus)); - } else { - // We are running a deficit which we'd like to fill from young. - // Ignore that this will directly impact young_generation()->max_capacity(), - // indirectly impacting young_reserve and old_reserve. These computations are conservative. - // Note that deficit is rounded up by one region. - const size_t old_need = (old_reserve - max_old_available + region_size_bytes - 1) / region_size_bytes; - const size_t max_old_region_xfer = old_xfer_limit / region_size_bytes; + size_t old_region_deficit = 0; + size_t old_region_surplus = 0; - // Round down the regions we can transfer from young to old. If we're running short - // on young-gen memory, we restrict the xfer. Old-gen collection activities will be - // curtailed if the budget is restricted. - const size_t old_region_deficit = MIN2(old_need, max_old_region_xfer); + size_t mutator_region_xfer_limit = mutator_xfer_limit / region_size_bytes; + // align the mutator_xfer_limit on region size + mutator_xfer_limit = mutator_region_xfer_limit * region_size_bytes; + + if (old_available >= old_reserve) { + // We are running a surplus, so the old region surplus can go to young + const size_t old_surplus = old_available - old_reserve; + old_region_surplus = old_surplus / region_size_bytes; + const size_t unaffiliated_old_regions = old_generation()->free_unaffiliated_regions() + old_trashed_regions; + old_region_surplus = MIN2(old_region_surplus, unaffiliated_old_regions); + old_generation()->set_region_balance(checked_cast(old_region_surplus)); + } else if (old_available + mutator_xfer_limit >= old_reserve) { + // Mutator's xfer limit is sufficient to satisfy our need: transfer all memory from there + size_t old_deficit = old_reserve - old_available; + old_region_deficit = (old_deficit + region_size_bytes - 1) / region_size_bytes; + old_generation()->set_region_balance(0 - checked_cast(old_region_deficit)); + } else { + // We'll try to xfer from both mutator excess and from young collector reserve + size_t available_reserves = old_available + young_reserve + mutator_xfer_limit; + size_t old_entitlement = (available_reserves * ShenandoahOldEvacPercent) / 100; + + // Round old_entitlement down to nearest multiple of regions to be transferred to old + size_t entitled_xfer = old_entitlement - old_available; + entitled_xfer = region_size_bytes * (entitled_xfer / region_size_bytes); + size_t unaffiliated_young_regions = young_generation()->free_unaffiliated_regions(); + size_t unaffiliated_young_memory = unaffiliated_young_regions * region_size_bytes; + if (entitled_xfer > unaffiliated_young_memory) { + entitled_xfer = unaffiliated_young_memory; + } + old_entitlement = old_available + entitled_xfer; + if (old_entitlement < old_reserve) { + // There's not enough memory to satisfy our desire. Scale back our old-gen intentions. + size_t budget_overrun = old_reserve - old_entitlement;; + if (reserve_for_promo > budget_overrun) { + reserve_for_promo -= budget_overrun; + old_reserve -= budget_overrun; + } else { + budget_overrun -= reserve_for_promo; + reserve_for_promo = 0; + reserve_for_mixed = (reserve_for_mixed > budget_overrun)? reserve_for_mixed - budget_overrun: 0; + old_reserve = reserve_for_promo + reserve_for_mixed; + } + } + + // Because of adjustments above, old_reserve may be smaller now than it was when we tested the branch + // condition above: "(old_available + mutator_xfer_limit >= old_reserve) + // Therefore, we do NOT know that: mutator_xfer_limit < old_reserve - old_available + + size_t old_deficit = old_reserve - old_available; + old_region_deficit = (old_deficit + region_size_bytes - 1) / region_size_bytes; + + // Shrink young_reserve to account for loan to old reserve + const size_t reserve_xfer_regions = old_region_deficit - mutator_region_xfer_limit; + young_reserve -= reserve_xfer_regions * region_size_bytes; old_generation()->set_region_balance(0 - checked_cast(old_region_deficit)); } -} -void ShenandoahGenerationalHeap::reset_generation_reserves() { - ShenandoahHeapLocker locker(lock()); - young_generation()->set_evacuation_reserve(0); - old_generation()->set_evacuation_reserve(0); - old_generation()->set_promoted_reserve(0); + assert(old_region_deficit == 0 || old_region_surplus == 0, "Only surplus or deficit, never both"); + assert(young_reserve + reserve_for_mixed + reserve_for_promo <= old_available + young_available, + "Cannot reserve more memory than is available: %zu + %zu + %zu <= %zu + %zu", + young_reserve, reserve_for_mixed, reserve_for_promo, old_available, young_available); + + // deficit/surplus adjustments to generation sizes will precede rebuild + young_generation()->set_evacuation_reserve(young_reserve); + old_generation()->set_evacuation_reserve(reserve_for_mixed); + old_generation()->set_promoted_reserve(reserve_for_promo); } void ShenandoahGenerationalHeap::coalesce_and_fill_old_regions(bool concurrent) { @@ -1026,10 +1104,6 @@ void ShenandoahGenerationalHeap::final_update_refs_update_region_states() { void ShenandoahGenerationalHeap::complete_degenerated_cycle() { shenandoah_assert_heaplocked_or_safepoint(); - // In case degeneration interrupted concurrent evacuation or update references, we need to clean up - // transient state. Otherwise, these actions have no effect. - reset_generation_reserves(); - if (!old_generation()->is_parsable()) { ShenandoahGCPhase phase(ShenandoahPhaseTimings::degen_gc_coalesce_and_fill); coalesce_and_fill_old_regions(false); @@ -1047,7 +1121,6 @@ void ShenandoahGenerationalHeap::complete_concurrent_cycle() { // throw off the heuristics. entry_global_coalesce_and_fill(); } - reset_generation_reserves(); } void ShenandoahGenerationalHeap::entry_global_coalesce_and_fill() { diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.hpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.hpp index 736026916f7..719bae52a83 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.hpp @@ -44,13 +44,13 @@ public: void post_initialize_heuristics() override; static ShenandoahGenerationalHeap* heap() { - assert(ShenandoahCardBarrier, "Should have card barrier to use genenrational heap"); + assert(ShenandoahCardBarrier, "Should have card barrier to use generational heap"); CollectedHeap* heap = Universe::heap(); return cast(heap); } static ShenandoahGenerationalHeap* cast(CollectedHeap* heap) { - assert(ShenandoahCardBarrier, "Should have card barrier to use genenrational heap"); + assert(ShenandoahCardBarrier, "Should have card barrier to use generational heap"); return checked_cast(heap); } @@ -136,7 +136,7 @@ public: void reset_generation_reserves(); // Computes the optimal size for the old generation, represented as a surplus or deficit of old regions - void compute_old_generation_balance(size_t old_xfer_limit, size_t old_cset_regions); + void compute_old_generation_balance(size_t old_xfer_limit, size_t old_trashed_regions, size_t young_trashed_regions); // Balances generations, coalesces and fills old regions if necessary void complete_degenerated_cycle(); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp index 3bf53f800a2..ef99bd98c93 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp @@ -425,22 +425,29 @@ jint ShenandoahHeap::initialize() { _affiliations[i] = ShenandoahAffiliation::FREE; } + + if (mode()->is_generational()) { + size_t young_reserve = (soft_max_capacity() * ShenandoahEvacReserve) / 100; + young_generation()->set_evacuation_reserve(young_reserve); + old_generation()->set_evacuation_reserve((size_t) 0); + old_generation()->set_promoted_reserve((size_t) 0); + } + _free_set = new ShenandoahFreeSet(this, _num_regions); - - post_initialize_heuristics(); + // We are initializing free set. We ignore cset region tallies. - size_t young_cset_regions, old_cset_regions, first_old, last_old, num_old; - _free_set->prepare_to_rebuild(young_cset_regions, old_cset_regions, first_old, last_old, num_old); + size_t young_trashed_regions, old_trashed_regions, first_old, last_old, num_old; + _free_set->prepare_to_rebuild(young_trashed_regions, old_trashed_regions, first_old, last_old, num_old); if (mode()->is_generational()) { ShenandoahGenerationalHeap* gen_heap = ShenandoahGenerationalHeap::heap(); // We cannot call // gen_heap->young_generation()->heuristics()->bytes_of_allocation_runway_before_gc_trigger(young_cset_regions) // until after the heap is fully initialized. So we make up a safe value here. size_t allocation_runway = InitialHeapSize / 2; - gen_heap->compute_old_generation_balance(allocation_runway, old_cset_regions); + gen_heap->compute_old_generation_balance(allocation_runway, old_trashed_regions, young_trashed_regions); } - _free_set->finish_rebuild(young_cset_regions, old_cset_regions, num_old); + _free_set->finish_rebuild(young_trashed_regions, old_trashed_regions, num_old); } if (AlwaysPreTouch) { @@ -1658,7 +1665,7 @@ void ShenandoahHeap::verify(VerifyOption vo) { } } size_t ShenandoahHeap::tlab_capacity() const { - return _free_set->capacity(); + return _free_set->capacity_not_holding_lock(); } class ObjectIterateScanRootClosure : public BasicOopIterateClosure { @@ -2138,7 +2145,7 @@ GCTracer* ShenandoahHeap::tracer() { } size_t ShenandoahHeap::tlab_used() const { - return _free_set->used(); + return _free_set->used_not_holding_lock(); } bool ShenandoahHeap::try_cancel_gc(GCCause::Cause cause) { @@ -2523,14 +2530,10 @@ void ShenandoahHeap::final_update_refs_update_region_states() { parallel_heap_region_iterate(&cl); } -void ShenandoahHeap::rebuild_free_set(bool concurrent) { - ShenandoahGCPhase phase(concurrent ? - ShenandoahPhaseTimings::final_update_refs_rebuild_freeset : - ShenandoahPhaseTimings::degen_gc_final_update_refs_rebuild_freeset); +void ShenandoahHeap::rebuild_free_set_within_phase() { ShenandoahHeapLocker locker(lock()); - size_t young_cset_regions, old_cset_regions; - size_t first_old_region, last_old_region, old_region_count; - _free_set->prepare_to_rebuild(young_cset_regions, old_cset_regions, first_old_region, last_old_region, old_region_count); + size_t young_trashed_regions, old_trashed_regions, first_old_region, last_old_region, old_region_count; + _free_set->prepare_to_rebuild(young_trashed_regions, old_trashed_regions, first_old_region, last_old_region, old_region_count); // If there are no old regions, first_old_region will be greater than last_old_region assert((first_old_region > last_old_region) || ((last_old_region + 1 - first_old_region >= old_region_count) && @@ -2548,19 +2551,12 @@ void ShenandoahHeap::rebuild_free_set(bool concurrent) { // The computation of bytes_of_allocation_runway_before_gc_trigger is quite conservative so consider all of this // available for transfer to old. Note that transfer of humongous regions does not impact available. ShenandoahGenerationalHeap* gen_heap = ShenandoahGenerationalHeap::heap(); - size_t allocation_runway = gen_heap->young_generation()->heuristics()->bytes_of_allocation_runway_before_gc_trigger(young_cset_regions); - gen_heap->compute_old_generation_balance(allocation_runway, old_cset_regions); - - // Total old_available may have been expanded to hold anticipated promotions. We trigger if the fragmented available - // memory represents more than 16 regions worth of data. Note that fragmentation may increase when we promote regular - // regions in place when many of these regular regions have an abundant amount of available memory within them. Fragmentation - // will decrease as promote-by-copy consumes the available memory within these partially consumed regions. - // - // We consider old-gen to have excessive fragmentation if more than 12.5% of old-gen is free memory that resides - // within partially consumed regions of memory. + size_t allocation_runway = + gen_heap->young_generation()->heuristics()->bytes_of_allocation_runway_before_gc_trigger(young_trashed_regions); + gen_heap->compute_old_generation_balance(allocation_runway, old_trashed_regions, young_trashed_regions); } // Rebuild free set based on adjusted generation sizes. - _free_set->finish_rebuild(young_cset_regions, old_cset_regions, old_region_count); + _free_set->finish_rebuild(young_trashed_regions, old_trashed_regions, old_region_count); if (mode()->is_generational()) { ShenandoahGenerationalHeap* gen_heap = ShenandoahGenerationalHeap::heap(); @@ -2569,6 +2565,13 @@ void ShenandoahHeap::rebuild_free_set(bool concurrent) { } } +void ShenandoahHeap::rebuild_free_set(bool concurrent) { + ShenandoahGCPhase phase(concurrent ? + ShenandoahPhaseTimings::final_update_refs_rebuild_freeset : + ShenandoahPhaseTimings::degen_gc_final_update_refs_rebuild_freeset); + rebuild_free_set_within_phase(); +} + bool ShenandoahHeap::is_bitmap_slice_committed(ShenandoahHeapRegion* r, bool skip_self) { size_t slice = r->index() / _bitmap_regions_per_slice; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp index 65e3803627c..174001170f4 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp @@ -481,7 +481,9 @@ private: void rendezvous_threads(const char* name); void recycle_trash(); public: + // The following two functions rebuild the free set at the end of GC, in preparation for an idle phase. void rebuild_free_set(bool concurrent); + void rebuild_free_set_within_phase(); void notify_gc_progress(); void notify_gc_no_progress(); size_t get_gc_no_progress_count() const; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.cpp index 3cd5cdd2ec3..6bb8382de0a 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.cpp @@ -75,6 +75,7 @@ ShenandoahHeapRegion::ShenandoahHeapRegion(HeapWord* start, size_t index, bool c _plab_allocs(0), _live_data(0), _critical_pins(0), + _mixed_candidate_garbage_words(0), _update_watermark(start), _age(0), #ifdef SHENANDOAH_CENSUS_NOISE @@ -565,6 +566,7 @@ void ShenandoahHeapRegion::recycle_internal() { assert(_recycling.is_set() && is_trash(), "Wrong state"); ShenandoahHeap* heap = ShenandoahHeap::heap(); + _mixed_candidate_garbage_words = 0; set_top(bottom()); clear_live_data(); reset_alloc_metadata(); @@ -593,6 +595,8 @@ void ShenandoahHeapRegion::try_recycle_under_lock() { _recycling.unset(); } else { // Ensure recycling is unset before returning to mutator to continue memory allocation. + // Otherwise, the mutator might see region as fully recycled and might change its affiliation only to have + // the racing GC worker thread overwrite its affiliation to FREE. while (_recycling.is_set()) { if (os::is_MP()) { SpinPause(); @@ -603,6 +607,8 @@ void ShenandoahHeapRegion::try_recycle_under_lock() { } } +// Note that return from try_recycle() does not mean the region has been recycled. It only means that +// some GC worker thread has taken responsibility to recycle the region, eventually. void ShenandoahHeapRegion::try_recycle() { shenandoah_assert_not_heaplocked(); if (is_trash() && _recycling.try_set()) { diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.hpp b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.hpp index cf0dc5476d0..9da2816e2c9 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.hpp @@ -43,6 +43,7 @@ class ShenandoahHeapRegion { friend class VMStructs; friend class ShenandoahHeapRegionStateConstant; private: + /* Region state is described by a state machine. Transitions are guarded by heap lock, which allows changing the state of several regions atomically. @@ -259,6 +260,8 @@ private: volatile size_t _live_data; volatile size_t _critical_pins; + size_t _mixed_candidate_garbage_words; + HeapWord* volatile _update_watermark; uint _age; @@ -398,6 +401,14 @@ public: // above TAMS. inline size_t get_live_data_words() const; + inline size_t get_mixed_candidate_live_data_bytes() const; + inline size_t get_mixed_candidate_live_data_words() const; + + inline void capture_mixed_candidate_garbage(); + + // Returns garbage by calculating difference between used and get_live_data_words. The value returned is only + // meaningful immediately following completion of marking. If there have been subsequent allocations in this region, + // use a different approach to determine garbage, such as (used() - get_mixed_candidate_live_data_bytes()) inline size_t garbage() const; void print_on(outputStream* st) const; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.inline.hpp index b9304ee9daa..be982433885 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.inline.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.inline.hpp @@ -163,6 +163,23 @@ inline size_t ShenandoahHeapRegion::get_live_data_bytes() const { return get_live_data_words() * HeapWordSize; } +inline size_t ShenandoahHeapRegion::get_mixed_candidate_live_data_bytes() const { + shenandoah_assert_heaplocked_or_safepoint(); + assert(used() >= _mixed_candidate_garbage_words * HeapWordSize, "used must exceed garbage"); + return used() - _mixed_candidate_garbage_words * HeapWordSize; +} + +inline size_t ShenandoahHeapRegion::get_mixed_candidate_live_data_words() const { + shenandoah_assert_heaplocked_or_safepoint(); + assert(used() >= _mixed_candidate_garbage_words * HeapWordSize, "used must exceed garbage"); + return used() / HeapWordSize - _mixed_candidate_garbage_words; +} + +inline void ShenandoahHeapRegion::capture_mixed_candidate_garbage() { + shenandoah_assert_heaplocked_or_safepoint(); + _mixed_candidate_garbage_words = garbage() / HeapWordSize; +} + inline bool ShenandoahHeapRegion::has_live() const { return get_live_data_words() != 0; } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp index 0a95ee9f149..ba24e890769 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp @@ -167,7 +167,7 @@ inline void ShenandoahMark::do_chunked_array_start(ShenandoahObjToScanQueue* q, if (len <= (int) ObjArrayMarkingStride*2) { // A few slices only, process directly - array->oop_iterate_range(cl, 0, len); + array->oop_iterate_elements_range(cl, 0, len); } else { int bits = log2i_graceful(len); // Compensate for non-power-of-two arrays, cover the array in excess: @@ -216,7 +216,7 @@ inline void ShenandoahMark::do_chunked_array_start(ShenandoahObjToScanQueue* q, // Process the irregular tail, if present int from = last_idx; if (from < len) { - array->oop_iterate_range(cl, from, len); + array->oop_iterate_elements_range(cl, from, len); } } } @@ -226,8 +226,6 @@ inline void ShenandoahMark::do_chunked_array(ShenandoahObjToScanQueue* q, T* cl, assert(obj->is_objArray(), "expect object array"); objArrayOop array = objArrayOop(obj); - assert (ObjArrayMarkingStride > 0, "sanity"); - // Split out tasks, as suggested in ShenandoahMarkTask docs. Avoid pushing tasks that // are known to start beyond the array. while ((1 << pow) > (int)ObjArrayMarkingStride && (chunk*2 < ShenandoahMarkTask::chunk_size())) { @@ -248,7 +246,7 @@ inline void ShenandoahMark::do_chunked_array(ShenandoahObjToScanQueue* q, T* cl, assert (0 < to && to <= len, "to is sane: %d/%d", to, len); #endif - array->oop_iterate_range(cl, from, to); + array->oop_iterate_elements_range(cl, from, to); } template diff --git a/src/hotspot/share/gc/shenandoah/shenandoahMetrics.cpp b/src/hotspot/share/gc/shenandoah/shenandoahMetrics.cpp index d774a8dba42..81c62ebbda9 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahMetrics.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahMetrics.cpp @@ -30,7 +30,7 @@ ShenandoahMetricsSnapshot::ShenandoahMetricsSnapshot(ShenandoahFreeSet* free_set) : _free_set(free_set) - , _used_before(free_set->used()) + , _used_before(free_set->used_not_holding_lock()) , _if_before(free_set->internal_fragmentation()) , _ef_before(free_set->external_fragmentation()) { } @@ -38,7 +38,6 @@ ShenandoahMetricsSnapshot::ShenandoahMetricsSnapshot(ShenandoahFreeSet* free_set bool ShenandoahMetricsSnapshot::is_good_progress() const { // Under the critical threshold? const size_t free_actual = _free_set->available(); - assert(free_actual != ShenandoahFreeSet::FreeSetUnderConstruction, "Avoid this race"); // ShenandoahCriticalFreeThreshold is expressed as a percentage. We multiply this percentage by 1/100th // of the soft max capacity to determine whether the available memory within the mutator partition of the @@ -52,7 +51,7 @@ bool ShenandoahMetricsSnapshot::is_good_progress() const { } // Freed up enough? - const size_t used_after = _free_set->used(); + const size_t used_after = _free_set->used_not_holding_lock(); const size_t progress_actual = (_used_before > used_after) ? _used_before - used_after : 0; const size_t progress_expected = ShenandoahHeapRegion::region_size_bytes(); const bool prog_used = progress_actual >= progress_expected; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahOldGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahOldGC.cpp index a44a831ef3d..ff441a0c868 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahOldGC.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahOldGC.cpp @@ -128,8 +128,6 @@ bool ShenandoahOldGC::collect(GCCause::Cause cause) { // the space. This would be the last action if there is nothing to evacuate. entry_cleanup_early(); - heap->free_set()->log_status_under_lock(); - assert(!heap->is_concurrent_strong_root_in_progress(), "No evacuations during old gc."); // We must execute this vm operation if we completed final mark. We cannot @@ -138,7 +136,10 @@ bool ShenandoahOldGC::collect(GCCause::Cause cause) { // collection. heap->concurrent_final_roots(); - size_t allocation_runway = heap->young_generation()->heuristics()->bytes_of_allocation_runway_before_gc_trigger(0); - heap->compute_old_generation_balance(allocation_runway, 0); + // After concurrent old marking finishes, we reclaim immediate garbage. Further, we may also want to expand OLD in order + // to make room for anticipated promotions and/or for mixed evacuations. Mixed evacuations are especially likely to + // follow the end of OLD marking. + heap->rebuild_free_set_within_phase(); + heap->free_set()->log_status_under_lock(); return true; } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.cpp b/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.cpp index c7cf013d034..d5e34d02b13 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.cpp @@ -63,7 +63,7 @@ private: uint _nworkers; ShenandoahHeapRegion** _coalesce_and_fill_region_array; uint _coalesce_and_fill_region_count; - volatile bool _is_preempted; + Atomic _is_preempted; public: ShenandoahConcurrentCoalesceAndFillTask(uint nworkers, @@ -88,7 +88,7 @@ public: if (!r->oop_coalesce_and_fill(true)) { // Coalesce and fill has been preempted - AtomicAccess::store(&_is_preempted, true); + _is_preempted.store_relaxed(true); return; } } @@ -96,7 +96,7 @@ public: // Value returned from is_completed() is only valid after all worker thread have terminated. bool is_completed() { - return !AtomicAccess::load(&_is_preempted); + return !_is_preempted.load_relaxed(); } }; @@ -147,23 +147,23 @@ void ShenandoahOldGeneration::augment_promoted_reserve(size_t increment) { void ShenandoahOldGeneration::reset_promoted_expended() { shenandoah_assert_heaplocked_or_safepoint(); - AtomicAccess::store(&_promoted_expended, static_cast(0)); - AtomicAccess::store(&_promotion_failure_count, static_cast(0)); - AtomicAccess::store(&_promotion_failure_words, static_cast(0)); + _promoted_expended.store_relaxed(0); + _promotion_failure_count.store_relaxed(0); + _promotion_failure_words.store_relaxed(0); } size_t ShenandoahOldGeneration::expend_promoted(size_t increment) { shenandoah_assert_heaplocked_or_safepoint(); assert(get_promoted_expended() + increment <= get_promoted_reserve(), "Do not expend more promotion than budgeted"); - return AtomicAccess::add(&_promoted_expended, increment); + return _promoted_expended.add_then_fetch(increment); } size_t ShenandoahOldGeneration::unexpend_promoted(size_t decrement) { - return AtomicAccess::sub(&_promoted_expended, decrement); + return _promoted_expended.sub_then_fetch(decrement); } size_t ShenandoahOldGeneration::get_promoted_expended() const { - return AtomicAccess::load(&_promoted_expended); + return _promoted_expended.load_relaxed(); } bool ShenandoahOldGeneration::can_allocate(const ShenandoahAllocRequest &req) const { @@ -412,9 +412,12 @@ void ShenandoahOldGeneration::prepare_regions_and_collection_set(bool concurrent ShenandoahGCPhase phase(concurrent ? ShenandoahPhaseTimings::final_rebuild_freeset : ShenandoahPhaseTimings::degen_gc_final_rebuild_freeset); + ShenandoahFreeSet* free_set = heap->free_set(); ShenandoahHeapLocker locker(heap->lock()); - size_t young_trash_regions, old_trash_regions; - size_t first_old, last_old, num_old; + + // This is completion of old-gen marking. We rebuild in order to reclaim immediate garbage and to + // prepare for subsequent mixed evacuations. + size_t young_trash_regions, old_trash_regions, first_old, last_old, num_old; heap->free_set()->prepare_to_rebuild(young_trash_regions, old_trash_regions, first_old, last_old, num_old); // At the end of old-gen, we may find that we have reclaimed immediate garbage, allowing a longer allocation runway. // We may also find that we have accumulated canddiate regions for mixed evacuation. If so, we will want to expand @@ -424,8 +427,7 @@ void ShenandoahOldGeneration::prepare_regions_and_collection_set(bool concurrent ShenandoahGenerationalHeap* gen_heap = ShenandoahGenerationalHeap::heap(); size_t allocation_runway = gen_heap->young_generation()->heuristics()->bytes_of_allocation_runway_before_gc_trigger(young_trash_regions); - gen_heap->compute_old_generation_balance(allocation_runway, old_trash_regions); - + gen_heap->compute_old_generation_balance(allocation_runway, old_trash_regions, young_trash_regions); heap->free_set()->finish_rebuild(young_trash_regions, old_trash_regions, num_old); } } @@ -580,8 +582,8 @@ void ShenandoahOldGeneration::handle_failed_evacuation() { } void ShenandoahOldGeneration::handle_failed_promotion(Thread* thread, size_t size) { - AtomicAccess::inc(&_promotion_failure_count); - AtomicAccess::add(&_promotion_failure_words, size); + _promotion_failure_count.add_then_fetch(1UL); + _promotion_failure_words.and_then_fetch(size); LogTarget(Debug, gc, plab) lt; LogStream ls(lt); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.hpp b/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.hpp index 90c1458ac97..5ebad461f3c 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.hpp @@ -64,10 +64,10 @@ private: // is therefore always accessed through atomic operations. This is increased when a // PLAB is allocated for promotions. The value is decreased by the amount of memory // remaining in a PLAB when it is retired. - size_t _promoted_expended; + Atomic _promoted_expended; - // Represents the quantity of live bytes we expect to promote during the next evacuation - // cycle. This value is used by the young heuristic to trigger mixed collections. + // Represents the quantity of live bytes we expect to promote during the next GC cycle, either by + // evacuation or by promote-in-place. This value is used by the young heuristic to trigger mixed collections. // It is also used when computing the optimum size for the old generation. size_t _promotion_potential; @@ -78,8 +78,8 @@ private: // Keep track of the number and size of promotions that failed. Perhaps we should use this to increase // the size of the old generation for the next collection cycle. - size_t _promotion_failure_count; - size_t _promotion_failure_words; + Atomic _promotion_failure_count; + Atomic _promotion_failure_words; // During construction of the collection set, we keep track of regions that are eligible // for promotion in place. These fields track the count of those humongous and regular regions. @@ -126,8 +126,8 @@ public: size_t get_promoted_expended() const; // Return the count and size (in words) of failed promotions since the last reset - size_t get_promotion_failed_count() const { return AtomicAccess::load(&_promotion_failure_count); } - size_t get_promotion_failed_words() const { return AtomicAccess::load(&_promotion_failure_words); } + size_t get_promotion_failed_count() const { return _promotion_failure_count.load_relaxed(); } + size_t get_promotion_failed_words() const { return _promotion_failure_words.load_relaxed(); } // Test if there is enough memory reserved for this promotion bool can_promote(size_t requested_bytes) const { diff --git a/src/hotspot/share/gc/shenandoah/shenandoahRegulatorThread.cpp b/src/hotspot/share/gc/shenandoah/shenandoahRegulatorThread.cpp index 964b6f0a10a..ec4b7c7217c 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahRegulatorThread.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahRegulatorThread.cpp @@ -149,6 +149,13 @@ bool ShenandoahRegulatorThread::start_global_cycle() const { bool ShenandoahRegulatorThread::request_concurrent_gc(ShenandoahGeneration* generation) const { double now = os::elapsedTime(); + + // This call may find the control thread waiting on workers which have suspended + // to allow a safepoint to run. If this regulator thread does not yield, the safepoint + // will not run. The worker threads won't progress, the control thread won't progress, + // and the regulator thread may never yield. Therefore, we leave the suspendible + // thread set before making this call. + SuspendibleThreadSetLeaver leaver; bool accepted = _control_thread->request_concurrent_gc(generation); if (LogTarget(Debug, gc, thread)::is_enabled() && accepted) { double wait_time = os::elapsedTime() - now; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.cpp b/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.cpp index a3c96a7d53b..05af25f13ad 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.cpp @@ -243,8 +243,7 @@ HeapWord* ShenandoahCardCluster::first_object_start(const size_t card_index, con #ifdef ASSERT assert(ShenandoahHeap::heap()->mode()->is_generational(), "Do not use in non-generational mode"); assert(region->is_old(), "Do not use for young regions"); - // For HumongousRegion:s it's more efficient to jump directly to the - // start region. + // For humongous regions it's more efficient to jump directly to the start region. assert(!region->is_humongous(), "Use region->humongous_start_region() instead"); #endif diff --git a/src/hotspot/share/gc/shenandoah/shenandoahVerifier.cpp b/src/hotspot/share/gc/shenandoah/shenandoahVerifier.cpp index 543df2422c0..0cc6d4c6ed4 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahVerifier.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahVerifier.cpp @@ -420,7 +420,14 @@ public: // span is the total memory affiliated with these stats (some of which is in use and other is available) size_t span() const { return _regions * ShenandoahHeapRegion::region_size_bytes(); } - size_t non_trashed_span() const { return (_regions - _trashed_regions) * ShenandoahHeapRegion::region_size_bytes(); } + size_t non_trashed_span() const { + assert(_regions >= _trashed_regions, "sanity"); + return (_regions - _trashed_regions) * ShenandoahHeapRegion::region_size_bytes(); + } + size_t non_trashed_committed() const { + assert(_committed >= _trashed_regions * ShenandoahHeapRegion::region_size_bytes(), "sanity"); + return _committed - (_trashed_regions * ShenandoahHeapRegion::region_size_bytes()); + } }; class ShenandoahGenerationStatsClosure : public ShenandoahHeapRegionClosure { diff --git a/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp b/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp index 15520c47bbc..3eb1a06a911 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp @@ -400,27 +400,20 @@ "reserve/waste is incorrect, at the risk that application " \ "runs out of memory too early.") \ \ - product(uintx, ShenandoahOldEvacRatioPercent, 75, EXPERIMENTAL, \ - "The maximum proportion of evacuation from old-gen memory, " \ - "expressed as a percentage. The default value 75 denotes that " \ - "no more than 75% of the collection set evacuation workload may " \ - "be towards evacuation of old-gen heap regions. This limits both "\ - "the promotion of aged regions and the compaction of existing " \ - "old regions. A value of 75 denotes that the total evacuation " \ - "work may increase to up to four times the young gen evacuation " \ - "work. A larger value allows quicker promotion and allows " \ - "a smaller number of mixed evacuations to process " \ - "the entire list of old-gen collection candidates at the cost " \ - "of an increased disruption of the normal cadence of young-gen " \ - "collections. A value of 100 allows a mixed evacuation to " \ - "focus entirely on old-gen memory, allowing no young-gen " \ - "regions to be collected, likely resulting in subsequent " \ - "allocation failures because the allocation pool is not " \ - "replenished. A value of 0 allows a mixed evacuation to " \ - "focus entirely on young-gen memory, allowing no old-gen " \ - "regions to be collected, likely resulting in subsequent " \ - "promotion failures and triggering of stop-the-world full GC " \ - "events.") \ + product(uintx, ShenandoahOldEvacPercent, 75, EXPERIMENTAL, \ + "The maximum evacuation to old-gen expressed as a percent of " \ + "the total live memory within the collection set. With the " \ + "default setting, if collection set evacuates X, no more than " \ + "75% of X may hold objects evacuated from old or promoted to " \ + "old from young. A value of 100 allows the entire collection " \ + "set to be comprised of old-gen regions and young regions that " \ + "have reached the tenure age. Larger values allow fewer mixed " \ + "evacuations to reclaim all the garbage from old. Smaller " \ + "values result in less variation in GC cycle times between " \ + "young vs. mixed cycles. A value of 0 prevents mixed " \ + "evacations from running and blocks promotion of aged regions " \ + "by evacuation. Setting the value to 0 does not prevent " \ + "regions from being promoted in place.") \ range(0,100) \ \ product(bool, ShenandoahEvacTracking, false, DIAGNOSTIC, \ @@ -430,18 +423,6 @@ "by thread type (worker or mutator) and evacuation type (young, " \ "old, or promotion.") \ \ - product(uintx, ShenandoahMinYoungPercentage, 20, EXPERIMENTAL, \ - "The minimum percentage of the heap to use for the young " \ - "generation. Heuristics will not adjust the young generation " \ - "to be less than this.") \ - range(0, 100) \ - \ - product(uintx, ShenandoahMaxYoungPercentage, 100, EXPERIMENTAL, \ - "The maximum percentage of the heap to use for the young " \ - "generation. Heuristics will not adjust the young generation " \ - "to be more than this.") \ - range(0, 100) \ - \ product(uintx, ShenandoahCriticalFreeThreshold, 1, EXPERIMENTAL, \ "How much of the heap needs to be free after recovery cycles, " \ "either Degenerated or Full GC to be claimed successful. If this "\ diff --git a/src/hotspot/share/gc/z/vmStructs_z.hpp b/src/hotspot/share/gc/z/vmStructs_z.hpp index a88dae188d3..de32b964a51 100644 --- a/src/hotspot/share/gc/z/vmStructs_z.hpp +++ b/src/hotspot/share/gc/z/vmStructs_z.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, 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 @@ -111,7 +111,6 @@ typedef ZValue ZPerNUMAZPartition; \ nonstatic_field(ZForwarding, _virtual, const ZVirtualMemory) \ nonstatic_field(ZForwarding, _object_alignment_shift, const size_t) \ - volatile_nonstatic_field(ZForwarding, _ref_count, int) \ nonstatic_field(ZForwarding, _entries, const ZAttachedArrayForForwarding) \ nonstatic_field(ZForwardingEntry, _entry, uint64_t) \ nonstatic_field(ZAttachedArrayForForwarding, _length, const size_t) diff --git a/src/hotspot/share/gc/z/zAbort.cpp b/src/hotspot/share/gc/z/zAbort.cpp index 3310793f730..82523ddebe8 100644 --- a/src/hotspot/share/gc/z/zAbort.cpp +++ b/src/hotspot/share/gc/z/zAbort.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, 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,10 +22,9 @@ */ #include "gc/z/zAbort.hpp" -#include "runtime/atomicAccess.hpp" -volatile bool ZAbort::_should_abort = false; +Atomic ZAbort::_should_abort{}; void ZAbort::abort() { - AtomicAccess::store(&_should_abort, true); + _should_abort.store_relaxed(true); } diff --git a/src/hotspot/share/gc/z/zAbort.hpp b/src/hotspot/share/gc/z/zAbort.hpp index 925b0a79ac3..64633752d5d 100644 --- a/src/hotspot/share/gc/z/zAbort.hpp +++ b/src/hotspot/share/gc/z/zAbort.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, 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,11 @@ #define SHARE_GC_Z_ZABORT_HPP #include "memory/allStatic.hpp" +#include "runtime/atomic.hpp" class ZAbort : public AllStatic { private: - static volatile bool _should_abort; + static Atomic _should_abort; public: static bool should_abort(); diff --git a/src/hotspot/share/gc/z/zAbort.inline.hpp b/src/hotspot/share/gc/z/zAbort.inline.hpp index 37503e25f70..856179e9d2a 100644 --- a/src/hotspot/share/gc/z/zAbort.inline.hpp +++ b/src/hotspot/share/gc/z/zAbort.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, 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,10 +26,8 @@ #include "gc/z/zAbort.hpp" -#include "runtime/atomicAccess.hpp" - inline bool ZAbort::should_abort() { - return AtomicAccess::load(&_should_abort); + return _should_abort.load_relaxed(); } #endif // SHARE_GC_Z_ZABORT_INLINE_HPP diff --git a/src/hotspot/share/gc/z/zAddress.inline.hpp b/src/hotspot/share/gc/z/zAddress.inline.hpp index c8c8ec7ae3a..0b99802729b 100644 --- a/src/hotspot/share/gc/z/zAddress.inline.hpp +++ b/src/hotspot/share/gc/z/zAddress.inline.hpp @@ -199,18 +199,18 @@ CREATE_ZOFFSET_OPERATORS(zoffset) inline uintptr_t untype(zbacking_offset offset) { const uintptr_t value = static_cast(offset); - assert(value < ZBackingOffsetMax, "Offset out of bounds (" PTR_FORMAT " < " PTR_FORMAT ")", value, ZAddressOffsetMax); + assert(value < ZBackingOffsetMax, "Offset out of bounds (" PTR_FORMAT " < " PTR_FORMAT ")", value, ZBackingOffsetMax); return value; } inline uintptr_t untype(zbacking_offset_end offset) { const uintptr_t value = static_cast(offset); - assert(value <= ZBackingOffsetMax, "Offset out of bounds (" PTR_FORMAT " <= " PTR_FORMAT ")", value, ZAddressOffsetMax); + assert(value <= ZBackingOffsetMax, "Offset out of bounds (" PTR_FORMAT " <= " PTR_FORMAT ")", value, ZBackingOffsetMax); return value; } inline zbacking_offset to_zbacking_offset(uintptr_t value) { - assert(value < ZBackingOffsetMax, "Value out of bounds (" PTR_FORMAT " < " PTR_FORMAT ")", value, ZAddressOffsetMax); + assert(value < ZBackingOffsetMax, "Value out of bounds (" PTR_FORMAT " < " PTR_FORMAT ")", value, ZBackingOffsetMax); return zbacking_offset(value); } @@ -227,7 +227,7 @@ inline zbacking_offset_end to_zbacking_offset_end(zbacking_offset start, size_t } inline zbacking_offset_end to_zbacking_offset_end(uintptr_t value) { - assert(value <= ZBackingOffsetMax, "Value out of bounds (" PTR_FORMAT " <= " PTR_FORMAT ")", value, ZAddressOffsetMax); + assert(value <= ZBackingOffsetMax, "Value out of bounds (" PTR_FORMAT " <= " PTR_FORMAT ")", value, ZBackingOffsetMax); return zbacking_offset_end(value); } diff --git a/src/hotspot/share/gc/z/zArray.hpp b/src/hotspot/share/gc/z/zArray.hpp index 2c2f8a5dbfb..d39def4096e 100644 --- a/src/hotspot/share/gc/z/zArray.hpp +++ b/src/hotspot/share/gc/z/zArray.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, 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,7 +26,7 @@ #include "cppstdlib/type_traits.hpp" #include "memory/allocation.hpp" -#include "runtime/atomicAccess.hpp" +#include "runtime/atomic.hpp" #include "runtime/os.hpp" #include "runtime/thread.hpp" #include "utilities/growableArray.hpp" @@ -78,7 +78,9 @@ public: template class ZArrayIteratorImpl : public StackObj { private: - size_t _next; + using NextType = std::conditional_t, size_t>; + + NextType _next; const size_t _end; const T* const _array; diff --git a/src/hotspot/share/gc/z/zArray.inline.hpp b/src/hotspot/share/gc/z/zArray.inline.hpp index 9e2bc19118e..15caa54ebab 100644 --- a/src/hotspot/share/gc/z/zArray.inline.hpp +++ b/src/hotspot/share/gc/z/zArray.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, 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,6 @@ #include "gc/z/zArray.hpp" #include "gc/z/zLock.inline.hpp" -#include "runtime/atomicAccess.hpp" template ZArraySlice::ZArraySlice(T* data, int len) @@ -130,7 +129,7 @@ inline bool ZArrayIteratorImpl::next_serial(size_t* index) { template inline bool ZArrayIteratorImpl::next_parallel(size_t* index) { - const size_t claimed_index = AtomicAccess::fetch_then_add(&_next, 1u, memory_order_relaxed); + const size_t claimed_index = _next.fetch_then_add(1u, memory_order_relaxed); if (claimed_index < _end) { *index = claimed_index; @@ -177,7 +176,7 @@ inline bool ZArrayIteratorImpl::next_if(T* elem, Function predicate template inline bool ZArrayIteratorImpl::next_index(size_t* index) { - if (Parallel) { + if constexpr (Parallel) { return next_parallel(index); } else { return next_serial(index); diff --git a/src/hotspot/share/gc/z/zForwarding.cpp b/src/hotspot/share/gc/z/zForwarding.cpp index 820bb9dbc35..92fc1bc89df 100644 --- a/src/hotspot/share/gc/z/zForwarding.cpp +++ b/src/hotspot/share/gc/z/zForwarding.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, 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 @@ -29,7 +29,6 @@ #include "gc/z/zStat.hpp" #include "gc/z/zUtils.inline.hpp" #include "logging/log.hpp" -#include "runtime/atomicAccess.hpp" #include "utilities/align.hpp" // @@ -50,7 +49,7 @@ // bool ZForwarding::claim() { - return AtomicAccess::cmpxchg(&_claimed, false, true) == false; + return _claimed.compare_set(false, true); } void ZForwarding::in_place_relocation_start(zoffset relocated_watermark) { @@ -60,7 +59,7 @@ void ZForwarding::in_place_relocation_start(zoffset relocated_watermark) { // Support for ZHeap::is_in checks of from-space objects // in a page that is in-place relocating - AtomicAccess::store(&_in_place_thread, Thread::current()); + _in_place_thread.store_relaxed(Thread::current()); _in_place_top_at_start = _page->top(); } @@ -76,17 +75,17 @@ void ZForwarding::in_place_relocation_finish() { } // Disable relaxed ZHeap::is_in checks - AtomicAccess::store(&_in_place_thread, (Thread*)nullptr); + _in_place_thread.store_relaxed(nullptr); } bool ZForwarding::in_place_relocation_is_below_top_at_start(zoffset offset) const { // Only the relocating thread is allowed to know about the old relocation top. - return AtomicAccess::load(&_in_place_thread) == Thread::current() && offset < _in_place_top_at_start; + return _in_place_thread.load_relaxed() == Thread::current() && offset < _in_place_top_at_start; } bool ZForwarding::retain_page(ZRelocateQueue* queue) { for (;;) { - const int32_t ref_count = AtomicAccess::load_acquire(&_ref_count); + const int32_t ref_count = _ref_count.load_acquire(); if (ref_count == 0) { // Released @@ -101,7 +100,7 @@ bool ZForwarding::retain_page(ZRelocateQueue* queue) { return false; } - if (AtomicAccess::cmpxchg(&_ref_count, ref_count, ref_count + 1) == ref_count) { + if (_ref_count.compare_set(ref_count, ref_count + 1)) { // Retained return true; } @@ -110,11 +109,11 @@ bool ZForwarding::retain_page(ZRelocateQueue* queue) { void ZForwarding::in_place_relocation_claim_page() { for (;;) { - const int32_t ref_count = AtomicAccess::load(&_ref_count); + const int32_t ref_count = _ref_count.load_relaxed(); assert(ref_count > 0, "Invalid state"); // Invert reference count - if (AtomicAccess::cmpxchg(&_ref_count, ref_count, -ref_count) != ref_count) { + if (!_ref_count.compare_set(ref_count, -ref_count)) { continue; } @@ -122,7 +121,7 @@ void ZForwarding::in_place_relocation_claim_page() { // and we have now claimed the page. Otherwise we wait until it is claimed. if (ref_count != 1) { ZLocker locker(&_ref_lock); - while (AtomicAccess::load_acquire(&_ref_count) != -1) { + while (_ref_count.load_acquire() != -1) { _ref_lock.wait(); } } @@ -134,12 +133,12 @@ void ZForwarding::in_place_relocation_claim_page() { void ZForwarding::release_page() { for (;;) { - const int32_t ref_count = AtomicAccess::load(&_ref_count); + const int32_t ref_count = _ref_count.load_relaxed(); assert(ref_count != 0, "Invalid state"); if (ref_count > 0) { // Decrement reference count - if (AtomicAccess::cmpxchg(&_ref_count, ref_count, ref_count - 1) != ref_count) { + if (!_ref_count.compare_set(ref_count, ref_count - 1)) { continue; } @@ -152,7 +151,7 @@ void ZForwarding::release_page() { } } else { // Increment reference count - if (AtomicAccess::cmpxchg(&_ref_count, ref_count, ref_count + 1) != ref_count) { + if (!_ref_count.compare_set(ref_count, ref_count + 1)) { continue; } @@ -171,9 +170,9 @@ void ZForwarding::release_page() { ZPage* ZForwarding::detach_page() { // Wait until released - if (AtomicAccess::load_acquire(&_ref_count) != 0) { + if (_ref_count.load_acquire() != 0) { ZLocker locker(&_ref_lock); - while (AtomicAccess::load_acquire(&_ref_count) != 0) { + while (_ref_count.load_acquire() != 0) { _ref_lock.wait(); } } @@ -182,16 +181,16 @@ ZPage* ZForwarding::detach_page() { } ZPage* ZForwarding::page() { - assert(AtomicAccess::load(&_ref_count) != 0, "The page has been released/detached"); + assert(_ref_count.load_relaxed() != 0, "The page has been released/detached"); return _page; } void ZForwarding::mark_done() { - AtomicAccess::store(&_done, true); + _done.store_relaxed(true); } bool ZForwarding::is_done() const { - return AtomicAccess::load(&_done); + return _done.load_relaxed(); } // @@ -288,7 +287,7 @@ void ZForwarding::relocated_remembered_fields_publish() { // used to have remembered set entries. Now publish the fields to // the YC. - const ZPublishState res = AtomicAccess::cmpxchg(&_relocated_remembered_fields_state, ZPublishState::none, ZPublishState::published); + const ZPublishState res = _relocated_remembered_fields_state.compare_exchange(ZPublishState::none, ZPublishState::published); // none: OK to publish // published: Not possible - this operation makes this transition @@ -319,7 +318,7 @@ void ZForwarding::relocated_remembered_fields_notify_concurrent_scan_of() { // Invariant: The page is being retained assert(ZGeneration::young()->is_phase_mark(), "Only called when"); - const ZPublishState res = AtomicAccess::cmpxchg(&_relocated_remembered_fields_state, ZPublishState::none, ZPublishState::reject); + const ZPublishState res = _relocated_remembered_fields_state.compare_exchange(ZPublishState::none, ZPublishState::reject); // none: OC has not completed relocation // published: OC has completed and published all relocated remembered fields @@ -340,7 +339,7 @@ void ZForwarding::relocated_remembered_fields_notify_concurrent_scan_of() { // OC relocation already collected and published fields // Still notify concurrent scanning and reject the collected data from the OC - const ZPublishState res2 = AtomicAccess::cmpxchg(&_relocated_remembered_fields_state, ZPublishState::published, ZPublishState::reject); + const ZPublishState res2 = _relocated_remembered_fields_state.compare_exchange(ZPublishState::published, ZPublishState::reject); assert(res2 == ZPublishState::published, "Should not fail"); log_debug(gc, remset)("Forwarding remset eager and reject: " PTR_FORMAT " " PTR_FORMAT, untype(start()), untype(end())); @@ -368,7 +367,7 @@ bool ZForwarding::relocated_remembered_fields_published_contains(volatile zpoint } void ZForwarding::verify() const { - guarantee(_ref_count != 0, "Invalid reference count"); + guarantee(_ref_count.load_relaxed() != 0, "Invalid reference count"); guarantee(_page != nullptr, "Invalid page"); uint32_t live_objects = 0; diff --git a/src/hotspot/share/gc/z/zForwarding.hpp b/src/hotspot/share/gc/z/zForwarding.hpp index 29b5cf4aabe..72319bd2ebb 100644 --- a/src/hotspot/share/gc/z/zForwarding.hpp +++ b/src/hotspot/share/gc/z/zForwarding.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, 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 @@ -32,6 +32,7 @@ #include "gc/z/zPageAge.hpp" #include "gc/z/zPageType.hpp" #include "gc/z/zVirtualMemory.hpp" +#include "runtime/atomic.hpp" class ObjectClosure; class ZForwardingAllocator; @@ -62,13 +63,13 @@ private: const uint32_t _partition_id; const ZPageAge _from_age; const ZPageAge _to_age; - volatile bool _claimed; + Atomic _claimed; mutable ZConditionLock _ref_lock; - volatile int32_t _ref_count; - volatile bool _done; + Atomic _ref_count; + Atomic _done; // Relocated remembered set fields support - volatile ZPublishState _relocated_remembered_fields_state; + Atomic _relocated_remembered_fields_state; PointerArray _relocated_remembered_fields_array; uint32_t _relocated_remembered_fields_publish_young_seqnum; @@ -77,7 +78,7 @@ private: zoffset_end _in_place_top_at_start; // Debugging - volatile Thread* _in_place_thread; + Atomic _in_place_thread; ZForwardingEntry* entries() const; ZForwardingEntry at(ZForwardingCursor* cursor) const; diff --git a/src/hotspot/share/gc/z/zForwarding.inline.hpp b/src/hotspot/share/gc/z/zForwarding.inline.hpp index 45b5d495e79..02f61eb5b05 100644 --- a/src/hotspot/share/gc/z/zForwarding.inline.hpp +++ b/src/hotspot/share/gc/z/zForwarding.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, 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 @@ -196,7 +196,7 @@ void ZForwarding::oops_do_in_forwarded_via_table(Function function) { } inline bool ZForwarding::in_place_relocation() const { - assert(AtomicAccess::load(&_ref_count) != 0, "The page has been released/detached"); + assert(_ref_count.load_relaxed() != 0, "The page has been released/detached"); return _in_place; } @@ -307,7 +307,7 @@ inline void ZForwarding::relocated_remembered_fields_register(volatile zpointer* // Invariant: Page is being retained assert(ZGeneration::young()->is_phase_mark(), "Only called when"); - const ZPublishState res = AtomicAccess::load(&_relocated_remembered_fields_state); + const ZPublishState res = _relocated_remembered_fields_state.load_relaxed(); // none: Gather remembered fields // published: Have already published fields - not possible since they haven't been @@ -327,7 +327,7 @@ inline void ZForwarding::relocated_remembered_fields_register(volatile zpointer* // Returns true iff the page is being (or about to be) relocated by the OC // while the YC gathered the remembered fields of the "from" page. inline bool ZForwarding::relocated_remembered_fields_is_concurrently_scanned() const { - return AtomicAccess::load(&_relocated_remembered_fields_state) == ZPublishState::reject; + return _relocated_remembered_fields_state.load_relaxed() == ZPublishState::reject; } template @@ -335,7 +335,7 @@ inline void ZForwarding::relocated_remembered_fields_apply_to_published(Function // Invariant: Page is not being retained assert(ZGeneration::young()->is_phase_mark(), "Only called when"); - const ZPublishState res = AtomicAccess::load_acquire(&_relocated_remembered_fields_state); + const ZPublishState res = _relocated_remembered_fields_state.load_acquire(); // none: Nothing published - page had already been relocated before YC started // published: OC relocated and published relocated remembered fields @@ -363,14 +363,14 @@ inline void ZForwarding::relocated_remembered_fields_apply_to_published(Function // collection. Mark that it is unsafe (and unnecessary) to call scan_page // on the page in the page table. assert(res != ZPublishState::accept, "Unexpected"); - AtomicAccess::store(&_relocated_remembered_fields_state, ZPublishState::reject); + _relocated_remembered_fields_state.store_relaxed(ZPublishState::reject); } else { log_debug(gc, remset)("scan_forwarding failed retain safe " PTR_FORMAT, untype(start())); // Guaranteed that the page was fully relocated and removed from page table. // Because of this we can signal to scan_page that any page found in page table // of the same slot as the current forwarding is a page that is safe to scan, // and in fact must be scanned. - AtomicAccess::store(&_relocated_remembered_fields_state, ZPublishState::accept); + _relocated_remembered_fields_state.store_relaxed(ZPublishState::accept); } } diff --git a/src/hotspot/share/gc/z/zForwardingAllocator.cpp b/src/hotspot/share/gc/z/zForwardingAllocator.cpp index 0b4e6c7b08c..451a1d62754 100644 --- a/src/hotspot/share/gc/z/zForwardingAllocator.cpp +++ b/src/hotspot/share/gc/z/zForwardingAllocator.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, 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,6 +34,7 @@ ZForwardingAllocator::~ZForwardingAllocator() { } void ZForwardingAllocator::reset(size_t size) { - _start = _top = REALLOC_C_HEAP_ARRAY(char, _start, size, mtGC); + _start = REALLOC_C_HEAP_ARRAY(char, _start, size, mtGC); + _top.store_relaxed(_start); _end = _start + size; } diff --git a/src/hotspot/share/gc/z/zForwardingAllocator.hpp b/src/hotspot/share/gc/z/zForwardingAllocator.hpp index 8424fe6cd70..62d84885692 100644 --- a/src/hotspot/share/gc/z/zForwardingAllocator.hpp +++ b/src/hotspot/share/gc/z/zForwardingAllocator.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, 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,13 +24,14 @@ #ifndef SHARE_GC_Z_ZFORWARDINGALLOCATOR_HPP #define SHARE_GC_Z_ZFORWARDINGALLOCATOR_HPP +#include "runtime/atomic.hpp" #include "utilities/globalDefinitions.hpp" class ZForwardingAllocator { private: - char* _start; - char* _end; - char* _top; + char* _start; + char* _end; + Atomic _top; public: ZForwardingAllocator(); diff --git a/src/hotspot/share/gc/z/zForwardingAllocator.inline.hpp b/src/hotspot/share/gc/z/zForwardingAllocator.inline.hpp index dc71507175c..c8811c84ede 100644 --- a/src/hotspot/share/gc/z/zForwardingAllocator.inline.hpp +++ b/src/hotspot/share/gc/z/zForwardingAllocator.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, 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,7 +26,6 @@ #include "gc/z/zForwardingAllocator.hpp" -#include "runtime/atomicAccess.hpp" #include "utilities/debug.hpp" inline size_t ZForwardingAllocator::size() const { @@ -34,11 +33,11 @@ inline size_t ZForwardingAllocator::size() const { } inline bool ZForwardingAllocator::is_full() const { - return _top == _end; + return _top.load_relaxed() == _end; } inline void* ZForwardingAllocator::alloc(size_t size) { - char* const addr = AtomicAccess::fetch_then_add(&_top, size); + char* const addr = _top.fetch_then_add(size); assert(addr + size <= _end, "Allocation should never fail"); return addr; } diff --git a/src/hotspot/share/gc/z/zGeneration.cpp b/src/hotspot/share/gc/z/zGeneration.cpp index a8c646bf895..27f352a624f 100644 --- a/src/hotspot/share/gc/z/zGeneration.cpp +++ b/src/hotspot/share/gc/z/zGeneration.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, 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 @@ -56,7 +56,6 @@ #include "logging/log.hpp" #include "memory/universe.hpp" #include "prims/jvmtiTagMap.hpp" -#include "runtime/atomicAccess.hpp" #include "runtime/continuation.hpp" #include "runtime/handshake.hpp" #include "runtime/safepoint.hpp" @@ -298,33 +297,33 @@ bool ZGeneration::is_relocate_queue_active() const { void ZGeneration::reset_statistics() { assert(SafepointSynchronize::is_at_safepoint(), "Should be at safepoint"); - _freed = 0; - _promoted = 0; - _compacted = 0; + _freed.store_relaxed(0u); + _promoted.store_relaxed(0u); + _compacted.store_relaxed(0u); } size_t ZGeneration::freed() const { - return _freed; + return _freed.load_relaxed(); } void ZGeneration::increase_freed(size_t size) { - AtomicAccess::add(&_freed, size, memory_order_relaxed); + _freed.add_then_fetch(size, memory_order_relaxed); } size_t ZGeneration::promoted() const { - return _promoted; + return _promoted.load_relaxed();; } void ZGeneration::increase_promoted(size_t size) { - AtomicAccess::add(&_promoted, size, memory_order_relaxed); + _promoted.add_then_fetch(size, memory_order_relaxed); } size_t ZGeneration::compacted() const { - return _compacted; + return _compacted.load_relaxed();; } void ZGeneration::increase_compacted(size_t size) { - AtomicAccess::add(&_compacted, size, memory_order_relaxed); + _compacted.add_then_fetch(size, memory_order_relaxed); } ConcurrentGCTimer* ZGeneration::gc_timer() const { diff --git a/src/hotspot/share/gc/z/zGeneration.hpp b/src/hotspot/share/gc/z/zGeneration.hpp index 3b700361460..1371865958b 100644 --- a/src/hotspot/share/gc/z/zGeneration.hpp +++ b/src/hotspot/share/gc/z/zGeneration.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, 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,6 +37,7 @@ #include "gc/z/zWeakRootsProcessor.hpp" #include "gc/z/zWorkers.hpp" #include "memory/allocation.hpp" +#include "runtime/atomic.hpp" class ThreadClosure; class ZForwardingTable; @@ -70,9 +71,9 @@ protected: ZRelocate _relocate; ZRelocationSet _relocation_set; - volatile size_t _freed; - volatile size_t _promoted; - volatile size_t _compacted; + Atomic _freed; + Atomic _promoted; + Atomic _compacted; Phase _phase; uint32_t _seqnum; diff --git a/src/hotspot/share/gc/z/zHeapIterator.cpp b/src/hotspot/share/gc/z/zHeapIterator.cpp index d6289178ea8..73ced211058 100644 --- a/src/hotspot/share/gc/z/zHeapIterator.cpp +++ b/src/hotspot/share/gc/z/zHeapIterator.cpp @@ -456,7 +456,7 @@ void ZHeapIterator::follow_array_chunk(const ZHeapIteratorContext& context, cons // Follow array chunk ZHeapIteratorOopClosure cl(this, context, obj); - ZIterator::oop_iterate_range(obj, &cl, start, end); + ZIterator::oop_iterate_elements_range(obj, &cl, start, end); } template diff --git a/src/hotspot/share/gc/z/zIterator.hpp b/src/hotspot/share/gc/z/zIterator.hpp index 3a1de049dd0..e048002e52e 100644 --- a/src/hotspot/share/gc/z/zIterator.hpp +++ b/src/hotspot/share/gc/z/zIterator.hpp @@ -41,7 +41,7 @@ public: static void oop_iterate(oop obj, OopClosureT* cl); template - static void oop_iterate_range(objArrayOop obj, OopClosureT* cl, int start, int end); + static void oop_iterate_elements_range(objArrayOop obj, OopClosureT* cl, int start, int end); // This function skips invisible roots template diff --git a/src/hotspot/share/gc/z/zIterator.inline.hpp b/src/hotspot/share/gc/z/zIterator.inline.hpp index fb20a424288..cbfe1a79aaf 100644 --- a/src/hotspot/share/gc/z/zIterator.inline.hpp +++ b/src/hotspot/share/gc/z/zIterator.inline.hpp @@ -66,9 +66,9 @@ void ZIterator::oop_iterate(oop obj, OopClosureT* cl) { } template -void ZIterator::oop_iterate_range(objArrayOop obj, OopClosureT* cl, int start, int end) { +void ZIterator::oop_iterate_elements_range(objArrayOop obj, OopClosureT* cl, int start, int end) { assert(!is_invisible_object_array(obj), "not safe"); - obj->oop_iterate_range(cl, start, end); + obj->oop_iterate_elements_range(cl, start, end); } template diff --git a/src/hotspot/share/gc/z/zJNICritical.cpp b/src/hotspot/share/gc/z/zJNICritical.cpp index 28cc70cb3b5..f50cc113936 100644 --- a/src/hotspot/share/gc/z/zJNICritical.cpp +++ b/src/hotspot/share/gc/z/zJNICritical.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, 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,7 +24,6 @@ #include "gc/z/zJNICritical.hpp" #include "gc/z/zLock.inline.hpp" #include "gc/z/zStat.hpp" -#include "runtime/atomicAccess.hpp" #include "runtime/interfaceSupport.inline.hpp" #include "runtime/thread.inline.hpp" #include "utilities/debug.hpp" @@ -46,22 +45,22 @@ static const ZStatCriticalPhase ZCriticalPhaseJNICriticalStall("JNI Critical Stall", false /* verbose */); -volatile int64_t ZJNICritical::_count; -ZConditionLock* ZJNICritical::_lock; +Atomic ZJNICritical::_count; +ZConditionLock* ZJNICritical::_lock; void ZJNICritical::initialize() { - _count = 0; + precond(_count.load_relaxed() == 0); _lock = new ZConditionLock(); } void ZJNICritical::block() { for (;;) { - const int64_t count = AtomicAccess::load_acquire(&_count); + const int64_t count = _count.load_acquire(); if (count < 0) { // Already blocked, wait until unblocked ZLocker locker(_lock); - while (AtomicAccess::load_acquire(&_count) < 0) { + while (_count.load_acquire() < 0) { _lock->wait(); } @@ -70,7 +69,7 @@ void ZJNICritical::block() { } // Increment and invert count - if (AtomicAccess::cmpxchg(&_count, count, -(count + 1)) != count) { + if (!_count.compare_set(count, -(count + 1))) { continue; } @@ -80,7 +79,7 @@ void ZJNICritical::block() { if (count != 0) { // Wait until blocked ZLocker locker(_lock); - while (AtomicAccess::load_acquire(&_count) != -1) { + while (_count.load_acquire() != -1) { _lock->wait(); } } @@ -91,18 +90,18 @@ void ZJNICritical::block() { } void ZJNICritical::unblock() { - const int64_t count = AtomicAccess::load_acquire(&_count); + const int64_t count = _count.load_acquire(); assert(count == -1, "Invalid count"); // Notify unblocked ZLocker locker(_lock); - AtomicAccess::release_store(&_count, (int64_t)0); + _count.release_store(0); _lock->notify_all(); } void ZJNICritical::enter_inner(JavaThread* thread) { for (;;) { - const int64_t count = AtomicAccess::load_acquire(&_count); + const int64_t count = _count.load_acquire(); if (count < 0) { // Wait until unblocked @@ -112,7 +111,7 @@ void ZJNICritical::enter_inner(JavaThread* thread) { ThreadBlockInVM tbivm(thread); ZLocker locker(_lock); - while (AtomicAccess::load_acquire(&_count) < 0) { + while (_count.load_acquire() < 0) { _lock->wait(); } @@ -121,7 +120,7 @@ void ZJNICritical::enter_inner(JavaThread* thread) { } // Increment count - if (AtomicAccess::cmpxchg(&_count, count, count + 1) != count) { + if (!_count.compare_set(count, count + 1)) { continue; } @@ -142,17 +141,17 @@ void ZJNICritical::enter(JavaThread* thread) { void ZJNICritical::exit_inner() { for (;;) { - const int64_t count = AtomicAccess::load_acquire(&_count); + const int64_t count = _count.load_acquire(); assert(count != 0, "Invalid count"); if (count > 0) { // No block in progress, decrement count - if (AtomicAccess::cmpxchg(&_count, count, count - 1) != count) { + if (!_count.compare_set(count, count - 1)) { continue; } } else { // Block in progress, increment count - if (AtomicAccess::cmpxchg(&_count, count, count + 1) != count) { + if (!_count.compare_set(count, count + 1)) { continue; } diff --git a/src/hotspot/share/gc/z/zJNICritical.hpp b/src/hotspot/share/gc/z/zJNICritical.hpp index d2ba80eddba..def6001ae9f 100644 --- a/src/hotspot/share/gc/z/zJNICritical.hpp +++ b/src/hotspot/share/gc/z/zJNICritical.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, 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,14 +25,15 @@ #define SHARE_GC_Z_ZJNICRITICAL_HPP #include "memory/allocation.hpp" +#include "runtime/atomic.hpp" class JavaThread; class ZConditionLock; class ZJNICritical : public AllStatic { private: - static volatile int64_t _count; - static ZConditionLock* _lock; + static Atomic _count; + static ZConditionLock* _lock; static void enter_inner(JavaThread* thread); static void exit_inner(); diff --git a/src/hotspot/share/gc/z/zLiveMap.cpp b/src/hotspot/share/gc/z/zLiveMap.cpp index c4ae41b0873..8b03a97e03f 100644 --- a/src/hotspot/share/gc/z/zLiveMap.cpp +++ b/src/hotspot/share/gc/z/zLiveMap.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, 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,6 @@ #include "gc/z/zStat.hpp" #include "gc/z/zUtils.hpp" #include "logging/log.hpp" -#include "runtime/atomicAccess.hpp" #include "utilities/debug.hpp" #include "utilities/powerOfTwo.hpp" #include "utilities/spinYield.hpp" @@ -60,18 +59,18 @@ void ZLiveMap::reset(ZGenerationId id) { // Multiple threads can enter here, make sure only one of them // resets the marking information while the others busy wait. - for (uint32_t seqnum = AtomicAccess::load_acquire(&_seqnum); + for (uint32_t seqnum = _seqnum.load_acquire(); seqnum != generation->seqnum(); - seqnum = AtomicAccess::load_acquire(&_seqnum)) { + seqnum = _seqnum.load_acquire()) { if (seqnum != seqnum_initializing) { // No one has claimed initialization of the livemap yet - if (AtomicAccess::cmpxchg(&_seqnum, seqnum, seqnum_initializing) == seqnum) { + if (_seqnum.compare_set(seqnum, seqnum_initializing)) { // This thread claimed the initialization // Reset marking information - _live_bytes = 0; - _live_objects = 0; + _live_bytes.store_relaxed(0u); + _live_objects.store_relaxed(0u); // Clear segment claimed/live bits segment_live_bits().clear(); @@ -81,13 +80,13 @@ void ZLiveMap::reset(ZGenerationId id) { // a bit is about to be set for the first time. initialize_bitmap(); - assert(_seqnum == seqnum_initializing, "Invalid"); + assert(_seqnum.load_relaxed() == seqnum_initializing, "Invalid"); // Make sure the newly reset marking information is ordered // before the update of the page seqnum, such that when the // up-to-date seqnum is load acquired, the bit maps will not // contain stale information. - AtomicAccess::release_store(&_seqnum, generation->seqnum()); + _seqnum.release_store(generation->seqnum()); break; } } diff --git a/src/hotspot/share/gc/z/zLiveMap.hpp b/src/hotspot/share/gc/z/zLiveMap.hpp index 71457d05a41..9373f6aad98 100644 --- a/src/hotspot/share/gc/z/zLiveMap.hpp +++ b/src/hotspot/share/gc/z/zLiveMap.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, 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,6 +28,7 @@ #include "gc/z/zBitMap.hpp" #include "gc/z/zGenerationId.hpp" #include "memory/allocation.hpp" +#include "runtime/atomic.hpp" class ObjectClosure; @@ -41,9 +42,9 @@ private: const uint32_t _segment_size; const int _segment_shift; - volatile uint32_t _seqnum; - volatile uint32_t _live_objects; - volatile size_t _live_bytes; + Atomic _seqnum; + Atomic _live_objects; + Atomic _live_bytes; BitMap::bm_word_t _segment_live_bits; BitMap::bm_word_t _segment_claim_bits; ZBitMap _bitmap; diff --git a/src/hotspot/share/gc/z/zLiveMap.inline.hpp b/src/hotspot/share/gc/z/zLiveMap.inline.hpp index e6176b928ff..69687d0b595 100644 --- a/src/hotspot/share/gc/z/zLiveMap.inline.hpp +++ b/src/hotspot/share/gc/z/zLiveMap.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, 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,24 +31,23 @@ #include "gc/z/zGeneration.inline.hpp" #include "gc/z/zMark.hpp" #include "gc/z/zUtils.inline.hpp" -#include "runtime/atomicAccess.hpp" #include "utilities/bitMap.inline.hpp" #include "utilities/debug.hpp" inline void ZLiveMap::reset() { - _seqnum = 0; + _seqnum.store_relaxed(0u); } inline bool ZLiveMap::is_marked(ZGenerationId id) const { - return AtomicAccess::load_acquire(&_seqnum) == ZGeneration::generation(id)->seqnum(); + return _seqnum.load_acquire() == ZGeneration::generation(id)->seqnum(); } inline uint32_t ZLiveMap::live_objects() const { - return _live_objects; + return _live_objects.load_relaxed(); } inline size_t ZLiveMap::live_bytes() const { - return _live_bytes; + return _live_bytes.load_relaxed(); } inline const BitMapView ZLiveMap::segment_live_bits() const { @@ -116,8 +115,8 @@ inline bool ZLiveMap::set(ZGenerationId id, BitMap::idx_t index, bool finalizabl } inline void ZLiveMap::inc_live(uint32_t objects, size_t bytes) { - AtomicAccess::add(&_live_objects, objects); - AtomicAccess::add(&_live_bytes, bytes); + _live_objects.add_then_fetch(objects); + _live_bytes.add_then_fetch(bytes); } inline BitMap::idx_t ZLiveMap::segment_start(BitMap::idx_t segment) const { diff --git a/src/hotspot/share/gc/z/zLock.hpp b/src/hotspot/share/gc/z/zLock.hpp index 640a9fb02d3..a843f2389d7 100644 --- a/src/hotspot/share/gc/z/zLock.hpp +++ b/src/hotspot/share/gc/z/zLock.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, 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,6 +25,7 @@ #define SHARE_GC_Z_ZLOCK_HPP #include "memory/allocation.hpp" +#include "runtime/atomic.hpp" #include "runtime/mutex.hpp" class ZLock : public CHeapObj { @@ -39,9 +40,9 @@ public: class ZReentrantLock { private: - ZLock _lock; - Thread* volatile _owner; - uint64_t _count; + ZLock _lock; + Atomic _owner; + uint64_t _count; public: ZReentrantLock(); diff --git a/src/hotspot/share/gc/z/zLock.inline.hpp b/src/hotspot/share/gc/z/zLock.inline.hpp index edf59be5b4c..ba5d6692521 100644 --- a/src/hotspot/share/gc/z/zLock.inline.hpp +++ b/src/hotspot/share/gc/z/zLock.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, 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,7 +26,6 @@ #include "gc/z/zLock.hpp" -#include "runtime/atomicAccess.hpp" #include "runtime/javaThread.hpp" #include "runtime/os.inline.hpp" #include "utilities/debug.hpp" @@ -50,11 +49,11 @@ inline ZReentrantLock::ZReentrantLock() inline void ZReentrantLock::lock() { Thread* const thread = Thread::current(); - Thread* const owner = AtomicAccess::load(&_owner); + Thread* const owner = _owner.load_relaxed(); if (owner != thread) { _lock.lock(); - AtomicAccess::store(&_owner, thread); + _owner.store_relaxed(thread); } _count++; @@ -67,14 +66,14 @@ inline void ZReentrantLock::unlock() { _count--; if (_count == 0) { - AtomicAccess::store(&_owner, (Thread*)nullptr); + _owner.store_relaxed(nullptr); _lock.unlock(); } } inline bool ZReentrantLock::is_owned() const { Thread* const thread = Thread::current(); - Thread* const owner = AtomicAccess::load(&_owner); + Thread* const owner = _owner.load_relaxed(); return owner == thread; } diff --git a/src/hotspot/share/gc/z/zMark.cpp b/src/hotspot/share/gc/z/zMark.cpp index 3b247fdd35e..03701ae9998 100644 --- a/src/hotspot/share/gc/z/zMark.cpp +++ b/src/hotspot/share/gc/z/zMark.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, 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 @@ -57,7 +57,6 @@ #include "memory/iterator.inline.hpp" #include "oops/objArrayOop.inline.hpp" #include "oops/oop.inline.hpp" -#include "runtime/atomicAccess.hpp" #include "runtime/continuation.hpp" #include "runtime/handshake.hpp" #include "runtime/javaThread.hpp" @@ -152,13 +151,14 @@ void ZMark::prepare_work() { _terminate.reset(_nworkers); // Reset flush counters - _work_nproactiveflush = _work_nterminateflush = 0; + _work_nproactiveflush.store_relaxed(0u); + _work_nterminateflush.store_relaxed(0u); } void ZMark::finish_work() { // Accumulate proactive/terminate flush counters - _nproactiveflush += _work_nproactiveflush; - _nterminateflush += _work_nterminateflush; + _nproactiveflush += _work_nproactiveflush.load_relaxed(); + _nterminateflush += _work_nterminateflush.load_relaxed(); } void ZMark::follow_work_complete() { @@ -594,7 +594,7 @@ bool ZMark::flush() { } bool ZMark::try_terminate_flush() { - AtomicAccess::inc(&_work_nterminateflush); + _work_nterminateflush.add_then_fetch(1u); _terminate.set_resurrected(false); if (ZVerifyMarking) { @@ -610,12 +610,12 @@ bool ZMark::try_proactive_flush() { return false; } - if (AtomicAccess::load(&_work_nproactiveflush) == ZMarkProactiveFlushMax) { + if (_work_nproactiveflush.load_relaxed() == ZMarkProactiveFlushMax) { // Limit reached or we're trying to terminate return false; } - AtomicAccess::inc(&_work_nproactiveflush); + _work_nproactiveflush.add_then_fetch(1u); SuspendibleThreadSetLeaver sts_leaver; return flush(); diff --git a/src/hotspot/share/gc/z/zMark.hpp b/src/hotspot/share/gc/z/zMark.hpp index cc398addeb8..72a527b957d 100644 --- a/src/hotspot/share/gc/z/zMark.hpp +++ b/src/hotspot/share/gc/z/zMark.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, 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,6 +30,7 @@ #include "gc/z/zMarkStackEntry.hpp" #include "gc/z/zMarkTerminate.hpp" #include "oops/oopsHierarchy.hpp" +#include "runtime/atomic.hpp" #include "utilities/globalDefinitions.hpp" class Thread; @@ -60,8 +61,8 @@ private: ZMarkingSMR _marking_smr; ZMarkStripeSet _stripes; ZMarkTerminate _terminate; - volatile size_t _work_nproactiveflush; - volatile size_t _work_nterminateflush; + Atomic _work_nproactiveflush; + Atomic _work_nterminateflush; size_t _nproactiveflush; size_t _nterminateflush; size_t _ntrycomplete; diff --git a/src/hotspot/share/gc/z/zMarkTerminate.hpp b/src/hotspot/share/gc/z/zMarkTerminate.hpp index cff1f8e73fa..9def3e72120 100644 --- a/src/hotspot/share/gc/z/zMarkTerminate.hpp +++ b/src/hotspot/share/gc/z/zMarkTerminate.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, 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,6 +25,7 @@ #define SHARE_GC_Z_ZMARKTERMINATE_HPP #include "gc/z/zLock.hpp" +#include "runtime/atomic.hpp" #include "utilities/globalDefinitions.hpp" class ZMarkStripeSet; @@ -32,9 +33,9 @@ class ZMarkStripeSet; class ZMarkTerminate { private: uint _nworkers; - volatile uint _nworking; - volatile uint _nawakening; - volatile bool _resurrected; + Atomic _nworking; + Atomic _nawakening; + Atomic _resurrected; ZConditionLock _lock; void maybe_reduce_stripes(ZMarkStripeSet* stripes, size_t used_nstripes); diff --git a/src/hotspot/share/gc/z/zMarkTerminate.inline.hpp b/src/hotspot/share/gc/z/zMarkTerminate.inline.hpp index 575044e3a39..84a545623bc 100644 --- a/src/hotspot/share/gc/z/zMarkTerminate.inline.hpp +++ b/src/hotspot/share/gc/z/zMarkTerminate.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, 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,7 +30,6 @@ #include "gc/z/zLock.inline.hpp" #include "gc/z/zMarkStack.hpp" #include "logging/log.hpp" -#include "runtime/atomicAccess.hpp" #include "runtime/osThread.hpp" #include "runtime/thread.inline.hpp" @@ -42,24 +41,23 @@ inline ZMarkTerminate::ZMarkTerminate() _lock() {} inline void ZMarkTerminate::reset(uint nworkers) { - AtomicAccess::store(&_nworkers, nworkers); - AtomicAccess::store(&_nworking, nworkers); - _nawakening = 0; + _nworkers = nworkers; + _nworking.store_relaxed(nworkers); + _nawakening.store_relaxed(0u); } inline void ZMarkTerminate::leave() { SuspendibleThreadSetLeaver sts_leaver; ZLocker locker(&_lock); - AtomicAccess::store(&_nworking, _nworking - 1); - if (_nworking == 0) { + if (_nworking.sub_then_fetch(1u, memory_order_relaxed) == 0) { // Last thread leaving; notify waiters _lock.notify_all(); } } inline void ZMarkTerminate::maybe_reduce_stripes(ZMarkStripeSet* stripes, size_t used_nstripes) { - size_t nstripes = stripes->nstripes(); + const size_t nstripes = stripes->nstripes(); if (used_nstripes == nstripes && nstripes > 1u) { stripes->try_set_nstripes(nstripes, nstripes >> 1); } @@ -69,8 +67,7 @@ inline bool ZMarkTerminate::try_terminate(ZMarkStripeSet* stripes, size_t used_n SuspendibleThreadSetLeaver sts_leaver; ZLocker locker(&_lock); - AtomicAccess::store(&_nworking, _nworking - 1); - if (_nworking == 0) { + if (_nworking.sub_then_fetch(1u, memory_order_relaxed) == 0) { // Last thread entering termination: success _lock.notify_all(); return true; @@ -83,24 +80,24 @@ inline bool ZMarkTerminate::try_terminate(ZMarkStripeSet* stripes, size_t used_n // We either got notification about more work // or got a spurious wakeup; don't terminate - if (_nawakening > 0) { - AtomicAccess::store(&_nawakening, _nawakening - 1); + if (_nawakening.load_relaxed() > 0) { + _nawakening.sub_then_fetch(1u, memory_order_relaxed); } - if (_nworking == 0) { + if (_nworking.load_relaxed() == 0) { // We got notified all work is done; terminate return true; } - AtomicAccess::store(&_nworking, _nworking + 1); + _nworking.add_then_fetch(1u, memory_order_relaxed); return false; } inline void ZMarkTerminate::wake_up() { - uint nworking = AtomicAccess::load(&_nworking); - uint nawakening = AtomicAccess::load(&_nawakening); - if (nworking + nawakening == AtomicAccess::load(&_nworkers)) { + const uint nworking = _nworking.load_relaxed(); + const uint nawakening = _nawakening.load_relaxed(); + if (nworking + nawakening == _nworkers) { // Everyone is working or about to return; } @@ -111,24 +108,24 @@ inline void ZMarkTerminate::wake_up() { } ZLocker locker(&_lock); - if (_nworking + _nawakening != _nworkers) { + if (_nworking.load_relaxed() + _nawakening.load_relaxed() != _nworkers) { // Everyone is not working - AtomicAccess::store(&_nawakening, _nawakening + 1); + _nawakening.add_then_fetch(1u, memory_order_relaxed); _lock.notify(); } } inline bool ZMarkTerminate::saturated() const { - uint nworking = AtomicAccess::load(&_nworking); - uint nawakening = AtomicAccess::load(&_nawakening); + const uint nworking = _nworking.load_relaxed(); + const uint nawakening = _nawakening.load_relaxed(); - return nworking + nawakening == AtomicAccess::load(&_nworkers); + return nworking + nawakening == _nworkers; } inline void ZMarkTerminate::set_resurrected(bool value) { // Update resurrected if it changed if (resurrected() != value) { - AtomicAccess::store(&_resurrected, value); + _resurrected.store_relaxed(value); if (value) { log_debug(gc, marking)("Resurrection broke termination"); } else { @@ -138,7 +135,7 @@ inline void ZMarkTerminate::set_resurrected(bool value) { } inline bool ZMarkTerminate::resurrected() const { - return AtomicAccess::load(&_resurrected); + return _resurrected.load_relaxed(); } #endif // SHARE_GC_Z_ZMARKTERMINATE_INLINE_HPP diff --git a/src/hotspot/share/gc/z/zNMethodTableIteration.cpp b/src/hotspot/share/gc/z/zNMethodTableIteration.cpp index bdd4270ddac..423e98dd28b 100644 --- a/src/hotspot/share/gc/z/zNMethodTableIteration.cpp +++ b/src/hotspot/share/gc/z/zNMethodTableIteration.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, 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,7 +24,6 @@ #include "gc/z/zNMethodTableEntry.hpp" #include "gc/z/zNMethodTableIteration.hpp" #include "memory/iterator.hpp" -#include "runtime/atomicAccess.hpp" #include "utilities/debug.hpp" #include "utilities/globalDefinitions.hpp" @@ -42,11 +41,11 @@ void ZNMethodTableIteration::nmethods_do_begin(ZNMethodTableEntry* table, size_t _table = table; _size = size; - _claimed = 0; + _claimed.store_relaxed(0u); } void ZNMethodTableIteration::nmethods_do_end() { - assert(_claimed >= _size, "Failed to claim all table entries"); + assert(_claimed.load_relaxed() >= _size, "Failed to claim all table entries"); // Finish iteration _table = nullptr; @@ -57,7 +56,7 @@ void ZNMethodTableIteration::nmethods_do(NMethodClosure* cl) { // Claim table partition. Each partition is currently sized to span // two cache lines. This number is just a guess, but seems to work well. const size_t partition_size = (ZCacheLineSize * 2) / sizeof(ZNMethodTableEntry); - const size_t partition_start = MIN2(AtomicAccess::fetch_then_add(&_claimed, partition_size), _size); + const size_t partition_start = MIN2(_claimed.fetch_then_add(partition_size), _size); const size_t partition_end = MIN2(partition_start + partition_size, _size); if (partition_start == partition_end) { // End of table diff --git a/src/hotspot/share/gc/z/zNMethodTableIteration.hpp b/src/hotspot/share/gc/z/zNMethodTableIteration.hpp index 064c3eafca0..12dc096491d 100644 --- a/src/hotspot/share/gc/z/zNMethodTableIteration.hpp +++ b/src/hotspot/share/gc/z/zNMethodTableIteration.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, 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,15 +25,16 @@ #define SHARE_GC_Z_ZNMETHODTABLEITERATION_HPP #include "gc/z/zGlobals.hpp" +#include "runtime/atomic.hpp" class NMethodClosure; class ZNMethodTableEntry; class ZNMethodTableIteration { private: - ZNMethodTableEntry* _table; - size_t _size; - ZCACHE_ALIGNED volatile size_t _claimed; + ZNMethodTableEntry* _table; + size_t _size; + ZCACHE_ALIGNED Atomic _claimed; bool in_progress() const; diff --git a/src/hotspot/share/gc/z/zRelocate.cpp b/src/hotspot/share/gc/z/zRelocate.cpp index da07f67d859..d51cf5abbae 100644 --- a/src/hotspot/share/gc/z/zRelocate.cpp +++ b/src/hotspot/share/gc/z/zRelocate.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, 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 @@ -64,31 +64,31 @@ ZRelocateQueue::ZRelocateQueue() _needs_attention(0) {} bool ZRelocateQueue::needs_attention() const { - return AtomicAccess::load(&_needs_attention) != 0; + return _needs_attention.load_relaxed() != 0; } void ZRelocateQueue::inc_needs_attention() { - const int needs_attention = AtomicAccess::add(&_needs_attention, 1); + const int needs_attention = _needs_attention.add_then_fetch(1); assert(needs_attention == 1 || needs_attention == 2, "Invalid state"); } void ZRelocateQueue::dec_needs_attention() { - const int needs_attention = AtomicAccess::sub(&_needs_attention, 1); + const int needs_attention = _needs_attention.sub_then_fetch(1); assert(needs_attention == 0 || needs_attention == 1, "Invalid state"); } void ZRelocateQueue::activate(uint nworkers) { - _is_active = true; + _is_active.store_relaxed(true); join(nworkers); } void ZRelocateQueue::deactivate() { - AtomicAccess::store(&_is_active, false); + _is_active.store_relaxed(false); clear(); } bool ZRelocateQueue::is_active() const { - return AtomicAccess::load(&_is_active); + return _is_active.load_relaxed(); } void ZRelocateQueue::join(uint nworkers) { @@ -453,7 +453,7 @@ static void retire_target_page(ZGeneration* generation, ZPage* page) { class ZRelocateSmallAllocator { private: ZGeneration* const _generation; - volatile size_t _in_place_count; + Atomic _in_place_count; public: ZRelocateSmallAllocator(ZGeneration* generation) @@ -463,7 +463,7 @@ public: ZPage* alloc_and_retire_target_page(ZForwarding* forwarding, ZPage* target) { ZPage* const page = alloc_page(forwarding); if (page == nullptr) { - AtomicAccess::inc(&_in_place_count); + _in_place_count.add_then_fetch(1u); } if (target != nullptr) { @@ -493,7 +493,7 @@ public: } size_t in_place_count() const { - return _in_place_count; + return _in_place_count.load_relaxed(); } }; @@ -503,7 +503,7 @@ private: ZConditionLock _lock; ZRelocationTargets* _shared_targets; bool _in_place; - volatile size_t _in_place_count; + Atomic _in_place_count; public: ZRelocateMediumAllocator(ZGeneration* generation, ZRelocationTargets* shared_targets) @@ -539,7 +539,7 @@ public: ZPage* const to_page = alloc_page(forwarding); _shared_targets->set(partition_id, to_age, to_page); if (to_page == nullptr) { - AtomicAccess::inc(&_in_place_count); + _in_place_count.add_then_fetch(1u); _in_place = true; } @@ -579,7 +579,7 @@ public: } size_t in_place_count() const { - return _in_place_count; + return _in_place_count.load_relaxed(); } }; diff --git a/src/hotspot/share/gc/z/zRelocate.hpp b/src/hotspot/share/gc/z/zRelocate.hpp index 038efba83eb..d25536e1bce 100644 --- a/src/hotspot/share/gc/z/zRelocate.hpp +++ b/src/hotspot/share/gc/z/zRelocate.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, 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,6 +28,7 @@ #include "gc/z/zPageAge.hpp" #include "gc/z/zRelocationSet.hpp" #include "gc/z/zValue.hpp" +#include "runtime/atomic.hpp" class ZForwarding; class ZGeneration; @@ -42,8 +43,8 @@ private: uint _nworkers; uint _nsynchronized; bool _synchronize; - volatile bool _is_active; - volatile int _needs_attention; + Atomic _is_active; + Atomic _needs_attention; bool needs_attention() const; void inc_needs_attention(); diff --git a/src/hotspot/share/gc/z/zTLABUsage.cpp b/src/hotspot/share/gc/z/zTLABUsage.cpp index c2e3db6bedf..d3613cf8632 100644 --- a/src/hotspot/share/gc/z/zTLABUsage.cpp +++ b/src/hotspot/share/gc/z/zTLABUsage.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, 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 @@ -23,24 +23,23 @@ #include "gc/z/zTLABUsage.hpp" #include "logging/log.hpp" -#include "runtime/atomicAccess.hpp" ZTLABUsage::ZTLABUsage() : _used(0), _used_history() {} void ZTLABUsage::increase_used(size_t size) { - AtomicAccess::add(&_used, size, memory_order_relaxed); + _used.add_then_fetch(size, memory_order_relaxed); } void ZTLABUsage::decrease_used(size_t size) { - precond(size <= _used); + precond(size <= _used.load_relaxed()); - AtomicAccess::sub(&_used, size, memory_order_relaxed); + _used.sub_then_fetch(size, memory_order_relaxed); } void ZTLABUsage::reset() { - const size_t used = AtomicAccess::xchg(&_used, (size_t) 0); + const size_t used = _used.exchange(0u); // Avoid updates when nothing has been allocated since the last YC if (used == 0) { diff --git a/src/hotspot/share/gc/z/zTLABUsage.hpp b/src/hotspot/share/gc/z/zTLABUsage.hpp index 3d1b084fe16..68c7620c26e 100644 --- a/src/hotspot/share/gc/z/zTLABUsage.hpp +++ b/src/hotspot/share/gc/z/zTLABUsage.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, 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,6 +24,7 @@ #ifndef SHARE_GC_Z_ZTLABUSAGE_HPP #define SHARE_GC_Z_ZTLABUSAGE_HPP +#include "runtime/atomic.hpp" #include "utilities/globalDefinitions.hpp" #include "utilities/numberSeq.hpp" @@ -42,9 +43,9 @@ class ZTLABUsage { private: // Accounting TLAB used until the next GC cycle - volatile size_t _used; + Atomic _used; // Sequence of historic used values - TruncatedSeq _used_history; + TruncatedSeq _used_history; public: ZTLABUsage(); diff --git a/src/hotspot/share/interpreter/bytecodeTracer.cpp b/src/hotspot/share/interpreter/bytecodeTracer.cpp index f9980e389e2..69fc93b6c0f 100644 --- a/src/hotspot/share/interpreter/bytecodeTracer.cpp +++ b/src/hotspot/share/interpreter/bytecodeTracer.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, 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 @@ -192,18 +192,20 @@ void BytecodeTracer::trace_interpreter(const methodHandle& method, address bcp, } #endif -void BytecodeTracer::print_method_codes(const methodHandle& method, int from, int to, outputStream* st, int flags) { +void BytecodeTracer::print_method_codes(const methodHandle& method, int from, int to, outputStream* st, int flags, bool buffered) { BytecodePrinter method_printer(flags); BytecodeStream s(method); s.set_interval(from, to); - // Keep output to st coherent: collect all lines and print at once. ResourceMark rm; stringStream ss; + outputStream* out = buffered ? &ss : st; while (s.next() >= 0) { - method_printer.trace(method, s.bcp(), &ss); + method_printer.trace(method, s.bcp(), out); + } + if (buffered) { + st->print("%s", ss.as_string()); } - st->print("%s", ss.as_string()); } void BytecodePrinter::print_constant(int cp_index, outputStream* st) { diff --git a/src/hotspot/share/interpreter/bytecodeTracer.hpp b/src/hotspot/share/interpreter/bytecodeTracer.hpp index b32e55abbcf..e199a2b7ea2 100644 --- a/src/hotspot/share/interpreter/bytecodeTracer.hpp +++ b/src/hotspot/share/interpreter/bytecodeTracer.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, 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 @@ -39,7 +39,7 @@ class BytecodeClosure; class BytecodeTracer: AllStatic { public: NOT_PRODUCT(static void trace_interpreter(const methodHandle& method, address bcp, uintptr_t tos, uintptr_t tos2, outputStream* st = tty);) - static void print_method_codes(const methodHandle& method, int from, int to, outputStream* st, int flags); + static void print_method_codes(const methodHandle& method, int from, int to, outputStream* st, int flags, bool buffered = true); }; #endif // SHARE_INTERPRETER_BYTECODETRACER_HPP diff --git a/src/hotspot/share/jfr/jfr.cpp b/src/hotspot/share/jfr/jfr.cpp index b09a89594ad..d9892f80b6f 100644 --- a/src/hotspot/share/jfr/jfr.cpp +++ b/src/hotspot/share/jfr/jfr.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, 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 @@ -32,6 +32,7 @@ #include "jfr/recorder/repository/jfrEmergencyDump.hpp" #include "jfr/recorder/repository/jfrRepository.hpp" #include "jfr/recorder/service/jfrOptionSet.hpp" +#include "jfr/recorder/service/jfrRecorderService.hpp" #include "jfr/support/jfrClassDefineEvent.hpp" #include "jfr/support/jfrKlassExtension.hpp" #include "jfr/support/jfrResolution.hpp" @@ -43,6 +44,7 @@ #include "runtime/java.hpp" #include "runtime/javaThread.hpp" + bool Jfr::is_enabled() { return JfrRecorder::is_enabled(); } @@ -153,9 +155,9 @@ void Jfr::on_resolution(const Method* caller, const Method* target, TRAPS) { } #endif -void Jfr::on_vm_shutdown(bool emit_old_object_samples, bool emit_event_shutdown, bool halt) { +void Jfr::on_vm_shutdown(bool exception_handler /* false */, bool halt /* false */, bool oom /* false */) { if (!halt && JfrRecorder::is_recording()) { - JfrEmergencyDump::on_vm_shutdown(emit_old_object_samples, emit_event_shutdown); + JfrEmergencyDump::on_vm_shutdown(exception_handler, oom); } } @@ -173,6 +175,12 @@ bool Jfr::on_start_flight_recording_option(const JavaVMOption** option, char* de return JfrOptionSet::parse_start_flight_recording_option(option, delimiter); } +void Jfr::on_report_java_out_of_memory() { + if (CrashOnOutOfMemoryError && JfrRecorder::is_recording()) { + JfrRecorderService::emit_leakprofiler_events_on_oom(); + } +} + #if INCLUDE_CDS void Jfr::on_restoration(const Klass* k, JavaThread* jt) { assert(k != nullptr, "invariant"); diff --git a/src/hotspot/share/jfr/jfr.hpp b/src/hotspot/share/jfr/jfr.hpp index db567cc9a29..ac6a232dda1 100644 --- a/src/hotspot/share/jfr/jfr.hpp +++ b/src/hotspot/share/jfr/jfr.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, 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 @@ -71,7 +71,7 @@ class Jfr : AllStatic { static void on_resolution(const Method* caller, const Method* target, TRAPS); static void on_java_thread_start(JavaThread* starter, JavaThread* startee); static void on_set_current_thread(JavaThread* jt, oop thread); - static void on_vm_shutdown(bool emit_old_object_samples, bool emit_event_shutdown, bool halt = false); + static void on_vm_shutdown(bool exception_handler = false, bool halt = false, bool oom = false); static void on_vm_error_report(outputStream* st); static bool on_flight_recorder_option(const JavaVMOption** option, char* delimiter); static bool on_start_flight_recording_option(const JavaVMOption** option, char* delimiter); @@ -79,6 +79,7 @@ class Jfr : AllStatic { static void initialize_main_thread(JavaThread* jt); static bool has_sample_request(JavaThread* jt); static void check_and_process_sample_request(JavaThread* jt); + static void on_report_java_out_of_memory(); CDS_ONLY(static void on_restoration(const Klass* k, JavaThread* jt);) }; diff --git a/src/hotspot/share/jfr/jni/jfrJniMethod.cpp b/src/hotspot/share/jfr/jni/jfrJniMethod.cpp index 6a1146587bc..885484020bd 100644 --- a/src/hotspot/share/jfr/jni/jfrJniMethod.cpp +++ b/src/hotspot/share/jfr/jni/jfrJniMethod.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2026, 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 @@ -66,10 +66,6 @@ #include "runtime/mutexLocker.hpp" #include "runtime/os.hpp" #include "utilities/debug.hpp" -#ifdef LINUX -#include "os_linux.hpp" -#include "osContainer_linux.hpp" -#endif #define NO_TRANSITION(result_type, header) extern "C" { result_type JNICALL header { #define NO_TRANSITION_END } } @@ -364,8 +360,7 @@ JVM_ENTRY_NO_ENV(void, jfr_set_force_instrumentation(JNIEnv* env, jclass jvm, jb JVM_END NO_TRANSITION(void, jfr_emit_old_object_samples(JNIEnv* env, jclass jvm, jlong cutoff_ticks, jboolean emit_all, jboolean skip_bfs)) - JfrRecorderService service; - service.emit_leakprofiler_events(cutoff_ticks, emit_all == JNI_TRUE, skip_bfs == JNI_TRUE); + JfrRecorderService::emit_leakprofiler_events(cutoff_ticks, emit_all == JNI_TRUE, skip_bfs == JNI_TRUE); NO_TRANSITION_END JVM_ENTRY_NO_ENV(void, jfr_exclude_thread(JNIEnv* env, jclass jvm, jobject t)) @@ -401,35 +396,18 @@ JVM_ENTRY_NO_ENV(jboolean, jfr_is_class_instrumented(JNIEnv* env, jclass jvm, jc JVM_END JVM_ENTRY_NO_ENV(jboolean, jfr_is_containerized(JNIEnv* env, jclass jvm)) -#ifdef LINUX - return OSContainer::is_containerized(); -#else - return false; -#endif + return os::is_containerized(); JVM_END JVM_ENTRY_NO_ENV(jlong, jfr_host_total_memory(JNIEnv* env, jclass jvm)) -#ifdef LINUX - // We want the host memory, not the container limit. - // os::physical_memory() would return the container limit. - return static_cast(os::Linux::physical_memory()); -#else - return static_cast(os::physical_memory()); -#endif + return static_cast(os::Machine::physical_memory()); JVM_END JVM_ENTRY_NO_ENV(jlong, jfr_host_total_swap_memory(JNIEnv* env, jclass jvm)) -#ifdef LINUX - // We want the host swap memory, not the container value. - physical_memory_size_type host_swap = 0; - (void)os::Linux::host_swap(host_swap); // Discard return value and treat as no swap - return static_cast(host_swap); -#else physical_memory_size_type total_swap_space = 0; // Return value ignored - defaulting to 0 on failure. - (void)os::total_swap_space(total_swap_space); + (void)os::Machine::total_swap_space(total_swap_space); return static_cast(total_swap_space); -#endif JVM_END JVM_ENTRY_NO_ENV(void, jfr_emit_data_loss(JNIEnv* env, jclass jvm, jlong bytes)) diff --git a/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampler.cpp b/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampler.cpp index 7e7747ba396..0a8b3975139 100644 --- a/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampler.cpp +++ b/src/hotspot/share/jfr/periodic/sampling/jfrThreadSampler.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2026, 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 @@ -232,41 +232,50 @@ void JfrSamplerThread::task_stacktrace(JfrSampleRequestType type, JavaThread** l JavaThread* start = nullptr; elapsedTimer sample_time; sample_time.start(); - ThreadsListHandle tlh; - // Resolve a sample session relative start position index into the thread list array. - // In cases where the last sampled thread is null or not-null but stale, find_index() returns -1. - _cur_index = tlh.list()->find_index_of_JavaThread(*last_thread); - JavaThread* current = _cur_index != -1 ? *last_thread : nullptr; + { + /* + * Take the Threads_lock for three purposes: + * + * 1) Avoid sampling right through a safepoint, + * which could result in touching oops in case of virtual threads. + * 2) Prevent JFR from issuing an epoch rotation while the sampler thread + * is actively processing a thread in state native, as both threads are outside the safepoint protocol. + * 3) Some operating systems (BSD / Mac) require a process lock when sending a signal with pthread_kill. + * Holding the Threads_lock prevents a JavaThread from calling os::create_thread(), which also takes the process lock. + * In a sense, we provide a coarse signal mask, so we can always send the resume signal. + */ + MutexLocker tlock(Threads_lock); + ThreadsListHandle tlh; + // Resolve a sample session relative start position index into the thread list array. + // In cases where the last sampled thread is null or not-null but stale, find_index() returns -1. + _cur_index = tlh.list()->find_index_of_JavaThread(*last_thread); + JavaThread* current = _cur_index != -1 ? *last_thread : nullptr; - while (num_samples < sample_limit) { - current = next_thread(tlh.list(), start, current); - if (current == nullptr) { - break; - } - if (is_excluded(current)) { - continue; - } - if (start == nullptr) { - start = current; // remember the thread where we started to attempt sampling - } - bool success; - if (JAVA_SAMPLE == type) { - success = sample_java_thread(current); - } else { - assert(type == NATIVE_SAMPLE, "invariant"); - success = sample_native_thread(current); - } - if (success) { - num_samples++; - } - if (SafepointSynchronize::is_at_safepoint()) { - // For _thread_in_native, we cannot get the Threads_lock. - // For _thread_in_Java, well, there are none. - break; + while (num_samples < sample_limit) { + current = next_thread(tlh.list(), start, current); + if (current == nullptr) { + break; + } + if (is_excluded(current)) { + continue; + } + if (start == nullptr) { + start = current; // remember the thread where we started to attempt sampling + } + bool success; + if (JAVA_SAMPLE == type) { + success = sample_java_thread(current); + } else { + assert(type == NATIVE_SAMPLE, "invariant"); + success = sample_native_thread(current); + } + if (success) { + num_samples++; + } } + + *last_thread = current; // remember the thread we last attempted to sample } - - *last_thread = current; // remember the thread we last attempted to sample sample_time.stop(); log_trace(jfr)("JFR thread sampling done in %3.7f secs with %d java %d native samples", sample_time.seconds(), type == JAVA_SAMPLE ? num_samples : 0, type == NATIVE_SAMPLE ? num_samples : 0); @@ -297,6 +306,7 @@ class OSThreadSampler : public SuspendedThreadTask { // Sampling a thread in state _thread_in_Java // involves a platform-specific thread suspend and CPU context retrieval. bool JfrSamplerThread::sample_java_thread(JavaThread* jt) { + assert_lock_strong(Threads_lock); if (jt->thread_state() != _thread_in_Java) { return false; } @@ -328,6 +338,7 @@ static JfrSamplerThread* _sampler_thread = nullptr; // without thread suspension and CPU context retrieval, // if we carefully order the loads of the thread state. bool JfrSamplerThread::sample_native_thread(JavaThread* jt) { + assert_lock_strong(Threads_lock); if (jt->thread_state() != _thread_in_native) { return false; } @@ -343,24 +354,14 @@ bool JfrSamplerThread::sample_native_thread(JavaThread* jt) { SafepointMechanism::arm_local_poll_release(jt); - // Take the Threads_lock for two purposes: - // 1) Avoid sampling through a safepoint which could result - // in touching oops in case of virtual threads. - // 2) Prevent JFR from issuing an epoch rotation while the sampler thread - // is actively processing a thread in native, as both threads are now - // outside the safepoint protocol. - - // OrderAccess::fence() as part of acquiring the lock prevents loads from floating up. - JfrMutexTryLock threads_lock(Threads_lock); - - if (!threads_lock.acquired() || !jt->has_last_Java_frame()) { - // Remove the native sample request and release the potentially waiting thread. - JfrSampleMonitor jsm(tl); - return false; + // Separate the arming of the poll (above) from the reading of JavaThread state (below). + if (UseSystemMemoryBarrier) { + SystemMemoryBarrier::emit(); + } else { + OrderAccess::fence(); } - if (jt->thread_state() != _thread_in_native) { - assert_lock_strong(Threads_lock); + if (jt->thread_state() != _thread_in_native || !jt->has_last_Java_frame()) { JfrSampleMonitor jsm(tl); if (jsm.is_waiting()) { // The thread has already returned from native, diff --git a/src/hotspot/share/jfr/recorder/repository/jfrEmergencyDump.cpp b/src/hotspot/share/jfr/recorder/repository/jfrEmergencyDump.cpp index 309ae961808..cdebcbcfcb5 100644 --- a/src/hotspot/share/jfr/recorder/repository/jfrEmergencyDump.cpp +++ b/src/hotspot/share/jfr/recorder/repository/jfrEmergencyDump.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2026, 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 @@ -38,6 +38,8 @@ #include "runtime/mutexLocker.hpp" #include "runtime/os.hpp" #include "runtime/thread.inline.hpp" +#include "runtime/vmOperations.hpp" +#include "runtime/vmThread.hpp" #include "utilities/growableArray.hpp" #include "utilities/ostream.hpp" @@ -460,15 +462,6 @@ static void release_locks(Thread* thread) { assert(thread != nullptr, "invariant"); assert(!thread->is_Java_thread() || JavaThread::cast(thread)->thread_state() == _thread_in_vm, "invariant"); -#ifdef ASSERT - Mutex* owned_lock = thread->owned_locks(); - while (owned_lock != nullptr) { - Mutex* next = owned_lock->next(); - owned_lock->unlock(); - owned_lock = next; - } -#endif // ASSERT - if (Threads_lock->owned_by_self()) { Threads_lock->unlock(); } @@ -550,17 +543,14 @@ class JavaThreadInVMAndNative : public StackObj { } }; -static void post_events(bool emit_old_object_samples, bool emit_event_shutdown, Thread* thread) { - if (emit_old_object_samples) { - LeakProfiler::emit_events(max_jlong, false, false); - } - if (emit_event_shutdown) { +static void post_events(bool exception_handler, bool oom, Thread * thread) { + if (exception_handler) { EventShutdown e; - e.set_reason("VM Error"); + e.set_reason(oom ? "CrashOnOutOfMemoryError" : "VM Error"); e.commit(); } EventDumpReason event; - event.set_reason(emit_old_object_samples ? "Out of Memory" : "Crash"); + event.set_reason(exception_handler && oom ? "CrashOnOutOfMemoryError" : exception_handler ? "Crash" : "Out of Memory"); event.set_recordingId(-1); event.commit(); } @@ -594,20 +584,40 @@ static bool guard_reentrancy() { return false; } -void JfrEmergencyDump::on_vm_shutdown(bool emit_old_object_samples, bool emit_event_shutdown) { +void JfrEmergencyDump::on_vm_shutdown(bool exception_handler, bool oom) { if (!guard_reentrancy()) { return; } + Thread* const thread = Thread::current_or_null_safe(); assert(thread != nullptr, "invariant"); - if (thread->is_Watcher_thread()) { - log_info(jfr, system)("The Watcher thread crashed so no jfr emergency dump will be generated."); - return; - } + // Ensure a JavaThread is _thread_in_vm when we make this call JavaThreadInVMAndNative jtivm(thread); + post_events(exception_handler, oom, thread); + + if (thread->is_Watcher_thread()) { + // We cannot attempt an emergency dump using the Watcher thread + // because we rely on the WatcherThread task "is_error_reported()", + // to exit the VM after a hardcoded timeout, should the relatively + // risky operation of an emergency dump fail (deadlock, livelock). + log_warning(jfr, system) + ("The Watcher thread crashed so no jfr emergency dump will be generated."); + return; + } + + if (thread->is_VM_thread()) { + const VM_Operation* const operation = VMThread::vm_operation(); + if (operation != nullptr && operation->type() == VM_Operation::VMOp_JFROldObject) { + // We will not be able to issue a rotation because the rotation lock + // is held by the JFR Recorder Thread that issued the VM_Operation. + log_warning(jfr, system) + ("The VM Thread crashed as part of emitting leak profiler events so no jfr emergency dump will be generated."); + return; + } + } + release_locks(thread); - post_events(emit_old_object_samples, emit_event_shutdown, thread); // if JavaThread, transition to _thread_in_native to issue a final flushpoint NoHandleMark nhm; diff --git a/src/hotspot/share/jfr/recorder/repository/jfrEmergencyDump.hpp b/src/hotspot/share/jfr/recorder/repository/jfrEmergencyDump.hpp index 04c2851a516..b337d73364a 100644 --- a/src/hotspot/share/jfr/recorder/repository/jfrEmergencyDump.hpp +++ b/src/hotspot/share/jfr/recorder/repository/jfrEmergencyDump.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, 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 @@ -39,7 +39,7 @@ class JfrEmergencyDump : AllStatic { static const char* chunk_path(const char* repository_path); static void on_vm_error(const char* repository_path); static void on_vm_error_report(outputStream* st, const char* repository_path); - static void on_vm_shutdown(bool emit_old_object_samples, bool emit_event_shutdown); + static void on_vm_shutdown(bool exception_handler, bool oom); }; #endif // SHARE_JFR_RECORDER_REPOSITORY_JFREMERGENCYDUMP_HPP diff --git a/src/hotspot/share/jfr/recorder/service/jfrPostBox.cpp b/src/hotspot/share/jfr/recorder/service/jfrPostBox.cpp index a9ba456ad76..6db7a42ab27 100644 --- a/src/hotspot/share/jfr/recorder/service/jfrPostBox.cpp +++ b/src/hotspot/share/jfr/recorder/service/jfrPostBox.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2026, 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,8 @@ (MSGBIT(MSG_START)) | \ (MSGBIT(MSG_CLONE_IN_MEMORY)) | \ (MSGBIT(MSG_VM_ERROR)) | \ - (MSGBIT(MSG_FLUSHPOINT)) \ + (MSGBIT(MSG_FLUSHPOINT)) | \ + (MSGBIT(MSG_EMIT_LEAKP_REFCHAINS)) \ ) static JfrPostBox* _instance = nullptr; @@ -165,7 +166,7 @@ void JfrPostBox::notify_waiters() { assert(JfrMsg_lock->owned_by_self(), "incrementing _msg_handled_serial is protected by JfrMsg_lock."); // Update made visible on release of JfrMsg_lock via fence instruction in Monitor::IUnlock. ++_msg_handled_serial; - JfrMsg_lock->notify(); + JfrMsg_lock->notify_all(); } // safeguard to ensure no threads are left waiting diff --git a/src/hotspot/share/jfr/recorder/service/jfrPostBox.hpp b/src/hotspot/share/jfr/recorder/service/jfrPostBox.hpp index 10457261643..92f70b1dc9b 100644 --- a/src/hotspot/share/jfr/recorder/service/jfrPostBox.hpp +++ b/src/hotspot/share/jfr/recorder/service/jfrPostBox.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2026, 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,6 +43,7 @@ enum JFR_Msg { MSG_SHUTDOWN, MSG_VM_ERROR, MSG_FLUSHPOINT, + MSG_EMIT_LEAKP_REFCHAINS, MSG_NO_OF_MSGS }; @@ -51,23 +52,25 @@ enum JFR_Msg { * * Synchronous messages (posting thread waits for message completion): * - * MSG_CLONE_IN_MEMORY (0) ; MSGBIT(MSG_CLONE_IN_MEMORY) == (1 << 0) == 0x1 - * MSG_START(1) ; MSGBIT(MSG_START) == (1 << 0x1) == 0x2 - * MSG_STOP (2) ; MSGBIT(MSG_STOP) == (1 << 0x2) == 0x4 - * MSG_ROTATE (3) ; MSGBIT(MSG_ROTATE) == (1 << 0x3) == 0x8 - * MSG_VM_ERROR (8) ; MSGBIT(MSG_VM_ERROR) == (1 << 0x8) == 0x100 - * MSG_FLUSHPOINT (9) ; MSGBIT(MSG_FLUSHPOINT) == (1 << 0x9) == 0x200 + * MSG_CLONE_IN_MEMORY (0) ; MSGBIT(MSG_CLONE_IN_MEMORY) == (1 << 0) == 0x1 + * MSG_START(1) ; MSGBIT(MSG_START) == (1 << 0x1) == 0x2 + * MSG_STOP (2) ; MSGBIT(MSG_STOP) == (1 << 0x2) == 0x4 + * MSG_ROTATE (3) ; MSGBIT(MSG_ROTATE) == (1 << 0x3) == 0x8 + * MSG_VM_ERROR (8) ; MSGBIT(MSG_VM_ERROR) == (1 << 0x8) == 0x100 + * MSG_FLUSHPOINT (9) ; MSGBIT(MSG_FLUSHPOINT) == (1 << 0x9) == 0x200 + * MSG_EMIT_LEAKP_REFCHAINS (10); MSGBIT(MSG_EMIT_LEAKP_REFCHAINS) == (1 << 0xa) == 0x400 * * Asynchronous messages (posting thread returns immediately upon deposit): * - * MSG_FULLBUFFER (4) ; MSGBIT(MSG_FULLBUFFER) == (1 << 0x4) == 0x10 - * MSG_CHECKPOINT (5) ; MSGBIT(CHECKPOINT) == (1 << 0x5) == 0x20 - * MSG_WAKEUP (6) ; MSGBIT(WAKEUP) == (1 << 0x6) == 0x40 - * MSG_SHUTDOWN (7) ; MSGBIT(MSG_SHUTDOWN) == (1 << 0x7) == 0x80 + * MSG_FULLBUFFER (4) ; MSGBIT(MSG_FULLBUFFER) == (1 << 0x4) == 0x10 + * MSG_CHECKPOINT (5) ; MSGBIT(CHECKPOINT) == (1 << 0x5) == 0x20 + * MSG_WAKEUP (6) ; MSGBIT(WAKEUP) == (1 << 0x6) == 0x40 + * MSG_SHUTDOWN (7) ; MSGBIT(MSG_SHUTDOWN) == (1 << 0x7) == 0x80 */ class JfrPostBox : public JfrCHeapObj { friend class JfrRecorder; + friend class JfrRecorderService; public: void post(JFR_Msg msg); diff --git a/src/hotspot/share/jfr/recorder/service/jfrRecorderService.cpp b/src/hotspot/share/jfr/recorder/service/jfrRecorderService.cpp index 08250a1ae59..6f8d44fb1a4 100644 --- a/src/hotspot/share/jfr/recorder/service/jfrRecorderService.cpp +++ b/src/hotspot/share/jfr/recorder/service/jfrRecorderService.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2026, 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 @@ -55,6 +55,7 @@ #include "runtime/safepoint.hpp" #include "runtime/vmOperations.hpp" #include "runtime/vmThread.hpp" +#include "utilities/growableArray.hpp" // incremented on each flushpoint static u8 flushpoint_id = 0; @@ -391,6 +392,7 @@ class JfrSafepointWriteVMOperation : public VM_Operation { JfrRecorderService::JfrRecorderService() : _checkpoint_manager(JfrCheckpointManager::instance()), _chunkwriter(JfrRepository::chunkwriter()), + _post_box(JfrPostBox::instance()), _repository(JfrRepository::instance()), _stack_trace_repository(JfrStackTraceRepository::instance()), _storage(JfrStorage::instance()), @@ -670,17 +672,173 @@ void JfrRecorderService::evaluate_chunk_size_for_rotation() { JfrChunkRotation::evaluate(_chunkwriter); } -void JfrRecorderService::emit_leakprofiler_events(int64_t cutoff_ticks, bool emit_all, bool skip_bfs) { - DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_native(JavaThread::current())); - // Take the rotation lock to exclude flush() during event emits. This is because event emit - // also creates a number checkpoint events. Those checkpoint events require a future typeset checkpoint - // event for completeness, i.e. to be generated before being flushed to a segment. - // The upcoming flush() or rotation() after event emit completes this typeset checkpoint - // and serializes all event emit checkpoint events to the same segment. - JfrRotationLock lock; - // Take the rotation lock before the transition. - JavaThread* current_thread = JavaThread::current(); - MACOS_AARCH64_ONLY(ThreadWXEnable __wx(WXWrite, current_thread)); - ThreadInVMfromNative transition(current_thread); - LeakProfiler::emit_events(cutoff_ticks, emit_all, skip_bfs); +// LeakProfiler event serialization support. + +struct JfrLeakProfilerEmitRequest { + int64_t cutoff_ticks; + bool emit_all; + bool skip_bfs; + bool oom; +}; + +typedef GrowableArrayCHeap JfrLeakProfilerEmitRequestQueue; +static JfrLeakProfilerEmitRequestQueue* _queue = nullptr; +constexpr const static int64_t _no_path_to_gc_roots = 0; +static bool _oom_emit_request_posted = false; +static bool _oom_emit_request_delivered = false; + +static inline bool exclude_paths_to_gc_roots(int64_t cutoff_ticks) { + return cutoff_ticks <= _no_path_to_gc_roots; +} + +static void enqueue(const JfrLeakProfilerEmitRequest& request) { + assert(JfrRotationLock::is_owner(), "invariant"); + if (_queue == nullptr) { + _queue = new JfrLeakProfilerEmitRequestQueue(4); + } + assert(_queue != nullptr, "invariant"); + assert(!_oom_emit_request_posted, "invariant"); + if (request.oom) { + _oom_emit_request_posted = true; + } + _queue->append(request); +} + +static JfrLeakProfilerEmitRequest dequeue() { + assert(JfrRotationLock::is_owner(), "invariant"); + assert(_queue != nullptr, "invariant"); + assert(_queue->is_nonempty(), "invariant"); + const JfrLeakProfilerEmitRequest& request = _queue->first(); + _queue->remove_at(0); + return request; +} + +// This version of emit excludes path-to-gc-roots, i.e. it skips reference chains. +static void emit_leakprofiler_events(bool emit_all, bool skip_bfs, JavaThread* jt) { + assert(jt != nullptr, "invariant"); + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_native(jt)); + // Take the rotation lock to exclude flush() during event emits. This is because the event emit operation + // also creates a number of checkpoint events. Those checkpoint events require a future typeset checkpoint + // event for completeness, i.e., to be generated before being flushed to a segment. + // The upcoming flush() or rotation() after event emit completes this typeset checkpoint + // and serializes all checkpoint events to the same segment. + JfrRotationLock lock; + // Take the rotation lock before the thread transition, to avoid blocking safepoints. + if (_oom_emit_request_posted) { + // A request to emit leakprofiler events in response to CrashOnOutOfMemoryError + // is pending or has already been completed. We are about to crash at any time now. + assert(CrashOnOutOfMemoryError, "invariant"); + return; + } + MACOS_AARCH64_ONLY(ThreadWXEnable __wx(WXWrite, jt)); + ThreadInVMfromNative transition(jt); + // Since we are not requesting path-to-gc-roots, i.e., reference chains, we need not issue a VM_Operation. + // Therefore, we can let the requesting thread process the request directly, since it already holds the requisite lock. + LeakProfiler::emit_events(_no_path_to_gc_roots, emit_all, skip_bfs); +} + +void JfrRecorderService::transition_and_post_leakprofiler_emit_msg(JavaThread* jt) { + assert(jt != nullptr, "invariant"); + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_native(jt);) + assert(!JfrRotationLock::is_owner(), "invariant"); + // Transition to _thread_in_VM and post a synchronous message to the JFR Recorder Thread + // for it to process our enqueued request, which includes paths-to-gc-roots, i.e., reference chains. + MACOS_AARCH64_ONLY(ThreadWXEnable __wx(WXWrite, jt)); + ThreadInVMfromNative transition(jt); + _post_box.post(MSG_EMIT_LEAKP_REFCHAINS); +} + +// This version of emit includes path-to-gc-roots, i.e., it includes in the request traversing of reference chains. +// Traversing reference chains is performed as part of a VM_Operation, and we initiate it from the JFR Recorder Thread. +// Because multiple threads can concurrently report_on_java_out_of_memory(), having them all post a synchronous JFR msg, +// they rendezvous at a safepoint in a convenient state, ThreadBlockInVM. This mechanism prevents any thread from racing past +// this point and begin executing VMError::report_and_die(), until at least one oom request has been delivered. +void JfrRecorderService::emit_leakprofiler_events_paths_to_gc_roots(int64_t cutoff_ticks, + bool emit_all, + bool skip_bfs, + bool oom, + JavaThread* jt) { + assert(jt != nullptr, "invariant"); + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_native(jt);) + assert(!exclude_paths_to_gc_roots(cutoff_ticks), "invariant"); + + { + JfrRotationLock lock; + // Take the rotation lock to read and post a request for the JFR Recorder Thread. + if (_oom_emit_request_posted) { + if (!oom) { + // A request to emit leakprofiler events in response to CrashOnOutOfMemoryError + // is pending or has already been completed. We are about to crash at any time now. + assert(CrashOnOutOfMemoryError, "invariant"); + return; + } + } else { + assert(!_oom_emit_request_posted, "invariant"); + JfrLeakProfilerEmitRequest request = { cutoff_ticks, emit_all, skip_bfs, oom }; + enqueue(request); + } + } + JfrRecorderService service; + service.transition_and_post_leakprofiler_emit_msg(jt); +} + +// Leakprofiler serialization request, the jdk.jfr.internal.JVM.emitOldObjectSamples() Java entry point. +void JfrRecorderService::emit_leakprofiler_events(int64_t cutoff_ticks, + bool emit_all, + bool skip_bfs) { + JavaThread* const jt = JavaThread::current(); + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_native(jt);) + if (exclude_paths_to_gc_roots(cutoff_ticks)) { + ::emit_leakprofiler_events(emit_all, skip_bfs, jt); + return; + } + emit_leakprofiler_events_paths_to_gc_roots(cutoff_ticks, emit_all, skip_bfs, /* oom */ false, jt); +} + +// Leakprofiler serialization request, the report_on_java_out_of_memory VM entry point. +void JfrRecorderService::emit_leakprofiler_events_on_oom() { + assert(CrashOnOutOfMemoryError, "invariant"); + if (EventOldObjectSample::is_enabled()) { + JavaThread* const jt = JavaThread::current(); + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_vm(jt);) + ThreadToNativeFromVM transition(jt); + emit_leakprofiler_events_paths_to_gc_roots(max_jlong, false, false, /* oom */ true, jt); + } +} + +// The worker routine for the JFR Recorder Thread when processing MSG_EMIT_LEAKP_REFCHAINS messages. +void JfrRecorderService::emit_leakprofiler_events() { + JavaThread* const jt = JavaThread::current(); + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_native(jt)); + // Take the rotation lock before the transition. + JfrRotationLock lock; + if (_oom_emit_request_delivered) { + // A request to emit leakprofiler events in response to CrashOnOutOfMemoryError + // has already been completed. We are about to crash at any time now. + assert(_oom_emit_request_posted, "invariant"); + assert(CrashOnOutOfMemoryError, "invariant"); + return; + } + + assert(_queue->is_nonempty(), "invariant"); + + { + MACOS_AARCH64_ONLY(ThreadWXEnable __wx(WXWrite, jt)); + ThreadInVMfromNative transition(jt); + while (_queue->is_nonempty()) { + const JfrLeakProfilerEmitRequest& request = dequeue(); + LeakProfiler::emit_events(request.cutoff_ticks, request.emit_all, request.skip_bfs); + if (_oom_emit_request_posted && request.oom) { + assert(CrashOnOutOfMemoryError, "invariant"); + _oom_emit_request_delivered = true; + break; + } + } + } + + // If processing involved an out-of-memory request, issue an immediate flush operation. + DEBUG_ONLY(JfrJavaSupport::check_java_thread_in_native(jt)); + if (_chunkwriter.is_valid() && _oom_emit_request_delivered) { + invoke_flush(); + } } diff --git a/src/hotspot/share/jfr/recorder/service/jfrRecorderService.hpp b/src/hotspot/share/jfr/recorder/service/jfrRecorderService.hpp index e5b4500afc0..3759ff98828 100644 --- a/src/hotspot/share/jfr/recorder/service/jfrRecorderService.hpp +++ b/src/hotspot/share/jfr/recorder/service/jfrRecorderService.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2026, 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,23 @@ #include "jfr/utilities/jfrAllocation.hpp" +class JavaThread; class JfrCheckpointManager; class JfrChunkWriter; +class JfrPostBox; class JfrRepository; class JfrStackTraceRepository; class JfrStorage; class JfrStringPool; class JfrRecorderService : public StackObj { + friend class Jfr; friend class JfrSafepointClearVMOperation; friend class JfrSafepointWriteVMOperation; private: JfrCheckpointManager& _checkpoint_manager; JfrChunkWriter& _chunkwriter; + JfrPostBox& _post_box; JfrRepository& _repository; JfrStackTraceRepository& _stack_trace_repository; JfrStorage& _storage; @@ -64,6 +68,14 @@ class JfrRecorderService : public StackObj { void invoke_safepoint_write(); void post_safepoint_write(); + void transition_and_post_leakprofiler_emit_msg(JavaThread* jt); + + static void emit_leakprofiler_events_on_oom(); + static void emit_leakprofiler_events_paths_to_gc_roots(int64_t cutoff_ticks, + bool emit_all, + bool skip_bfs, + bool oom, + JavaThread* jt); public: JfrRecorderService(); void start(); @@ -72,8 +84,12 @@ class JfrRecorderService : public StackObj { void flushpoint(); void process_full_buffers(); void evaluate_chunk_size_for_rotation(); - void emit_leakprofiler_events(int64_t cutoff_ticks, bool emit_all, bool skip_bfs); + void emit_leakprofiler_events(); + static bool is_recording(); + static void emit_leakprofiler_events(int64_t cutoff_ticks, + bool emit_all, + bool skip_bfs); }; #endif // SHARE_JFR_RECORDER_SERVICE_JFRRECORDERSERVICE_HPP diff --git a/src/hotspot/share/jfr/recorder/service/jfrRecorderThreadLoop.cpp b/src/hotspot/share/jfr/recorder/service/jfrRecorderThreadLoop.cpp index de015e9a502..bd01adf5b3a 100644 --- a/src/hotspot/share/jfr/recorder/service/jfrRecorderThreadLoop.cpp +++ b/src/hotspot/share/jfr/recorder/service/jfrRecorderThreadLoop.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2026, 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 @@ -44,6 +44,7 @@ void recorderthread_entry(JavaThread* thread, JavaThread* unused) { #define ROTATE (msgs & (MSGBIT(MSG_ROTATE)|MSGBIT(MSG_STOP))) #define FLUSHPOINT (msgs & (MSGBIT(MSG_FLUSHPOINT))) #define PROCESS_FULL_BUFFERS (msgs & (MSGBIT(MSG_ROTATE)|MSGBIT(MSG_STOP)|MSGBIT(MSG_FULLBUFFER))) + #define LEAKPROFILER_REFCHAINS (msgs & MSGBIT(MSG_EMIT_LEAKP_REFCHAINS)) JfrPostBox& post_box = JfrRecorderThreadEntry::post_box(); log_debug(jfr, system)("Recorder thread STARTED"); @@ -70,6 +71,9 @@ void recorderthread_entry(JavaThread* thread, JavaThread* unused) { if (PROCESS_FULL_BUFFERS) { service.process_full_buffers(); } + if (LEAKPROFILER_REFCHAINS) { + service.emit_leakprofiler_events(); + } // Check amount of data written to chunk already // if it warrants asking for a new chunk. service.evaluate_chunk_size_for_rotation(); @@ -98,5 +102,5 @@ void recorderthread_entry(JavaThread* thread, JavaThread* unused) { #undef ROTATE #undef FLUSHPOINT #undef PROCESS_FULL_BUFFERS - #undef SCAVENGE + #undef LEAKPROFILER_REFCHAINS } diff --git a/src/hotspot/share/jfr/utilities/jfrSet.hpp b/src/hotspot/share/jfr/utilities/jfrSet.hpp index 1432d40e4b2..c443434800a 100644 --- a/src/hotspot/share/jfr/utilities/jfrSet.hpp +++ b/src/hotspot/share/jfr/utilities/jfrSet.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, 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,6 +25,8 @@ #ifndef SHARE_JFR_UTILITIES_JFRSET_HPP #define SHARE_JFR_UTILITIES_JFRSET_HPP +#include "cppstdlib/new.hpp" +#include "cppstdlib/type_traits.hpp" #include "jfr/utilities/jfrTypes.hpp" #include "memory/allocation.hpp" @@ -67,7 +69,9 @@ class JfrSetStorage : public AnyObj { } else { table = NEW_RESOURCE_ARRAY(K, table_size); } - memset(table, 0, table_size * sizeof(K)); + for (unsigned i = 0; i < table_size; ++i) { + ::new (&table[i]) K{}; + } return table; } @@ -88,7 +92,7 @@ class JfrSetStorage : public AnyObj { assert(is_nonempty(), "invariant"); for (unsigned i = 0; i < _table_size; ++i) { K k = _table[i]; - if (k != 0) { + if (k != K{}) { functor(k); } } @@ -107,7 +111,14 @@ class JfrSetStorage : public AnyObj { } void clear() { - memset(_table, 0, _table_size * sizeof(K)); + for (unsigned i = 0; i < _table_size; ++i) { + if constexpr (std::is_copy_assignable_v) { + _table[i] = K{}; + } else { + _table[i].~K(); + ::new (&_table[i]) K{}; + } + } } }; @@ -136,11 +147,11 @@ class JfrSet : public JfrSetStorage { _resize_threshold = old_table_size; for (unsigned i = 0; i < old_table_size; ++i) { const K k = old_table[i]; - if (k != 0) { + if (k != K{}) { uint32_t idx = slot_idx(CONFIG::hash(k)); do { K v = this->_table[idx]; - if (v == 0) { + if (v == K{}) { this->_table[idx] = k; break; } @@ -161,7 +172,7 @@ class JfrSet : public JfrSetStorage { K* result = nullptr; while (true) { K v = this->_table[idx]; - if (v == 0) { + if (v == K{}) { result = &this->_table[idx]; break; } @@ -196,7 +207,7 @@ class JfrSet : public JfrSetStorage { // Already exists. return false; } - assert(*slot == 0, "invariant"); + assert(*slot == K{}, "invariant"); *slot = k; if (++this->_elements == _resize_threshold) { resize(); diff --git a/src/hotspot/share/jvmci/jvmciCompilerToVMInit.cpp b/src/hotspot/share/jvmci/jvmciCompilerToVMInit.cpp index 801a9bf8eb4..3e9d8c97b88 100644 --- a/src/hotspot/share/jvmci/jvmciCompilerToVMInit.cpp +++ b/src/hotspot/share/jvmci/jvmciCompilerToVMInit.cpp @@ -236,16 +236,23 @@ void CompilerToVM::Data::initialize(JVMCI_TRAPS) { JVMTI_ONLY( _should_notify_object_alloc = &JvmtiExport::_should_notify_object_alloc; ) BarrierSet* bs = BarrierSet::barrier_set(); +#if INCLUDE_G1GC + if (bs->is_a(BarrierSet::G1BarrierSet)) { + cardtable_start_address = nullptr; + cardtable_shift = CardTable::card_shift(); + } else +#endif +#if INCLUDE_SHENANDOAHGC + if (bs->is_a(BarrierSet::ShenandoahBarrierSet)) { + cardtable_start_address = nullptr; + cardtable_shift = CardTable::card_shift(); + } else +#endif if (bs->is_a(BarrierSet::CardTableBarrierSet)) { - CardTable::CardValue* base = ci_card_table_address(); + CardTable::CardValue* base = ci_card_table_address_const(); assert(base != nullptr, "unexpected byte_map_base"); cardtable_start_address = base; cardtable_shift = CardTable::card_shift(); -#if INCLUDE_SHENANDOAHGC - } else if (bs->is_a(BarrierSet::ShenandoahBarrierSet)) { - cardtable_start_address = nullptr; - cardtable_shift = CardTable::card_shift(); -#endif } else { // No card mark barriers cardtable_start_address = nullptr; diff --git a/src/hotspot/share/memory/allocation.cpp b/src/hotspot/share/memory/allocation.cpp index 9df91225d6f..2a109688ca6 100644 --- a/src/hotspot/share/memory/allocation.cpp +++ b/src/hotspot/share/memory/allocation.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, 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,6 +22,7 @@ * */ +#include "cds/aotMetaspace.hpp" #include "memory/allocation.inline.hpp" #include "memory/arena.hpp" #include "memory/metaspace.hpp" @@ -161,12 +162,33 @@ void AnyObj::set_allocation_type(address res, allocation_type type) { } } +void AnyObj::set_in_aot_cache() { + _allocation_t[0] = 0; + _allocation_t[1] = 0; +} + +bool AnyObj::in_aot_cache() const { + if (AOTMetaspace::in_aot_cache(this)) { + precond(_allocation_t[0] == 0); + precond(_allocation_t[1] == 0); + return true; + } else { + return false; + } +} + AnyObj::allocation_type AnyObj::get_allocation_type() const { + if (in_aot_cache()) { + return STACK_OR_EMBEDDED; + } assert(~(_allocation_t[0] | allocation_mask) == (uintptr_t)this, "lost resource object"); return (allocation_type)((~_allocation_t[0]) & allocation_mask); } bool AnyObj::is_type_set() const { + if (in_aot_cache()) { + return true; + } allocation_type type = (allocation_type)(_allocation_t[1] & allocation_mask); return get_allocation_type() == type && (_allocation_t[1] - type) == (uintptr_t)(&_allocation_t[1]); diff --git a/src/hotspot/share/memory/allocation.hpp b/src/hotspot/share/memory/allocation.hpp index 963ca04aadf..15b7750a363 100644 --- a/src/hotspot/share/memory/allocation.hpp +++ b/src/hotspot/share/memory/allocation.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, 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 @@ -420,6 +420,7 @@ class AnyObj { public: enum allocation_type { STACK_OR_EMBEDDED = 0, RESOURCE_AREA, C_HEAP, ARENA, allocation_mask = 0x3 }; static void set_allocation_type(address res, allocation_type type) NOT_DEBUG_RETURN; + void set_in_aot_cache() NOT_DEBUG_RETURN; #ifdef ASSERT private: // When this object is allocated on stack the new() operator is not @@ -429,6 +430,7 @@ class AnyObj { uintptr_t _allocation_t[2]; bool is_type_set() const; void initialize_allocation_info(); + bool in_aot_cache() const; public: allocation_type get_allocation_type() const; bool allocated_on_stack_or_embedded() const { return get_allocation_type() == STACK_OR_EMBEDDED; } diff --git a/src/hotspot/share/memory/metaspaceClosure.cpp b/src/hotspot/share/memory/metaspaceClosure.cpp index f3c6e350034..0239eadf692 100644 --- a/src/hotspot/share/memory/metaspaceClosure.cpp +++ b/src/hotspot/share/memory/metaspaceClosure.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, 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,7 +22,21 @@ * */ +#include "cds/aotGrowableArray.hpp" +#include "classfile/packageEntry.hpp" #include "memory/metaspaceClosure.hpp" +#include "oops/array.hpp" +#include "oops/instanceKlass.hpp" + +// Sanity checks +static_assert(!HAS_METASPACE_POINTERS_DO(int)); + +static_assert(HAS_METASPACE_POINTERS_DO(Array)); +static_assert(HAS_METASPACE_POINTERS_DO(Array)); +static_assert(HAS_METASPACE_POINTERS_DO(InstanceKlass)); +static_assert(HAS_METASPACE_POINTERS_DO(PackageEntry)); +static_assert(HAS_METASPACE_POINTERS_DO(AOTGrowableArray)); +static_assert(HAS_METASPACE_POINTERS_DO(AOTGrowableArray)); void MetaspaceClosure::push_impl(MetaspaceClosure::Ref* ref) { if (_enclosing_ref != nullptr) { diff --git a/src/hotspot/share/memory/metaspaceClosure.hpp b/src/hotspot/share/memory/metaspaceClosure.hpp index 329502d7a47..b6ba69d6f63 100644 --- a/src/hotspot/share/memory/metaspaceClosure.hpp +++ b/src/hotspot/share/memory/metaspaceClosure.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, 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,9 +25,11 @@ #ifndef SHARE_MEMORY_METASPACECLOSURE_HPP #define SHARE_MEMORY_METASPACECLOSURE_HPP +#include "cds/aotGrowableArray.hpp" #include "cppstdlib/type_traits.hpp" #include "logging/log.hpp" #include "memory/allocation.hpp" +#include "memory/metaspaceClosureType.hpp" #include "metaprogramming/enableIf.hpp" #include "oops/array.hpp" #include "utilities/debug.hpp" @@ -36,41 +38,27 @@ #include "utilities/macros.hpp" #include "utilities/resizableHashTable.hpp" -// The metadata hierarchy is separate from the oop hierarchy - class MetaspaceObj; // no C++ vtable -//class Array; // no C++ vtable - class Annotations; // no C++ vtable - class ConstantPoolCache; // no C++ vtable - class ConstMethod; // no C++ vtable - class MethodCounters; // no C++ vtable - class Symbol; // no C++ vtable - class Metadata; // has C++ vtable (so do all subclasses) - class ConstantPool; - class MethodData; - class Method; - class Klass; - class InstanceKlass; - class InstanceMirrorKlass; - class InstanceClassLoaderKlass; - class InstanceRefKlass; - class ArrayKlass; - class ObjArrayKlass; - class TypeArrayKlass; +// This macro just check for the existence of a member with the name "metaspace_pointers_do". If the +// parameter list is not (MetaspaceClosure* it), you will get a compilation error. +#define HAS_METASPACE_POINTERS_DO(T) HasMetaspacePointersDo::value + +template +class HasMetaspacePointersDo { + template static void* test(decltype(&U::metaspace_pointers_do)); + template static int test(...); + using test_type = decltype(test(nullptr)); +public: + static constexpr bool value = std::is_pointer_v; +}; // class MetaspaceClosure -- // -// This class is used for iterating the objects in the HotSpot Metaspaces. It +// This class is used for iterating the class metadata objects. It // provides an API to walk all the reachable objects starting from a set of // root references (such as all Klass'es in the SystemDictionary). // -// Currently it is used for compacting the CDS archive by eliminate temporary -// objects allocated during archive creation time. See ArchiveBuilder for an example. -// -// To support MetaspaceClosure, each subclass of MetaspaceObj must provide -// a method of the type void metaspace_pointers_do(MetaspaceClosure*). This method -// should call MetaspaceClosure::push() on every pointer fields of this -// class that points to a MetaspaceObj. See Annotations::metaspace_pointers_do() -// for an example. +// Currently it is used to copy class metadata into the AOT cache. +// See ArchiveBuilder for an example. class MetaspaceClosure { public: enum Writability { @@ -79,16 +67,32 @@ public: _default }; +#define METASPACE_CLOSURE_TYPE_NAME_CASE(name) case MetaspaceClosureType::name ## Type: return #name; + + static const char* type_name(MetaspaceClosureType type) { + switch(type) { + METASPACE_CLOSURE_TYPES_DO(METASPACE_CLOSURE_TYPE_NAME_CASE) + default: + ShouldNotReachHere(); + return nullptr; + } + } + // class MetaspaceClosure::Ref -- // - // MetaspaceClosure can be viewed as a very simple type of copying garbage - // collector. For it to function properly, it requires each subclass of - // MetaspaceObj to provide two methods: + // For type X to be iterable by MetaspaceClosure, X (or one of X's supertypes) must have + // the following public functions: + // void metaspace_pointers_do(MetaspaceClosure* it); + // static bool is_read_only_by_default() { return true; } // - // size_t size(); -- to determine how much data to copy - // void metaspace_pointers_do(MetaspaceClosure*); -- to locate all the embedded pointers + // In addition, if X is not a subtype of MetaspaceObj, it must have the following function: + // MetaspaceClosureType type() const; + // int size_in_heapwords() const; // - // Calling these methods would be trivial if these two were virtual methods. + // Currently, the iterable types include all subtypes of MetsapceObj, as well + // as GrowableArray, ModuleEntry and PackageEntry. + // + // Calling these functions would be trivial if these were virtual functions. // However, to save space, MetaspaceObj has NO vtable. The vtable is introduced // only in the Metadata class. // @@ -97,7 +101,7 @@ public: // so that we can statically discover the type of a object. The use of Ref // depends on the fact that: // - // [1] We don't use polymorphic pointers for MetaspaceObj's that are not subclasses + // [1] We don't use polymorphic pointers to MetaspaceObj's that are not subclasses // of Metadata. I.e., we don't do this: // class Klass { // MetaspaceObj *_obj; @@ -130,8 +134,7 @@ public: virtual bool not_null() const = 0; virtual int size() const = 0; virtual void metaspace_pointers_do(MetaspaceClosure *it) const = 0; - virtual void metaspace_pointers_do_at(MetaspaceClosure *it, address new_loc) const = 0; - virtual MetaspaceObj::Type msotype() const = 0; + virtual MetaspaceClosureType type() const = 0; virtual bool is_read_only_by_default() const = 0; virtual ~Ref() {} @@ -180,7 +183,26 @@ public: } private: - // MSORef -- iterate an instance of MetaspaceObj + template ::value)> + static int get_size(T* obj) { + return obj->size(); + } + + template ::value)> + static int get_size(T* obj) { + return obj->size_in_heapwords(); + } + + static MetaspaceClosureType as_type(MetaspaceClosureType t) { + return t; + } + + static MetaspaceClosureType as_type(MetaspaceObj::Type msotype) { + precond(msotype < MetaspaceObj::_number_of_types); + return (MetaspaceClosureType)msotype; + } + + // MSORef -- iterate an instance of T, where T::metaspace_pointers_do() exists. template class MSORef : public Ref { T** _mpp; T* dereference() const { @@ -196,18 +218,20 @@ private: virtual bool is_read_only_by_default() const { return T::is_read_only_by_default(); } virtual bool not_null() const { return dereference() != nullptr; } - virtual int size() const { return dereference()->size(); } - virtual MetaspaceObj::Type msotype() const { return dereference()->type(); } + virtual int size() const { return get_size(dereference()); } + virtual MetaspaceClosureType type() const { return as_type(dereference()->type()); } virtual void metaspace_pointers_do(MetaspaceClosure *it) const { dereference()->metaspace_pointers_do(it); } - virtual void metaspace_pointers_do_at(MetaspaceClosure *it, address new_loc) const { - ((T*)new_loc)->metaspace_pointers_do(it); - } }; - // abstract base class for MSOArrayRef, MSOPointerArrayRef and OtherArrayRef + //--------------------- + // Support for Array + //--------------------- + + // Abstract base class for MSOArrayRef, MSOPointerArrayRef and OtherArrayRef. + // These are used for iterating Array. template class ArrayRef : public Ref { Array** _mpp; protected: @@ -224,10 +248,10 @@ private: virtual bool is_read_only_by_default() const { return true; } virtual bool not_null() const { return dereference() != nullptr; } virtual int size() const { return dereference()->size(); } - virtual MetaspaceObj::Type msotype() const { return MetaspaceObj::array_type(sizeof(T)); } + virtual MetaspaceClosureType type() const { return as_type(MetaspaceObj::array_type(sizeof(T))); } }; - // OtherArrayRef -- iterate an instance of Array, where T is NOT a subtype of MetaspaceObj. + // OtherArrayRef -- iterate an instance of Array, where T does NOT have metaspace_pointer_do(). // T can be a primitive type, such as int, or a structure. However, we do not scan // the fields inside T, so you should not embed any pointers inside T. template class OtherArrayRef : public ArrayRef { @@ -238,13 +262,9 @@ private: Array* array = ArrayRef::dereference(); log_trace(aot)("Iter(OtherArray): %p [%d]", array, array->length()); } - virtual void metaspace_pointers_do_at(MetaspaceClosure *it, address new_loc) const { - Array* array = (Array*)new_loc; - log_trace(aot)("Iter(OtherArray): %p [%d]", array, array->length()); - } }; - // MSOArrayRef -- iterate an instance of Array, where T is a subtype of MetaspaceObj. + // MSOArrayRef -- iterate an instance of Array, where T has metaspace_pointer_do(). // We recursively call T::metaspace_pointers_do() for each element in this array. template class MSOArrayRef : public ArrayRef { public: @@ -253,9 +273,6 @@ private: virtual void metaspace_pointers_do(MetaspaceClosure *it) const { metaspace_pointers_do_at_impl(it, ArrayRef::dereference()); } - virtual void metaspace_pointers_do_at(MetaspaceClosure *it, address new_loc) const { - metaspace_pointers_do_at_impl(it, (Array*)new_loc); - } private: void metaspace_pointers_do_at_impl(MetaspaceClosure *it, Array* array) const { log_trace(aot)("Iter(MSOArray): %p [%d]", array, array->length()); @@ -266,7 +283,7 @@ private: } }; - // MSOPointerArrayRef -- iterate an instance of Array, where T is a subtype of MetaspaceObj. + // MSOPointerArrayRef -- iterate an instance of Array, where T has metaspace_pointer_do(). // We recursively call MetaspaceClosure::push() for each pointer in this array. template class MSOPointerArrayRef : public ArrayRef { public: @@ -275,9 +292,6 @@ private: virtual void metaspace_pointers_do(MetaspaceClosure *it) const { metaspace_pointers_do_at_impl(it, ArrayRef::dereference()); } - virtual void metaspace_pointers_do_at(MetaspaceClosure *it, address new_loc) const { - metaspace_pointers_do_at_impl(it, (Array*)new_loc); - } private: void metaspace_pointers_do_at_impl(MetaspaceClosure *it, Array* array) const { log_trace(aot)("Iter(MSOPointerArray): %p [%d]", array, array->length()); @@ -288,6 +302,102 @@ private: } }; + //-------------------------------- + // Support for AOTGrowableArray + //-------------------------------- + + // Abstract base class for MSOCArrayRef, MSOPointerCArrayRef and OtherCArrayRef. + // These are used for iterating the buffer held by AOTGrowableArray. + template class CArrayRef : public Ref { + T** _mpp; + int _num_elems; // Number of elements + + int byte_size() const { + return _num_elems * sizeof(T); + } + + protected: + // C pointer arrays don't support tagged pointers. + T* dereference() const { + return *_mpp; + } + virtual void** mpp() const { + return (void**)_mpp; + } + int num_elems() const { + return _num_elems; + } + public: + CArrayRef(T** mpp, int num_elems, Writability w) + : Ref(w), _mpp(mpp), _num_elems(num_elems) { + assert(is_aligned(byte_size(), BytesPerWord), "must be"); + } + + virtual bool is_read_only_by_default() const { return false; } + virtual bool not_null() const { return dereference() != nullptr; } + virtual int size() const { return (int)heap_word_size(byte_size()); } + virtual MetaspaceClosureType type() const { return MetaspaceClosureType::CArrayType; } + }; + + // OtherCArrayRef -- iterate a C array of type T, where T does NOT have metaspace_pointer_do(). + // T can be a primitive type, such as int, or a structure. However, we do not scan + // the fields inside T, so you should not embed any pointers inside T. + template class OtherCArrayRef : public CArrayRef { + public: + OtherCArrayRef(T** mpp, int num_elems, Writability w) : CArrayRef(mpp, num_elems, w) {} + + virtual void metaspace_pointers_do(MetaspaceClosure *it) const { + T* array = CArrayRef::dereference(); + log_trace(aot)("Iter(OtherCArray): %p [%d]", array, CArrayRef::num_elems()); + } + }; + + // MSOCArrayRef -- iterate a C array of type T, where T has metaspace_pointer_do(). + // We recursively call T::metaspace_pointers_do() for each element in this array. + // This is for supporting AOTGrowableArray. + // + // E.g., PackageEntry* _pkg_entry_pointers[2]; // a buffer that has 2 PackageEntry objects + // ... + // it->push(&_pkg_entry_pointers, 2); + // /* calls _pkg_entry_pointers[0].metaspace_pointers_do(it); */ + // /* calls _pkg_entry_pointers[1].metaspace_pointers_do(it); */ + template class MSOCArrayRef : public CArrayRef { + public: + MSOCArrayRef(T** mpp, int num_elems, Writability w) : CArrayRef(mpp, num_elems, w) {} + + virtual void metaspace_pointers_do(MetaspaceClosure *it) const { + T* array = CArrayRef::dereference(); + log_trace(aot)("Iter(MSOCArray): %p [%d]", array, CArrayRef::num_elems()); + for (int i = 0; i < CArrayRef::num_elems(); i++) { + T* elm = array + i; + elm->metaspace_pointers_do(it); + } + } + }; + + // MSOPointerCArrayRef -- iterate a C array of type T*, where T has metaspace_pointer_do(). + // We recursively call MetaspaceClosure::push() for each pointer in this array. + // This is for supporting AOTGrowableArray. + // + // E.g., PackageEntry** _pkg_entry_pointers[2]; // a buffer that has 2 PackageEntry pointers + // ... + // it->push(&_pkg_entry_pointers, 2); + // /* calls _pkg_entry_pointers[0]->metaspace_pointers_do(it); */ + // /* calls _pkg_entry_pointers[1]->metaspace_pointers_do(it); */ + template class MSOPointerCArrayRef : public CArrayRef { + public: + MSOPointerCArrayRef(T*** mpp, int num_elems, Writability w) : CArrayRef(mpp, num_elems, w) {} + + virtual void metaspace_pointers_do(MetaspaceClosure *it) const { + T** array = CArrayRef::dereference(); + log_trace(aot)("Iter(MSOPointerCArray): %p [%d]", array, CArrayRef::num_elems()); + for (int i = 0; i < CArrayRef::num_elems(); i++) { + T** mpp = array + i; + it->push(mpp); + } + } + }; + // Normally, chains of references like a->b->c->d are iterated recursively. However, // if recursion is too deep, we save the Refs in _pending_refs, and push them later in // MetaspaceClosure::finish(). This avoids overflowing the C stack. @@ -330,37 +440,61 @@ public: // Array*>* a4 = ...; it->push(&a4); => MSOPointerArrayRef // Array* a5 = ...; it->push(&a5); => MSOPointerArrayRef // - // Note that the following will fail to compile (to prevent you from adding new fields - // into the MetaspaceObj subtypes that cannot be properly copied by CDS): + // AOTGrowableArrays have a separate "C array" buffer, so they are scanned in two steps: // - // MemoryPool* p = ...; it->push(&p); => MemoryPool is not a subclass of MetaspaceObj - // Array* a6 = ...; it->push(&a6); => MemoryPool is not a subclass of MetaspaceObj - // Array* a7 = ...; it->push(&a7); => int is not a subclass of MetaspaceObj + // AOTGrowableArray* ga1 = ...; it->push(&ga1); => MSORef => OtherCArrayRef + // AOTGrowableArray* ga2 = ...; it->push(&ga2); => MSORef => MSOCArrayRef + // AOTGrowableArray* ga3 = ...; it->push(&ga3); => MSORef => MSOPointerCArrayRef + // + // Note that the following will fail to compile: + // + // MemoryPool* p = ...; it->push(&p); => MemoryPool doesn't have metaspace_pointers_do + // Array* a6 = ...; it->push(&a6); => MemoryPool doesn't have metaspace_pointers_do + // Array* a7 = ...; it->push(&a7); => int doesn't have metaspace_pointers_do + // --- Regular iterable objects template void push(T** mpp, Writability w = _default) { - static_assert(std::is_base_of::value, "Do not push pointers of arbitrary types"); + static_assert(HAS_METASPACE_POINTERS_DO(T), "Do not push pointers of arbitrary types"); push_with_ref>(mpp, w); } - template ::value)> + // --- Array + template void push(Array** mpp, Writability w = _default) { push_with_ref>(mpp, w); } - template ::value)> + template void push(Array** mpp, Writability w = _default) { push_with_ref>(mpp, w); } template void push(Array** mpp, Writability w = _default) { - static_assert(std::is_base_of::value, "Do not push Arrays of arbitrary pointer types"); + static_assert(HAS_METASPACE_POINTERS_DO(T), "Do not push Arrays of arbitrary pointer types"); push_with_ref>(mpp, w); } + + // --- The buffer of AOTGrowableArray + template + void push_c_array(T** mpp, int num_elems, Writability w = _default) { + push_impl(new OtherCArrayRef(mpp, num_elems, w)); + } + + template + void push_c_array(T** mpp, int num_elems, Writability w = _default) { + push_impl(new MSOCArrayRef(mpp, num_elems, w)); + } + + template + void push_c_array(T*** mpp, int num_elems, Writability w = _default) { + static_assert(HAS_METASPACE_POINTERS_DO(T), "Do not push C arrays of arbitrary pointer types"); + push_impl(new MSOPointerCArrayRef(mpp, num_elems, w)); + } }; -// This is a special MetaspaceClosure that visits each unique MetaspaceObj once. +// This is a special MetaspaceClosure that visits each unique object once. class UniqueMetaspaceClosure : public MetaspaceClosure { static const int INITIAL_TABLE_SIZE = 15889; static const int MAX_TABLE_SIZE = 1000000; diff --git a/src/hotspot/share/memory/metaspaceClosureType.hpp b/src/hotspot/share/memory/metaspaceClosureType.hpp new file mode 100644 index 00000000000..adf6805521b --- /dev/null +++ b/src/hotspot/share/memory/metaspaceClosureType.hpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2026, 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. + * + */ + +#ifndef SHARE_MEMORY_METASPACECLOSURETYPE_HPP +#define SHARE_MEMORY_METASPACECLOSURETYPE_HPP + +#include "memory/allocation.hpp" + +// MetaspaceClosure is able to iterate on MetaspaceObjs, plus the following classes +#define METASPACE_CLOSURE_TYPES_DO(f) \ + METASPACE_OBJ_TYPES_DO(f) \ + f(CArray) \ + f(GrowableArray) \ + f(ModuleEntry) \ + f(PackageEntry) \ + +#define METASPACE_CLOSURE_TYPE_DECLARE(name) name ## Type, + +enum class MetaspaceClosureType : int { + METASPACE_CLOSURE_TYPES_DO(METASPACE_CLOSURE_TYPE_DECLARE) + _number_of_types +}; + + +#endif // SHARE_MEMORY_METASPACECLOSURETYPE_HPP diff --git a/src/hotspot/share/metaprogramming/dependentAlwaysFalse.hpp b/src/hotspot/share/metaprogramming/dependentAlwaysFalse.hpp new file mode 100644 index 00000000000..b1f9d89df11 --- /dev/null +++ b/src/hotspot/share/metaprogramming/dependentAlwaysFalse.hpp @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2025, 2026, 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. + * + */ + +#ifndef SHARE_METAPROGRAMMING_DEPENDENTALWAYSFALSE_HPP +#define SHARE_METAPROGRAMMING_DEPENDENTALWAYSFALSE_HPP + +// This provides a workaround for static_assert(false) in discarded or +// otherwise uninstantiated places. Instead use +// static_assert(DependentAlwaysFalse, "...") +// See http://wg21.link/p2593r1. Some, but not all, compiler versions we're +// using have implemented that change as a DR: +// https://cplusplus.github.io/CWG/issues/2518.html +template inline constexpr bool DependentAlwaysFalse = false; + +#endif // SHARE_METAPROGRAMMING_DEPENDENTALWAYSFALSE_HPP diff --git a/src/hotspot/share/oops/array.hpp b/src/hotspot/share/oops/array.hpp index 90dcc9dfeaf..82067e007ea 100644 --- a/src/hotspot/share/oops/array.hpp +++ b/src/hotspot/share/oops/array.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, 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 @@ -33,6 +33,8 @@ // Array for metadata allocation +class MetaspaceClosure; + template class Array: public MetaspaceObj { friend class ArchiveBuilder; @@ -157,6 +159,9 @@ protected: st->print("Array(" PTR_FORMAT ")", p2i(this)); } + // This function does nothing. The iteration of the elements are done inside metaspaceClosure.hpp + void metaspace_pointers_do(MetaspaceClosure* it) {} + #ifndef PRODUCT void print(outputStream* st) { for (int i = 0; i< _length; i++) { diff --git a/src/hotspot/share/oops/instanceKlass.cpp b/src/hotspot/share/oops/instanceKlass.cpp index 56a80daed60..1963327fc78 100644 --- a/src/hotspot/share/oops/instanceKlass.cpp +++ b/src/hotspot/share/oops/instanceKlass.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, 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 @@ -2693,6 +2693,10 @@ void InstanceKlass::metaspace_pointers_do(MetaspaceClosure* it) { it->push(&_nest_members); it->push(&_permitted_subclasses); it->push(&_record_components); + + if (CDSConfig::is_dumping_full_module_graph() && !defined_by_other_loaders()) { + it->push(&_package_entry); + } } #if INCLUDE_CDS @@ -2791,24 +2795,9 @@ void InstanceKlass::remove_java_mirror() { void InstanceKlass::init_shared_package_entry() { assert(CDSConfig::is_dumping_archive(), "must be"); -#if !INCLUDE_CDS_JAVA_HEAP - _package_entry = nullptr; -#else - if (CDSConfig::is_dumping_full_module_graph()) { - if (defined_by_other_loaders()) { - _package_entry = nullptr; - } else { - _package_entry = PackageEntry::get_archived_entry(_package_entry); - } - } else if (CDSConfig::is_dumping_dynamic_archive() && - CDSConfig::is_using_full_module_graph() && - AOTMetaspace::in_aot_cache(_package_entry)) { - // _package_entry is an archived package in the base archive. Leave it as is. - } else { + if (!CDSConfig::is_dumping_full_module_graph() || defined_by_other_loaders()) { _package_entry = nullptr; } - ArchivePtrMarker::mark_pointer((address**)&_package_entry); -#endif } void InstanceKlass::compute_has_loops_flag_for_methods() { diff --git a/src/hotspot/share/oops/instanceKlass.hpp b/src/hotspot/share/oops/instanceKlass.hpp index 23a59d26093..e370a3b7a7c 100644 --- a/src/hotspot/share/oops/instanceKlass.hpp +++ b/src/hotspot/share/oops/instanceKlass.hpp @@ -353,6 +353,9 @@ class InstanceKlass: public Klass { int static_oop_field_count() const { return (int)_static_oop_field_count; } void set_static_oop_field_count(u2 size) { _static_oop_field_count = size; } + bool trust_final_fields() { return _misc_flags.trust_final_fields(); } + void set_trust_final_fields(bool value) { _misc_flags.set_trust_final_fields(value); } + // Java itable int itable_length() const { return _itable_len; } void set_itable_length(int len) { _itable_len = len; } diff --git a/src/hotspot/share/oops/instanceKlassFlags.hpp b/src/hotspot/share/oops/instanceKlassFlags.hpp index 5eebaab27d1..d576805b30f 100644 --- a/src/hotspot/share/oops/instanceKlassFlags.hpp +++ b/src/hotspot/share/oops/instanceKlassFlags.hpp @@ -54,6 +54,7 @@ class InstanceKlassFlags { flag(has_localvariable_table , 1 << 11) /* has localvariable information */ \ flag(has_miranda_methods , 1 << 12) /* True if this class has miranda methods in it's vtable */ \ flag(has_final_method , 1 << 13) /* True if klass has final method */ \ + flag(trust_final_fields , 1 << 14) /* All instance final fields in this class should be trusted */ \ /* end of list */ #define IK_FLAGS_ENUM_NAME(name, value) _misc_##name = value, diff --git a/src/hotspot/share/oops/klass.hpp b/src/hotspot/share/oops/klass.hpp index b6974762209..d59db9744cb 100644 --- a/src/hotspot/share/oops/klass.hpp +++ b/src/hotspot/share/oops/klass.hpp @@ -510,18 +510,20 @@ protected: return (BasicType) btvalue; } - // Want a pattern to quickly diff against layout header in register - // find something less clever! + // Return a value containing a single set bit that is in the bitset difference between the + // layout helpers for array-of-boolean and array-of-byte. static int layout_helper_boolean_diffbit() { - jint zlh = array_layout_helper(T_BOOLEAN); - jint blh = array_layout_helper(T_BYTE); - assert(zlh != blh, "array layout helpers must differ"); - int diffbit = 1; - while ((diffbit & (zlh ^ blh)) == 0 && (diffbit & zlh) == 0) { - diffbit <<= 1; - assert(diffbit != 0, "make sure T_BOOLEAN has a different bit than T_BYTE"); - } - return diffbit; + uint zlh = static_cast(array_layout_helper(T_BOOLEAN)); + uint blh = static_cast(array_layout_helper(T_BYTE)); + // get all the bits that are set in zlh and clear in blh + uint candidates = (zlh & ~blh); + assert(candidates != 0, "must be"); // must be some if there is a solution. + // Use well known bit hack to isolate the low bit of candidates. + uint result = candidates & (-candidates); + assert(is_power_of_2(result), "must be power of 2"); + assert((result & zlh) != 0, "must be set in alh of T_BOOLEAN"); + assert((result & blh) == 0, "must be clear in alh of T_BYTE"); + return static_cast(result); } static int layout_helper_log2_element_size(jint lh) { diff --git a/src/hotspot/share/oops/method.cpp b/src/hotspot/share/oops/method.cpp index 1a2e5f0bee4..949441585d8 100644 --- a/src/hotspot/share/oops/method.cpp +++ b/src/hotspot/share/oops/method.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, 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 @@ -1898,15 +1898,15 @@ void Method::print_name(outputStream* st) const { #endif // !PRODUCT || INCLUDE_JVMTI -void Method::print_codes_on(outputStream* st, int flags) const { - print_codes_on(0, code_size(), st, flags); +void Method::print_codes_on(outputStream* st, int flags, bool buffered) const { + print_codes_on(0, code_size(), st, flags, buffered); } -void Method::print_codes_on(int from, int to, outputStream* st, int flags) const { +void Method::print_codes_on(int from, int to, outputStream* st, int flags, bool buffered) const { Thread *thread = Thread::current(); ResourceMark rm(thread); methodHandle mh (thread, (Method*)this); - BytecodeTracer::print_method_codes(mh, from, to, st, flags); + BytecodeTracer::print_method_codes(mh, from, to, st, flags, buffered); } CompressedLineNumberReadStream::CompressedLineNumberReadStream(u_char* buffer) : CompressedReadStream(buffer) { diff --git a/src/hotspot/share/oops/method.hpp b/src/hotspot/share/oops/method.hpp index 4592cb8a8c0..add8e59b2be 100644 --- a/src/hotspot/share/oops/method.hpp +++ b/src/hotspot/share/oops/method.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, 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 @@ -466,8 +466,8 @@ public: // prints byte codes void print_codes(int flags = 0) const { print_codes_on(tty, flags); } - void print_codes_on(outputStream* st, int flags = 0) const; - void print_codes_on(int from, int to, outputStream* st, int flags = 0) const; + void print_codes_on(outputStream* st, int flags = 0, bool buffered = true) const; + void print_codes_on(int from, int to, outputStream* st, int flags = 0, bool buffered = true) const; // method parameters bool has_method_parameters() const diff --git a/src/hotspot/share/oops/methodData.hpp b/src/hotspot/share/oops/methodData.hpp index f9f36c8ad15..196537359b5 100644 --- a/src/hotspot/share/oops/methodData.hpp +++ b/src/hotspot/share/oops/methodData.hpp @@ -1148,6 +1148,8 @@ public: // is seen. A per ReceiverTypeData counter is incremented on type // overflow (when there's no more room for a not yet profiled Klass*). // +// Updated by platform-specific code, for example MacroAssembler::profile_receiver_type. +// class ReceiverTypeData : public CounterData { friend class VMStructs; friend class JVMCIVMStructs; diff --git a/src/hotspot/share/oops/objArrayKlass.hpp b/src/hotspot/share/oops/objArrayKlass.hpp index 12db56351ed..cdf28b883e6 100644 --- a/src/hotspot/share/oops/objArrayKlass.hpp +++ b/src/hotspot/share/oops/objArrayKlass.hpp @@ -135,17 +135,16 @@ class ObjArrayKlass : public ArrayKlass { template inline void oop_oop_iterate_bounded(oop obj, OopClosureType* closure, MemRegion mr); - // Iterate over oop elements within [start, end), and metadata. - template - inline void oop_oop_iterate_range(objArrayOop a, OopClosureType* closure, int start, int end); - - public: - // Iterate over all oop elements. + // Iterate over all oop elements, and no metadata. template inline void oop_oop_iterate_elements(objArrayOop a, OopClosureType* closure); + // Iterate over oop elements within index range [start, end), and no metadata. + template + inline void oop_oop_iterate_elements_range(objArrayOop a, OopClosureType* closure, int start, int end); + private: - // Iterate over all oop elements with indices within mr. + // Iterate over all oop elements bounded by addresses [low, high), and no metadata. template inline void oop_oop_iterate_elements_bounded(objArrayOop a, OopClosureType* closure, void* low, void* high); diff --git a/src/hotspot/share/oops/objArrayKlass.inline.hpp b/src/hotspot/share/oops/objArrayKlass.inline.hpp index a92c42d21a8..ee559548529 100644 --- a/src/hotspot/share/oops/objArrayKlass.inline.hpp +++ b/src/hotspot/share/oops/objArrayKlass.inline.hpp @@ -38,10 +38,18 @@ template void ObjArrayKlass::oop_oop_iterate_elements(objArrayOop a, OopClosureType* closure) { - T* p = (T*)a->base(); - T* const end = p + a->length(); + oop_oop_iterate_elements_range(a, closure, 0, a->length()); +} - for (;p < end; p++) { +// Like oop_oop_iterate but only iterates over a specified range and only used +// for objArrayOops. +template +void ObjArrayKlass::oop_oop_iterate_elements_range(objArrayOop a, OopClosureType* closure, int start, int end) { + T* base = (T*)a->base(); + T* p = base + start; + T* const end_p = base + end; + + for (;p < end_p; ++p) { Devirtualizer::do_oop(closure, p); } } @@ -98,24 +106,4 @@ void ObjArrayKlass::oop_oop_iterate_bounded(oop obj, OopClosureType* closure, Me oop_oop_iterate_elements_bounded(a, closure, mr.start(), mr.end()); } -// Like oop_oop_iterate but only iterates over a specified range and only used -// for objArrayOops. -template -void ObjArrayKlass::oop_oop_iterate_range(objArrayOop a, OopClosureType* closure, int start, int end) { - T* low = (T*)a->base() + start; - T* high = (T*)a->base() + end; - - oop_oop_iterate_elements_bounded(a, closure, low, high); -} - -// Placed here to resolve include cycle between objArrayKlass.inline.hpp and objArrayOop.inline.hpp -template -void objArrayOopDesc::oop_iterate_range(OopClosureType* blk, int start, int end) { - if (UseCompressedOops) { - ((ObjArrayKlass*)klass())->oop_oop_iterate_range(this, blk, start, end); - } else { - ((ObjArrayKlass*)klass())->oop_oop_iterate_range(this, blk, start, end); - } -} - #endif // SHARE_OOPS_OBJARRAYKLASS_INLINE_HPP diff --git a/src/hotspot/share/oops/objArrayOop.hpp b/src/hotspot/share/oops/objArrayOop.hpp index 82bb41b5fd1..0af059efccf 100644 --- a/src/hotspot/share/oops/objArrayOop.hpp +++ b/src/hotspot/share/oops/objArrayOop.hpp @@ -83,9 +83,9 @@ class objArrayOopDesc : public arrayOopDesc { Klass* element_klass(); public: - // special iterators for index ranges, returns size of object + // Special iterators for an element index range. template - void oop_iterate_range(OopClosureType* blk, int start, int end); + void oop_iterate_elements_range(OopClosureType* blk, int start, int end); }; // See similar requirement for oopDesc. diff --git a/src/hotspot/share/oops/objArrayOop.inline.hpp b/src/hotspot/share/oops/objArrayOop.inline.hpp index 21f95b756de..63295a1459d 100644 --- a/src/hotspot/share/oops/objArrayOop.inline.hpp +++ b/src/hotspot/share/oops/objArrayOop.inline.hpp @@ -29,6 +29,7 @@ #include "oops/access.hpp" #include "oops/arrayOop.hpp" +#include "oops/objArrayKlass.inline.hpp" #include "oops/oop.inline.hpp" #include "runtime/globals.hpp" @@ -51,4 +52,13 @@ inline void objArrayOopDesc::obj_at_put(int index, oop value) { HeapAccess::oop_store_at(as_oop(), offset, value); } +template +void objArrayOopDesc::oop_iterate_elements_range(OopClosureType* blk, int start, int end) { + if (UseCompressedOops) { + ((ObjArrayKlass*)klass())->oop_oop_iterate_elements_range(this, blk, start, end); + } else { + ((ObjArrayKlass*)klass())->oop_oop_iterate_elements_range(this, blk, start, end); + } +} + #endif // SHARE_OOPS_OBJARRAYOOP_INLINE_HPP diff --git a/src/hotspot/share/opto/addnode.cpp b/src/hotspot/share/opto/addnode.cpp index 40cd6337c17..e04da430ef0 100644 --- a/src/hotspot/share/opto/addnode.cpp +++ b/src/hotspot/share/opto/addnode.cpp @@ -1195,7 +1195,7 @@ const Type* XorLNode::Value(PhaseGVN* phase) const { return AddNode::Value(phase); } -Node* MaxNode::build_min_max_int(Node* a, Node* b, bool is_max) { +Node* MinMaxNode::build_min_max_int(Node* a, Node* b, bool is_max) { if (is_max) { return new MaxINode(a, b); } else { @@ -1203,7 +1203,7 @@ Node* MaxNode::build_min_max_int(Node* a, Node* b, bool is_max) { } } -Node* MaxNode::build_min_max_long(PhaseGVN* phase, Node* a, Node* b, bool is_max) { +Node* MinMaxNode::build_min_max_long(PhaseGVN* phase, Node* a, Node* b, bool is_max) { if (is_max) { return new MaxLNode(phase->C, a, b); } else { @@ -1211,7 +1211,7 @@ Node* MaxNode::build_min_max_long(PhaseGVN* phase, Node* a, Node* b, bool is_max } } -Node* MaxNode::build_min_max(Node* a, Node* b, bool is_max, bool is_unsigned, const Type* t, PhaseGVN& gvn) { +Node* MinMaxNode::build_min_max(Node* a, Node* b, bool is_max, bool is_unsigned, const Type* t, PhaseGVN& gvn) { bool is_int = gvn.type(a)->isa_int(); assert(is_int || gvn.type(a)->isa_long(), "int or long inputs"); assert(is_int == (gvn.type(b)->isa_int() != nullptr), "inconsistent inputs"); @@ -1243,7 +1243,7 @@ Node* MaxNode::build_min_max(Node* a, Node* b, bool is_max, bool is_unsigned, co return res; } -Node* MaxNode::build_min_max_diff_with_zero(Node* a, Node* b, bool is_max, const Type* t, PhaseGVN& gvn) { +Node* MinMaxNode::build_min_max_diff_with_zero(Node* a, Node* b, bool is_max, const Type* t, PhaseGVN& gvn) { bool is_int = gvn.type(a)->isa_int(); assert(is_int || gvn.type(a)->isa_long(), "int or long inputs"); assert(is_int == (gvn.type(b)->isa_int() != nullptr), "inconsistent inputs"); @@ -1290,7 +1290,7 @@ static bool can_overflow(const TypeLong* t, jlong c) { // Let = x_operands and = y_operands. // If x == y and neither add(x, x_off) nor add(y, y_off) overflow, return // add(x, op(x_off, y_off)). Otherwise, return nullptr. -Node* MaxNode::extract_add(PhaseGVN* phase, ConstAddOperands x_operands, ConstAddOperands y_operands) { +Node* MinMaxNode::extract_add(PhaseGVN* phase, ConstAddOperands x_operands, ConstAddOperands y_operands) { Node* x = x_operands.first; Node* y = y_operands.first; int opcode = Opcode(); @@ -1327,7 +1327,7 @@ static ConstAddOperands as_add_with_constant(Node* n) { return ConstAddOperands(x, c_type->is_int()->get_con()); } -Node* MaxNode::IdealI(PhaseGVN* phase, bool can_reshape) { +Node* MinMaxNode::IdealI(PhaseGVN* phase, bool can_reshape) { Node* n = AddNode::Ideal(phase, can_reshape); if (n != nullptr) { return n; @@ -1401,7 +1401,7 @@ Node* MaxINode::Identity(PhaseGVN* phase) { return in(2); } - return MaxNode::Identity(phase); + return MinMaxNode::Identity(phase); } //============================================================================= @@ -1434,7 +1434,7 @@ Node* MinINode::Identity(PhaseGVN* phase) { return in(1); } - return MaxNode::Identity(phase); + return MinMaxNode::Identity(phase); } //------------------------------add_ring--------------------------------------- @@ -1564,7 +1564,7 @@ Node* MaxLNode::Identity(PhaseGVN* phase) { return in(2); } - return MaxNode::Identity(phase); + return MinMaxNode::Identity(phase); } Node* MaxLNode::Ideal(PhaseGVN* phase, bool can_reshape) { @@ -1596,7 +1596,7 @@ Node* MinLNode::Identity(PhaseGVN* phase) { return in(1); } - return MaxNode::Identity(phase); + return MinMaxNode::Identity(phase); } Node* MinLNode::Ideal(PhaseGVN* phase, bool can_reshape) { @@ -1610,7 +1610,7 @@ Node* MinLNode::Ideal(PhaseGVN* phase, bool can_reshape) { return nullptr; } -int MaxNode::opposite_opcode() const { +int MinMaxNode::opposite_opcode() const { if (Opcode() == max_opcode()) { return min_opcode(); } else { @@ -1621,7 +1621,7 @@ int MaxNode::opposite_opcode() const { // Given a redundant structure such as Max/Min(A, Max/Min(B, C)) where A == B or A == C, return the useful part of the structure. // 'operation' is the node expected to be the inner 'Max/Min(B, C)', and 'operand' is the node expected to be the 'A' operand of the outer node. -Node* MaxNode::find_identity_operation(Node* operation, Node* operand) { +Node* MinMaxNode::find_identity_operation(Node* operation, Node* operand) { if (operation->Opcode() == Opcode() || operation->Opcode() == opposite_opcode()) { Node* n1 = operation->in(1); Node* n2 = operation->in(2); @@ -1645,17 +1645,17 @@ Node* MaxNode::find_identity_operation(Node* operation, Node* operand) { return nullptr; } -Node* MaxNode::Identity(PhaseGVN* phase) { +Node* MinMaxNode::Identity(PhaseGVN* phase) { if (in(1) == in(2)) { return in(1); } - Node* identity_1 = MaxNode::find_identity_operation(in(2), in(1)); + Node* identity_1 = MinMaxNode::find_identity_operation(in(2), in(1)); if (identity_1 != nullptr) { return identity_1; } - Node* identity_2 = MaxNode::find_identity_operation(in(1), in(2)); + Node* identity_2 = MinMaxNode::find_identity_operation(in(1), in(2)); if (identity_2 != nullptr) { return identity_2; } diff --git a/src/hotspot/share/opto/addnode.hpp b/src/hotspot/share/opto/addnode.hpp index 1bbdae92e48..6128de00efb 100644 --- a/src/hotspot/share/opto/addnode.hpp +++ b/src/hotspot/share/opto/addnode.hpp @@ -226,8 +226,11 @@ public: Base, // Base oop, for GC purposes Address, // Actually address, derived from base Offset } ; // Offset added to address - AddPNode( Node *base, Node *ptr, Node *off ) : Node(nullptr,base,ptr,off) { + AddPNode(Node *base, Node *ptr, Node *off) : Node(nullptr,base,ptr,off) { init_class_id(Class_AddP); + assert((ptr->bottom_type() == Type::TOP) || + ((base == Compile::current()->top()) == (ptr->bottom_type()->make_ptr()->isa_oopptr() == nullptr)), + "base input only needed for heap addresses"); } virtual int Opcode() const; virtual Node* Identity(PhaseGVN* phase); @@ -324,14 +327,16 @@ public: //------------------------------MaxNode---------------------------------------- // Max (or min) of 2 values. Included with the ADD nodes because it inherits // all the behavior of addition on a ring. -class MaxNode : public AddNode { +class MinMaxNode : public AddNode { private: static Node* build_min_max(Node* a, Node* b, bool is_max, bool is_unsigned, const Type* t, PhaseGVN& gvn); static Node* build_min_max_diff_with_zero(Node* a, Node* b, bool is_max, const Type* t, PhaseGVN& gvn); Node* extract_add(PhaseGVN* phase, ConstAddOperands x_operands, ConstAddOperands y_operands); public: - MaxNode( Node *in1, Node *in2 ) : AddNode(in1,in2) {} + MinMaxNode(Node* in1, Node* in2) : AddNode(in1, in2) { + init_class_id(Class_MinMax); + } virtual int Opcode() const = 0; virtual int max_opcode() const = 0; virtual int min_opcode() const = 0; @@ -373,9 +378,9 @@ public: //------------------------------MaxINode--------------------------------------- // Maximum of 2 integers. Included with the ADD nodes because it inherits // all the behavior of addition on a ring. -class MaxINode : public MaxNode { +class MaxINode : public MinMaxNode { public: - MaxINode( Node *in1, Node *in2 ) : MaxNode(in1,in2) {} + MaxINode(Node* in1, Node* in2) : MinMaxNode(in1, in2) {} virtual int Opcode() const; virtual const Type *add_ring( const Type *, const Type * ) const; virtual const Type *add_id() const { return TypeInt::make(min_jint); } @@ -390,9 +395,9 @@ public: //------------------------------MinINode--------------------------------------- // MINimum of 2 integers. Included with the ADD nodes because it inherits // all the behavior of addition on a ring. -class MinINode : public MaxNode { +class MinINode : public MinMaxNode { public: - MinINode( Node *in1, Node *in2 ) : MaxNode(in1,in2) {} + MinINode(Node* in1, Node* in2) : MinMaxNode(in1, in2) {} virtual int Opcode() const; virtual const Type *add_ring( const Type *, const Type * ) const; virtual const Type *add_id() const { return TypeInt::make(max_jint); } @@ -406,9 +411,9 @@ public: //------------------------------MaxLNode--------------------------------------- // MAXimum of 2 longs. -class MaxLNode : public MaxNode { +class MaxLNode : public MinMaxNode { public: - MaxLNode(Compile* C, Node* in1, Node* in2) : MaxNode(in1, in2) { + MaxLNode(Compile* C, Node* in1, Node* in2) : MinMaxNode(in1, in2) { init_flags(Flag_is_macro); C->add_macro_node(this); } @@ -425,9 +430,9 @@ public: //------------------------------MinLNode--------------------------------------- // MINimum of 2 longs. -class MinLNode : public MaxNode { +class MinLNode : public MinMaxNode { public: - MinLNode(Compile* C, Node* in1, Node* in2) : MaxNode(in1, in2) { + MinLNode(Compile* C, Node* in1, Node* in2) : MinMaxNode(in1, in2) { init_flags(Flag_is_macro); C->add_macro_node(this); } @@ -444,9 +449,9 @@ public: //------------------------------MaxFNode--------------------------------------- // Maximum of 2 floats. -class MaxFNode : public MaxNode { +class MaxFNode : public MinMaxNode { public: - MaxFNode(Node *in1, Node *in2) : MaxNode(in1, in2) {} + MaxFNode(Node* in1, Node* in2) : MinMaxNode(in1, in2) {} virtual int Opcode() const; virtual const Type *add_ring(const Type*, const Type*) const; virtual const Type *add_id() const { return TypeF::NEG_INF; } @@ -458,9 +463,9 @@ public: //------------------------------MinFNode--------------------------------------- // Minimum of 2 floats. -class MinFNode : public MaxNode { +class MinFNode : public MinMaxNode { public: - MinFNode(Node *in1, Node *in2) : MaxNode(in1, in2) {} + MinFNode(Node* in1, Node* in2) : MinMaxNode(in1, in2) {} virtual int Opcode() const; virtual const Type *add_ring(const Type*, const Type*) const; virtual const Type *add_id() const { return TypeF::POS_INF; } @@ -472,9 +477,9 @@ public: //------------------------------MaxHFNode-------------------------------------- // Maximum of 2 half floats. -class MaxHFNode : public MaxNode { +class MaxHFNode : public MinMaxNode { public: - MaxHFNode(Node* in1, Node* in2) : MaxNode(in1, in2) {} + MaxHFNode(Node* in1, Node* in2) : MinMaxNode(in1, in2) {} virtual int Opcode() const; virtual const Type* add_ring(const Type*, const Type*) const; virtual const Type* add_id() const { return TypeH::NEG_INF; } @@ -486,9 +491,9 @@ public: //------------------------------MinHFNode--------------------------------------- // Minimum of 2 half floats. -class MinHFNode : public MaxNode { +class MinHFNode : public MinMaxNode { public: - MinHFNode(Node* in1, Node* in2) : MaxNode(in1, in2) {} + MinHFNode(Node* in1, Node* in2) : MinMaxNode(in1, in2) {} virtual int Opcode() const; virtual const Type* add_ring(const Type*, const Type*) const; virtual const Type* add_id() const { return TypeH::POS_INF; } @@ -500,9 +505,9 @@ public: //------------------------------MaxDNode--------------------------------------- // Maximum of 2 doubles. -class MaxDNode : public MaxNode { +class MaxDNode : public MinMaxNode { public: - MaxDNode(Node *in1, Node *in2) : MaxNode(in1, in2) {} + MaxDNode(Node* in1, Node* in2) : MinMaxNode(in1, in2) {} virtual int Opcode() const; virtual const Type *add_ring(const Type*, const Type*) const; virtual const Type *add_id() const { return TypeD::NEG_INF; } @@ -514,9 +519,9 @@ public: //------------------------------MinDNode--------------------------------------- // Minimum of 2 doubles. -class MinDNode : public MaxNode { +class MinDNode : public MinMaxNode { public: - MinDNode(Node *in1, Node *in2) : MaxNode(in1, in2) {} + MinDNode(Node* in1, Node* in2) : MinMaxNode(in1, in2) {} virtual int Opcode() const; virtual const Type *add_ring(const Type*, const Type*) const; virtual const Type *add_id() const { return TypeD::POS_INF; } diff --git a/src/hotspot/share/opto/c2_globals.hpp b/src/hotspot/share/opto/c2_globals.hpp index 7fa3ca638c2..f470049bf7e 100644 --- a/src/hotspot/share/opto/c2_globals.hpp +++ b/src/hotspot/share/opto/c2_globals.hpp @@ -675,9 +675,6 @@ product(bool, PrintIntrinsics, false, DIAGNOSTIC, \ "prints attempted and successful inlining of intrinsics") \ \ - develop(bool, VerifyIntrinsicChecks, false, \ - "Verify in intrinsic that Java level checks work as expected") \ - \ develop(bool, StressReflectiveCode, false, \ "Use inexact types at allocations, etc., to test reflection") \ \ diff --git a/src/hotspot/share/opto/callGenerator.cpp b/src/hotspot/share/opto/callGenerator.cpp index db03dd9d80c..1465da02ac8 100644 --- a/src/hotspot/share/opto/callGenerator.cpp +++ b/src/hotspot/share/opto/callGenerator.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, 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 @@ -418,7 +418,8 @@ bool LateInlineMHCallGenerator::do_late_inline_check(Compile* C, JVMState* jvms) C->inline_printer()->record(cg->method(), call_node()->jvms(), InliningResult::FAILURE, "late method handle call resolution"); } - assert(!cg->is_late_inline() || cg->is_mh_late_inline() || AlwaysIncrementalInline || StressIncrementalInlining, "we're doing late inlining"); + assert(!cg->is_late_inline() || cg->is_mh_late_inline() || cg->is_virtual_late_inline() || + AlwaysIncrementalInline || StressIncrementalInlining, "we're doing late inlining"); _inline_cg = cg; return true; } else { diff --git a/src/hotspot/share/opto/callnode.cpp b/src/hotspot/share/opto/callnode.cpp index 4cf72fbde4b..9b3d7b38d15 100644 --- a/src/hotspot/share/opto/callnode.cpp +++ b/src/hotspot/share/opto/callnode.cpp @@ -1031,6 +1031,15 @@ bool CallNode::is_call_to_arraycopystub() const { return false; } +bool CallNode::is_call_to_multianewarray_stub() const { + if (_name != nullptr && + strstr(_name, "multianewarray") != nullptr && + strstr(_name, "C2 runtime") != nullptr) { + return true; + } + return false; +} + //============================================================================= uint CallJavaNode::size_of() const { return sizeof(*this); } bool CallJavaNode::cmp( const Node &n ) const { @@ -1728,11 +1737,11 @@ void AllocateNode::compute_MemBar_redundancy(ciMethod* initializer) _is_allocation_MemBar_redundant = true; } } -Node *AllocateNode::make_ideal_mark(PhaseGVN *phase, Node* obj, Node* control, Node* mem) { +Node *AllocateNode::make_ideal_mark(PhaseGVN* phase, Node* control, Node* mem) { Node* mark_node = nullptr; if (UseCompactObjectHeaders) { Node* klass_node = in(AllocateNode::KlassNode); - Node* proto_adr = phase->transform(new AddPNode(klass_node, klass_node, phase->MakeConX(in_bytes(Klass::prototype_header_offset())))); + Node* proto_adr = phase->transform(new AddPNode(phase->C->top(), klass_node, phase->MakeConX(in_bytes(Klass::prototype_header_offset())))); mark_node = LoadNode::make(*phase, control, mem, proto_adr, TypeRawPtr::BOTTOM, TypeX_X, TypeX_X->basic_type(), MemNode::unordered); } else { // For now only enable fast locking for non-array types diff --git a/src/hotspot/share/opto/callnode.hpp b/src/hotspot/share/opto/callnode.hpp index de4b8941359..41d0c9f5aac 100644 --- a/src/hotspot/share/opto/callnode.hpp +++ b/src/hotspot/share/opto/callnode.hpp @@ -758,6 +758,7 @@ public: virtual uint match_edge(uint idx) const; bool is_call_to_arraycopystub() const; + bool is_call_to_multianewarray_stub() const; virtual void copy_call_debug_info(PhaseIterGVN* phase, SafePointNode* sfpt) {} @@ -1100,7 +1101,7 @@ public: void compute_MemBar_redundancy(ciMethod* initializer); bool is_allocation_MemBar_redundant() { return _is_allocation_MemBar_redundant; } - Node* make_ideal_mark(PhaseGVN *phase, Node* obj, Node* control, Node* mem); + Node* make_ideal_mark(PhaseGVN* phase, Node* control, Node* mem); NOT_PRODUCT(virtual void dump_spec(outputStream* st) const;) }; diff --git a/src/hotspot/share/opto/castnode.cpp b/src/hotspot/share/opto/castnode.cpp index 998b6a79903..2ebbdd7cdb3 100644 --- a/src/hotspot/share/opto/castnode.cpp +++ b/src/hotspot/share/opto/castnode.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2026, 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 @@ -392,8 +392,8 @@ Node* CastLLNode::Ideal(PhaseGVN* phase, bool can_reshape) { if (t != Type::TOP && t_in != Type::TOP) { const TypeLong* tl = t->is_long(); const TypeLong* t_in_l = t_in->is_long(); - assert(tl->_lo >= t_in_l->_lo && tl->_hi <= t_in_l->_hi, "CastLL type should be narrower than or equal to the type of its input"); - assert((tl != t_in_l) == (tl->_lo > t_in_l->_lo || tl->_hi < t_in_l->_hi), "if type differs then this nodes's type must be narrower"); + assert(t_in_l->contains(tl), "CastLL type should be narrower than or equal to the type of its input"); + assert((tl != t_in_l) == t_in_l->strictly_contains(tl), "if type differs then this nodes's type must be narrower"); if (tl != t_in_l) { const TypeInt* ti = TypeInt::make(checked_cast(tl->_lo), checked_cast(tl->_hi), tl->_widen); Node* castii = phase->transform(new CastIINode(in(0), in1->in(1), ti)); diff --git a/src/hotspot/share/opto/compile.cpp b/src/hotspot/share/opto/compile.cpp index cea2b09e142..c4c9445b61a 100644 --- a/src/hotspot/share/opto/compile.cpp +++ b/src/hotspot/share/opto/compile.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, 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 @@ -581,7 +581,7 @@ void Compile::print_phase(const char* phase_name) { tty->print_cr("%u.\t%s", ++_phase_counter, phase_name); } -void Compile::print_ideal_ir(const char* phase_name) { +void Compile::print_ideal_ir(const char* compile_phase_name) const { // keep the following output all in one block // This output goes directly to the tty, not the compiler log. // To enable tools to match it up with the compilation activity, @@ -593,7 +593,7 @@ void Compile::print_ideal_ir(const char* phase_name) { stringStream ss; if (_output == nullptr) { - ss.print_cr("AFTER: %s", phase_name); + ss.print_cr("AFTER: %s", compile_phase_name); // Print out all nodes in ascending order of index. // It is important that we traverse both inputs and outputs of nodes, // so that we reach all nodes that are connected to Root. @@ -610,7 +610,7 @@ void Compile::print_ideal_ir(const char* phase_name) { xtty->head("ideal compile_id='%d'%s compile_phase='%s'", compile_id(), is_osr_compilation() ? " compile_kind='osr'" : "", - phase_name); + compile_phase_name); } tty->print("%s", ss.as_string()); @@ -671,7 +671,7 @@ Compile::Compile(ciEnv* ci_env, ciMethod* target, int osr_bci, _coarsened_locks(comp_arena(), 8, 0, nullptr), _congraph(nullptr), NOT_PRODUCT(_igv_printer(nullptr) COMMA) - _unique(0), + _unique(0), _dead_node_count(0), _dead_node_list(comp_arena()), _node_arena_one(mtCompiler, Arena::Tag::tag_node), @@ -865,7 +865,7 @@ Compile::Compile(ciEnv* ci_env, ciMethod* target, int osr_bci, #ifndef PRODUCT if (should_print_ideal()) { - print_ideal_ir("print_ideal"); + print_ideal_ir("PrintIdeal"); } #endif @@ -938,7 +938,7 @@ Compile::Compile(ciEnv* ci_env, _for_merge_stores_igvn(comp_arena(), 8, 0, nullptr), _congraph(nullptr), NOT_PRODUCT(_igv_printer(nullptr) COMMA) - _unique(0), + _unique(0), _dead_node_count(0), _dead_node_list(comp_arena()), _node_arena_one(mtCompiler, Arena::Tag::tag_node), @@ -1876,7 +1876,7 @@ void Compile::process_for_post_loop_opts_igvn(PhaseIterGVN& igvn) { // at least to this point, even if no loop optimizations were done. PhaseIdealLoop::verify(igvn); - if (has_loops() || _loop_opts_cnt > 0) { + if (_print_phase_loop_opts) { print_method(PHASE_AFTER_LOOP_OPTS, 2); } C->set_post_loop_opts_phase(); // no more loop opts allowed @@ -2098,6 +2098,7 @@ void Compile::inline_boxing_calls(PhaseIterGVN& igvn) { bool Compile::inline_incrementally_one() { assert(IncrementalInline, "incremental inlining should be on"); + assert(_late_inlines.length() > 0, "should have been checked by caller"); TracePhase tp(_t_incrInline_inline); @@ -2209,6 +2210,10 @@ void Compile::inline_incrementally(PhaseIterGVN& igvn) { igvn_worklist()->ensure_empty(); // should be done with igvn + if (_late_inlines.length() == 0) { + break; // no more progress + } + while (inline_incrementally_one()) { assert(!failing_internal() || failure_is_artificial(), "inconsistent"); } @@ -2219,10 +2224,6 @@ void Compile::inline_incrementally(PhaseIterGVN& igvn) { print_method(PHASE_INCREMENTAL_INLINE_STEP, 3); if (failing()) return; - - if (_late_inlines.length() == 0) { - break; // no more progress - } } igvn_worklist()->ensure_empty(); // should be done with igvn @@ -2404,7 +2405,8 @@ void Compile::Optimize() { if (failing()) return; - if (has_loops()) { + _print_phase_loop_opts = has_loops(); + if (_print_phase_loop_opts) { print_method(PHASE_BEFORE_LOOP_OPTS, 2); } @@ -5165,17 +5167,17 @@ void Compile::sort_macro_nodes() { } } -void Compile::print_method(CompilerPhaseType cpt, int level, Node* n) { +void Compile::print_method(CompilerPhaseType compile_phase, int level, Node* n) { if (failing_internal()) { return; } // failing_internal to not stress bailouts from printing code. EventCompilerPhase event(UNTIMED); if (event.should_commit()) { - CompilerEvent::PhaseEvent::post(event, C->_latest_stage_start_counter, cpt, C->_compile_id, level); + CompilerEvent::PhaseEvent::post(event, C->_latest_stage_start_counter, compile_phase, C->_compile_id, level); } #ifndef PRODUCT ResourceMark rm; stringStream ss; - ss.print_raw(CompilerPhaseTypeHelper::to_description(cpt)); - int iter = ++_igv_phase_iter[cpt]; + ss.print_raw(CompilerPhaseTypeHelper::to_description(compile_phase)); + int iter = ++_igv_phase_iter[compile_phase]; if (iter > 1) { ss.print(" %d", iter); } @@ -5203,8 +5205,8 @@ void Compile::print_method(CompilerPhaseType cpt, int level, Node* n) { if (should_print_phase(level)) { print_phase(name); } - if (should_print_ideal_phase(cpt)) { - print_ideal_ir(CompilerPhaseTypeHelper::to_name(cpt)); + if (should_print_ideal_phase(compile_phase)) { + print_ideal_ir(CompilerPhaseTypeHelper::to_name(compile_phase)); } #endif C->_latest_stage_start_counter.stamp(); diff --git a/src/hotspot/share/opto/compile.hpp b/src/hotspot/share/opto/compile.hpp index 45a3a4f548f..d9a8ea15724 100644 --- a/src/hotspot/share/opto/compile.hpp +++ b/src/hotspot/share/opto/compile.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, 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 @@ -355,6 +355,7 @@ class Compile : public Phase { bool _print_assembly; // True if we should dump assembly code for this compilation bool _print_inlining; // True if we should print inlining for this compilation bool _print_intrinsics; // True if we should print intrinsics for this compilation + bool _print_phase_loop_opts; // True if we should print before and after loop opts phase #ifndef PRODUCT uint _phase_counter; // Counter for the number of already printed phases uint _igv_idx; // Counter for IGV node identifiers @@ -666,7 +667,7 @@ public: uint next_igv_idx() { return _igv_idx++; } bool trace_opto_output() const { return _trace_opto_output; } void print_phase(const char* phase_name); - void print_ideal_ir(const char* phase_name); + void print_ideal_ir(const char* compile_phase_name) const; bool should_print_ideal() const { return _directive->PrintIdealOption; } bool parsed_irreducible_loop() const { return _parsed_irreducible_loop; } void set_parsed_irreducible_loop(bool z) { _parsed_irreducible_loop = z; } @@ -680,7 +681,7 @@ public: void begin_method(); void end_method(); - void print_method(CompilerPhaseType cpt, int level, Node* n = nullptr); + void print_method(CompilerPhaseType compile_phase, int level, Node* n = nullptr); #ifndef PRODUCT bool should_print_igv(int level); diff --git a/src/hotspot/share/opto/divnode.cpp b/src/hotspot/share/opto/divnode.cpp index db4fedbba3b..ed72d8a11cf 100644 --- a/src/hotspot/share/opto/divnode.cpp +++ b/src/hotspot/share/opto/divnode.cpp @@ -1112,8 +1112,6 @@ Node *ModINode::Ideal(PhaseGVN *phase, bool can_reshape) { if( !ti->is_con() ) return nullptr; jint con = ti->get_con(); - Node *hook = new Node(1); - // First, special check for modulo 2^k-1 if( con >= 0 && con < max_jint && is_power_of_2(con+1) ) { uint k = exact_log2(con+1); // Extract k @@ -1129,7 +1127,9 @@ Node *ModINode::Ideal(PhaseGVN *phase, bool can_reshape) { Node *x = in(1); // Value being mod'd Node *divisor = in(2); // Also is mask - hook->init_req(0, x); // Add a use to x to prevent him from dying + // Add a use to x to prevent it from dying + Node* hook = new Node(1); + hook->init_req(0, x); // Generate code to reduce X rapidly to nearly 2^k-1. for( int i = 0; i < trip_count; i++ ) { Node *xl = phase->transform( new AndINode(x,divisor) ); @@ -1185,6 +1185,7 @@ Node *ModINode::Ideal(PhaseGVN *phase, bool can_reshape) { } // Save in(1) so that it cannot be changed or deleted + Node* hook = new Node(1); hook->init_req(0, in(1)); // Divide using the transform from DivI to MulL @@ -1407,8 +1408,6 @@ Node *ModLNode::Ideal(PhaseGVN *phase, bool can_reshape) { if( !tl->is_con() ) return nullptr; jlong con = tl->get_con(); - Node *hook = new Node(1); - // Expand mod if(con >= 0 && con < max_jlong && is_power_of_2(con + 1)) { uint k = log2i_exact(con + 1); // Extract k @@ -1426,13 +1425,15 @@ Node *ModLNode::Ideal(PhaseGVN *phase, bool can_reshape) { Node *x = in(1); // Value being mod'd Node *divisor = in(2); // Also is mask - hook->init_req(0, x); // Add a use to x to prevent him from dying + // Add a use to x to prevent it from dying + Node* hook = new Node(1); + hook->init_req(0, x); // Generate code to reduce X rapidly to nearly 2^k-1. for( int i = 0; i < trip_count; i++ ) { Node *xl = phase->transform( new AndLNode(x,divisor) ); Node *xh = phase->transform( new RShiftLNode(x,phase->intcon(k)) ); // Must be signed x = phase->transform( new AddLNode(xh,xl) ); - hook->set_req(0, x); // Add a use to x to prevent him from dying + hook->set_req(0, x); // Add a use to x to prevent it from dying } // Generate sign-fixup code. Was original value positive? @@ -1482,6 +1483,8 @@ Node *ModLNode::Ideal(PhaseGVN *phase, bool can_reshape) { } // Save in(1) so that it cannot be changed or deleted + // Add a use to x to prevent him from dying + Node* hook = new Node(1); hook->init_req(0, in(1)); // Divide using the transform from DivL to MulL diff --git a/src/hotspot/share/opto/escape.cpp b/src/hotspot/share/opto/escape.cpp index b067d93176a..b46a12fcf89 100644 --- a/src/hotspot/share/opto/escape.cpp +++ b/src/hotspot/share/opto/escape.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, 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 @@ -1058,6 +1058,39 @@ void ConnectionGraph::updates_after_load_split(Node* data_phi, Node* previous_lo // "new_load" might actually be a constant, parameter, etc. if (new_load->is_Load()) { Node* new_addp = new_load->in(MemNode::Address); + + // If new_load is a Load but not from an AddP, it means that the load is folded into another + // load. And since this load is not from a field, we cannot create a unique type for it. + // For example: + // + // if (b) { + // Holder h1 = new Holder(); + // Object o = ...; + // h.o = o.getClass(); + // } else { + // Holder h2 = ...; + // } + // Holder h = Phi(h1, h2); + // Object r = h.o; + // + // Then, splitting r through the merge point results in: + // + // if (b) { + // Holder h1 = new Holder(); + // Object o = ...; + // h.o = o.getClass(); + // Object o1 = h.o; + // } else { + // Holder h2 = ...; + // Object o2 = h2.o; + // } + // Object r = Phi(o1, o2); + // + // In this case, o1 is folded to o.getClass() which is a Load but not from an AddP, but from + // an OopHandle that is loaded from the Klass of o. + if (!new_addp->is_AddP()) { + continue; + } Node* base = get_addp_base(new_addp); // The base might not be something that we can create an unique @@ -2066,8 +2099,7 @@ void ConnectionGraph::add_call_node(CallNode* call) { // Use bytecode estimator to record whether the call's return value escapes. ciMethod* meth = call->as_CallJava()->method(); if (meth == nullptr) { - const char* name = call->as_CallStaticJava()->_name; - assert(strncmp(name, "C2 Runtime multianewarray", 25) == 0, "TODO: add failed case check"); + assert(call->as_CallStaticJava()->is_call_to_multianewarray_stub(), "TODO: add failed case check"); // Returns a newly allocated non-escaped object. add_java_object(call, PointsToNode::NoEscape); set_not_scalar_replaceable(ptnode_adr(call_idx) NOT_PRODUCT(COMMA "is result of multinewarray")); @@ -2775,8 +2807,7 @@ int ConnectionGraph::find_init_values_phantom(JavaObjectNode* pta) { assert(pta->arraycopy_dst() || alloc->as_CallStaticJava(), "sanity"); #ifdef ASSERT if (!pta->arraycopy_dst() && alloc->as_CallStaticJava()->method() == nullptr) { - const char* name = alloc->as_CallStaticJava()->_name; - assert(strncmp(name, "C2 Runtime multianewarray", 25) == 0, "sanity"); + assert(alloc->as_CallStaticJava()->is_call_to_multianewarray_stub(), "sanity"); } #endif // Non-escaped allocation returned from Java or runtime call have unknown values in fields. @@ -3480,10 +3511,7 @@ bool ConnectionGraph::is_oop_field(Node* n, int offset, bool* unsafe) { bt = field->layout_type(); } else { // Check for unsafe oop field access - if (n->has_out_with(Op_StoreP, Op_LoadP, Op_StoreN, Op_LoadN) || - n->has_out_with(Op_GetAndSetP, Op_GetAndSetN, Op_CompareAndExchangeP, Op_CompareAndExchangeN) || - n->has_out_with(Op_CompareAndSwapP, Op_CompareAndSwapN, Op_WeakCompareAndSwapP, Op_WeakCompareAndSwapN) || - BarrierSet::barrier_set()->barrier_set_c2()->escape_has_out_with_unsafe_object(n)) { + if (has_oop_node_outs(n)) { bt = T_OBJECT; (*unsafe) = true; } @@ -3499,16 +3527,22 @@ bool ConnectionGraph::is_oop_field(Node* n, int offset, bool* unsafe) { } } else if (adr_type->isa_rawptr() || adr_type->isa_klassptr()) { // Allocation initialization, ThreadLocal field access, unsafe access - if (n->has_out_with(Op_StoreP, Op_LoadP, Op_StoreN, Op_LoadN) || - n->has_out_with(Op_GetAndSetP, Op_GetAndSetN, Op_CompareAndExchangeP, Op_CompareAndExchangeN) || - n->has_out_with(Op_CompareAndSwapP, Op_CompareAndSwapN, Op_WeakCompareAndSwapP, Op_WeakCompareAndSwapN) || - BarrierSet::barrier_set()->barrier_set_c2()->escape_has_out_with_unsafe_object(n)) { + if (has_oop_node_outs(n)) { bt = T_OBJECT; } } } // Note: T_NARROWOOP is not classed as a real reference type - return (is_reference_type(bt) || bt == T_NARROWOOP); + bool res = (is_reference_type(bt) || bt == T_NARROWOOP); + assert(!has_oop_node_outs(n) || res, "sanity: AddP has oop outs, needs to be treated as oop field"); + return res; +} + +bool ConnectionGraph::has_oop_node_outs(Node* n) { + return n->has_out_with(Op_StoreP, Op_LoadP, Op_StoreN, Op_LoadN) || + n->has_out_with(Op_GetAndSetP, Op_GetAndSetN, Op_CompareAndExchangeP, Op_CompareAndExchangeN) || + n->has_out_with(Op_CompareAndSwapP, Op_CompareAndSwapN, Op_WeakCompareAndSwapP, Op_WeakCompareAndSwapN) || + BarrierSet::barrier_set()->barrier_set_c2()->escape_has_out_with_unsafe_object(n); } // Returns unique pointed java object or null. @@ -3741,9 +3775,9 @@ Node* ConnectionGraph::get_addp_base(Node *addp) { // | | // AddP ( base == address ) // - // case #6. Constant Pool, ThreadLocal, CastX2P or + // case #6. Constant Pool, ThreadLocal, CastX2P, Klass, OSR buffer buf or // Raw object's field reference: - // {ConP, ThreadLocal, CastX2P, raw Load} + // {ConP, ThreadLocal, CastX2P, raw Load, Parm0} // top | // \ | // AddP ( base == top ) @@ -3785,7 +3819,9 @@ Node* ConnectionGraph::get_addp_base(Node *addp) { int opcode = uncast_base->Opcode(); assert(opcode == Op_ConP || opcode == Op_ThreadLocal || opcode == Op_CastX2P || uncast_base->is_DecodeNarrowPtr() || + (_igvn->C->is_osr_compilation() && uncast_base->is_Parm() && uncast_base->as_Parm()->_con == TypeFunc::Parms)|| (uncast_base->is_Mem() && (uncast_base->bottom_type()->isa_rawptr() != nullptr)) || + (uncast_base->is_Mem() && (uncast_base->bottom_type()->isa_klassptr() != nullptr)) || is_captured_store_address(addp), "sanity"); } } @@ -4380,7 +4416,6 @@ void ConnectionGraph::split_unique_types(GrowableArray &alloc_worklist, uint new_index_start = (uint) _compile->num_alias_types(); VectorSet visited; ideal_nodes.clear(); // Reset for use with set_map/get_map. - uint unique_old = _compile->unique(); // Phase 1: Process possible allocations from alloc_worklist. // Create instance types for the CheckCastPP for allocations where possible. diff --git a/src/hotspot/share/opto/escape.hpp b/src/hotspot/share/opto/escape.hpp index 04a9dc82982..dcb832889ec 100644 --- a/src/hotspot/share/opto/escape.hpp +++ b/src/hotspot/share/opto/escape.hpp @@ -539,7 +539,8 @@ private: } // Helper functions - bool is_oop_field(Node* n, int offset, bool* unsafe); + bool is_oop_field(Node* n, int offset, bool* unsafe); + bool has_oop_node_outs(Node* n); static Node* find_second_addp(Node* addp, Node* n); // offset of a field reference int address_offset(Node* adr, PhaseValues* phase); diff --git a/src/hotspot/share/opto/graphKit.cpp b/src/hotspot/share/opto/graphKit.cpp index f5c15a81cb6..3d127322439 100644 --- a/src/hotspot/share/opto/graphKit.cpp +++ b/src/hotspot/share/opto/graphKit.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, 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 @@ -1489,8 +1489,7 @@ Node* GraphKit::must_be_not_null(Node* value, bool do_replace_in_map) { } Node *if_f = _gvn.transform(new IfFalseNode(iff)); Node *frame = _gvn.transform(new ParmNode(C->start(), TypeFunc::FramePtr)); - Node* halt = _gvn.transform(new HaltNode(if_f, frame, "unexpected null in intrinsic")); - C->root()->add_req(halt); + halt(if_f, frame, "unexpected null in intrinsic"); Node *if_t = _gvn.transform(new IfTrueNode(iff)); set_control(if_t); return cast_not_null(value, do_replace_in_map); @@ -2073,6 +2072,12 @@ void GraphKit::increment_counter(Node* counter_addr) { store_to_memory(ctrl, counter_addr, incr, T_LONG, MemNode::unordered); } +void GraphKit::halt(Node* ctrl, Node* frameptr, const char* reason, bool generate_code_in_product) { + Node* halt = new HaltNode(ctrl, frameptr, reason + PRODUCT_ONLY(COMMA generate_code_in_product)); + halt = _gvn.transform(halt); + root()->add_req(halt); +} //------------------------------uncommon_trap---------------------------------- // Bail out to the interpreter in mid-method. Implemented by calling the @@ -2195,11 +2200,15 @@ Node* GraphKit::uncommon_trap(int trap_request, // The debug info is the only real input to this call. // Halt-and-catch fire here. The above call should never return! - HaltNode* halt = new HaltNode(control(), frameptr(), "uncommon trap returned which should never happen" - PRODUCT_ONLY(COMMA /*reachable*/false)); - _gvn.set_type_bottom(halt); - root()->add_req(halt); - + // We only emit code for the HaltNode in debug, which is enough for + // verifying correctness. In product, we don't want to emit it so + // that we can save on code space. HaltNode often get folded because + // the compiler can prove that the unreachable path is dead. But we + // cannot generally expect that for uncommon traps, which are often + // reachable and occasionally taken. + halt(control(), frameptr(), + "uncommon trap returned which should never happen", + false /* don't emit code in product */); stop_and_kill_map(); return call; } @@ -2734,7 +2743,7 @@ Node* Phase::gen_subtype_check(Node* subklass, Node* superklass, Node** ctrl, No // will always succeed. We could leave a dependency behind to ensure this. // First load the super-klass's check-offset - Node *p1 = gvn.transform(new AddPNode(superklass, superklass, gvn.MakeConX(in_bytes(Klass::super_check_offset_offset())))); + Node *p1 = gvn.transform(new AddPNode(C->top(), superklass, gvn.MakeConX(in_bytes(Klass::super_check_offset_offset())))); Node* m = C->immutable_memory(); Node *chk_off = gvn.transform(new LoadINode(nullptr, m, p1, gvn.type(p1)->is_ptr(), TypeInt::INT, MemNode::unordered)); int cacheoff_con = in_bytes(Klass::secondary_super_cache_offset()); @@ -2752,7 +2761,7 @@ Node* Phase::gen_subtype_check(Node* subklass, Node* superklass, Node** ctrl, No #ifdef _LP64 chk_off_X = gvn.transform(new ConvI2LNode(chk_off_X)); #endif - Node *p2 = gvn.transform(new AddPNode(subklass,subklass,chk_off_X)); + Node* p2 = gvn.transform(new AddPNode(C->top(), subklass, chk_off_X)); // For some types like interfaces the following loadKlass is from a 1-word // cache which is mutable so can't use immutable memory. Other // types load from the super-class display table which is immutable. @@ -3418,6 +3427,7 @@ Node* GraphKit::insert_mem_bar(int opcode, Node* precedent) { mb->init_req(TypeFunc::Control, control()); mb->init_req(TypeFunc::Memory, reset_memory()); Node* membar = _gvn.transform(mb); + record_for_igvn(membar); set_control(_gvn.transform(new ProjNode(membar, TypeFunc::Control))); set_all_memory_call(membar); return membar; @@ -3447,6 +3457,7 @@ Node* GraphKit::insert_mem_bar_volatile(int opcode, int alias_idx, Node* precede mb->set_req(TypeFunc::Memory, memory(alias_idx)); } Node* membar = _gvn.transform(mb); + record_for_igvn(membar); set_control(_gvn.transform(new ProjNode(membar, TypeFunc::Control))); if (alias_idx == Compile::AliasIdxBot) { merged_memory()->set_base_memory(_gvn.transform(new ProjNode(membar, TypeFunc::Memory))); @@ -3587,7 +3598,7 @@ Node* GraphKit::get_layout_helper(Node* klass_node, jint& constant_value) { } } constant_value = Klass::_lh_neutral_value; // put in a known value - Node* lhp = basic_plus_adr(klass_node, klass_node, in_bytes(Klass::layout_helper_offset())); + Node* lhp = basic_plus_adr(top(), klass_node, in_bytes(Klass::layout_helper_offset())); return make_load(nullptr, lhp, TypeInt::INT, T_INT, MemNode::unordered); } diff --git a/src/hotspot/share/opto/graphKit.hpp b/src/hotspot/share/opto/graphKit.hpp index 56e9a949e6f..0537d31ae36 100644 --- a/src/hotspot/share/opto/graphKit.hpp +++ b/src/hotspot/share/opto/graphKit.hpp @@ -709,6 +709,8 @@ class GraphKit : public Phase { void increment_counter(address counter_addr); // increment a debug counter void increment_counter(Node* counter_addr); // increment a debug counter + void halt(Node* ctrl, Node* frameptr, const char* reason, bool generate_code_in_product = true); + // Bail out to the interpreter right now // The optional klass is the one causing the trap. // The optional reason is debug information written to the compile log. diff --git a/src/hotspot/share/opto/library_call.cpp b/src/hotspot/share/opto/library_call.cpp index a057f66a989..e481833c816 100644 --- a/src/hotspot/share/opto/library_call.cpp +++ b/src/hotspot/share/opto/library_call.cpp @@ -940,11 +940,7 @@ inline Node* LibraryCallKit::generate_limit_guard(Node* offset, } // Emit range checks for the given String.value byte array -void LibraryCallKit::generate_string_range_check(Node* array, - Node* offset, - Node* count, - bool char_count, - bool halt_on_oob) { +void LibraryCallKit::generate_string_range_check(Node* array, Node* offset, Node* count, bool char_count) { if (stopped()) { return; // already stopped } @@ -962,17 +958,10 @@ void LibraryCallKit::generate_string_range_check(Node* array, generate_limit_guard(offset, count, load_array_length(array), bailout); if (bailout->req() > 1) { - if (halt_on_oob) { - bailout = _gvn.transform(bailout)->as_Region(); - Node* frame = _gvn.transform(new ParmNode(C->start(), TypeFunc::FramePtr)); - Node* halt = _gvn.transform(new HaltNode(bailout, frame, "unexpected guard failure in intrinsic")); - C->root()->add_req(halt); - } else { - PreserveJVMState pjvms(this); - set_control(_gvn.transform(bailout)); - uncommon_trap(Deoptimization::Reason_intrinsic, - Deoptimization::Action_maybe_recompile); - } + PreserveJVMState pjvms(this); + set_control(_gvn.transform(bailout)); + uncommon_trap(Deoptimization::Reason_intrinsic, + Deoptimization::Action_maybe_recompile); } } @@ -1130,7 +1119,6 @@ bool LibraryCallKit::inline_array_equals(StrIntrinsicNode::ArgEnc ae) { //------------------------------inline_countPositives------------------------------ -// int java.lang.StringCoding#countPositives0(byte[] ba, int off, int len) bool LibraryCallKit::inline_countPositives() { if (too_many_traps(Deoptimization::Reason_intrinsic)) { return false; @@ -1142,14 +1130,13 @@ bool LibraryCallKit::inline_countPositives() { Node* offset = argument(1); Node* len = argument(2); - if (VerifyIntrinsicChecks) { - ba = must_be_not_null(ba, true); - generate_string_range_check(ba, offset, len, false, true); - if (stopped()) { - return true; - } - } + ba = must_be_not_null(ba, true); + // Range checks + generate_string_range_check(ba, offset, len, false); + if (stopped()) { + return true; + } Node* ba_start = array_element_address(ba, offset, T_BYTE); Node* result = new CountPositivesNode(control(), memory(TypeAryPtr::BYTES), ba_start, len); set_result(_gvn.transform(result)); @@ -3013,7 +3000,7 @@ bool LibraryCallKit::inline_unsafe_allocate() { // Note: The argument might still be an illegal value like // Serializable.class or Object[].class. The runtime will handle it. // But we must make an explicit check for initialization. - Node* insp = basic_plus_adr(kls, in_bytes(InstanceKlass::init_state_offset())); + Node* insp = basic_plus_adr(top(), kls, in_bytes(InstanceKlass::init_state_offset())); // Use T_BOOLEAN for InstanceKlass::_init_state so the compiler // can generate code to load it as unsigned byte. Node* inst = make_load(nullptr, insp, TypeInt::UBYTE, T_BOOLEAN, MemNode::acquire); @@ -3061,7 +3048,7 @@ bool LibraryCallKit::inline_native_vthread_start_transition(address funcAddr, co IdealKit ideal(this); Node* thread = ideal.thread(); - Node* jt_addr = basic_plus_adr(thread, in_bytes(JavaThread::is_in_vthread_transition_offset())); + Node* jt_addr = basic_plus_adr(top(), thread, in_bytes(JavaThread::is_in_vthread_transition_offset())); Node* vt_addr = basic_plus_adr(vt_oop, java_lang_Thread::is_in_vthread_transition_offset()); access_store_at(nullptr, jt_addr, _gvn.type(jt_addr)->is_ptr(), ideal.ConI(1), TypeInt::BOOL, T_BOOLEAN, IN_NATIVE | MO_UNORDERED); access_store_at(nullptr, vt_addr, _gvn.type(vt_addr)->is_ptr(), ideal.ConI(1), TypeInt::BOOL, T_BOOLEAN, IN_NATIVE | MO_UNORDERED); @@ -3102,7 +3089,7 @@ bool LibraryCallKit::inline_native_vthread_end_transition(address funcAddr, cons ideal.sync_kit(this); } ideal.else_(); { Node* thread = ideal.thread(); - Node* jt_addr = basic_plus_adr(thread, in_bytes(JavaThread::is_in_vthread_transition_offset())); + Node* jt_addr = basic_plus_adr(top(), thread, in_bytes(JavaThread::is_in_vthread_transition_offset())); Node* vt_addr = basic_plus_adr(vt_oop, java_lang_Thread::is_in_vthread_transition_offset()); sync_kit(ideal); @@ -3128,7 +3115,7 @@ bool LibraryCallKit::inline_native_notify_jvmti_sync() { // unconditionally update the is_disable_suspend bit in current JavaThread Node* thread = ideal.thread(); Node* arg = _gvn.transform(argument(0)); // argument for notification - Node* addr = basic_plus_adr(thread, in_bytes(JavaThread::is_disable_suspend_offset())); + Node* addr = basic_plus_adr(top(), thread, in_bytes(JavaThread::is_disable_suspend_offset())); const TypePtr *addr_type = _gvn.type(addr)->isa_ptr(); sync_kit(ideal); @@ -3702,7 +3689,7 @@ void LibraryCallKit::extend_setCurrentThread(Node* jt, Node* thread) { IfNode* iff_thread_not_equal_carrierThread = create_and_map_if(control(), test_thread_not_equal_carrierThread, PROB_FAIR, COUNT_UNKNOWN); - Node* vthread_offset = basic_plus_adr(jt, in_bytes(THREAD_LOCAL_OFFSET_JFR + VTHREAD_OFFSET_JFR)); + Node* vthread_offset = basic_plus_adr(top(), jt, in_bytes(THREAD_LOCAL_OFFSET_JFR + VTHREAD_OFFSET_JFR)); // False branch, is carrierThread. Node* thread_equal_carrierThread = _gvn.transform(new IfFalseNode(iff_thread_not_equal_carrierThread)); @@ -3727,7 +3714,7 @@ void LibraryCallKit::extend_setCurrentThread(Node* jt, Node* thread) { Node* tid = load_field_from_object(thread, "tid", "J"); // Store the vthread tid to the jfr thread local. - Node* thread_id_offset = basic_plus_adr(jt, in_bytes(THREAD_LOCAL_OFFSET_JFR + VTHREAD_ID_OFFSET_JFR)); + Node* thread_id_offset = basic_plus_adr(top(), jt, in_bytes(THREAD_LOCAL_OFFSET_JFR + VTHREAD_ID_OFFSET_JFR)); Node* tid_memory = store_to_memory(control(), thread_id_offset, tid, T_LONG, MemNode::unordered, true); // Branch is_excluded to conditionalize updating the epoch . @@ -3749,7 +3736,7 @@ void LibraryCallKit::extend_setCurrentThread(Node* jt, Node* thread) { Node* epoch = _gvn.transform(new AndINode(epoch_raw, _gvn.transform(epoch_mask))); // Store the vthread epoch to the jfr thread local. - Node* vthread_epoch_offset = basic_plus_adr(jt, in_bytes(THREAD_LOCAL_OFFSET_JFR + VTHREAD_EPOCH_OFFSET_JFR)); + Node* vthread_epoch_offset = basic_plus_adr(top(), jt, in_bytes(THREAD_LOCAL_OFFSET_JFR + VTHREAD_EPOCH_OFFSET_JFR)); Node* included_memory = store_to_memory(control(), vthread_epoch_offset, epoch, T_CHAR, MemNode::unordered, true); RegionNode* excluded_rgn = new RegionNode(PATH_LIMIT); @@ -3772,7 +3759,7 @@ void LibraryCallKit::extend_setCurrentThread(Node* jt, Node* thread) { set_all_memory(excluded_mem); // Store the vthread exclusion state to the jfr thread local. - Node* thread_local_excluded_offset = basic_plus_adr(jt, in_bytes(THREAD_LOCAL_OFFSET_JFR + VTHREAD_EXCLUDED_OFFSET_JFR)); + Node* thread_local_excluded_offset = basic_plus_adr(top(), jt, in_bytes(THREAD_LOCAL_OFFSET_JFR + VTHREAD_EXCLUDED_OFFSET_JFR)); store_to_memory(control(), thread_local_excluded_offset, _gvn.transform(exclusion), T_BOOLEAN, MemNode::unordered, true); // Store release @@ -3827,7 +3814,7 @@ bool LibraryCallKit::inline_native_setCurrentThread() { // Change the _monitor_owner_id of the JavaThread Node* tid = load_field_from_object(arr, "tid", "J"); - Node* monitor_owner_id_offset = basic_plus_adr(thread, in_bytes(JavaThread::monitor_owner_id_offset())); + Node* monitor_owner_id_offset = basic_plus_adr(top(), thread, in_bytes(JavaThread::monitor_owner_id_offset())); store_to_memory(control(), monitor_owner_id_offset, tid, T_LONG, MemNode::unordered, true); JFR_ONLY(extend_setCurrentThread(thread, arr);) @@ -3969,7 +3956,7 @@ bool LibraryCallKit::inline_native_Continuation_pinning(bool unpin) { //---------------------------load_mirror_from_klass---------------------------- // Given a klass oop, load its java mirror (a java.lang.Class oop). Node* LibraryCallKit::load_mirror_from_klass(Node* klass) { - Node* p = basic_plus_adr(klass, in_bytes(Klass::java_mirror_offset())); + Node* p = basic_plus_adr(top(), klass, in_bytes(Klass::java_mirror_offset())); Node* load = make_load(nullptr, p, TypeRawPtr::NOTNULL, T_ADDRESS, MemNode::unordered); // mirror = ((OopHandle)mirror)->resolve(); return access_load(load, TypeInstPtr::MIRROR, T_OBJECT, IN_NATIVE); @@ -4009,7 +3996,7 @@ Node* LibraryCallKit::generate_klass_flags_guard(Node* kls, int modifier_mask, i ByteSize offset, const Type* type, BasicType bt) { // Branch around if the given klass has the given modifier bit set. // Like generate_guard, adds a new path onto the region. - Node* modp = basic_plus_adr(kls, in_bytes(offset)); + Node* modp = basic_plus_adr(top(), kls, in_bytes(offset)); Node* mods = make_load(nullptr, modp, type, bt, MemNode::unordered); Node* mask = intcon(modifier_mask); Node* bits = intcon(modifier_bits); @@ -4143,7 +4130,7 @@ bool LibraryCallKit::inline_native_Class_query(vmIntrinsics::ID id) { phi->add_req(null()); } // If we fall through, it's a plain class. Get its _super. - p = basic_plus_adr(kls, in_bytes(Klass::super_offset())); + p = basic_plus_adr(top(), kls, in_bytes(Klass::super_offset())); kls = _gvn.transform(LoadKlassNode::make(_gvn, immutable_memory(), p, TypeRawPtr::BOTTOM, TypeInstKlassPtr::OBJECT_OR_NULL)); null_ctl = top(); kls = null_check_oop(kls, &null_ctl); @@ -4681,7 +4668,7 @@ Node* LibraryCallKit::generate_virtual_guard(Node* obj_klass, int entry_offset = in_bytes(Klass::vtable_start_offset()) + vtable_index*vtableEntry::size_in_bytes() + in_bytes(vtableEntry::method_offset()); - Node* entry_addr = basic_plus_adr(obj_klass, entry_offset); + Node* entry_addr = basic_plus_adr(top(), obj_klass, entry_offset); Node* target_call = make_load(nullptr, entry_addr, TypePtr::NOTNULL, T_ADDRESS, MemNode::unordered); // Compare the target method with the expected method (e.g., Object.hashCode). @@ -6184,9 +6171,6 @@ CallStaticJavaNode* LibraryCallKit::get_uncommon_trap_from_success_proj(Node* no } //-------------inline_encodeISOArray----------------------------------- -// int sun.nio.cs.ISO_8859_1.Encoder#encodeISOArray0(byte[] sa, int sp, byte[] da, int dp, int len) -// int java.lang.StringCoding#encodeISOArray0(byte[] sa, int sp, byte[] da, int dp, int len) -// int java.lang.StringCoding#encodeAsciiArray0(char[] sa, int sp, byte[] da, int dp, int len) // encode char[] to byte[] in ISO_8859_1 or ASCII bool LibraryCallKit::inline_encodeISOArray(bool ascii) { assert(callee()->signature()->size() == 5, "encodeISOArray has 5 parameters"); @@ -6197,14 +6181,8 @@ bool LibraryCallKit::inline_encodeISOArray(bool ascii) { Node *dst_offset = argument(3); Node *length = argument(4); - // Cast source & target arrays to not-null - if (VerifyIntrinsicChecks) { - src = must_be_not_null(src, true); - dst = must_be_not_null(dst, true); - if (stopped()) { - return true; - } - } + src = must_be_not_null(src, true); + dst = must_be_not_null(dst, true); const TypeAryPtr* src_type = src->Value(&_gvn)->isa_aryptr(); const TypeAryPtr* dst_type = dst->Value(&_gvn)->isa_aryptr(); @@ -6221,15 +6199,6 @@ bool LibraryCallKit::inline_encodeISOArray(bool ascii) { return false; } - // Check source & target bounds - if (VerifyIntrinsicChecks) { - generate_string_range_check(src, src_offset, length, src_elem == T_BYTE, true); - generate_string_range_check(dst, dst_offset, length, false, true); - if (stopped()) { - return true; - } - } - Node* src_start = array_element_address(src, src_offset, T_CHAR); Node* dst_start = array_element_address(dst, dst_offset, dst_elem); // 'src_start' points to src array + scaled offset diff --git a/src/hotspot/share/opto/library_call.hpp b/src/hotspot/share/opto/library_call.hpp index bfe29814dec..00ba4c795f1 100644 --- a/src/hotspot/share/opto/library_call.hpp +++ b/src/hotspot/share/opto/library_call.hpp @@ -163,8 +163,7 @@ class LibraryCallKit : public GraphKit { Node* array_length, RegionNode* region); void generate_string_range_check(Node* array, Node* offset, - Node* length, bool char_count, - bool halt_on_oob = false); + Node* length, bool char_count); Node* current_thread_helper(Node* &tls_output, ByteSize handle_offset, bool is_immutable); Node* generate_current_thread(Node* &tls_output); diff --git a/src/hotspot/share/opto/loopnode.cpp b/src/hotspot/share/opto/loopnode.cpp index dacc1a1a734..fab354e3e3d 100644 --- a/src/hotspot/share/opto/loopnode.cpp +++ b/src/hotspot/share/opto/loopnode.cpp @@ -590,7 +590,7 @@ Node* PhaseIdealLoop::loop_nest_replace_iv(Node* iv_to_replace, Node* inner_iv, // Add a Parse Predicate with an uncommon trap on the failing/false path. Normal control will continue on the true path. void PhaseIdealLoop::add_parse_predicate(Deoptimization::DeoptReason reason, Node* inner_head, IdealLoopTree* loop, SafePointNode* sfpt) { - if (!C->too_many_traps(reason)) { + if (!C->too_many_traps(sfpt->jvms()->method(), sfpt->jvms()->bci(), reason)) { ParsePredicateNode* parse_predicate = new ParsePredicateNode(inner_head->in(LoopNode::EntryControl), reason, &_igvn); register_control(parse_predicate, loop, inner_head->in(LoopNode::EntryControl)); Node* if_false = new IfFalseNode(parse_predicate); @@ -760,6 +760,24 @@ SafePointNode* PhaseIdealLoop::find_safepoint(Node* back_control, Node* x, Ideal return safepoint; } +void PhaseIdealLoop::add_parse_predicates(IdealLoopTree* outer_ilt, LoopNode* inner_head, SafePointNode* cloned_sfpt) { + if (ShortRunningLongLoop) { + add_parse_predicate(Deoptimization::Reason_short_running_long_loop, inner_head, outer_ilt, cloned_sfpt); + } + if (UseLoopPredicate) { + add_parse_predicate(Deoptimization::Reason_predicate, inner_head, outer_ilt, cloned_sfpt); + if (UseProfiledLoopPredicate) { + add_parse_predicate(Deoptimization::Reason_profile_predicate, inner_head, outer_ilt, cloned_sfpt); + } + } + + if (UseAutoVectorizationPredicate) { + add_parse_predicate(Deoptimization::Reason_auto_vectorization_check, inner_head, outer_ilt, cloned_sfpt); + } + + add_parse_predicate(Deoptimization::Reason_loop_limit_check, inner_head, outer_ilt, cloned_sfpt); +} + // If the loop has the shape of a counted loop but with a long // induction variable, transform the loop in a loop nest: an inner // loop that iterates for at most max int iterations with an integer @@ -979,9 +997,9 @@ bool PhaseIdealLoop::create_loop_nest(IdealLoopTree* loop, Node_List &old_new) { Node* inner_iters_max = nullptr; if (stride_con > 0) { - inner_iters_max = MaxNode::max_diff_with_zero(limit, outer_phi, TypeInteger::bottom(bt), _igvn); + inner_iters_max = MinMaxNode::max_diff_with_zero(limit, outer_phi, TypeInteger::bottom(bt), _igvn); } else { - inner_iters_max = MaxNode::max_diff_with_zero(outer_phi, limit, TypeInteger::bottom(bt), _igvn); + inner_iters_max = MinMaxNode::max_diff_with_zero(outer_phi, limit, TypeInteger::bottom(bt), _igvn); } Node* inner_iters_limit = _igvn.integercon(iters_limit, bt); @@ -989,7 +1007,7 @@ bool PhaseIdealLoop::create_loop_nest(IdealLoopTree* loop, Node_List &old_new) { // Long.MIN_VALUE to Long.MAX_VALUE for instance). Use an unsigned // min. const TypeInteger* inner_iters_actual_range = TypeInteger::make(0, iters_limit, Type::WidenMin, bt); - Node* inner_iters_actual = MaxNode::unsigned_min(inner_iters_max, inner_iters_limit, inner_iters_actual_range, _igvn); + Node* inner_iters_actual = MinMaxNode::unsigned_min(inner_iters_max, inner_iters_limit, inner_iters_actual_range, _igvn); Node* inner_iters_actual_int; if (bt == T_LONG) { @@ -1123,26 +1141,7 @@ bool PhaseIdealLoop::create_loop_nest(IdealLoopTree* loop, Node_List &old_new) { if (safepoint != nullptr) { SafePointNode* cloned_sfpt = old_new[safepoint->_idx]->as_SafePoint(); - if (ShortRunningLongLoop) { - add_parse_predicate(Deoptimization::Reason_short_running_long_loop, inner_head, outer_ilt, cloned_sfpt); - } - if (UseLoopPredicate) { - add_parse_predicate(Deoptimization::Reason_predicate, inner_head, outer_ilt, cloned_sfpt); - if (UseProfiledLoopPredicate) { - add_parse_predicate(Deoptimization::Reason_profile_predicate, inner_head, outer_ilt, cloned_sfpt); - } - } - - if (UseAutoVectorizationPredicate) { - // We only want to use the auto-vectorization check as a trap once per bci. And - // PhaseIdealLoop::add_parse_predicate only checks trap limits per method, so - // we do a custom check here. - if (!C->too_many_traps(cloned_sfpt->jvms()->method(), cloned_sfpt->jvms()->bci(), Deoptimization::Reason_auto_vectorization_check)) { - add_parse_predicate(Deoptimization::Reason_auto_vectorization_check, inner_head, outer_ilt, cloned_sfpt); - } - } - - add_parse_predicate(Deoptimization::Reason_loop_limit_check, inner_head, outer_ilt, cloned_sfpt); + add_parse_predicates(outer_ilt, inner_head, cloned_sfpt); } #ifndef PRODUCT @@ -1618,7 +1617,7 @@ void PhaseIdealLoop::transform_long_range_checks(int stride_con, const Node_List Node* max_jint_plus_one_long = longcon((jlong)max_jint + 1); Node* max_range = new AddLNode(max_jint_plus_one_long, L); register_new_node(max_range, entry_control); - R = MaxNode::unsigned_min(R, max_range, TypeLong::POS, _igvn); + R = MinMaxNode::unsigned_min(R, max_range, TypeLong::POS, _igvn); set_subtree_ctrl(R, true); } @@ -1717,9 +1716,9 @@ void PhaseIdealLoop::transform_long_range_checks(int stride_con, const Node_List } Node* PhaseIdealLoop::clamp(Node* R, Node* L, Node* H) { - Node* min = MaxNode::signed_min(R, H, TypeLong::LONG, _igvn); + Node* min = MinMaxNode::signed_min(R, H, TypeLong::LONG, _igvn); set_subtree_ctrl(min, true); - Node* max = MaxNode::signed_max(L, min, TypeLong::LONG, _igvn); + Node* max = MinMaxNode::signed_max(L, min, TypeLong::LONG, _igvn); set_subtree_ctrl(max, true); return max; } @@ -1893,7 +1892,7 @@ bool PhaseIdealLoop::convert_to_long_loop(Node* cmp, Node* phi, IdealLoopTree* l #endif //------------------------------is_counted_loop-------------------------------- -bool PhaseIdealLoop::is_counted_loop(Node* x, IdealLoopTree*&loop, BasicType iv_bt) { +bool PhaseIdealLoop::is_counted_loop(Node* x, IdealLoopTree*& loop, BasicType iv_bt) { PhaseGVN *gvn = &_igvn; Node* back_control = loop_exit_control(x, loop); @@ -3485,14 +3484,14 @@ void OuterStripMinedLoopNode::adjust_strip_mined_loop(PhaseIterGVN* igvn) { // the loop body to be run for LoopStripMiningIter. Node* max = nullptr; if (stride > 0) { - max = MaxNode::max_diff_with_zero(limit, iv_phi, TypeInt::INT, *igvn); + max = MinMaxNode::max_diff_with_zero(limit, iv_phi, TypeInt::INT, *igvn); } else { - max = MaxNode::max_diff_with_zero(iv_phi, limit, TypeInt::INT, *igvn); + max = MinMaxNode::max_diff_with_zero(iv_phi, limit, TypeInt::INT, *igvn); } // sub is positive and can be larger than the max signed int // value. Use an unsigned min. Node* const_iters = igvn->intcon(scaled_iters); - Node* min = MaxNode::unsigned_min(max, const_iters, TypeInt::make(0, scaled_iters, Type::WidenMin), *igvn); + Node* min = MinMaxNode::unsigned_min(max, const_iters, TypeInt::make(0, scaled_iters, Type::WidenMin), *igvn); // min is the number of iterations for the next inner loop execution: // unsigned_min(max(limit - iv_phi, 0), scaled_iters) if stride > 0 // unsigned_min(max(iv_phi - limit, 0), scaled_iters) if stride < 0 @@ -3528,7 +3527,6 @@ void OuterStripMinedLoopNode::transform_to_counted_loop(PhaseIterGVN* igvn, Phas CountedLoopEndNode* cle = inner_cl->loopexit(); Node* inner_test = cle->in(1); IfNode* outer_le = outer_loop_end(); - CountedLoopEndNode* inner_cle = inner_cl->loopexit(); Node* safepoint = outer_safepoint(); fix_sunk_stores_when_back_to_counted_loop(igvn, iloop); diff --git a/src/hotspot/share/opto/loopnode.hpp b/src/hotspot/share/opto/loopnode.hpp index ffc283ac941..24976d76a51 100644 --- a/src/hotspot/share/opto/loopnode.hpp +++ b/src/hotspot/share/opto/loopnode.hpp @@ -1368,6 +1368,9 @@ public: #endif void add_parse_predicate(Deoptimization::DeoptReason reason, Node* inner_head, IdealLoopTree* loop, SafePointNode* sfpt); SafePointNode* find_safepoint(Node* back_control, Node* x, IdealLoopTree* loop); + + void add_parse_predicates(IdealLoopTree* outer_ilt, LoopNode* inner_head, SafePointNode* cloned_sfpt); + IdealLoopTree* insert_outer_loop(IdealLoopTree* loop, LoopNode* outer_l, Node* outer_ift); IdealLoopTree* create_outer_strip_mined_loop(Node* init_control, IdealLoopTree* loop, float cl_prob, float le_fcnt, diff --git a/src/hotspot/share/opto/loopopts.cpp b/src/hotspot/share/opto/loopopts.cpp index 8f85b7f270e..1855263539b 100644 --- a/src/hotspot/share/opto/loopopts.cpp +++ b/src/hotspot/share/opto/loopopts.cpp @@ -1708,6 +1708,7 @@ void PhaseIdealLoop::try_sink_out_of_loop(Node* n) { !n->is_OpaqueInitializedAssertionPredicate() && !n->is_OpaqueTemplateAssertionPredicate() && !is_raw_to_oop_cast && // don't extend live ranges of raw oops + n->Opcode() != Op_CreateEx && (KillPathsReachableByDeadTypeNode || !n->is_Type()) ) { Node *n_ctrl = get_ctrl(n); @@ -1921,11 +1922,6 @@ bool PhaseIdealLoop::ctrl_of_all_uses_out_of_loop(const Node* n, Node* n_ctrl, I if (u->is_Opaque1()) { return false; // Found loop limit, bugfix for 4677003 } - // We can't reuse tags in PhaseIdealLoop::dom_lca_for_get_late_ctrl_internal() so make sure calls to - // get_late_ctrl_with_anti_dep() use their own tag - _dom_lca_tags_round++; - assert(_dom_lca_tags_round != 0, "shouldn't wrap around"); - if (u->is_Phi()) { for (uint j = 1; j < u->req(); ++j) { if (u->in(j) == n && !ctrl_of_use_out_of_loop(n, n_ctrl, n_loop, u->in(0)->in(j))) { @@ -1959,6 +1955,11 @@ bool PhaseIdealLoop::would_sink_below_pre_loop_exit(IdealLoopTree* n_loop, Node* bool PhaseIdealLoop::ctrl_of_use_out_of_loop(const Node* n, Node* n_ctrl, IdealLoopTree* n_loop, Node* ctrl) { if (n->is_Load()) { + // We can't reuse tags in PhaseIdealLoop::dom_lca_for_get_late_ctrl_internal() so make sure each call to + // get_late_ctrl_with_anti_dep() uses its own tag + _dom_lca_tags_round++; + assert(_dom_lca_tags_round != 0, "shouldn't wrap around"); + ctrl = get_late_ctrl_with_anti_dep(n->as_Load(), n_ctrl, ctrl); } IdealLoopTree *u_loop = get_loop(ctrl); diff --git a/src/hotspot/share/opto/macro.cpp b/src/hotspot/share/opto/macro.cpp index 80818a4ddc7..9470001b2d2 100644 --- a/src/hotspot/share/opto/macro.cpp +++ b/src/hotspot/share/opto/macro.cpp @@ -1198,8 +1198,8 @@ bool PhaseMacroExpand::eliminate_boxing_node(CallStaticJavaNode *boxing) { } -Node* PhaseMacroExpand::make_load(Node* ctl, Node* mem, Node* base, int offset, const Type* value_type, BasicType bt) { - Node* adr = basic_plus_adr(base, offset); +Node* PhaseMacroExpand::make_load_raw(Node* ctl, Node* mem, Node* base, int offset, const Type* value_type, BasicType bt) { + Node* adr = basic_plus_adr(top(), base, offset); const TypePtr* adr_type = adr->bottom_type()->is_ptr(); Node* value = LoadNode::make(_igvn, ctl, mem, adr, adr_type, value_type, bt, MemNode::unordered); transform_later(value); @@ -1207,8 +1207,8 @@ Node* PhaseMacroExpand::make_load(Node* ctl, Node* mem, Node* base, int offset, } -Node* PhaseMacroExpand::make_store(Node* ctl, Node* mem, Node* base, int offset, Node* value, BasicType bt) { - Node* adr = basic_plus_adr(base, offset); +Node* PhaseMacroExpand::make_store_raw(Node* ctl, Node* mem, Node* base, int offset, Node* value, BasicType bt) { + Node* adr = basic_plus_adr(top(), base, offset); mem = StoreNode::make(_igvn, ctl, mem, adr, nullptr, value, bt, MemNode::unordered); transform_later(mem); return mem; @@ -1753,20 +1753,20 @@ PhaseMacroExpand::initialize_object(AllocateNode* alloc, Node* size_in_bytes) { InitializeNode* init = alloc->initialization(); // Store the klass & mark bits - Node* mark_node = alloc->make_ideal_mark(&_igvn, object, control, rawmem); + Node* mark_node = alloc->make_ideal_mark(&_igvn, control, rawmem); if (!mark_node->is_Con()) { transform_later(mark_node); } - rawmem = make_store(control, rawmem, object, oopDesc::mark_offset_in_bytes(), mark_node, TypeX_X->basic_type()); + rawmem = make_store_raw(control, rawmem, object, oopDesc::mark_offset_in_bytes(), mark_node, TypeX_X->basic_type()); if (!UseCompactObjectHeaders) { - rawmem = make_store(control, rawmem, object, oopDesc::klass_offset_in_bytes(), klass_node, T_METADATA); + rawmem = make_store_raw(control, rawmem, object, oopDesc::klass_offset_in_bytes(), klass_node, T_METADATA); } int header_size = alloc->minimum_header_size(); // conservatively small // Array length if (length != nullptr) { // Arrays need length field - rawmem = make_store(control, rawmem, object, arrayOopDesc::length_offset_in_bytes(), length, T_INT); + rawmem = make_store_raw(control, rawmem, object, arrayOopDesc::length_offset_in_bytes(), length, T_INT); // conservatively small header size: header_size = arrayOopDesc::base_offset_in_bytes(T_BYTE); if (_igvn.type(klass_node)->isa_aryklassptr()) { // we know the exact header size in most cases: @@ -1792,6 +1792,7 @@ PhaseMacroExpand::initialize_object(AllocateNode* alloc, if (!(UseTLAB && ZeroTLAB)) { rawmem = ClearArrayNode::clear_memory(control, rawmem, object, header_size, size_in_bytes, + true, &_igvn); } } else { @@ -1946,7 +1947,7 @@ Node* PhaseMacroExpand::prefetch_allocation(Node* i_o, Node*& needgc_false, uint step_size = AllocatePrefetchStepSize; uint distance = AllocatePrefetchDistance; for ( intx i = 0; i < lines; i++ ) { - prefetch_adr = new AddPNode( old_eden_top, new_eden_top, + prefetch_adr = new AddPNode( top(), new_eden_top, _igvn.MakeConX(distance) ); transform_later(prefetch_adr); prefetch = new PrefetchAllocationNode( i_o, prefetch_adr ); @@ -2321,12 +2322,7 @@ void PhaseMacroExpand::expand_unlock_node(UnlockNode *unlock) { // No need for a null check on unlock // Make the merge point - Node *region; - Node *mem_phi; - - region = new RegionNode(3); - // create a Phi for the memory state - mem_phi = new PhiNode( region, Type::MEMORY, TypeRawPtr::BOTTOM); + Node* region = new RegionNode(3); FastUnlockNode *funlock = new FastUnlockNode( ctrl, obj, box ); funlock = transform_later( funlock )->as_FastUnlock(); @@ -2355,12 +2351,15 @@ void PhaseMacroExpand::expand_unlock_node(UnlockNode *unlock) { transform_later(region); _igvn.replace_node(_callprojs.fallthrough_proj, region); - Node *memproj = transform_later(new ProjNode(call, TypeFunc::Memory) ); - mem_phi->init_req(1, memproj ); - mem_phi->init_req(2, mem); - transform_later(mem_phi); - - _igvn.replace_node(_callprojs.fallthrough_memproj, mem_phi); + if (_callprojs.fallthrough_memproj != nullptr) { + // create a Phi for the memory state + Node* mem_phi = new PhiNode( region, Type::MEMORY, TypeRawPtr::BOTTOM); + Node* memproj = transform_later(new ProjNode(call, TypeFunc::Memory)); + mem_phi->init_req(1, memproj); + mem_phi->init_req(2, mem); + transform_later(mem_phi); + _igvn.replace_node(_callprojs.fallthrough_memproj, mem_phi); + } } void PhaseMacroExpand::expand_subtypecheck_node(SubTypeCheckNode *check) { @@ -2577,11 +2576,11 @@ void PhaseMacroExpand::eliminate_opaque_looplimit_macro_nodes() { // a CMoveL construct now. At least until here, the type could be computed // precisely. CMoveL is not so smart, but we can give it at least the best // type we know abouot n now. - Node* repl = MaxNode::signed_max(n->in(1), n->in(2), _igvn.type(n), _igvn); + Node* repl = MinMaxNode::signed_max(n->in(1), n->in(2), _igvn.type(n), _igvn); _igvn.replace_node(n, repl); success = true; } else if (n->Opcode() == Op_MinL) { - Node* repl = MaxNode::signed_min(n->in(1), n->in(2), _igvn.type(n), _igvn); + Node* repl = MinMaxNode::signed_min(n->in(1), n->in(2), _igvn.type(n), _igvn); _igvn.replace_node(n, repl); success = true; } diff --git a/src/hotspot/share/opto/macro.hpp b/src/hotspot/share/opto/macro.hpp index c899bed567c..0f8d8fb5172 100644 --- a/src/hotspot/share/opto/macro.hpp +++ b/src/hotspot/share/opto/macro.hpp @@ -58,10 +58,10 @@ public: _igvn.register_new_node_with_optimizer(n); return n; } - Node* make_load( Node* ctl, Node* mem, Node* base, int offset, - const Type* value_type, BasicType bt); - Node* make_store(Node* ctl, Node* mem, Node* base, int offset, - Node* value, BasicType bt); + Node* make_load_raw(Node* ctl, Node* mem, Node* base, int offset, + const Type* value_type, BasicType bt); + Node* make_store_raw(Node* ctl, Node* mem, Node* base, int offset, + Node* value, BasicType bt); Node* make_leaf_call(Node* ctrl, Node* mem, const TypeFunc* call_type, address call_addr, @@ -144,11 +144,10 @@ private: Node* slice_idx, Node* slice_len, Node* dest_size); - bool generate_block_arraycopy(Node** ctrl, MergeMemNode** mem, Node* io, + bool generate_block_arraycopy(Node** ctrl, MergeMemNode** mem, const TypePtr* adr_type, BasicType basic_elem_type, - AllocateNode* alloc, - Node* src, Node* src_offset, + Node* src, Node* src_offset, Node* dest, Node* dest_offset, Node* dest_size, bool dest_uninitialized); MergeMemNode* generate_slow_arraycopy(ArrayCopyNode *ac, diff --git a/src/hotspot/share/opto/macroArrayCopy.cpp b/src/hotspot/share/opto/macroArrayCopy.cpp index fb4a1842a36..0719ffc45a5 100644 --- a/src/hotspot/share/opto/macroArrayCopy.cpp +++ b/src/hotspot/share/opto/macroArrayCopy.cpp @@ -384,7 +384,6 @@ Node* PhaseMacroExpand::generate_arraycopy(ArrayCopyNode *ac, AllocateArrayNode* transform_later(slow_region); } - Node* original_dest = dest; bool dest_needs_zeroing = false; bool acopy_to_uninitialized = false; @@ -424,7 +423,6 @@ Node* PhaseMacroExpand::generate_arraycopy(ArrayCopyNode *ac, AllocateArrayNode* // No zeroing elimination needed here. alloc = nullptr; acopy_to_uninitialized = false; - //original_dest = dest; //dest_needs_zeroing = false; } @@ -557,10 +555,9 @@ Node* PhaseMacroExpand::generate_arraycopy(ArrayCopyNode *ac, AllocateArrayNode* MergeMemNode* local_mem = MergeMemNode::make(mem); transform_later(local_mem); - didit = generate_block_arraycopy(&local_ctrl, &local_mem, local_io, - adr_type, basic_elem_type, alloc, - src, src_offset, dest, dest_offset, - dest_size, acopy_to_uninitialized); + didit = generate_block_arraycopy(&local_ctrl, &local_mem, adr_type, + basic_elem_type, src, src_offset, + dest, dest_offset, dest_size, acopy_to_uninitialized); if (didit) { // Present the results of the block-copying fast call. result_region->init_req(bcopy_path, local_ctrl); @@ -641,7 +638,7 @@ Node* PhaseMacroExpand::generate_arraycopy(ArrayCopyNode *ac, AllocateArrayNode* // (At this point we can assume disjoint_bases, since types differ.) int ek_offset = in_bytes(ObjArrayKlass::element_klass_offset()); - Node* p1 = basic_plus_adr(dest_klass, ek_offset); + Node* p1 = basic_plus_adr(top(), dest_klass, ek_offset); Node* n1 = LoadKlassNode::make(_igvn, C->immutable_memory(), p1, TypeRawPtr::BOTTOM); Node* dest_elem_klass = transform_later(n1); Node* cv = generate_checkcast_arraycopy(&local_ctrl, &local_mem, @@ -918,12 +915,12 @@ void PhaseMacroExpand::generate_clear_array(Node* ctrl, MergeMemNode* merge_mem, if (start_con >= 0 && end_con >= 0) { // Constant start and end. Simple. mem = ClearArrayNode::clear_memory(ctrl, mem, dest, - start_con, end_con, &_igvn); + start_con, end_con, false, &_igvn); } else if (start_con >= 0 && dest_size != top()) { // Constant start, pre-rounded end after the tail of the array. Node* end = dest_size; mem = ClearArrayNode::clear_memory(ctrl, mem, dest, - start_con, end, &_igvn); + start_con, end, false, &_igvn); } else if (start_con >= 0 && slice_len != top()) { // Constant start, non-constant end. End needs rounding up. // End offset = round_up(abase + ((slice_idx_con + slice_len) << scale), 8) @@ -936,7 +933,7 @@ void PhaseMacroExpand::generate_clear_array(Node* ctrl, MergeMemNode* merge_mem, end = transform_later(new AddXNode(end, MakeConX(end_base)) ); end = transform_later(new AndXNode(end, MakeConX(~end_round)) ); mem = ClearArrayNode::clear_memory(ctrl, mem, dest, - start_con, end, &_igvn); + start_con, end, false, &_igvn); } else if (start_con < 0 && dest_size != top()) { // Non-constant start, pre-rounded end after the tail of the array. // This is almost certainly a "round-to-end" operation. @@ -970,7 +967,7 @@ void PhaseMacroExpand::generate_clear_array(Node* ctrl, MergeMemNode* merge_mem, } Node* end = dest_size; // pre-rounded mem = ClearArrayNode::clear_memory(ctrl, mem, dest, - start, end, &_igvn); + start, end, false, &_igvn); } else { // Non-constant start, unrounded non-constant end. // (Nobody zeroes a random midsection of an array using this routine.) @@ -981,11 +978,10 @@ void PhaseMacroExpand::generate_clear_array(Node* ctrl, MergeMemNode* merge_mem, merge_mem->set_memory_at(alias_idx, mem); } -bool PhaseMacroExpand::generate_block_arraycopy(Node** ctrl, MergeMemNode** mem, Node* io, +bool PhaseMacroExpand::generate_block_arraycopy(Node** ctrl, MergeMemNode** mem, const TypePtr* adr_type, BasicType basic_elem_type, - AllocateNode* alloc, - Node* src, Node* src_offset, + Node* src, Node* src_offset, Node* dest, Node* dest_offset, Node* dest_size, bool dest_uninitialized) { // See if there is an advantage from block transfer. @@ -1133,7 +1129,7 @@ Node* PhaseMacroExpand::generate_checkcast_arraycopy(Node** ctrl, MergeMemNode** // look in each non-null element's class, at the desired klass's // super_check_offset, for the desired klass. int sco_offset = in_bytes(Klass::super_check_offset_offset()); - Node* p3 = basic_plus_adr(dest_elem_klass, sco_offset); + Node* p3 = basic_plus_adr(top(), dest_elem_klass, sco_offset); Node* n3 = new LoadINode(nullptr, *mem /*memory(p3)*/, p3, _igvn.type(p3)->is_ptr(), TypeInt::INT, MemNode::unordered); Node* check_offset = ConvI2X(transform_later(n3)); Node* check_value = dest_elem_klass; diff --git a/src/hotspot/share/opto/matcher.hpp b/src/hotspot/share/opto/matcher.hpp index a071cff9e3c..9579af84f24 100644 --- a/src/hotspot/share/opto/matcher.hpp +++ b/src/hotspot/share/opto/matcher.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, 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 @@ -327,6 +327,8 @@ public: // e.g. Op_ vector nodes and other intrinsics while guarding with vlen static bool match_rule_supported_vector(int opcode, int vlen, BasicType bt); + // Returns true if the platform efficiently implements the given masked vector + // operation using predicate features, false otherwise. static bool match_rule_supported_vector_masked(int opcode, int vlen, BasicType bt); // Determines if a vector operation needs to be partially implemented with a mask diff --git a/src/hotspot/share/opto/memnode.cpp b/src/hotspot/share/opto/memnode.cpp index 5b76f5b42cf..7f152eddd65 100644 --- a/src/hotspot/share/opto/memnode.cpp +++ b/src/hotspot/share/opto/memnode.cpp @@ -2563,7 +2563,13 @@ Node* LoadNode::klass_identity_common(PhaseGVN* phase) { ) { int mirror_field = in_bytes(Klass::java_mirror_offset()); if (tkls->offset() == mirror_field) { - return adr2->in(AddPNode::Base); +#ifdef ASSERT + const TypeKlassPtr* tkls2 = phase->type(adr2->in(AddPNode::Address))->is_klassptr(); + assert(tkls2->offset() == 0, "not a load of java_mirror"); +#endif + assert(adr2->in(AddPNode::Base)->is_top(), "not an off heap load"); + assert(adr2->in(AddPNode::Offset)->find_intptr_t_con(-1) == in_bytes(Klass::java_mirror_offset()), "incorrect offset"); + return adr2->in(AddPNode::Address); } } } @@ -3913,7 +3919,6 @@ const Type* SCMemProjNode::Value(PhaseGVN* phase) const LoadStoreNode::LoadStoreNode( Node *c, Node *mem, Node *adr, Node *val, const TypePtr* at, const Type* rt, uint required ) : Node(required), _type(rt), - _adr_type(at), _barrier_data(0) { init_req(MemNode::Control, c ); @@ -3921,6 +3926,7 @@ LoadStoreNode::LoadStoreNode( Node *c, Node *mem, Node *adr, Node *val, const Ty init_req(MemNode::Address, adr); init_req(MemNode::ValueIn, val); init_class_id(Class_LoadStore); + DEBUG_ONLY(_adr_type = at; adr_type();) } //------------------------------Value----------------------------------------- @@ -3944,6 +3950,11 @@ const Type* LoadStoreNode::Value(PhaseGVN* phase) const { return bottom_type(); } +const TypePtr* LoadStoreNode::adr_type() const { + const TypePtr* cross_check = DEBUG_ONLY(_adr_type) NOT_DEBUG(nullptr); + return MemNode::calculate_adr_type(in(MemNode::Address)->bottom_type(), cross_check); +} + uint LoadStoreNode::ideal_reg() const { return _type->ideal_reg(); } @@ -4107,18 +4118,27 @@ bool ClearArrayNode::step_through(Node** np, uint instance_id, PhaseValues* phas return true; } +Node* ClearArrayNode::make_address(Node* dest, Node* offset, bool raw_base, PhaseGVN* phase) { + Node* base = dest; + if (raw_base) { + // May be called as part of the initialization of a just allocated object + base = phase->C->top(); + } + return phase->transform(new AddPNode(base, dest, offset)); +} + //----------------------------clear_memory------------------------------------- // Generate code to initialize object storage to zero. Node* ClearArrayNode::clear_memory(Node* ctl, Node* mem, Node* dest, intptr_t start_offset, Node* end_offset, + bool raw_base, PhaseGVN* phase) { intptr_t offset = start_offset; int unit = BytesPerLong; if ((offset % unit) != 0) { - Node* adr = new AddPNode(dest, dest, phase->MakeConX(offset)); - adr = phase->transform(adr); + Node* adr = make_address(dest, phase->MakeConX(offset), raw_base, phase); const TypePtr* atp = TypeRawPtr::BOTTOM; mem = StoreNode::make(*phase, ctl, mem, adr, atp, phase->zerocon(T_INT), T_INT, MemNode::unordered); mem = phase->transform(mem); @@ -4127,12 +4147,13 @@ Node* ClearArrayNode::clear_memory(Node* ctl, Node* mem, Node* dest, assert((offset % unit) == 0, ""); // Initialize the remaining stuff, if any, with a ClearArray. - return clear_memory(ctl, mem, dest, phase->MakeConX(offset), end_offset, phase); + return clear_memory(ctl, mem, dest, phase->MakeConX(offset), end_offset, raw_base, phase); } Node* ClearArrayNode::clear_memory(Node* ctl, Node* mem, Node* dest, Node* start_offset, Node* end_offset, + bool raw_base, PhaseGVN* phase) { if (start_offset == end_offset) { // nothing to do @@ -4152,7 +4173,7 @@ Node* ClearArrayNode::clear_memory(Node* ctl, Node* mem, Node* dest, // Bulk clear double-words Node* zsize = phase->transform(new SubXNode(zend, zbase) ); - Node* adr = phase->transform(new AddPNode(dest, dest, start_offset) ); + Node* adr = make_address(dest, start_offset, raw_base, phase); mem = new ClearArrayNode(ctl, mem, zsize, adr, false); return phase->transform(mem); } @@ -4160,6 +4181,7 @@ Node* ClearArrayNode::clear_memory(Node* ctl, Node* mem, Node* dest, Node* ClearArrayNode::clear_memory(Node* ctl, Node* mem, Node* dest, intptr_t start_offset, intptr_t end_offset, + bool raw_base, PhaseGVN* phase) { if (start_offset == end_offset) { // nothing to do @@ -4173,11 +4195,10 @@ Node* ClearArrayNode::clear_memory(Node* ctl, Node* mem, Node* dest, } if (done_offset > start_offset) { mem = clear_memory(ctl, mem, dest, - start_offset, phase->MakeConX(done_offset), phase); + start_offset, phase->MakeConX(done_offset), raw_base, phase); } if (done_offset < end_offset) { // emit the final 32-bit store - Node* adr = new AddPNode(dest, dest, phase->MakeConX(done_offset)); - adr = phase->transform(adr); + Node* adr = make_address(dest, phase->MakeConX(done_offset), raw_base, phase); const TypePtr* atp = TypeRawPtr::BOTTOM; mem = StoreNode::make(*phase, ctl, mem, adr, atp, phase->zerocon(T_INT), T_INT, MemNode::unordered); mem = phase->transform(mem); @@ -5384,6 +5405,7 @@ Node* InitializeNode::complete_stores(Node* rawctl, Node* rawmem, Node* rawptr, zeroes_done = align_down(zeroes_done, BytesPerInt); rawmem = ClearArrayNode::clear_memory(rawctl, rawmem, rawptr, zeroes_done, zeroes_needed, + true, phase); zeroes_done = zeroes_needed; if (zsize > InitArrayShortSize && ++big_init_gaps > 2) @@ -5442,7 +5464,7 @@ Node* InitializeNode::complete_stores(Node* rawctl, Node* rawmem, Node* rawptr, } if (zeroes_done < size_limit) { rawmem = ClearArrayNode::clear_memory(rawctl, rawmem, rawptr, - zeroes_done, size_in_bytes, phase); + zeroes_done, size_in_bytes, true, phase); } } diff --git a/src/hotspot/share/opto/memnode.hpp b/src/hotspot/share/opto/memnode.hpp index d554c037012..249df51beb9 100644 --- a/src/hotspot/share/opto/memnode.hpp +++ b/src/hotspot/share/opto/memnode.hpp @@ -797,11 +797,6 @@ public: virtual int Opcode() const; virtual bool is_CFG() const { return false; } virtual const Type *bottom_type() const {return Type::MEMORY;} - virtual const TypePtr *adr_type() const { - Node* ctrl = in(0); - if (ctrl == nullptr) return nullptr; // node is dead - return ctrl->in(MemNode::Memory)->adr_type(); - } virtual uint ideal_reg() const { return 0;} // memory projections don't have a register virtual const Type* Value(PhaseGVN* phase) const; #ifndef PRODUCT @@ -814,9 +809,11 @@ public: class LoadStoreNode : public Node { private: const Type* const _type; // What kind of value is loaded? - const TypePtr* _adr_type; // What kind of memory is being addressed? uint8_t _barrier_data; // Bit field with barrier information virtual uint size_of() const; // Size is bigger +#ifdef ASSERT + const TypePtr* _adr_type; // What kind of memory is being addressed? +#endif // ASSERT public: LoadStoreNode( Node *c, Node *mem, Node *adr, Node *val, const TypePtr* at, const Type* rt, uint required ); virtual bool depends_only_on_test() const { return false; } @@ -824,7 +821,7 @@ public: virtual const Type *bottom_type() const { return _type; } virtual uint ideal_reg() const; - virtual const class TypePtr *adr_type() const { return _adr_type; } // returns bottom_type of address + virtual const TypePtr* adr_type() const; virtual const Type* Value(PhaseGVN* phase) const; bool result_not_used() const; @@ -1075,6 +1072,7 @@ public: class ClearArrayNode: public Node { private: bool _is_large; + static Node* make_address(Node* dest, Node* offset, bool raw_base, PhaseGVN* phase); public: ClearArrayNode( Node *ctrl, Node *arymem, Node *word_cnt, Node *base, bool is_large) : Node(ctrl,arymem,word_cnt,base), _is_large(is_large) { @@ -1102,14 +1100,17 @@ public: static Node* clear_memory(Node* control, Node* mem, Node* dest, intptr_t start_offset, intptr_t end_offset, + bool raw_base, PhaseGVN* phase); static Node* clear_memory(Node* control, Node* mem, Node* dest, intptr_t start_offset, Node* end_offset, + bool raw_base, PhaseGVN* phase); static Node* clear_memory(Node* control, Node* mem, Node* dest, Node* start_offset, Node* end_offset, + bool raw_base, PhaseGVN* phase); // Return allocation input memory edge if it is different instance // or itself if it is the one we are looking for. diff --git a/src/hotspot/share/opto/movenode.cpp b/src/hotspot/share/opto/movenode.cpp index 66db1df339b..6b6becb434f 100644 --- a/src/hotspot/share/opto/movenode.cpp +++ b/src/hotspot/share/opto/movenode.cpp @@ -271,9 +271,9 @@ Node* CMoveNode::Ideal_minmax(PhaseGVN* phase, CMoveNode* cmove) { // Create the Min/Max node based on the type and kind if (cmp_op == Op_CmpL) { - return MaxNode::build_min_max_long(phase, cmp_l, cmp_r, is_max); + return MinMaxNode::build_min_max_long(phase, cmp_l, cmp_r, is_max); } else { - return MaxNode::build_min_max_int(cmp_l, cmp_r, is_max); + return MinMaxNode::build_min_max_int(cmp_l, cmp_r, is_max); } } diff --git a/src/hotspot/share/opto/node.cpp b/src/hotspot/share/opto/node.cpp index 9d68e058103..832727a424e 100644 --- a/src/hotspot/share/opto/node.cpp +++ b/src/hotspot/share/opto/node.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2024, 2025, Alibaba Group Holding Limited. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -1213,7 +1213,7 @@ bool Node::has_special_unique_user() const { if (this->is_Store()) { // Condition for back-to-back stores folding. return n->Opcode() == op && n->in(MemNode::Memory) == this; - } else if ((this->is_Load() || this->is_DecodeN() || this->is_Phi()) && n->Opcode() == Op_MemBarAcquire) { + } else if ((this->is_Load() || this->is_DecodeN() || this->is_Phi() || this->is_Con()) && n->Opcode() == Op_MemBarAcquire) { // Condition for removing an unused LoadNode or DecodeNNode from the MemBarAcquire precedence input return true; } else if (this->is_Load() && n->is_Move()) { diff --git a/src/hotspot/share/opto/node.hpp b/src/hotspot/share/opto/node.hpp index 0adb2072100..5ddc4236b3e 100644 --- a/src/hotspot/share/opto/node.hpp +++ b/src/hotspot/share/opto/node.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2024, 2025, Alibaba Group Holding Limited. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -130,6 +130,7 @@ class MemBarNode; class MemBarStoreStoreNode; class MemNode; class MergeMemNode; +class MinMaxNode; class MoveNode; class MulNode; class MultiNode; @@ -809,6 +810,7 @@ public: DEFINE_CLASS_ID(AddP, Node, 9) DEFINE_CLASS_ID(BoxLock, Node, 10) DEFINE_CLASS_ID(Add, Node, 11) + DEFINE_CLASS_ID(MinMax, Add, 0) DEFINE_CLASS_ID(Mul, Node, 12) DEFINE_CLASS_ID(ClearArray, Node, 14) DEFINE_CLASS_ID(Halt, Node, 15) @@ -843,7 +845,8 @@ public: Flag_has_swapped_edges = 1ULL << 11, Flag_is_scheduled = 1ULL << 12, Flag_is_expensive = 1ULL << 13, - Flag_is_predicated_vector = 1ULL << 14, + Flag_is_predicated_vector = 1ULL << 14, // Marked on a vector node that has an additional + // mask input controlling the lane operations. Flag_for_post_loop_opts_igvn = 1ULL << 15, Flag_for_merge_stores_igvn = 1ULL << 16, Flag_is_removed_by_peephole = 1ULL << 17, @@ -986,6 +989,7 @@ public: DEFINE_CLASS_QUERY(MemBar) DEFINE_CLASS_QUERY(MemBarStoreStore) DEFINE_CLASS_QUERY(MergeMem) + DEFINE_CLASS_QUERY(MinMax) DEFINE_CLASS_QUERY(Move) DEFINE_CLASS_QUERY(Mul) DEFINE_CLASS_QUERY(Multi) diff --git a/src/hotspot/share/opto/parse.hpp b/src/hotspot/share/opto/parse.hpp index 19b302a8924..397a7796f88 100644 --- a/src/hotspot/share/opto/parse.hpp +++ b/src/hotspot/share/opto/parse.hpp @@ -442,7 +442,7 @@ class Parse : public GraphKit { SafePointNode* create_entry_map(); // OSR helpers - Node *fetch_interpreter_state(int index, BasicType bt, Node *local_addrs, Node *local_addrs_base); + Node *fetch_interpreter_state(int index, BasicType bt, Node* local_addrs); Node* check_interpreter_type(Node* l, const Type* type, SafePointNode* &bad_type_exit); void load_interpreter_state(Node* osr_buf); diff --git a/src/hotspot/share/opto/parse1.cpp b/src/hotspot/share/opto/parse1.cpp index 7aa96d2ace3..2f699650037 100644 --- a/src/hotspot/share/opto/parse1.cpp +++ b/src/hotspot/share/opto/parse1.cpp @@ -103,10 +103,9 @@ void Parse::print_statistics() { // on stack replacement. Node *Parse::fetch_interpreter_state(int index, BasicType bt, - Node *local_addrs, - Node *local_addrs_base) { + Node* local_addrs) { Node *mem = memory(Compile::AliasIdxRaw); - Node *adr = basic_plus_adr( local_addrs_base, local_addrs, -index*wordSize ); + Node *adr = basic_plus_adr(top(), local_addrs, -index*wordSize); Node *ctl = control(); // Very similar to LoadNode::make, except we handle un-aligned longs and @@ -121,7 +120,7 @@ Node *Parse::fetch_interpreter_state(int index, case T_DOUBLE: { // Since arguments are in reverse order, the argument address 'adr' // refers to the back half of the long/double. Recompute adr. - adr = basic_plus_adr(local_addrs_base, local_addrs, -(index+1)*wordSize); + adr = basic_plus_adr(top(), local_addrs, -(index+1)*wordSize); if (Matcher::misaligned_doubles_ok) { l = (bt == T_DOUBLE) ? (Node*)new LoadDNode(ctl, mem, adr, TypeRawPtr::BOTTOM, Type::DOUBLE, MemNode::unordered) @@ -220,7 +219,7 @@ void Parse::load_interpreter_state(Node* osr_buf) { // Commute monitors from interpreter frame to compiler frame. assert(jvms()->monitor_depth() == 0, "should be no active locks at beginning of osr"); int mcnt = osr_block->flow()->monitor_count(); - Node *monitors_addr = basic_plus_adr(osr_buf, osr_buf, (max_locals+mcnt*2-1)*wordSize); + Node *monitors_addr = basic_plus_adr(top(), osr_buf, (max_locals+mcnt*2-1)*wordSize); for (index = 0; index < mcnt; index++) { // Make a BoxLockNode for the monitor. BoxLockNode* osr_box = new BoxLockNode(next_monitor()); @@ -241,9 +240,9 @@ void Parse::load_interpreter_state(Node* osr_buf) { // Displaced headers and locked objects are interleaved in the // temp OSR buffer. We only copy the locked objects out here. // Fetch the locked object from the OSR temp buffer and copy to our fastlock node. - Node *lock_object = fetch_interpreter_state(index*2, T_OBJECT, monitors_addr, osr_buf); + Node *lock_object = fetch_interpreter_state(index*2, T_OBJECT, monitors_addr); // Try and copy the displaced header to the BoxNode - Node *displaced_hdr = fetch_interpreter_state((index*2) + 1, T_ADDRESS, monitors_addr, osr_buf); + Node *displaced_hdr = fetch_interpreter_state((index*2) + 1, T_ADDRESS, monitors_addr); store_to_memory(control(), box, displaced_hdr, T_ADDRESS, MemNode::unordered); @@ -271,7 +270,7 @@ void Parse::load_interpreter_state(Node* osr_buf) { } // Extract the needed locals from the interpreter frame. - Node *locals_addr = basic_plus_adr(osr_buf, osr_buf, (max_locals-1)*wordSize); + Node *locals_addr = basic_plus_adr(top(), osr_buf, (max_locals-1)*wordSize); // find all the locals that the interpreter thinks contain live oops const ResourceBitMap live_oops = method()->live_local_oops_at_bci(osr_bci()); @@ -318,7 +317,7 @@ void Parse::load_interpreter_state(Node* osr_buf) { // really for T_OBJECT types so correct it. bt = T_OBJECT; } - Node *value = fetch_interpreter_state(index, bt, locals_addr, osr_buf); + Node *value = fetch_interpreter_state(index, bt, locals_addr); set_local(index, value); } @@ -1229,8 +1228,7 @@ void Parse::do_method_entry() { Node* not_subtype_ctrl = gen_subtype_check(receiver_obj, holder_klass); assert(!stopped(), "not a subtype"); - Node* halt = _gvn.transform(new HaltNode(not_subtype_ctrl, frameptr(), "failed receiver subtype check")); - C->root()->add_req(halt); + halt(not_subtype_ctrl, frameptr(), "failed receiver subtype check"); } } #endif // ASSERT @@ -2129,7 +2127,7 @@ void Parse::call_register_finalizer() { Node* klass_addr = basic_plus_adr( receiver, receiver, oopDesc::klass_offset_in_bytes() ); Node* klass = _gvn.transform(LoadKlassNode::make(_gvn, immutable_memory(), klass_addr, TypeInstPtr::KLASS)); - Node* access_flags_addr = basic_plus_adr(klass, klass, in_bytes(Klass::misc_flags_offset())); + Node* access_flags_addr = basic_plus_adr(top(), klass, in_bytes(Klass::misc_flags_offset())); Node* access_flags = make_load(nullptr, access_flags_addr, TypeInt::UBYTE, T_BOOLEAN, MemNode::unordered); Node* mask = _gvn.transform(new AndINode(access_flags, intcon(KlassFlags::_misc_has_finalizer))); diff --git a/src/hotspot/share/opto/parseHelper.cpp b/src/hotspot/share/opto/parseHelper.cpp index 1a1cea05454..2e8fca68a8d 100644 --- a/src/hotspot/share/opto/parseHelper.cpp +++ b/src/hotspot/share/opto/parseHelper.cpp @@ -220,7 +220,7 @@ void Parse::array_store_check() { // Extract the array element class int element_klass_offset = in_bytes(ObjArrayKlass::element_klass_offset()); - Node* p2 = basic_plus_adr(array_klass, array_klass, element_klass_offset); + Node* p2 = basic_plus_adr(top(), array_klass, element_klass_offset); Node* a_e_klass = _gvn.transform(LoadKlassNode::make(_gvn, immutable_memory(), p2, tak)); assert(array_klass->is_Con() == a_e_klass->is_Con() || StressReflectiveCode, "a constant array type must come with a constant element type"); diff --git a/src/hotspot/share/opto/phaseX.cpp b/src/hotspot/share/opto/phaseX.cpp index 1e4cd42e09a..52badca8050 100644 --- a/src/hotspot/share/opto/phaseX.cpp +++ b/src/hotspot/share/opto/phaseX.cpp @@ -762,26 +762,36 @@ bool PhaseGVN::is_dominator_helper(Node *d, Node *n, bool linear_only) { //------------------------------dead_loop_check-------------------------------- // Check for a simple dead loop when a data node references itself directly // or through an other data node excluding cons and phis. -void PhaseGVN::dead_loop_check( Node *n ) { - // Phi may reference itself in a loop - if (n != nullptr && !n->is_dead_loop_safe() && !n->is_CFG()) { - // Do 2 levels check and only data inputs. - bool no_dead_loop = true; - uint cnt = n->req(); - for (uint i = 1; i < cnt && no_dead_loop; i++) { - Node *in = n->in(i); - if (in == n) { - no_dead_loop = false; - } else if (in != nullptr && !in->is_dead_loop_safe()) { - uint icnt = in->req(); - for (uint j = 1; j < icnt && no_dead_loop; j++) { - if (in->in(j) == n || in->in(j) == in) - no_dead_loop = false; - } +void PhaseGVN::dead_loop_check(Node* n) { + // Phi may reference itself in a loop. + if (n == nullptr || n->is_dead_loop_safe() || n->is_CFG()) { + return; + } + + // Do 2 levels check and only data inputs. + for (uint i = 1; i < n->req(); i++) { + Node* in = n->in(i); + if (in == n) { + n->dump_bfs(100, nullptr, ""); + fatal("Dead loop detected, node references itself: %s (%d)", + n->Name(), n->_idx); + } + + if (in == nullptr || in->is_dead_loop_safe()) { + continue; + } + for (uint j = 1; j < in->req(); j++) { + if (in->in(j) == n) { + n->dump_bfs(100, nullptr, ""); + fatal("Dead loop detected, node input references current node: %s (%d) -> %s (%d)", + in->Name(), in->_idx, n->Name(), n->_idx); + } + if (in->in(j) == in) { + n->dump_bfs(100, nullptr, ""); + fatal("Dead loop detected, node input references itself: %s (%d)", + in->Name(), in->_idx); } } - if (!no_dead_loop) { n->dump_bfs(100, nullptr, ""); } - assert(no_dead_loop, "dead loop detected"); } } @@ -2623,6 +2633,15 @@ void PhaseIterGVN::add_users_of_use_to_worklist(Node* n, Node* use, Unique_Node_ } } } + // Check for Max/Min(A, Max/Min(B, C)) where A == B or A == C + if (use->is_MinMax()) { + for (DUIterator_Fast i2max, i2 = use->fast_outs(i2max); i2 < i2max; i2++) { + Node* u = use->fast_out(i2); + if (u->Opcode() == use->Opcode()) { + worklist.push(u); + } + } + } auto enqueue_init_mem_projs = [&](ProjNode* proj) { add_users_to_worklist0(proj, worklist); }; diff --git a/src/hotspot/share/opto/rangeinference.hpp b/src/hotspot/share/opto/rangeinference.hpp index ebfd98ca4a6..66ea741a2da 100644 --- a/src/hotspot/share/opto/rangeinference.hpp +++ b/src/hotspot/share/opto/rangeinference.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, 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 @@ -147,7 +147,7 @@ public: static const Type* int_type_xmeet(const CT* i1, const Type* t2); template - static CTP int_type_union(CTP t1, CTP t2) { + static auto 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; @@ -209,7 +209,7 @@ public: 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) { + static TypeIntMirror make(const TypeIntPrototype& t, int widen = 0) { 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, @@ -217,11 +217,15 @@ public: canonicalized_t._data._bits}; } + TypeIntMirror meet(const TypeIntMirror& o) const { + return TypeIntHelper::int_type_union(this, &o); + } + // 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. + static TypeIntMirror make(const TypeIntMirror& t, int widen); 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; @@ -322,7 +326,7 @@ private: // Infer a result given the input types of a binary operation template static CTP infer_binary(CTP t1, CTP t2, Inference infer) { - CTP res; + TypeIntMirror, U> res; bool is_init = false; SimpleIntervalIterable t1_simple_intervals(t1); @@ -330,10 +334,10 @@ private: for (auto& st1 : t1_simple_intervals) { for (auto& st2 : t2_simple_intervals) { - CTP current = infer(st1, st2); + TypeIntMirror, U> current = infer(st1, st2); if (is_init) { - res = res->meet(current)->template cast>(); + res = res.meet(current); } else { is_init = true; res = current; @@ -342,7 +346,22 @@ private: } assert(is_init, "must be initialized"); - return res; + // It is important that widen is computed on the whole result instead of during each step. This + // is because we normalize the widen of small Type instances to 0, so computing the widen value + // for each step and taking the union of them may return a widen value that conflicts with + // other computations, trigerring the monotonicity assert during CCP. + // + // For example, let us consider the operation r = x ^ y: + // - During the first step of CCP, type(x) = {0}, type(y) = [-2, 2], w = 3. + // Since x is a constant that is the identity element of the xor operation, type(r) = type(y) = [-2, 2], w = 3 + // - During the second step, type(x) is widened to [0, 2], w = 0. + // We then compute the range for: + // r1 = x ^ y1, type(x) = [0, 2], w = 0, type(y1) = [0, 2], w = 0 + // r2 = x ^ y2, type(x) = [0, 2], w = 0, type(y2) = [-2, -1], w = 0 + // This results in type(r1) = [0, 3], w = 0, and type(r2) = [-4, -1], w = 0 + // So the union of type(r1) and type(r2) is [-4, 3], w = 0. This widen value is smaller than + // that of the previous step, triggering the monotonicity assert. + return CT::make(res, MAX2(t1->_widen, t2->_widen)); } public: @@ -357,7 +376,7 @@ public: 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)); + return TypeIntMirror, U>::make(TypeIntPrototype, U>{{lo, hi}, {ulo, uhi}, {zeros, ones}}); }); } @@ -372,7 +391,7 @@ public: 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)); + return TypeIntMirror, U>::make(TypeIntPrototype, U>{{lo, hi}, {ulo, uhi}, {zeros, ones}}); }); } @@ -385,7 +404,7 @@ public: 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)); + return TypeIntMirror, U>::make(TypeIntPrototype, U>{{lo, hi}, {ulo, uhi}, {zeros, ones}}); }); } }; diff --git a/src/hotspot/share/opto/subnode.cpp b/src/hotspot/share/opto/subnode.cpp index a10cd2a8d5b..548df58eb35 100644 --- a/src/hotspot/share/opto/subnode.cpp +++ b/src/hotspot/share/opto/subnode.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, 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 @@ -745,71 +745,40 @@ const Type* CmpINode::Value(PhaseGVN* phase) const { // Simplify a CmpU (compare 2 integers) node, based on local information. // If both inputs are constants, compare them. -const Type *CmpUNode::sub( const Type *t1, const Type *t2 ) const { - assert(!t1->isa_ptr(), "obsolete usage of CmpU"); +const Type* CmpUNode::sub(const Type* t1, const Type* t2) const { + const TypeInt* r0 = t1->is_int(); + const TypeInt* r1 = t2->is_int(); - // comparing two unsigned ints - const TypeInt *r0 = t1->is_int(); // Handy access - const TypeInt *r1 = t2->is_int(); - - // Current installed version - // Compare ranges for non-overlap - juint lo0 = r0->_lo; - juint hi0 = r0->_hi; - juint lo1 = r1->_lo; - juint hi1 = r1->_hi; - - // If either one has both negative and positive values, - // it therefore contains both 0 and -1, and since [0..-1] is the - // full unsigned range, the type must act as an unsigned bottom. - bool bot0 = ((jint)(lo0 ^ hi0) < 0); - bool bot1 = ((jint)(lo1 ^ hi1) < 0); - - if (bot0 || bot1) { - // All unsigned values are LE -1 and GE 0. - if (lo0 == 0 && hi0 == 0) { - return TypeInt::CC_LE; // 0 <= bot - } else if ((jint)lo0 == -1 && (jint)hi0 == -1) { - return TypeInt::CC_GE; // -1 >= bot - } else if (lo1 == 0 && hi1 == 0) { - return TypeInt::CC_GE; // bot >= 0 - } else if ((jint)lo1 == -1 && (jint)hi1 == -1) { - return TypeInt::CC_LE; // bot <= -1 - } - } else { - // We can use ranges of the form [lo..hi] if signs are the same. - assert(lo0 <= hi0 && lo1 <= hi1, "unsigned ranges are valid"); - // results are reversed, '-' > '+' for unsigned compare - if (hi0 < lo1) { - return TypeInt::CC_LT; // smaller - } else if (lo0 > hi1) { - return TypeInt::CC_GT; // greater - } else if (hi0 == lo1 && lo0 == hi1) { - return TypeInt::CC_EQ; // Equal results - } else if (lo0 >= hi1) { - return TypeInt::CC_GE; - } else if (hi0 <= lo1) { - // Check for special case in Hashtable::get. (See below.) - if ((jint)lo0 >= 0 && (jint)lo1 >= 0 && is_index_range_check()) - return TypeInt::CC_LT; - return TypeInt::CC_LE; - } - } // Check for special case in Hashtable::get - the hash index is // mod'ed to the table size so the following range check is useless. // Check for: (X Mod Y) CmpU Y, where the mod result and Y both have // to be positive. // (This is a gross hack, since the sub method never // looks at the structure of the node in any other case.) - if ((jint)lo0 >= 0 && (jint)lo1 >= 0 && is_index_range_check()) + if (r0->_lo >= 0 && r1->_lo >= 0 && is_index_range_check()) { return TypeInt::CC_LT; + } + + if (r0->_uhi < r1->_ulo) { + return TypeInt::CC_LT; + } else if (r0->_ulo > r1->_uhi) { + return TypeInt::CC_GT; + } else if (r0->is_con() && r1->is_con()) { + // Since r0->_ulo == r0->_uhi == r0->get_con(), we only reach here if the constants are equal + assert(r0->get_con() == r1->get_con(), "must reach a previous branch otherwise"); + return TypeInt::CC_EQ; + } else if (r0->_uhi == r1->_ulo) { + return TypeInt::CC_LE; + } else if (r0->_ulo == r1->_uhi) { + return TypeInt::CC_GE; + } const Type* joined = r0->join(r1); if (joined == Type::TOP) { return TypeInt::CC_NE; } - return TypeInt::CC; // else use worst case results + return TypeInt::CC; } const Type* CmpUNode::Value(PhaseGVN* phase) const { @@ -963,51 +932,21 @@ const Type *CmpLNode::sub( const Type *t1, const Type *t2 ) const { // Simplify a CmpUL (compare 2 unsigned longs) node, based on local information. // If both inputs are constants, compare them. const Type* CmpULNode::sub(const Type* t1, const Type* t2) const { - assert(!t1->isa_ptr(), "obsolete usage of CmpUL"); - - // comparing two unsigned longs - const TypeLong* r0 = t1->is_long(); // Handy access + const TypeLong* r0 = t1->is_long(); const TypeLong* r1 = t2->is_long(); - // Current installed version - // Compare ranges for non-overlap - julong lo0 = r0->_lo; - julong hi0 = r0->_hi; - julong lo1 = r1->_lo; - julong hi1 = r1->_hi; - - // If either one has both negative and positive values, - // it therefore contains both 0 and -1, and since [0..-1] is the - // full unsigned range, the type must act as an unsigned bottom. - bool bot0 = ((jlong)(lo0 ^ hi0) < 0); - bool bot1 = ((jlong)(lo1 ^ hi1) < 0); - - if (bot0 || bot1) { - // All unsigned values are LE -1 and GE 0. - if (lo0 == 0 && hi0 == 0) { - return TypeInt::CC_LE; // 0 <= bot - } else if ((jlong)lo0 == -1 && (jlong)hi0 == -1) { - return TypeInt::CC_GE; // -1 >= bot - } else if (lo1 == 0 && hi1 == 0) { - return TypeInt::CC_GE; // bot >= 0 - } else if ((jlong)lo1 == -1 && (jlong)hi1 == -1) { - return TypeInt::CC_LE; // bot <= -1 - } - } else { - // We can use ranges of the form [lo..hi] if signs are the same. - assert(lo0 <= hi0 && lo1 <= hi1, "unsigned ranges are valid"); - // results are reversed, '-' > '+' for unsigned compare - if (hi0 < lo1) { - return TypeInt::CC_LT; // smaller - } else if (lo0 > hi1) { - return TypeInt::CC_GT; // greater - } else if (hi0 == lo1 && lo0 == hi1) { - return TypeInt::CC_EQ; // Equal results - } else if (lo0 >= hi1) { - return TypeInt::CC_GE; - } else if (hi0 <= lo1) { - return TypeInt::CC_LE; - } + if (r0->_uhi < r1->_ulo) { + return TypeInt::CC_LT; + } else if (r0->_ulo > r1->_uhi) { + return TypeInt::CC_GT; + } else if (r0->is_con() && r1->is_con()) { + // Since r0->_ulo == r0->_uhi == r0->get_con(), we only reach here if the constants are equal + assert(r0->get_con() == r1->get_con(), "must reach a previous branch otherwise"); + return TypeInt::CC_EQ; + } else if (r0->_uhi == r1->_ulo) { + return TypeInt::CC_LE; + } else if (r0->_ulo == r1->_uhi) { + return TypeInt::CC_GE; } const Type* joined = r0->join(r1); @@ -1015,7 +954,7 @@ const Type* CmpULNode::sub(const Type* t1, const Type* t2) const { return TypeInt::CC_NE; } - return TypeInt::CC; // else use worst case results + return TypeInt::CC; } //============================================================================= diff --git a/src/hotspot/share/opto/subtypenode.cpp b/src/hotspot/share/opto/subtypenode.cpp index b58194b87d2..8e4c7d829a7 100644 --- a/src/hotspot/share/opto/subtypenode.cpp +++ b/src/hotspot/share/opto/subtypenode.cpp @@ -182,7 +182,7 @@ bool SubTypeCheckNode::verify(PhaseGVN* phase) { return verify_helper(phase, load_klass(phase), cached_t); } case Compile::SSC_full_test: { - Node* p1 = phase->transform(new AddPNode(superklass, superklass, phase->MakeConX(in_bytes(Klass::super_check_offset_offset())))); + Node* p1 = phase->transform(new AddPNode(C->top(), superklass, phase->MakeConX(in_bytes(Klass::super_check_offset_offset())))); Node* chk_off = phase->transform(new LoadINode(nullptr, C->immutable_memory(), p1, phase->type(p1)->is_ptr(), TypeInt::INT, MemNode::unordered)); record_for_cleanup(chk_off, phase); @@ -194,7 +194,7 @@ bool SubTypeCheckNode::verify(PhaseGVN* phase) { #ifdef _LP64 chk_off_X = phase->transform(new ConvI2LNode(chk_off_X)); #endif - Node* p2 = phase->transform(new AddPNode(subklass, subklass, chk_off_X)); + Node* p2 = phase->transform(new AddPNode(C->top(), subklass, chk_off_X)); Node* nkls = phase->transform(LoadKlassNode::make(*phase, C->immutable_memory(), p2, phase->type(p2)->is_ptr(), TypeInstKlassPtr::OBJECT_OR_NULL)); return verify_helper(phase, nkls, cached_t); diff --git a/src/hotspot/share/opto/superword.cpp b/src/hotspot/share/opto/superword.cpp index 35b46d22732..31cc8fa0460 100644 --- a/src/hotspot/share/opto/superword.cpp +++ b/src/hotspot/share/opto/superword.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2007, 2026, 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 @@ -2482,6 +2482,11 @@ static bool can_subword_truncate(Node* in, const Type* type) { return false; } + // Since casts specifically change the type of a node, stay on the safe side and do not truncate them. + if (in->is_ConstraintCast()) { + return false; + } + // Cannot be truncated: switch (opc) { case Op_AbsI: diff --git a/src/hotspot/share/opto/type.cpp b/src/hotspot/share/opto/type.cpp index ecb8c2c1cd8..c637737eef9 100644 --- a/src/hotspot/share/opto/type.cpp +++ b/src/hotspot/share/opto/type.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, 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 @@ -1816,6 +1816,13 @@ bool TypeInt::contains(const TypeInt* t) const { return TypeIntHelper::int_type_is_subset(this, t); } +#ifdef ASSERT +bool TypeInt::strictly_contains(const TypeInt* t) const { + assert(!_is_dual && !t->_is_dual, "dual types should only be used for join calculation"); + return TypeIntHelper::int_type_is_subset(this, t) && !TypeIntHelper::int_type_is_equal(this, t); +} +#endif // ASSERT + const Type* TypeInt::xmeet(const Type* t) const { return TypeIntHelper::int_type_xmeet(this, t); } @@ -1944,6 +1951,13 @@ bool TypeLong::contains(const TypeLong* t) const { return TypeIntHelper::int_type_is_subset(this, t); } +#ifdef ASSERT +bool TypeLong::strictly_contains(const TypeLong* t) const { + assert(!_is_dual && !t->_is_dual, "dual types should only be used for join calculation"); + return TypeIntHelper::int_type_is_subset(this, t) && !TypeIntHelper::int_type_is_equal(this, t); +} +#endif // ASSERT + const Type* TypeLong::xmeet(const Type* t) const { return TypeIntHelper::int_type_xmeet(this, t); } @@ -2443,6 +2457,12 @@ const TypeVect* TypeVect::make(BasicType elem_bt, uint length, bool is_mask) { return nullptr; } +// Create a vector mask type with the given element basic type and length. +// - Returns "TypeVectMask" (PVectMask) for platforms that support the predicate +// feature and it is implemented properly in the backend, allowing the mask to +// be stored in a predicate/mask register. +// - Returns a normal vector type "TypeVectA ~ TypeVectZ" (NVectMask) otherwise, +// where the vector mask is stored in a vector register. const TypeVect* TypeVect::makemask(BasicType elem_bt, uint length) { if (Matcher::has_predicated_vectors() && Matcher::match_rule_supported_vector_masked(Op_VectorLoadMask, length, elem_bt)) { diff --git a/src/hotspot/share/opto/type.hpp b/src/hotspot/share/opto/type.hpp index 73e2ba0045a..135f37f7267 100644 --- a/src/hotspot/share/opto/type.hpp +++ b/src/hotspot/share/opto/type.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, 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 @@ -799,6 +799,9 @@ public: 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(); } + static const TypeInt* make(const TypeIntMirror& t, int widen) { + return (new TypeInt(TypeIntPrototype{{t._lo, t._hi}, {t._ulo, t._uhi}, t._bits}, widen, false))->hashcons()->is_int(); + } // Check for single integer bool is_con() const { return _lo == _hi; } @@ -809,6 +812,11 @@ public: bool contains(jint i) const; bool contains(const TypeInt* t) const; +#ifdef ASSERT + // Check whether t is a proper subset (i.e. a subset that is not equal to the superset) of this + bool strictly_contains(const TypeInt* t) const; +#endif // ASSERT + virtual bool is_finite() const; // Has a finite value virtual const Type* xmeet(const Type* t) const; @@ -881,6 +889,9 @@ public: 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(); } + static const TypeLong* make(const TypeIntMirror& t, int widen) { + return (new TypeLong(TypeIntPrototype{{t._lo, t._hi}, {t._ulo, t._uhi}, t._bits}, widen, false))->hashcons()->is_long(); + } // Check for single integer bool is_con() const { return _lo == _hi; } @@ -891,6 +902,11 @@ public: bool contains(jlong i) const; bool contains(const TypeLong* t) const; +#ifdef ASSERT + // Check whether t is a proper subset (i.e. a subset that is not equal to the superset) of this + bool strictly_contains(const TypeLong* t) const; +#endif // ASSERT + // Check for positive 32-bit value. int is_positive_int() const { return _lo >= 0 && _hi <= (jlong)max_jint; } @@ -1012,7 +1028,7 @@ public: }; //------------------------------TypeVect--------------------------------------- -// Class of Vector Types +// Basic class of vector (mask) types. class TypeVect : public Type { const BasicType _elem_bt; // Vector's element type const uint _length; // Elements in vector (power of 2) @@ -1052,6 +1068,16 @@ public: #endif }; +// TypeVect subclasses representing vectors or vector masks with "BVectMask" or "NVectMask" +// layout (see vectornode.hpp for detailed notes on vector mask representations), mapped +// to vector registers and distinguished by vector register size: +// +// - TypeVectA: Scalable vector type (variable size, e.g., AArch64 SVE, RISC-V RVV) +// - TypeVectS: 32-bit vector type +// - TypeVectD: 64-bit vector type +// - TypeVectX: 128-bit vector type +// - TypeVectY: 256-bit vector type +// - TypeVectZ: 512-bit vector type class TypeVectA : public TypeVect { friend class TypeVect; TypeVectA(BasicType elem_bt, uint length) : TypeVect(VectorA, elem_bt, length) {} @@ -1082,6 +1108,9 @@ class TypeVectZ : public TypeVect { TypeVectZ(BasicType elem_bt, uint length) : TypeVect(VectorZ, elem_bt, length) {} }; +// Class of TypeVectMask, representing vector masks with "PVectMask" layout (see +// vectornode.hpp for detailed notes on vector mask representations), mapped to +// dedicated hardware predicate/mask registers. class TypeVectMask : public TypeVect { public: friend class TypeVect; diff --git a/src/hotspot/share/opto/vector.cpp b/src/hotspot/share/opto/vector.cpp index cf01b2442e6..f44df7e6da2 100644 --- a/src/hotspot/share/opto/vector.cpp +++ b/src/hotspot/share/opto/vector.cpp @@ -326,37 +326,15 @@ Node* PhaseVector::expand_vbox_node_helper(Node* vbox, return expand_vbox_alloc_node(vbox_alloc, vect, box_type, vect_type); } - // Handle the case when both the allocation input and vector input to - // VectorBoxNode are Phi. This case is generated after the transformation of - // Phi: Phi (VectorBox1 VectorBox2) => VectorBox (Phi1 Phi2). - // With this optimization, the relative two allocation inputs of VectorBox1 and - // VectorBox2 are gathered into Phi1 now. Similarly, the original vector - // inputs of two VectorBox nodes are in Phi2. - // - // See PhiNode::merge_through_phi in cfg.cpp for more details. - if (vbox->is_Phi() && vect->is_Phi()) { - assert(vbox->as_Phi()->region() == vect->as_Phi()->region(), ""); + // Handle the case when the allocation input to VectorBoxNode is a Phi. + // This is generated after the transformation in PhiNode::merge_through_phi: + // Phi (VectorBox1 VectorBox2) => VectorBox (Phi1 Phi2) + // The vector input may also be a Phi (Phi2 above), or it may have been + // value-numbered to a single node if all inputs were identical. + if (vbox->is_Phi()) { + bool same_region = vect->is_Phi() && vbox->as_Phi()->region() == vect->as_Phi()->region(); for (uint i = 1; i < vbox->req(); i++) { - Node* new_box = expand_vbox_node_helper(vbox->in(i), vect->in(i), - box_type, vect_type, visited); - if (!new_box->is_Phi()) { - C->initial_gvn()->hash_delete(vbox); - vbox->set_req(i, new_box); - } - } - return C->initial_gvn()->transform(vbox); - } - - // Handle the case when the allocation input to VectorBoxNode is a phi - // but the vector input is not, which can definitely be the case if the - // vector input has been value-numbered. It seems to be safe to do by - // construction because VectorBoxNode and VectorBoxAllocate come in a - // specific order as a result of expanding an intrinsic call. After that, if - // any of the inputs to VectorBoxNode are value-numbered they can only - // move up and are guaranteed to dominate. - if (vbox->is_Phi() && (vect->is_Vector() || vect->is_LoadVector())) { - for (uint i = 1; i < vbox->req(); i++) { - Node* new_box = expand_vbox_node_helper(vbox->in(i), vect, + Node* new_box = expand_vbox_node_helper(vbox->in(i), same_region ? vect->in(i) : vect, box_type, vect_type, visited); if (!new_box->is_Phi()) { C->initial_gvn()->hash_delete(vbox); diff --git a/src/hotspot/share/opto/vectorIntrinsics.cpp b/src/hotspot/share/opto/vectorIntrinsics.cpp index b48b5f2cd05..883a0526053 100644 --- a/src/hotspot/share/opto/vectorIntrinsics.cpp +++ b/src/hotspot/share/opto/vectorIntrinsics.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, 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,6 +25,7 @@ #include "ci/ciSymbols.hpp" #include "classfile/vmSymbols.hpp" #include "opto/library_call.hpp" +#include "opto/rootnode.hpp" #include "opto/runtime.hpp" #include "opto/vectornode.hpp" #include "prims/vectorSupport.hpp" @@ -73,6 +74,11 @@ static bool is_vector_mask(ciKlass* klass) { return klass->is_subclass_of(ciEnv::current()->vector_VectorMask_klass()); } +static Node* trace_vector(Node* operation) { + VectorNode::trace_new_vector(operation, "VectorAPI"); + return operation; +} + bool LibraryCallKit::arch_supports_vector_rotate(int opc, int num_elem, BasicType elem_bt, VectorMaskUseType mask_use_type, bool has_scalar_args) { bool is_supported = true; @@ -169,6 +175,10 @@ Node* GraphKit::unbox_vector(Node* v, const TypeInstPtr* vbox_type, BasicType el assert(check_vbox(vbox_type), ""); const TypeVect* vt = TypeVect::make(elem_bt, num_elem, is_vector_mask(vbox_type->instance_klass())); Node* unbox = gvn().transform(new VectorUnboxNode(C, vt, v, merged_memory())); + if (gvn().type(unbox)->isa_vect() == nullptr) { + assert(gvn().type(unbox) == Type::TOP, "sanity"); + return nullptr; // not a vector + } return unbox; } @@ -457,7 +467,7 @@ bool LibraryCallKit::inline_vector_nary_operation(int n) { } default: fatal("unsupported arity: %d", n); } - + trace_vector(operation); if (is_masked_op && mask != nullptr) { if (use_predicate) { operation->add_req(mask); @@ -465,7 +475,7 @@ bool LibraryCallKit::inline_vector_nary_operation(int n) { } else { operation->add_flag(Node::Flag_is_predicated_using_blend); operation = gvn().transform(operation); - operation = new VectorBlendNode(opd1, operation, mask); + operation = trace_vector(new VectorBlendNode(opd1, operation, mask)); } } operation = gvn().transform(operation); @@ -626,7 +636,7 @@ bool LibraryCallKit::inline_vector_mask_operation() { mask_vec = gvn().transform(VectorStoreMaskNode::make(gvn(), mask_vec, elem_bt, num_elem)); } const Type* maskoper_ty = mopc == Op_VectorMaskToLong ? (const Type*)TypeLong::LONG : (const Type*)TypeInt::INT; - Node* maskoper = gvn().transform(VectorMaskOpNode::make(mask_vec, maskoper_ty, mopc)); + Node* maskoper = gvn().transform(trace_vector(VectorMaskOpNode::make(mask_vec, maskoper_ty, mopc))); if (mopc != Op_VectorMaskToLong) { maskoper = ConvI2L(maskoper); } @@ -709,10 +719,10 @@ bool LibraryCallKit::inline_vector_frombits_coerced() { if (opc == Op_VectorLongToMask) { const TypeVect* vt = TypeVect::makemask(elem_bt, num_elem); if (Matcher::mask_op_prefers_predicate(opc, vt)) { - broadcast = gvn().transform(new VectorLongToMaskNode(elem, vt)); + broadcast = gvn().transform(trace_vector(new VectorLongToMaskNode(elem, vt))); } else { const TypeVect* mvt = TypeVect::make(T_BOOLEAN, num_elem); - broadcast = gvn().transform(new VectorLongToMaskNode(elem, mvt)); + broadcast = gvn().transform(trace_vector(new VectorLongToMaskNode(elem, mvt))); broadcast = gvn().transform(new VectorLoadMaskNode(broadcast, vt)); } } else { @@ -740,7 +750,7 @@ bool LibraryCallKit::inline_vector_frombits_coerced() { } default: fatal("%s", type2name(elem_bt)); } - broadcast = VectorNode::scalar2vector(elem, num_elem, elem_bt, is_mask); + broadcast = trace_vector(VectorNode::scalar2vector(elem, num_elem, elem_bt, is_mask)); broadcast = gvn().transform(broadcast); } @@ -926,22 +936,22 @@ bool LibraryCallKit::inline_vector_mem_operation(bool is_store) { if (is_mask) { val = gvn().transform(VectorStoreMaskNode::make(gvn(), val, elem_bt, num_elem)); } - Node* vstore = gvn().transform(StoreVectorNode::make(0, control(), memory(addr), addr, addr_type, val, store_num_elem)); + Node* vstore = gvn().transform(trace_vector(StoreVectorNode::make(0, control(), memory(addr), addr, addr_type, val, store_num_elem))); set_memory(vstore, addr_type); } else { // When using byte array, we need to load as byte then reinterpret the value. Otherwise, do a simple vector load. Node* vload = nullptr; if (mismatched_ms) { - vload = gvn().transform(LoadVectorNode::make(0, control(), memory(addr), addr, addr_type, mem_num_elem, mem_elem_bt)); + vload = gvn().transform(trace_vector(LoadVectorNode::make(0, control(), memory(addr), addr, addr_type, mem_num_elem, mem_elem_bt))); const TypeVect* to_vect_type = TypeVect::make(elem_bt, num_elem); vload = gvn().transform(new VectorReinterpretNode(vload, vload->bottom_type()->is_vect(), to_vect_type)); } else { // Special handle for masks if (is_mask) { - vload = gvn().transform(LoadVectorNode::make(0, control(), memory(addr), addr, addr_type, num_elem, T_BOOLEAN)); + vload = gvn().transform(trace_vector(LoadVectorNode::make(0, control(), memory(addr), addr, addr_type, num_elem, T_BOOLEAN))); vload = gvn().transform(new VectorLoadMaskNode(vload, TypeVect::makemask(elem_bt, num_elem))); } else { - vload = gvn().transform(LoadVectorNode::make(0, control(), memory(addr), addr, addr_type, num_elem, elem_bt)); + vload = gvn().transform(trace_vector(LoadVectorNode::make(0, control(), memory(addr), addr, addr_type, num_elem, elem_bt))); } } Node* box = box_vector(vload, vbox_type, elem_bt, num_elem); @@ -1139,7 +1149,7 @@ bool LibraryCallKit::inline_vector_mem_masked_operation(bool is_store) { const TypeVect* to_mask_type = TypeVect::makemask(mem_elem_bt, mem_num_elem); mask = gvn().transform(new VectorReinterpretNode(mask, from_mask_type, to_mask_type)); } - Node* vstore = gvn().transform(new StoreVectorMaskedNode(control(), memory(addr), addr, val, addr_type, mask)); + Node* vstore = gvn().transform(trace_vector(new StoreVectorMaskedNode(control(), memory(addr), addr, val, addr_type, mask))); set_memory(vstore, addr_type); } else { Node* vload = nullptr; @@ -1154,13 +1164,13 @@ bool LibraryCallKit::inline_vector_mem_masked_operation(bool is_store) { if (supports_predicate) { // Generate masked load vector node if predicate feature is supported. const TypeVect* vt = TypeVect::make(mem_elem_bt, mem_num_elem); - vload = gvn().transform(new LoadVectorMaskedNode(control(), memory(addr), addr, addr_type, vt, mask)); + vload = gvn().transform(trace_vector(new LoadVectorMaskedNode(control(), memory(addr), addr, addr_type, vt, mask))); } else { // Use the vector blend to implement the masked load vector. The biased elements are zeros. Node* zero = gvn().transform(gvn().zerocon(mem_elem_bt)); zero = gvn().transform(VectorNode::scalar2vector(zero, mem_num_elem, mem_elem_bt)); - vload = gvn().transform(LoadVectorNode::make(0, control(), memory(addr), addr, addr_type, mem_num_elem, mem_elem_bt)); - vload = gvn().transform(new VectorBlendNode(zero, vload, mask)); + vload = gvn().transform(trace_vector(LoadVectorNode::make(0, control(), memory(addr), addr, addr_type, mem_num_elem, mem_elem_bt))); + vload = gvn().transform(trace_vector(new VectorBlendNode(zero, vload, mask))); } if (mismatched_ms) { @@ -1364,17 +1374,17 @@ bool LibraryCallKit::inline_vector_gather_scatter(bool is_scatter) { Node* vstore = nullptr; if (mask != nullptr) { - vstore = gvn().transform(new StoreVectorScatterMaskedNode(control(), memory(addr), addr, addr_type, val, indexes, mask)); + vstore = gvn().transform(trace_vector(new StoreVectorScatterMaskedNode(control(), memory(addr), addr, addr_type, val, indexes, mask))); } else { - vstore = gvn().transform(new StoreVectorScatterNode(control(), memory(addr), addr, addr_type, val, indexes)); + vstore = gvn().transform(trace_vector(new StoreVectorScatterNode(control(), memory(addr), addr, addr_type, val, indexes))); } set_memory(vstore, addr_type); } else { Node* vload = nullptr; if (mask != nullptr) { - vload = gvn().transform(new LoadVectorGatherMaskedNode(control(), memory(addr), addr, addr_type, vector_type, indexes, mask)); + vload = gvn().transform(trace_vector(new LoadVectorGatherMaskedNode(control(), memory(addr), addr, addr_type, vector_type, indexes, mask))); } else { - vload = gvn().transform(new LoadVectorGatherNode(control(), memory(addr), addr, addr_type, vector_type, indexes)); + vload = gvn().transform(trace_vector(new LoadVectorGatherNode(control(), memory(addr), addr, addr_type, vector_type, indexes))); } Node* box = box_vector(vload, vbox_type, elem_bt, num_elem); set_result(box); @@ -1492,7 +1502,7 @@ bool LibraryCallKit::inline_vector_reduction() { // Make an unordered Reduction node. This affects only AddReductionVF/VD and MulReductionVF/VD, // as these operations are allowed to be associative (not requiring strict order) in VectorAPI. - value = ReductionNode::make(opc, nullptr, init, value, elem_bt, /* requires_strict_order */ false); + value = trace_vector(ReductionNode::make(opc, nullptr, init, value, elem_bt, /* requires_strict_order */ false)); if (mask != nullptr && use_predicate) { value->add_req(mask); @@ -1584,7 +1594,7 @@ bool LibraryCallKit::inline_vector_test() { return false; // operand unboxing failed } - Node* cmp = gvn().transform(new VectorTestNode(opd1, opd2, booltest)); + Node* cmp = gvn().transform(trace_vector(new VectorTestNode(opd1, opd2, booltest))); BoolTest::mask test = Matcher::vectortest_mask(booltest == BoolTest::overflow, opd1->bottom_type()->isa_vectmask(), num_elem); Node* bol = gvn().transform(new BoolNode(cmp, test)); @@ -1652,7 +1662,7 @@ bool LibraryCallKit::inline_vector_blend() { return false; // operand unboxing failed } - Node* blend = gvn().transform(new VectorBlendNode(v1, v2, mask)); + Node* blend = gvn().transform(trace_vector(new VectorBlendNode(v1, v2, mask))); Node* box = box_vector(blend, vbox_type, elem_bt, num_elem); set_result(box); @@ -1747,6 +1757,7 @@ bool LibraryCallKit::inline_vector_compare() { const TypeVect* vmask_type = TypeVect::makemask(mask_bt, num_elem); Node* operation = new VectorMaskCmpNode(pred, v1, v2, pred_node, vmask_type); + trace_vector(operation); if (is_masked_op) { if (use_predicate) { @@ -1886,6 +1897,7 @@ bool LibraryCallKit::inline_vector_rearrange() { } Node* rearrange = new VectorRearrangeNode(v1, shuffle); + trace_vector(rearrange); if (is_masked_op) { if (use_predicate) { rearrange->add_req(mask); @@ -2033,6 +2045,7 @@ bool LibraryCallKit::inline_vector_select_from() { // and finally rearrange Node* rearrange = new VectorRearrangeNode(v2, shuffle); + trace_vector(rearrange); if (is_masked_op) { if (use_predicate) { // masked rearrange is supported so use that directly @@ -2192,6 +2205,7 @@ bool LibraryCallKit::inline_vector_broadcast_int() { } Node* operation = VectorNode::make(opc, opd1, opd2, num_elem, elem_bt); + trace_vector(operation); if (is_masked_op && mask != nullptr) { if (use_predicate) { operation->add_req(mask); @@ -2330,6 +2344,21 @@ bool LibraryCallKit::inline_vector_convert() { Node* op = opd1; if (is_cast) { assert(!is_mask || num_elem_from == num_elem_to, "vector mask cast needs the same elem num"); + + // Make sure the precondition of VectorCastNode::opcode holds: we can only have + // unsigned casts for integral types (excluding long). VectorAPI code is not + // expected to violate this at runtime, but we may compile unreachable code + // where such impossible combinations arise. + if (is_ucast && (!is_integral_type(elem_bt_from) || elem_bt_from == T_LONG)) { + // Halt-and-catch fire here. This condition should never happen at runtime. + stringStream ss; + ss.print("impossible combination: unsigned vector cast from %s", type2name(elem_bt_from)); + halt(control(), frameptr(), ss.as_string(C->comp_arena())); + stop_and_kill_map(); + log_if_needed(" ** impossible combination: unsigned cast from %s", type2name(elem_bt_from)); + return true; + } + int cast_vopc = VectorCastNode::opcode(-1, elem_bt_from, !is_ucast); // Make sure that vector cast is implemented to particular type/size combination if it is @@ -2354,7 +2383,7 @@ bool LibraryCallKit::inline_vector_convert() { return false; } - op = gvn().transform(VectorCastNode::make(cast_vopc, op, elem_bt_to, num_elem_for_cast)); + op = gvn().transform(trace_vector(VectorCastNode::make(cast_vopc, op, elem_bt_to, num_elem_for_cast))); // Now ensure that the destination gets properly resized to needed size. op = gvn().transform(new VectorReinterpretNode(op, op->bottom_type()->is_vect(), dst_type)); } else if (num_elem_from > num_elem_to) { @@ -2375,7 +2404,7 @@ bool LibraryCallKit::inline_vector_convert() { const TypeVect* resize_type = TypeVect::make(elem_bt_from, num_elem_for_resize); op = gvn().transform(new VectorReinterpretNode(op, src_type, resize_type)); - op = gvn().transform(VectorCastNode::make(cast_vopc, op, elem_bt_to, num_elem_to)); + op = gvn().transform(trace_vector(VectorCastNode::make(cast_vopc, op, elem_bt_to, num_elem_to))); } else { // num_elem_from == num_elem_to if (is_mask) { // Make sure that cast for vector mask is implemented to particular type/size combination. @@ -2384,16 +2413,16 @@ bool LibraryCallKit::inline_vector_convert() { num_elem_to, type2name(elem_bt_to), is_mask); return false; } - op = gvn().transform(new VectorMaskCastNode(op, dst_type)); + op = gvn().transform(trace_vector(new VectorMaskCastNode(op, dst_type))); } else { // Since input and output number of elements match, and since we know this vector size is // supported, simply do a cast with no resize needed. - op = gvn().transform(VectorCastNode::make(cast_vopc, op, elem_bt_to, num_elem_to)); + op = gvn().transform(trace_vector(VectorCastNode::make(cast_vopc, op, elem_bt_to, num_elem_to))); } } } else if (!Type::equals(src_type, dst_type)) { assert(!is_cast, "must be reinterpret"); - op = gvn().transform(new VectorReinterpretNode(op, src_type, dst_type)); + op = gvn().transform(trace_vector(new VectorReinterpretNode(op, src_type, dst_type))); } const TypeInstPtr* vbox_type_to = TypeInstPtr::make_exact(TypePtr::NotNull, vbox_klass_to); @@ -2478,7 +2507,7 @@ bool LibraryCallKit::inline_vector_insert() { default: fatal("%s", type2name(elem_bt)); break; } - Node* operation = gvn().transform(VectorInsertNode::make(opd, insert_val, idx->get_con(), gvn())); + Node* operation = gvn().transform(trace_vector(VectorInsertNode::make(opd, insert_val, idx->get_con(), gvn()))); Node* vbox = box_vector(operation, vbox_type, elem_bt, num_elem); set_result(vbox); @@ -2536,7 +2565,7 @@ bool LibraryCallKit::inline_vector_extract() { if (opd == nullptr) { return false; } - opd = gvn().transform(VectorStoreMaskNode::make(gvn(), opd, elem_bt, num_elem)); + opd = gvn().transform(trace_vector(VectorStoreMaskNode::make(gvn(), opd, elem_bt, num_elem))); opd = gvn().transform(new ExtractUBNode(opd, pos)); opd = gvn().transform(new ConvI2LNode(opd)); } else if (arch_supports_vector(Op_VectorMaskToLong, num_elem, elem_bt, VecMaskUseLoad)) { @@ -2546,7 +2575,7 @@ bool LibraryCallKit::inline_vector_extract() { } // VectorMaskToLongNode requires the input is either a mask or a vector with BOOLEAN type. if (!Matcher::mask_op_prefers_predicate(Op_VectorMaskToLong, opd->bottom_type()->is_vect())) { - opd = gvn().transform(VectorStoreMaskNode::make(gvn(), opd, elem_bt, num_elem)); + opd = gvn().transform(trace_vector(VectorStoreMaskNode::make(gvn(), opd, elem_bt, num_elem))); } // ((toLong() >>> pos) & 1L opd = gvn().transform(new VectorMaskToLongNode(opd, TypeLong::LONG)); @@ -2664,8 +2693,8 @@ static Node* LowerSelectFromTwoVectorOperation(PhaseGVN& phase, Node* index_vec, vmask_type = TypeVect::makemask(elem_bt, num_elem); mask = phase.transform(new VectorMaskCastNode(mask, vmask_type)); - Node* p1 = phase.transform(new VectorRearrangeNode(src1, wrapped_index_vec)); - Node* p2 = phase.transform(new VectorRearrangeNode(src2, wrapped_index_vec)); + Node* p1 = phase.transform(trace_vector(new VectorRearrangeNode(src1, wrapped_index_vec))); + Node* p2 = phase.transform(trace_vector(new VectorRearrangeNode(src2, wrapped_index_vec))); return new VectorBlendNode(p2, p1, mask); } @@ -2783,7 +2812,7 @@ bool LibraryCallKit::inline_vector_select_from_two_vectors() { Node* wrap_mask = gvn().makecon(TypeInteger::make(indexRangeMask, indexRangeMask, Type::WidenMin, index_elem_bt != T_LONG ? T_INT : index_elem_bt)); Node* wrap_mask_vec = gvn().transform(VectorNode::scalar2vector(wrap_mask, num_elem, index_elem_bt, false)); opd1 = gvn().transform(VectorNode::make(Op_AndV, opd1, wrap_mask_vec, opd1->bottom_type()->is_vect())); - operation = gvn().transform(VectorNode::make(Op_SelectFromTwoVector, opd1, opd2, opd3, vt)); + operation = gvn().transform(trace_vector(VectorNode::make(Op_SelectFromTwoVector, opd1, opd2, opd3, vt))); } // Wrap it up in VectorBox to keep object type information. @@ -2868,7 +2897,7 @@ bool LibraryCallKit::inline_vector_compress_expand() { } const TypeVect* vt = TypeVect::make(elem_bt, num_elem, opc == Op_CompressM); - Node* operation = gvn().transform(VectorNode::make(opc, opd1, mask, vt)); + Node* operation = gvn().transform(trace_vector(VectorNode::make(opc, opd1, mask, vt))); // Wrap it up in VectorBox to keep object type information. const TypeInstPtr* box_type = opc == Op_CompressM ? mbox_type : vbox_type; @@ -3001,12 +3030,12 @@ bool LibraryCallKit::inline_index_vector() { default: fatal("%s", type2name(elem_bt)); } scale = gvn().transform(VectorNode::scalar2vector(scale, num_elem, elem_bt)); - index = gvn().transform(VectorNode::make(vmul_op, index, scale, vt)); + index = gvn().transform(trace_vector(VectorNode::make(vmul_op, index, scale, vt))); } // Add "opd" if addition is needed. if (needs_add) { - index = gvn().transform(VectorNode::make(vadd_op, opd, index, vt)); + index = gvn().transform(trace_vector(VectorNode::make(vadd_op, opd, index, vt))); } Node* vbox = box_vector(index, vbox_type, elem_bt, num_elem); set_result(vbox); @@ -3123,7 +3152,7 @@ bool LibraryCallKit::inline_index_partially_in_upper_range() { // Compute the vector mask with "mask = iota < indexLimit". ConINode* pred_node = (ConINode*)gvn().makecon(TypeInt::make(BoolTest::lt)); const TypeVect* vmask_type = TypeVect::makemask(elem_bt, num_elem); - mask = gvn().transform(new VectorMaskCmpNode(BoolTest::lt, iota, indexLimit, pred_node, vmask_type)); + mask = gvn().transform(trace_vector(new VectorMaskCmpNode(BoolTest::lt, iota, indexLimit, pred_node, vmask_type))); } Node* vbox = box_vector(mask, box_type, elem_bt, num_elem); set_result(vbox); diff --git a/src/hotspot/share/opto/vectorization.cpp b/src/hotspot/share/opto/vectorization.cpp index f81ee1b7ddb..8e0ca927a16 100644 --- a/src/hotspot/share/opto/vectorization.cpp +++ b/src/hotspot/share/opto/vectorization.cpp @@ -187,7 +187,10 @@ VStatus VLoopAnalyzer::setup_submodules_helper() { return body_status; } - _memory_slices.find_memory_slices(); + VStatus slices_status = _memory_slices.find_memory_slices(); + if (!slices_status.is_success()) { + return slices_status; + } // If there is no memory slice detected, it means there is no store. // If there is no reduction and no store, then we give up, because @@ -207,9 +210,11 @@ VStatus VLoopAnalyzer::setup_submodules_helper() { } // There are 2 kinds of slices: -// - No memory phi: only loads. All have the same input memory state from before the loop. +// - No memory phi: only loads. +// - Usually, all loads have the same input memory state from before the loop. +// - Only rarely this is not the case, and we just bail out for now. // - With memory phi. Chain of memory operations inside the loop. -void VLoopMemorySlices::find_memory_slices() { +VStatus VLoopMemorySlices::find_memory_slices() { Compile* C = _vloop.phase()->C; // We iterate over the body, which is topologically sorted. Hence, if there is a phi // in a slice, we will find it first, and the loads and stores afterwards. @@ -228,8 +233,15 @@ void VLoopMemorySlices::find_memory_slices() { PhiNode* head = _heads.at(alias_idx); if (head == nullptr) { // We did not find a phi on this slice yet -> must be a slice with only loads. - assert(_inputs.at(alias_idx) == nullptr || _inputs.at(alias_idx) == load->in(1), - "not yet touched or the same input"); + // For now, we can only handle slices with a single memory input before the loop, + // so if we find multiple, we bail out of auto vectorization. If this becomes + // too restrictive in the fututure, we could consider tracking multiple inputs. + // Different memory inputs can for example happen if one load has its memory state + // optimized, and the other load fails to have it optimized, for example because + // it does not end up on the IGVN worklist any more. + if (_inputs.at(alias_idx) != nullptr && _inputs.at(alias_idx) != load->in(1)) { + return VStatus::make_failure(FAILURE_DIFFERENT_MEMORY_INPUT); + } _inputs.at_put(alias_idx, load->in(1)); } // else: the load belongs to a slice with a phi that already set heads and inputs. #ifdef ASSERT @@ -243,6 +255,7 @@ void VLoopMemorySlices::find_memory_slices() { } } NOT_PRODUCT( if (_vloop.is_trace_memory_slices()) { print(); } ) + return VStatus::make_success(); } #ifndef PRODUCT @@ -1109,7 +1122,7 @@ Node* make_last(Node* initL, jint stride, Node* limitL, PhaseIdealLoop* phase) { Node* last = new AddLNode(initL, k_mul_stride); // Make sure that the last does not lie "before" init. - Node* last_clamped = MaxNode::build_min_max_long(&igvn, initL, last, stride > 0); + Node* last_clamped = MinMaxNode::build_min_max_long(&igvn, initL, last, stride > 0); phase->register_new_node_with_ctrl_of(diffL, initL); phase->register_new_node_with_ctrl_of(diffL_m1, initL); diff --git a/src/hotspot/share/opto/vectorization.hpp b/src/hotspot/share/opto/vectorization.hpp index 9308712f78a..b187435c04d 100644 --- a/src/hotspot/share/opto/vectorization.hpp +++ b/src/hotspot/share/opto/vectorization.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2023, Arm Limited. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -504,6 +504,8 @@ private: // class VLoopMemorySlices : public StackObj { private: + static constexpr char const* FAILURE_DIFFERENT_MEMORY_INPUT = "Load only slice has multiple memory inputs"; + const VLoop& _vloop; const VLoopBody& _body; @@ -521,7 +523,7 @@ public: const GrowableArray& inputs() const { return _inputs; } const GrowableArray& heads() const { return _heads; } - void find_memory_slices(); + VStatus find_memory_slices(); void get_slice_in_reverse_order(PhiNode* head, MemNode* tail, GrowableArray& slice) const; bool same_memory_slice(MemNode* m1, MemNode* m2) const; diff --git a/src/hotspot/share/opto/vectornode.cpp b/src/hotspot/share/opto/vectornode.cpp index 271dc901dcb..7401efaf7c2 100644 --- a/src/hotspot/share/opto/vectornode.cpp +++ b/src/hotspot/share/opto/vectornode.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2007, 2026, 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 @@ -1919,6 +1919,15 @@ Node* VectorMaskToLongNode::Ideal_MaskAll(PhaseGVN* phase) { // saved with a predicate type. if (in1->Opcode() == Op_VectorStoreMask) { Node* mask = in1->in(1); + // Skip the optimization if the mask is dead. + if (phase->type(mask) == Type::TOP) { + return nullptr; + } + // If the ideal graph is transformed correctly, the input mask should be a + // vector type node. Following optimization can ignore the mismatched type + // issue. But we still keep the sanity check for the mask type by using + // "is_vect()" in the assertion below, so that there can be less optimizations + // evolved before the compiler finally runs into a problem. assert(!Matcher::mask_op_prefers_predicate(Opcode(), mask->bottom_type()->is_vect()), "sanity"); in1 = mask; } diff --git a/src/hotspot/share/opto/vectornode.hpp b/src/hotspot/share/opto/vectornode.hpp index dc7aa13cf36..b1976d8f0db 100644 --- a/src/hotspot/share/opto/vectornode.hpp +++ b/src/hotspot/share/opto/vectornode.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2007, 2026, 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 @@ -33,7 +33,63 @@ #include "opto/opcodes.hpp" #include "prims/vectorSupport.hpp" -//------------------------------VectorNode------------------------------------- +//=======================Notes-for-VectorMask-Representation======================= +// +// There are three distinct representations for vector masks based on platform and +// use scenarios: +// +// - BVectMask: Platform-independent mask stored in a vector register with 8-bit +// lanes containing 1/0 values. The corresponding type is TypeVectA ~ TypeVectZ. +// +// - NVectMask: Platform-specific mask stored in vector registers with N-bit lanes, +// where all bits in each lane are either set (true) or unset (false). Generated +// on architectures without predicate/mask feature, such as AArch64 NEON, x86 +// AVX2, etc. The corresponding type is TypeVectA ~ TypeVectZ. +// +// - PVectMask: Platform-specific mask stored in predicate/mask registers. Generated +// on architectures with predicate/mask feature, such as AArch64 SVE, x86 AVX-512, +// and RISC-V Vector Extension (RVV). The corresponding type is TypeVectMask. +// +// NVectMask and PVectMask encode element data type and vector length information. +// They are the primary mask representations used in most mask and masked vector +// operations. BVectMask primarily represents mask values loaded from or stored to +// Java boolean memory (mask backing storage). VectorLoadMask/VectorStoreMask nodes +// are needed to transform it to/from P/NVectMask. It is also used in certain mask +// operations (e.g. VectorMaskOpNode). +// +//=========================Notes-for-Masked-Vector-Nodes=========================== +// +// Each lane-wise and cross-lane (reduction) ALU node supports both non-masked +// and masked operations. +// +// Currently masked vector nodes are only used to implement the Vector API's masked +// operations (which might also be used for auto-vectorization in future), such as: +// Vector lanewise(VectorOperators.Binary op, Vector v, VectorMask m) +// +// They are generated during intrinsification for Vector API, only on architectures +// that support the relevant predicated instructions. The compiler uses +// "Matcher::match_rule_supported_vector_masked()" to check whether the current +// platform supports the predicated/masked vector instructions for an operation. It +// generates the masked vector node for the operation if supported. Otherwise, it +// generates the unpredicated vector node and implements the masked operation with +// the help of a VectorBlendNode. Please see more details from API intrinsification +// in vectorIntrinsics.cpp. +// +// To differentiate the masked and non-masked nodes, flag "Flag_is_predicated_vector" +// is set for the masked version. Meanwhile, there is an additional mask input for +// the masked nodes. +// +// For example: +// - Non-masked version: +// in1 in2 +// \ / +// AddVBNode +// +// - Masked version (with "Flag_is_predicated_vector" being set): +// in1 in2 mask +// \ | / +// AddVBNode + // Vector Operation class VectorNode : public TypeNode { public: @@ -103,6 +159,8 @@ class VectorNode : public TypeNode { static bool implemented(int opc, uint vlen, BasicType bt); static bool is_shift(Node* n); static bool is_vshift_cnt(Node* n); + // Returns true if the lower vlen bits (bits [0, vlen-1]) of the long value + // are all 1s or all 0s, indicating a "mask all" or "mask none" pattern. static bool is_maskall_type(const TypeLong* type, int vlen); static bool is_muladds2i(const Node* n); static bool is_roundopD(Node* n); @@ -176,7 +234,6 @@ class SaturatingVectorNode : public VectorNode { bool is_unsigned() { return _is_unsigned; } }; -//------------------------------AddVBNode-------------------------------------- // Vector add byte class AddVBNode : public VectorNode { public: @@ -184,7 +241,6 @@ class AddVBNode : public VectorNode { virtual int Opcode() const; }; -//------------------------------AddVSNode-------------------------------------- // Vector add char/short class AddVSNode : public VectorNode { public: @@ -192,7 +248,6 @@ class AddVSNode : public VectorNode { virtual int Opcode() const; }; -//------------------------------AddVINode-------------------------------------- // Vector add int class AddVINode : public VectorNode { public: @@ -200,7 +255,6 @@ class AddVINode : public VectorNode { virtual int Opcode() const; }; -//------------------------------AddVLNode-------------------------------------- // Vector add long class AddVLNode : public VectorNode { public: @@ -208,7 +262,6 @@ public: virtual int Opcode() const; }; -//------------------------------AddVHFNode-------------------------------------- // Vector add float class AddVHFNode : public VectorNode { public: @@ -216,7 +269,6 @@ public: virtual int Opcode() const; }; -//------------------------------AddVFNode-------------------------------------- // Vector add float class AddVFNode : public VectorNode { public: @@ -224,7 +276,6 @@ public: virtual int Opcode() const; }; -//------------------------------AddVDNode-------------------------------------- // Vector add double class AddVDNode : public VectorNode { public: @@ -232,7 +283,6 @@ public: virtual int Opcode() const; }; -//------------------------------ReductionNode------------------------------------ // Perform reduction of a vector class ReductionNode : public Node { private: @@ -294,7 +344,6 @@ class ReductionNode : public Node { #endif }; -//------------------------------AddReductionVINode-------------------------------------- // Vector add byte, short and int as a reduction class AddReductionVINode : public ReductionNode { public: @@ -302,7 +351,6 @@ public: virtual int Opcode() const; }; -//------------------------------AddReductionVLNode-------------------------------------- // Vector add long as a reduction class AddReductionVLNode : public ReductionNode { public: @@ -310,7 +358,6 @@ public: virtual int Opcode() const; }; -//------------------------------AddReductionVFNode-------------------------------------- // Vector add float as a reduction class AddReductionVFNode : public ReductionNode { private: @@ -337,7 +384,6 @@ public: virtual uint size_of() const { return sizeof(*this); } }; -//------------------------------AddReductionVDNode-------------------------------------- // Vector add double as a reduction class AddReductionVDNode : public ReductionNode { private: @@ -364,7 +410,6 @@ public: virtual uint size_of() const { return sizeof(*this); } }; -//------------------------------SubVBNode-------------------------------------- // Vector subtract byte class SubVBNode : public VectorNode { public: @@ -372,7 +417,6 @@ class SubVBNode : public VectorNode { virtual int Opcode() const; }; -//------------------------------SubVSNode-------------------------------------- // Vector subtract short class SubVSNode : public VectorNode { public: @@ -380,7 +424,6 @@ class SubVSNode : public VectorNode { virtual int Opcode() const; }; -//------------------------------SubVINode-------------------------------------- // Vector subtract int class SubVINode : public VectorNode { public: @@ -388,7 +431,6 @@ class SubVINode : public VectorNode { virtual int Opcode() const; }; -//------------------------------SubVLNode-------------------------------------- // Vector subtract long class SubVLNode : public VectorNode { public: @@ -396,7 +438,6 @@ class SubVLNode : public VectorNode { virtual int Opcode() const; }; -//------------------------------SaturatingAddVNode----------------------------- // Vector saturating addition. class SaturatingAddVNode : public SaturatingVectorNode { public: @@ -404,7 +445,6 @@ class SaturatingAddVNode : public SaturatingVectorNode { virtual int Opcode() const; }; -//------------------------------SaturatingSubVNode----------------------------- // Vector saturating subtraction. class SaturatingSubVNode : public SaturatingVectorNode { public: @@ -412,7 +452,6 @@ class SaturatingSubVNode : public SaturatingVectorNode { virtual int Opcode() const; }; -//------------------------------SubVHFNode-------------------------------------- // Vector subtract half float class SubVHFNode : public VectorNode { public: @@ -420,8 +459,6 @@ public: virtual int Opcode() const; }; - -//------------------------------SubVFNode-------------------------------------- // Vector subtract float class SubVFNode : public VectorNode { public: @@ -429,7 +466,6 @@ class SubVFNode : public VectorNode { virtual int Opcode() const; }; -//------------------------------SubVDNode-------------------------------------- // Vector subtract double class SubVDNode : public VectorNode { public: @@ -437,7 +473,6 @@ class SubVDNode : public VectorNode { virtual int Opcode() const; }; -//------------------------------MulVBNode-------------------------------------- // Vector multiply byte class MulVBNode : public VectorNode { public: @@ -445,7 +480,6 @@ class MulVBNode : public VectorNode { virtual int Opcode() const; }; -//------------------------------MulVSNode-------------------------------------- // Vector multiply short class MulVSNode : public VectorNode { public: @@ -453,7 +487,6 @@ class MulVSNode : public VectorNode { virtual int Opcode() const; }; -//------------------------------MulVINode-------------------------------------- // Vector multiply int class MulVINode : public VectorNode { public: @@ -461,7 +494,6 @@ class MulVINode : public VectorNode { virtual int Opcode() const; }; -//------------------------------MulVLNode-------------------------------------- // Vector multiply long class MulVLNode : public VectorNode { public: @@ -473,7 +505,6 @@ public: bool has_uint_inputs() const; }; -//------------------------------MulVFNode-------------------------------------- // Vector multiply half float class MulVHFNode : public VectorNode { public: @@ -481,7 +512,6 @@ public: virtual int Opcode() const; }; -//------------------------------MulVFNode-------------------------------------- // Vector multiply float class MulVFNode : public VectorNode { public: @@ -489,7 +519,6 @@ public: virtual int Opcode() const; }; -//------------------------------MulVDNode-------------------------------------- // Vector multiply double class MulVDNode : public VectorNode { public: @@ -497,7 +526,6 @@ public: virtual int Opcode() const; }; -//------------------------------MulAddVS2VINode-------------------------------- // Vector multiply shorts to int and add adjacent ints. class MulAddVS2VINode : public VectorNode { public: @@ -505,7 +533,6 @@ class MulAddVS2VINode : public VectorNode { virtual int Opcode() const; }; -//------------------------------FmaVNode-------------------------------------- // Vector fused-multiply-add class FmaVNode : public VectorNode { public: @@ -515,7 +542,6 @@ public: virtual Node* Ideal(PhaseGVN* phase, bool can_reshape); }; -//------------------------------FmaVDNode-------------------------------------- // Vector fused-multiply-add double class FmaVDNode : public FmaVNode { public: @@ -523,7 +549,6 @@ public: virtual int Opcode() const; }; -//------------------------------FmaVFNode-------------------------------------- // Vector fused-multiply-add float class FmaVFNode : public FmaVNode { public: @@ -531,7 +556,6 @@ public: virtual int Opcode() const; }; -//------------------------------FmaVHFNode------------------------------------- // Vector fused-multiply-add half-precision float class FmaVHFNode : public FmaVNode { public: @@ -539,7 +563,6 @@ public: virtual int Opcode() const; }; -//------------------------------MulReductionVINode-------------------------------------- // Vector multiply byte, short and int as a reduction class MulReductionVINode : public ReductionNode { public: @@ -547,7 +570,6 @@ public: virtual int Opcode() const; }; -//------------------------------MulReductionVLNode-------------------------------------- // Vector multiply int as a reduction class MulReductionVLNode : public ReductionNode { public: @@ -555,7 +577,6 @@ public: virtual int Opcode() const; }; -//------------------------------MulReductionVFNode-------------------------------------- // Vector multiply float as a reduction class MulReductionVFNode : public ReductionNode { // True if mul reduction operation for floats requires strict ordering. @@ -581,7 +602,6 @@ public: virtual uint size_of() const { return sizeof(*this); } }; -//------------------------------MulReductionVDNode-------------------------------------- // Vector multiply double as a reduction class MulReductionVDNode : public ReductionNode { // True if mul reduction operation for doubles requires strict ordering. @@ -607,7 +627,6 @@ public: virtual uint size_of() const { return sizeof(*this); } }; -//------------------------------DivVHFNode------------------------------------- // Vector divide half float class DivVHFNode : public VectorNode { public: @@ -615,7 +634,6 @@ public: virtual int Opcode() const; }; -//------------------------------DivVFNode-------------------------------------- // Vector divide float class DivVFNode : public VectorNode { public: @@ -623,7 +641,6 @@ class DivVFNode : public VectorNode { virtual int Opcode() const; }; -//------------------------------DivVDNode-------------------------------------- // Vector Divide double class DivVDNode : public VectorNode { public: @@ -631,7 +648,6 @@ class DivVDNode : public VectorNode { virtual int Opcode() const; }; -//------------------------------AbsVBNode-------------------------------------- // Vector Abs byte class AbsVBNode : public VectorNode { public: @@ -639,7 +655,6 @@ public: virtual int Opcode() const; }; -//------------------------------AbsVSNode-------------------------------------- // Vector Abs short class AbsVSNode : public VectorNode { public: @@ -647,7 +662,6 @@ public: virtual int Opcode() const; }; -//------------------------------MinVNode-------------------------------------- // Vector Min class MinVNode : public VectorNode { public: @@ -655,7 +669,6 @@ public: virtual int Opcode() const; }; -//------------------------------MinVHFNode------------------------------------ // Vector Min for half floats class MinVHFNode : public VectorNode { public: @@ -663,7 +676,6 @@ public: virtual int Opcode() const; }; -//------------------------------MaxVHFNode------------------------------------ // Vector Max for half floats class MaxVHFNode : public VectorNode { public: @@ -671,6 +683,7 @@ public: virtual int Opcode() const; }; +// Vector Unsigned Min class UMinVNode : public VectorNode { public: UMinVNode(Node* in1, Node* in2, const TypeVect* vt) : VectorNode(in1, in2 ,vt) { @@ -681,8 +694,6 @@ class UMinVNode : public VectorNode { virtual int Opcode() const; }; - -//------------------------------MaxVNode-------------------------------------- // Vector Max class MaxVNode : public VectorNode { public: @@ -690,6 +701,7 @@ class MaxVNode : public VectorNode { virtual int Opcode() const; }; +// Vector Unsigned Max class UMaxVNode : public VectorNode { public: UMaxVNode(Node* in1, Node* in2, const TypeVect* vt) : VectorNode(in1, in2, vt) { @@ -700,7 +712,6 @@ class UMaxVNode : public VectorNode { virtual int Opcode() const; }; -//------------------------------AbsVINode-------------------------------------- // Vector Abs int class AbsVINode : public VectorNode { public: @@ -708,7 +719,6 @@ class AbsVINode : public VectorNode { virtual int Opcode() const; }; -//------------------------------AbsVLNode-------------------------------------- // Vector Abs long class AbsVLNode : public VectorNode { public: @@ -716,7 +726,6 @@ public: virtual int Opcode() const; }; -//------------------------------AbsVFNode-------------------------------------- // Vector Abs float class AbsVFNode : public VectorNode { public: @@ -724,7 +733,6 @@ class AbsVFNode : public VectorNode { virtual int Opcode() const; }; -//------------------------------AbsVDNode-------------------------------------- // Vector Abs double class AbsVDNode : public VectorNode { public: @@ -732,7 +740,6 @@ class AbsVDNode : public VectorNode { virtual int Opcode() const; }; -//------------------------------NegVNode--------------------------------------- // Vector Neg parent class (not for code generation). class NegVNode : public VectorNode { public: @@ -746,7 +753,6 @@ class NegVNode : public VectorNode { Node* degenerate_integral_negate(PhaseGVN* phase, bool is_predicated); }; -//------------------------------NegVINode-------------------------------------- // Vector Neg byte/short/int class NegVINode : public NegVNode { public: @@ -754,7 +760,6 @@ class NegVINode : public NegVNode { virtual int Opcode() const; }; -//------------------------------NegVLNode-------------------------------------- // Vector Neg long class NegVLNode : public NegVNode { public: @@ -762,7 +767,6 @@ class NegVLNode : public NegVNode { virtual int Opcode() const; }; -//------------------------------NegVFNode-------------------------------------- // Vector Neg float class NegVFNode : public NegVNode { public: @@ -770,7 +774,6 @@ class NegVFNode : public NegVNode { virtual int Opcode() const; }; -//------------------------------NegVDNode-------------------------------------- // Vector Neg double class NegVDNode : public NegVNode { public: @@ -778,7 +781,6 @@ class NegVDNode : public NegVNode { virtual int Opcode() const; }; -//------------------------------PopCountVINode--------------------------------- // Vector popcount integer bits class PopCountVINode : public VectorNode { public: @@ -786,7 +788,6 @@ class PopCountVINode : public VectorNode { virtual int Opcode() const; }; -//------------------------------PopCountVLNode--------------------------------- // Vector popcount long bits class PopCountVLNode : public VectorNode { public: @@ -796,7 +797,6 @@ class PopCountVLNode : public VectorNode { virtual int Opcode() const; }; -//------------------------------SqrtVHFNode------------------------------------- // Vector Sqrt half-precision float class SqrtVHFNode : public VectorNode { public: @@ -804,14 +804,13 @@ public: virtual int Opcode() const; }; -//------------------------------SqrtVFNode-------------------------------------- // Vector Sqrt float class SqrtVFNode : public VectorNode { public: SqrtVFNode(Node* in, const TypeVect* vt) : VectorNode(in,vt) {} virtual int Opcode() const; }; -//------------------------------RoundDoubleVNode-------------------------------- + // Vector round double class RoundDoubleModeVNode : public VectorNode { public: @@ -819,7 +818,6 @@ class RoundDoubleModeVNode : public VectorNode { virtual int Opcode() const; }; -//------------------------------SqrtVDNode-------------------------------------- // Vector Sqrt double class SqrtVDNode : public VectorNode { public: @@ -827,8 +825,7 @@ class SqrtVDNode : public VectorNode { virtual int Opcode() const; }; -//------------------------------ShiftVNode----------------------------------- -// Class ShiftV functionality. This covers the common behaviors for all kinds +// Class ShiftV functionality. This covers the common behaviors for all kinds // of vector shifts. class ShiftVNode : public VectorNode { private: @@ -848,7 +845,6 @@ class ShiftVNode : public VectorNode { virtual uint size_of() const { return sizeof(ShiftVNode); } }; -//------------------------------LShiftVBNode----------------------------------- // Vector left shift bytes class LShiftVBNode : public ShiftVNode { public: @@ -857,7 +853,6 @@ class LShiftVBNode : public ShiftVNode { virtual int Opcode() const; }; -//------------------------------LShiftVSNode----------------------------------- // Vector left shift shorts class LShiftVSNode : public ShiftVNode { public: @@ -866,7 +861,6 @@ class LShiftVSNode : public ShiftVNode { virtual int Opcode() const; }; -//------------------------------LShiftVINode----------------------------------- // Vector left shift ints class LShiftVINode : public ShiftVNode { public: @@ -875,7 +869,6 @@ class LShiftVINode : public ShiftVNode { virtual int Opcode() const; }; -//------------------------------LShiftVLNode----------------------------------- // Vector left shift longs class LShiftVLNode : public ShiftVNode { public: @@ -884,7 +877,6 @@ class LShiftVLNode : public ShiftVNode { virtual int Opcode() const; }; -//------------------------------RShiftVBNode----------------------------------- // Vector right arithmetic (signed) shift bytes class RShiftVBNode : public ShiftVNode { public: @@ -893,7 +885,6 @@ class RShiftVBNode : public ShiftVNode { virtual int Opcode() const; }; -//------------------------------RShiftVSNode----------------------------------- // Vector right arithmetic (signed) shift shorts class RShiftVSNode : public ShiftVNode { public: @@ -902,7 +893,6 @@ class RShiftVSNode : public ShiftVNode { virtual int Opcode() const; }; -//------------------------------RShiftVINode----------------------------------- // Vector right arithmetic (signed) shift ints class RShiftVINode : public ShiftVNode { public: @@ -911,7 +901,6 @@ class RShiftVINode : public ShiftVNode { virtual int Opcode() const; }; -//------------------------------RShiftVLNode----------------------------------- // Vector right arithmetic (signed) shift longs class RShiftVLNode : public ShiftVNode { public: @@ -920,7 +909,6 @@ class RShiftVLNode : public ShiftVNode { virtual int Opcode() const; }; -//------------------------------URShiftVBNode---------------------------------- // Vector right logical (unsigned) shift bytes class URShiftVBNode : public ShiftVNode { public: @@ -929,7 +917,6 @@ class URShiftVBNode : public ShiftVNode { virtual int Opcode() const; }; -//------------------------------URShiftVSNode---------------------------------- // Vector right logical (unsigned) shift shorts class URShiftVSNode : public ShiftVNode { public: @@ -938,7 +925,6 @@ class URShiftVSNode : public ShiftVNode { virtual int Opcode() const; }; -//------------------------------URShiftVINode---------------------------------- // Vector right logical (unsigned) shift ints class URShiftVINode : public ShiftVNode { public: @@ -947,7 +933,6 @@ class URShiftVINode : public ShiftVNode { virtual int Opcode() const; }; -//------------------------------URShiftVLNode---------------------------------- // Vector right logical (unsigned) shift longs class URShiftVLNode : public ShiftVNode { public: @@ -956,7 +941,6 @@ class URShiftVLNode : public ShiftVNode { virtual int Opcode() const; }; -//------------------------------LShiftCntVNode--------------------------------- // Vector left shift count class LShiftCntVNode : public VectorNode { public: @@ -964,7 +948,6 @@ class LShiftCntVNode : public VectorNode { virtual int Opcode() const; }; -//------------------------------RShiftCntVNode--------------------------------- // Vector right shift count class RShiftCntVNode : public VectorNode { public: @@ -972,7 +955,6 @@ class RShiftCntVNode : public VectorNode { virtual int Opcode() const; }; -//------------------------------AndVNode--------------------------------------- // Vector and integer class AndVNode : public VectorNode { public: @@ -981,7 +963,6 @@ class AndVNode : public VectorNode { virtual Node* Identity(PhaseGVN* phase); }; -//------------------------------AndReductionVNode-------------------------------------- // Vector and byte, short, int, long as a reduction class AndReductionVNode : public ReductionNode { public: @@ -989,8 +970,7 @@ class AndReductionVNode : public ReductionNode { virtual int Opcode() const; }; -//------------------------------OrVNode--------------------------------------- -// Vector or byte, short, int, long as a reduction +// Vector or integer class OrVNode : public VectorNode { public: OrVNode(Node* in1, Node* in2, const TypeVect* vt) : VectorNode(in1,in2,vt) {} @@ -998,15 +978,13 @@ class OrVNode : public VectorNode { virtual Node* Identity(PhaseGVN* phase); }; -//------------------------------OrReductionVNode-------------------------------------- -// Vector xor byte, short, int, long as a reduction +// Vector or byte, short, int, long as a reduction class OrReductionVNode : public ReductionNode { public: OrReductionVNode(Node* ctrl, Node* in1, Node* in2) : ReductionNode(ctrl, in1, in2) {} virtual int Opcode() const; }; -//------------------------------XorVNode--------------------------------------- // Vector xor integer class XorVNode : public VectorNode { public: @@ -1016,15 +994,13 @@ class XorVNode : public VectorNode { Node* Ideal_XorV_VectorMaskCmp(PhaseGVN* phase, bool can_reshape); }; -//------------------------------XorReductionVNode-------------------------------------- -// Vector and int, long as a reduction +// Vector xor byte, short, int, long as a reduction class XorReductionVNode : public ReductionNode { public: XorReductionVNode(Node* ctrl, Node* in1, Node* in2) : ReductionNode(ctrl, in1, in2) {} virtual int Opcode() const; }; -//------------------------------MinReductionVNode-------------------------------------- // Vector min byte, short, int, long, float, double as a reduction class MinReductionVNode : public ReductionNode { public: @@ -1032,15 +1008,13 @@ public: virtual int Opcode() const; }; -//------------------------------MaxReductionVNode-------------------------------------- -// Vector min byte, short, int, long, float, double as a reduction +// Vector max byte, short, int, long, float, double as a reduction class MaxReductionVNode : public ReductionNode { public: MaxReductionVNode(Node* ctrl, Node* in1, Node* in2) : ReductionNode(ctrl, in1, in2) {} virtual int Opcode() const; }; -//------------------------------CompressVNode-------------------------------------- // Vector compress class CompressVNode: public VectorNode { public: @@ -1051,6 +1025,7 @@ class CompressVNode: public VectorNode { virtual int Opcode() const; }; +// Vector mask compress class CompressMNode: public VectorNode { public: CompressMNode(Node* mask, const TypeVect* vt) : @@ -1060,7 +1035,6 @@ class CompressMNode: public VectorNode { virtual int Opcode() const; }; -//------------------------------ExpandVNode-------------------------------------- // Vector expand class ExpandVNode: public VectorNode { public: @@ -1073,7 +1047,6 @@ class ExpandVNode: public VectorNode { //================================= M E M O R Y =============================== -//------------------------------LoadVectorNode--------------------------------- // Load Vector from memory class LoadVectorNode : public LoadNode { private: @@ -1115,7 +1088,6 @@ class LoadVectorNode : public LoadNode { #endif }; -//------------------------------LoadVectorGatherNode------------------------------ // Load Vector from memory via index map class LoadVectorGatherNode : public LoadVectorNode { public: @@ -1138,7 +1110,6 @@ class LoadVectorGatherNode : public LoadVectorNode { } }; -//------------------------------StoreVectorNode-------------------------------- // Store Vector to memory class StoreVectorNode : public StoreNode { private: @@ -1180,9 +1151,7 @@ class StoreVectorNode : public StoreNode { #endif }; -//------------------------------StoreVectorScatterNode------------------------------ // Store Vector into memory via index map - class StoreVectorScatterNode : public StoreVectorNode { public: enum { Indices = 4 }; @@ -1200,7 +1169,6 @@ class StoreVectorNode : public StoreNode { virtual Node* indices() const { return in(Indices); } }; -//------------------------------StoreVectorMaskedNode-------------------------------- // Store Vector to memory under the influence of a predicate register(mask). class StoreVectorMaskedNode : public StoreVectorNode { public: @@ -1221,7 +1189,6 @@ class StoreVectorMaskedNode : public StoreVectorNode { virtual Node* mask() const { return in(Mask); } }; -//------------------------------LoadVectorMaskedNode-------------------------------- // Load Vector from memory under the influence of a predicate register(mask). class LoadVectorMaskedNode : public LoadVectorNode { public: @@ -1245,7 +1212,6 @@ class LoadVectorMaskedNode : public LoadVectorNode { } }; -//-------------------------------LoadVectorGatherMaskedNode--------------------------------- // Load Vector from memory via index map under the influence of a predicate register(mask). class LoadVectorGatherMaskedNode : public LoadVectorNode { public: @@ -1268,7 +1234,6 @@ class LoadVectorGatherMaskedNode : public LoadVectorNode { } }; -//------------------------------StoreVectorScatterMaskedNode-------------------------------- // Store Vector into memory via index map under the influence of a predicate register(mask). class StoreVectorScatterMaskedNode : public StoreVectorNode { public: @@ -1312,7 +1277,6 @@ public: virtual const Type *bottom_type() const { return in(1)->bottom_type(); } }; -//------------------------------VectorCmpMaskedNode-------------------------------- // Vector Comparison under the influence of a predicate register(mask). class VectorCmpMaskedNode : public TypeNode { public: @@ -1325,7 +1289,8 @@ class VectorCmpMaskedNode : public TypeNode { virtual int Opcode() const; }; -//------------------------------VectorMaskGenNode---------------------------------- +// Generate a vector mask based on the given length. Lanes with indices in +// [0, length) are set to true, while the remaining lanes are set to false. class VectorMaskGenNode : public TypeNode { public: VectorMaskGenNode(Node* length, const Type* ty): TypeNode(ty, 2) { @@ -1338,7 +1303,8 @@ class VectorMaskGenNode : public TypeNode { static Node* make(Node* length, BasicType vmask_bt, int vmask_len); }; -//------------------------------VectorMaskOpNode----------------------------------- +// Base class for certain vector mask operations. The supported input mask can +// be either "BVectMask" or "PVectMask" depending on the platform. class VectorMaskOpNode : public TypeNode { private: int _mopc; @@ -1359,6 +1325,7 @@ class VectorMaskOpNode : public TypeNode { static Node* make(Node* mask, const Type* ty, int mopc); }; +// Count the number of true (set) lanes in the vector mask. class VectorMaskTrueCountNode : public VectorMaskOpNode { public: VectorMaskTrueCountNode(Node* mask, const Type* ty): @@ -1366,6 +1333,8 @@ class VectorMaskTrueCountNode : public VectorMaskOpNode { virtual int Opcode() const; }; +// Returns the index of the first true (set) lane in the vector mask. +// If no lanes are set, returns the vector length. class VectorMaskFirstTrueNode : public VectorMaskOpNode { public: VectorMaskFirstTrueNode(Node* mask, const Type* ty): @@ -1373,6 +1342,8 @@ class VectorMaskFirstTrueNode : public VectorMaskOpNode { virtual int Opcode() const; }; +// Returns the index of the last true (set) lane in the vector mask. +// If no lanes are set, returns -1 . class VectorMaskLastTrueNode : public VectorMaskOpNode { public: VectorMaskLastTrueNode(Node* mask, const Type* ty): @@ -1380,6 +1351,10 @@ class VectorMaskLastTrueNode : public VectorMaskOpNode { virtual int Opcode() const; }; +// Pack the mask lane values into a long value, supporting at most the +// first 64 lanes. Each mask lane is packed into one bit in the long +// value, ordered from the least significant bit to the most significant +// bit. class VectorMaskToLongNode : public VectorMaskOpNode { public: VectorMaskToLongNode(Node* mask, const Type* ty): @@ -1391,6 +1366,9 @@ class VectorMaskToLongNode : public VectorMaskOpNode { virtual Node* Identity(PhaseGVN* phase); }; +// Unpack bits from a long value into vector mask lane values. Each bit +// in the long value is unpacked into one mask lane, ordered from the +// least significant bit to the sign bit. class VectorLongToMaskNode : public VectorNode { public: VectorLongToMaskNode(Node* mask, const TypeVect* ty): @@ -1400,36 +1378,40 @@ class VectorLongToMaskNode : public VectorNode { virtual Node* Ideal(PhaseGVN* phase, bool can_reshape); }; -//-------------------------- Vector mask broadcast ----------------------------------- +// Broadcast a scalar value to all lanes of a vector mask. All lanes are set +// to true if the input value is non-zero, or false if the input value is +// zero. This node is only used to generate a mask with "PVectMask" layout. class MaskAllNode : public VectorNode { public: MaskAllNode(Node* in, const TypeVect* vt) : VectorNode(in, vt) {} virtual int Opcode() const; }; -//--------------------------- Vector mask logical and -------------------------------- +// Perform a bitwise AND operation between two vector masks. This node is +// only used for vector masks with "PVectMask" layout. class AndVMaskNode : public AndVNode { public: AndVMaskNode(Node* in1, Node* in2, const TypeVect* vt) : AndVNode(in1, in2, vt) {} virtual int Opcode() const; }; -//--------------------------- Vector mask logical or --------------------------------- +// Perform a bitwise OR operation between two vector masks. This node is +// only used for vector masks with "PVectMask" layout. class OrVMaskNode : public OrVNode { public: OrVMaskNode(Node* in1, Node* in2, const TypeVect* vt) : OrVNode(in1, in2, vt) {} virtual int Opcode() const; }; -//--------------------------- Vector mask logical xor -------------------------------- +// Perform a bitwise XOR operation between two vector masks. This node is +// only used for vector masks with "PVectMask" layout. class XorVMaskNode : public XorVNode { public: XorVMaskNode(Node* in1, Node* in2, const TypeVect* vt) : XorVNode(in1, in2, vt) {} virtual int Opcode() const; }; -//=========================Promote_Scalar_to_Vector============================ - +// Replicate a scalar value to all lanes of a vector. class ReplicateNode : public VectorNode { public: ReplicateNode(Node* in1, const TypeVect* vt) : VectorNode(in1, vt) { @@ -1439,7 +1421,7 @@ class ReplicateNode : public VectorNode { virtual int Opcode() const; }; -//======================Populate_Indices_into_a_Vector========================= +// Populate indices into a vector. class PopulateIndexNode : public VectorNode { public: PopulateIndexNode(Node* in1, Node* in2, const TypeVect* vt) : VectorNode(in1, in2, vt) {} @@ -1448,7 +1430,6 @@ class PopulateIndexNode : public VectorNode { //========================Pack_Scalars_into_a_Vector=========================== -//------------------------------PackNode--------------------------------------- // Pack parent class (not for code generation). class PackNode : public VectorNode { public: @@ -1466,7 +1447,6 @@ class PackNode : public VectorNode { static PackNode* make(Node* s, uint vlen, BasicType bt); }; -//------------------------------PackBNode-------------------------------------- // Pack byte scalars into vector class PackBNode : public PackNode { public: @@ -1474,7 +1454,6 @@ class PackBNode : public PackNode { virtual int Opcode() const; }; -//------------------------------PackSNode-------------------------------------- // Pack short scalars into a vector class PackSNode : public PackNode { public: @@ -1483,7 +1462,6 @@ class PackSNode : public PackNode { virtual int Opcode() const; }; -//------------------------------PackINode-------------------------------------- // Pack integer scalars into a vector class PackINode : public PackNode { public: @@ -1492,7 +1470,6 @@ class PackINode : public PackNode { virtual int Opcode() const; }; -//------------------------------PackLNode-------------------------------------- // Pack long scalars into a vector class PackLNode : public PackNode { public: @@ -1501,7 +1478,6 @@ class PackLNode : public PackNode { virtual int Opcode() const; }; -//------------------------------Pack2LNode------------------------------------- // Pack 2 long scalars into a vector class Pack2LNode : public PackNode { public: @@ -1509,7 +1485,6 @@ class Pack2LNode : public PackNode { virtual int Opcode() const; }; -//------------------------------PackFNode-------------------------------------- // Pack float scalars into vector class PackFNode : public PackNode { public: @@ -1518,7 +1493,6 @@ class PackFNode : public PackNode { virtual int Opcode() const; }; -//------------------------------PackDNode-------------------------------------- // Pack double scalars into a vector class PackDNode : public PackNode { public: @@ -1527,7 +1501,6 @@ class PackDNode : public PackNode { virtual int Opcode() const; }; -//------------------------------Pack2DNode------------------------------------- // Pack 2 double scalars into a vector class Pack2DNode : public PackNode { public: @@ -1535,7 +1508,10 @@ class Pack2DNode : public PackNode { virtual int Opcode() const; }; - +// Load the IOTA constant vector containing sequential indices starting from 0 +// and incrementing by 1 up to "VLENGTH - 1". So far, the first input is an int +// constant 0. For example, a 128-bit vector with int (32-bit) elements produces +// a vector like "[0, 1, 2, 3]". class VectorLoadConstNode : public VectorNode { public: VectorLoadConstNode(Node* in1, const TypeVect* vt) : VectorNode(in1, vt) {} @@ -1544,8 +1520,7 @@ class VectorLoadConstNode : public VectorNode { //========================Extract_Scalar_from_Vector=========================== -//------------------------------ExtractNode------------------------------------ -// Extract a scalar from a vector at position "pos" +// The base class for all extract nodes. class ExtractNode : public Node { public: ExtractNode(Node* src, Node* pos) : Node(nullptr, src, pos) {} @@ -1554,8 +1529,7 @@ class ExtractNode : public Node { static int opcode(BasicType bt); }; -//------------------------------ExtractBNode----------------------------------- -// Extract a byte from a vector at position "pos" +// Extract a byte from a vector at position "pos". class ExtractBNode : public ExtractNode { public: ExtractBNode(Node* src, Node* pos) : ExtractNode(src, pos) {} @@ -1564,8 +1538,7 @@ class ExtractBNode : public ExtractNode { virtual uint ideal_reg() const { return Op_RegI; } }; -//------------------------------ExtractUBNode---------------------------------- -// Extract a boolean from a vector at position "pos" +// Extract a boolean from a vector at position "pos". class ExtractUBNode : public ExtractNode { public: ExtractUBNode(Node* src, Node* pos) : ExtractNode(src, pos) {} @@ -1574,8 +1547,7 @@ class ExtractUBNode : public ExtractNode { virtual uint ideal_reg() const { return Op_RegI; } }; -//------------------------------ExtractCNode----------------------------------- -// Extract a char from a vector at position "pos" +// Extract a char from a vector at position "pos". class ExtractCNode : public ExtractNode { public: ExtractCNode(Node* src, Node* pos) : ExtractNode(src, pos) {} @@ -1584,8 +1556,7 @@ class ExtractCNode : public ExtractNode { virtual uint ideal_reg() const { return Op_RegI; } }; -//------------------------------ExtractSNode----------------------------------- -// Extract a short from a vector at position "pos" +// Extract a short from a vector at position "pos". class ExtractSNode : public ExtractNode { public: ExtractSNode(Node* src, Node* pos) : ExtractNode(src, pos) {} @@ -1594,8 +1565,7 @@ class ExtractSNode : public ExtractNode { virtual uint ideal_reg() const { return Op_RegI; } }; -//------------------------------ExtractINode----------------------------------- -// Extract an int from a vector at position "pos" +// Extract an int from a vector at position "pos". class ExtractINode : public ExtractNode { public: ExtractINode(Node* src, Node* pos) : ExtractNode(src, pos) {} @@ -1604,8 +1574,7 @@ class ExtractINode : public ExtractNode { virtual uint ideal_reg() const { return Op_RegI; } }; -//------------------------------ExtractLNode----------------------------------- -// Extract a long from a vector at position "pos" +// Extract a long from a vector at position "pos". class ExtractLNode : public ExtractNode { public: ExtractLNode(Node* src, Node* pos) : ExtractNode(src, pos) {} @@ -1614,8 +1583,7 @@ class ExtractLNode : public ExtractNode { virtual uint ideal_reg() const { return Op_RegL; } }; -//------------------------------ExtractFNode----------------------------------- -// Extract a float from a vector at position "pos" +// Extract a float from a vector at position "pos". class ExtractFNode : public ExtractNode { public: ExtractFNode(Node* src, Node* pos) : ExtractNode(src, pos) {} @@ -1624,8 +1592,7 @@ class ExtractFNode : public ExtractNode { virtual uint ideal_reg() const { return Op_RegF; } }; -//------------------------------ExtractDNode----------------------------------- -// Extract a double from a vector at position "pos" +// Extract a double from a vector at position "pos". class ExtractDNode : public ExtractNode { public: ExtractDNode(Node* src, Node* pos) : ExtractNode(src, pos) {} @@ -1634,7 +1601,6 @@ class ExtractDNode : public ExtractNode { virtual uint ideal_reg() const { return Op_RegD; } }; -//------------------------------MacroLogicVNode------------------------------- // Vector logical operations packing node. class MacroLogicVNode : public VectorNode { private: @@ -1653,6 +1619,8 @@ public: Node* mask, uint truth_table, const TypeVect* vt); }; +// Compare two vectors lane-wise using the specified predicate and produce a +// vector mask. class VectorMaskCmpNode : public VectorNode { private: BoolTest::mask _predicate; @@ -1697,6 +1665,8 @@ class VectorMaskWrapperNode : public VectorNode { Node* vector_mask() const { return in(2); } }; +// Test whether all or any lanes in the first input vector mask is true, +// based on the specified predicate. class VectorTestNode : public CmpNode { private: BoolTest::mask _predicate; @@ -1719,6 +1689,9 @@ class VectorTestNode : public CmpNode { } }; +// Blend two vectors based on a vector mask. For each lane, select the value +// from the first input vector (vec1) if the corresponding mask lane is set, +// otherwise select from the second input vector (vec2). class VectorBlendNode : public VectorNode { public: VectorBlendNode(Node* vec1, Node* vec2, Node* mask) @@ -1732,11 +1705,14 @@ class VectorBlendNode : public VectorNode { Node* vec_mask() const { return in(3); } }; +// Rearrange lane elements from a source vector under the control of a shuffle +// (indexes) vector. Each lane in the shuffle vector specifies which lane from +// the source vector to select for the corresponding output lane. All indexes +// are in the range [0, VLENGTH). class VectorRearrangeNode : public VectorNode { public: VectorRearrangeNode(Node* vec1, Node* shuffle) : VectorNode(vec1, shuffle, vec1->bottom_type()->is_vect()) { - // assert(mask->is_VectorMask(), "VectorBlendNode requires that third argument be a mask"); } virtual int Opcode() const; @@ -1744,9 +1720,12 @@ class VectorRearrangeNode : public VectorNode { Node* vec_shuffle() const { return in(2); } }; - -// Select elements from two source vectors based on the wrapped indexes held in -// the first vector. +// Select lane elements from two source vectors ("src1" and "src2") under the +// control of an "indexes" vector. The two source vectors are logically concatenated +// to form a table of 2*VLENGTH elements, where src1 occupies indices [0, VLENGTH) +// and src2 occupies indices [VLENGTH, 2*VLENGTH). Each lane in the "indexes" +// vector specifies which element from this table to select for the corresponding +// output lane. class SelectFromTwoVectorNode : public VectorNode { public: SelectFromTwoVectorNode(Node* indexes, Node* src1, Node* src2, const TypeVect* vt) @@ -1758,13 +1737,19 @@ public: virtual int Opcode() const; }; -// The target may not directly support the rearrange operation for an element type. In those cases, -// we can transform the rearrange into a different element type. For example, on x86 before AVX512, -// there is no rearrange instruction for short elements, what we will then do is to transform the -// shuffle vector into one that we can do byte rearrange such that it would provide the same -// result. This could have been done in VectorRearrangeNode during code emission but we eagerly -// expand this out because it is often the case that an index vector is reused in many rearrange -// operations. This allows the index preparation to be GVN-ed as well as hoisted out of loops, etc. +// Transform a shuffle vector when the target does not directly support rearrange +// operations for the original element type. In such cases, the rearrange can be +// transformed to use a different element type. +// +// For example, on x86 before AVX512, there are no rearrange instructions for short +// elements. The shuffle vector is transformed into one suitable for byte rearrange +// that produces the same result. This could have been done in VectorRearrangeNode +// during code emission, but we eagerly expand it out because shuffle vectors are +// often reused in many rearrange operations. This allows the transformation to be +// GVN-ed and hoisted out of loops. +// +// Input: Original shuffle vector (indices for the desired element type) +// Output: Transformed shuffle vector (indices for the supported element type) class VectorLoadShuffleNode : public VectorNode { public: VectorLoadShuffleNode(Node* in, const TypeVect* vt) @@ -1773,6 +1758,8 @@ class VectorLoadShuffleNode : public VectorNode { virtual int Opcode() const; }; +// Convert a "BVectMask" into a platform-specific vector mask (either "NVectMask" +// or "PVectMask"). class VectorLoadMaskNode : public VectorNode { public: VectorLoadMaskNode(Node* in, const TypeVect* vt) : VectorNode(in, vt) { @@ -1784,6 +1771,8 @@ class VectorLoadMaskNode : public VectorNode { Node* Ideal(PhaseGVN* phase, bool can_reshape); }; +// Convert a platform-specific vector mask (either "NVectMask" or "PVectMask") +// into a "BVectMask". class VectorStoreMaskNode : public VectorNode { protected: VectorStoreMaskNode(Node* in1, ConINode* in2, const TypeVect* vt) : VectorNode(in1, in2, vt) {} @@ -1795,6 +1784,8 @@ class VectorStoreMaskNode : public VectorNode { static VectorStoreMaskNode* make(PhaseGVN& gvn, Node* in, BasicType in_type, uint num_elem); }; +// Lane-wise type cast a vector mask to the given vector type. The vector length +// of the input and output must be the same. class VectorMaskCastNode : public VectorNode { public: VectorMaskCastNode(Node* in, const TypeVect* vt) : VectorNode(in, vt) { @@ -1831,6 +1822,8 @@ class VectorReinterpretNode : public VectorNode { virtual int Opcode() const; }; +// Lane-wise type cast a vector to the given vector type. This is the base +// class for all vector type cast operations. class VectorCastNode : public VectorNode { public: VectorCastNode(Node* in, const TypeVect* vt) : VectorNode(in, vt) {} @@ -1843,6 +1836,7 @@ class VectorCastNode : public VectorNode { virtual Node* Identity(PhaseGVN* phase); }; +// Cast a byte vector to the given vector type. class VectorCastB2XNode : public VectorCastNode { public: VectorCastB2XNode(Node* in, const TypeVect* vt) : VectorCastNode(in, vt) { @@ -1851,6 +1845,7 @@ class VectorCastB2XNode : public VectorCastNode { virtual int Opcode() const; }; +// Cast a short vector to the given vector type. class VectorCastS2XNode : public VectorCastNode { public: VectorCastS2XNode(Node* in, const TypeVect* vt) : VectorCastNode(in, vt) { @@ -1859,6 +1854,7 @@ class VectorCastS2XNode : public VectorCastNode { virtual int Opcode() const; }; +// Cast an int vector to the given vector type. class VectorCastI2XNode : public VectorCastNode { public: VectorCastI2XNode(Node* in, const TypeVect* vt) : VectorCastNode(in, vt) { @@ -1867,6 +1863,7 @@ class VectorCastI2XNode : public VectorCastNode { virtual int Opcode() const; }; +// Cast a long vector to the given vector type. class VectorCastL2XNode : public VectorCastNode { public: VectorCastL2XNode(Node* in, const TypeVect* vt) : VectorCastNode(in, vt) { @@ -1875,6 +1872,7 @@ class VectorCastL2XNode : public VectorCastNode { virtual int Opcode() const; }; +// Cast a float vector to the given vector type. class VectorCastF2XNode : public VectorCastNode { public: VectorCastF2XNode(Node* in, const TypeVect* vt) : VectorCastNode(in, vt) { @@ -1883,6 +1881,7 @@ class VectorCastF2XNode : public VectorCastNode { virtual int Opcode() const; }; +// Cast a double vector to the given vector type. class VectorCastD2XNode : public VectorCastNode { public: VectorCastD2XNode(Node* in, const TypeVect* vt) : VectorCastNode(in, vt) { @@ -1891,6 +1890,7 @@ class VectorCastD2XNode : public VectorCastNode { virtual int Opcode() const; }; +// Cast a half float vector to float vector type. class VectorCastHF2FNode : public VectorCastNode { public: VectorCastHF2FNode(Node* in, const TypeVect* vt) : VectorCastNode(in, vt) { @@ -1899,6 +1899,7 @@ class VectorCastHF2FNode : public VectorCastNode { virtual int Opcode() const; }; +// Cast a float vector to a half float vector type. class VectorCastF2HFNode : public VectorCastNode { public: VectorCastF2HFNode(Node* in, const TypeVect* vt) : VectorCastNode(in, vt) { @@ -1907,8 +1908,12 @@ class VectorCastF2HFNode : public VectorCastNode { virtual int Opcode() const; }; -// So far, VectorUCastNode can only be used in Vector API unsigned extensions -// between integral types. E.g., extending byte to float is not supported now. +// Unsigned vector cast operations can only be used in unsigned (zero) +// extensions between integral types so far. E.g., extending byte to +// float is not supported now. + +// Unsigned cast a byte vector to the given vector type with short, int, +// or long element type. class VectorUCastB2XNode : public VectorCastNode { public: VectorUCastB2XNode(Node* in, const TypeVect* vt) : VectorCastNode(in, vt) { @@ -1920,6 +1925,8 @@ class VectorUCastB2XNode : public VectorCastNode { virtual int Opcode() const; }; +// Unsigned cast a short vector to the given vector type with int or long +// element type. class VectorUCastS2XNode : public VectorCastNode { public: VectorUCastS2XNode(Node* in, const TypeVect* vt) : VectorCastNode(in, vt) { @@ -1930,6 +1937,7 @@ class VectorUCastS2XNode : public VectorCastNode { virtual int Opcode() const; }; +// Unsigned cast an int vector to the given vector type with long element type. class VectorUCastI2XNode : public VectorCastNode { public: VectorUCastI2XNode(Node* in, const TypeVect* vt) : VectorCastNode(in, vt) { @@ -1939,6 +1947,7 @@ class VectorUCastI2XNode : public VectorCastNode { virtual int Opcode() const; }; +// Vector round float to nearest integer. class RoundVFNode : public VectorNode { public: RoundVFNode(Node* in, const TypeVect* vt) :VectorNode(in, vt) { @@ -1947,6 +1956,7 @@ class RoundVFNode : public VectorNode { virtual int Opcode() const; }; +// Vector round double to nearest integer. class RoundVDNode : public VectorNode { public: RoundVDNode(Node* in, const TypeVect* vt) : VectorNode(in, vt) { @@ -1955,6 +1965,7 @@ class RoundVDNode : public VectorNode { virtual int Opcode() const; }; +// Insert a new value into a vector lane at the specified position. class VectorInsertNode : public VectorNode { public: VectorInsertNode(Node* vsrc, Node* new_val, ConINode* pos, const TypeVect* vt) : VectorNode(vsrc, new_val, (Node*)pos, vt) { @@ -1968,6 +1979,9 @@ class VectorInsertNode : public VectorNode { static Node* make(Node* vec, Node* new_val, int position, PhaseGVN& gvn); }; +// Box a vector value into a Vector API object (e.g., IntMaxVector). +// This is a macro node that gets expanded during vector optimization +// phase. class VectorBoxNode : public Node { private: const TypeInstPtr* const _box_type; @@ -1995,6 +2009,8 @@ class VectorBoxNode : public Node { static const TypeFunc* vec_box_type(const TypeInstPtr* box_type); }; +// Allocate storage for boxing a vector value. This is used during vector +// box expansion. class VectorBoxAllocateNode : public CallStaticJavaNode { public: VectorBoxAllocateNode(Compile* C, const TypeInstPtr* vbox_type) @@ -2009,6 +2025,9 @@ class VectorBoxAllocateNode : public CallStaticJavaNode { #endif // !PRODUCT }; +// Unbox a Vector API object (e.g., IntMaxVector) to extract the underlying +// vector value. This is a macro node expanded during vector optimization +// phase. class VectorUnboxNode : public VectorNode { protected: uint size_of() const { return sizeof(*this); } @@ -2027,6 +2046,7 @@ class VectorUnboxNode : public VectorNode { Node* Ideal(PhaseGVN* phase, bool can_reshape); }; +// Lane-wise right rotation of the first input by the second input. class RotateRightVNode : public VectorNode { public: RotateRightVNode(Node* in1, Node* in2, const TypeVect* vt) @@ -2036,6 +2056,7 @@ public: Node* Ideal(PhaseGVN* phase, bool can_reshape); }; +// Lane-wise left rotation of the first input by the second input. class RotateLeftVNode : public VectorNode { public: RotateLeftVNode(Node* in1, Node* in2, const TypeVect* vt) @@ -2045,6 +2066,7 @@ public: Node* Ideal(PhaseGVN* phase, bool can_reshape); }; +// Count the number of leading zeros in each lane of the input. class CountLeadingZerosVNode : public VectorNode { public: CountLeadingZerosVNode(Node* in, const TypeVect* vt) @@ -2056,6 +2078,7 @@ class CountLeadingZerosVNode : public VectorNode { virtual int Opcode() const; }; +// Count the number of trailing zeros in each lane of the input. class CountTrailingZerosVNode : public VectorNode { public: CountTrailingZerosVNode(Node* in, const TypeVect* vt) @@ -2067,6 +2090,7 @@ class CountTrailingZerosVNode : public VectorNode { virtual int Opcode() const; }; +// Reverse the bits within each lane (e.g., 0b10110010 becomes 0b01001101). class ReverseVNode : public VectorNode { public: ReverseVNode(Node* in, const TypeVect* vt) @@ -2076,6 +2100,7 @@ public: virtual int Opcode() const; }; +// Reverse the byte order within each lane (e.g., 0x12345678 becomes 0x78563412). class ReverseBytesVNode : public VectorNode { public: ReverseBytesVNode(Node* in, const TypeVect* vt) @@ -2085,6 +2110,7 @@ public: virtual int Opcode() const; }; +// Vector signum float. class SignumVFNode : public VectorNode { public: SignumVFNode(Node* in1, Node* zero, Node* one, const TypeVect* vt) @@ -2093,6 +2119,7 @@ public: virtual int Opcode() const; }; +// Vector signum double. class SignumVDNode : public VectorNode { public: SignumVDNode(Node* in1, Node* zero, Node* one, const TypeVect* vt) @@ -2101,6 +2128,8 @@ public: virtual int Opcode() const; }; +// Compress (extract and pack) bits in each lane of the first input +// based on the mask input. class CompressBitsVNode : public VectorNode { public: CompressBitsVNode(Node* in, Node* mask, const TypeVect* vt) @@ -2108,6 +2137,8 @@ public: virtual int Opcode() const; }; +// Expand (deposit) bits in each lane of the first input based on the +// mask input. class ExpandBitsVNode : public VectorNode { public: ExpandBitsVNode(Node* in, Node* mask, const TypeVect* vt) diff --git a/src/hotspot/share/prims/jvm.cpp b/src/hotspot/share/prims/jvm.cpp index ef5aca96a57..6d6937a97e9 100644 --- a/src/hotspot/share/prims/jvm.cpp +++ b/src/hotspot/share/prims/jvm.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, 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 @@ -115,9 +115,6 @@ #if INCLUDE_MANAGEMENT #include "services/finalizerService.hpp" #endif -#ifdef LINUX -#include "osContainer_linux.hpp" -#endif #include @@ -500,11 +497,9 @@ JVM_LEAF(jboolean, JVM_IsUseContainerSupport(void)) JVM_END JVM_LEAF(jboolean, JVM_IsContainerized(void)) -#ifdef LINUX - if (OSContainer::is_containerized()) { + if (os::is_containerized()) { return JNI_TRUE; } -#endif return JNI_FALSE; JVM_END diff --git a/src/hotspot/share/prims/whitebox.cpp b/src/hotspot/share/prims/whitebox.cpp index 1c2f5527ff9..de5bc9ea58f 100644 --- a/src/hotspot/share/prims/whitebox.cpp +++ b/src/hotspot/share/prims/whitebox.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2026, 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 @@ -129,7 +129,6 @@ #ifdef LINUX #include "cgroupSubsystem_linux.hpp" #include "os_linux.hpp" -#include "osContainer_linux.hpp" #endif #define CHECK_JNI_EXCEPTION_(env, value) \ @@ -2582,14 +2581,12 @@ WB_ENTRY(jboolean, WB_CheckLibSpecifiesNoexecstack(JNIEnv* env, jobject o, jstri WB_END WB_ENTRY(jboolean, WB_IsContainerized(JNIEnv* env, jobject o)) - LINUX_ONLY(return OSContainer::is_containerized();) - return false; + return os::is_containerized(); WB_END // Physical memory of the host machine (including containers) WB_ENTRY(jlong, WB_HostPhysicalMemory(JNIEnv* env, jobject o)) - LINUX_ONLY(return static_cast(os::Linux::physical_memory());) - return static_cast(os::physical_memory()); + return static_cast(os::Machine::physical_memory()); WB_END // Available memory of the host machine (container-aware) diff --git a/src/hotspot/share/runtime/arguments.cpp b/src/hotspot/share/runtime/arguments.cpp index cf0a1ab9757..546ae610769 100644 --- a/src/hotspot/share/runtime/arguments.cpp +++ b/src/hotspot/share/runtime/arguments.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, 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 @@ -64,7 +64,6 @@ #include "runtime/vm_version.hpp" #include "services/management.hpp" #include "utilities/align.hpp" -#include "utilities/checkedCast.hpp" #include "utilities/debug.hpp" #include "utilities/defaultStream.hpp" #include "utilities/macros.hpp" @@ -1207,16 +1206,22 @@ bool Arguments::process_settings_file(const char* file_name, bool should_exist, } char token[1024]; - int pos = 0; + size_t pos = 0; bool in_white_space = true; bool in_comment = false; bool in_quote = false; - int quote_c = 0; + char quote_c = 0; bool result = true; - int c = getc(stream); - while(c != EOF && pos < (int)(sizeof(token)-1)) { + int c_or_eof = getc(stream); + while (c_or_eof != EOF && pos < (sizeof(token) - 1)) { + // We have checked the c_or_eof for EOF. getc should only ever return the + // EOF or an unsigned char converted to an int. We cast down to a char to + // avoid the char to int promotions we would otherwise do in the comparisons + // below (which would be incorrect if we ever compared to a non-ascii char), + // and the int to char conversions we would otherwise do in the assignments. + const char c = static_cast(c_or_eof); if (in_white_space) { if (in_comment) { if (c == '\n') in_comment = false; @@ -1224,7 +1229,7 @@ bool Arguments::process_settings_file(const char* file_name, bool should_exist, if (c == '#') in_comment = true; else if (!isspace((unsigned char) c)) { in_white_space = false; - token[pos++] = checked_cast(c); + token[pos++] = c; } } } else { @@ -1244,10 +1249,10 @@ bool Arguments::process_settings_file(const char* file_name, bool should_exist, } else if (in_quote && (c == quote_c)) { in_quote = false; } else { - token[pos++] = checked_cast(c); + token[pos++] = c; } } - c = getc(stream); + c_or_eof = getc(stream); } if (pos > 0) { token[pos] = '\0'; diff --git a/src/hotspot/share/runtime/atomic.hpp b/src/hotspot/share/runtime/atomic.hpp index 0c335913b5c..f708e9c18ca 100644 --- a/src/hotspot/share/runtime/atomic.hpp +++ b/src/hotspot/share/runtime/atomic.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, 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,6 +26,7 @@ #define SHARE_RUNTIME_ATOMIC_HPP #include "cppstdlib/type_traits.hpp" +#include "metaprogramming/dependentAlwaysFalse.hpp" #include "metaprogramming/primitiveConversions.hpp" #include "runtime/atomicAccess.hpp" #include "utilities/globalDefinitions.hpp" @@ -74,6 +75,7 @@ // v.release_store(x) -> void // v.release_store_fence(x) -> void // v.compare_exchange(x, y [, o]) -> T +// v.compare_set(x, y [, o]) -> bool // v.exchange(x [, o]) -> T // // (2) All atomic types are default constructible. @@ -266,6 +268,11 @@ public: return AtomicAccess::cmpxchg(value_ptr(), compare_value, new_value, order); } + bool compare_set(T compare_value, T new_value, + atomic_memory_order order = memory_order_conservative) { + return compare_exchange(compare_value, new_value, order) == compare_value; + } + T exchange(T new_value, atomic_memory_order order = memory_order_conservative) { return AtomicAccess::xchg(this->value_ptr(), new_value, order); @@ -478,6 +485,13 @@ public: order)); } + bool compare_set(T compare_value, T new_value, + atomic_memory_order order = memory_order_conservative) { + return _value.compare_set(decay(compare_value), + decay(new_value), + order); + } + T exchange(T new_value, atomic_memory_order order = memory_order_conservative) { return recover(_value.exchange(decay(new_value), order)); } diff --git a/src/hotspot/share/runtime/globals.hpp b/src/hotspot/share/runtime/globals.hpp index b3e9edc0a80..6a2bbc9c648 100644 --- a/src/hotspot/share/runtime/globals.hpp +++ b/src/hotspot/share/runtime/globals.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, 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 @@ -809,6 +809,7 @@ const int ObjectAlignmentInBytes = 8; \ develop(uintx, PreallocatedOutOfMemoryErrorCount, 4, \ "Number of OutOfMemoryErrors preallocated with backtrace") \ + range(0, 1024) \ \ product(bool, UseXMMForArrayCopy, false, \ "Use SSE2 MOVQ instruction for Arraycopy") \ diff --git a/src/hotspot/share/runtime/handshake.cpp b/src/hotspot/share/runtime/handshake.cpp index 89b02717a7a..b54068d65d6 100644 --- a/src/hotspot/share/runtime/handshake.cpp +++ b/src/hotspot/share/runtime/handshake.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, 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 @@ -521,8 +521,8 @@ HandshakeOperation* HandshakeState::get_op_for_self(bool allow_suspend, bool che assert(_lock.owned_by_self(), "Lock must be held"); assert(allow_suspend || !check_async_exception, "invalid case"); #if INCLUDE_JVMTI - if (allow_suspend && _handshakee->is_disable_suspend()) { - // filter out suspend operations while JavaThread is in disable_suspend mode + if (allow_suspend && (_handshakee->is_disable_suspend() || _handshakee->is_vthread_transition_disabler())) { + // filter out suspend operations while JavaThread can not be suspended allow_suspend = false; } #endif diff --git a/src/hotspot/share/runtime/java.cpp b/src/hotspot/share/runtime/java.cpp index c49a9f5d4b8..ee4f776df06 100644 --- a/src/hotspot/share/runtime/java.cpp +++ b/src/hotspot/share/runtime/java.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, 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 @@ -466,10 +466,7 @@ void before_exit(JavaThread* thread, bool halt) { event.commit(); } - // 2nd argument (emit_event_shutdown) should be set to false - // because EventShutdown would be emitted at Threads::destroy_vm(). - // (one of the callers of before_exit()) - JFR_ONLY(Jfr::on_vm_shutdown(true, false, halt);) + JFR_ONLY(Jfr::on_vm_shutdown(false, halt);) // Stop the WatcherThread. We do this before disenrolling various // PeriodicTasks to reduce the likelihood of races. diff --git a/src/hotspot/share/runtime/javaThread.cpp b/src/hotspot/share/runtime/javaThread.cpp index 4ee9a9dfd79..e73347f35d8 100644 --- a/src/hotspot/share/runtime/javaThread.cpp +++ b/src/hotspot/share/runtime/javaThread.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2021, Azul Systems, Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -499,7 +499,7 @@ JavaThread::JavaThread(MemTag mem_tag) : _suspend_resume_manager(this, &_handshake._lock), _is_in_vthread_transition(false), - DEBUG_ONLY(_is_vthread_transition_disabler(false) COMMA) + JVMTI_ONLY(_is_vthread_transition_disabler(false) COMMA) DEBUG_ONLY(_is_disabler_at_start(false) COMMA) _popframe_preserved_args(nullptr), @@ -1165,11 +1165,13 @@ void JavaThread::set_is_in_vthread_transition(bool val) { AtomicAccess::store(&_is_in_vthread_transition, val); } -#ifdef ASSERT +#if INCLUDE_JVMTI void JavaThread::set_is_vthread_transition_disabler(bool val) { _is_vthread_transition_disabler = val; } +#endif +#ifdef ASSERT void JavaThread::set_is_disabler_at_start(bool val) { _is_disabler_at_start = val; } @@ -1183,7 +1185,9 @@ void JavaThread::set_is_disabler_at_start(bool val) { // bool JavaThread::java_suspend(bool register_vthread_SR) { // Suspending a vthread transition disabler can cause deadlocks. - assert(!is_vthread_transition_disabler(), "no suspend allowed for vthread transition disablers"); + // The HandshakeState::has_operation does not allow such suspends. + // But the suspender thread is an exclusive transition disablers, so there can't be other disabers here. + JVMTI_ONLY(assert(!is_vthread_transition_disabler(), "suspender thread is an exclusive transition disabler");) guarantee(Thread::is_JavaThread_protected(/* target */ this), "target JavaThread is not protected in calling context."); diff --git a/src/hotspot/share/runtime/javaThread.hpp b/src/hotspot/share/runtime/javaThread.hpp index d4c12887e10..1aae37c0697 100644 --- a/src/hotspot/share/runtime/javaThread.hpp +++ b/src/hotspot/share/runtime/javaThread.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2021, Azul Systems, Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -734,14 +734,14 @@ public: private: bool _is_in_vthread_transition; // thread is in virtual thread mount state transition - DEBUG_ONLY(bool _is_vthread_transition_disabler;) // thread currently disabled vthread transitions + JVMTI_ONLY(bool _is_vthread_transition_disabler;) // thread currently disabled vthread transitions DEBUG_ONLY(bool _is_disabler_at_start;) // thread at process of disabling vthread transitions public: bool is_in_vthread_transition() const; void set_is_in_vthread_transition(bool val); + JVMTI_ONLY(bool is_vthread_transition_disabler() const { return _is_vthread_transition_disabler; }) + JVMTI_ONLY(void set_is_vthread_transition_disabler(bool val);) #ifdef ASSERT - bool is_vthread_transition_disabler() const { return _is_vthread_transition_disabler; } - void set_is_vthread_transition_disabler(bool val); bool is_disabler_at_start() const { return _is_disabler_at_start; } void set_is_disabler_at_start(bool val); #endif diff --git a/src/hotspot/share/runtime/mountUnmountDisabler.cpp b/src/hotspot/share/runtime/mountUnmountDisabler.cpp index 8635eeb2dcc..65a82d6c563 100644 --- a/src/hotspot/share/runtime/mountUnmountDisabler.cpp +++ b/src/hotspot/share/runtime/mountUnmountDisabler.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, 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 @@ -129,7 +129,8 @@ bool MountUnmountDisabler::is_start_transition_disabled(JavaThread* thread, oop int base_disable_count = notify_jvmti_events() ? 1 : 0; return java_lang_Thread::vthread_transition_disable_count(vthread) > 0 || global_vthread_transition_disable_count() > base_disable_count - JVMTI_ONLY(|| (JvmtiVTSuspender::is_vthread_suspended(java_lang_Thread::thread_id(vthread)) || thread->is_suspended())); + JVMTI_ONLY(|| (!thread->is_vthread_transition_disabler() && + (JvmtiVTSuspender::is_vthread_suspended(java_lang_Thread::thread_id(vthread)) || thread->is_suspended()))); } void MountUnmountDisabler::start_transition(JavaThread* current, oop vthread, bool is_mount, bool is_thread_end) { @@ -294,7 +295,7 @@ MountUnmountDisabler::disable_transition_for_one() { // carrierThread to float up. // This pairs with the release barrier in end_transition(). OrderAccess::acquire(); - DEBUG_ONLY(JavaThread::current()->set_is_vthread_transition_disabler(true);) + JVMTI_ONLY(JavaThread::current()->set_is_vthread_transition_disabler(true);) } // disable transitions for all virtual threads @@ -335,7 +336,7 @@ MountUnmountDisabler::disable_transition_for_all() { // carrierThread to float up. // This pairs with the release barrier in end_transition(). OrderAccess::acquire(); - DEBUG_ONLY(thread->set_is_vthread_transition_disabler(true);) + JVMTI_ONLY(JavaThread::current()->set_is_vthread_transition_disabler(true);) DEBUG_ONLY(thread->set_is_disabler_at_start(false);) } @@ -358,14 +359,12 @@ MountUnmountDisabler::enable_transition_for_one() { if (java_lang_Thread::vthread_transition_disable_count(_vthread()) == 0) { ml.notify_all(); } - DEBUG_ONLY(JavaThread::current()->set_is_vthread_transition_disabler(false);) + JVMTI_ONLY(JavaThread::current()->set_is_vthread_transition_disabler(false);) } // enable transitions for all virtual threads void MountUnmountDisabler::enable_transition_for_all() { - JavaThread* thread = JavaThread::current(); - // End of the critical section. If some target was unmounted, we need a // release barrier before decrementing _global_vthread_transition_disable_count // to make sure any memory operations executed by the disabler are visible to @@ -384,7 +383,7 @@ MountUnmountDisabler::enable_transition_for_all() { if (global_vthread_transition_disable_count() == base_disable_count || _is_exclusive) { ml.notify_all(); } - DEBUG_ONLY(thread->set_is_vthread_transition_disabler(false);) + JVMTI_ONLY(JavaThread::current()->set_is_vthread_transition_disabler(false);) } int MountUnmountDisabler::global_vthread_transition_disable_count() { diff --git a/src/hotspot/share/runtime/os.cpp b/src/hotspot/share/runtime/os.cpp index cf18388c625..57971897400 100644 --- a/src/hotspot/share/runtime/os.cpp +++ b/src/hotspot/share/runtime/os.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, 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 @@ -81,10 +81,6 @@ #include "utilities/permitForbiddenFunctions.hpp" #include "utilities/powerOfTwo.hpp" -#ifdef LINUX -#include "osContainer_linux.hpp" -#endif - #ifndef _WINDOWS # include #endif @@ -2205,19 +2201,60 @@ static void assert_nonempty_range(const char* addr, size_t bytes) { } bool os::used_memory(physical_memory_size_type& value) { -#ifdef LINUX - if (OSContainer::is_containerized()) { - return OSContainer::memory_usage_in_bytes(value); + if (is_containerized()) { + return Container::used_memory(value); } -#endif + + return Machine::used_memory(value); +} + +bool os::Machine::used_memory(physical_memory_size_type& value) { physical_memory_size_type avail_mem = 0; // Return value ignored - defaulting to 0 on failure. - (void)os::available_memory(avail_mem); - physical_memory_size_type phys_mem = os::physical_memory(); + (void)os::Machine::available_memory(avail_mem); + physical_memory_size_type phys_mem = os::Machine::physical_memory(); value = phys_mem - avail_mem; return true; } +#ifndef LINUX +bool os::is_containerized() { + return false; +} + +bool os::Container::processor_count(double& value) { + return false; +} + +bool os::Container::available_memory(physical_memory_size_type& value) { + return false; +} + +bool os::Container::used_memory(physical_memory_size_type& value) { + return false; +} + +bool os::Container::total_swap_space(physical_memory_size_type& value) { + return false; +} + +bool os::Container::free_swap_space(physical_memory_size_type& value) { + return false; +} + +bool os::Container::memory_limit(physical_memory_size_type& value) { + return false; +} + +bool os::Container::memory_soft_limit(physical_memory_size_type& value) { + return false; +} + +bool os::Container::memory_throttle_limit(physical_memory_size_type& value) { + return false; +} +#endif + bool os::commit_memory(char* addr, size_t bytes, bool executable) { assert_nonempty_range(addr, bytes); diff --git a/src/hotspot/share/runtime/os.hpp b/src/hotspot/share/runtime/os.hpp index d585d3e5fc0..29c872157fd 100644 --- a/src/hotspot/share/runtime/os.hpp +++ b/src/hotspot/share/runtime/os.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, 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 @@ -342,6 +342,52 @@ class os: AllStatic { static bool is_server_class_machine(); static size_t rss(); + // On platforms with container support (currently only Linux) we combine machine values with + // potential container values in os:: methods, abstracting which value is actually used. + // The os::Machine and os::Container classes and containing methods are used to get machine + // and container values (when available) separately. + static bool is_containerized(); + + // The os::Machine class reports system resource metrics from the perspective of the operating + // system, without considering container-imposed limits. The values returned by these methods + // reflect the resources visible to the process as reported by the OS, and may already be + // affected by mechanisms such as virtualization, hypervisor limits, or process affinity, + // but do NOT consider further restrictions imposed by container runtimes (e.g., cgroups) + class Machine : AllStatic { + public: + static int active_processor_count(); + + [[nodiscard]] static bool available_memory(physical_memory_size_type& value); + [[nodiscard]] static bool used_memory(physical_memory_size_type& value); + [[nodiscard]] static bool free_memory(physical_memory_size_type& value); + + [[nodiscard]] static bool total_swap_space(physical_memory_size_type& value); + [[nodiscard]] static bool free_swap_space(physical_memory_size_type& value); + + static physical_memory_size_type physical_memory(); + }; + + // The os::Container class reports resource limits as imposed by a supported container runtime + // (currently only cgroup-based Linux runtimes). If the process is running inside a + // containerized environment, methods from this class report the effective limits imposed + // by the container, which may be more restrictive than what os::Machine reports. + // Methods return true and set the out-parameter if a limit is found, + // or false if no limit exists or it cannot be determined. + class Container : AllStatic { + public: + [[nodiscard]] static bool processor_count(double& value); // Returns the core-equivalent CPU quota + + [[nodiscard]] static bool available_memory(physical_memory_size_type& value); + [[nodiscard]] static bool used_memory(physical_memory_size_type& value); + + [[nodiscard]] static bool total_swap_space(physical_memory_size_type& value); + [[nodiscard]] static bool free_swap_space(physical_memory_size_type& value); + + [[nodiscard]] static bool memory_limit(physical_memory_size_type& value); + [[nodiscard]] static bool memory_soft_limit(physical_memory_size_type& value); + [[nodiscard]] static bool memory_throttle_limit(physical_memory_size_type& value); + }; + // Returns the id of the processor on which the calling thread is currently executing. // The returned value is guaranteed to be between 0 and (os::processor_count() - 1). static uint processor_id(); diff --git a/src/hotspot/share/runtime/prefetch.hpp b/src/hotspot/share/runtime/prefetch.hpp deleted file mode 100644 index 601337c14af..00000000000 --- a/src/hotspot/share/runtime/prefetch.hpp +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2003, 2022, 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. - * - */ - -#ifndef SHARE_RUNTIME_PREFETCH_HPP -#define SHARE_RUNTIME_PREFETCH_HPP - -#include "memory/allStatic.hpp" - -// If calls to prefetch methods are in a loop, the loop should be cloned -// such that if Prefetch{Scan,Copy}Interval and/or PrefetchFieldInterval -// say not to do prefetching, these methods aren't called. At the very -// least, they take up a memory issue slot. They should be implemented -// as inline assembly code: doing an actual call isn't worth the cost. - -class Prefetch : AllStatic { - public: - // Prefetch anticipating read; must not fault, semantically a no-op - static void read(const void* loc, intx interval); - - // Prefetch anticipating write; must not fault, semantically a no-op - static void write(void* loc, intx interval); -}; - -#endif // SHARE_RUNTIME_PREFETCH_HPP diff --git a/src/hotspot/share/runtime/prefetch.inline.hpp b/src/hotspot/share/runtime/prefetch.inline.hpp index 4cc1d93f613..18630f71a62 100644 --- a/src/hotspot/share/runtime/prefetch.inline.hpp +++ b/src/hotspot/share/runtime/prefetch.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2026, 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,9 +25,24 @@ #ifndef SHARE_RUNTIME_PREFETCH_INLINE_HPP #define SHARE_RUNTIME_PREFETCH_INLINE_HPP -#include "runtime/prefetch.hpp" - +#include "memory/allStatic.hpp" #include "utilities/macros.hpp" + +// If calls to prefetch methods are in a loop, the loop should be cloned +// such that if Prefetch{Scan,Copy}Interval and/or PrefetchFieldInterval +// say not to do prefetching, these methods aren't called. At the very +// least, they take up a memory issue slot. They should be implemented +// as inline assembly code: doing an actual call isn't worth the cost. + +class Prefetch : AllStatic { + public: + // Prefetch anticipating read; must not fault, semantically a no-op + static void read(const void* loc, intx interval); + + // Prefetch anticipating write; must not fault, semantically a no-op + static void write(void* loc, intx interval); +}; + #include OS_CPU_HEADER_INLINE(prefetch) #endif // SHARE_RUNTIME_PREFETCH_INLINE_HPP diff --git a/src/hotspot/share/runtime/sharedRuntime.cpp b/src/hotspot/share/runtime/sharedRuntime.cpp index 7e3bf763d08..d426fd31a43 100644 --- a/src/hotspot/share/runtime/sharedRuntime.cpp +++ b/src/hotspot/share/runtime/sharedRuntime.cpp @@ -3362,7 +3362,7 @@ JRT_LEAF(intptr_t*, SharedRuntime::OSR_migration_begin( JavaThread *current) ) RegisterMap::WalkContinuation::skip); frame sender = fr.sender(&map); if (sender.is_interpreted_frame()) { - current->push_cont_fastpath(sender.sp()); + current->push_cont_fastpath(sender.unextended_sp()); } return buf; diff --git a/src/hotspot/share/runtime/sharedRuntime.hpp b/src/hotspot/share/runtime/sharedRuntime.hpp index 140207e5d63..11bd39c839f 100644 --- a/src/hotspot/share/runtime/sharedRuntime.hpp +++ b/src/hotspot/share/runtime/sharedRuntime.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, 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,7 +30,6 @@ #include "code/vmreg.hpp" #include "interpreter/linkResolver.hpp" #include "memory/allStatic.hpp" -#include "memory/metaspaceClosure.hpp" #include "memory/resourceArea.hpp" #include "runtime/safepointVerifiers.hpp" #include "runtime/stubInfo.hpp" @@ -38,6 +37,7 @@ class AdapterHandlerEntry; class AdapterFingerPrint; +class MetaspaceClosure; class vframeStream; // Runtime is the base class for various runtime interfaces diff --git a/src/hotspot/share/runtime/stubDeclarations.hpp b/src/hotspot/share/runtime/stubDeclarations.hpp index f9080364dc4..c478eda3e7c 100644 --- a/src/hotspot/share/runtime/stubDeclarations.hpp +++ b/src/hotspot/share/runtime/stubDeclarations.hpp @@ -191,8 +191,7 @@ // // C2 stub blob/field names // -// C2 stubs are provided with names in the format "C2 Runtime -// _blob". +// C2 stubs are provided with names in the format "_blob (C2 runtime)". // // A stub creation method OptoRuntime::generate(ciEnv* env) is // generated which invokes the C2 compiler to generate each stub in diff --git a/src/hotspot/share/runtime/stubInfo.cpp b/src/hotspot/share/runtime/stubInfo.cpp index 47a31fe7967..ee90631145a 100644 --- a/src/hotspot/share/runtime/stubInfo.cpp +++ b/src/hotspot/share/runtime/stubInfo.cpp @@ -479,7 +479,7 @@ void StubInfo::process_stubgen_entry(StubGroup& group_cursor, #define PROCESS_SHARED_BLOB(name, type) \ process_shared_blob(_group_cursor, _blob_cursor, \ _stub_cursor, _entry_cursor, \ - "Shared Runtime " # name "_blob", \ + #name "_blob (shared runtime)", \ BlobId:: JOIN3(shared, name, id), \ StubId:: JOIN3(shared, name, id), \ EntryId:: JOIN3(shared, name, id), \ @@ -488,7 +488,7 @@ void StubInfo::process_stubgen_entry(StubGroup& group_cursor, #define PROCESS_C1_BLOB(name) \ process_c1_blob(_group_cursor, _blob_cursor, \ _stub_cursor, _entry_cursor, \ - "C1 Runtime " # name "_blob", \ + #name "_blob (C1 runtime)", \ BlobId:: JOIN3(c1, name, id), \ StubId:: JOIN3(c1, name, id), \ EntryId:: JOIN3(c1, name, id)); \ @@ -496,7 +496,7 @@ void StubInfo::process_stubgen_entry(StubGroup& group_cursor, #define PROCESS_C2_BLOB(name, type) \ process_c2_blob(_group_cursor, _blob_cursor, \ _stub_cursor, _entry_cursor, \ - "C2 Runtime " # name "_blob", \ + #name "_blob (C2 runtime)", \ BlobId:: JOIN3(c2, name, id), \ StubId:: JOIN3(c2, name, id), \ EntryId:: JOIN3(c2, name, id)); \ @@ -504,7 +504,7 @@ void StubInfo::process_stubgen_entry(StubGroup& group_cursor, #define PROCESS_C2_STUB(name, fancy_jump, pass_tls, return_pc) \ process_c2_blob(_group_cursor, _blob_cursor, \ _stub_cursor, _entry_cursor, \ - "C2 Runtime " # name "_blob", \ + #name "_blob (C2 runtime)", \ BlobId:: JOIN3(c2, name, id), \ StubId:: JOIN3(c2, name, id), \ EntryId:: JOIN3(c2, name, id)); \ @@ -512,20 +512,20 @@ void StubInfo::process_stubgen_entry(StubGroup& group_cursor, #define PROCESS_STUBGEN_BLOB(blob) \ process_stubgen_blob(_group_cursor, _blob_cursor, \ _stub_cursor, _entry_cursor, \ - "Stub Generator " # blob "_blob", \ + #blob "_blob (stub gen)", \ BlobId:: JOIN3(stubgen, blob, id)); \ #define PROCESS_STUBGEN_STUB(blob, stub) \ process_stubgen_stub(_group_cursor, _blob_cursor, \ _stub_cursor, _entry_cursor, \ - "Stub Generator " # stub "_stub", \ + #stub "_stub (stub gen)", \ BlobId:: JOIN3(stubgen, blob, id), \ StubId:: JOIN3(stubgen, stub, id)); \ #define PROCESS_STUBGEN_ENTRY(blob, stub, field_name, getter_name) \ process_stubgen_entry(_group_cursor, _blob_cursor, \ _stub_cursor, _entry_cursor, \ - "Stub Generator " # field_name "_entry", \ + #field_name "_entry (stub gen)", \ BlobId:: JOIN3(stubgen, blob, id), \ StubId:: JOIN3(stubgen, stub, id), \ EntryId:: JOIN3(stubgen, field_name, id), \ @@ -535,7 +535,7 @@ void StubInfo::process_stubgen_entry(StubGroup& group_cursor, init_funcion) \ process_stubgen_entry(_group_cursor, _blob_cursor, \ _stub_cursor, _entry_cursor, \ - "Stub Generator " # field_name "_entry", \ + #field_name "_entry (stub gen)", \ BlobId:: JOIN3(stubgen, blob, id), \ StubId:: JOIN3(stubgen, stub, id), \ EntryId:: JOIN3(stubgen, field_name, id), \ @@ -545,7 +545,7 @@ void StubInfo::process_stubgen_entry(StubGroup& group_cursor, count) \ process_stubgen_entry(_group_cursor, _blob_cursor, \ _stub_cursor, _entry_cursor, \ - "Stub Generator " # field_name "_entry", \ + #field_name "_entry (stub gen)", \ BlobId:: JOIN3(stubgen, blob, id), \ StubId:: JOIN3(stubgen, stub, id), \ EntryId:: JOIN3(stubgen, field_name, id), \ @@ -567,7 +567,7 @@ void StubInfo::process_stubgen_entry(StubGroup& group_cursor, init_function) \ process_stubgen_entry(_group_cursor, _blob_cursor, \ _stub_cursor, _entry_cursor, \ - "Stub Generator " # arch_name "_" # field_name "_entry", \ + #arch_name "_" # field_name "_entry (stub gen)",\ BlobId:: JOIN3(stubgen, blob, id), \ StubId:: JOIN3(stubgen, stub, id), \ EntryId:: JOIN4(stubgen, arch_name, \ diff --git a/src/hotspot/share/runtime/suspendResumeManager.cpp b/src/hotspot/share/runtime/suspendResumeManager.cpp index 067579b6386..3408d763e57 100644 --- a/src/hotspot/share/runtime/suspendResumeManager.cpp +++ b/src/hotspot/share/runtime/suspendResumeManager.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, 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 @@ -130,6 +130,7 @@ void SuspendResumeManager::do_owner_suspend() { assert(_state_lock->owned_by_self(), "Lock must be held"); assert(!_target->has_last_Java_frame() || _target->frame_anchor()->walkable(), "should have walkable stack"); assert(_target->thread_state() == _thread_blocked, "Caller should have transitioned to _thread_blocked"); + JVMTI_ONLY(assert(!_target->is_vthread_transition_disabler(), "attempt to suspend a vthread transition disabler");) while (is_suspended()) { log_trace(thread, suspend)("JavaThread:" INTPTR_FORMAT " suspended", p2i(_target)); diff --git a/src/hotspot/share/runtime/thread.cpp b/src/hotspot/share/runtime/thread.cpp index f3c08ae62f7..bfbd4727e9a 100644 --- a/src/hotspot/share/runtime/thread.cpp +++ b/src/hotspot/share/runtime/thread.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2021, Azul Systems, Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -36,6 +36,7 @@ #include "memory/resourceArea.hpp" #include "nmt/memTracker.hpp" #include "oops/oop.inline.hpp" +#include "runtime/atomic.hpp" #include "runtime/atomicAccess.hpp" #include "runtime/handles.inline.hpp" #include "runtime/javaThread.inline.hpp" @@ -82,7 +83,7 @@ Thread::Thread(MemTag mem_tag) { _threads_hazard_ptr = nullptr; _threads_list_ptr = nullptr; _nested_threads_hazard_ptr_cnt = 0; - _rcu_counter = 0; + _rcu_counter.store_relaxed(0); // the handle mark links itself to last_handle_mark new HandleMark(this); diff --git a/src/hotspot/share/runtime/thread.hpp b/src/hotspot/share/runtime/thread.hpp index 181dfc46f87..dcd6fb2d3fd 100644 --- a/src/hotspot/share/runtime/thread.hpp +++ b/src/hotspot/share/runtime/thread.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2021, Azul Systems, Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -30,6 +30,7 @@ #include "gc/shared/threadLocalAllocBuffer.hpp" #include "jni.h" #include "memory/allocation.hpp" +#include "runtime/atomic.hpp" #include "runtime/atomicAccess.hpp" #include "runtime/globals.hpp" #include "runtime/os.hpp" @@ -238,9 +239,9 @@ class Thread: public ThreadShadow { // Support for GlobalCounter private: - volatile uintx _rcu_counter; + Atomic _rcu_counter; public: - volatile uintx* get_rcu_counter() { + Atomic* get_rcu_counter() { return &_rcu_counter; } diff --git a/src/hotspot/share/runtime/vframeArray.cpp b/src/hotspot/share/runtime/vframeArray.cpp index a68a0adf299..9f1c082ed8f 100644 --- a/src/hotspot/share/runtime/vframeArray.cpp +++ b/src/hotspot/share/runtime/vframeArray.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, 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 @@ -491,6 +491,15 @@ void vframeArrayElement::unpack_on_stack(int caller_actual_parameters, #ifndef PRODUCT if (PrintDeoptimizationDetails) { + const bool print_codes = WizardMode && Verbose; + ResourceMark rm(thread); + stringStream codes_ss; + if (print_codes) { + // print_codes_on() may acquire MDOExtraData_lock (rank nosafepoint-1). + // To keep the lock acquisition order correct, call it before taking tty_lock. + // Avoid double buffering: set buffered=false. + method()->print_codes_on(&codes_ss, 0, false); + } ttyLocker ttyl; tty->print_cr("[%d. Interpreted Frame]", ++unpack_counter); iframe()->print_on(tty); @@ -500,7 +509,9 @@ void vframeArrayElement::unpack_on_stack(int caller_actual_parameters, RegisterMap::WalkContinuation::skip); vframe* f = vframe::new_vframe(iframe(), &map, thread); f->print(); - if (WizardMode && Verbose) method()->print_codes(); + if (print_codes) { + tty->print("%s", codes_ss.as_string()); + } tty->cr(); } #endif // !PRODUCT diff --git a/src/hotspot/share/utilities/byteswap.hpp b/src/hotspot/share/utilities/byteswap.hpp index 371095bce49..9fc367c784e 100644 --- a/src/hotspot/share/utilities/byteswap.hpp +++ b/src/hotspot/share/utilities/byteswap.hpp @@ -1,4 +1,5 @@ /* + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2023, 2024, Google and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -141,7 +142,7 @@ struct ByteswapImpl : public ByteswapFallbackImpl {}; *****************************************************************************/ #elif defined(TARGET_COMPILER_visCPP) -#include +#include "cppstdlib/cstdlib.hpp" #pragma intrinsic(_byteswap_ushort) #pragma intrinsic(_byteswap_ulong) diff --git a/src/hotspot/share/utilities/concurrentHashTable.hpp b/src/hotspot/share/utilities/concurrentHashTable.hpp index 1a50ecabc3e..d2317847307 100644 --- a/src/hotspot/share/utilities/concurrentHashTable.hpp +++ b/src/hotspot/share/utilities/concurrentHashTable.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, 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 @@ -70,7 +70,7 @@ class ConcurrentHashTable : public CHeapObj { class Node { private: DEBUG_ONLY(size_t _saved_hash); - Node * volatile _next; + Atomic _next; VALUE _value; public: Node(const VALUE& value, Node* next = nullptr) @@ -80,8 +80,8 @@ class ConcurrentHashTable : public CHeapObj { } Node* next() const; - void set_next(Node* node) { _next = node; } - Node* const volatile * next_ptr() { return &_next; } + void set_next(Node* node) { _next.store_relaxed(node); } + const Atomic* next_ptr() const { return &_next; } #ifdef ASSERT size_t saved_hash() const { return _saved_hash; } void set_saved_hash(size_t hash) { _saved_hash = hash; } @@ -123,7 +123,7 @@ class ConcurrentHashTable : public CHeapObj { // unlocked -> locked -> redirect // Locked state only applies to an updater. // Reader only check for redirect. - Node * volatile _first; + Atomic _first; static const uintptr_t STATE_LOCK_BIT = 0x1; static const uintptr_t STATE_REDIRECT_BIT = 0x2; @@ -157,20 +157,25 @@ class ConcurrentHashTable : public CHeapObj { // A bucket is only one pointer with the embedded state. Bucket() : _first(nullptr) {}; + NONCOPYABLE(Bucket); + + // Copy bucket's first entry to this. + void assign(const Bucket& bucket); + // Get the first pointer unmasked. Node* first() const; // Get a pointer to the const first pointer. Do not deference this // pointer, the pointer pointed to _may_ contain an embedded state. Such // pointer should only be used as input to release_assign_node_ptr. - Node* const volatile * first_ptr() { return &_first; } + const Atomic* first_ptr() const { return &_first; } // This is the only place where a pointer to a Node pointer that potentially // is _first should be changed. Otherwise we destroy the embedded state. We // only give out pointer to const Node pointer to avoid accidental // assignment, thus here we must cast const part away. Method is not static // due to an assert. - void release_assign_node_ptr(Node* const volatile * dst, Node* node) const; + void release_assign_node_ptr(const Atomic* dst, Node* node) const; // This method assigns this buckets last Node next ptr to input Node. void release_assign_last_node_next(Node* node); diff --git a/src/hotspot/share/utilities/concurrentHashTable.inline.hpp b/src/hotspot/share/utilities/concurrentHashTable.inline.hpp index b96a487324c..62d2dd29dab 100644 --- a/src/hotspot/share/utilities/concurrentHashTable.inline.hpp +++ b/src/hotspot/share/utilities/concurrentHashTable.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, 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 @@ -63,7 +63,7 @@ inline typename ConcurrentHashTable::Node* ConcurrentHashTable:: Node::next() const { - return AtomicAccess::load_acquire(&_next); + return _next.load_acquire(); } // Bucket @@ -72,19 +72,26 @@ inline typename ConcurrentHashTable::Node* ConcurrentHashTable:: Bucket::first_raw() const { - return AtomicAccess::load_acquire(&_first); + return _first.load_acquire(); } template inline void ConcurrentHashTable:: Bucket::release_assign_node_ptr( - typename ConcurrentHashTable::Node* const volatile * dst, + const Atomic::Node*>* dst, typename ConcurrentHashTable::Node* node) const { // Due to this assert this methods is not static. assert(is_locked(), "Must be locked."); - Node** tmp = (Node**)dst; - AtomicAccess::release_store(tmp, clear_set_state(node, *dst)); + Atomic* tmp = const_cast*>(dst); + tmp->release_store(clear_set_state(node, dst->load_relaxed())); +} + +template +inline void ConcurrentHashTable:: + Bucket::assign(const Bucket& bucket) +{ + _first.store_relaxed(bucket._first.load_relaxed()); } template @@ -93,7 +100,7 @@ ConcurrentHashTable:: Bucket::first() const { // We strip the states bit before returning the ptr. - return clear_state(AtomicAccess::load_acquire(&_first)); + return clear_state(_first.load_acquire()); } template @@ -134,9 +141,9 @@ inline void ConcurrentHashTable:: typename ConcurrentHashTable::Node* node) { assert(is_locked(), "Must be locked."); - Node* const volatile * ret = first_ptr(); - while (clear_state(*ret) != nullptr) { - ret = clear_state(*ret)->next_ptr(); + const Atomic* ret = first_ptr(); + while (clear_state(ret->load_relaxed()) != nullptr) { + ret = clear_state(ret->load_relaxed())->next_ptr(); } release_assign_node_ptr(ret, node); } @@ -150,7 +157,7 @@ inline bool ConcurrentHashTable:: if (is_locked()) { return false; } - if (AtomicAccess::cmpxchg(&_first, expect, node) == expect) { + if (_first.compare_set(expect, node)) { return true; } return false; @@ -165,7 +172,7 @@ inline bool ConcurrentHashTable:: } // We will expect a clean first pointer. Node* tmp = first(); - if (AtomicAccess::cmpxchg(&_first, tmp, set_state(tmp, STATE_LOCK_BIT)) == tmp) { + if (_first.compare_set(tmp, set_state(tmp, STATE_LOCK_BIT))) { return true; } return false; @@ -178,7 +185,7 @@ inline void ConcurrentHashTable:: assert(is_locked(), "Must be locked."); assert(!have_redirect(), "Unlocking a bucket after it has reached terminal state."); - AtomicAccess::release_store(&_first, clear_state(first())); + _first.release_store(clear_state(first())); } template @@ -186,7 +193,7 @@ inline void ConcurrentHashTable:: Bucket::redirect() { assert(is_locked(), "Must be locked."); - AtomicAccess::release_store(&_first, set_state(_first, STATE_REDIRECT_BIT)); + _first.release_store(set_state(_first.load_relaxed(), STATE_REDIRECT_BIT)); } // InternalTable @@ -413,8 +420,8 @@ inline void ConcurrentHashTable:: bucket->lock(); size_t odd_index = even_index + _table->_size; - _new_table->get_buckets()[even_index] = *bucket; - _new_table->get_buckets()[odd_index] = *bucket; + _new_table->get_buckets()[even_index].assign(*bucket); + _new_table->get_buckets()[odd_index].assign(*bucket); // Moves lockers go to new table, where they will wait until unlock() below. bucket->redirect(); /* Must release stores above */ @@ -445,7 +452,7 @@ inline bool ConcurrentHashTable:: { Bucket* bucket = get_bucket_locked(thread, lookup_f.get_hash()); assert(bucket->is_locked(), "Must be locked."); - Node* const volatile * rem_n_prev = bucket->first_ptr(); + const Atomic* rem_n_prev = bucket->first_ptr(); Node* rem_n = bucket->first(); while (rem_n != nullptr) { if (lookup_f.equals(rem_n->value())) { @@ -534,7 +541,7 @@ inline void ConcurrentHashTable:: size_t dels = 0; Node* ndel[StackBufferSize]; - Node* const volatile * rem_n_prev = bucket->first_ptr(); + const Atomic* rem_n_prev = bucket->first_ptr(); Node* rem_n = bucket->first(); while (rem_n != nullptr) { if (lookup_f.is_dead(rem_n->value())) { @@ -643,8 +650,8 @@ inline bool ConcurrentHashTable:: return false; } Node* delete_me = nullptr; - Node* const volatile * even = new_table->get_bucket(even_index)->first_ptr(); - Node* const volatile * odd = new_table->get_bucket(odd_index)->first_ptr(); + const Atomic* even = new_table->get_bucket(even_index)->first_ptr(); + const Atomic* odd = new_table->get_bucket(odd_index)->first_ptr(); while (aux != nullptr) { bool dead_hash = false; size_t aux_hash = CONFIG::get_hash(*aux->value(), &dead_hash); @@ -742,11 +749,11 @@ inline void ConcurrentHashTable:: b_old_even->lock(); b_old_odd->lock(); - _new_table->get_buckets()[bucket_it] = *b_old_even; + _new_table->get_buckets()[bucket_it].assign(*b_old_even); // Put chains together. _new_table->get_bucket(bucket_it)-> - release_assign_last_node_next(*(b_old_odd->first_ptr())); + release_assign_last_node_next(b_old_odd->first_ptr()->load_relaxed()); b_old_even->redirect(); b_old_odd->redirect(); @@ -981,7 +988,7 @@ inline size_t ConcurrentHashTable:: size_t num_del, Node** ndel, GrowableArrayCHeap& extra) { size_t dels = 0; - Node* const volatile * rem_n_prev = bucket->first_ptr(); + const Atomic* rem_n_prev = bucket->first_ptr(); Node* rem_n = bucket->first(); while (rem_n != nullptr) { if (eval_f(rem_n->value())) { diff --git a/src/hotspot/share/utilities/debug.cpp b/src/hotspot/share/utilities/debug.cpp index de39fe32dc1..504b923237e 100644 --- a/src/hotspot/share/utilities/debug.cpp +++ b/src/hotspot/share/utilities/debug.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, 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 @@ -63,12 +63,17 @@ #include "utilities/nativeStackPrinter.hpp" #include "utilities/unsigned5.hpp" #include "utilities/vmError.hpp" +#if INCLUDE_JFR +#include "jfr/jfr.hpp" +#endif #include #include -// These functions needs to be exported on Windows only -#define DEBUGEXPORT WINDOWS_ONLY(JNIEXPORT) +// These functions needs to be exported on Windows +// On Linux it is also beneficial to export them to avoid +// losing them e.g. with linktime gc +#define DEBUGEXPORT JNIEXPORT // Support for showing register content on asserts/guarantees. #ifdef CAN_SHOW_REGISTERS_ON_ASSERT @@ -262,6 +267,8 @@ void report_untested(const char* file, int line, const char* message) { void report_java_out_of_memory(const char* message) { static int out_of_memory_reported = 0; + JFR_ONLY(Jfr::on_report_java_out_of_memory();) + // A number of threads may attempt to report OutOfMemoryError at around the // same time. To avoid dumping the heap or executing the data collection // commands multiple times we just do it once when the first threads reports @@ -422,10 +429,8 @@ extern "C" DEBUGEXPORT void pp(void* p) { tty->print_cr("null"); return; } - if (Universe::heap()->is_in(p)) { - oop obj = cast_to_oop(p); - obj->print(); - } else { + + if (!Universe::heap()->print_location(tty, p)) { // Ask NMT about this pointer. // GDB note: We will be using SafeFetch to access the supposed malloc header. If the address is // not readable, this will generate a signal. That signal will trip up the debugger: gdb will @@ -648,13 +653,12 @@ extern "C" DEBUGEXPORT intptr_t u5p(intptr_t addr, void pp(intptr_t p) { pp((void*)p); } void pp(oop p) { pp((void*)p); } -void help() { +extern "C" DEBUGEXPORT void help() { Command c("help"); tty->print_cr("basic"); tty->print_cr(" pp(void* p) - try to make sense of p"); tty->print_cr(" ps() - print current thread stack"); tty->print_cr(" pss() - print all thread stacks"); - tty->print_cr(" pm(int pc) - print Method* given compiled PC"); tty->print_cr(" findnm(intptr_t pc) - find nmethod*"); tty->print_cr(" findm(intptr_t pc) - find Method*"); tty->print_cr(" find(intptr_t x) - find & print nmethod/stub/bytecode/oop based on pointer into it"); diff --git a/src/hotspot/share/utilities/forbiddenFunctions.hpp b/src/hotspot/share/utilities/forbiddenFunctions.hpp index 47becd7b4c7..9d1b88e6233 100644 --- a/src/hotspot/share/utilities/forbiddenFunctions.hpp +++ b/src/hotspot/share/utilities/forbiddenFunctions.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2026, 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 @@ -32,12 +32,6 @@ #include #include -// Workaround for noreturn functions: exit, _exit, _Exit - see the clang -// definition of FORBIDDEN_FUNCTION_NORETURN_ATTRIBUTE. -#ifdef __clang__ -#include -#endif - #ifdef _WINDOWS #include "forbiddenFunctions_windows.hpp" #else @@ -49,12 +43,6 @@ // or have security concerns, either with preferred alternatives, or to be // avoided entirely. -FORBID_IMPORTED_NORETURN_C_FUNCTION(void exit(int), noexcept, "use os::exit") -FORBID_IMPORTED_NORETURN_C_FUNCTION(void _Exit(int), noexcept, "use os::exit") - -// Windows puts _exit in , POSIX in . -FORBID_IMPORTED_NORETURN_C_FUNCTION(void _exit(int), /* not noexcept */, "use os::exit") - FORBID_IMPORTED_C_FUNCTION(char* strerror(int), noexcept, "use os::strerror"); FORBID_IMPORTED_C_FUNCTION(char* strtok(char*, const char*), noexcept, "use strtok_r"); @@ -70,13 +58,8 @@ FORBID_C_FUNCTION(int vsprintf(char*, const char*, va_list), noexcept, "use os:: FORBID_C_FUNCTION(int vsnprintf(char*, size_t, const char*, va_list), noexcept, "use os::vsnprintf"); PRAGMA_DIAG_POP -// All of the following functions return raw C-heap pointers (sometimes as an -// option, e.g. realpath or getwd) or, in case of free(), take raw C-heap -// pointers. We generally want allocation to be done through NMT. -FORBID_IMPORTED_C_FUNCTION(void* malloc(size_t size), noexcept, "use os::malloc"); -FORBID_IMPORTED_C_FUNCTION(void free(void *ptr), noexcept, "use os::free"); -FORBID_IMPORTED_C_FUNCTION(void* calloc(size_t nmemb, size_t size), noexcept, "use os::malloc and zero out manually"); -FORBID_IMPORTED_C_FUNCTION(void* realloc(void *ptr, size_t size), noexcept, "use os::realloc"); +// All of the following functions return raw C-heap pointers. We generally +// want allocation to be done through NMT. FORBID_IMPORTED_C_FUNCTION(char* strdup(const char *s), noexcept, "use os::strdup"); FORBID_IMPORTED_C_FUNCTION(wchar_t* wcsdup(const wchar_t *s), noexcept, "don't use"); diff --git a/src/hotspot/share/utilities/globalCounter.cpp b/src/hotspot/share/utilities/globalCounter.cpp index 7019273d937..114d3b5fed1 100644 --- a/src/hotspot/share/utilities/globalCounter.cpp +++ b/src/hotspot/share/utilities/globalCounter.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, 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 @@ -23,7 +23,7 @@ */ #include "memory/iterator.hpp" -#include "runtime/atomicAccess.hpp" +#include "runtime/atomic.hpp" #include "runtime/javaThread.hpp" #include "runtime/threadSMR.inline.hpp" #include "runtime/vmThread.hpp" @@ -41,7 +41,7 @@ class GlobalCounter::CounterThreadCheck : public ThreadClosure { SpinYield yield; // Loops on this thread until it has exited the critical read section. while(true) { - uintx cnt = AtomicAccess::load_acquire(thread->get_rcu_counter()); + uintx cnt = thread->get_rcu_counter()->load_acquire(); // This checks if the thread's counter is active. And if so is the counter // for a pre-existing reader (belongs to this grace period). A pre-existing // reader will have a lower counter than the global counter version for this @@ -57,9 +57,9 @@ class GlobalCounter::CounterThreadCheck : public ThreadClosure { }; void GlobalCounter::write_synchronize() { - assert((*Thread::current()->get_rcu_counter() & COUNTER_ACTIVE) == 0x0, "must be outside a critcal section"); - // AtomicAccess::add must provide fence since we have storeload dependency. - uintx gbl_cnt = AtomicAccess::add(&_global_counter._counter, COUNTER_INCREMENT); + assert((Thread::current()->get_rcu_counter()->load_relaxed() & COUNTER_ACTIVE) == 0x0, "must be outside a critcal section"); + // Atomic add must provide fence since we have storeload dependency. + uintx gbl_cnt = _global_counter._counter.add_then_fetch(COUNTER_INCREMENT); // Do all RCU threads. CounterThreadCheck ctc(gbl_cnt); diff --git a/src/hotspot/share/utilities/globalCounter.hpp b/src/hotspot/share/utilities/globalCounter.hpp index c78831acfa5..80fc1b3e94e 100644 --- a/src/hotspot/share/utilities/globalCounter.hpp +++ b/src/hotspot/share/utilities/globalCounter.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, 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,6 +27,7 @@ #include "memory/allStatic.hpp" #include "memory/padded.hpp" +#include "runtime/atomic.hpp" class Thread; @@ -47,7 +48,7 @@ class GlobalCounter : public AllStatic { // counter is on a separate cacheline. struct PaddedCounter { DEFINE_PAD_MINUS_SIZE(0, DEFAULT_PADDING_SIZE, 0); - volatile uintx _counter; + Atomic _counter; DEFINE_PAD_MINUS_SIZE(1, DEFAULT_PADDING_SIZE, sizeof(volatile uintx)); }; diff --git a/src/hotspot/share/utilities/globalCounter.inline.hpp b/src/hotspot/share/utilities/globalCounter.inline.hpp index 9cc746173b8..ed37b8a878d 100644 --- a/src/hotspot/share/utilities/globalCounter.inline.hpp +++ b/src/hotspot/share/utilities/globalCounter.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, 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,30 +27,29 @@ #include "utilities/globalCounter.hpp" -#include "runtime/atomicAccess.hpp" +#include "runtime/atomic.hpp" #include "runtime/javaThread.hpp" inline GlobalCounter::CSContext GlobalCounter::critical_section_begin(Thread *thread) { assert(thread == Thread::current(), "must be current thread"); - uintx old_cnt = AtomicAccess::load(thread->get_rcu_counter()); + uintx old_cnt = thread->get_rcu_counter()->load_relaxed(); // Retain the old counter value if already active, e.g. nested. // Otherwise, set the counter to the current version + active bit. uintx new_cnt = old_cnt; if ((new_cnt & COUNTER_ACTIVE) == 0) { - new_cnt = AtomicAccess::load(&_global_counter._counter) | COUNTER_ACTIVE; + new_cnt = _global_counter._counter.load_relaxed() | COUNTER_ACTIVE; } - AtomicAccess::release_store_fence(thread->get_rcu_counter(), new_cnt); + thread->get_rcu_counter()->release_store_fence(new_cnt); return static_cast(old_cnt); } inline void GlobalCounter::critical_section_end(Thread *thread, CSContext context) { assert(thread == Thread::current(), "must be current thread"); - assert((*thread->get_rcu_counter() & COUNTER_ACTIVE) == COUNTER_ACTIVE, "must be in critical section"); + assert((thread->get_rcu_counter()->load_relaxed() & COUNTER_ACTIVE) == COUNTER_ACTIVE, "must be in critical section"); // Restore the counter value from before the associated begin. - AtomicAccess::release_store(thread->get_rcu_counter(), - static_cast(context)); + thread->get_rcu_counter()->release_store(static_cast(context)); } class GlobalCounter::CriticalSection { diff --git a/src/hotspot/share/utilities/globalDefinitions.hpp b/src/hotspot/share/utilities/globalDefinitions.hpp index 3284fd3bd15..54602297759 100644 --- a/src/hotspot/share/utilities/globalDefinitions.hpp +++ b/src/hotspot/share/utilities/globalDefinitions.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, 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 @@ -1374,14 +1374,6 @@ template int primitive_compare(const K& k0, const K& k1) { template std::add_rvalue_reference_t declval() noexcept; -// This provides a workaround for static_assert(false) in discarded or -// otherwise uninstantiated places. Instead use -// static_assert(DependentAlwaysFalse, "...") -// See http://wg21.link/p2593r1. Some, but not all, compiler versions we're -// using have implemented that change as a DR: -// https://cplusplus.github.io/CWG/issues/2518.html -template inline constexpr bool DependentAlwaysFalse = false; - // Quickly test to make sure IEEE-754 subnormal numbers are correctly // handled. bool IEEE_subnormal_handling_OK(); diff --git a/src/hotspot/share/utilities/globalDefinitions_gcc.hpp b/src/hotspot/share/utilities/globalDefinitions_gcc.hpp index f82f9b1386a..302a62de6c1 100644 --- a/src/hotspot/share/utilities/globalDefinitions_gcc.hpp +++ b/src/hotspot/share/utilities/globalDefinitions_gcc.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2026, 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 @@ // globally used constants & types, class (forward) // declarations and a few frequently used utility functions. +#include "cppstdlib/cstdlib.hpp" + #include #include #include @@ -44,15 +46,6 @@ #include #include #include -#include -// In stdlib.h on AIX malloc is defined as a macro causing -// compiler errors when resolving them in different depths as it -// happens in the log tags. This avoids the macro. -#if (defined(__VEC__) || defined(__AIXVEC)) && defined(AIX) \ - && defined(__open_xl_version__) && __open_xl_version__ >= 17 - #undef malloc - extern void *malloc(size_t) asm("vec_malloc"); -#endif #include #include #include diff --git a/src/hotspot/share/utilities/globalDefinitions_visCPP.hpp b/src/hotspot/share/utilities/globalDefinitions_visCPP.hpp index b9d25096cd5..dfd6f2f1880 100644 --- a/src/hotspot/share/utilities/globalDefinitions_visCPP.hpp +++ b/src/hotspot/share/utilities/globalDefinitions_visCPP.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, 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,6 +34,8 @@ // Need this on windows to get the math constants (e.g., M_PI). #define _USE_MATH_DEFINES +#include "cppstdlib/cstdlib.hpp" + # include # include # include // for _isnan @@ -45,7 +47,6 @@ # include // for offsetof # include # include -# include # include # include # include diff --git a/src/hotspot/share/utilities/growableArray.hpp b/src/hotspot/share/utilities/growableArray.hpp index 1fef271c8c0..1823a2ba861 100644 --- a/src/hotspot/share/utilities/growableArray.hpp +++ b/src/hotspot/share/utilities/growableArray.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, 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 @@ -116,6 +116,12 @@ protected: ~GrowableArrayView() {} +protected: + // Used by AOTGrowableArray for MetaspaceClosure support. + E** data_addr() { + return &_data; + } + public: bool operator==(const GrowableArrayView& rhs) const { if (_len != rhs._len) diff --git a/src/hotspot/share/utilities/lockFreeStack.hpp b/src/hotspot/share/utilities/lockFreeStack.hpp index 3f63482a268..871e697f404 100644 --- a/src/hotspot/share/utilities/lockFreeStack.hpp +++ b/src/hotspot/share/utilities/lockFreeStack.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, 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,6 +25,7 @@ #ifndef SHARE_UTILITIES_LOCKFREESTACK_HPP #define SHARE_UTILITIES_LOCKFREESTACK_HPP +#include "metaprogramming/dependentAlwaysFalse.hpp" #include "runtime/atomic.hpp" #include "runtime/atomicAccess.hpp" #include "utilities/debug.hpp" diff --git a/src/hotspot/share/utilities/parseInteger.hpp b/src/hotspot/share/utilities/parseInteger.hpp index 3e3eafada87..8840275a8cb 100644 --- a/src/hotspot/share/utilities/parseInteger.hpp +++ b/src/hotspot/share/utilities/parseInteger.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2022 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,6 +26,7 @@ #ifndef SHARE_UTILITIES_PARSE_INTEGER_HPP #define SHARE_UTILITIES_PARSE_INTEGER_HPP +#include "cppstdlib/cstdlib.hpp" #include "cppstdlib/limits.hpp" #include "metaprogramming/enableIf.hpp" #include "utilities/debug.hpp" @@ -33,7 +34,6 @@ #include "utilities/macros.hpp" #include -#include // ************************************************************************* // ** Attention compatibility! ** diff --git a/src/hotspot/share/utilities/permitForbiddenFunctions.hpp b/src/hotspot/share/utilities/permitForbiddenFunctions.hpp index 1ba42f8e386..71719ac8a76 100644 --- a/src/hotspot/share/utilities/permitForbiddenFunctions.hpp +++ b/src/hotspot/share/utilities/permitForbiddenFunctions.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2026, 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,6 +25,7 @@ #ifndef SHARE_UTILITIES_PERMITFORBIDDENFUNCTIONS_HPP #define SHARE_UTILITIES_PERMITFORBIDDENFUNCTIONS_HPP +#include "cppstdlib/cstdlib.hpp" #include "utilities/compilerWarnings.hpp" #include "utilities/globalDefinitions.hpp" @@ -34,6 +35,9 @@ #include "permitForbiddenFunctions_posix.hpp" #endif +#include +#include + // Provide wrappers for some functions otherwise forbidden from use in HotSpot. // // There may be special circumstances where an otherwise forbidden function @@ -53,7 +57,6 @@ namespace permit_forbidden_function { BEGIN_ALLOW_FORBIDDEN_FUNCTIONS [[noreturn]] inline void exit(int status) { ::exit(status); } -[[noreturn]] inline void _exit(int status) { ::_exit(status); } ATTRIBUTE_PRINTF(3, 0) inline int vsnprintf(char* str, size_t size, const char* format, va_list ap) { diff --git a/src/hotspot/share/utilities/vmError.cpp b/src/hotspot/share/utilities/vmError.cpp index a290602e0be..88f81b31293 100644 --- a/src/hotspot/share/utilities/vmError.cpp +++ b/src/hotspot/share/utilities/vmError.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2017, 2024 SAP SE. All rights reserved. * Copyright (c) 2023, 2025, Red Hat, Inc. and/or its affiliates. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. @@ -1898,7 +1898,7 @@ void VMError::report_and_die(int id, const char* message, const char* detail_fmt log.set_fd(-1); } - JFR_ONLY(Jfr::on_vm_shutdown(static_cast(_id) == OOM_JAVA_HEAP_FATAL, true);) + JFR_ONLY(Jfr::on_vm_shutdown(true, false, static_cast(_id) == OOM_JAVA_HEAP_FATAL);) if (PrintNMTStatistics) { fdStream fds(fd_out); diff --git a/src/hotspot/share/utilities/waitBarrier_generic.cpp b/src/hotspot/share/utilities/waitBarrier_generic.cpp index b268b10c757..0892feab699 100644 --- a/src/hotspot/share/utilities/waitBarrier_generic.cpp +++ b/src/hotspot/share/utilities/waitBarrier_generic.cpp @@ -181,7 +181,7 @@ void GenericWaitBarrier::Cell::disarm(int32_t expected_tag) { tag, waiters); int64_t new_state = encode(0, waiters); - if (_state.compare_exchange(state, new_state) == state) { + if (_state.compare_set(state, new_state)) { // Successfully disarmed. break; } @@ -218,7 +218,7 @@ void GenericWaitBarrier::Cell::wait(int32_t expected_tag) { tag, waiters); int64_t new_state = encode(tag, waiters + 1); - if (_state.compare_exchange(state, new_state) == state) { + if (_state.compare_set(state, new_state)) { // Success! Proceed to wait. break; } @@ -247,7 +247,7 @@ void GenericWaitBarrier::Cell::wait(int32_t expected_tag) { tag, waiters); int64_t new_state = encode(tag, waiters - 1); - if (_state.compare_exchange(state, new_state) == state) { + if (_state.compare_set(state, new_state)) { // Success! break; } diff --git a/src/java.base/linux/native/libnio/ch/FileDispatcherImpl.c b/src/java.base/linux/native/libnio/ch/FileDispatcherImpl.c index efbd0ca5684..54d640b03a4 100644 --- a/src/java.base/linux/native/libnio/ch/FileDispatcherImpl.c +++ b/src/java.base/linux/native/libnio/ch/FileDispatcherImpl.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, 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 @@ -63,7 +63,7 @@ Java_sun_nio_ch_FileDispatcherImpl_transferFrom0(JNIEnv *env, jobject this, if (n < 0) { if (errno == EAGAIN) return IOS_UNAVAILABLE; - if (errno == ENOSYS) + if (errno == ENOSYS || errno == EOPNOTSUPP) return IOS_UNSUPPORTED_CASE; if ((errno == EBADF || errno == EINVAL || errno == EXDEV) && ((ssize_t)count >= 0)) @@ -103,6 +103,7 @@ Java_sun_nio_ch_FileDispatcherImpl_transferTo0(JNIEnv *env, jobject this, case EINVAL: case ENOSYS: case EXDEV: + case EOPNOTSUPP: // ignore and try sendfile() break; default: diff --git a/src/java.base/linux/native/libnio/fs/LinuxNativeDispatcher.c b/src/java.base/linux/native/libnio/fs/LinuxNativeDispatcher.c index c90e99dda07..4677411b0ba 100644 --- a/src/java.base/linux/native/libnio/fs/LinuxNativeDispatcher.c +++ b/src/java.base/linux/native/libnio/fs/LinuxNativeDispatcher.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2026, 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 @@ -193,6 +193,7 @@ Java_sun_nio_fs_LinuxNativeDispatcher_directCopy0 case EINVAL: case ENOSYS: case EXDEV: + case EOPNOTSUPP: // ignore and try sendfile() break; default: diff --git a/src/java.base/macosx/native/libosxsecurity/KeystoreImpl.m b/src/java.base/macosx/native/libosxsecurity/KeystoreImpl.m index 31572eaeb81..6d8eb832370 100644 --- a/src/java.base/macosx/native/libosxsecurity/KeystoreImpl.m +++ b/src/java.base/macosx/native/libosxsecurity/KeystoreImpl.m @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2026, 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 @@ -134,10 +134,11 @@ static OSStatus completeCertChain( CFArrayAppendValue(certArray, identity); /* the single element in certs-to-be-evaluated comes from the identity */ - ortn = SecIdentityCopyCertificate(identity, &certRef); - if(ortn) { + ortn = SecIdentityCopyCertificate(identity, &certRef); + if (ortn) { /* should never happen */ cssmPerror("SecIdentityCopyCertificate", ortn); + CFRelease(certArray); return ortn; } @@ -283,6 +284,7 @@ static void addIdentitiesToKeystore(JNIEnv *env, jobject keyStore, jmethodID jm_ OSStatus err = SecIdentitySearchCreate(NULL, 0, &identitySearch); SecIdentityRef theIdentity = NULL; OSErr searchResult = noErr; + CFArrayRef certChain = NULL; do { searchResult = SecIdentitySearchCopyNext(identitySearch, &theIdentity); @@ -291,7 +293,6 @@ static void addIdentitiesToKeystore(JNIEnv *env, jobject keyStore, jmethodID jm_ // Get the cert from the identity, then generate a chain. SecCertificateRef certificate; SecIdentityCopyCertificate(theIdentity, &certificate); - CFArrayRef certChain = NULL; // *** Should do something with this error... err = completeCertChain(theIdentity, NULL, TRUE, &certChain); @@ -357,6 +358,11 @@ static void addIdentitiesToKeystore(JNIEnv *env, jobject keyStore, jmethodID jm_ if ((*env)->ExceptionCheck(env)) { goto errOut; } + + if (certChain != NULL) { + CFRelease(certChain); + certChain = NULL; + } } } while (searchResult == noErr); @@ -364,6 +370,9 @@ errOut: if (identitySearch != NULL) { CFRelease(identitySearch); } + if (certChain != NULL) { + CFRelease(certChain); + } } #define ADD(list, str) { \ diff --git a/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM.java b/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM.java index 1280ccdad74..bf6576f4d93 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM.java @@ -1353,22 +1353,16 @@ public final class ML_KEM { } } - // The intrinsic implementations assume that the input and output buffers - // are such that condensed can be read in 96-byte chunks and - // parsed can be written in 64 shorts chunks except for the last chunk - // that can be either 48 or 64 shorts. In other words, - // if (i - 1) * 64 < parsedLengths <= i * 64 then - // parsed.length should be either i * 64 or (i-1) * 64 + 48 and - // condensed.length should be at least index + i * 96. + // An intrinsic implementation assumes that the input and output buffers + // are such that condensed can be read in chunks of 192 bytes and + // parsed can be written in chunks of 128 shorts, so callers should allocate + // the condensed and parsed arrays accordingly, see the assert() private void twelve2Sixteen(byte[] condensed, int index, short[] parsed, int parsedLength) { - int i = parsedLength / 64; - int remainder = parsedLength - i * 64; - if (remainder != 0) { - i++; - } - assert ((remainder == 0) || (remainder == 48)) && - (index + i * 96 <= condensed.length); + int n = (parsedLength + 127) / 128; + assert ((parsed.length >= n * 128) && + (condensed.length >= index + n * 192)); + implKyber12To16(condensed, index, parsed, parsedLength); } diff --git a/src/java.base/share/classes/java/lang/Integer.java b/src/java.base/share/classes/java/lang/Integer.java index a9da1c32490..85ca80735f8 100644 --- a/src/java.base/share/classes/java/lang/Integer.java +++ b/src/java.base/share/classes/java/lang/Integer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1994, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1994, 2026, 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 @@ -1448,6 +1448,7 @@ public final class Integer extends Number * @param divisor the value doing the dividing * @return the unsigned quotient of the first argument divided by * the second argument + * @throws ArithmeticException if the divisor is zero * @see #remainderUnsigned * @since 1.8 */ @@ -1466,6 +1467,7 @@ public final class Integer extends Number * @param divisor the value doing the dividing * @return the unsigned remainder of the first argument divided by * the second argument + * @throws ArithmeticException if the divisor is zero * @see #divideUnsigned * @since 1.8 */ diff --git a/src/java.base/share/classes/java/lang/LazyConstant.java b/src/java.base/share/classes/java/lang/LazyConstant.java index 703d67b8abf..85f9d0e82fd 100644 --- a/src/java.base/share/classes/java/lang/LazyConstant.java +++ b/src/java.base/share/classes/java/lang/LazyConstant.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, 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 @@ -87,9 +87,8 @@ import java.util.function.Supplier; * is thrown. Hence, a lazy constant can never hold a {@code null} value. Clients who * want to use a nullable constant can wrap the value into an {@linkplain Optional} holder. *

    - * If the computing function recursively invokes itself (directly or indirectly via - * the lazy constant), an {@linkplain IllegalStateException} is thrown, and the lazy - * constant is not initialized. + * If the computing function recursively invokes itself via the lazy constant, an + * {@linkplain IllegalStateException} is thrown, and the lazy constant is not initialized. * *

    Composing lazy constants

    * A lazy constant can depend on other lazy constants, forming a dependency graph diff --git a/src/java.base/share/classes/java/lang/Long.java b/src/java.base/share/classes/java/lang/Long.java index c5cd9650f2d..5fa1b8fc2ea 100644 --- a/src/java.base/share/classes/java/lang/Long.java +++ b/src/java.base/share/classes/java/lang/Long.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1994, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1994, 2026, 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 @@ -1411,6 +1411,7 @@ public final class Long extends Number * @param divisor the value doing the dividing * @return the unsigned quotient of the first argument divided by * the second argument + * @throws ArithmeticException if the divisor is zero * @see #remainderUnsigned * @since 1.8 */ @@ -1434,6 +1435,7 @@ public final class Long extends Number * @param divisor the value doing the dividing * @return the unsigned remainder of the first argument divided by * the second argument + * @throws ArithmeticException if the divisor is zero * @see #divideUnsigned * @since 1.8 */ diff --git a/src/java.base/share/classes/java/lang/Math.java b/src/java.base/share/classes/java/lang/Math.java index 0f39ecf0a8a..55659bed57b 100644 --- a/src/java.base/share/classes/java/lang/Math.java +++ b/src/java.base/share/classes/java/lang/Math.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1994, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1994, 2026, 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 @@ -2378,6 +2378,20 @@ public final class Math { */ @IntrinsicCandidate public static double fma(double a, double b, double c) { + // Implementation note: this method is intentionally coded in + // a straightforward manner relying on BigDecimal for the + // heavy-lifting of the numerical computation. It would be + // possible for the computation to be done solely using binary + // floating-point and integer operations, at the cost of more + // complicated logic. Since most processors have hardware + // support for fma and this method is an intrinsic candidate, + // the software implementation below would only be used on + // processors without native fma support (and also possibly on + // processors with native fma support while running in the + // interpreter). Therefore, the direct performance of the code + // is less of a concern than the code's simplicity, + // maintainability, and testability. + /* * Infinity and NaN arithmetic is not quite the same with two * roundings as opposed to just one so the simple expression @@ -2492,6 +2506,8 @@ public final class Math { */ @IntrinsicCandidate public static float fma(float a, float b, float c) { + // See implementation note in fma(double, double, double). + if (Float.isFinite(a) && Float.isFinite(b) && Float.isFinite(c)) { if (a == 0.0 || b == 0.0) { return a * b + c; // Handled signed zero cases diff --git a/src/java.base/share/classes/java/lang/String.java b/src/java.base/share/classes/java/lang/String.java index d7aef113e15..1ac15e3a8b2 100644 --- a/src/java.base/share/classes/java/lang/String.java +++ b/src/java.base/share/classes/java/lang/String.java @@ -1111,7 +1111,7 @@ public final class String int sp = 0; int sl = len; while (sp < sl) { - int ret = StringCoding.encodeISOArray(val, sp, dst, dp, len); + int ret = StringCoding.implEncodeISOArray(val, sp, dst, dp, len); sp = sp + ret; dp = dp + ret; if (ret != len) { @@ -2045,19 +2045,26 @@ public final class String return encode(Charset.defaultCharset(), coder(), value); } - boolean bytesCompatible(Charset charset) { + boolean bytesCompatible(Charset charset, int srcIndex, int numChars) { if (isLatin1()) { if (charset == ISO_8859_1.INSTANCE) { return true; // ok, same encoding } else if (charset == UTF_8.INSTANCE || charset == US_ASCII.INSTANCE) { - return !StringCoding.hasNegatives(value, 0, value.length); // ok, if ASCII-compatible + return !StringCoding.hasNegatives(value, srcIndex, numChars); // ok, if ASCII-compatible } } return false; } - void copyToSegmentRaw(MemorySegment segment, long offset) { - MemorySegment.copy(value, 0, segment, ValueLayout.JAVA_BYTE, offset, value.length); + void copyToSegmentRaw(MemorySegment segment, long offset, int srcIndex, int srcLength) { + if (!isLatin1()) { + // This method is intended to be used together with bytesCompatible, which currently only supports + // latin1 strings. In the future, bytesCompatible could be updated to handle more cases, like + // UTF-16 strings (when the platform and charset endianness match, and the String doesn’t contain + // unpaired surrogates). If that happens, copyToSegmentRaw should also be updated. + throw new IllegalStateException("This string does not support copyToSegmentRaw"); + } + MemorySegment.copy(value, srcIndex, segment, ValueLayout.JAVA_BYTE, offset, srcLength); } /** diff --git a/src/java.base/share/classes/java/lang/StringCoding.java b/src/java.base/share/classes/java/lang/StringCoding.java index 545f216b755..c02af28c37d 100644 --- a/src/java.base/share/classes/java/lang/StringCoding.java +++ b/src/java.base/share/classes/java/lang/StringCoding.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2024, Alibaba Group Holding Limited. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,11 +26,8 @@ package java.lang; -import jdk.internal.util.Preconditions; import jdk.internal.vm.annotation.IntrinsicCandidate; -import java.util.function.BiFunction; - /** * Utility class for string encoding and decoding. */ @@ -41,7 +38,7 @@ class StringCoding { /** * Count the number of leading non-zero ascii chars in the range. */ - static int countNonZeroAscii(String s) { + public static int countNonZeroAscii(String s) { byte[] value = s.value(); if (s.isLatin1()) { return countNonZeroAsciiLatin1(value, 0, value.length); @@ -53,7 +50,7 @@ class StringCoding { /** * Count the number of non-zero ascii chars in the range. */ - private static int countNonZeroAsciiLatin1(byte[] ba, int off, int len) { + public static int countNonZeroAsciiLatin1(byte[] ba, int off, int len) { int limit = off + len; for (int i = off; i < limit; i++) { if (ba[i] <= 0) { @@ -66,7 +63,7 @@ class StringCoding { /** * Count the number of leading non-zero ascii chars in the range. */ - private static int countNonZeroAsciiUTF16(byte[] ba, int off, int strlen) { + public static int countNonZeroAsciiUTF16(byte[] ba, int off, int strlen) { int limit = off + strlen; for (int i = off; i < limit; i++) { char c = StringUTF16.charAt(ba, i); @@ -77,7 +74,7 @@ class StringCoding { return strlen; } - static boolean hasNegatives(byte[] ba, int off, int len) { + public static boolean hasNegatives(byte[] ba, int off, int len) { return countPositives(ba, off, len) != len; } @@ -88,24 +85,9 @@ class StringCoding { * bytes in the range. If there are negative bytes, the implementation must return * a value that is less than or equal to the index of the first negative byte * in the range. - * - * @param ba a byte array - * @param off the index of the first byte to start reading from - * @param len the total number of bytes to read - * @throws NullPointerException if {@code ba} is null - * @throws ArrayIndexOutOfBoundsException if the provided sub-range is - * {@linkplain Preconditions#checkFromIndexSize(int, int, int, BiFunction) out of bounds} */ - static int countPositives(byte[] ba, int off, int len) { - Preconditions.checkFromIndexSize( - off, len, - ba.length, // Implicit null check on `ba` - Preconditions.AIOOBE_FORMATTER); - return countPositives0(ba, off, len); - } - @IntrinsicCandidate - private static int countPositives0(byte[] ba, int off, int len) { + public static int countPositives(byte[] ba, int off, int len) { int limit = off + len; for (int i = off; i < limit; i++) { if (ba[i] < 0) { @@ -115,37 +97,9 @@ class StringCoding { return len; } - /** - * Encodes as many ISO-8859-1 codepoints as possible from the source byte - * array containing characters encoded in UTF-16, into the destination byte - * array, assuming that the encoding is ISO-8859-1 compatible. - * - * @param sa the source byte array containing characters encoded in UTF-16 - * @param sp the index of the character (not byte!) from the source array to start reading from - * @param da the target byte array - * @param dp the index of the target array to start writing to - * @param len the maximum number of characters (not bytes!) to be encoded - * @return the total number of characters (not bytes!) successfully encoded - * @throws NullPointerException if any of the provided arrays is null - */ - static int encodeISOArray(byte[] sa, int sp, - byte[] da, int dp, int len) { - // This method should tolerate invalid arguments, matching the lenient behavior of the VM intrinsic. - // Hence, using operator expressions instead of `Preconditions`, which throw on failure. - int sl; - if ((sp | dp | len) < 0 || - // Halving the length of `sa` to obtain the number of characters: - sp >= (sl = sa.length >>> 1) || // Implicit null check on `sa` - dp >= da.length) { // Implicit null check on `da` - return 0; - } - int minLen = Math.min(len, Math.min(sl - sp, da.length - dp)); - return encodeISOArray0(sa, sp, da, dp, minLen); - } - @IntrinsicCandidate - private static int encodeISOArray0(byte[] sa, int sp, - byte[] da, int dp, int len) { + public static int implEncodeISOArray(byte[] sa, int sp, + byte[] da, int dp, int len) { int i = 0; for (; i < len; i++) { char c = StringUTF16.getChar(sa, sp++); @@ -156,35 +110,10 @@ class StringCoding { return i; } - /** - * Encodes as many ASCII codepoints as possible from the source - * character array into the destination byte array, assuming that - * the encoding is ASCII compatible. - * - * @param sa the source character array - * @param sp the index of the source array to start reading from - * @param da the target byte array - * @param dp the index of the target array to start writing to - * @param len the maximum number of characters to be encoded - * @return the total number of characters successfully encoded - * @throws NullPointerException if any of the provided arrays is null - */ - static int encodeAsciiArray(char[] sa, int sp, - byte[] da, int dp, int len) { - // This method should tolerate invalid arguments, matching the lenient behavior of the VM intrinsic. - // Hence, using operator expressions instead of `Preconditions`, which throw on failure. - if ((sp | dp | len) < 0 || - sp >= sa.length || // Implicit null check on `sa` - dp >= da.length) { // Implicit null check on `da` - return 0; - } - int minLen = Math.min(len, Math.min(sa.length - sp, da.length - dp)); - return encodeAsciiArray0(sa, sp, da, dp, minLen); - } - @IntrinsicCandidate - static int encodeAsciiArray0(char[] sa, int sp, - byte[] da, int dp, int len) { + public static int implEncodeAsciiArray(char[] sa, int sp, + byte[] da, int dp, int len) + { int i = 0; for (; i < len; i++) { char c = sa[sp++]; diff --git a/src/java.base/share/classes/java/lang/System.java b/src/java.base/share/classes/java/lang/System.java index 5c2b47afe3d..cfe09c61375 100644 --- a/src/java.base/share/classes/java/lang/System.java +++ b/src/java.base/share/classes/java/lang/System.java @@ -55,6 +55,7 @@ import java.util.Properties; import java.util.ResourceBundle; import java.util.Set; import java.util.concurrent.Executor; +import java.util.concurrent.ScheduledExecutorService; import java.util.function.Supplier; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Stream; @@ -2176,8 +2177,8 @@ public final class System { return String.decodeASCII(src, srcOff, dst, dstOff, len); } - public int encodeASCII(char[] sa, int sp, byte[] da, int dp, int len) { - return StringCoding.encodeAsciiArray(sa, sp, da, dp, len); + public int uncheckedEncodeASCII(char[] src, int srcOff, byte[] dst, int dstOff, int len) { + return StringCoding.implEncodeAsciiArray(src, srcOff, dst, dstOff, len); } public InputStream initialSystemIn() { @@ -2330,13 +2331,13 @@ public final class System { } @Override - public void copyToSegmentRaw(String string, MemorySegment segment, long offset) { - string.copyToSegmentRaw(segment, offset); + public void copyToSegmentRaw(String string, MemorySegment segment, long offset, int srcIndex, int srcLength) { + string.copyToSegmentRaw(segment, offset, srcIndex, srcLength); } @Override - public boolean bytesCompatible(String string, Charset charset) { - return string.bytesCompatible(charset); + public boolean bytesCompatible(String string, Charset charset, int srcIndex, int numChars) { + return string.bytesCompatible(charset, srcIndex, numChars); } }); } diff --git a/src/java.base/share/classes/java/lang/VirtualThread.java b/src/java.base/share/classes/java/lang/VirtualThread.java index 514bf07e665..5526618c67b 100644 --- a/src/java.base/share/classes/java/lang/VirtualThread.java +++ b/src/java.base/share/classes/java/lang/VirtualThread.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, 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 @@ -89,15 +89,19 @@ final class VirtualThread extends BaseVirtualThread { * * RUNNING -> PARKING // Thread parking with LockSupport.park * PARKING -> PARKED // cont.yield successful, parked indefinitely - * PARKING -> PINNED // cont.yield failed, parked indefinitely on carrier * PARKED -> UNPARKED // unparked, may be scheduled to continue - * PINNED -> RUNNING // unparked, continue execution on same carrier * UNPARKED -> RUNNING // continue execution after park * + * PARKING -> RUNNING // cont.yield failed, need to park on carrier + * RUNNING -> PINNED // park on carrier + * PINNED -> RUNNING // unparked, continue execution on same carrier + * * RUNNING -> TIMED_PARKING // Thread parking with LockSupport.parkNanos * TIMED_PARKING -> TIMED_PARKED // cont.yield successful, timed-parked - * TIMED_PARKING -> TIMED_PINNED // cont.yield failed, timed-parked on carrier * TIMED_PARKED -> UNPARKED // unparked, may be scheduled to continue + * + * TIMED_PARKING -> RUNNING // cont.yield failed, need to park on carrier + * RUNNING -> TIMED_PINNED // park on carrier * TIMED_PINNED -> RUNNING // unparked, continue execution on same carrier * * RUNNING -> BLOCKING // blocking on monitor enter @@ -108,7 +112,7 @@ final class VirtualThread extends BaseVirtualThread { * RUNNING -> WAITING // transitional state during wait on monitor * WAITING -> WAIT // waiting on monitor * WAIT -> BLOCKED // notified, waiting to be unblocked by monitor owner - * WAIT -> UNBLOCKED // timed-out/interrupted + * WAIT -> UNBLOCKED // interrupted * * RUNNING -> TIMED_WAITING // transition state during timed-waiting on monitor * TIMED_WAITING -> TIMED_WAIT // timed-waiting on monitor @@ -616,9 +620,12 @@ final class VirtualThread extends BaseVirtualThread { // Object.wait if (s == WAITING || s == TIMED_WAITING) { int newState; + boolean blocked; boolean interruptible = interruptibleWait; if (s == WAITING) { setState(newState = WAIT); + // may have been notified while in transition + blocked = notified && compareAndSetState(WAIT, BLOCKED); } else { // For timed-wait, a timeout task is scheduled to execute. The timeout // task will change the thread state to UNBLOCKED and submit the thread @@ -633,22 +640,22 @@ final class VirtualThread extends BaseVirtualThread { byte seqNo = ++timedWaitSeqNo; timeoutTask = schedule(() -> waitTimeoutExpired(seqNo), timeout, MILLISECONDS); setState(newState = TIMED_WAIT); + // May have been notified while in transition. This must be done while + // holding the monitor to avoid changing the state of a new timed wait call. + blocked = notified && compareAndSetState(TIMED_WAIT, BLOCKED); } } - // may have been notified while in transition to wait state - if (notified && compareAndSetState(newState, BLOCKED)) { - // may have even been unblocked already + if (blocked) { + // may have been unblocked already if (blockPermit && compareAndSetState(BLOCKED, UNBLOCKED)) { - submitRunContinuation(); + lazySubmitRunContinuation(); + } + } else { + // may have been interrupted while in transition to wait state + if (interruptible && interrupted && compareAndSetState(newState, UNBLOCKED)) { + lazySubmitRunContinuation(); } - return; - } - - // may have been interrupted while in transition to wait state - if (interruptible && interrupted && compareAndSetState(newState, UNBLOCKED)) { - submitRunContinuation(); - return; } return; } @@ -856,16 +863,20 @@ final class VirtualThread extends BaseVirtualThread { * Re-enables this virtual thread for scheduling. If this virtual thread is parked * then its task is scheduled to continue, otherwise its next call to {@code park} or * {@linkplain #parkNanos(long) parkNanos} is guaranteed not to block. + * @param lazySubmit to use lazySubmit if possible * @throws RejectedExecutionException if the scheduler cannot accept a task */ - @Override - void unpark() { + private void unpark(boolean lazySubmit) { if (!getAndSetParkPermit(true) && currentThread() != this) { int s = state(); // unparked while parked if ((s == PARKED || s == TIMED_PARKED) && compareAndSetState(s, UNPARKED)) { - submitRunContinuation(); + if (lazySubmit) { + lazySubmitRunContinuation(); + } else { + submitRunContinuation(); + } return; } @@ -888,6 +899,11 @@ final class VirtualThread extends BaseVirtualThread { } } + @Override + void unpark() { + unpark(false); + } + /** * Invoked by unblocker thread to unblock this virtual thread. */ @@ -904,11 +920,7 @@ final class VirtualThread extends BaseVirtualThread { */ private void parkTimeoutExpired() { assert !VirtualThread.currentThread().isVirtual(); - if (!getAndSetParkPermit(true) - && (state() == TIMED_PARKED) - && compareAndSetState(TIMED_PARKED, UNPARKED)) { - lazySubmitRunContinuation(); - } + unpark(true); } /** diff --git a/src/java.base/share/classes/java/lang/classfile/instruction/IncrementInstruction.java b/src/java.base/share/classes/java/lang/classfile/instruction/IncrementInstruction.java index 352f83f529c..4054a06c3e2 100644 --- a/src/java.base/share/classes/java/lang/classfile/instruction/IncrementInstruction.java +++ b/src/java.base/share/classes/java/lang/classfile/instruction/IncrementInstruction.java @@ -31,6 +31,8 @@ import java.lang.classfile.Instruction; import java.lang.classfile.Opcode; import jdk.internal.classfile.impl.AbstractInstruction; +import jdk.internal.classfile.impl.BytecodeHelpers; +import jdk.internal.classfile.impl.Util; /** * Models a local variable increment instruction in the {@code code} array of a @@ -82,6 +84,37 @@ public sealed interface IncrementInstruction extends Instruction * @throws IllegalArgumentException if {@code slot} or {@code constant} is out of range */ static IncrementInstruction of(int slot, int constant) { - return new AbstractInstruction.UnboundIncrementInstruction(slot, constant); + var opcode = BytecodeHelpers.validateAndIsWideIinc(slot, constant) ? Opcode.IINC_W: Opcode.IINC; + return new AbstractInstruction.UnboundIncrementInstruction(opcode, slot, constant); + } + + /** + * {@return an increment instruction} + *

    + * {@code slot} must be {@link java.lang.classfile##u1 u1} and + * {@code constant} must be within {@code [-128, 127]} for + * {@link Opcode#IINC iinc}, or {@code slot} must be + * {@link java.lang.classfile##u2 u2} and {@code constant} must be + * within {@code [-32768, 32767]} for {@link Opcode#IINC_W wide iinc}. + * + * @apiNote + * The explicit {@code op} argument allows creating {@code wide} or + * regular increment instructions when {@code slot} and + * {@code constant} can be encoded with more optimized + * increment instructions. + * + * @param op the opcode for the specific type of increment instruction, + * which must be of kind {@link Opcode.Kind#INCREMENT} + * @param slot the local variable slot to increment + * @param constant the increment constant + * @throws IllegalArgumentException if the opcode kind is not + * {@link Opcode.Kind#INCREMENT} or {@code slot} or + * {@code constant} is out of range + * @since 27 + */ + static IncrementInstruction of(Opcode op, int slot, int constant) { + Util.checkKind(op, Opcode.Kind.INCREMENT); + BytecodeHelpers.validateIncrement(op, slot, constant); + return new AbstractInstruction.UnboundIncrementInstruction(op, slot, constant); } } diff --git a/src/java.base/share/classes/java/lang/foreign/MemorySegment.java b/src/java.base/share/classes/java/lang/foreign/MemorySegment.java index 196f44d1abe..78098e39a17 100644 --- a/src/java.base/share/classes/java/lang/foreign/MemorySegment.java +++ b/src/java.base/share/classes/java/lang/foreign/MemorySegment.java @@ -1296,12 +1296,7 @@ public sealed interface MemorySegment permits AbstractMemorySegmentImpl { * over the decoding process is required. *

    * Getting a string from a segment with a known byte offset and - * known byte length can be done like so: - * {@snippet lang=java : - * byte[] bytes = new byte[length]; - * MemorySegment.copy(segment, JAVA_BYTE, offset, bytes, 0, length); - * return new String(bytes, charset); - * } + * known byte length can be done using {@link #getString(long, Charset, long)}. * * @param offset offset in bytes (relative to this segment address) at which this * access operation will occur @@ -1328,6 +1323,41 @@ public sealed interface MemorySegment permits AbstractMemorySegmentImpl { */ String getString(long offset, Charset charset); + /** + * Reads a string from this segment at the given offset, using the provided length + * and charset. + *

    + * This method always replaces malformed-input and unmappable-character + * sequences with this charset's default replacement string. The {@link + * java.nio.charset.CharsetDecoder} class should be used when more control + * over the decoding process is required. + *

    + * If the string contains any {@code '\0'} characters, they will be read as well. + * This differs from {@link #getString(long, Charset)}, which will only read up + * to the first {@code '\0'}, resulting in truncation for string data that contains + * the {@code '\0'} character. + * + * @param offset offset in bytes (relative to this segment address) at which this + * access operation will occur + * @param charset the charset used to {@linkplain Charset#newDecoder() decode} the + * string bytes + * @param byteLength length, in bytes, of the region of memory to read and decode into + * a string + * @return a Java string constructed from the bytes read from the given starting + * address up to the given length + * @throws IllegalArgumentException if the size of the string is greater than the + * largest string supported by the platform + * @throws IndexOutOfBoundsException if {@code offset < 0} + * @throws IndexOutOfBoundsException if {@code offset > byteSize() - byteLength} + * @throws IllegalStateException if the {@linkplain #scope() scope} associated with + * this segment is not {@linkplain Scope#isAlive() alive} + * @throws WrongThreadException if this method is called from a thread {@code T}, + * such that {@code isAccessibleBy(T) == false} + * @throws IllegalArgumentException if {@code byteLength < 0} + * @since 27 + */ + String getString(long offset, Charset charset, long byteLength); + /** * Writes the given string into this segment at the given offset, converting it to * a null-terminated byte sequence using the {@linkplain StandardCharsets#UTF_8 UTF-8} @@ -1366,7 +1396,8 @@ public sealed interface MemorySegment permits AbstractMemorySegmentImpl { * If the given string contains any {@code '\0'} characters, they will be * copied as well. This means that, depending on the method used to read * the string, such as {@link MemorySegment#getString(long)}, the string - * will appear truncated when read again. + * will appear truncated when read again. The string can be read without + * truncation using {@link #getString(long, Charset, long)}. * * @param offset offset in bytes (relative to this segment address) at which this * access operation will occur, the final address of this write @@ -2606,6 +2637,51 @@ public sealed interface MemorySegment permits AbstractMemorySegmentImpl { elementCount); } + /** + * Copies the byte sequence of the given string encoded using the provided charset + * to the destination segment. + *

    + * This method always replaces malformed-input and unmappable-character + * sequences with this charset's default replacement string. The {@link + * java.nio.charset.CharsetDecoder} class should be used when more control + * over the decoding process is required. + *

    + * If the given string contains any {@code '\0'} characters, they will be + * copied as well. This means that, depending on the method used to read + * the string, such as {@link MemorySegment#getString(long)}, the string + * will appear truncated when read again. The string can be read without + * truncation using {@link #getString(long, Charset, long)}. + * + * @param src the Java string to be written into the destination segment + * @param dstEncoding the charset used to {@linkplain Charset#newEncoder() encode} + * the string bytes. + * @param srcIndex the starting character index of the source string + * @param dst the destination segment + * @param dstOffset the starting offset, in bytes, of the destination segment + * @param numChars the number of characters to be copied + * @throws IllegalStateException if the {@linkplain #scope() scope} associated with + * {@code dst} is not {@linkplain Scope#isAlive() alive} + * @throws WrongThreadException if this method is called from a thread {@code T}, + * such that {@code dst.isAccessibleBy(T) == false} + * @throws IndexOutOfBoundsException if either {@code srcIndex}, {@code numChars}, or {@code dstOffset} + * are {@code < 0} + * @throws IndexOutOfBoundsException if {@code srcIndex > src.length() - numChars} + * @throws IllegalArgumentException if {@code dst} is {@linkplain #isReadOnly() read-only} + * @throws IndexOutOfBoundsException if {@code dstOffset > dstSegment.byteSize() - B} where {@code B} is the size, + * in bytes, of the substring of {@code src} encoded using the given charset + * @return the number of copied bytes. + * @since 27 + */ + @ForceInline + static long copy(String src, Charset dstEncoding, int srcIndex, MemorySegment dst, long dstOffset, int numChars) { + Objects.requireNonNull(src); + Objects.requireNonNull(dstEncoding); + Objects.requireNonNull(dst); + Objects.checkFromIndexSize(srcIndex, numChars, src.length()); + + return AbstractMemorySegmentImpl.copy(src, dstEncoding, srcIndex, dst, dstOffset, numChars); + } + /** * Finds and returns the relative offset, in bytes, of the first mismatch between the * source and the destination segments. More specifically, the bytes at offset diff --git a/src/java.base/share/classes/java/lang/foreign/SegmentAllocator.java b/src/java.base/share/classes/java/lang/foreign/SegmentAllocator.java index 1297406dcf1..03d92ec24ef 100644 --- a/src/java.base/share/classes/java/lang/foreign/SegmentAllocator.java +++ b/src/java.base/share/classes/java/lang/foreign/SegmentAllocator.java @@ -111,7 +111,8 @@ public interface SegmentAllocator { * If the given string contains any {@code '\0'} characters, they will be * copied as well. This means that, depending on the method used to read * the string, such as {@link MemorySegment#getString(long)}, the string - * will appear truncated when read again. + * will appear truncated when read again. The string can be read without + * truncation using {@link MemorySegment#getString(long, Charset, long)}. * * @param str the Java string to be converted into a C string * @param charset the charset used to {@linkplain Charset#newEncoder() encode} the @@ -137,10 +138,10 @@ public interface SegmentAllocator { int termCharSize = StringSupport.CharsetKind.of(charset).terminatorCharSize(); MemorySegment segment; int length; - if (StringSupport.bytesCompatible(str, charset)) { + if (StringSupport.bytesCompatible(str, charset, 0, str.length())) { length = str.length(); segment = allocateNoInit((long) length + termCharSize); - StringSupport.copyToSegmentRaw(str, segment, 0); + StringSupport.copyToSegmentRaw(str, segment, 0, 0, str.length()); } else { byte[] bytes = str.getBytes(charset); length = bytes.length; @@ -153,6 +154,54 @@ public interface SegmentAllocator { return segment; } + /** + * Encodes a Java string using the provided charset and stores the resulting + * byte array into a memory segment. + *

    + * This method always replaces malformed-input and unmappable-character + * sequences with this charset's default replacement byte array. The + * {@link java.nio.charset.CharsetEncoder} class should be used when more + * control over the encoding process is required. + *

    + * If the given string contains any {@code '\0'} characters, they will be + * copied as well. This means that, depending on the method used to read + * the string, such as {@link MemorySegment#getString(long)}, the string + * will appear truncated when read again. The string can be read without + * truncation using {@link MemorySegment#getString(long, Charset, long)}. + * + * @param str the Java string to be encoded + * @param charset the charset used to {@linkplain Charset#newEncoder() encode} the + * string bytes + * @param srcIndex the starting index of the source string + * @param numChars the number of characters to be copied + * @return a new native segment containing the encoded string + * @throws IndexOutOfBoundsException if either {@code srcIndex} or {@code numChars} are {@code < 0} + * @throws IndexOutOfBoundsException if {@code srcIndex > str.length() - numChars} + * + * @implSpec The default implementation for this method copies the contents of the + * provided Java string into a new memory segment obtained by calling + * {@code this.allocate(B)}, where {@code B} is the size, in bytes, of + * the string encoded using the provided charset + * (e.g. {@code str.getBytes(charset).length}); + * @since 27 + */ + @ForceInline + default MemorySegment allocateFrom(String str, Charset charset, int srcIndex, int numChars) { + Objects.requireNonNull(charset); + Objects.requireNonNull(str); + Objects.checkFromIndexSize(srcIndex, numChars, str.length()); + MemorySegment segment; + if (StringSupport.bytesCompatible(str, charset, srcIndex, numChars)) { + segment = allocateNoInit(numChars); + StringSupport.copyToSegmentRaw(str, segment, 0, srcIndex, numChars); + } else { + byte[] bytes = str.substring(srcIndex, srcIndex + numChars).getBytes(charset); + segment = allocateNoInit(bytes.length); + MemorySegment.copy(bytes, 0, segment, ValueLayout.JAVA_BYTE, 0, bytes.length); + } + return segment; + } + /** * {@return a new memory segment initialized with the provided byte value} *

    diff --git a/src/java.base/share/classes/java/lang/invoke/MethodHandles.java b/src/java.base/share/classes/java/lang/invoke/MethodHandles.java index feb8aaaa1a9..7fde9d1dd89 100644 --- a/src/java.base/share/classes/java/lang/invoke/MethodHandles.java +++ b/src/java.base/share/classes/java/lang/invoke/MethodHandles.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2026, 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 @@ -4826,7 +4826,9 @@ assert((int)twice.invokeExact(21) == 42); * Before the method handle is returned, the passed-in value is converted to the requested type. * If the requested type is primitive, widening primitive conversions are attempted, * else reference conversions are attempted. - *

    The returned method handle is equivalent to {@code identity(type).bindTo(value)}. + *

    The returned method handle is equivalent to {@code identity(type).bindTo(value)}, + * for reference types. For all types it is equivalent to + * {@code insertArguments(identity(type), 0, value)}. * @param type the return type of the desired method handle * @param value the value to return * @return a method handle of the given return type and no arguments, which always returns the given value diff --git a/src/java.base/share/classes/java/lang/runtime/ExactConversionsSupport.java b/src/java.base/share/classes/java/lang/runtime/ExactConversionsSupport.java index a1a93859c42..8d4389f7295 100644 --- a/src/java.base/share/classes/java/lang/runtime/ExactConversionsSupport.java +++ b/src/java.base/share/classes/java/lang/runtime/ExactConversionsSupport.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2026, 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 @@ -64,12 +64,9 @@ package java.lang.runtime; * floating-point type is considered exact. * * - * @see - * JLS 5.7.1 Exact Testing Conversions - * @see - * JLS 5.7.2 Unconditionally Exact Testing Conversions - * @see - * JLS 15.20.2 The instanceof Operator + * @jls primitive-types-in-patterns-instanceof-switch-5.7.1 Exact Testing Conversions + * @jls primitive-types-in-patterns-instanceof-switch-5.7.2 Unconditionally Exact Testing Conversions + * @jls primitive-types-in-patterns-instanceof-switch-15.20.2 The {@code instanceof} Operator * * @implNote Some exactness checks describe a test which can be redirected * safely through one of the existing methods. Those are omitted too (i.e., diff --git a/src/java.base/share/classes/java/lang/runtime/SwitchBootstraps.java b/src/java.base/share/classes/java/lang/runtime/SwitchBootstraps.java index 30b6df0073e..087d2cc23a9 100644 --- a/src/java.base/share/classes/java/lang/runtime/SwitchBootstraps.java +++ b/src/java.base/share/classes/java/lang/runtime/SwitchBootstraps.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, 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 @@ -188,13 +188,17 @@ public final class SwitchBootstraps { String invocationName, MethodType invocationType, Object... labels) { + requireNonNull(lookup); + requireNonNull(invocationType); + requireNonNull(labels); + Class selectorType = invocationType.parameterType(0); if (invocationType.parameterCount() != 2 || (!invocationType.returnType().equals(int.class)) || !invocationType.parameterType(1).equals(int.class)) throw new IllegalArgumentException("Illegal invocation type " + invocationType); - for (Object l : labels) { // implicit null-check + for (Object l : labels) { verifyLabel(l, selectorType); } @@ -292,6 +296,10 @@ public final class SwitchBootstraps { String invocationName, MethodType invocationType, Object... labels) { + requireNonNull(lookup); + requireNonNull(invocationType); + requireNonNull(labels); + if (invocationType.parameterCount() != 2 || (!invocationType.returnType().equals(int.class)) || invocationType.parameterType(0).isPrimitive() @@ -299,7 +307,7 @@ public final class SwitchBootstraps { || !invocationType.parameterType(1).equals(int.class)) throw new IllegalArgumentException("Illegal invocation type " + invocationType); - labels = labels.clone(); // implicit null check + labels = labels.clone(); Class enumClass = invocationType.parameterType(0); boolean constantsOnly = true; @@ -307,7 +315,7 @@ public final class SwitchBootstraps { for (int i = 0; i < len; i++) { Object convertedLabel = - convertEnumConstants(lookup, enumClass, labels[i]); + convertEnumConstants(enumClass, labels[i]); labels[i] = convertedLabel; if (constantsOnly) constantsOnly = convertedLabel instanceof EnumDesc; @@ -331,7 +339,7 @@ public final class SwitchBootstraps { return new ConstantCallSite(target); } - private static > Object convertEnumConstants(MethodHandles.Lookup lookup, Class enumClassTemplate, Object label) { + private static > Object convertEnumConstants(Class enumClassTemplate, Object label) { if (label == null) { throw new IllegalArgumentException("null label found"); } diff --git a/src/java.base/share/classes/java/nio/charset/Charset-X-Coder.java.template b/src/java.base/share/classes/java/nio/charset/Charset-X-Coder.java.template index e900c2eca0f..aca987ed678 100644 --- a/src/java.base/share/classes/java/nio/charset/Charset-X-Coder.java.template +++ b/src/java.base/share/classes/java/nio/charset/Charset-X-Coder.java.template @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, 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 @@ -819,6 +819,12 @@ public abstract class Charset$Coder$ { */ public final $Otype$Buffer $code$($Itype$Buffer in) throws CharacterCodingException + { + return $code$(in, true); + } + + private $Otype$Buffer $code$($Itype$Buffer in, boolean throwOnError) + throws CharacterCodingException { int n = Math.min((int)(in.remaining() * average$ItypesPerOtype$()), ArraysSupport.SOFT_MAX_ARRAY_LENGTH); @@ -844,7 +850,11 @@ public abstract class Charset$Coder$ { out = o; continue; } - cr.throwException(); + if (throwOnError) { + cr.throwException(); + } else { + return null; + } } out.flip(); return out; @@ -938,7 +948,8 @@ public abstract class Charset$Coder$ { try { onMalformedInput(CodingErrorAction.REPORT); onUnmappableCharacter(CodingErrorAction.REPORT); - encode(cb); + ByteBuffer bb = encode(cb, false); + return bb != null; } catch (CharacterCodingException x) { return false; } finally { @@ -946,7 +957,6 @@ public abstract class Charset$Coder$ { onUnmappableCharacter(ua); reset(); } - return true; } /** diff --git a/src/java.base/share/classes/java/nio/file/SecureDirectoryStream.java b/src/java.base/share/classes/java/nio/file/SecureDirectoryStream.java index 4348c60f5e2..92a292bbac6 100644 --- a/src/java.base/share/classes/java/nio/file/SecureDirectoryStream.java +++ b/src/java.base/share/classes/java/nio/file/SecureDirectoryStream.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2007, 2026, 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 @@ -185,8 +185,8 @@ public interface SecureDirectoryStream /** * Move a file from this directory to another directory. * - *

    This method works in a similar manner to {@link Files#move move} - * method when the {@link StandardCopyOption#ATOMIC_MOVE ATOMIC_MOVE} option + *

    This method works in a similar manner to {@link Files#move Files.move} + * when the {@link StandardCopyOption#ATOMIC_MOVE ATOMIC_MOVE} option * is specified. That is, this method moves a file as an atomic file system * operation. If the {@code srcpath} parameter is an {@link Path#isAbsolute * absolute} path then it locates the source file. If the parameter is a @@ -194,14 +194,15 @@ public interface SecureDirectoryStream * the {@code targetpath} parameter is absolute then it locates the target * file (the {@code targetdir} parameter is ignored). If the parameter is * a relative path it is located relative to the open directory identified - * by the {@code targetdir} parameter. In all cases, if the target file - * exists then it is implementation specific if it is replaced or this - * method fails. + * by the {@code targetdir} parameter, unless {@code targetdir} is + * {@code null}, in which case it is located relative to the current + * working directory. In all cases, if the target file exists then it is + * implementation specific if it is replaced or this method fails. * * @param srcpath * the name of the file to move * @param targetdir - * the destination directory + * the destination directory; can be {@code null} * @param targetpath * the name to give the file in the destination directory * diff --git a/src/java.base/share/classes/java/security/CodeSource.java b/src/java.base/share/classes/java/security/CodeSource.java index 7476b8a1d61..9e69b2f0849 100644 --- a/src/java.base/share/classes/java/security/CodeSource.java +++ b/src/java.base/share/classes/java/security/CodeSource.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, 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 @@ -238,7 +238,12 @@ public class CodeSource implements java.io.Serializable { } else if (certs != null) { // Convert the certs to code signers signers = convertCertArrayToSignerArray(certs); - return signers.clone(); + if (signers != null) { + return signers.clone(); + + } else { + return new CodeSigner[0]; + } } else { return null; diff --git a/src/java.base/share/classes/java/security/Security.java b/src/java.base/share/classes/java/security/Security.java index 6969fe8a8e1..30a22b05742 100644 --- a/src/java.base/share/classes/java/security/Security.java +++ b/src/java.base/share/classes/java/security/Security.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2026, 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,6 +34,7 @@ import java.net.URL; import java.nio.file.Files; import java.nio.file.InvalidPathException; import java.nio.file.Path; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Enumeration; @@ -112,7 +113,7 @@ public final class Security { private static Path currentPath; - private static final Set activePaths = new HashSet<>(); + private static final List activePaths = new ArrayList<>(); static void loadAll() { // first load the master properties file to @@ -262,30 +263,40 @@ public final class Security { } } + private static void checkCyclicInclude(Path path) { + for (Path activePath : activePaths) { + try { + if (Files.isSameFile(path, activePath)) { + throw new InternalError( + "Cyclic include of '" + path + "'"); + } + } catch (IOException e) { + if (sdebug != null) { + sdebug.println("skipped exception when checking for " + + "cyclic inclusion of " + path + ":"); + e.printStackTrace(); + } + } + } + } + private static void loadFromPath(Path path, LoadingMode mode) throws IOException { - boolean isRegularFile = Files.isRegularFile(path); - if (isRegularFile) { - path = path.toRealPath(); - } else if (Files.isDirectory(path)) { + if (Files.isDirectory(path)) { throw new IOException("Is a directory"); - } else { - path = path.toAbsolutePath(); - } - if (activePaths.contains(path)) { - throw new InternalError("Cyclic include of '" + path + "'"); } try (InputStream is = Files.newInputStream(path)) { + checkCyclicInclude(path); reset(mode); Path previousPath = currentPath; - currentPath = isRegularFile ? path : null; + currentPath = Files.isRegularFile(path) ? path : null; activePaths.add(path); try { debugLoad(true, path); props.load(is); debugLoad(false, path); } finally { - activePaths.remove(path); + activePaths.removeLast(); currentPath = previousPath; } } diff --git a/src/java.base/share/classes/java/time/ZonedDateTime.java b/src/java.base/share/classes/java/time/ZonedDateTime.java index 57dc98d5c68..b1ffe7b87d6 100644 --- a/src/java.base/share/classes/java/time/ZonedDateTime.java +++ b/src/java.base/share/classes/java/time/ZonedDateTime.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2026, 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 @@ -2207,7 +2207,10 @@ public final class ZonedDateTime * Outputs this date-time as a {@code String}, such as * {@code 2007-12-03T10:15:30+01:00[Europe/Paris]}. *

    - * The format consists of the {@code LocalDateTime} followed by the {@code ZoneOffset}. + * The format consists of the output of {@link LocalDateTime#toString()}, + * followed by the output of {@link ZoneOffset#toString()}. + * If the time has zero seconds and/or nanoseconds, they are + * omitted to produce the shortest representation. * If the {@code ZoneId} is not the same as the offset, then the ID is output. * The output is compatible with ISO-8601 if the offset and ID are the same, * and the seconds in the offset are zero. diff --git a/src/java.base/share/classes/java/time/format/DateTimeFormatter.java b/src/java.base/share/classes/java/time/format/DateTimeFormatter.java index 16d7193c556..9368cf54afd 100644 --- a/src/java.base/share/classes/java/time/format/DateTimeFormatter.java +++ b/src/java.base/share/classes/java/time/format/DateTimeFormatter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2026, 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 @@ -817,6 +817,7 @@ public final class DateTimeFormatter { *

  • The {@link #ISO_LOCAL_DATE} *
  • The {@link ZoneOffset#getId() offset ID}. If the offset has seconds then * they will be handled even though this is not part of the ISO-8601 standard. + * The offset parsing is lenient, which allows the minutes and seconds to be optional. * Parsing is case insensitive. * *

    @@ -829,7 +830,9 @@ public final class DateTimeFormatter { ISO_OFFSET_DATE = new DateTimeFormatterBuilder() .parseCaseInsensitive() .append(ISO_LOCAL_DATE) + .parseLenient() .appendOffsetId() + .parseStrict() .toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE); } @@ -846,6 +849,7 @@ public final class DateTimeFormatter { *

  • If the offset is not available then the format is complete. *
  • The {@link ZoneOffset#getId() offset ID}. If the offset has seconds then * they will be handled even though this is not part of the ISO-8601 standard. + * The offset parsing is lenient, which allows the minutes and seconds to be optional. * Parsing is case insensitive. * *

    @@ -862,7 +866,9 @@ public final class DateTimeFormatter { .parseCaseInsensitive() .append(ISO_LOCAL_DATE) .optionalStart() + .parseLenient() .appendOffsetId() + .parseStrict() .toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE); } @@ -919,6 +925,7 @@ public final class DateTimeFormatter { *

  • The {@link #ISO_LOCAL_TIME} *
  • The {@link ZoneOffset#getId() offset ID}. If the offset has seconds then * they will be handled even though this is not part of the ISO-8601 standard. + * The offset parsing is lenient, which allows the minutes and seconds to be optional. * Parsing is case insensitive. * *

    @@ -930,7 +937,9 @@ public final class DateTimeFormatter { ISO_OFFSET_TIME = new DateTimeFormatterBuilder() .parseCaseInsensitive() .append(ISO_LOCAL_TIME) + .parseLenient() .appendOffsetId() + .parseStrict() .toFormatter(ResolverStyle.STRICT, null); } @@ -947,6 +956,7 @@ public final class DateTimeFormatter { *

  • If the offset is not available then the format is complete. *
  • The {@link ZoneOffset#getId() offset ID}. If the offset has seconds then * they will be handled even though this is not part of the ISO-8601 standard. + * The offset parsing is lenient, which allows the minutes and seconds to be optional. * Parsing is case insensitive. * *

    @@ -962,7 +972,9 @@ public final class DateTimeFormatter { .parseCaseInsensitive() .append(ISO_LOCAL_TIME) .optionalStart() + .parseLenient() .appendOffsetId() + .parseStrict() .toFormatter(ResolverStyle.STRICT, null); } @@ -1075,6 +1087,7 @@ public final class DateTimeFormatter { *

  • If the offset is not available to format or parse then the format is complete. *
  • The {@link ZoneOffset#getId() offset ID}. If the offset has seconds then * they will be handled even though this is not part of the ISO-8601 standard. + * The offset parsing is lenient, which allows the minutes and seconds to be optional. *
  • If the zone ID is not available or is a {@code ZoneOffset} then the format is complete. *
  • An open square bracket '['. *
  • The {@link ZoneId#getId() zone ID}. This is not part of the ISO-8601 standard. @@ -1094,7 +1107,9 @@ public final class DateTimeFormatter { ISO_DATE_TIME = new DateTimeFormatterBuilder() .append(ISO_LOCAL_DATE_TIME) .optionalStart() + .parseLenient() .appendOffsetId() + .parseStrict() .optionalStart() .appendLiteral('[') .parseCaseSensitive() @@ -1121,6 +1136,7 @@ public final class DateTimeFormatter { *
  • If the offset is not available to format or parse then the format is complete. *
  • The {@link ZoneOffset#getId() offset ID}. If the offset has seconds then * they will be handled even though this is not part of the ISO-8601 standard. + * The offset parsing is lenient, which allows the minutes and seconds to be optional. * Parsing is case insensitive. * *

    @@ -1139,7 +1155,9 @@ public final class DateTimeFormatter { .appendLiteral('-') .appendValue(DAY_OF_YEAR, 3) .optionalStart() + .parseLenient() .appendOffsetId() + .parseStrict() .toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE); } @@ -1165,6 +1183,7 @@ public final class DateTimeFormatter { *

  • If the offset is not available to format or parse then the format is complete. *
  • The {@link ZoneOffset#getId() offset ID}. If the offset has seconds then * they will be handled even though this is not part of the ISO-8601 standard. + * The offset parsing is lenient, which allows the minutes and seconds to be optional. * Parsing is case insensitive. * *

    @@ -1185,7 +1204,9 @@ public final class DateTimeFormatter { .appendLiteral('-') .appendValue(DAY_OF_WEEK, 1) .optionalStart() + .parseLenient() .appendOffsetId() + .parseStrict() .toFormatter(ResolverStyle.STRICT, IsoChronology.INSTANCE); } diff --git a/src/java.base/share/classes/java/util/List.java b/src/java.base/share/classes/java/util/List.java index 43408de292a..5f9a90e1748 100644 --- a/src/java.base/share/classes/java/util/List.java +++ b/src/java.base/share/classes/java/util/List.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, 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 @@ -1224,7 +1224,7 @@ public interface List extends SequencedCollection { * The returned list and its {@link List#subList(int, int) subList()} or * {@link List#reversed()} views implement the {@link RandomAccess} interface. *

    - * If the provided computing function recursively calls itself or the returned + * If the provided computing function recursively calls itself via the returned * lazy list for the same index, an {@linkplain IllegalStateException} * will be thrown. *

    diff --git a/src/java.base/share/classes/java/util/Map.java b/src/java.base/share/classes/java/util/Map.java index 177f0522b1b..fa16fb89050 100644 --- a/src/java.base/share/classes/java/util/Map.java +++ b/src/java.base/share/classes/java/util/Map.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, 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 @@ -1777,7 +1777,7 @@ public interface Map { * The values of any {@link Map#values()} or {@link Map#entrySet()} views of * the returned map are also lazily computed. *

    - * If the provided computing function recursively calls itself or + * If the provided computing function recursively calls itself via * the returned lazy map for the same key, an {@linkplain IllegalStateException} * will be thrown. *

    diff --git a/src/java.base/share/classes/java/util/Optional.java b/src/java.base/share/classes/java/util/Optional.java index 3e577bd379c..3d0375c4354 100644 --- a/src/java.base/share/classes/java/util/Optional.java +++ b/src/java.base/share/classes/java/util/Optional.java @@ -25,7 +25,7 @@ package java.util; -import jdk.internal.vm.annotation.Stable; +import jdk.internal.vm.annotation.TrustFinalFields; import java.util.function.Consumer; import java.util.function.Function; @@ -62,6 +62,7 @@ import java.util.stream.Stream; * @since 1.8 */ @jdk.internal.ValueBased +@TrustFinalFields public final class Optional { /** * Common instance for {@code empty()}. @@ -71,7 +72,6 @@ public final class Optional { /** * If non-null, the value; if null, indicates no value is present */ - @Stable private final T value; /** diff --git a/src/java.base/share/classes/java/util/concurrent/CopyOnWriteArraySet.java b/src/java.base/share/classes/java/util/concurrent/CopyOnWriteArraySet.java index cef1682b0b1..abc9fdb348c 100644 --- a/src/java.base/share/classes/java/util/concurrent/CopyOnWriteArraySet.java +++ b/src/java.base/share/classes/java/util/concurrent/CopyOnWriteArraySet.java @@ -35,6 +35,13 @@ package java.util.concurrent; +import jdk.internal.misc.Unsafe; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectStreamException; +import java.io.Serial; +import java.io.StreamCorruptedException; import java.util.AbstractSet; import java.util.Collection; import java.util.Iterator; @@ -445,4 +452,38 @@ public class CopyOnWriteArraySet extends AbstractSet return Spliterators.spliterator (al.getArray(), Spliterator.IMMUTABLE | Spliterator.DISTINCT); } + + /** + * De-serialization without data not supported for this class. + */ + @Serial + private void readObjectNoData() throws ObjectStreamException { + throw new StreamCorruptedException("Deserialized CopyOnWriteArraySet requires data"); + } + + /** + * Reconstitutes the {@code CopyOnWriteArraySet} instance from a stream + * (that is, deserializes it). + * @throws StreamCorruptedException if the object read from the stream is invalid. + */ + @Serial + private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { + CopyOnWriteArrayList newAl; // Set during the duplicate check + + @SuppressWarnings("unchecked") + CopyOnWriteArrayList inAl = (CopyOnWriteArrayList) in.readFields().get("al", null); + + if (inAl == null + || inAl.getClass() != CopyOnWriteArrayList.class + || (newAl = new CopyOnWriteArrayList<>()).addAllAbsent(inAl) != inAl.size()) { + throw new StreamCorruptedException("Content is invalid"); + } + + final Unsafe U = Unsafe.getUnsafe(); + U.putReference( + this, + U.objectFieldOffset(CopyOnWriteArraySet.class, "al"), + newAl + ); + } } diff --git a/src/java.base/share/classes/java/util/concurrent/StructuredTaskScope.java b/src/java.base/share/classes/java/util/concurrent/StructuredTaskScope.java index 01bf1158bf6..66ba2c29bb3 100644 --- a/src/java.base/share/classes/java/util/concurrent/StructuredTaskScope.java +++ b/src/java.base/share/classes/java/util/concurrent/StructuredTaskScope.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, 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 @@ -1081,10 +1081,9 @@ public sealed interface StructuredTaskScope * does not throw then the {@code Joiner}'s {@code result()} method is invoked to * get the result or throw. * - *

    This method may only be invoked by the scope owner. Once the result or - * exception outcome is obtained, this method may not be invoked again. The only - * case where the method may be called again is where {@code InterruptedException} - * is thrown while waiting. + *

    This method may only be invoked by the scope owner. It may only be invoked once + * to get the result, exception or timeout outcome, unless the previous invocation + * resulted in an {@code InterruptedException} being thrown. * * @return the result * @throws WrongThreadException if the current thread is not the scope owner @@ -1093,8 +1092,11 @@ public sealed interface StructuredTaskScope * exception from {@link Joiner#result() Joiner.result()} as the cause * @throws TimeoutException if a timeout is set, the timeout expires before or while * waiting, and {@link Joiner#onTimeout() Joiner.onTimeout()} throws this exception - * @throws InterruptedException if interrupted while waiting + * @throws InterruptedException if the current thread is interrupted before or + * while waiting. The current thread's interrupted status is cleared when this + * exception is thrown. * @since 25 + * @see Thread##thread-interruption Thread Interruption */ R join() throws InterruptedException; diff --git a/src/java.base/share/classes/java/util/concurrent/atomic/AtomicIntegerFieldUpdater.java b/src/java.base/share/classes/java/util/concurrent/atomic/AtomicIntegerFieldUpdater.java index 2250009e8f5..70acb8a0889 100644 --- a/src/java.base/share/classes/java/util/concurrent/atomic/AtomicIntegerFieldUpdater.java +++ b/src/java.base/share/classes/java/util/concurrent/atomic/AtomicIntegerFieldUpdater.java @@ -42,6 +42,8 @@ import java.util.function.IntUnaryOperator; import jdk.internal.misc.Unsafe; import jdk.internal.reflect.CallerSensitive; import jdk.internal.reflect.Reflection; +import jdk.internal.vm.annotation.TrustFinalFields; + import java.lang.invoke.VarHandle; /** @@ -371,6 +373,7 @@ public abstract class AtomicIntegerFieldUpdater { /** * Standard hotspot implementation using intrinsics. */ + @TrustFinalFields private static final class AtomicIntegerFieldUpdaterImpl extends AtomicIntegerFieldUpdater { private static final Unsafe U = Unsafe.getUnsafe(); diff --git a/src/java.base/share/classes/java/util/concurrent/atomic/AtomicLongFieldUpdater.java b/src/java.base/share/classes/java/util/concurrent/atomic/AtomicLongFieldUpdater.java index 5f0a666cb04..d3a3fe63d0f 100644 --- a/src/java.base/share/classes/java/util/concurrent/atomic/AtomicLongFieldUpdater.java +++ b/src/java.base/share/classes/java/util/concurrent/atomic/AtomicLongFieldUpdater.java @@ -42,6 +42,8 @@ import java.util.function.LongUnaryOperator; import jdk.internal.misc.Unsafe; import jdk.internal.reflect.CallerSensitive; import jdk.internal.reflect.Reflection; +import jdk.internal.vm.annotation.TrustFinalFields; + import java.lang.invoke.VarHandle; /** @@ -368,6 +370,7 @@ public abstract class AtomicLongFieldUpdater { return next; } + @TrustFinalFields private static final class CASUpdater extends AtomicLongFieldUpdater { private static final Unsafe U = Unsafe.getUnsafe(); private final long offset; diff --git a/src/java.base/share/classes/java/util/concurrent/atomic/AtomicReferenceFieldUpdater.java b/src/java.base/share/classes/java/util/concurrent/atomic/AtomicReferenceFieldUpdater.java index 4a758f77a47..3d47e8e323a 100644 --- a/src/java.base/share/classes/java/util/concurrent/atomic/AtomicReferenceFieldUpdater.java +++ b/src/java.base/share/classes/java/util/concurrent/atomic/AtomicReferenceFieldUpdater.java @@ -42,6 +42,8 @@ import java.util.function.UnaryOperator; import jdk.internal.misc.Unsafe; import jdk.internal.reflect.CallerSensitive; import jdk.internal.reflect.Reflection; +import jdk.internal.vm.annotation.TrustFinalFields; + import java.lang.invoke.VarHandle; /** @@ -312,6 +314,7 @@ public abstract class AtomicReferenceFieldUpdater { return next; } + @TrustFinalFields private static final class AtomicReferenceFieldUpdaterImpl extends AtomicReferenceFieldUpdater { private static final Unsafe U = Unsafe.getUnsafe(); diff --git a/src/java.base/share/classes/java/util/zip/GZIPInputStream.java b/src/java.base/share/classes/java/util/zip/GZIPInputStream.java index ab7ea53793f..ebcb9e3204c 100644 --- a/src/java.base/share/classes/java/util/zip/GZIPInputStream.java +++ b/src/java.base/share/classes/java/util/zip/GZIPInputStream.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2026, 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 @@ -79,7 +79,11 @@ public class GZIPInputStream extends InflaterInputStream { super(in, createInflater(in, size), size); usesDefaultInflater = true; try { - readHeader(in); + // we don't expect the stream to be at EOF + // and if it is, then we want readHeader to + // raise an exception, so we pass "true" for + // the "failOnEOF" param. + readHeader(in, true); } catch (IOException ioe) { this.inf.end(); throw ioe; @@ -190,12 +194,40 @@ public class GZIPInputStream extends InflaterInputStream { /* * Reads GZIP member header and returns the total byte number * of this member header. + * If failOnEOF is false and if the given InputStream has already + * reached EOF when this method was invoked, then this method returns + * -1 (indicating that there's no GZIP member header). + * In all other cases of malformed header or EOF being detected + * when reading the header, this method will throw an IOException. */ - private int readHeader(InputStream this_in) throws IOException { + private int readHeader(InputStream this_in, boolean failOnEOF) throws IOException { CheckedInputStream in = new CheckedInputStream(this_in, crc); crc.reset(); + + int magic; + if (!failOnEOF) { + // read an unsigned short value representing the GZIP magic header. + // this is the same as calling readUShort(in), except that here, + // when reading the first byte, we don't raise an EOFException + // if the stream has already reached EOF. + + // read unsigned byte + int b = in.read(); + if (b == -1) { // EOF + crc.reset(); + return -1; // represents no header bytes available + } + checkUnexpectedByte(b); + // read the next unsigned byte to form the unsigned + // short. we throw the usual EOFException/ZipException + // from this point on if there is no more data or + // the data doesn't represent a header. + magic = (readUByte(in) << 8) | b; + } else { + magic = readUShort(in); + } // Check header magic - if (readUShort(in) != GZIP_MAGIC) { + if (magic != GZIP_MAGIC) { throw new ZipException("Not in GZIP format"); } // Check compression method @@ -261,7 +293,11 @@ public class GZIPInputStream extends InflaterInputStream { // try concatenated case int m = 8; // this.trailer try { - m += readHeader(in); // next.header + int numNextHeaderBytes = readHeader(in, false); // next.header (if available) + if (numNextHeaderBytes == -1) { + return true; // end of stream reached + } + m += numNextHeaderBytes; } catch (IOException ze) { return true; // ignore any malformed, do nothing } @@ -295,14 +331,18 @@ public class GZIPInputStream extends InflaterInputStream { if (b == -1) { throw new EOFException(); } - if (b < -1 || b > 255) { - // Report on this.in, not argument in; see read{Header, Trailer}. - throw new IOException(this.in.getClass().getName() - + ".read() returned value out of range -1..255: " + b); - } + checkUnexpectedByte(b); return b; } + private void checkUnexpectedByte(final int b) throws IOException { + if (b < -1 || b > 255) { + // report the InputStream type which returned this unexpected byte + throw new IOException(this.in.getClass().getName() + + ".read() returned value out of range -1..255: " + b); + } + } + private byte[] tmpbuf = new byte[128]; /* diff --git a/src/java.base/share/classes/java/util/zip/ZipFile.java b/src/java.base/share/classes/java/util/zip/ZipFile.java index 7fa507980c2..a198c35c366 100644 --- a/src/java.base/share/classes/java/util/zip/ZipFile.java +++ b/src/java.base/share/classes/java/util/zip/ZipFile.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1995, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1995, 2026, 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,6 +37,7 @@ import java.nio.charset.Charset; import java.nio.file.InvalidPathException; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.Files; +import java.nio.file.attribute.FileTime; import java.util.*; import java.util.function.Consumer; import java.util.function.IntFunction; @@ -86,11 +87,11 @@ public class ZipFile implements ZipConstants, Closeable { private final ZipCoder zipCoder; private volatile boolean closeRequested; - // The "resource" used by this ZIP file that needs to be - // cleaned after use. + // An object holding state which needs to be cleaned after + // this ZipFile is closed or becomes unreachable: // a) the input streams that need to be closed // b) the list of cached Inflater objects - // c) the "native" source of this ZIP file. + // c) the Source object providing read access to the actual ZIP file private final @Stable CleanableResource res; private static final int STORED = ZipEntry.STORED; @@ -1444,11 +1445,13 @@ public class ZipFile implements ZipConstants, Closeable { * The unique combination of these components identifies a Source of a ZipFile. */ private static class Key { - private final BasicFileAttributes attrs; private final File file; + private final Object fileKey; + private final FileTime lastModifiedTime; // the Charset that was provided when constructing the ZipFile instance private final Charset charset; + /** * Constructs a {@code Key} to a {@code Source} of a {@code ZipFile} * @@ -1457,7 +1460,8 @@ public class ZipFile implements ZipConstants, Closeable { * @param charset the Charset that was provided when constructing the ZipFile instance */ public Key(File file, BasicFileAttributes attrs, Charset charset) { - this.attrs = attrs; + this.fileKey = attrs.fileKey(); + this.lastModifiedTime = attrs.lastModifiedTime(); this.file = file; this.charset = charset; } @@ -1465,10 +1469,9 @@ public class ZipFile implements ZipConstants, Closeable { @Override public int hashCode() { long t = charset.hashCode(); - t += attrs.lastModifiedTime().toMillis(); - Object fk = attrs.fileKey(); + t += lastModifiedTime.toMillis(); return Long.hashCode(t) + - (fk != null ? fk.hashCode() : file.hashCode()); + (fileKey != null ? fileKey.hashCode() : file.hashCode()); } @Override @@ -1477,12 +1480,12 @@ public class ZipFile implements ZipConstants, Closeable { if (!charset.equals(key.charset)) { return false; } - if (!attrs.lastModifiedTime().equals(key.attrs.lastModifiedTime())) { + if (!lastModifiedTime.equals(key.lastModifiedTime)) { return false; } - Object fk = attrs.fileKey(); - if (fk != null) { - return fk.equals(key.attrs.fileKey()); + + if (fileKey != null) { + return fileKey.equals(key.fileKey); } else { return file.equals(key.file); } diff --git a/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java b/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java index 86e5f317dc7..b2a224f5917 100644 --- a/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java +++ b/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java @@ -448,19 +448,15 @@ public interface JavaLangAccess { PrintStream initialSystemErr(); /** - * Encodes as many ASCII codepoints as possible from the source - * character array into the destination byte array, assuming that - * the encoding is ASCII compatible. + * Encodes as many ASCII codepoints as possible from the source array into + * the destination byte array, assuming that the encoding is ASCII + * compatible. + *

    + * WARNING: This method does not perform any bound checks. * - * @param sa the source character array - * @param sp the index of the source array to start reading from - * @param da the target byte array - * @param dp the index of the target array to start writing to - * @param len the total number of characters to be encoded - * @return the total number of characters successfully encoded - * @throws NullPointerException if any of the provided arrays is null + * @return the number of bytes successfully encoded, or 0 if none */ - int encodeASCII(char[] sa, int sp, byte[] da, int dp, int len); + int uncheckedEncodeASCII(char[] src, int srcOff, byte[] dst, int dstOff, int len); /** * Set the cause of Throwable @@ -638,10 +634,10 @@ public interface JavaLangAccess { /** * Copy the string bytes to an existing segment, avoiding intermediate copies. */ - void copyToSegmentRaw(String string, MemorySegment segment, long offset); + void copyToSegmentRaw(String string, MemorySegment segment, long offset, int srcIndex, int srcLength); /** * Are the string bytes compatible with the given charset? */ - boolean bytesCompatible(String string, Charset charset); + boolean bytesCompatible(String string, Charset charset, int srcIndex, int numChars); } diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractInstruction.java b/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractInstruction.java index 2197ac81e37..177103917de 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractInstruction.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/AbstractInstruction.java @@ -832,10 +832,8 @@ public abstract sealed class AbstractInstruction final int slot; final int constant; - public UnboundIncrementInstruction(int slot, int constant) { - super(BytecodeHelpers.validateAndIsWideIinc(slot, constant) - ? Opcode.IINC_W - : Opcode.IINC); + public UnboundIncrementInstruction(Opcode op, int slot, int constant) { + super(op); this.slot = slot; this.constant = constant; } diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/BytecodeHelpers.java b/src/java.base/share/classes/jdk/internal/classfile/impl/BytecodeHelpers.java index a51728eb3e9..50c6f8b131c 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/BytecodeHelpers.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/BytecodeHelpers.java @@ -450,6 +450,13 @@ public class BytecodeHelpers { return ret; } + public static void validateIncrement(Opcode opcode, int slot, int constant) { + if (validateAndIsWideIinc(slot, constant) && opcode != Opcode.IINC_W) { + throw new IllegalArgumentException( + "IINC: operands require wide encoding for %s".formatted(opcode)); + } + } + public static void validateRet(Opcode opcode, int slot) { if (opcode == Opcode.RET && (slot & ~0xFF) == 0 || opcode == Opcode.RET_W && (slot & ~0xFFFF) == 0) diff --git a/src/java.base/share/classes/jdk/internal/foreign/AbstractMemorySegmentImpl.java b/src/java.base/share/classes/jdk/internal/foreign/AbstractMemorySegmentImpl.java index a0c8a0a5a4f..f75d67adbbb 100644 --- a/src/java.base/share/classes/jdk/internal/foreign/AbstractMemorySegmentImpl.java +++ b/src/java.base/share/classes/jdk/internal/foreign/AbstractMemorySegmentImpl.java @@ -551,6 +551,13 @@ public abstract sealed class AbstractMemorySegmentImpl unsafeGetOffset() == that.unsafeGetOffset(); } + @Override + public String getString(long offset, Charset charset, long byteLength) { + Utils.checkNonNegativeArgument(byteLength, "byteLength"); + Objects.requireNonNull(charset); + return StringSupport.read(this, offset, charset, byteLength); + } + @Override public int hashCode() { return Objects.hash( @@ -702,6 +709,16 @@ public abstract sealed class AbstractMemorySegmentImpl } } + @ForceInline + public static long copy(String src, Charset dstEncoding, int srcIndex, MemorySegment dst, long dstOffset, int numChars) { + Objects.requireNonNull(src); + Objects.requireNonNull(dstEncoding); + Objects.requireNonNull(dst); + + AbstractMemorySegmentImpl destImpl = (AbstractMemorySegmentImpl)dst; + return StringSupport.copyBytes(src, destImpl, dstEncoding, dstOffset, srcIndex, numChars); + } + // accessors @ForceInline diff --git a/src/java.base/share/classes/jdk/internal/foreign/StringSupport.java b/src/java.base/share/classes/jdk/internal/foreign/StringSupport.java index 208c6d54aab..b9f528969f0 100644 --- a/src/java.base/share/classes/jdk/internal/foreign/StringSupport.java +++ b/src/java.base/share/classes/jdk/internal/foreign/StringSupport.java @@ -30,11 +30,14 @@ import jdk.internal.access.SharedSecrets; import jdk.internal.misc.ScopedMemoryAccess; import jdk.internal.util.Architecture; import jdk.internal.util.ArraysSupport; +import jdk.internal.util.Preconditions; import jdk.internal.vm.annotation.ForceInline; import java.lang.foreign.MemorySegment; +import java.lang.reflect.Array; import java.nio.charset.CharacterCodingException; import java.nio.charset.Charset; +import java.util.Objects; import static java.lang.foreign.ValueLayout.*; @@ -58,6 +61,27 @@ public final class StringSupport { }; } + @ForceInline + public static String read(AbstractMemorySegmentImpl segment, long offset, Charset charset, long length) { + return readBytes(segment, offset, charset, length); + } + + @ForceInline + public static String readBytes(AbstractMemorySegmentImpl segment, long offset, Charset charset, long length) { + if (length > Integer.MAX_VALUE) { + throw new IllegalArgumentException("Required length exceeds implementation limit"); + } + final int lengthBytes = (int) length; + final byte[] bytes = new byte[lengthBytes]; + MemorySegment.copy(segment, JAVA_BYTE, offset, bytes, 0, lengthBytes); + try { + return JAVA_LANG_ACCESS.uncheckedNewStringOrThrow(bytes, charset); + } catch (CharacterCodingException _) { + // use replacement characters for malformed input + return new String(bytes, charset); + } + } + @ForceInline public static void write(AbstractMemorySegmentImpl segment, long offset, Charset charset, String string) { switch (CharsetKind.of(charset)) { @@ -70,14 +94,7 @@ public final class StringSupport { @ForceInline private static String readByte(AbstractMemorySegmentImpl segment, long offset, Charset charset) { final int len = strlenByte(segment, offset, segment.byteSize()); - final byte[] bytes = new byte[len]; - MemorySegment.copy(segment, JAVA_BYTE, offset, bytes, 0, len); - try { - return JAVA_LANG_ACCESS.uncheckedNewStringOrThrow(bytes, charset); - } catch (CharacterCodingException _) { - // use replacement characters for malformed input - return new String(bytes, charset); - } + return readBytes(segment, offset, charset, len); } @ForceInline @@ -89,14 +106,7 @@ public final class StringSupport { @ForceInline private static String readShort(AbstractMemorySegmentImpl segment, long offset, Charset charset) { int len = strlenShort(segment, offset, segment.byteSize()); - byte[] bytes = new byte[len]; - MemorySegment.copy(segment, JAVA_BYTE, offset, bytes, 0, len); - try { - return JAVA_LANG_ACCESS.uncheckedNewStringOrThrow(bytes, charset); - } catch (CharacterCodingException _) { - // use replacement characters for malformed input - return new String(bytes, charset); - } + return readBytes(segment, offset, charset, len); } @ForceInline @@ -108,14 +118,7 @@ public final class StringSupport { @ForceInline private static String readInt(AbstractMemorySegmentImpl segment, long offset, Charset charset) { int len = strlenInt(segment, offset, segment.byteSize()); - byte[] bytes = new byte[len]; - MemorySegment.copy(segment, JAVA_BYTE, offset, bytes, 0, len); - try { - return JAVA_LANG_ACCESS.uncheckedNewStringOrThrow(bytes, charset); - } catch (CharacterCodingException _) { - // use replacement characters for malformed input - return new String(bytes, charset); - } + return readBytes(segment, offset, charset, len); } @ForceInline @@ -345,22 +348,26 @@ public final class StringSupport { } } - public static boolean bytesCompatible(String string, Charset charset) { - return JAVA_LANG_ACCESS.bytesCompatible(string, charset); + public static boolean bytesCompatible(String string, Charset charset, int srcIndex, int numChars) { + return JAVA_LANG_ACCESS.bytesCompatible(string, charset, srcIndex, numChars); } public static int copyBytes(String string, MemorySegment segment, Charset charset, long offset) { - if (bytesCompatible(string, charset)) { - copyToSegmentRaw(string, segment, offset); - return string.length(); + return copyBytes(string, segment, charset, offset, 0, string.length()); + } + + public static int copyBytes(String string, MemorySegment segment, Charset charset, long offset, int srcIndex, int numChars) { + if (bytesCompatible(string, charset, srcIndex, numChars)) { + copyToSegmentRaw(string, segment, offset, srcIndex, numChars); + return numChars; } else { - byte[] bytes = string.getBytes(charset); + byte[] bytes = string.substring(srcIndex, srcIndex + numChars).getBytes(charset); MemorySegment.copy(bytes, 0, segment, JAVA_BYTE, offset, bytes.length); return bytes.length; } } - public static void copyToSegmentRaw(String string, MemorySegment segment, long offset) { - JAVA_LANG_ACCESS.copyToSegmentRaw(string, segment, offset); + public static void copyToSegmentRaw(String string, MemorySegment segment, long offset, int srcIndex, int srcLength) { + JAVA_LANG_ACCESS.copyToSegmentRaw(string, segment, offset, srcIndex, srcLength); } } diff --git a/src/java.base/share/classes/jdk/internal/jimage/BasicImageReader.java b/src/java.base/share/classes/jdk/internal/jimage/BasicImageReader.java index 6807928b2c2..10aa0397f0c 100644 --- a/src/java.base/share/classes/jdk/internal/jimage/BasicImageReader.java +++ b/src/java.base/share/classes/jdk/internal/jimage/BasicImageReader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2026, 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 @@ -220,14 +220,6 @@ public class BasicImageReader implements AutoCloseable { return slice(buffer, offset, size).order(byteOrder).asIntBuffer(); } - public static void releaseByteBuffer(ByteBuffer buffer) { - Objects.requireNonNull(buffer); - - if (!MAP_ALL) { - ImageBufferCache.releaseBuffer(buffer); - } - } - public String getName() { return name; } @@ -362,13 +354,15 @@ public class BasicImageReader implements AutoCloseable { if (offset < 0 || Integer.MAX_VALUE <= offset) { throw new IndexOutOfBoundsException("Bad offset: " + offset); } + int checkedOffset = (int) offset; if (size < 0 || Integer.MAX_VALUE <= size) { - throw new IndexOutOfBoundsException("Bad size: " + size); + throw new IllegalArgumentException("Bad size: " + size); } + int checkedSize = (int) size; if (MAP_ALL) { - ByteBuffer buffer = slice(memoryMap, (int)offset, (int)size); + ByteBuffer buffer = slice(memoryMap, checkedOffset, checkedSize); buffer.order(ByteOrder.BIG_ENDIAN); return buffer; @@ -376,21 +370,18 @@ public class BasicImageReader implements AutoCloseable { if (channel == null) { throw new InternalError("Image file channel not open"); } - - ByteBuffer buffer = ImageBufferCache.getBuffer(size); + ByteBuffer buffer = ByteBuffer.allocate(checkedSize); int read; try { - read = channel.read(buffer, offset); + read = channel.read(buffer, checkedOffset); buffer.rewind(); } catch (IOException ex) { - ImageBufferCache.releaseBuffer(buffer); throw new RuntimeException(ex); } - if (read != size) { - ImageBufferCache.releaseBuffer(buffer); + if (read != checkedSize) { throw new RuntimeException("Short read: " + read + - " instead of " + size + " bytes"); + " instead of " + checkedSize + " bytes"); } return buffer; @@ -406,17 +397,12 @@ public class BasicImageReader implements AutoCloseable { public byte[] getResource(ImageLocation loc) { ByteBuffer buffer = getResourceBuffer(loc); - - if (buffer != null) { - byte[] bytes = getBufferBytes(buffer); - ImageBufferCache.releaseBuffer(buffer); - - return bytes; - } - - return null; + return buffer != null ? getBufferBytes(buffer) : null; } + /** + * Returns the content of jimage location in a newly allocated byte buffer. + */ public ByteBuffer getResourceBuffer(ImageLocation loc) { Objects.requireNonNull(loc); long offset = loc.getContentOffset() + indexSize; @@ -437,10 +423,8 @@ public class BasicImageReader implements AutoCloseable { return readBuffer(offset, uncompressedSize); } else { ByteBuffer buffer = readBuffer(offset, compressedSize); - if (buffer != null) { byte[] bytesIn = getBufferBytes(buffer); - ImageBufferCache.releaseBuffer(buffer); byte[] bytesOut; try { diff --git a/src/java.base/share/classes/jdk/internal/jimage/ImageBufferCache.java b/src/java.base/share/classes/jdk/internal/jimage/ImageBufferCache.java deleted file mode 100644 index c2cc4a028be..00000000000 --- a/src/java.base/share/classes/jdk/internal/jimage/ImageBufferCache.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright (c) 2014, 2016, 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. Oracle designates this - * particular file as subject to the "Classpath" exception as provided - * by Oracle in the LICENSE file that accompanied this code. - * - * 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. - */ -package jdk.internal.jimage; - -import java.lang.ref.WeakReference; -import java.nio.ByteBuffer; -import java.util.AbstractMap; -import java.util.Arrays; -import java.util.Comparator; -import java.util.Map; - -/** - * @implNote This class needs to maintain JDK 8 source compatibility. - * - * It is used internally in the JDK to implement jimage/jrtfs access, - * but also compiled and delivered as part of the jrt-fs.jar to support access - * to the jimage file provided by the shipped JDK by tools running on JDK 8. - */ -class ImageBufferCache { - private static final int MAX_CACHED_BUFFERS = 3; - private static final int LARGE_BUFFER = 0x10000; - - /* - * We used to have a class BufferReference extending from WeakReference. - * BufferReference class had an instance field called "capacity". This field was - * used to make DECREASING_CAPACITY_NULLS_LAST comparator stable in the presence - * of GC clearing the WeakReference concurrently. - * - * But this scheme results in metaspace leak. The thread local is alive till the - * the thread is alive. And so ImageBufferCache$BufferReference class was kept alive. - * Because this class and ImageBufferCache$BufferReference are all loaded by a URL - * class loader from jrt-fs.jar, the class loader and so all the classes loaded by it - * were alive! - * - * Solution is to avoid using a URL loader loaded class type with thread local. All we - * need is a pair of WeakReference, Integer (saved capacity for stability - * of comparator). We use Map.Entry as pair implementation. With this, all types used - * with thread local are bootstrap types and so no metaspace leak. - */ - @SuppressWarnings("unchecked") - private static final ThreadLocal, Integer>[]> CACHE = - new ThreadLocal, Integer>[]>() { - @Override - protected Map.Entry, Integer>[] initialValue() { - // 1 extra slot to simplify logic of releaseBuffer() - return (Map.Entry, Integer>[])new Map.Entry[MAX_CACHED_BUFFERS + 1]; - } - }; - - private static ByteBuffer allocateBuffer(long size) { - return ByteBuffer.allocateDirect((int)((size + 0xFFF) & ~0xFFF)); - } - - static ByteBuffer getBuffer(long size) { - if (size < 0 || Integer.MAX_VALUE < size) { - throw new IndexOutOfBoundsException("size"); - } - - ByteBuffer result = null; - - if (size > LARGE_BUFFER) { - result = allocateBuffer(size); - } else { - Map.Entry, Integer>[] cache = CACHE.get(); - - // buffers are ordered by decreasing capacity - // cache[MAX_CACHED_BUFFERS] is always null - for (int i = MAX_CACHED_BUFFERS - 1; i >= 0; i--) { - Map.Entry, Integer> reference = cache[i]; - - if (reference != null) { - ByteBuffer buffer = getByteBuffer(reference); - - if (buffer != null && size <= buffer.capacity()) { - cache[i] = null; - result = buffer; - result.rewind(); - break; - } - } - } - - if (result == null) { - result = allocateBuffer(size); - } - } - - result.limit((int)size); - - return result; - } - - static void releaseBuffer(ByteBuffer buffer) { - if (buffer.capacity() > LARGE_BUFFER) { - return; - } - - Map.Entry, Integer>[] cache = CACHE.get(); - - // expunge cleared BufferRef(s) - for (int i = 0; i < MAX_CACHED_BUFFERS; i++) { - Map.Entry, Integer> reference = cache[i]; - if (reference != null && getByteBuffer(reference) == null) { - cache[i] = null; - } - } - - // insert buffer back with new BufferRef wrapping it - cache[MAX_CACHED_BUFFERS] = newCacheEntry(buffer); - Arrays.sort(cache, DECREASING_CAPACITY_NULLS_LAST); - // squeeze the smallest one out - cache[MAX_CACHED_BUFFERS] = null; - } - - private static Map.Entry, Integer> newCacheEntry(ByteBuffer bb) { - return new AbstractMap.SimpleEntry, Integer>( - new WeakReference(bb), bb.capacity()); - } - - private static int getCapacity(Map.Entry, Integer> e) { - return e == null? 0 : e.getValue(); - } - - private static ByteBuffer getByteBuffer(Map.Entry, Integer> e) { - return e == null? null : e.getKey().get(); - } - - private static Comparator, Integer>> DECREASING_CAPACITY_NULLS_LAST = - new Comparator, Integer>>() { - @Override - public int compare(Map.Entry, Integer> br1, - Map.Entry, Integer> br2) { - return Integer.compare(getCapacity(br1), getCapacity(br2)); - } - }; -} diff --git a/src/java.base/share/classes/jdk/internal/jimage/ImageReader.java b/src/java.base/share/classes/jdk/internal/jimage/ImageReader.java index e062e1629ff..c36e265ee2f 100644 --- a/src/java.base/share/classes/jdk/internal/jimage/ImageReader.java +++ b/src/java.base/share/classes/jdk/internal/jimage/ImageReader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2026, 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 @@ -190,20 +190,7 @@ public final class ImageReader implements AutoCloseable { } /** - * Releases a (possibly cached) {@link ByteBuffer} obtained via - * {@link #getResourceBuffer(Node)}. - * - *

    Note that no testing is performed to check whether the buffer about - * to be released actually came from a call to {@code getResourceBuffer()}. - */ - public static void releaseByteBuffer(ByteBuffer buffer) { - BasicImageReader.releaseByteBuffer(buffer); - } - - /** - * Returns the content of a resource node in a possibly cached byte buffer. - * Callers of this method must call {@link #releaseByteBuffer(ByteBuffer)} - * when they are finished with it. + * Returns the content of a resource node in a newly allocated byte buffer. */ public ByteBuffer getResourceBuffer(Node node) { requireOpen(); diff --git a/src/java.base/share/classes/jdk/internal/module/SystemModuleFinders.java b/src/java.base/share/classes/jdk/internal/module/SystemModuleFinders.java index 370c151af84..afebb89168c 100644 --- a/src/java.base/share/classes/jdk/internal/module/SystemModuleFinders.java +++ b/src/java.base/share/classes/jdk/internal/module/SystemModuleFinders.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, 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 @@ -467,12 +467,6 @@ public final class SystemModuleFinders { .map(reader::getResourceBuffer); } - @Override - public void release(ByteBuffer bb) { - Objects.requireNonNull(bb); - ImageReader.releaseByteBuffer(bb); - } - @Override public Stream list() throws IOException { if (closed) diff --git a/src/java.base/share/classes/jdk/internal/util/ByteArray.java b/src/java.base/share/classes/jdk/internal/util/ByteArray.java index 94395df2c34..92b9dbbef19 100644 --- a/src/java.base/share/classes/jdk/internal/util/ByteArray.java +++ b/src/java.base/share/classes/jdk/internal/util/ByteArray.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2026, 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 @@ -241,7 +241,7 @@ public final class ByteArray { * @param offset where setting (writing) in the array should begin * @param value value to set in the array * @throws IndexOutOfBoundsException if the provided {@code offset} is outside - * the range [0, array.length] + * the range [0, array.length - 1] * @see #getBoolean(byte[], int) */ public static void setBoolean(byte[] array, int offset, boolean value) { @@ -328,7 +328,7 @@ public final class ByteArray { * @param offset where setting (writing) in the array should begin * @param value value to set in the array * @throws IndexOutOfBoundsException if the provided {@code offset} is outside - * the range [0, array.length - 2] + * the range [0, array.length - 4] * @see #getFloat(byte[], int) */ public static void setFloat(byte[] array, int offset, float value) { @@ -350,7 +350,7 @@ public final class ByteArray { * @param offset where setting (writing) in the array should begin * @param value value to set in the array * @throws IndexOutOfBoundsException if the provided {@code offset} is outside - * the range [0, array.length - 2] + * the range [0, array.length - 4] * @see #getFloatRaw(byte[], int) */ public static void setFloatRaw(byte[] array, int offset, float value) { @@ -368,7 +368,7 @@ public final class ByteArray { * @param offset where setting (writing) in the array should begin * @param value value to set in the array * @throws IndexOutOfBoundsException if the provided {@code offset} is outside - * the range [0, array.length - 4] + * the range [0, array.length - 8] * @see #getLong(byte[], int) */ public static void setLong(byte[] array, int offset, long value) { @@ -387,7 +387,7 @@ public final class ByteArray { * @param offset where setting (writing) in the array should begin * @param value value to set in the array * @throws IndexOutOfBoundsException if the provided {@code offset} is outside - * the range [0, array.length - 2] + * the range [0, array.length - 8] * @see #getDouble(byte[], int) */ public static void setDouble(byte[] array, int offset, double value) { @@ -409,7 +409,7 @@ public final class ByteArray { * @param offset where setting (writing) in the array should begin * @param value value to set in the array * @throws IndexOutOfBoundsException if the provided {@code offset} is outside - * the range [0, array.length - 2] + * the range [0, array.length - 8] * @see #getDoubleRaw(byte[], int) */ public static void setDoubleRaw(byte[] array, int offset, double value) { diff --git a/src/java.base/share/classes/jdk/internal/util/ByteArrayLittleEndian.java b/src/java.base/share/classes/jdk/internal/util/ByteArrayLittleEndian.java index fcd7ca2b9bf..cc50686429b 100644 --- a/src/java.base/share/classes/jdk/internal/util/ByteArrayLittleEndian.java +++ b/src/java.base/share/classes/jdk/internal/util/ByteArrayLittleEndian.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2026, 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 @@ -241,7 +241,7 @@ public final class ByteArrayLittleEndian { * @param offset where setting (writing) in the array should begin * @param value value to set in the array * @throws IndexOutOfBoundsException if the provided {@code offset} is outside - * the range [0, array.length] + * the range [0, array.length - 1] * @see #getBoolean(byte[], int) */ public static void setBoolean(byte[] array, int offset, boolean value) { @@ -328,7 +328,7 @@ public final class ByteArrayLittleEndian { * @param offset where setting (writing) in the array should begin * @param value value to set in the array * @throws IndexOutOfBoundsException if the provided {@code offset} is outside - * the range [0, array.length - 2] + * the range [0, array.length - 4] * @see #getFloat(byte[], int) */ public static void setFloat(byte[] array, int offset, float value) { @@ -350,7 +350,7 @@ public final class ByteArrayLittleEndian { * @param offset where setting (writing) in the array should begin * @param value value to set in the array * @throws IndexOutOfBoundsException if the provided {@code offset} is outside - * the range [0, array.length - 2] + * the range [0, array.length - 4] * @see #getFloatRaw(byte[], int) */ public static void setFloatRaw(byte[] array, int offset, float value) { @@ -368,7 +368,7 @@ public final class ByteArrayLittleEndian { * @param offset where setting (writing) in the array should begin * @param value value to set in the array * @throws IndexOutOfBoundsException if the provided {@code offset} is outside - * the range [0, array.length - 4] + * the range [0, array.length - 8] * @see #getLong(byte[], int) */ public static void setLong(byte[] array, int offset, long value) { @@ -387,7 +387,7 @@ public final class ByteArrayLittleEndian { * @param offset where setting (writing) in the array should begin * @param value value to set in the array * @throws IndexOutOfBoundsException if the provided {@code offset} is outside - * the range [0, array.length - 2] + * the range [0, array.length - 8] * @see #getDouble(byte[], int) */ public static void setDouble(byte[] array, int offset, double value) { @@ -409,7 +409,7 @@ public final class ByteArrayLittleEndian { * @param offset where setting (writing) in the array should begin * @param value value to set in the array * @throws IndexOutOfBoundsException if the provided {@code offset} is outside - * the range [0, array.length - 2] + * the range [0, array.length - 8] * @see #getDoubleRaw(byte[], int) */ public static void setDoubleRaw(byte[] array, int offset, double value) { diff --git a/src/java.base/share/classes/jdk/internal/vm/annotation/TrustFinalFields.java b/src/java.base/share/classes/jdk/internal/vm/annotation/TrustFinalFields.java new file mode 100644 index 00000000000..a94f58159a2 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/vm/annotation/TrustFinalFields.java @@ -0,0 +1,57 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package jdk.internal.vm.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/// Indicates all instance final fields declared in the annotated class should +/// be trusted as constants by compilers in `ciField::is_constant`. +/// +/// The compiler already treats static final fields and instance final fields in +/// record classes and hidden classes as constant. All classes in select +/// packages (Defined in `trust_final_non_static_fields` in `ciField.cpp`) in +/// the boot class loader also have their instance final fields trusted. This +/// annotation is not necessary in these cases. +/// +/// The [Stable] annotation treats fields as constants once they are not the +/// zero or null value. In comparison, a non-stable final instance field +/// trusted by this annotation can treat zero and null values as constants. +/// +/// This annotation is suitable when constant treatment of final fields is +/// performance sensitive, yet package-wide final field constant treatment may +/// be at risk from final field modifications such as serialization. +/// +/// This annotation is only recognized on classes from the boot and platform +/// class loaders and is ignored elsewhere. +/// +/// @since 26 +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface TrustFinalFields { +} diff --git a/src/java.base/share/classes/module-info.java b/src/java.base/share/classes/module-info.java index 70a79390828..d20f6311bca 100644 --- a/src/java.base/share/classes/module-info.java +++ b/src/java.base/share/classes/module-info.java @@ -274,7 +274,8 @@ module java.base { jdk.httpserver, jdk.jlink, jdk.jpackage, - jdk.net; + jdk.net, + jdk.security.auth; exports sun.net to java.net.http, jdk.naming.dns; diff --git a/src/java.base/share/classes/sun/nio/cs/CESU_8.java b/src/java.base/share/classes/sun/nio/cs/CESU_8.java index 409b375ec88..11f21f56139 100644 --- a/src/java.base/share/classes/sun/nio/cs/CESU_8.java +++ b/src/java.base/share/classes/sun/nio/cs/CESU_8.java @@ -445,7 +445,7 @@ class CESU_8 extends Unicode int dl = dst.arrayOffset() + dst.limit(); // Handle ASCII-only prefix - int n = JLA.encodeASCII(sa, sp, da, dp, Math.min(sl - sp, dl - dp)); + int n = JLA.uncheckedEncodeASCII(sa, sp, da, dp, Math.min(sl - sp, dl - dp)); sp += n; dp += n; diff --git a/src/java.base/share/classes/sun/nio/cs/DoubleByte.java b/src/java.base/share/classes/sun/nio/cs/DoubleByte.java index 0969669a35b..0aaae14bbf9 100644 --- a/src/java.base/share/classes/sun/nio/cs/DoubleByte.java +++ b/src/java.base/share/classes/sun/nio/cs/DoubleByte.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2009, 2026, 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 @@ -583,6 +583,16 @@ public class DoubleByte { return encodeChar(c) != UNMAPPABLE_ENCODING; } + public boolean canEncode(CharSequence cs) { + int length = cs.length(); + for (int i = 0; i < length; i++) { + if (!canEncode(cs.charAt(i))) { + return false; + } + } + return true; + } + protected Surrogate.Parser sgp() { if (sgp == null) sgp = new Surrogate.Parser(); @@ -600,7 +610,7 @@ public class DoubleByte { try { if (isASCIICompatible) { - int n = JLA.encodeASCII(sa, sp, da, dp, Math.min(dl - dp, sl - sp)); + int n = JLA.uncheckedEncodeASCII(sa, sp, da, dp, Math.min(dl - dp, sl - sp)); sp += n; dp += n; } diff --git a/src/java.base/share/classes/sun/nio/cs/ISO_8859_1.java b/src/java.base/share/classes/sun/nio/cs/ISO_8859_1.java index ff5970e92a8..9240ac3f380 100644 --- a/src/java.base/share/classes/sun/nio/cs/ISO_8859_1.java +++ b/src/java.base/share/classes/sun/nio/cs/ISO_8859_1.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, 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 @@ -35,6 +35,7 @@ import java.util.Objects; import jdk.internal.access.JavaLangAccess; import jdk.internal.access.SharedSecrets; +import jdk.internal.util.Preconditions; import jdk.internal.vm.annotation.IntrinsicCandidate; public class ISO_8859_1 @@ -135,40 +136,36 @@ public class ISO_8859_1 return c <= '\u00FF'; } + public boolean canEncode(CharSequence cs) { + int length = cs.length(); + for (int i = 0; i < length; i++) { + if (!canEncode(cs.charAt(i))) { + return false; + } + } + return true; + } + public boolean isLegalReplacement(byte[] repl) { return true; // we accept any byte value } private final Surrogate.Parser sgp = new Surrogate.Parser(); - /** - * Encodes as many ISO-8859-1 codepoints as possible from the source - * character array into the destination byte array, assuming that - * the encoding is ISO-8859-1 compatible. - * - * @param sa the source character array - * @param sp the index of the source array to start reading from - * @param da the target byte array - * @param dp the index of the target array to start writing to - * @param len the maximum number of characters to be encoded - * @return the total number of characters successfully encoded - * @throws NullPointerException if any of the provided arrays is null - */ + // Method possible replaced with a compiler intrinsic. private static int encodeISOArray(char[] sa, int sp, byte[] da, int dp, int len) { - // This method should tolerate invalid arguments, matching the lenient behavior of the VM intrinsic. - // Hence, using operator expressions instead of `Preconditions`, which throw on failure. - if ((sp | dp | len) < 0 || - sp >= sa.length || // Implicit null check on `sa` - dp >= da.length) { // Implicit null check on `da` + if (len <= 0) { return 0; } - int minLen = Math.min(len, Math.min(sa.length - sp, da.length - dp)); - return encodeISOArray0(sa, sp, da, dp, minLen); + encodeISOArrayCheck(sa, sp, da, dp, len); + return implEncodeISOArray(sa, sp, da, dp, len); } @IntrinsicCandidate - private static int encodeISOArray0(char[] sa, int sp, byte[] da, int dp, int len) { + private static int implEncodeISOArray(char[] sa, int sp, + byte[] da, int dp, int len) + { int i = 0; for (; i < len; i++) { char c = sa[sp++]; @@ -179,6 +176,17 @@ public class ISO_8859_1 return i; } + private static void encodeISOArrayCheck(char[] sa, int sp, + byte[] da, int dp, int len) { + Objects.requireNonNull(sa); + Objects.requireNonNull(da); + Preconditions.checkIndex(sp, sa.length, Preconditions.AIOOBE_FORMATTER); + Preconditions.checkIndex(dp, da.length, Preconditions.AIOOBE_FORMATTER); + + Preconditions.checkIndex(sp + len - 1, sa.length, Preconditions.AIOOBE_FORMATTER); + Preconditions.checkIndex(dp + len - 1, da.length, Preconditions.AIOOBE_FORMATTER); + } + private CoderResult encodeArrayLoop(CharBuffer src, ByteBuffer dst) { diff --git a/src/java.base/share/classes/sun/nio/cs/SingleByte.java b/src/java.base/share/classes/sun/nio/cs/SingleByte.java index a5bf06cb251..d4127b7c043 100644 --- a/src/java.base/share/classes/sun/nio/cs/SingleByte.java +++ b/src/java.base/share/classes/sun/nio/cs/SingleByte.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2026, 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 @@ -201,6 +201,16 @@ public class SingleByte return encode(c) != UNMAPPABLE_ENCODING; } + public boolean canEncode(CharSequence cs) { + int length = cs.length(); + for (int i = 0; i < length; i++) { + if (!canEncode(cs.charAt(i))) { + return false; + } + } + return true; + } + public boolean isLegalReplacement(byte[] repl) { return ((repl.length == 1 && repl[0] == (byte)'?') || super.isLegalReplacement(repl)); @@ -217,7 +227,7 @@ public class SingleByte int len = Math.min(dl - dp, sl - sp); if (isASCIICompatible) { - int n = JLA.encodeASCII(sa, sp, da, dp, len); + int n = JLA.uncheckedEncodeASCII(sa, sp, da, dp, len); sp += n; dp += n; len -= n; diff --git a/src/java.base/share/classes/sun/nio/cs/US_ASCII.java b/src/java.base/share/classes/sun/nio/cs/US_ASCII.java index 3886978d209..61c4948e949 100644 --- a/src/java.base/share/classes/sun/nio/cs/US_ASCII.java +++ b/src/java.base/share/classes/sun/nio/cs/US_ASCII.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, 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 @@ -139,6 +139,16 @@ public class US_ASCII return c < 0x80; } + public boolean canEncode(CharSequence cs) { + int length = cs.length(); + for (int i = 0; i < length; i++) { + if (!canEncode(cs.charAt(i))) { + return false; + } + } + return true; + } + public boolean isLegalReplacement(byte[] repl) { return (repl.length == 1 && repl[0] >= 0) || super.isLegalReplacement(repl); @@ -159,7 +169,7 @@ public class US_ASCII assert (dp <= dl); dp = (dp <= dl ? dp : dl); - int n = JLA.encodeASCII(sa, sp, da, dp, Math.min(sl - sp, dl - dp)); + int n = JLA.uncheckedEncodeASCII(sa, sp, da, dp, Math.min(sl - sp, dl - dp)); sp += n; dp += n; diff --git a/src/java.base/share/classes/sun/nio/cs/UTF_8.java b/src/java.base/share/classes/sun/nio/cs/UTF_8.java index d2d6d7e485d..54e479f838a 100644 --- a/src/java.base/share/classes/sun/nio/cs/UTF_8.java +++ b/src/java.base/share/classes/sun/nio/cs/UTF_8.java @@ -452,7 +452,7 @@ public final class UTF_8 extends Unicode { int dl = dst.arrayOffset() + dst.limit(); // Handle ASCII-only prefix - int n = JLA.encodeASCII(sa, sp, da, dp, Math.min(sl - sp, dl - dp)); + int n = JLA.uncheckedEncodeASCII(sa, sp, da, dp, Math.min(sl - sp, dl - dp)); sp += n; dp += n; diff --git a/src/java.base/share/classes/sun/security/ec/ECDSAOperations.java b/src/java.base/share/classes/sun/security/ec/ECDSAOperations.java index f58d7d8f2d7..7badcf42d9c 100644 --- a/src/java.base/share/classes/sun/security/ec/ECDSAOperations.java +++ b/src/java.base/share/classes/sun/security/ec/ECDSAOperations.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 @@ -68,7 +68,7 @@ public class ECDSAOperations { public ECDSAOperations(ECOperations ecOps, ECPoint basePoint) { this.ecOps = ecOps; - this.basePoint = toAffinePoint(basePoint, ecOps.getField()); + this.basePoint = AffinePoint.fromECPoint(basePoint, ecOps.getField()); } public ECOperations getEcOperations() { @@ -79,14 +79,6 @@ public class ECDSAOperations { return ecOps.multiply(basePoint, scalar).asAffine(); } - public static AffinePoint toAffinePoint(ECPoint point, - IntegerFieldModuloP field) { - - ImmutableIntegerModuloP affineX = field.getElement(point.getAffineX()); - ImmutableIntegerModuloP affineY = field.getElement(point.getAffineY()); - return new AffinePoint(affineX, affineY); - } - public static Optional forParameters(ECParameterSpec ecParams) { Optional curveOps = diff --git a/src/java.base/share/classes/sun/security/provider/certpath/URICertStore.java b/src/java.base/share/classes/sun/security/provider/certpath/URICertStore.java index 28729a56dbd..3e1fc8db164 100644 --- a/src/java.base/share/classes/sun/security/provider/certpath/URICertStore.java +++ b/src/java.base/share/classes/sun/security/provider/certpath/URICertStore.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2006, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2006, 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 @@ -29,6 +29,7 @@ import java.io.InputStream; import java.io.IOException; import java.net.HttpURLConnection; import java.net.URI; +import java.net.URISyntaxException; import java.net.URLConnection; import java.security.InvalidAlgorithmParameterException; import java.security.NoSuchAlgorithmException; @@ -48,8 +49,11 @@ import java.security.cert.X509CRL; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.LinkedHashSet; import java.util.List; import java.util.Locale; +import java.util.Optional; +import java.util.Set; import sun.security.x509.AccessDescription; import sun.security.x509.GeneralNameInterface; @@ -58,6 +62,8 @@ import sun.security.util.Cache; import sun.security.util.Debug; import sun.security.util.SecurityProperties; +import javax.security.auth.x500.X500Principal; + /** * A CertStore that retrieves Certificates or * CRLs from a URI, for example, as specified in an X.509 @@ -182,6 +188,166 @@ class URICertStore extends CertStoreSpi { return timeoutVal; } + /** + * Enumeration for the allowed schemes we support when following a + * URI from an authorityInfoAccess extension on a certificate. + */ + private enum AllowedScheme { + HTTP(HttpFtpRuleMatcher.HTTP), + HTTPS(HttpFtpRuleMatcher.HTTPS), + LDAP(LdapRuleMatcher.LDAP), + LDAPS(LdapRuleMatcher.LDAPS), + FTP(HttpFtpRuleMatcher.FTP); + + final URIRuleMatcher ruleMatcher; + + AllowedScheme(URIRuleMatcher matcher) { + ruleMatcher = matcher; + } + + /** + * Return an {@code AllowedScheme} based on a case-insensitive match + * @param name the scheme name to be matched + * @return the {@code AllowedScheme} that corresponds to the + * {@code name} provided, or null if there is no match. + */ + static AllowedScheme nameOf(String name) { + if (name == null) { + return null; + } + + try { + return AllowedScheme.valueOf(name.toUpperCase(Locale.ROOT)); + } catch (IllegalArgumentException _) { + return null; + } + } + } + + private static Set CA_ISS_URI_FILTERS = null; + private static final boolean CA_ISS_ALLOW_ANY; + + static { + boolean allowAny = false; + try { + if (Builder.USE_AIA) { + CA_ISS_URI_FILTERS = new LinkedHashSet<>(); + String aiaPropVal = Optional.ofNullable( + SecurityProperties.getOverridableProperty( + "com.sun.security.allowedAIALocations")). + map(String::trim).orElse(""); + if (aiaPropVal.equalsIgnoreCase("any")) { + allowAny = true; + if (debug != null) { + debug.println("allowedAIALocations: Warning: " + + "Allow-All URI filtering enabled!"); + } + } else { + // Load all the valid rules from the Security property + if (!aiaPropVal.isEmpty()) { + String[] aiaUriStrs = aiaPropVal.trim().split("\\s+"); + addCaIssUriFilters(aiaUriStrs); + } + + if (CA_ISS_URI_FILTERS.isEmpty()) { + if (debug != null) { + debug.println("allowedAIALocations: Warning: " + + "No valid filters found. Deny-all URI " + + "filtering is active."); + } + } + } + } + } finally { + CA_ISS_ALLOW_ANY = allowAny; + } + } + + /** + * Populate the filter collection from the list of AIA CA issuer URIs + * found in the {@code com.sun.security.allowedAIALocations} security + * or system property. + * + * @param aiaUriStrs array containing String URI filters + */ + private static void addCaIssUriFilters(String[] aiaUriStrs) { + for (String aiaStr : aiaUriStrs) { + if (aiaStr != null && !aiaStr.isEmpty()) { + try { + AllowedScheme scheme; + URI aiaUri = new URI(aiaStr).normalize(); + // It must be absolute and non-opaque + if (!aiaUri.isAbsolute() || aiaUri.isOpaque()) { + if (debug != null) { + debug.println("allowedAIALocations: Skipping " + + "non-absolute or opaque URI " + aiaUri); + } + } else if (aiaUri.getHost() == null) { + // We do not allow rules with URIs that omit a hostname + // or address. + if (debug != null) { + debug.println("allowedAIALocations: Skipping " + + "URI rule with no hostname or address: " + + aiaUri); + } + } else if ((scheme = AllowedScheme.nameOf( + aiaUri.getScheme())) != null) { + // When it is an LDAP type, we can check the path + // portion (the DN) for proper structure and reject + // the rule early if it isn't correct. + if (scheme == AllowedScheme.LDAP || + scheme == AllowedScheme.LDAPS) { + try { + new X500Principal(aiaUri.getPath(). + replaceFirst("^/+", "")); + } catch (IllegalArgumentException iae) { + if (debug != null) { + debug.println("allowedAIALocations: " + + "Skipping LDAP rule: " + iae); + } + continue; + } + } + + // When a URI has a non-null query or fragment + // warn the user upon adding the rule that those + // components will be ignored + if (aiaUri.getQuery() != null) { + if (debug != null) { + debug.println("allowedAIALocations: " + + "Rule will ignore non-null query"); + } + } + if (aiaUri.getFragment() != null) { + if (debug != null) { + debug.println("allowedAIALocations: " + + "Rule will ignore non-null fragment"); + } + } + + CA_ISS_URI_FILTERS.add(aiaUri); + if (debug != null) { + debug.println("allowedAIALocations: Added " + + aiaUri + " to URI filters"); + } + } else { + if (debug != null) { + debug.println("allowedAIALocations: Disallowed " + + "filter URI scheme: " + + aiaUri.getScheme()); + } + } + } catch (URISyntaxException urise) { + if (debug != null) { + debug.println("allowedAIALocations: Skipping " + + "filter URI entry " + aiaStr + + ": parse failure at index " + urise.getIndex()); + } + } + } + } + } + /** * Creates a URICertStore. * @@ -244,6 +410,39 @@ class URICertStore extends CertStoreSpi { return null; } URI uri = ((URIName) gn).getURI(); + + // Before performing any instantiation make sure that + // the URI passes any filtering rules. This processing should + // only occur if the com.sun.security.enableAIAcaIssuers is true + // and the "any" rule has not been specified. + if (Builder.USE_AIA && !CA_ISS_ALLOW_ANY) { + URI normAIAUri = uri.normalize(); + AllowedScheme scheme = AllowedScheme.nameOf(normAIAUri.getScheme()); + + if (scheme == null) { + if (debug != null) { + debug.println("allowedAIALocations: No matching ruleset " + + "for scheme " + normAIAUri.getScheme()); + } + return null; + } + + // Go through each of the filter rules and see if any will + // make a positive match against the caIssuer URI. If nothing + // matches then we won't instantiate a URICertStore. + if (CA_ISS_URI_FILTERS.stream().noneMatch(rule -> + scheme.ruleMatcher.matchRule(rule, normAIAUri))) { + if (debug != null) { + debug.println("allowedAIALocations: Warning - " + + "The caIssuer URI " + normAIAUri + + " in the AuthorityInfoAccess extension is denied " + + "access. Use the com.sun.security.allowedAIALocations" + + " security/system property to allow access."); + } + return null; + } + } + try { return URICertStore.getInstance(new URICertStoreParameters(uri)); } catch (Exception ex) { @@ -270,7 +469,7 @@ class URICertStore extends CertStoreSpi { @Override @SuppressWarnings("unchecked") public synchronized Collection engineGetCertificates - (CertSelector selector) throws CertStoreException { + (CertSelector selector) throws CertStoreException { if (ldap) { // caching mechanism, see the class description for more info. @@ -462,4 +661,159 @@ class URICertStore extends CertStoreSpi { super(spi, p, type, params); } } + + /** + * URIRuleMatcher - abstract base class for the rule sets used for + * various URI schemes. + */ + static abstract class URIRuleMatcher { + protected final int wellKnownPort; + + protected URIRuleMatcher(int port) { + wellKnownPort = port; + } + + /** + * Attempt to match the scheme, host and port between a filter + * rule URI and a URI coming from an AIA extension. + * + * @param filterRule the filter rule to match against + * @param caIssuer the AIA URI being compared + * @return true if the scheme, host and port numbers match, false if + * any of the components do not match. If a port number is omitted in + * either the filter rule or AIA URI, the well-known port for that + * scheme is used in the comparison. + */ + boolean schemeHostPortCheck(URI filterRule, URI caIssuer) { + if (!filterRule.getScheme().equalsIgnoreCase( + caIssuer.getScheme())) { + return false; + } else if (!filterRule.getHost().equalsIgnoreCase( + caIssuer.getHost())) { + return false; + } else { + try { + // Check for port matching, taking into consideration + // default ports + int fPort = (filterRule.getPort() == -1) ? wellKnownPort : + filterRule.getPort(); + int caiPort = (caIssuer.getPort() == -1) ? wellKnownPort : + caIssuer.getPort(); + if (fPort != caiPort) { + return false; + } + } catch (IllegalArgumentException iae) { + return false; + } + } + return true; + } + + /** + * Attempt to match an AIA URI against a specific filter rule. The + * specific rules to apply are implementation dependent. + * + * @param filterRule the filter rule to match against + * @param caIssuer the AIA URI being compared + * @return true if all matching rules pass, false if any fail. + */ + abstract boolean matchRule(URI filterRule, URI caIssuer); + } + + static class HttpFtpRuleMatcher extends URIRuleMatcher { + static final HttpFtpRuleMatcher HTTP = new HttpFtpRuleMatcher(80); + static final HttpFtpRuleMatcher HTTPS = new HttpFtpRuleMatcher(443); + static final HttpFtpRuleMatcher FTP = new HttpFtpRuleMatcher(21); + + private HttpFtpRuleMatcher(int port) { + super(port); + } + + @Override + boolean matchRule(URI filterRule, URI caIssuer) { + // Check for scheme/host/port matching + if (!schemeHostPortCheck(filterRule, caIssuer)) { + return false; + } + + // Check the path component to make sure the filter is at + // least a root of the AIA caIssuer URI's path. It must be + // a case-sensitive match for all platforms. + if (!isRootOf(filterRule, caIssuer)) { + if (debug != null) { + debug.println("allowedAIALocations: Match failed: " + + "AIA URI is not within the rule's path hierarchy."); + } + return false; + } + return true; + } + + /** + * Performs a hierarchical containment check, ensuring that the + * base URI's path is a root component of the candidate path. The + * path comparison is case-sensitive. If the base path ends in a + * slash (/) then all candidate paths that begin with the base + * path are allowed. If it does not end in a slash, then it is + * assumed that the leaf node in the base path is a file component + * and both paths must match exactly. + * + * @param base the URI that contains the root path + * @param candidate the URI that contains the path being evaluated + * @return true if {@code candidate} is a child path of {@code base}, + * false otherwise. + */ + private static boolean isRootOf(URI base, URI candidate) { + // Note: The URIs have already been normalized at this point and + // HTTP URIs cannot have null paths. If it's an empty path + // then consider the path to be "/". + String basePath = Optional.of(base.getPath()). + filter(p -> !p.isEmpty()).orElse("/"); + String candPath = Optional.of(candidate.getPath()). + filter(p -> !p.isEmpty()).orElse("/"); + return (basePath.endsWith("/")) ? candPath.startsWith(basePath) : + candPath.equals(basePath); + } + } + + static class LdapRuleMatcher extends URIRuleMatcher { + static final LdapRuleMatcher LDAP = new LdapRuleMatcher(389); + static final LdapRuleMatcher LDAPS = new LdapRuleMatcher(636); + + private LdapRuleMatcher(int port) { + super(port); + } + + @Override + boolean matchRule(URI filterRule, URI caIssuer) { + // Check for scheme/host/port matching + if (!schemeHostPortCheck(filterRule, caIssuer)) { + return false; + } + + // Obtain the base DN component and compare + try { + X500Principal filterBaseDn = new X500Principal( + filterRule.getPath().replaceFirst("^/+", "")); + X500Principal caIssBaseDn = new X500Principal( + caIssuer.getPath().replaceFirst("^/+", "")); + if (!filterBaseDn.equals(caIssBaseDn)) { + if (debug != null) { + debug.println("allowedAIALocations: Match failed: " + + "Base DN mismatch (" + filterBaseDn + " vs " + + caIssBaseDn + ")"); + } + return false; + } + } catch (IllegalArgumentException iae) { + if (debug != null) { + debug.println("allowedAIALocations: Match failed on DN: " + + iae); + } + return false; + } + + return true; + } + } } diff --git a/src/java.base/share/classes/sun/security/ssl/DHasKEM.java b/src/java.base/share/classes/sun/security/ssl/DHasKEM.java new file mode 100644 index 00000000000..763013f280c --- /dev/null +++ b/src/java.base/share/classes/sun/security/ssl/DHasKEM.java @@ -0,0 +1,254 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package sun.security.ssl; + +import sun.security.util.ArrayUtil; +import sun.security.util.CurveDB; +import sun.security.util.ECUtil; +import sun.security.util.NamedCurve; + +import javax.crypto.DecapsulateException; +import javax.crypto.KEM; +import javax.crypto.KEMSpi; +import javax.crypto.KeyAgreement; +import javax.crypto.SecretKey; +import java.io.IOException; +import java.math.BigInteger; +import java.security.*; +import java.security.interfaces.ECKey; +import java.security.interfaces.ECPublicKey; +import java.security.interfaces.XECKey; +import java.security.interfaces.XECPublicKey; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.ECPoint; +import java.security.spec.ECPublicKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.security.spec.NamedParameterSpec; +import java.security.spec.XECPublicKeySpec; +import java.util.Arrays; + +/** + * The DHasKEM class presents a KEM abstraction layer over traditional + * DH-based key exchange, which can be used for either straight + * ECDH/XDH or TLS hybrid key exchanges. + * + * This class can be alongside standard full post-quantum KEMs + * when hybrid implementations are required. + */ +public class DHasKEM implements KEMSpi { + + @Override + public EncapsulatorSpi engineNewEncapsulator( + PublicKey publicKey, AlgorithmParameterSpec spec, + SecureRandom secureRandom) throws InvalidKeyException { + return new Handler(publicKey, null, secureRandom); + } + + @Override + public DecapsulatorSpi engineNewDecapsulator(PrivateKey privateKey, + AlgorithmParameterSpec spec) throws InvalidKeyException { + return new Handler(null, privateKey, null); + } + + private static final class Handler + implements KEMSpi.EncapsulatorSpi, KEMSpi.DecapsulatorSpi { + private final PublicKey pkR; + private final PrivateKey skR; + private final SecureRandom sr; + private final Params params; + + Handler(PublicKey pk, PrivateKey sk, SecureRandom sr) + throws InvalidKeyException { + this.pkR = pk; + this.skR = sk; + this.sr = sr; + this.params = paramsFromKey(pk == null ? sk : pk); + } + + @Override + public KEM.Encapsulated engineEncapsulate(int from, int to, + String algorithm) { + KeyPair kpE = params.generateKeyPair(sr); + PrivateKey skE = kpE.getPrivate(); + PublicKey pkE = kpE.getPublic(); + byte[] pkEm = params.SerializePublicKey(pkE); + try { + SecretKey dh = params.DH(algorithm, skE, pkR); + return new KEM.Encapsulated( + sub(dh, from, to), + pkEm, null); + } catch (Exception e) { + throw new ProviderException("internal error", e); + } + } + + @Override + public int engineSecretSize() { + return params.secretLen; + } + + @Override + public int engineEncapsulationSize() { + return params.publicKeyLen; + } + + @Override + public SecretKey engineDecapsulate(byte[] encapsulation, int from, + int to, String algorithm) throws DecapsulateException { + if (encapsulation.length != params.publicKeyLen) { + throw new DecapsulateException("incorrect encapsulation size"); + } + try { + PublicKey pkE = params.DeserializePublicKey(encapsulation); + SecretKey dh = params.DH(algorithm, skR, pkE); + return sub(dh, from, to); + } catch (IOException | InvalidKeyException e) { + throw new DecapsulateException("Cannot decapsulate", e); + } catch (Exception e) { + throw new ProviderException("internal error", e); + } + } + + private SecretKey sub(SecretKey key, int from, int to) { + if (from == 0 && to == params.secretLen) { + return key; + } + + // Key slicing should never happen. Otherwise, there might be + // a programming error. + throw new AssertionError( + "Unexpected key slicing: from=" + from + ", to=" + to); + } + + // This KEM is designed to be able to represent every ECDH and XDH + private Params paramsFromKey(Key k) throws InvalidKeyException { + if (k instanceof ECKey eckey) { + if (ECUtil.equals(eckey.getParams(), CurveDB.P_256)) { + return Params.P256; + } else if (ECUtil.equals(eckey.getParams(), CurveDB.P_384)) { + return Params.P384; + } else if (ECUtil.equals(eckey.getParams(), CurveDB.P_521)) { + return Params.P521; + } + } else if (k instanceof XECKey xkey + && xkey.getParams() instanceof NamedParameterSpec ns) { + if (ns.getName().equalsIgnoreCase( + NamedParameterSpec.X25519.getName())) { + return Params.X25519; + } else if (ns.getName().equalsIgnoreCase( + NamedParameterSpec.X448.getName())) { + return Params.X448; + } + } + throw new InvalidKeyException("Unsupported key"); + } + } + + private enum Params { + + P256(32, 2 * 32 + 1, + "ECDH", "EC", CurveDB.P_256), + + P384(48, 2 * 48 + 1, + "ECDH", "EC", CurveDB.P_384), + + P521(66, 2 * 66 + 1, + "ECDH", "EC", CurveDB.P_521), + + X25519(32, 32, + "XDH", "XDH", NamedParameterSpec.X25519), + + X448(56, 56, + "XDH", "XDH", NamedParameterSpec.X448); + + private final int secretLen; + private final int publicKeyLen; + private final String kaAlgorithm; + private final String keyAlgorithm; + private final AlgorithmParameterSpec spec; + + Params(int secretLen, int publicKeyLen, String kaAlgorithm, + String keyAlgorithm, AlgorithmParameterSpec spec) { + this.spec = spec; + this.secretLen = secretLen; + this.publicKeyLen = publicKeyLen; + this.kaAlgorithm = kaAlgorithm; + this.keyAlgorithm = keyAlgorithm; + } + + private boolean isEC() { + return this == P256 || this == P384 || this == P521; + } + + private KeyPair generateKeyPair(SecureRandom sr) { + try { + KeyPairGenerator g = KeyPairGenerator.getInstance(keyAlgorithm); + g.initialize(spec, sr); + return g.generateKeyPair(); + } catch (Exception e) { + throw new ProviderException("internal error", e); + } + } + + private byte[] SerializePublicKey(PublicKey k) { + if (isEC()) { + ECPoint w = ((ECPublicKey) k).getW(); + return ECUtil.encodePoint(w, ((NamedCurve) spec).getCurve()); + } else { + byte[] uArray = ((XECPublicKey) k).getU().toByteArray(); + ArrayUtil.reverse(uArray); + return Arrays.copyOf(uArray, publicKeyLen); + } + } + + private PublicKey DeserializePublicKey(byte[] data) throws + IOException, NoSuchAlgorithmException, + InvalidKeySpecException { + KeySpec keySpec; + if (isEC()) { + NamedCurve curve = (NamedCurve) this.spec; + keySpec = new ECPublicKeySpec( + ECUtil.decodePoint(data, curve.getCurve()), curve); + } else { + data = data.clone(); + ArrayUtil.reverse(data); + keySpec = new XECPublicKeySpec( + this.spec, new BigInteger(1, data)); + } + return KeyFactory.getInstance(keyAlgorithm). + generatePublic(keySpec); + } + + private SecretKey DH(String alg, PrivateKey skE, PublicKey pkR) + throws NoSuchAlgorithmException, InvalidKeyException { + KeyAgreement ka = KeyAgreement.getInstance(kaAlgorithm); + ka.init(skE); + ka.doPhase(pkR, true); + return ka.generateSecret(alg); + } + } +} diff --git a/src/java.base/share/classes/sun/security/ssl/Hybrid.java b/src/java.base/share/classes/sun/security/ssl/Hybrid.java new file mode 100644 index 00000000000..e3e2cfa0b23 --- /dev/null +++ b/src/java.base/share/classes/sun/security/ssl/Hybrid.java @@ -0,0 +1,474 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package sun.security.ssl; + +import sun.security.util.ArrayUtil; +import sun.security.util.CurveDB; +import sun.security.util.ECUtil; +import sun.security.util.RawKeySpec; +import sun.security.x509.X509Key; + +import javax.crypto.DecapsulateException; +import javax.crypto.KEM; +import javax.crypto.KEMSpi; +import javax.crypto.SecretKey; +import java.math.BigInteger; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.KeyFactory; +import java.security.KeyFactorySpi; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyPairGeneratorSpi; +import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.ProviderException; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.spec.*; +import java.util.Arrays; +import java.util.Locale; + +// The Hybrid class wraps two underlying algorithms (left and right sides) +// in a single TLS hybrid named group. +// It implements: +// - Hybrid KeyPair generation +// - Hybrid KeyFactory for decoding concatenated hybrid public keys +// - Hybrid KEM implementation for performing encapsulation and +// decapsulation over two underlying algorithms (traditional +// algorithm and post-quantum KEM algorithm) + +public class Hybrid { + + public static final NamedParameterSpec X25519_MLKEM768 = + new NamedParameterSpec("X25519MLKEM768"); + + public static final NamedParameterSpec SECP256R1_MLKEM768 = + new NamedParameterSpec("SecP256r1MLKEM768"); + + public static final NamedParameterSpec SECP384R1_MLKEM1024 = + new NamedParameterSpec("SecP384r1MLKEM1024"); + + private static AlgorithmParameterSpec getSpec(String name) { + if (name.startsWith("secp")) { + return new ECGenParameterSpec(name); + } else { + return new NamedParameterSpec(name); + } + } + + private static KeyPairGenerator getKeyPairGenerator(String name) throws + NoSuchAlgorithmException { + if (name.startsWith("secp")) { + name = "EC"; + } + return KeyPairGenerator.getInstance(name); + } + + private static KeyFactory getKeyFactory(String name) throws + NoSuchAlgorithmException { + if (name.startsWith("secp")) { + name = "EC"; + } + return KeyFactory.getInstance(name); + } + + /** + * Returns a KEM instance for each side of the hybrid algorithm. + * For traditional key exchange algorithms, we use the DH-based KEM + * implementation provided by DHasKEM class. + * For ML-KEM post-quantum algorithms, we obtain a KEM instance + * with "ML-KEM". This is done to work with 3rd-party providers that + * only have "ML-KEM" KEM algorithm. + */ + private static KEM getKEM(String name) throws NoSuchAlgorithmException { + if (name.startsWith("secp") || name.equals("X25519")) { + return KEM.getInstance("DH", HybridProvider.PROVIDER); + } else { + return KEM.getInstance("ML-KEM"); + } + } + + public static class KeyPairGeneratorImpl extends KeyPairGeneratorSpi { + private final KeyPairGenerator left; + private final KeyPairGenerator right; + private final AlgorithmParameterSpec leftSpec; + private final AlgorithmParameterSpec rightSpec; + + public KeyPairGeneratorImpl(String leftAlg, String rightAlg) + throws NoSuchAlgorithmException { + left = getKeyPairGenerator(leftAlg); + right = getKeyPairGenerator(rightAlg); + leftSpec = getSpec(leftAlg); + rightSpec = getSpec(rightAlg); + } + + @Override + public void initialize(AlgorithmParameterSpec params, + SecureRandom random) + throws InvalidAlgorithmParameterException { + left.initialize(leftSpec, random); + right.initialize(rightSpec, random); + } + + @Override + public void initialize(int keysize, SecureRandom random) { + // NO-OP (do nothing) + } + + @Override + public KeyPair generateKeyPair() { + var kp1 = left.generateKeyPair(); + var kp2 = right.generateKeyPair(); + return new KeyPair( + new PublicKeyImpl("Hybrid", kp1.getPublic(), + kp2.getPublic()), + new PrivateKeyImpl("Hybrid", kp1.getPrivate(), + kp2.getPrivate())); + } + } + + public static class KeyFactoryImpl extends KeyFactorySpi { + private final KeyFactory left; + private final KeyFactory right; + private final int leftlen; + private final String leftname; + private final String rightname; + + public KeyFactoryImpl(String left, String right) + throws NoSuchAlgorithmException { + this.left = getKeyFactory(left); + this.right = getKeyFactory(right); + this.leftlen = leftPublicLength(left); + this.leftname = left; + this.rightname = right; + } + + @Override + protected PublicKey engineGeneratePublic(KeySpec keySpec) + throws InvalidKeySpecException { + if (keySpec == null) { + throw new InvalidKeySpecException("keySpec must not be null"); + } + + if (keySpec instanceof RawKeySpec rks) { + byte[] key = rks.getKeyArr(); + if (key == null) { + throw new InvalidKeySpecException( + "RawkeySpec contains null key data"); + } + if (key.length <= leftlen) { + throw new InvalidKeySpecException( + "Hybrid key length " + key.length + + " is too short and its left key length is " + + leftlen); + } + + byte[] leftKeyBytes = Arrays.copyOfRange(key, 0, leftlen); + byte[] rightKeyBytes = Arrays.copyOfRange(key, leftlen, + key.length); + PublicKey leftKey, rightKey; + + try { + if (leftname.startsWith("secp")) { + var curve = CurveDB.lookup(leftname); + var ecSpec = new ECPublicKeySpec( + ECUtil.decodePoint(leftKeyBytes, + curve.getCurve()), curve); + leftKey = left.generatePublic(ecSpec); + } else if (leftname.startsWith("ML-KEM")) { + leftKey = left.generatePublic(new RawKeySpec( + leftKeyBytes)); + } else { + throw new InvalidKeySpecException("Unsupported left" + + " algorithm" + leftname); + } + + if (rightname.equals("X25519")) { + ArrayUtil.reverse(rightKeyBytes); + var xecSpec = new XECPublicKeySpec( + new NamedParameterSpec(rightname), + new BigInteger(1, rightKeyBytes)); + rightKey = right.generatePublic(xecSpec); + } else if (rightname.startsWith("ML-KEM")) { + rightKey = right.generatePublic(new RawKeySpec( + rightKeyBytes)); + } else { + throw new InvalidKeySpecException("Unsupported right" + + " algorithm: " + rightname); + } + + return new PublicKeyImpl("Hybrid", leftKey, rightKey); + } catch (Exception e) { + throw new InvalidKeySpecException("Failed to decode " + + "hybrid key", e); + } + } + + throw new InvalidKeySpecException( + "KeySpec type:" + + keySpec.getClass().getName() + " not supported"); + } + + private static int leftPublicLength(String name) { + return switch (name.toLowerCase(Locale.ROOT)) { + case "secp256r1" -> 65; + case "secp384r1" -> 97; + case "ml-kem-768" -> 1184; + default -> throw new IllegalArgumentException( + "Unknown named group: " + name); + }; + } + + @Override + protected PrivateKey engineGeneratePrivate(KeySpec keySpec) throws + InvalidKeySpecException { + throw new UnsupportedOperationException(); + } + + @Override + protected T engineGetKeySpec(Key key, + Class keySpec) throws InvalidKeySpecException { + throw new UnsupportedOperationException(); + } + + @Override + protected Key engineTranslateKey(Key key) throws InvalidKeyException { + throw new UnsupportedOperationException(); + } + } + + public static class KEMImpl implements KEMSpi { + private final KEM left; + private final KEM right; + + public KEMImpl(String left, String right) + throws NoSuchAlgorithmException { + this.left = getKEM(left); + this.right = getKEM(right); + } + + @Override + public EncapsulatorSpi engineNewEncapsulator(PublicKey publicKey, + AlgorithmParameterSpec spec, SecureRandom secureRandom) throws + InvalidAlgorithmParameterException, InvalidKeyException { + if (publicKey instanceof PublicKeyImpl pk) { + return new Handler(left.newEncapsulator(pk.left, secureRandom), + right.newEncapsulator(pk.right, secureRandom), + null, null); + } + throw new InvalidKeyException(); + } + + @Override + public DecapsulatorSpi engineNewDecapsulator(PrivateKey privateKey, + AlgorithmParameterSpec spec) + throws InvalidAlgorithmParameterException, InvalidKeyException { + if (privateKey instanceof PrivateKeyImpl pk) { + return new Handler(null, null, left.newDecapsulator(pk.left), + right.newDecapsulator(pk.right)); + } + throw new InvalidKeyException(); + } + } + + private static byte[] concat(byte[]... inputs) { + int outLen = 0; + for (byte[] in : inputs) { + outLen += in.length; + } + byte[] out = new byte[outLen]; + int pos = 0; + for (byte[] in : inputs) { + System.arraycopy(in, 0, out, pos, in.length); + pos += in.length; + } + return out; + } + + private record Handler(KEM.Encapsulator le, KEM.Encapsulator re, + KEM.Decapsulator ld, KEM.Decapsulator rd) + implements KEMSpi.EncapsulatorSpi, KEMSpi.DecapsulatorSpi { + @Override + public KEM.Encapsulated engineEncapsulate(int from, int to, + String algorithm) { + int expectedSecretSize = engineSecretSize(); + if (!(from == 0 && to == expectedSecretSize)) { + throw new IllegalArgumentException( + "Invalid range for encapsulation: from = " + from + + " to = " + to + ", expected total secret size = " + + expectedSecretSize); + } + + var left = le.encapsulate(); + var right = re.encapsulate(); + return new KEM.Encapsulated( + new SecretKeyImpl(left.key(), right.key()), + concat(left.encapsulation(), right.encapsulation()), + null); + } + + @Override + public int engineSecretSize() { + if (le != null) { + return le.secretSize() + re.secretSize(); + } else { + return ld.secretSize() + rd.secretSize(); + } + } + + @Override + public int engineEncapsulationSize() { + if (le != null) { + return le.encapsulationSize() + re.encapsulationSize(); + } else { + return ld.encapsulationSize() + rd.encapsulationSize(); + } + } + + @Override + public SecretKey engineDecapsulate(byte[] encapsulation, int from, + int to, String algorithm) throws DecapsulateException { + int expectedEncSize = engineEncapsulationSize(); + if (encapsulation.length != expectedEncSize) { + throw new IllegalArgumentException( + "Invalid key encapsulation message length: " + + encapsulation.length + + ", expected = " + expectedEncSize); + } + + int expectedSecretSize = engineSecretSize(); + if (!(from == 0 && to == expectedSecretSize)) { + throw new IllegalArgumentException( + "Invalid range for decapsulation: from = " + from + + " to = " + to + ", expected total secret size = " + + expectedSecretSize); + } + + var left = Arrays.copyOf(encapsulation, ld.encapsulationSize()); + var right = Arrays.copyOfRange(encapsulation, + ld.encapsulationSize(), encapsulation.length); + return new SecretKeyImpl( + ld.decapsulate(left), + rd.decapsulate(right) + ); + } + } + + // Package-private + record SecretKeyImpl(SecretKey k1, SecretKey k2) + implements SecretKey { + @Override + public String getAlgorithm() { + return "Generic"; + } + + @Override + public String getFormat() { + return null; + } + + @Override + public byte[] getEncoded() { + return null; + } + } + + /** + * Hybrid public key combines two underlying public keys (left and right). + * Public keys can be transmitted/encoded because the hybrid protocol + * requires the public component to be sent. + */ + // Package-private + record PublicKeyImpl(String algorithm, PublicKey left, + PublicKey right) implements PublicKey { + @Override + public String getAlgorithm() { + return algorithm; + } + + // getFormat() returns "RAW" as hybrid key uses RAW concatenation + // of underlying encodings. + @Override + public String getFormat() { + return "RAW"; + } + + // getEncoded() returns the concatenation of the encoded bytes of the + // left and right public keys. + @Override + public byte[] getEncoded() { + return concat(onlyKey(left), onlyKey(right)); + } + + static byte[] onlyKey(PublicKey key) { + if (key instanceof X509Key xk) { + return xk.getKeyAsBytes(); + } + + // Fallback for 3rd-party providers + if (!"X.509".equalsIgnoreCase(key.getFormat())) { + throw new ProviderException("Invalid public key encoding " + + "format"); + } + var xk = new X509Key(); + try { + xk.decode(key.getEncoded()); + } catch (InvalidKeyException e) { + throw new ProviderException("Invalid public key encoding", e); + } + return xk.getKeyAsBytes(); + } + } + + /** + * Hybrid private key combines two underlying private keys (left and right). + * It is for internal use only. The private keys should never be exported. + */ + private record PrivateKeyImpl(String algorithm, PrivateKey left, + PrivateKey right) implements PrivateKey { + + @Override + public String getAlgorithm() { + return algorithm; + } + + // getFormat() returns null because there is no standard + // format for a hybrid private key. + @Override + public String getFormat() { + return null; + } + + // getEncoded() returns an empty byte array because there is no + // standard encoding format for a hybrid private key. + @Override + public byte[] getEncoded() { + return null; + } + } +} diff --git a/src/java.base/share/classes/sun/security/ssl/HybridProvider.java b/src/java.base/share/classes/sun/security/ssl/HybridProvider.java new file mode 100644 index 00000000000..c77d6f66273 --- /dev/null +++ b/src/java.base/share/classes/sun/security/ssl/HybridProvider.java @@ -0,0 +1,130 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ + +package sun.security.ssl; + +import java.security.Provider; +import java.security.NoSuchAlgorithmException; +import java.util.List; +import java.util.Map; + +import static sun.security.util.SecurityConstants.PROVIDER_VER; + +// This is an internal provider used in the JSSE code for DH-as-KEM +// and Hybrid KEM support. It doesn't actually get installed in the +// system's list of security providers that is searched at runtime. +// JSSE loads this provider internally. +// It registers Hybrid KeyPairGenerator, KeyFactory, and KEM +// implementations for hybrid named groups as Provider services. + +public class HybridProvider { + + public static final Provider PROVIDER = new ProviderImpl(); + + private static final class ProviderImpl extends Provider { + @java.io.Serial + private static final long serialVersionUID = 0L; + + ProviderImpl() { + super("HybridAndDHAsKEM", PROVIDER_VER, + "Hybrid and DHAsKEM provider"); + put("KEM.DH", DHasKEM.class.getName()); + + // Hybrid KeyPairGenerator/KeyFactory/KEM + + // The order of shares in the concatenation for group name + // X25519MLKEM768 has been reversed as per the current + // draft RFC. + var attrs = Map.of("name", "X25519MLKEM768", "left", "ML-KEM-768", + "right", "X25519"); + putService(new HybridService(this, "KeyPairGenerator", + "X25519MLKEM768", + "sun.security.ssl.Hybrid$KeyPairGeneratorImpl", + null, attrs)); + putService(new HybridService(this, "KEM", + "X25519MLKEM768", + "sun.security.ssl.Hybrid$KEMImpl", + null, attrs)); + putService(new HybridService(this, "KeyFactory", + "X25519MLKEM768", + "sun.security.ssl.Hybrid$KeyFactoryImpl", + null, attrs)); + + attrs = Map.of("name", "SecP256r1MLKEM768", "left", "secp256r1", + "right", "ML-KEM-768"); + putService(new HybridService(this, "KeyPairGenerator", + "SecP256r1MLKEM768", + "sun.security.ssl.Hybrid$KeyPairGeneratorImpl", + null, attrs)); + putService(new HybridService(this, "KEM", + "SecP256r1MLKEM768", + "sun.security.ssl.Hybrid$KEMImpl", + null, attrs)); + putService(new HybridService(this, "KeyFactory", + "SecP256r1MLKEM768", + "sun.security.ssl.Hybrid$KeyFactoryImpl", + null, attrs)); + + attrs = Map.of("name", "SecP384r1MLKEM1024", "left", "secp384r1", + "right", "ML-KEM-1024"); + putService(new HybridService(this, "KeyPairGenerator", + "SecP384r1MLKEM1024", + "sun.security.ssl.Hybrid$KeyPairGeneratorImpl", + null, attrs)); + putService(new HybridService(this, "KEM", + "SecP384r1MLKEM1024", + "sun.security.ssl.Hybrid$KEMImpl", + null, attrs)); + putService(new HybridService(this, "KeyFactory", + "SecP384r1MLKEM1024", + "sun.security.ssl.Hybrid$KeyFactoryImpl", + null, attrs)); + } + } + + private static class HybridService extends Provider.Service { + + HybridService(Provider p, String type, String algo, String cn, + List aliases, Map attrs) { + super(p, type, algo, cn, aliases, attrs); + } + + @Override + public Object newInstance(Object ctrParamObj) + throws NoSuchAlgorithmException { + String type = getType(); + return switch (type) { + case "KeyPairGenerator" -> new Hybrid.KeyPairGeneratorImpl( + getAttribute("left"), getAttribute("right")); + case "KeyFactory" -> new Hybrid.KeyFactoryImpl( + getAttribute("left"), getAttribute("right")); + case "KEM" -> new Hybrid.KEMImpl( + getAttribute("left"), getAttribute("right")); + default -> throw new NoSuchAlgorithmException( + "Unexpected value: " + type); + }; + } + } +} diff --git a/src/java.base/share/classes/sun/security/ssl/JsseJce.java b/src/java.base/share/classes/sun/security/ssl/JsseJce.java index 3ffc2d84168..1e610eeab1d 100644 --- a/src/java.base/share/classes/sun/security/ssl/JsseJce.java +++ b/src/java.base/share/classes/sun/security/ssl/JsseJce.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, 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 @@ -165,7 +165,6 @@ final class JsseJce { static { boolean mediator = true; try { - Signature.getInstance(SIGNATURE_ECDSA); Signature.getInstance(SIGNATURE_RAWECDSA); KeyAgreement.getInstance("ECDH"); KeyFactory.getInstance("EC"); diff --git a/src/java.base/share/classes/sun/security/ssl/KAKeyDerivation.java b/src/java.base/share/classes/sun/security/ssl/KAKeyDerivation.java index 623f83f547a..af62faf4706 100644 --- a/src/java.base/share/classes/sun/security/ssl/KAKeyDerivation.java +++ b/src/java.base/share/classes/sun/security/ssl/KAKeyDerivation.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, 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,7 +24,10 @@ */ package sun.security.ssl; +import sun.security.util.RawKeySpec; + import javax.crypto.KDF; +import javax.crypto.KEM; import javax.crypto.KeyAgreement; import javax.crypto.SecretKey; import javax.crypto.spec.HKDFParameterSpec; @@ -32,9 +35,11 @@ import javax.net.ssl.SSLHandshakeException; import java.io.IOException; import java.security.GeneralSecurityException; +import java.security.KeyFactory; import java.security.PrivateKey; +import java.security.Provider; import java.security.PublicKey; -import java.security.spec.AlgorithmParameterSpec; +import java.security.SecureRandom; import sun.security.util.KeyUtil; /** @@ -46,15 +51,32 @@ public class KAKeyDerivation implements SSLKeyDerivation { private final HandshakeContext context; private final PrivateKey localPrivateKey; private final PublicKey peerPublicKey; + private final byte[] keyshare; + private final Provider provider; + // Constructor called by Key Agreement KAKeyDerivation(String algorithmName, HandshakeContext context, PrivateKey localPrivateKey, PublicKey peerPublicKey) { + this(algorithmName, null, context, localPrivateKey, + peerPublicKey, null); + } + + // When the constructor called by KEM: store the client's public key or the + // encapsulated message in keyshare. + KAKeyDerivation(String algorithmName, + NamedGroup namedGroup, + HandshakeContext context, + PrivateKey localPrivateKey, + PublicKey peerPublicKey, + byte[] keyshare) { this.algorithmName = algorithmName; this.context = context; this.localPrivateKey = localPrivateKey; this.peerPublicKey = peerPublicKey; + this.keyshare = keyshare; + this.provider = (namedGroup != null) ? namedGroup.getProvider() : null; } @Override @@ -94,22 +116,15 @@ public class KAKeyDerivation implements SSLKeyDerivation { } } - /** - * Handle the TLSv1.3 objects, which use the HKDF algorithms. - */ - private SecretKey t13DeriveKey(String type) - throws IOException { - SecretKey sharedSecret = null; + private SecretKey deriveHandshakeSecret(String label, + SecretKey sharedSecret) + throws GeneralSecurityException, IOException { SecretKey earlySecret = null; SecretKey saltSecret = null; - try { - KeyAgreement ka = KeyAgreement.getInstance(algorithmName); - ka.init(localPrivateKey); - ka.doPhase(peerPublicKey, true); - sharedSecret = ka.generateSecret("TlsPremasterSecret"); - CipherSuite.HashAlg hashAlg = context.negotiatedCipherSuite.hashAlg; - SSLKeyDerivation kd = context.handshakeKeyDerivation; + CipherSuite.HashAlg hashAlg = context.negotiatedCipherSuite.hashAlg; + SSLKeyDerivation kd = context.handshakeKeyDerivation; + try { if (kd == null) { // No PSK is in use. // If PSK is not in use, Early Secret will still be // HKDF-Extract(0, 0). @@ -129,12 +144,90 @@ public class KAKeyDerivation implements SSLKeyDerivation { // the handshake secret key derivation (below) as it may not // work with the "sharedSecret" obj. KDF hkdf = KDF.getInstance(hashAlg.hkdfAlgorithm); - return hkdf.deriveKey(type, HKDFParameterSpec.ofExtract() - .addSalt(saltSecret).addIKM(sharedSecret).extractOnly()); + var spec = HKDFParameterSpec.ofExtract().addSalt(saltSecret); + if (sharedSecret instanceof Hybrid.SecretKeyImpl hsk) { + spec = spec.addIKM(hsk.k1()).addIKM(hsk.k2()); + } else { + spec = spec.addIKM(sharedSecret); + } + + return hkdf.deriveKey(label, spec.extractOnly()); + } finally { + KeyUtil.destroySecretKeys(earlySecret, saltSecret); + } + } + /** + * This method is called by the server to perform KEM encapsulation. + * It uses the client's public key (sent by the client as a keyshare) + * to encapsulate a shared secret and returns the encapsulated message. + * + * Package-private, used from KeyShareExtension.SHKeyShareProducer:: + * produce(). + */ + KEM.Encapsulated encapsulate(String algorithm, SecureRandom random) + throws IOException { + SecretKey sharedSecret = null; + + if (keyshare == null) { + throw new IOException("No keyshare available for KEM " + + "encapsulation"); + } + + try { + KeyFactory kf = (provider != null) ? + KeyFactory.getInstance(algorithmName, provider) : + KeyFactory.getInstance(algorithmName); + var pk = kf.generatePublic(new RawKeySpec(keyshare)); + + KEM kem = (provider != null) ? + KEM.getInstance(algorithmName, provider) : + KEM.getInstance(algorithmName); + KEM.Encapsulator e = kem.newEncapsulator(pk, random); + KEM.Encapsulated enc = e.encapsulate(); + sharedSecret = enc.key(); + + SecretKey derived = deriveHandshakeSecret(algorithm, sharedSecret); + + return new KEM.Encapsulated(derived, enc.encapsulation(), null); } catch (GeneralSecurityException gse) { throw new SSLHandshakeException("Could not generate secret", gse); } finally { - KeyUtil.destroySecretKeys(sharedSecret, earlySecret, saltSecret); + KeyUtil.destroySecretKeys(sharedSecret); + } + } + + /** + * Handle the TLSv1.3 objects, which use the HKDF algorithms. + */ + private SecretKey t13DeriveKey(String type) + throws IOException { + SecretKey sharedSecret = null; + + try { + if (keyshare != null) { + // Using KEM: called by the client after receiving the KEM + // ciphertext (keyshare) from the server in ServerHello. + // The client decapsulates it using its private key. + KEM kem = (provider != null) + ? KEM.getInstance(algorithmName, provider) + : KEM.getInstance(algorithmName); + var decapsulator = kem.newDecapsulator(localPrivateKey); + sharedSecret = decapsulator.decapsulate( + keyshare, 0, decapsulator.secretSize(), + "Generic"); + } else { + // Using traditional DH-style Key Agreement + KeyAgreement ka = KeyAgreement.getInstance(algorithmName); + ka.init(localPrivateKey); + ka.doPhase(peerPublicKey, true); + sharedSecret = ka.generateSecret("Generic"); + } + + return deriveHandshakeSecret(type, sharedSecret); + } catch (GeneralSecurityException gse) { + throw new SSLHandshakeException("Could not generate secret", gse); + } finally { + KeyUtil.destroySecretKeys(sharedSecret); } } } diff --git a/src/java.base/share/classes/sun/security/ssl/KEMKeyExchange.java b/src/java.base/share/classes/sun/security/ssl/KEMKeyExchange.java new file mode 100644 index 00000000000..fb8de6cb104 --- /dev/null +++ b/src/java.base/share/classes/sun/security/ssl/KEMKeyExchange.java @@ -0,0 +1,223 @@ +/* + * 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package sun.security.ssl; + +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.PrivateKey; +import java.security.Provider; +import java.security.ProviderException; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.spec.NamedParameterSpec; +import javax.crypto.SecretKey; + +import sun.security.ssl.NamedGroup.NamedGroupSpec; +import sun.security.x509.X509Key; + +/** + * Specifics for single or hybrid Key exchanges based on KEM + */ +final class KEMKeyExchange { + + static final SSLKeyAgreementGenerator kemKAGenerator + = new KEMKAGenerator(); + + static final class KEMCredentials implements NamedGroupCredentials { + + final NamedGroup namedGroup; + // Unlike other credentials, we directly store the key share + // value here, no need to convert to a key + private final byte[] keyshare; + + KEMCredentials(byte[] keyshare, NamedGroup namedGroup) { + this.keyshare = keyshare; + this.namedGroup = namedGroup; + } + + // For KEM, server performs encapsulation and the resulting + // encapsulated message becomes the key_share value sent to + // the client. It is not a public key, so no PublicKey object + // to return. + @Override + public PublicKey getPublicKey() { + throw new UnsupportedOperationException( + "KEMCredentials stores raw keyshare, not a PublicKey"); + } + + public byte[] getKeyShare() { + return keyshare; + } + + @Override + public NamedGroup getNamedGroup() { + return namedGroup; + } + + /** + * Instantiates a KEMCredentials object + */ + static KEMCredentials valueOf(NamedGroup namedGroup, + byte[] encodedPoint) { + + if (namedGroup.spec != NamedGroupSpec.NAMED_GROUP_KEM) { + throw new RuntimeException( + "Credentials decoding: Not KEM named group"); + } + + if (encodedPoint == null || encodedPoint.length == 0) { + return null; + } + + return new KEMCredentials(encodedPoint, namedGroup); + } + } + + private static class KEMPossession implements SSLPossession { + private final NamedGroup namedGroup; + + public KEMPossession(NamedGroup ng) { + this.namedGroup = ng; + } + public NamedGroup getNamedGroup() { + return namedGroup; + } + } + + static final class KEMReceiverPossession extends KEMPossession { + + private final PrivateKey privateKey; + private final PublicKey publicKey; + + KEMReceiverPossession(NamedGroup namedGroup, SecureRandom random) { + super(namedGroup); + String algName = null; + try { + // For KEM: This receiver side (client) generates a key pair. + algName = ((NamedParameterSpec)namedGroup.keAlgParamSpec). + getName(); + Provider provider = namedGroup.getProvider(); + KeyPairGenerator kpg = (provider != null) ? + KeyPairGenerator.getInstance(algName, provider) : + KeyPairGenerator.getInstance(algName); + + kpg.initialize(namedGroup.keAlgParamSpec, random); + KeyPair kp = kpg.generateKeyPair(); + privateKey = kp.getPrivate(); + publicKey = kp.getPublic(); + } catch (GeneralSecurityException e) { + throw new RuntimeException( + "Could not generate keypair for algorithm: " + + algName, e); + } + } + + @Override + public byte[] encode() { + if (publicKey instanceof X509Key xk) { + return xk.getKeyAsBytes(); + } else if (publicKey instanceof Hybrid.PublicKeyImpl hk) { + return hk.getEncoded(); + } + throw new ProviderException("Unsupported key type: " + publicKey); + } + + // Package-private + PublicKey getPublicKey() { + return publicKey; + } + + // Package-private + PrivateKey getPrivateKey() { + return privateKey; + } + } + + static final class KEMSenderPossession extends KEMPossession { + + private SecretKey key; + private final SecureRandom random; + + KEMSenderPossession(NamedGroup namedGroup, SecureRandom random) { + super(namedGroup); + this.random = random; + } + + // Package-private + SecureRandom getRandom() { + return random; + } + + // Package-private + SecretKey getKey() { + return key; + } + + // Package-private + void setKey(SecretKey key) { + this.key = key; + } + + @Override + public byte[] encode() { + throw new UnsupportedOperationException("encode() not supported"); + } + } + + private static final class KEMKAGenerator + implements SSLKeyAgreementGenerator { + + // Prevent instantiation of this class. + private KEMKAGenerator() { + // blank + } + + @Override + public SSLKeyDerivation createKeyDerivation( + HandshakeContext context) throws IOException { + for (SSLPossession poss : context.handshakePossessions) { + if (poss instanceof KEMReceiverPossession kposs) { + NamedGroup ng = kposs.getNamedGroup(); + for (SSLCredentials cred : context.handshakeCredentials) { + if (cred instanceof KEMCredentials kcred && + ng.equals(kcred.namedGroup)) { + String name = ((NamedParameterSpec) + ng.keAlgParamSpec).getName(); + return new KAKeyDerivation(name, ng, context, + kposs.getPrivateKey(), null, + kcred.getKeyShare()); + } + } + } + } + context.conContext.fatal(Alert.HANDSHAKE_FAILURE, + "No suitable KEM key agreement " + + "parameters negotiated"); + return null; + } + } +} diff --git a/src/java.base/share/classes/sun/security/ssl/KeyShareExtension.java b/src/java.base/share/classes/sun/security/ssl/KeyShareExtension.java index 8d785f7515a..0d2cbb8f529 100644 --- a/src/java.base/share/classes/sun/security/ssl/KeyShareExtension.java +++ b/src/java.base/share/classes/sun/security/ssl/KeyShareExtension.java @@ -27,8 +27,11 @@ package sun.security.ssl; import java.io.IOException; import java.nio.ByteBuffer; +import java.security.AlgorithmConstraints; import java.security.CryptoPrimitive; import java.security.GeneralSecurityException; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.NamedParameterSpec; import java.text.MessageFormat; import java.util.*; import javax.net.ssl.SSLProtocolException; @@ -297,7 +300,9 @@ final class KeyShareExtension { // update the context chc.handshakePossessions.add(pos); // May need more possession types in the future. - if (pos instanceof NamedGroupPossession) { + if (pos instanceof NamedGroupPossession || + pos instanceof + KEMKeyExchange.KEMReceiverPossession) { return pos.encode(); } } @@ -358,24 +363,16 @@ final class KeyShareExtension { try { SSLCredentials kaCred = ng.decodeCredentials(entry.keyExchange); - if (shc.algorithmConstraints != null && - kaCred instanceof - NamedGroupCredentials namedGroupCredentials) { - if (!shc.algorithmConstraints.permits( - EnumSet.of(CryptoPrimitive.KEY_AGREEMENT), - namedGroupCredentials.getPublicKey())) { - if (SSLLogger.isOn() && - SSLLogger.isOn("ssl,handshake")) { - SSLLogger.warning( + + if (!isCredentialPermitted(shc.algorithmConstraints, + kaCred)) { + if (SSLLogger.isOn() && + SSLLogger.isOn("ssl,handshake")) { + SSLLogger.warning( "key share entry of " + ng + " does not " + - " comply with algorithm constraints"); - } - - kaCred = null; + "comply with algorithm constraints"); } - } - - if (kaCred != null) { + } else { credentials.add(kaCred); } } catch (GeneralSecurityException ex) { @@ -513,7 +510,8 @@ final class KeyShareExtension { @Override public byte[] produce(ConnectionContext context, HandshakeMessage message) throws IOException { - // The producing happens in client side only. + // The producing happens in server side only. + ServerHandshakeContext shc = (ServerHandshakeContext)context; // In response to key_share request only @@ -571,7 +569,9 @@ final class KeyShareExtension { SSLPossession[] poses = ke.createPossessions(shc); for (SSLPossession pos : poses) { - if (!(pos instanceof NamedGroupPossession)) { + if (!(pos instanceof NamedGroupPossession || + pos instanceof + KEMKeyExchange.KEMSenderPossession)) { // May need more possession types in the future. continue; } @@ -579,7 +579,34 @@ final class KeyShareExtension { // update the context shc.handshakeKeyExchange = ke; shc.handshakePossessions.add(pos); - keyShare = new KeyShareEntry(ng.id, pos.encode()); + + // For KEM, perform encapsulation using the client’s public + // key (KEMCredentials). The resulting encapsulated message + // becomes the key_share value sent to the client. The + // shared secret derived from encapsulation is stored in + // the KEMSenderPossession for later use in the TLS key + // schedule. + + // SSLKeyExchange.createPossessions() returns at most one + // key-agreement possession or one KEMSenderPossession + // per handshake. + if (pos instanceof KEMKeyExchange.KEMSenderPossession xp) { + if (cd instanceof KEMKeyExchange.KEMCredentials kcred + && ng.equals(kcred.namedGroup)) { + String name = ((NamedParameterSpec) + ng.keAlgParamSpec).getName(); + KAKeyDerivation handshakeKD = new KAKeyDerivation( + name, ng, shc, null, null, + kcred.getKeyShare()); + var encaped = handshakeKD.encapsulate( + "TlsHandshakeSecret", xp.getRandom()); + xp.setKey(encaped.key()); + keyShare = new KeyShareEntry(ng.id, + encaped.encapsulation()); + } + } else { + keyShare = new KeyShareEntry(ng.id, pos.encode()); + } break; } @@ -663,19 +690,13 @@ final class KeyShareExtension { try { SSLCredentials kaCred = ng.decodeCredentials(keyShare.keyExchange); - if (chc.algorithmConstraints != null && - kaCred instanceof - NamedGroupCredentials namedGroupCredentials) { - if (!chc.algorithmConstraints.permits( - EnumSet.of(CryptoPrimitive.KEY_AGREEMENT), - namedGroupCredentials.getPublicKey())) { - chc.conContext.fatal(Alert.INSUFFICIENT_SECURITY, - "key share entry of " + ng + " does not " + - " comply with algorithm constraints"); - } - } - if (kaCred != null) { + if (!isCredentialPermitted(chc.algorithmConstraints, + kaCred)) { + chc.conContext.fatal(Alert.INSUFFICIENT_SECURITY, + "key share entry of " + ng + " does not " + + "comply with algorithm constraints"); + } else { credentials = kaCred; } } catch (GeneralSecurityException ex) { @@ -696,6 +717,34 @@ final class KeyShareExtension { } } + private static boolean isCredentialPermitted( + AlgorithmConstraints constraints, + SSLCredentials cred) { + + if (constraints == null) return true; + if (cred == null) return false; + + if (cred instanceof NamedGroupCredentials namedGroupCred) { + if (namedGroupCred instanceof KEMKeyExchange.KEMCredentials + kemCred) { + AlgorithmParameterSpec paramSpec = kemCred.getNamedGroup(). + keAlgParamSpec; + String algName = (paramSpec instanceof NamedParameterSpec nps) ? + nps.getName() : null; + return algName != null && constraints.permits( + EnumSet.of(CryptoPrimitive.KEY_AGREEMENT), + algName, + null); + } else { + return constraints.permits( + EnumSet.of(CryptoPrimitive.KEY_AGREEMENT), + namedGroupCred.getPublicKey()); + } + } + + return true; + } + /** * The absence processing if the extension is not present in * the ServerHello handshake message. diff --git a/src/java.base/share/classes/sun/security/ssl/NamedGroup.java b/src/java.base/share/classes/sun/security/ssl/NamedGroup.java index 877236ebfad..02524e67656 100644 --- a/src/java.base/share/classes/sun/security/ssl/NamedGroup.java +++ b/src/java.base/share/classes/sun/security/ssl/NamedGroup.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, 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,10 +30,12 @@ import java.security.spec.AlgorithmParameterSpec; import java.security.spec.ECParameterSpec; import java.security.spec.InvalidParameterSpecException; import java.security.spec.NamedParameterSpec; +import java.util.Arrays; import java.util.Collections; import java.util.EnumSet; import java.util.List; import java.util.ArrayList; +import java.util.Objects; import java.util.Set; import javax.crypto.KeyAgreement; import javax.crypto.spec.DHParameterSpec; @@ -214,6 +216,39 @@ enum NamedGroup { ProtocolVersion.PROTOCOLS_TO_13, PredefinedDHParameterSpecs.ffdheParams.get(8192)), + ML_KEM_512(0x0200, "MLKEM512", + NamedGroupSpec.NAMED_GROUP_KEM, + ProtocolVersion.PROTOCOLS_OF_13, + null), + + ML_KEM_768(0x0201, "MLKEM768", + NamedGroupSpec.NAMED_GROUP_KEM, + ProtocolVersion.PROTOCOLS_OF_13, + null), + + ML_KEM_1024(0x0202, "MLKEM1024", + NamedGroupSpec.NAMED_GROUP_KEM, + ProtocolVersion.PROTOCOLS_OF_13, + null), + + X25519MLKEM768(0x11ec, "X25519MLKEM768", + NamedGroupSpec.NAMED_GROUP_KEM, + ProtocolVersion.PROTOCOLS_OF_13, + Hybrid.X25519_MLKEM768, + HybridProvider.PROVIDER), + + SECP256R1MLKEM768(0x11eb, "SecP256r1MLKEM768", + NamedGroupSpec.NAMED_GROUP_KEM, + ProtocolVersion.PROTOCOLS_OF_13, + Hybrid.SECP256R1_MLKEM768, + HybridProvider.PROVIDER), + + SECP384R1MLKEM1024(0x11ed, "SecP384r1MLKEM1024", + NamedGroupSpec.NAMED_GROUP_KEM, + ProtocolVersion.PROTOCOLS_OF_13, + Hybrid.SECP384R1_MLKEM1024, + HybridProvider.PROVIDER), + // Elliptic Curves (RFC 4492) // // arbitrary prime and characteristic-2 curves @@ -234,22 +269,33 @@ enum NamedGroup { final AlgorithmParameterSpec keAlgParamSpec; final AlgorithmParameters keAlgParams; final boolean isAvailable; + final Provider defaultProvider; // performance optimization private static final Set KEY_AGREEMENT_PRIMITIVE_SET = Collections.unmodifiableSet(EnumSet.of(CryptoPrimitive.KEY_AGREEMENT)); - // Constructor used for all NamedGroup types NamedGroup(int id, String name, NamedGroupSpec namedGroupSpec, ProtocolVersion[] supportedProtocols, AlgorithmParameterSpec keAlgParamSpec) { + this(id, name, namedGroupSpec, supportedProtocols, keAlgParamSpec, + null); + } + + // Constructor used for all NamedGroup types + NamedGroup(int id, String name, + NamedGroupSpec namedGroupSpec, + ProtocolVersion[] supportedProtocols, + AlgorithmParameterSpec keAlgParamSpec, + Provider defaultProvider) { this.id = id; this.name = name; this.spec = namedGroupSpec; this.algorithm = namedGroupSpec.algorithm; this.supportedProtocols = supportedProtocols; this.keAlgParamSpec = keAlgParamSpec; + this.defaultProvider = defaultProvider; // Check if it is a supported named group. AlgorithmParameters algParams = null; @@ -266,16 +312,28 @@ enum NamedGroup { // Check the specific algorithm parameters. if (mediator) { try { - algParams = - AlgorithmParameters.getInstance(namedGroupSpec.algorithm); - algParams.init(keAlgParamSpec); + // Skip AlgorithmParameters for KEMs (not supported) + // Check KEM's availability via KeyFactory + if (namedGroupSpec == NamedGroupSpec.NAMED_GROUP_KEM) { + if (defaultProvider == null) { + KeyFactory.getInstance(name); + } else { + KeyFactory.getInstance(name, defaultProvider); + } + } else { + // ECDHE or others: use AlgorithmParameters as before + algParams = AlgorithmParameters.getInstance( + namedGroupSpec.algorithm); + algParams.init(keAlgParamSpec); + } } catch (InvalidParameterSpecException | NoSuchAlgorithmException exp) { if (namedGroupSpec != NamedGroupSpec.NAMED_GROUP_XDH) { mediator = false; if (SSLLogger.isOn() && SSLLogger.isOn("ssl,handshake")) { SSLLogger.warning( - "No AlgorithmParameters for " + name, exp); + "No AlgorithmParameters or KeyFactory for " + name, + exp); } } else { // Please remove the following code if the XDH/X25519/X448 @@ -307,6 +365,10 @@ enum NamedGroup { this.keAlgParams = mediator ? algParams : null; } + Provider getProvider() { + return defaultProvider; + } + // // The next set of methods search & retrieve NamedGroups. // @@ -403,10 +465,9 @@ enum NamedGroup { AlgorithmConstraints constraints, NamedGroupSpec type) { boolean hasFFDHEGroups = false; - for (String ng : sslConfig.namedGroups) { - NamedGroup namedGroup = NamedGroup.nameOf(ng); - if (namedGroup != null && - namedGroup.isAvailable && namedGroup.spec == type) { + for (NamedGroup namedGroup : + SupportedGroups.getGroupsFromConfig(sslConfig)) { + if (namedGroup.isAvailable && namedGroup.spec == type) { if (namedGroup.isPermitted(constraints)) { return true; } @@ -441,8 +502,8 @@ enum NamedGroup { // Is the named group supported? static boolean isEnabled(SSLConfiguration sslConfig, NamedGroup namedGroup) { - for (String ng : sslConfig.namedGroups) { - if (namedGroup.name.equalsIgnoreCase(ng)) { + for (NamedGroup ng : SupportedGroups.getGroupsFromConfig(sslConfig)) { + if (namedGroup.equals(ng)) { return true; } } @@ -456,12 +517,10 @@ enum NamedGroup { SSLConfiguration sslConfig, ProtocolVersion negotiatedProtocol, AlgorithmConstraints constraints, NamedGroupSpec[] types) { - for (String name : sslConfig.namedGroups) { - NamedGroup ng = NamedGroup.nameOf(name); - if (ng != null && ng.isAvailable && - (NamedGroupSpec.arrayContains(types, ng.spec)) && - ng.isAvailable(negotiatedProtocol) && - ng.isPermitted(constraints)) { + for (NamedGroup ng : SupportedGroups.getGroupsFromConfig(sslConfig)) { + if (ng.isAvailable && NamedGroupSpec.arrayContains(types, ng.spec) + && ng.isAvailable(negotiatedProtocol) + && ng.isPermitted(constraints)) { return ng; } } @@ -545,6 +604,10 @@ enum NamedGroup { return spec.decodeCredentials(this, encoded); } + SSLPossession createPossession(boolean isClient, SecureRandom random) { + return spec.createPossession(this, isClient, random); + } + SSLPossession createPossession(SecureRandom random) { return spec.createPossession(this, random); } @@ -566,6 +629,11 @@ enum NamedGroup { SSLKeyDerivation createKeyDerivation( HandshakeContext hc) throws IOException; + + default SSLPossession createPossession(NamedGroup ng, boolean isClient, + SecureRandom random) { + return createPossession(ng, random); + } } enum NamedGroupSpec implements NamedGroupScheme { @@ -578,6 +646,10 @@ enum NamedGroup { // Finite Field Groups (XDH) NAMED_GROUP_XDH("XDH", XDHScheme.instance), + // Post-Quantum Cryptography (PQC) KEM groups + // Currently used for hybrid named groups + NAMED_GROUP_KEM("KEM", KEMScheme.instance), + // arbitrary prime and curves (ECDHE) NAMED_GROUP_ARBITRARY("EC", null), @@ -634,6 +706,15 @@ enum NamedGroup { return null; } + public SSLPossession createPossession( + NamedGroup ng, boolean isClient, SecureRandom random) { + if (scheme != null) { + return scheme.createPossession(ng, isClient, random); + } + + return null; + } + @Override public SSLPossession createPossession( NamedGroup ng, SecureRandom random) { @@ -739,19 +820,128 @@ enum NamedGroup { } } + private static class KEMScheme implements NamedGroupScheme { + private static final KEMScheme instance = new KEMScheme(); + + @Override + public byte[] encodePossessionPublicKey(NamedGroupPossession poss) { + return poss.encode(); + } + + @Override + public SSLCredentials decodeCredentials(NamedGroup ng, + byte[] encoded) throws IOException, GeneralSecurityException { + return KEMKeyExchange.KEMCredentials.valueOf(ng, encoded); + } + + @Override + public SSLPossession createPossession(NamedGroup ng, + SecureRandom random) { + // Must call createPossession with isClient + throw new UnsupportedOperationException(); + } + + @Override + public SSLPossession createPossession( + NamedGroup ng, boolean isClient, SecureRandom random) { + return isClient + ? new KEMKeyExchange.KEMReceiverPossession(ng, random) + : new KEMKeyExchange.KEMSenderPossession(ng, random); + } + + @Override + public SSLKeyDerivation createKeyDerivation( + HandshakeContext hc) throws IOException { + return KEMKeyExchange.kemKAGenerator.createKeyDerivation(hc); + } + } + + // Inner class encapsulating supported named groups. static final class SupportedGroups { - // the supported named groups, non-null immutable list + + // Default named groups. + private static final NamedGroup[] defaultGroups = new NamedGroup[]{ + // Hybrid key agreement + X25519MLKEM768, + + // Primary XDH (RFC 7748) curves + X25519, + + // Primary NIST Suite B curves + SECP256_R1, + SECP384_R1, + SECP521_R1, + + // Secondary XDH curves + X448, + + // FFDHE (RFC 7919) + FFDHE_2048, + FFDHE_3072, + FFDHE_4096, + FFDHE_6144, + FFDHE_8192 + }; + + // Filter default groups names against default constraints. + // Those are the values being displayed to the user with + // "java -XshowSettings:security:tls" command. + private static final String[] defaultNames = Arrays.stream( + defaultGroups) + .filter(ng -> ng.isAvailable) + .filter(ng -> ng.isPermitted(SSLAlgorithmConstraints.DEFAULT)) + .map(ng -> ng.name) + .toArray(String[]::new); + + private static final NamedGroup[] customizedGroups = + getCustomizedNamedGroups(); + + // Note: user-passed groups are not being filtered against default + // algorithm constraints here. They will be displayed as-is. + private static final String[] customizedNames = + customizedGroups == null ? + null : Arrays.stream(customizedGroups) + .map(ng -> ng.name) + .toArray(String[]::new); + + // Named group names for SSLConfiguration. static final String[] namedGroups; static { - // The value of the System Property defines a list of enabled named - // groups in preference order, separated with comma. For example: - // - // jdk.tls.namedGroups="secp521r1, secp256r1, ffdhe2048" - // - // If the System Property is not defined or the value is empty, the - // default groups and preferences will be used. + if (customizedNames != null) { + namedGroups = customizedNames; + } else { + if (defaultNames.length == 0) { + SSLLogger.logWarning("ssl", "No default named groups"); + } + namedGroups = defaultNames; + } + } + + // Avoid the group lookup for default and customized groups. + static NamedGroup[] getGroupsFromConfig(SSLConfiguration sslConfig) { + if (sslConfig.namedGroups == defaultNames) { + return defaultGroups; + } else if (sslConfig.namedGroups == customizedNames) { + return customizedGroups; + } else { + return Arrays.stream(sslConfig.namedGroups) + .map(NamedGroup::nameOf) + .filter(Objects::nonNull) + .toArray(NamedGroup[]::new); + } + } + + // The value of the System Property defines a list of enabled named + // groups in preference order, separated with comma. For example: + // + // jdk.tls.namedGroups="secp521r1, secp256r1, ffdhe2048" + // + // If the System Property is not defined or the value is empty, the + // default groups and preferences will be used. + private static NamedGroup[] getCustomizedNamedGroups() { String property = System.getProperty("jdk.tls.namedGroups"); + if (property != null && !property.isEmpty()) { // remove double quote marks from beginning/end of the property if (property.length() > 1 && property.charAt(0) == '"' && @@ -760,63 +950,25 @@ enum NamedGroup { } } - ArrayList groupList; if (property != null && !property.isEmpty()) { - String[] groups = property.split(","); - groupList = new ArrayList<>(groups.length); - for (String group : groups) { - group = group.trim(); - if (!group.isEmpty()) { - NamedGroup namedGroup = nameOf(group); - if (namedGroup != null) { - if (namedGroup.isAvailable) { - groupList.add(namedGroup.name); - } - } // ignore unknown groups - } - } + NamedGroup[] ret = Arrays.stream(property.split(",")) + .map(String::trim) + .map(NamedGroup::nameOf) + .filter(Objects::nonNull) + .filter(ng -> ng.isAvailable) + .toArray(NamedGroup[]::new); - if (groupList.isEmpty()) { + if (ret.length == 0) { throw new IllegalArgumentException( "System property jdk.tls.namedGroups(" + - property + ") contains no supported named groups"); - } - } else { // default groups - NamedGroup[] groups = new NamedGroup[] { - - // Primary XDH (RFC 7748) curves - X25519, - - // Primary NIST Suite B curves - SECP256_R1, - SECP384_R1, - SECP521_R1, - - // Secondary XDH curves - X448, - - // FFDHE (RFC 7919) - FFDHE_2048, - FFDHE_3072, - FFDHE_4096, - FFDHE_6144, - FFDHE_8192, - }; - - groupList = new ArrayList<>(groups.length); - for (NamedGroup group : groups) { - if (group.isAvailable) { - groupList.add(group.name); - } + property + + ") contains no supported named groups"); } - if (groupList.isEmpty() && - SSLLogger.isOn() && SSLLogger.isOn("ssl")) { - SSLLogger.warning("No default named groups"); - } + return ret; } - namedGroups = groupList.toArray(new String[0]); + return null; } } } diff --git a/src/java.base/share/classes/sun/security/ssl/SSLConfiguration.java b/src/java.base/share/classes/sun/security/ssl/SSLConfiguration.java index ace60e41af9..3c68c669d05 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLConfiguration.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLConfiguration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, 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 @@ -64,9 +64,6 @@ final class SSLConfiguration implements Cloneable { // the configured named groups for the "supported_groups" extensions String[] namedGroups; - // the maximum protocol version of enabled protocols - ProtocolVersion maximumProtocolVersion; - // Configurations per SSLSocket or SSLEngine instance. boolean isClientMode; boolean enableSessionCreation; @@ -249,13 +246,6 @@ final class SSLConfiguration implements Cloneable { CustomizedServerSignatureSchemes.signatureSchemes : SupportedSigSchemes.DEFAULT; this.namedGroups = NamedGroup.SupportedGroups.namedGroups; - this.maximumProtocolVersion = ProtocolVersion.NONE; - for (ProtocolVersion pv : enabledProtocols) { - if (pv.compareTo(maximumProtocolVersion) > 0) { - this.maximumProtocolVersion = pv; - } - } - // Configurations per SSLSocket or SSLEngine instance. this.isClientMode = isClientMode; this.enableSessionCreation = true; @@ -323,13 +313,6 @@ final class SSLConfiguration implements Cloneable { sa = params.getProtocols(); if (sa != null) { this.enabledProtocols = ProtocolVersion.namesOf(sa); - - this.maximumProtocolVersion = ProtocolVersion.NONE; - for (ProtocolVersion pv : enabledProtocols) { - if (pv.compareTo(maximumProtocolVersion) > 0) { - this.maximumProtocolVersion = pv; - } - } } // otherwise, use the default values if (params.getNeedClientAuth()) { diff --git a/src/java.base/share/classes/sun/security/ssl/SSLKeyExchange.java b/src/java.base/share/classes/sun/security/ssl/SSLKeyExchange.java index 22a44590ce3..263308f0659 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLKeyExchange.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLKeyExchange.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2022, 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 @@ -570,7 +570,9 @@ final class SSLKeyExchange implements SSLKeyAgreementGenerator, @Override public SSLPossession createPossession(HandshakeContext hc) { - return namedGroup.createPossession(hc.sslContext.getSecureRandom()); + return namedGroup.createPossession( + hc instanceof ClientHandshakeContext, + hc.sslContext.getSecureRandom()); } @Override diff --git a/src/java.base/share/classes/sun/security/ssl/ServerHello.java b/src/java.base/share/classes/sun/security/ssl/ServerHello.java index 76c266a628a..0567c861e18 100644 --- a/src/java.base/share/classes/sun/security/ssl/ServerHello.java +++ b/src/java.base/share/classes/sun/security/ssl/ServerHello.java @@ -565,6 +565,34 @@ final class ServerHello { clientHello); shc.serverHelloRandom = shm.serverRandom; + // For key derivation, we will either use the traditional Key + // Agreement (KA) model or the Key Encapsulation Mechanism (KEM) + // model, depending on what key exchange group is used. + // + // For KA flows, the server first receives the client's share, + // then generates its key share, and finally comes here. + // However, this is changed for KEM: the server + // must perform both actions — derive the secret and generate + // the key encapsulation message at the same time during + // encapsulation in SHKeyShareProducer. + // + // Traditional Key Agreement (KA): + // - Both peers generate a key share and exchange it. + // - Each peer computes a shared secret sometime after + // receiving the other's key share. + // + // Key Encapsulation Mechanism (KEM): + // The client publishes a public key via a KeyShareExtension, + // which the server uses to: + // + // - generate the shared secret + // - encapsulate the message which is sent to the client in + // another KeyShareExtension + // + // The derived shared secret must be stored in a + // KEMSenderPossession so it can be retrieved for handshake + // traffic secret derivation later. + // Produce extensions for ServerHello handshake message. SSLExtension[] serverHelloExtensions = shc.sslConfig.getEnabledExtensions( @@ -590,9 +618,26 @@ final class ServerHello { "Not negotiated key shares"); } - SSLKeyDerivation handshakeKD = ke.createKeyDerivation(shc); - SecretKey handshakeSecret = handshakeKD.deriveKey( - "TlsHandshakeSecret"); + SecretKey handshakeSecret = null; + + // For KEM, the shared secret has already been generated and + // stored in the server’s possession (KEMSenderPossession) + // during encapsulation in SHKeyShareProducer. + // + // Only one key share is selected by the server, so at most one + // possession will contain the pre-derived shared secret. + for (var pos : shc.handshakePossessions) { + if (pos instanceof KEMKeyExchange.KEMSenderPossession xp) { + handshakeSecret = xp.getKey(); + break; + } + } + + if (handshakeSecret == null) { + SSLKeyDerivation handshakeKD = ke.createKeyDerivation(shc); + handshakeSecret = handshakeKD.deriveKey( + "TlsHandshakeSecret"); + } SSLTrafficKeyDerivation kdg = SSLTrafficKeyDerivation.valueOf(shc.negotiatedProtocol); diff --git a/src/java.base/share/classes/sun/security/ssl/TransportContext.java b/src/java.base/share/classes/sun/security/ssl/TransportContext.java index 980d9c4a6ce..35bdd2fff36 100644 --- a/src/java.base/share/classes/sun/security/ssl/TransportContext.java +++ b/src/java.base/share/classes/sun/security/ssl/TransportContext.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, 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 @@ -144,7 +144,6 @@ final class TransportContext implements ConnectionContext { // initial security parameters this.conSession = new SSLSessionImpl(); - this.protocolVersion = this.sslConfig.maximumProtocolVersion; this.clientVerifyData = emptyByteArray; this.serverVerifyData = emptyByteArray; diff --git a/src/java.base/share/classes/sun/security/tools/keytool/Main.java b/src/java.base/share/classes/sun/security/tools/keytool/Main.java index 9fb830da338..7f415da5270 100644 --- a/src/java.base/share/classes/sun/security/tools/keytool/Main.java +++ b/src/java.base/share/classes/sun/security/tools/keytool/Main.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, 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 @@ -1294,7 +1294,7 @@ public final class Main { } if (alias != null) { - doPrintEntry(rb.getString("the.certificate"), alias, out); + doPrintEntry(alias, out); } else { doPrintEntries(out); } @@ -2177,9 +2177,10 @@ public final class Main { /** * Prints a single keystore entry. */ - private void doPrintEntry(String label, String alias, PrintStream out) + private void doPrintEntry(String alias, PrintStream out) throws Exception { + String label = "<" + alias + ">"; CertPathConstraintsParameters cpcp; if (!keyStore.containsAlias(alias)) { MessageFormat form = new MessageFormat @@ -2631,7 +2632,7 @@ public final class Main { List aliases = Collections.list(keyStore.aliases()); aliases.sort(String::compareTo); for (String alias : aliases) { - doPrintEntry("<" + alias + ">", alias, out); + doPrintEntry(alias, out); if (verbose || rfc) { out.println(rb.getString("NEWLINE")); out.println(rb.getString diff --git a/src/java.base/share/classes/sun/security/util/CryptoAlgorithmConstraints.java b/src/java.base/share/classes/sun/security/util/CryptoAlgorithmConstraints.java index a8649106a38..ad3beab350f 100644 --- a/src/java.base/share/classes/sun/security/util/CryptoAlgorithmConstraints.java +++ b/src/java.base/share/classes/sun/security/util/CryptoAlgorithmConstraints.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, 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 @@ -82,8 +82,10 @@ public class CryptoAlgorithmConstraints extends AbstractAlgorithmConstraints { CryptoAlgorithmConstraints(String propertyName) { super(null); disabledServices = getAlgorithms(propertyName, true); - debug("Before " + Arrays.deepToString(disabledServices.toArray())); - for (String dk : disabledServices) { + String[] entries = disabledServices.toArray(new String[0]); + debug("Before " + Arrays.deepToString(entries)); + + for (String dk : entries) { int idx = dk.indexOf("."); if (idx < 1 || idx == dk.length() - 1) { // wrong syntax: missing "." or empty service or algorithm diff --git a/src/java.base/share/classes/sun/security/util/Password.java b/src/java.base/share/classes/sun/security/util/Password.java index e358bcd95de..02cdcaf53fd 100644 --- a/src/java.base/share/classes/sun/security/util/Password.java +++ b/src/java.base/share/classes/sun/security/util/Password.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, 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 @@ -32,6 +32,7 @@ import java.util.Arrays; import jdk.internal.access.SharedSecrets; import jdk.internal.io.JdkConsoleImpl; +import jdk.internal.misc.VM; /** * A utility class for reading passwords @@ -65,7 +66,9 @@ public class Password { } consoleBytes = ConsoleHolder.convertToBytes(consoleEntered); in = new ByteArrayInputStream(consoleBytes); - } else if (System.in.available() == 0) { + } else if (in == System.in && VM.isBooted() + && System.in.available() == 0) { + // Warn if reading password from System.in but it's empty. // This may be running in an IDE Run Window or in JShell, // which acts like an interactive console and echoes the // entered password. In this case, print a warning that @@ -73,6 +76,7 @@ public class Password { // it's more likely the input comes from a pipe, such as // "echo password |" or "cat password_file |" where input // will be silently consumed without echoing to the screen. + // Warn only if VM is booted and ResourcesMgr is available. System.err.print(ResourcesMgr.getString ("warning.input.may.be.visible.on.screen")); } diff --git a/src/java.base/share/classes/sun/security/util/Pem.java b/src/java.base/share/classes/sun/security/util/Pem.java index a9b2908bcc9..c49dcaa912f 100644 --- a/src/java.base/share/classes/sun/security/util/Pem.java +++ b/src/java.base/share/classes/sun/security/util/Pem.java @@ -59,6 +59,9 @@ public class Pem { // Pattern matching for stripping whitespace. private static final Pattern STRIP_WHITESPACE_PATTERN; + // Pattern matching for inserting line breaks. + private static final Pattern LINE_WRAP_64_PATTERN; + // Lazy initialized PBES2 OID value private static ObjectIdentifier PBES2OID; @@ -72,6 +75,7 @@ public class Pem { PBE_PATTERN = Pattern.compile("^PBEWith.*And.*", Pattern.CASE_INSENSITIVE); STRIP_WHITESPACE_PATTERN = Pattern.compile("\\s+"); + LINE_WRAP_64_PATTERN = Pattern.compile("(.{64})"); } public static final String CERTIFICATE = "CERTIFICATE"; @@ -359,7 +363,7 @@ public class Pem { * @return PEM in a string */ public static String pemEncoded(PEM pem) { - String p = pem.content().replaceAll("(.{64})", "$1\r\n"); + String p = LINE_WRAP_64_PATTERN.matcher(pem.content()).replaceAll("$1\r\n"); return pemEncoded(pem.type(), p); } diff --git a/src/java.base/share/classes/sun/security/validator/CADistrustPolicy.java b/src/java.base/share/classes/sun/security/validator/CADistrustPolicy.java index 9c64402c123..44c9ed2d075 100644 --- a/src/java.base/share/classes/sun/security/validator/CADistrustPolicy.java +++ b/src/java.base/share/classes/sun/security/validator/CADistrustPolicy.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, 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 @@ -83,6 +83,22 @@ enum CADistrustPolicy { } CamerfirmaTLSPolicy.checkDistrust(chain); } + }, + + /** + * Distrust TLS Server certificates anchored by the Chunghwa ePKI root CA + * and issued after March 17, 2026. If enabled, this policy is currently + * enforced by the PKIX and SunX509 TrustManager implementations + * of the SunJSSE provider implementation. + */ + CHUNGHWA_TLS { + void checkDistrust(String variant, X509Certificate[] chain) + throws ValidatorException { + if (!variant.equals(Validator.VAR_TLS_SERVER)) { + return; + } + ChunghwaTLSPolicy.checkDistrust(chain); + } }; /** diff --git a/src/java.base/share/classes/sun/security/validator/ChunghwaTLSPolicy.java b/src/java.base/share/classes/sun/security/validator/ChunghwaTLSPolicy.java new file mode 100644 index 00000000000..114f5043fbf --- /dev/null +++ b/src/java.base/share/classes/sun/security/validator/ChunghwaTLSPolicy.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2026, 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. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * 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. + */ +package sun.security.validator; + +import java.security.cert.X509Certificate; +import java.time.LocalDate; +import java.time.Month; +import java.time.ZoneOffset; +import java.util.Date; +import java.util.Map; +import java.util.Set; + +import sun.security.util.Debug; +import sun.security.x509.X509CertImpl; + +/** + * This class checks if Chunghwa issued TLS Server certificates should be + * restricted. + */ +final class ChunghwaTLSPolicy { + + private static final Debug debug = Debug.getInstance("certpath"); + + // SHA-256 certificate fingerprint of distrusted root for TLS + // cacerts alias: chunghwaepkirootca + // DN: OU=ePKI Root Certification Authority, + // O="Chunghwa Telecom Co., Ltd.", C=TW + private static final String FINGERPRINT = + "C0A6F4DC63A24BFDCF54EF2A6A082A0A72DE35803E2FF5FF527AE5D87206DFD5"; + + // Any TLS Server certificate that is anchored by the Chunghwa + // root above and is issued after this date will be distrusted. + private static final LocalDate MARCH_17_2026 = + LocalDate.of(2026, Month.MARCH, 17); + + /** + * This method assumes the eeCert is a TLS Server Cert and chains back to + * the anchor. + * + * @param chain the end-entity's certificate chain. The end entity cert + * is at index 0, the trust anchor at index n-1. + * @throws ValidatorException if the certificate is distrusted + */ + static void checkDistrust(X509Certificate[] chain) + throws ValidatorException { + X509Certificate anchor = chain[chain.length-1]; + String fp = fingerprint(anchor); + if (fp == null) { + throw new ValidatorException("Cannot generate fingerprint for " + + "trust anchor of TLS server certificate"); + } + if (FINGERPRINT.equalsIgnoreCase(fp)) { + Date notBefore = chain[0].getNotBefore(); + LocalDate ldNotBefore = LocalDate.ofInstant(notBefore.toInstant(), + ZoneOffset.UTC); + // reject if certificate is issued after March 17, 2026 + checkNotBefore(ldNotBefore, MARCH_17_2026, anchor); + } + } + + private static String fingerprint(X509Certificate cert) { + return X509CertImpl.getFingerprint("SHA-256", cert, debug); + } + + // Check whether the certificate's notBeforeDate is after the + // distrust date for the anchor (root CA). Throw ValidatorException + // if it is after the distrust date. + private static void checkNotBefore(LocalDate notBeforeDate, + LocalDate distrustDate, X509Certificate anchor) + throws ValidatorException { + if (notBeforeDate.isAfter(distrustDate)) { + throw new ValidatorException + ("TLS Server certificate issued after " + distrustDate + + " and anchored by a distrusted legacy Chunghwa root CA: " + + anchor.getSubjectX500Principal(), + ValidatorException.T_UNTRUSTED_CERT, anchor); + } + } + + private ChunghwaTLSPolicy() {} +} diff --git a/src/java.base/share/classes/sun/security/x509/X509Key.java b/src/java.base/share/classes/sun/security/x509/X509Key.java index c83e06f651e..1cfe3f9d95d 100644 --- a/src/java.base/share/classes/sun/security/x509/X509Key.java +++ b/src/java.base/share/classes/sun/security/x509/X509Key.java @@ -104,6 +104,10 @@ public class X509Key implements PublicKey, DerEncoder { return (BitArray)bitStringKey.clone(); } + public byte[] getKeyAsBytes() { + return bitStringKey.toByteArray(); + } + /** * Construct X.509 subject public key from a DER value. If * the runtime environment is configured with a specific class for diff --git a/src/java.base/share/conf/security/java.security b/src/java.base/share/conf/security/java.security index 3d26c1dcd96..9a81ba86268 100644 --- a/src/java.base/share/conf/security/java.security +++ b/src/java.base/share/conf/security/java.security @@ -1437,6 +1437,9 @@ jdk.sasl.disabledMechanisms= # CAMERFIRMA_TLS : Distrust TLS Server certificates anchored by # a Camerfirma root CA and issued after April 15, 2025. # +# CHUNGHWA_TLS : Distrust TLS Server certificates anchored by +# a Chunghwa root CA and issued after March 17, 2026. +# # Leading and trailing whitespace surrounding each value are ignored. # Unknown values are ignored. If the property is commented out or set to the # empty String, no policies are enforced. @@ -1448,7 +1451,8 @@ jdk.sasl.disabledMechanisms= # jdk.certpath.disabledAlgorithms; those restrictions are still enforced even # if this property is not enabled. # -jdk.security.caDistrustPolicies=SYMANTEC_TLS,ENTRUST_TLS,CAMERFIRMA_TLS +jdk.security.caDistrustPolicies=SYMANTEC_TLS,ENTRUST_TLS,CAMERFIRMA_TLS,\ + CHUNGHWA_TLS # # FilePermission path canonicalization @@ -1651,3 +1655,48 @@ jdk.tls.alpnCharset=ISO_8859_1 # withEncryption method. # jdk.epkcs8.defaultAlgorithm=PBEWithHmacSHA256AndAES_128 + +# +# X.509 AuthorityInfoAccess caIssuer URI Filtering +# +# This property defines a whitespace-separated list of filters that +# are applied to URIs found in the authorityInfoAccess extension in +# X.509 certificates. Any caIssuers URIs in X.509 certificates are only +# followed when the com.sun.security.enableAIAcaIssuers System property is +# enabled and the filter allows the URI. By default this property imposes a +# deny-all ruleset. This property may be overridden by a System property +# of the same name. +# +# The filters must take the form of absolute, hierarchical URIs as defined by +# the java.net.URI class. Additionally, only the following protocols are +# allowed as filters: http, https, ldap and ftp. +# See RFC 5280, section 4.2.2.1 for details about the types of URIs allowed for +# the extension and their specific requirements. +# The filter matching rules are applied to each CA issuer URI as follows: +# 1. The scheme must match (case-insensitive). +# 2. A hostname or address must be specified in the filter URI. It must match +# the host or address in the caIssuers URI (case-insensitive). No name +# resolution is performed on hostnames to match IP addresses. +# 3. The port number must match. For filter and caIssuer URIs, when a port +# number is omitted, the well-known port for that scheme will be used in the +# comparison. +# 4. For hierarchical filesystem schemes (e.g. http[s], ftp): +# a. The normalized path portion of the filter URI is matched in a +# case-sensitive manner. If the final component of the path does not end +# in a slash (/), it is considered to be a file path component and must +# be an exact match of the caIssuer's URI file path component. If the +# final filter component ends in a slash, then it must either match or be +# a prefix of the caIssuer's URI path component (e.g. a filter path of +# /ab/cd/ will match a caIssuer path of /ab/cd/, /ab/cd/ef and +# /ab/cd/ef/ghi). +# b. Query strings will be ignored in filter rules and caIssuer URIs. +# c. Fragments will be ignored in filter rules and caIssuer URIs. +# 5. For ldap URIs: +# a. The base DN must be an exact match (case-insensitive). +# b. Any query string in the rule, if specified, is ignored. +# 6. A single value "any" (case-insensitive) will create an allow-all rule. +# +# As an example, here is a valid filter policy consisting of two rules: +# com.sun.security.allowedAIALocations=http://some.company.com/cacert \ +# ldap://ldap.company.com/dc=company,dc=com?caCertificate;binary +com.sun.security.allowedAIALocations= diff --git a/src/java.base/share/data/tzdata/VERSION b/src/java.base/share/data/tzdata/VERSION index 4bd54efbcdc..ce25e7653b0 100644 --- a/src/java.base/share/data/tzdata/VERSION +++ b/src/java.base/share/data/tzdata/VERSION @@ -21,4 +21,4 @@ # or visit www.oracle.com if you need additional information or have any # questions. # -tzdata2025b +tzdata2025c diff --git a/src/java.base/share/data/tzdata/africa b/src/java.base/share/data/tzdata/africa index 8098f4bea9d..cf1247ee3ab 100644 --- a/src/java.base/share/data/tzdata/africa +++ b/src/java.base/share/data/tzdata/africa @@ -27,9 +27,9 @@ # 2009-05-17 by Arthur David Olson. # This file is by no means authoritative; if you think you know better, -# go ahead and edit the file (and please send any changes to -# tz@iana.org for general use in the future). For more, please see -# the file CONTRIBUTING in the tz distribution. +# go ahead and edit the file, and please send any changes to +# the public mailing list tz@iana.org for general use in the future. +# For more, please see the file CONTRIBUTING in the tz distribution. # From Paul Eggert (2018-05-27): # @@ -138,8 +138,9 @@ Zone Atlantic/Cape_Verde -1:34:04 - LMT 1912 Jan 01 2:00u # Praia -1:00 - %z # Chad +# Fort-Lamy was renamed to N’Djamena on 1973-04-06. # Zone NAME STDOFF RULES FORMAT [UNTIL] -Zone Africa/Ndjamena 1:00:12 - LMT 1912 Jan 1 # N'Djamena +Zone Africa/Ndjamena 1:00:12 - LMT 1912 Jan 1 # Fort-Lamy 1:00 - WAT 1979 Oct 14 1:00 1:00 WAST 1980 Mar 8 1:00 - WAT diff --git a/src/java.base/share/data/tzdata/antarctica b/src/java.base/share/data/tzdata/antarctica index 87787d31cfe..b3c186851f1 100644 --- a/src/java.base/share/data/tzdata/antarctica +++ b/src/java.base/share/data/tzdata/antarctica @@ -26,13 +26,10 @@ # This file is in the public domain, so clarified as of # 2009-05-17 by Arthur David Olson. -# From Paul Eggert (1999-11-15): -# To keep things manageable, we list only locations occupied year-round; see -# COMNAP - Stations and Bases -# http://www.comnap.aq/comnap/comnap.nsf/P/Stations/ -# and -# Summary of the Peri-Antarctic Islands (1998-07-23) -# http://www.spri.cam.ac.uk/bob/periant.htm +# From Paul Eggert (2025-08-16): +# To keep things manageable, list only locations occupied year-round; see +# Antarctic Facilities Information +# https://www.comnap.aq/antarctic-facilities-information # for information. # Unless otherwise specified, we have no time zone information. @@ -167,6 +164,7 @@ Zone Antarctica/Mawson 0 - -00 1954 Feb 13 # China - year-round bases # Great Wall, King George Island, -6213-05858, since 1985-02-20 # Zhongshan, Larsemann Hills, Prydz Bay, -6922+07623, since 1989-02-26 +# Qinling, Inexpressible I, Terra Nova Bay, -7456+16343, since 2024-02-07 # France - year-round bases (also see "France & Italy") # diff --git a/src/java.base/share/data/tzdata/asia b/src/java.base/share/data/tzdata/asia index 55b13134f9c..005abd01793 100644 --- a/src/java.base/share/data/tzdata/asia +++ b/src/java.base/share/data/tzdata/asia @@ -27,9 +27,9 @@ # 2009-05-17 by Arthur David Olson. # This file is by no means authoritative; if you think you know better, -# go ahead and edit the file (and please send any changes to -# tz@iana.org for general use in the future). For more, please see -# the file CONTRIBUTING in the tz distribution. +# go ahead and edit the file, and please send any changes to +# the public mailing list tz@iana.org for general use in the future. +# For more, please see the file CONTRIBUTING in the tz distribution. # From Paul Eggert (2019-07-11): # diff --git a/src/java.base/share/data/tzdata/australasia b/src/java.base/share/data/tzdata/australasia index d659d1fb4b1..638789f42da 100644 --- a/src/java.base/share/data/tzdata/australasia +++ b/src/java.base/share/data/tzdata/australasia @@ -960,9 +960,9 @@ Zone Pacific/Efate 11:13:16 - LMT 1912 Jan 13 # Vila # NOTES # This file is by no means authoritative; if you think you know better, -# go ahead and edit the file (and please send any changes to -# tz@iana.org for general use in the future). For more, please see -# the file CONTRIBUTING in the tz distribution. +# go ahead and edit the file, and please send any changes to +# the public mailing list tz@iana.org for general use in the future. +# For more, please see the file CONTRIBUTING in the tz distribution. # From Paul Eggert (2018-11-18): # @@ -2011,6 +2011,7 @@ Zone Pacific/Efate 11:13:16 - LMT 1912 Jan 13 # Vila # From Paul Eggert (2018-11-19): # The 1921-01-15 introduction of standard time is in Shanks; it is also in # "Standard Time Throughout the World", US National Bureau of Standards (1935), +# https://nvlpubs.nist.gov/nistpubs/Legacy/circ/nbscircular406.pdf # page 3, which does not give the UT offset. In response to a comment by # Phake Nick I set the Nauru time of occupation by Japan to # 1942-08-29/1945-09-08 by using dates from: @@ -2078,9 +2079,10 @@ Zone Pacific/Efate 11:13:16 - LMT 1912 Jan 13 # Vila # https://webspace.science.uu.nl/~gent0113/idl/idl_alaska_samoa.htm # Although Shanks & Pottenger says they both switched to UT -11:30 -# in 1911, and to -11 in 1950. many earlier sources give -11 +# in 1911, and to -11 in 1950, many earlier sources give -11 # for American Samoa, e.g., the US National Bureau of Standards # circular "Standard Time Throughout the World", 1932. +# https://nvlpubs.nist.gov/nistpubs/Legacy/circ/nbscircular399.pdf # Assume American Samoa switched to -11 in 1911, not 1950, # and that after 1950 they agreed until (western) Samoa skipped a # day in 2011. Assume also that the Samoas follow the US and New diff --git a/src/java.base/share/data/tzdata/europe b/src/java.base/share/data/tzdata/europe index 7ba6c679609..b82ca6f67bb 100644 --- a/src/java.base/share/data/tzdata/europe +++ b/src/java.base/share/data/tzdata/europe @@ -27,9 +27,9 @@ # 2009-05-17 by Arthur David Olson. # This file is by no means authoritative; if you think you know better, -# go ahead and edit the file (and please send any changes to -# tz@iana.org for general use in the future). For more, please see -# the file CONTRIBUTING in the tz distribution. +# go ahead and edit the file, and please send any changes to +# the public mailing list tz@iana.org for general use in the future. +# For more, please see the file CONTRIBUTING in the tz distribution. # From Paul Eggert (2017-02-10): # @@ -65,7 +65,7 @@ # . He writes: # "It is requested that corrections and additions to these tables # may be sent to Mr. John Milne, Royal Geographical Society, -# Savile Row, London." Nowadays please email them to tz@iana.org. +# Savile Row, London." Nowadays please see the file CONTRIBUTING. # # Byalokoz EL. New Counting of Time in Russia since July 1, 1919. # This Russian-language source was consulted by Vladimir Karpinsky; see @@ -77,7 +77,7 @@ # Десятая гос. тип., 1919. # http://resolver.gpntb.ru/purl?docushare/dsweb/Get/Resource-2011/Byalokoz__E.L.__Novyy__schet__vremeni__v__techenie__sutok__izd__2(1).pdf # -# Brazil's Divisão Serviço da Hora (DSHO), +# Brazil's Divisão de Serviços da Hora (DISHO) # History of Summer Time # # (1998-09-21, in Portuguese) @@ -937,7 +937,7 @@ Rule Belgium 1922 1927 - Oct Sat>=1 23:00s 0 - Rule Belgium 1923 only - Apr 21 23:00s 1:00 S Rule Belgium 1924 only - Mar 29 23:00s 1:00 S Rule Belgium 1925 only - Apr 4 23:00s 1:00 S -# DSH writes that a royal decree of 1926-02-22 specified the Sun following 3rd +# DISHO writes that a royal decree of 1926-02-22 specified the Sun following 3rd # Sat in Apr (except if it's Easter, in which case it's one Sunday earlier), # to Sun following 1st Sat in Oct, and that a royal decree of 1928-09-15 # changed the transition times to 02:00 GMT. @@ -1333,6 +1333,13 @@ Zone Europe/Helsinki 1:39:49 - LMT 1878 May 31 # France # Monaco +# From Robert H. van Gent (2025-07-21): +# The most recent issue of the Annuaire [par le Bureau des Longitudes] +# on Gallica (2021) ... lists information for France +# https://gallica.bnf.fr/ark:/12148/bpt6k9127672b/f52.item +# From Paul Eggert (2025-07-21): +# Go with the 2020 Annuaire (published 2021) except as noted below. + # From Ciro Discepolo (2000-12-20): # # Henri Le Corre, Régimes horaires pour le monde entier, Éditions @@ -1394,7 +1401,6 @@ Zone Europe/Helsinki 1:39:49 - LMT 1878 May 31 # problems in Algiers, Monaco and Tunis. # -# Shank & Pottenger seem to use '24:00' ambiguously; resolve it with Whitman. # Rule NAME FROM TO - IN ON AT SAVE LETTER/S Rule France 1916 only - Jun 14 23:00s 1:00 S Rule France 1916 1919 - Oct Sun>=1 23:00s 0 - @@ -1406,9 +1412,25 @@ Rule France 1920 only - Oct 23 23:00s 0 - Rule France 1921 only - Mar 14 23:00s 1:00 S Rule France 1921 only - Oct 25 23:00s 0 - Rule France 1922 only - Mar 25 23:00s 1:00 S -# DSH writes that a law of 1923-05-24 specified 3rd Sat in Apr at 23:00 to 1st -# Sat in Oct at 24:00; and that in 1930, because of Easter, the transitions -# were Apr 12 and Oct 5. Go with Shanks & Pottenger. +# From Robert H. van Gent (2025-07-22): +# There is a curious history behind the erroneous date for the start of +# daylight saving in France in 1923 as listed in the current issues of +# the Annuaire du Bureau des Longitudes. [See:] +# https://lists.iana.org/hyperkitty/list/tz@iana.org/message/MYQEJMSXO2AIEZ3UIXZKMTTAIPY7KNT2/ +# From Brian Inglis (2025-07-23): +# Légifrance JORF No. 0073 du 15 mars 1922 +# https://www.legifrance.gouv.fr/jorf/jo/id/JORFCONT000000008324 +# Légifrance JORF No. 0139 du 25 mai 1923 +# https://www.legifrance.gouv.fr/jorf/jo/id/JORFCONT000000008416 +# From Paul Eggert (2025-07-23): +# The latter specifies March's last Saturday at 23:00 to October's first +# Saturday at 24:00, except that if neighboring allies agree the dates +# can be moved to April's third Saturday and September's third Saturday. +# Apparently spring 1923 was tricky. DISHO writes that in 1930, +# because of Easter, the transitions were Apr 12 and Oct 5. +# Use the 2020 Annuaire dates, except for spring 1923 where +# Shanks & Pottenger's May 26 matches the dates given in the 1924 and +# 1961-2001 issues of the Annuaire. Rule France 1922 1938 - Oct Sat>=1 23:00s 0 - Rule France 1923 only - May 26 23:00s 1:00 S Rule France 1924 only - Mar 29 23:00s 1:00 S @@ -2119,12 +2141,10 @@ Zone Europe/Warsaw 1:24:00 - LMT 1880 # all clocks therefore having to be advanced or set back correspondingly ... # Rule NAME FROM TO - IN ON AT SAVE LETTER/S -# From Tim Parenti (2024-07-01), per Paul Eggert (1999-01-30): -# DSH writes in their history that Decreto 1469 of 1915-03-30 established -# summer time and that, "despite" this, the change to the clocks was not done -# every year, depending on what Spain did, because of railroad schedules. -# In fact, that decree had nothing to do with DST; rather, it regulated the -# sending of time signals. But we do see linkage to Spain in the 1920s below. +# From Tim Parenti (2024-07-01): +# Decreto 1469 of 1915-03-30 ... had nothing to do with DST; +# rather it regulated the sending of time signals. +# But we do see linkage to Spain in the 1920s below. # https://dre.pt/dr/detalhe/decreto/1469-1915-285721 # https://dre.pt/application/conteudo/285721 # diff --git a/src/java.base/share/data/tzdata/iso3166.tab b/src/java.base/share/data/tzdata/iso3166.tab index 7fa350ecbe3..ddcc841fe97 100644 --- a/src/java.base/share/data/tzdata/iso3166.tab +++ b/src/java.base/share/data/tzdata/iso3166.tab @@ -26,22 +26,22 @@ # This file is in the public domain, so clarified as of # 2009-05-17 by Arthur David Olson. # -# From Paul Eggert (2023-09-06): +# From Paul Eggert (2025-07-01): # This file contains a table of two-letter country codes. Columns are -# separated by a single tab. Lines beginning with '#' are comments. +# separated by a single tab. Lines beginning with ‘#’ are comments. # All text uses UTF-8 encoding. The columns of the table are as follows: # # 1. ISO 3166-1 alpha-2 country code, current as of -# ISO/TC 46 N1108 (2023-04-05). See: ISO/TC 46 Documents +# ISO/TC 46 N1127 (2024-02-29). See: ISO/TC 46 Documents # https://www.iso.org/committee/48750.html?view=documents # 2. The usual English name for the coded region. This sometimes # departs from ISO-listed names, sometimes so that sorted subsets -# of names are useful (e.g., "Samoa (American)" and "Samoa -# (western)" rather than "American Samoa" and "Samoa"), +# of names are useful (e.g., “Samoa (American)” and “Samoa +# (western)” rather than “American Samoa” and “Samoa”), # sometimes to avoid confusion among non-experts (e.g., -# "Czech Republic" and "Turkey" rather than "Czechia" and "Türkiye"), -# and sometimes to omit needless detail or churn (e.g., "Netherlands" -# rather than "Netherlands (the)" or "Netherlands (Kingdom of the)"). +# “Czech Republic” and “Turkey” rather than “Czechia” and “Türkiye”), +# and sometimes to omit needless detail or churn (e.g., “Netherlands” +# rather than “Netherlands (the)” or “Netherlands (Kingdom of the)”). # # The table is sorted by country code. # @@ -94,7 +94,7 @@ CD Congo (Dem. Rep.) CF Central African Rep. CG Congo (Rep.) CH Switzerland -CI Côte d'Ivoire +CI Côte d’Ivoire CK Cook Islands CL Chile CM Cameroon diff --git a/src/java.base/share/data/tzdata/leapseconds b/src/java.base/share/data/tzdata/leapseconds index 042a32b052c..9426b40f07e 100644 --- a/src/java.base/share/data/tzdata/leapseconds +++ b/src/java.base/share/data/tzdata/leapseconds @@ -28,7 +28,8 @@ # This file is generated automatically from the data in the public-domain # NIST/IERS format leap-seconds.list file, which can be copied from # -# or, in a variant with different comments, from +# or via a less-secure protocol and with different comments and +# less volatile last-modified and expiration timestamps, from # . # For more about leap-seconds.list, please see # The NTP Timescale and Leap Seconds @@ -92,11 +93,17 @@ Leap 2016 Dec 31 23:59:60 + S # Any additional leap seconds will come after this. # This Expires line is commented out for now, # so that pre-2020a zic implementations do not reject this file. -#Expires 2025 Dec 28 00:00:00 +#Expires 2026 Jun 28 00:00:00 -# POSIX timestamps for the data in this file: -#updated 1736208000 (2025-01-07 00:00:00 UTC) -#expires 1766880000 (2025-12-28 00:00:00 UTC) +# Here are POSIX timestamps for the data in this file. +# "#updated" gives the last time the leap seconds data changed +# or, if this file was derived from the IERS leap-seconds.list, +# the last time that file changed in any way. +# "#expires" gives the first time this file might be wrong; +# if this file was derived from the IERS leap-seconds.list, +# this is typically a bit less than one year after "updated". +#updated 1751846400 (2025-07-07 00:00:00 UTC) +#expires 1782604800 (2026-06-28 00:00:00 UTC) # Updated through IERS Bulletin C (https://hpiers.obspm.fr/iers/bul/bulc/bulletinc.dat) -# File expires on 28 December 2025 +# File expires on 28 June 2026 diff --git a/src/java.base/share/data/tzdata/northamerica b/src/java.base/share/data/tzdata/northamerica index 21f178ee8cb..f20879e9063 100644 --- a/src/java.base/share/data/tzdata/northamerica +++ b/src/java.base/share/data/tzdata/northamerica @@ -29,9 +29,9 @@ # also includes Central America and the Caribbean # This file is by no means authoritative; if you think you know better, -# go ahead and edit the file (and please send any changes to -# tz@iana.org for general use in the future). For more, please see -# the file CONTRIBUTING in the tz distribution. +# go ahead and edit the file, and please send any changes to +# the public mailing list tz@iana.org for general use in the future. +# For more, please see the file CONTRIBUTING in the tz distribution. # From Paul Eggert (1999-03-22): # A reliable and entertaining source about time zones is @@ -1241,6 +1241,16 @@ Zone America/Menominee -5:50:27 - LMT 1885 Sep 18 12:00 # _Los Angeles Times_ (1998-11-10), A1, A10; it cites # Jimmy Skaggs, _The Great Guano Rush_ (1994). +# From Rob van Gent (2025-07-23): +# Another useful source for historical time zone information appears to be +# a series of circulars with the title "Standard Time Throughout the World" +# issued between 1925 and 1950 by the U.S. Bureau of Standards. +# I found the following issues online: +# https://nvlpubs.nist.gov/nistpubs/Legacy/circ/nbscircular280.pdf (1925) +# https://nvlpubs.nist.gov/nistpubs/Legacy/circ/nbscircular399.pdf (1932) +# https://nvlpubs.nist.gov/nistpubs/Legacy/circ/nbscircular406.pdf (1935) +# https://nvlpubs.nist.gov/nistpubs/Legacy/circ/nbscircular496.pdf (1950) + ################################################################################ @@ -2457,12 +2467,35 @@ Zone America/Dawson -9:17:40 - LMT 1900 Aug 20 # on the same dates or with a difference of one day. # So it may be easier to implement these changes as DST with rule CA # during this whole period. + +# From Alois Treindl (2025-07-29): +# I did a quick newspaper archive research on https://hndm.iib.unam.mx/ +# and found that Periódico Oficial del Estado de Baja California Norte +# (1973-04-20) states clearly that DST was observed from last Sunday +# in April to last Sunday in October.... I have a few more data from the +# official bulletin for DST begin or end in Baja California 1964 1967 1969 +# 1972 1973 (already sent) 1974 1975 1976 I do not know whether it is safe to +# assume that it also applied in the years where I did not yet find proof. +# The 1974 end of DST contains a reference to an Acuerdo of 1973-dec-20 which +# I could not find.... One might assume that Baja California, which followed +# US-CA in all these other yours, did the same. # -# From Paul Eggert (2024-08-18): -# For now, maintain the slightly-different history for Baja California, +# From Paul Eggert (2025-08-04): +# Assume that Tijuana agreed with San Diego from 1953 through 1996, +# as this agrees with Alois Treindl's data and with Shanks. +# For now, keep the slightly-different 1948/1952 history for Baja California, # as we have no information on whether 1948/1952 clocks in Tijuana followed # the decrees or followed San Diego. +# From Mark Schapiro, writing in The Nation (2002-10-28): +# https://www.thenation.com/article/archive/sowing-disaster/ +# When Mexican clocks were turned back for daylight saving time in the spring, +# the Zapotecs refused to make the adjustment, insisting that they live in +# "God's time," not in what they derisively call "Fox time," referring to +# President Vicente Fox in far-off Mexico City. +# From Paul Eggert (2025-08-04): +# Unfortunately we have no data to track this informal practice. + # From Alan Perry (1996-02-15): # A guy from our Mexico subsidiary finally found the Presidential Decree # outlining the timezone changes in Mexico. @@ -2728,7 +2761,7 @@ Zone America/Mexico_City -6:36:36 - LMT 1922 Jan 1 7:00u # Chihuahua (near US border - western side) # This includes the municipios of Janos, Ascensión, Juárez, Guadalupe, and # Práxedis G Guerrero. -# http://gaceta.diputados.gob.mx/PDF/65/2a022/nov/20221124-VII.pdf +# https://gaceta.diputados.gob.mx/PDF/65/2022/nov/20221124-VII.pdf Zone America/Ciudad_Juarez -7:05:56 - LMT 1922 Jan 1 7:00u -7:00 - MST 1927 Jun 10 -6:00 - CST 1930 Nov 15 @@ -2743,7 +2776,7 @@ Zone America/Ciudad_Juarez -7:05:56 - LMT 1922 Jan 1 7:00u # Chihuahua (near US border - eastern side) # This includes the municipios of Coyame del Sotol, Ojinaga, and Manuel # Benavides. -# http://gaceta.diputados.gob.mx/PDF/65/2a022/nov/20221124-VII.pdf +# https://gaceta.diputados.gob.mx/PDF/65/2022/nov/20221124-VII.pdf Zone America/Ojinaga -6:57:40 - LMT 1922 Jan 1 7:00u -7:00 - MST 1927 Jun 10 -6:00 - CST 1930 Nov 15 @@ -2840,9 +2873,7 @@ Zone America/Tijuana -7:48:04 - LMT 1922 Jan 1 7:00u -8:00 1:00 PDT 1951 Sep 30 2:00 -8:00 - PST 1952 Apr 27 2:00 -8:00 1:00 PDT 1952 Sep 28 2:00 - -8:00 - PST 1954 - -8:00 CA P%sT 1961 - -8:00 - PST 1976 + -8:00 CA P%sT 1967 -8:00 US P%sT 1996 -8:00 Mexico P%sT 2001 -8:00 US P%sT 2002 Feb 20 diff --git a/src/java.base/share/data/tzdata/southamerica b/src/java.base/share/data/tzdata/southamerica index ca3c338591f..f932d5a72a1 100644 --- a/src/java.base/share/data/tzdata/southamerica +++ b/src/java.base/share/data/tzdata/southamerica @@ -27,9 +27,9 @@ # 2009-05-17 by Arthur David Olson. # This file is by no means authoritative; if you think you know better, -# go ahead and edit the file (and please send any changes to -# tz@iana.org for general use in the future). For more, please see -# the file CONTRIBUTING in the tz distribution. +# go ahead and edit the file, and please send any changes to +# the public mailing list tz@iana.org for general use in the future. +# For more, please see the file CONTRIBUTING in the tz distribution. # From Paul Eggert (2016-12-05): # diff --git a/src/java.base/share/man/java.md b/src/java.base/share/man/java.md index 30661a3f387..956a6aa144b 100644 --- a/src/java.base/share/man/java.md +++ b/src/java.base/share/man/java.md @@ -1,5 +1,5 @@ --- -# Copyright (c) 1994, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 1994, 2026, 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 @@ -61,7 +61,7 @@ To launch a source-file program: : Specifies the name of the class to be launched. Command-line entries following `classname` are the arguments for the main method. -`-jar` *jarfile* +[`-jar`]{#-jar} *jarfile* : Executes a program encapsulated in a JAR file. The *jarfile* argument is the name of a JAR file with a manifest that contains a line in the form `Main-Class:`*classname* that defines the class with the @@ -70,7 +70,7 @@ To launch a source-file program: is the source of all user classes, and other class path settings are ignored. If you're using JAR files, then see [jar](jar.html). -`-m` or `--module` *module*\[`/`*mainclass*\] +[`-m`]{#-m} or `--module` *module*\[`/`*mainclass*\] : Executes the main class in a module specified by *mainclass* if it is given, or, if it is not given, the value in the *module*. In other words, *mainclass* can be used when it is not specified by the module, or to @@ -370,7 +370,7 @@ the JVM. > **Note:** To specify an argument for a long option, you can use either `--`*name*`=`*value* or `--`*name* *value*. -`-agentlib:`*libname*\[`=`*options*\] +[`-agentlib:`]{#-agentlib}*libname*\[`=`*options*\] : Loads the specified native agent library. After the library name, a comma-separated list of options specific to the library can be used. If the option `-agentlib:foo` is specified, then the JVM attempts to @@ -393,12 +393,12 @@ the JVM. > `-agentlib:jdwp=transport=dt_socket,server=y,address=8000` -`-agentpath:`*pathname*\[`=`*options*\] +[`-agentpath:`]{#-agentpath}*pathname*\[`=`*options*\] : Loads the native agent library specified by the absolute path name. This option is equivalent to `-agentlib` but uses the full path and file name of the library. -`--class-path` *classpath*, `-classpath` *classpath*, or `-cp` *classpath* +[`--class-path`]{#--class-path} *classpath*, `-classpath` *classpath*, or `-cp` *classpath* : Specifies a list of directories, JAR files, and ZIP archives to search for class files. @@ -424,15 +424,15 @@ the JVM. expanded except by querying the environment, such as by calling `System.getenv("CLASSPATH")`. -`--disable-@files` +[`--disable-@files`]{#--disable-@files} : Can be used anywhere on the command line, including in an argument file, to prevent further `@filename` expansion. This option stops expanding `@`-argfiles after the option. -`--enable-preview` +[`--enable-preview`]{#--enable-preview} : Allows classes to depend on [preview features](https://docs.oracle.com/en/java/javase/12/language/index.html#JSLAN-GUID-5A82FE0E-0CA4-4F1F-B075-564874FE2823) of the release. -`--enable-native-access` *module*\[`,`*module*...\] +[`--enable-native-access`]{#--enable-native-access} *module*\[`,`*module*...\] : Native access involves access to code or data outside the Java runtime. This is generally unsafe and, if done incorrectly, might crash the JVM or result in memory corruption. Native access can occur as a result of calling a method that @@ -465,7 +465,7 @@ the JVM. run it with `--illegal-native-access=deny` along with any necessary `--enable-native-access` options. -`--enable-final-field-mutation` *module*\[,*module*...\] +[`--enable-final-field-mutation`]{#--enable-final-field-mutation} *module*\[,*module*...\] : Mutation of final fields is possible with the reflection API of the Java Platform. However, it compromises safety and performance in all programs. This option allows code in the specified modules to mutate final fields by reflection. @@ -498,13 +498,13 @@ the JVM. run it with `--illegal-final-field-mutation=deny` along with any necessary `--enable-final-field-mutation` options. -`--finalization=`*value* +[`--finalization=`]{#--finalization}*value* : Controls whether the JVM performs finalization of objects. Valid values are "enabled" and "disabled". Finalization is enabled by default, so the value "enabled" does nothing. The value "disabled" disables finalization, so that no finalizers are invoked. -`--module-path` *modulepath*... or `-p` *modulepath* +[`--module-path`]{#--module-path} *modulepath*... or `-p` *modulepath* : Specifies where to find application modules with a list of path elements. The elements of a module path can be a file path to a module or a directory containing modules. Each module is either a modular JAR or an @@ -513,7 +513,7 @@ the JVM. On Windows, semicolons (`;`) separate path elements in this list; on other platforms it is a colon (`:`). -`--upgrade-module-path` *modulepath*... +[`--upgrade-module-path`]{#--upgrade-module-path} *modulepath*... : Specifies where to find module replacements of upgradeable modules in the runtime image with a list of path elements. The elements of a module path can be a file path to a module or a directory @@ -523,33 +523,33 @@ the JVM. On Windows, semicolons (`;`) separate path elements in this list; on other platforms it is a colon (`:`). -`--add-modules` *module*\[`,`*module*...\] +[`--add-modules`]{#--add-modules} *module*\[`,`*module*...\] : Specifies the root modules to resolve in addition to the initial module. *module* can also be `ALL-DEFAULT`, `ALL-SYSTEM`, and `ALL-MODULE-PATH`. -`--list-modules` +[`--list-modules`]{#--list-modules} : Lists the observable modules and then exits. -`-d` *module\_name* or `--describe-module` *module\_name* +[`-d`]{#-d} *module\_name* or `--describe-module` *module\_name* : Describes a specified module and then exits. -`--dry-run` +[`--dry-run`]{#--dry-run} : Creates the VM but doesn't execute the main method. This `--dry-run` option might be useful for validating the command-line options such as the module system configuration. -`--validate-modules` +[`--validate-modules`]{#--validate-modules} : Validates all modules and exit. This option is helpful for finding conflicts and other errors with modules on the module path. -`-D`*property*`=`*value* +[`-D`]{#-D}*property*`=`*value* : Sets a system property value. The *property* variable is a string with no spaces that represents the name of the property. The *value* variable is a string that represents the value of the property. If *value* is a string with spaces, then enclose it in quotation marks (for example `-Dfoo="foo bar"`). -`-disableassertions`\[`:`\[*packagename*\]...\|`:`*classname*\] or `-da`\[`:`\[*packagename*\]...\|`:`*classname*\] +[`-disableassertions`]{#-disableassertions}\[`:`\[*packagename*\]...\|`:`*classname*\] or `-da`\[`:`\[*packagename*\]...\|`:`*classname*\] : Disables assertions. By default, assertions are disabled in all packages and classes. With no arguments, `-disableassertions` (`-da`) disables assertions in all packages and classes. With the *packagename* argument @@ -574,10 +574,10 @@ the JVM. > `java -ea:com.wombat.fruitbat... -da:com.wombat.fruitbat.Brickbat MyClass` -`-disablesystemassertions` or `-dsa` +[`-disablesystemassertions`]{#-disablesystemassertions} or `-dsa` : Disables assertions in all system classes. -`-enableassertions`\[`:`\[*packagename*\]...\|`:`*classname*\] or `-ea`\[`:`\[*packagename*\]...\|`:`*classname*\] +[`-enableassertions`]{#-enableassertions}\[`:`\[*packagename*\]...\|`:`*classname*\] or `-ea`\[`:`\[*packagename*\]...\|`:`*classname*\] : Enables assertions. By default, assertions are disabled in all packages and classes. With no arguments, `-enableassertions` (`-ea`) enables assertions in all packages and classes. With the *packagename* argument ending in @@ -604,28 +604,28 @@ the JVM. > `java -ea:com.wombat.fruitbat... -da:com.wombat.fruitbat.Brickbat MyClass` -`-enablesystemassertions` or `-esa` +[`-enablesystemassertions`]{#-enablesystemassertions} or `-esa` : Enables assertions in all system classes. -`-help`, `-h`, or `-?` +[`-help`]{#-help}, `-h`, or `-?` : Prints the help message to the error stream. -`--help` +[`--help`]{#--help} : Prints the help message to the output stream. -`-javaagent:`*jarpath*\[`=`*options*\] +[`-javaagent:`]{#-javaagent_}*jarpath*\[`=`*options*\] : Loads the specified Java programming language agent. See `java.lang.instrument`. -`--show-version` +[`--show-version`]{#--show-version} : Prints the product version to the output stream and continues. -`-showversion` +[`-showversion`]{#-showversion} : Prints the product version to the error stream and continues. -`--show-module-resolution` +[`--show-module-resolution`]{#--show-module-resolution} : Shows module resolution output during startup. -`-splash:`*imagepath* +[`-splash:`]{#-splash_}*imagepath* : Shows the splash screen with the image specified by *imagepath*. HiDPI scaled images are automatically supported and used if available. The unscaled image file name, such as `image.ext`, should always be passed as @@ -639,29 +639,29 @@ the JVM. See the SplashScreen API documentation for more information. -`-verbose:class` +[`-verbose:class`]{#-verbose_class} : Displays information about each loaded class. -`-verbose:gc` +[`-verbose:gc`]{#-verbose_gc} : Displays information about each garbage collection (GC) event. -`-verbose:jni` +[`-verbose:jni`]{#-verbose_jni} : Displays information about the use of native methods and other Java Native Interface (JNI) activity. -`-verbose:module` +[`-verbose:module`]{#-verbose_module} : Displays information about the modules in use. -`--version` +[`--version`]{#--version} : Prints product version to the output stream and exits. -`-version` +[`-version`]{#-version} : Prints product version to the error stream and exits. -`-X` +[`-X`]{#-X} : Prints the help on extra options to the error stream. -`--help-extra` +[`--help-extra`]{#--help-extra} : Prints the help on extra options to the output stream. `@`*argfile* @@ -688,7 +688,7 @@ the JVM. The following `java` options are general purpose options that are specific to the Java HotSpot Virtual Machine. -`-Xbatch` +[`-Xbatch`]{#-Xbatch} : Disables background compilation. By default, the JVM compiles the method as a background task, running the method in interpreter mode until the background compilation is finished. The `-Xbatch` flag disables background @@ -696,14 +696,14 @@ the Java HotSpot Virtual Machine. task until completed. This option is equivalent to `-XX:-BackgroundCompilation`. -`-Xbootclasspath/a:`*directories*\|*zip*\|*JAR-files* +[`-Xbootclasspath/a:`]{#-Xbootclasspath}*directories*\|*zip*\|*JAR-files* : Specifies a list of directories, JAR files, and ZIP archives to append to the end of the default bootstrap class path. On Windows, semicolons (`;`) separate entities in this list; on other platforms it is a colon (`:`). -`-Xcheck:jni` +[`-Xcheck:jni`]{#-Xcheck_jni} : Performs additional checks for Java Native Interface (JNI) functions. The following checks are considered indicative of significant problems @@ -739,22 +739,22 @@ the Java HotSpot Virtual Machine. Expect a performance degradation when this option is used. -`-Xcomp` +[`-Xcomp`]{#-Xcomp} : Testing mode to exercise JIT compilers. This option should not be used in production environments. -`-Xdebug` +[`-Xdebug`]{#-Xdebug} : Does nothing; deprecated for removal in a future release. -`-Xdiag` +[`-Xdiag`]{#-Xdiag} : Shows additional diagnostic messages. -`-Xint` +[`-Xint`]{#-Xint} : Runs the application in interpreted-only mode. Compilation to native code is disabled, and all bytecode is executed by the interpreter. The performance benefits offered by the just-in-time (JIT) compiler aren't present in this mode. -`-Xinternalversion` +[`-Xinternalversion`]{#-Xinternalversion} : Displays more detailed JVM version information than the `-version` option, and then exits. @@ -763,11 +763,11 @@ the Java HotSpot Virtual Machine. logging framework. See [Enable Logging with the JVM Unified Logging Framework]. -`-Xmixed` +[`-Xmixed`]{#-Xmixed} : Executes all bytecode by the interpreter except for hot methods, which are compiled to native code. On by default. Use `-Xint` to switch off. -`-Xmn` *size* +[`-Xmn`]{#-Xmn} *size* : Sets the initial and maximum size (in bytes) of the heap for the young generation (nursery) in the generational collectors. Append the letter `k` or `K` to indicate kilobytes, `m` or `M` to indicate megabytes, or @@ -793,7 +793,7 @@ the Java HotSpot Virtual Machine. the heap for the young generation, you can use `-XX:NewSize` to set the initial size and `-XX:MaxNewSize` to set the maximum size. -`-Xms` *size* +[`-Xms`]{#-Xms} *size* : Sets the minimum and the initial size (in bytes) of the heap. This value must be a multiple of 1024 and greater than 1 MB. Append the letter `k` or `K` to indicate kilobytes, `m` or `M` to indicate megabytes, or `g` or `G` @@ -815,7 +815,7 @@ the Java HotSpot Virtual Machine. initial heap size. If it appears after `-Xms` on the command line, then the initial heap size gets set to the value specified with `-XX:InitialHeapSize`. -`-Xmx` *size* +[`-Xmx`]{#-Xmx} *size* : Specifies the maximum size (in bytes) of the heap. This value must be a multiple of 1024 and greater than 2 MB. Append the letter `k` or `K` to indicate kilobytes, `m` or `M` to indicate megabytes, or `g` or `G` @@ -832,7 +832,7 @@ the Java HotSpot Virtual Machine. The `-Xmx` option is equivalent to `-XX:MaxHeapSize`. -`-Xnoclassgc` +[`-Xnoclassgc`]{#-Xnoclassgc} : Disables garbage collection (GC) of classes. This can save some GC time, which shortens interruptions during the application run. When you specify `-Xnoclassgc` at startup, the class objects in the application are left @@ -840,7 +840,7 @@ the Java HotSpot Virtual Machine. more memory being permanently occupied which, if not used carefully, throws an out-of-memory exception. -`-Xrs` +[`-Xrs`]{#-Xrs} : Reduces the use of operating system signals by the JVM. Shutdown hooks enable the orderly shutdown of a Java application by running user cleanup code (such as closing database connections) at shutdown, even if the JVM @@ -890,7 +890,7 @@ the Java HotSpot Virtual Machine. User code is responsible for causing shutdown hooks to run, for example, by calling `System.exit()` when the JVM is to be terminated. -`-Xshare:`*mode* +[`-Xshare:`]{#-Xshare_}*mode* : Sets the class data sharing (CDS) mode. Possible *mode* arguments for this option include the following: @@ -910,10 +910,10 @@ the Java HotSpot Virtual Machine. `off` : Do not attempt to use shared class data. -`-XshowSettings` +[`-XshowSettings`]{#-XshowSettings} : Shows all settings and then continues. -`-XshowSettings:`*category* +[`-XshowSettings:`]{#-XshowSettings_}*category* : Shows settings and continues. Possible *category* arguments for this option include the following: @@ -942,7 +942,7 @@ the Java HotSpot Virtual Machine. `system` : **Linux only:** Shows host system or container configuration and continues. -`-Xss` *size* +[`-Xss`]{#-Xss} *size* : Sets the thread stack size (in bytes). Append the letter `k` or `K` to indicate KB, `m` or `M` to indicate MB, or `g` or `G` to indicate GB. The actual size may be rounded up to a multiple of the system page size as @@ -970,32 +970,32 @@ the Java HotSpot Virtual Machine. This option is similar to `-XX:ThreadStackSize`. -`--add-reads` *module*`=`*target-module*(`,`*target-module*)\* +[`--add-reads`]{#--add-reads} *module*`=`*target-module*(`,`*target-module*)\* : Updates *module* to read the *target-module*, regardless of the module declaration. *target-module* can be `ALL-UNNAMED` to read all unnamed modules. -`--add-exports` *module*`/`*package*`=`*target-module*(`,`*target-module*)\* +[`--add-exports`]{#--add-exports} *module*`/`*package*`=`*target-module*(`,`*target-module*)\* : Updates *module* to export *package* to *target-module*, regardless of module declaration. *target-module* can be `ALL-UNNAMED` to export to all unnamed modules. -`--add-opens` *module*`/`*package*`=`*target-module*(`,`*target-module*)\* +[`--add-opens`]{#--add-opens} *module*`/`*package*`=`*target-module*(`,`*target-module*)\* : Updates *module* to open *package* to *target-module*, regardless of module declaration. -`--limit-modules` *module*\[`,`*module*...\] +[`--limit-modules`]{#--limit-modules} *module*\[`,`*module*...\] : Specifies the limit of the universe of observable modules. -`--patch-module` *module*`=`*file*(`;`*file*)\* +[`--patch-module`]{#--patch-module} *module*`=`*file*(`;`*file*)\* : Overrides or augments a module with classes and resources in JAR files or directories. -`--source` *version* +[`--source`]{#--source} *version* : Sets the version of the source in source-file mode. -`--sun-misc-unsafe-memory-access=` *value* +[`--sun-misc-unsafe-memory-access=`]{#--sun-misc-unsafe-memory-access} *value* : Allow or deny usage of unsupported API `sun.misc.Unsafe`. *value* is one of: `allow` @@ -1021,20 +1021,20 @@ the Java HotSpot Virtual Machine. The following extra options are macOS specific. -`-XstartOnFirstThread` +[`-XstartOnFirstThread`]{#-XstartOnFirstThread} : Runs the `main()` method on the first (AppKit) thread. -`-Xdock:name=`*application\_name* +[`-Xdock:name=`]{#-Xdock_name}*application\_name* : Overrides the default application name displayed in dock. -`-Xdock:icon=`*path\_to\_icon\_file* +[`-Xdock:icon=`]{#-Xdock_icon}*path\_to\_icon\_file* : Overrides the default icon displayed in dock. ## Advanced Options for Java These `java` options can be used to enable other advanced options. -`-XX:+UnlockDiagnosticVMOptions` +[`-XX:+UnlockDiagnosticVMOptions`]{#-XX__UnlockDiagnosticVMOptions} : Unlocks the options intended for diagnosing the JVM. By default, this option is disabled and diagnostic options aren't available. @@ -1046,7 +1046,7 @@ These `java` options can be used to enable other advanced options. of these options may be removed or their behavior changed without any warning. -`-XX:+UnlockExperimentalVMOptions` +[`-XX:+UnlockExperimentalVMOptions`]{#-XX__UnlockExperimentalVMOptions} : Unlocks the options that provide experimental features in the JVM. By default, this option is disabled and experimental features aren't available. @@ -1054,7 +1054,7 @@ These `java` options can be used to enable other advanced options. These `java` options control the runtime behavior of the Java HotSpot VM. -`-XX:ActiveProcessorCount=`*x* +[`-XX:ActiveProcessorCount=`]{#-XX_ActiveProcessorCount}*x* : Overrides the number of CPUs that the VM will use to calculate the size of thread pools it will use for various operations such as Garbage Collection and ForkJoinPool. @@ -1066,7 +1066,7 @@ These `java` options control the runtime behavior of the Java HotSpot VM. `-XX:-UseContainerSupport` for a description of enabling and disabling container support. -`-XX:AllocateHeapAt=`*path* +[`-XX:AllocateHeapAt=`]{#-XX_AllocateHeapAt}*path* : Takes a path to the file system and uses memory mapping to allocate the object heap on the memory device. Using this option enables the HotSpot VM to allocate the Java object heap on an alternative memory device, such as @@ -1084,7 +1084,7 @@ These `java` options control the runtime behavior of the Java HotSpot VM. The existing heap related flags (such as `-Xmx` and `-Xms`) and garbage-collection related flags continue to work as before. -`-XX:-CompactStrings` +[`-XX:-CompactStrings`]{#-XX__CompactStrings} : Disables the Compact Strings feature. By default, this option is enabled. When this option is enabled, Java Strings containing only single-byte characters are internally represented and stored as @@ -1107,7 +1107,7 @@ These `java` options control the runtime behavior of the Java HotSpot VM. In both of these scenarios, disabling Compact Strings makes sense. -`-XX:ErrorFile=`*filename* +[`-XX:ErrorFile=`]{#-XX_ErrorFile}*filename* : Specifies the path and file name to which error data is written when an irrecoverable error occurs. By default, this file is created in the current working directory and named `hs_err_pid`*pid*`.log` where *pid* is the @@ -1139,7 +1139,7 @@ These `java` options control the runtime behavior of the Java HotSpot VM. `TMP` environment variable; if that environment variable isn't defined, then the value of the `TEMP` environment variable is used. -`-XX:+ExtensiveErrorReports` +[`-XX:+ExtensiveErrorReports`]{#-XX__ExtensiveErrorReports} : Enables the reporting of more extensive error information in the `ErrorFile`. This option can be turned on in environments where maximal information is desired - even if the resulting logs may be quite large and/or contain @@ -1147,7 +1147,7 @@ These `java` options control the runtime behavior of the Java HotSpot VM. from release to release, and across different platforms. By default this option is disabled. -`-XX:FlightRecorderOptions=`*parameter*`=`*value* (or) `-XX:FlightRecorderOptions:`*parameter*`=`*value* +[`-XX:FlightRecorderOptions=`]{#-XX_FlightRecorderOptions}*parameter*`=`*value* (or) `-XX:FlightRecorderOptions:`*parameter*`=`*value* : Sets the parameters that control the behavior of JFR. Multiple parameters can be specified by separating them with a comma. @@ -1207,7 +1207,7 @@ These `java` options control the runtime behavior of the Java HotSpot VM. 4 kilobytes. Overriding this parameter could reduce performance and is not recommended. -`-XX:LargePageSizeInBytes=`*size* +[`-XX:LargePageSizeInBytes=`]{#-XX_LargePageSizeInBytes}*size* : Sets the maximum large page size (in bytes) used by the JVM. The *size* argument must be a valid page size supported by the environment to have any effect. Append the letter `k` or `K` to indicate kilobytes, @@ -1221,7 +1221,7 @@ These `java` options control the runtime behavior of the Java HotSpot VM. > `-XX:LargePageSizeInBytes=1g` -`-XX:MaxDirectMemorySize=`*size* +[`-XX:MaxDirectMemorySize=`]{#-XX_MaxDirectMemorySize}*size* : Sets the maximum total size (in bytes) of the `java.nio` package, direct-buffer allocations. Append the letter `k` or `K` to indicate kilobytes, `m` or `M` to indicate megabytes, or `g` or `G` to indicate @@ -1237,14 +1237,14 @@ These `java` options control the runtime behavior of the Java HotSpot VM. -XX:MaxDirectMemorySize=1048576 ``` -`-XX:-MaxFDLimit` +[`-XX:-MaxFDLimit`]{#-XX__MaxFDLimit} : Disables the attempt to set the soft limit for the number of open file descriptors to the hard limit. By default, this option is enabled on all platforms, but is ignored on Windows. The only time that you may need to disable this is on macOS, where its use imposes a maximum of 10240, which is lower than the actual system maximum. -`-XX:NativeMemoryTracking=`*mode* +[`-XX:NativeMemoryTracking=`]{#-XX_NativeMemoryTracking}*mode* : Specifies the mode for tracking JVM native memory usage. Possible *mode* arguments for this option include the following: @@ -1261,7 +1261,7 @@ These `java` options control the runtime behavior of the Java HotSpot VM. usage by individual `CallSite`, individual virtual memory region and its committed regions. -`-XX:TrimNativeHeapInterval=`*millis* +[`-XX:TrimNativeHeapInterval=`]{#-XX_TrimNativeHeapInterval}*millis* : Interval, in ms, at which the JVM will trim the native heap. Lower values will reclaim memory more eagerly at the cost of higher overhead. A value of 0 (default) disables native heap trimming. @@ -1269,7 +1269,7 @@ These `java` options control the runtime behavior of the Java HotSpot VM. This option is only supported on Linux with GNU C Library (glibc). -`-XX:ObjectAlignmentInBytes=`*alignment* +[`-XX:ObjectAlignmentInBytes=`]{#-XX_ObjectAlignmentInBytes}*alignment* : Sets the memory alignment of Java objects (in bytes). By default, the value is set to 8 bytes. The specified value should be a power of 2, and must be within the range of 8 and 256 (inclusive). This option makes it possible to @@ -1283,7 +1283,7 @@ These `java` options control the runtime behavior of the Java HotSpot VM. increases. As a result, you may not realize any benefits from using compressed pointers with large Java heap sizes. -`-XX:OnError=`*string* +[`-XX:OnError=`]{#-XX_OnError}*string* : Sets a custom command or a series of semicolon-separated commands to run when an irrecoverable error occurs. If the string contains spaces, then it must be enclosed in quotation marks. @@ -1304,7 +1304,7 @@ These `java` options control the runtime behavior of the Java HotSpot VM. > `-XX:OnError="userdump.exe %p"` -`-XX:OnOutOfMemoryError=`*string* +[`-XX:OnOutOfMemoryError=`]{#-XX_OnOutOfMemoryError}*string* : Sets a custom command or a series of semicolon-separated commands to run when an `OutOfMemoryError` exception is first thrown by the JVM. If the string @@ -1316,31 +1316,31 @@ These `java` options control the runtime behavior of the Java HotSpot VM. directly from Java code, nor by the JVM for other types of resource exhaustion (such as native thread creation errors). -`-XX:+PrintCommandLineFlags` +[`-XX:+PrintCommandLineFlags`]{#-XX__PrintCommandLineFlags} : Enables printing of ergonomically selected JVM flags that appeared on the command line. It can be useful to know the ergonomic values set by the JVM, such as the heap space size and the selected garbage collector. By default, this option is disabled and flags aren't printed. -`-XX:+PreserveFramePointer` +[`-XX:+PreserveFramePointer`]{#-XX__PreserveFramePointer} : Selects between using the RBP register as a general purpose register (`-XX:-PreserveFramePointer`) and using the RBP register to hold the frame pointer of the currently executing method (`-XX:+PreserveFramePointer`). If the frame pointer is available, then external profiling tools (for example, Linux perf) can construct more accurate stack traces. -`-XX:+PrintNMTStatistics` +[`-XX:+PrintNMTStatistics`]{#-XX__PrintNMTStatistics} : Enables printing of collected native memory tracking data at JVM exit when native memory tracking is enabled (see `-XX:NativeMemoryTracking`). By default, this option is disabled and native memory tracking data isn't printed. -`-XX:SharedArchiveFile=`*path* +[`-XX:SharedArchiveFile=`]{#-XX_SharedArchiveFile}*path* : Specifies the path and name of the class data sharing (CDS) archive file See [Application Class Data Sharing]. -`-XX:+VerifySharedSpaces` +[`-XX:+VerifySharedSpaces`]{#-XX__VerifySharedSpaces} : If this option is specified, the JVM will load a CDS archive file only if it passes an integrity check based on CRC32 checksums. The purpose of this flag is to check for unintentional damage to CDS archive files in transmission or storage. @@ -1348,10 +1348,10 @@ These `java` options control the runtime behavior of the Java HotSpot VM. ensure that the CDS archive files used by Java applications cannot be modified without proper authorization. -`-XX:SharedArchiveConfigFile=`*shared\_config\_file* +[`-XX:SharedArchiveConfigFile=`]{#-XX_SharedArchiveConfigFile}*shared\_config\_file* : Specifies additional shared data added to the archive file. -`-XX:SharedClassListFile=`*file\_name* +[`-XX:SharedClassListFile=`]{#-XX_SharedClassListFile}*file\_name* : Specifies the text file that contains the names of the classes to store in the class data sharing (CDS) archive. This file contains the full name of one class per line, except slashes (`/`) replace dots (`.`). For example, @@ -1369,7 +1369,7 @@ These `java` options control the runtime behavior of the Java HotSpot VM. See [Application Class Data Sharing]. -`-XX:+ShowCodeDetailsInExceptionMessages` +[`-XX:+ShowCodeDetailsInExceptionMessages`]{#-XX__ShowCodeDetailsInExceptionMessages} : Enables printing of improved `NullPointerException` messages. When an application throws a `NullPointerException`, the option enables the JVM to analyze the program's bytecode instructions to determine precisely which reference is `null`, @@ -1378,13 +1378,13 @@ These `java` options control the runtime behavior of the Java HotSpot VM. and will be printed as the exception message along with the method, filename, and line number. By default, this option is enabled. -`-XX:+ShowMessageBoxOnError` +[`-XX:+ShowMessageBoxOnError`]{#-XX__ShowMessageBoxOnError} : Enables the display of a dialog box when the JVM experiences an irrecoverable error. This prevents the JVM from exiting and keeps the process active so that you can attach a debugger to it to investigate the cause of the error. By default, this option is disabled. -`-XX:StartFlightRecording:`*parameter*`=`*value* +[`-XX:StartFlightRecording:`]{#-XX_StartFlightRecording_}*parameter*`=`*value* : Starts a JFR recording for the Java application. This option is equivalent to the `JFR.start` diagnostic command that starts a recording during runtime. `-XX:StartFlightRecording:help` prints available options and @@ -1503,7 +1503,7 @@ These `java` options control the runtime behavior of the Java HotSpot VM. To only see warnings and errors from JFR during startup set -Xlog:jfr+startup=warning. -`-XX:ThreadStackSize=`*size* +[`-XX:ThreadStackSize=`]{#-XX_ThreadStackSize}*size* : Sets the Java thread stack size (in kilobytes). Use of a scaling suffix, such as `k`, results in the scaling of the kilobytes value so that `-XX:ThreadStackSize=1k` sets the Java thread stack size to 1024\*1024 @@ -1529,7 +1529,7 @@ These `java` options control the runtime behavior of the Java HotSpot VM. This option is similar to `-Xss`. -`-XX:+UseCompactObjectHeaders` +[`-XX:+UseCompactObjectHeaders`]{#-XX__UseCompactObjectHeaders} : Enables compact object headers. By default, this option is disabled. Enabling this option reduces memory footprint in the Java heap by 4 bytes per object (on average) and often improves performance. @@ -1538,7 +1538,7 @@ These `java` options control the runtime behavior of the Java HotSpot VM. In a future release it is expected to be enabled by default, and eventually will be the only mode of operation. -`-XX:-UseCompressedOops` +[`-XX:-UseCompressedOops`]{#-XX__UseCompressedOops} : Disables the use of compressed pointers. By default, this option is enabled, and compressed pointers are used. This will automatically limit the maximum ergonomically determined Java heap size to the maximum amount @@ -1553,7 +1553,7 @@ These `java` options control the runtime behavior of the Java HotSpot VM. It's possible to use compressed pointers with Java heap sizes greater than 32 GB. See the `-XX:ObjectAlignmentInBytes` option. -`-XX:-UseContainerSupport` +[`-XX:-UseContainerSupport`]{#-XX__UseContainerSupport} : **Linux only:** The VM now provides automatic container detection support, which allows the VM to determine the amount of memory and number of processors that are available to a Java process running in docker containers. It uses this @@ -1568,28 +1568,28 @@ These `java` options control the runtime behavior of the Java HotSpot VM. information. See [Enable Logging with the JVM Unified Logging Framework] for a description of using Unified Logging. -`-XX:+UseLargePages` +[`-XX:+UseLargePages`]{#-XX__UseLargePages} : Enables the use of large page memory. By default, this option is disabled and large page memory isn't used. See [Large Pages]. -`-XX:+UseTransparentHugePages` +[`-XX:+UseTransparentHugePages`]{#-XX__UseTransparentHugePages} : **Linux only:** Enables the use of large pages that can dynamically grow or shrink. This option is disabled by default. You may encounter performance problems with transparent huge pages as the OS moves other pages around to create huge pages; this option is made available for experimentation. -`-XX:+AllowUserSignalHandlers` +[`-XX:+AllowUserSignalHandlers`]{#-XX__AllowUserSignalHandlers} : **Non-Windows:** Enables installation of signal handlers by the application. By default, this option is disabled and the application isn't allowed to install signal handlers. -`-XX:VMOptionsFile=`*filename* +[`-XX:VMOptionsFile=`]{#-XX_VMOptionsFile}*filename* : Allows user to specify VM options in a file, for example, `java -XX:VMOptionsFile=/var/my_vm_options HelloWorld`. -`-XX:UseBranchProtection=`*mode* +[`-XX:UseBranchProtection=`]{#-XX_UseBranchProtection}*mode* : **Linux AArch64 only:** Specifies the branch protection mode. All options other than `none` require the VM to have been built with branch protection @@ -1613,14 +1613,14 @@ These `java` options control the runtime behavior of the Java HotSpot VM. These `java` options control the dynamic just-in-time (JIT) compilation performed by the Java HotSpot VM. -`-XX:AllocateInstancePrefetchLines=`*lines* +[`-XX:AllocateInstancePrefetchLines=`]{#-XX_AllocateInstancePrefetchLines}*lines* : Sets the number of lines to prefetch ahead of the instance allocation pointer. By default, the number of lines to prefetch is set to 1: > `-XX:AllocateInstancePrefetchLines=1` -`-XX:AllocatePrefetchDistance=`*size* +[`-XX:AllocatePrefetchDistance=`]{#-XX_AllocatePrefetchDistance}*size* : Sets the size (in bytes) of the prefetch distance for object allocation. Memory about to be written with the value of new objects is prefetched up to this distance starting from the address of the last allocated object. @@ -1636,7 +1636,7 @@ performed by the Java HotSpot VM. > `-XX:AllocatePrefetchDistance=1024` -`-XX:AllocatePrefetchInstr=`*instruction* +[`-XX:AllocatePrefetchInstr=`]{#-XX_AllocatePrefetchInstr}*instruction* : Sets the prefetch instruction to prefetch ahead of the allocation pointer. Possible values are from 0 to 3. The actual instructions behind the values depend on the platform. By default, the prefetch instruction is set to 0: @@ -1644,7 +1644,7 @@ performed by the Java HotSpot VM. > `-XX:AllocatePrefetchInstr=0` -`-XX:AllocatePrefetchLines=`*lines* +[`-XX:AllocatePrefetchLines=`]{#-XX_AllocatePrefetchLines}*lines* : Sets the number of cache lines to load after the last object allocation by using the prefetch instructions generated in compiled code. The default value is 1 if the last allocated object was an instance, and 3 if it was an @@ -1656,7 +1656,7 @@ performed by the Java HotSpot VM. > `-XX:AllocatePrefetchLines=5` -`-XX:AllocatePrefetchStepSize=`*size* +[`-XX:AllocatePrefetchStepSize=`]{#-XX_AllocatePrefetchStepSize}*size* : Sets the step size (in bytes) for sequential prefetch instructions. Append the letter `k` or `K` to indicate kilobytes, `m` or `M` to indicate megabytes, `g` or `G` to indicate gigabytes. By default, the step size is @@ -1665,7 +1665,7 @@ performed by the Java HotSpot VM. > `-XX:AllocatePrefetchStepSize=16` -`-XX:AllocatePrefetchStyle=`*style* +[`-XX:AllocatePrefetchStyle=`]{#-XX_AllocatePrefetchStyle}*style* : Sets the generated code style for prefetch instructions. The *style* argument is an integer from 0 to 3: @@ -1684,12 +1684,12 @@ performed by the Java HotSpot VM. : Generate one prefetch instruction per cache line. -`-XX:+BackgroundCompilation` +[`-XX:+BackgroundCompilation`]{#-XX__BackgroundCompilation} : Enables background compilation. This option is enabled by default. To disable background compilation, specify `-XX:-BackgroundCompilation` (this is equivalent to specifying `-Xbatch`). -`-XX:CICompilerCount=`*threads* +[`-XX:CICompilerCount=`]{#-XX_CICompilerCount}*threads* : Sets the number of compiler threads to use for compilation. By default, the number of compiler threads is selected automatically depending on the number of CPUs and memory available for compiled code. @@ -1697,11 +1697,11 @@ performed by the Java HotSpot VM. > `-XX:CICompilerCount=2` -`-XX:+UseDynamicNumberOfCompilerThreads` +[`-XX:+UseDynamicNumberOfCompilerThreads`]{#-XX__UseDynamicNumberOfCompilerThreads} : Dynamically create compiler thread up to the limit specified by `-XX:CICompilerCount`. This option is enabled by default. -`-XX:CompileCommand=`*command*`,`*method*\[`,`*option*\] +[`-XX:CompileCommand=`]{#-XX_CompileCommand}*command*`,`*method*\[`,`*option*\] : Specifies a *command* to perform on a *method*. For example, to exclude the `indexOf()` method of the `String` class from being compiled, use the following: @@ -1800,7 +1800,7 @@ performed by the Java HotSpot VM. You can suppress this by specifying the `-XX:CompileCommand=quiet` option before other `-XX:CompileCommand` options. -`-XX:CompileCommandFile=`*filename* +[`-XX:CompileCommandFile=`]{#-XX_CompileCommandFile}*filename* : Sets the file from which JIT compiler commands are read. By default, the `.hotspot_compiler` file is used to store commands performed by the JIT compiler. @@ -1814,7 +1814,7 @@ performed by the Java HotSpot VM. If you're using commands for the JIT compiler to perform on methods, then see the `-XX:CompileCommand` option. -`-XX:CompilerDirectivesFile=`*file* +[`-XX:CompilerDirectivesFile=`]{#-XX_CompilerDirectivesFile}*file* : Adds directives from a file to the directives stack when a program starts. See [Compiler Control](https://docs.oracle.com/en/java/javase/12/vm/compiler-control1.html#GUID-94AD8194-786A-4F19-BFFF-278F8E237F3A). @@ -1822,14 +1822,14 @@ performed by the Java HotSpot VM. `-XX:UnlockDiagnosticVMOptions` option that unlocks diagnostic JVM options. -`-XX:+CompilerDirectivesPrint` +[`-XX:+CompilerDirectivesPrint`]{#-XX__CompilerDirectivesPrint} : Prints the directives stack when the program starts or when a new directive is added. The `-XX:+CompilerDirectivesPrint` option has to be used together with the `-XX:UnlockDiagnosticVMOptions` option that unlocks diagnostic JVM options. -`-XX:CompileOnly=`*methods* +[`-XX:CompileOnly=`]{#-XX_CompileOnly}*methods* : Sets the list of methods (separated by commas) to which compilation should be restricted. Only the specified methods are compiled. @@ -1841,7 +1841,7 @@ performed by the Java HotSpot VM. -XX:CompileCommand=compileonly,methodN ``` -`-XX:CompileThresholdScaling=`*scale* +[`-XX:CompileThresholdScaling=`]{#-XX_CompileThresholdScaling}*scale* : Provides unified control of first compilation. This option controls when methods are first compiled for both the tiered and the nontiered modes of operation. The `CompileThresholdScaling` option has a floating point value @@ -1851,11 +1851,11 @@ performed by the Java HotSpot VM. compilation while values greater than 1.0 delay compilation. Setting `CompileThresholdScaling` to 0 is equivalent to disabling compilation. -`-XX:+DoEscapeAnalysis` +[`-XX:+DoEscapeAnalysis`]{#-XX__DoEscapeAnalysis} : Enables the use of escape analysis. This option is enabled by default. To disable the use of escape analysis, specify `-XX:-DoEscapeAnalysis`. -`-XX:InitialCodeCacheSize=`*size* +[`-XX:InitialCodeCacheSize=`]{#-XX_InitialCodeCacheSize}*size* : Sets the initial code cache size (in bytes). Append the letter `k` or `K` to indicate kilobytes, `m` or `M` to indicate megabytes, or `g` or `G` to indicate gigabytes. The default value depends on the platform. The initial code @@ -1865,11 +1865,11 @@ performed by the Java HotSpot VM. > `-XX:InitialCodeCacheSize=32k` -`-XX:+Inline` +[`-XX:+Inline`]{#-XX__Inline} : Enables method inlining. This option is enabled by default to increase performance. To disable method inlining, specify `-XX:-Inline`. -`-XX:InlineSmallCode=`*size* +[`-XX:InlineSmallCode=`]{#-XX_InlineSmallCode}*size* : Sets the maximum code size (in bytes) for already compiled methods that may be inlined. This flag only applies to the C2 compiler. Append the letter `k` or `K` to indicate kilobytes, @@ -1879,7 +1879,7 @@ performed by the Java HotSpot VM. > `-XX:InlineSmallCode=1000` -`-XX:+LogCompilation` +[`-XX:+LogCompilation`]{#-XX__LogCompilation} : Enables logging of compilation activity to a file named `hotspot.log` in the current working directory. You can specify a different log file path and name using the `-XX:LogFile` option. @@ -1893,7 +1893,7 @@ performed by the Java HotSpot VM. `-XX:+PrintCompilation` option. -`-XX:FreqInlineSize=`*size* +[`-XX:FreqInlineSize=`]{#-XX_FreqInlineSize}*size* : Sets the maximum bytecode size (in bytes) of a hot method to be inlined. This flag only applies to the C2 compiler. Append the letter `k` or `K` to indicate kilobytes, `m` or `M` to indicate @@ -1903,7 +1903,7 @@ performed by the Java HotSpot VM. > `-XX:FreqInlineSize=325` -`-XX:MaxInlineSize=`*size* +[`-XX:MaxInlineSize=`]{#-XX_MaxInlineSize}*size* : Sets the maximum bytecode size (in bytes) of a cold method to be inlined. This flag only applies to the C2 compiler. Append the letter `k` or `K` to indicate kilobytes, `m` or `M` to indicate @@ -1912,7 +1912,7 @@ performed by the Java HotSpot VM. > `-XX:MaxInlineSize=35` -`-XX:C1MaxInlineSize=`*size* +[`-XX:C1MaxInlineSize=`]{#-XX_C1MaxInlineSize}*size* : Sets the maximum bytecode size (in bytes) of a cold method to be inlined. This flag only applies to the C1 compiler. Append the letter `k` or `K` to indicate kilobytes, `m` or `M` to indicate @@ -1921,7 +1921,7 @@ performed by the Java HotSpot VM. > `-XX:MaxInlineSize=35` -`-XX:MaxTrivialSize=`*size* +[`-XX:MaxTrivialSize=`]{#-XX_MaxTrivialSize}*size* : Sets the maximum bytecode size (in bytes) of a trivial method to be inlined. This flag only applies to the C2 compiler. Append the letter `k` or `K` to indicate kilobytes, `m` or `M` to @@ -1930,7 +1930,7 @@ performed by the Java HotSpot VM. > `-XX:MaxTrivialSize=6` -`-XX:C1MaxTrivialSize=`*size* +[`-XX:C1MaxTrivialSize=`]{#-XX_C1MaxTrivialSize}*size* : Sets the maximum bytecode size (in bytes) of a trivial method to be inlined. This flag only applies to the C1 compiler. Append the letter `k` or `K` to indicate kilobytes, `m` or `M` to @@ -1939,14 +1939,14 @@ performed by the Java HotSpot VM. > `-XX:MaxTrivialSize=6` -`-XX:MaxNodeLimit=`*nodes* +[`-XX:MaxNodeLimit=`]{#-XX_MaxNodeLimit}*nodes* : Sets the maximum number of nodes to be used during single method compilation. By default the value depends on the features enabled. In the following example the maximum number of nodes is set to 100,000: > `-XX:MaxNodeLimit=100000` -`-XX:NonNMethodCodeHeapSize=`*size* +[`-XX:NonNMethodCodeHeapSize=`]{#-XX_NonNMethodCodeHeapSize}*size* : Sets the size in bytes of the code segment containing nonmethod code. A nonmethod code segment containing nonmethod code, such as compiler @@ -1954,16 +1954,16 @@ performed by the Java HotSpot VM. cache forever. This flag is used only if `-XX:SegmentedCodeCache` is enabled. -`-XX:NonProfiledCodeHeapSize=`*size* +[`-XX:NonProfiledCodeHeapSize=`]{#-XX_NonProfiledCodeHeapSize}*size* : Sets the size in bytes of the code segment containing nonprofiled methods. This flag is used only if `-XX:SegmentedCodeCache` is enabled. -`-XX:+OptimizeStringConcat` +[`-XX:+OptimizeStringConcat`]{#-XX__OptimizeStringConcat} : Enables the optimization of `String` concatenation operations. This option is enabled by default. To disable the optimization of `String` concatenation operations, specify `-XX:-OptimizeStringConcat`. -`-XX:+PrintAssembly` +[`-XX:+PrintAssembly`]{#-XX__PrintAssembly} : Enables printing of assembly code for bytecoded and native methods by using the external `hsdis-.so` or `.dll` library. For 64-bit VM on Windows, it's `hsdis-amd64.dll`. This lets you to see the generated code, which may @@ -1973,11 +1973,11 @@ performed by the Java HotSpot VM. `-XX:+PrintAssembly` option has to be used together with the `-XX:UnlockDiagnosticVMOptions` option that unlocks diagnostic JVM options. -`-XX:ProfiledCodeHeapSize=`*size* +[`-XX:ProfiledCodeHeapSize=`]{#-XX_ProfiledCodeHeapSize}*size* : Sets the size in bytes of the code segment containing profiled methods. This flag is used only if `-XX:SegmentedCodeCache` is enabled. -`-XX:+PrintCompilation` +[`-XX:+PrintCompilation`]{#-XX__PrintCompilation} : Enables verbose diagnostic output from the JVM by printing a message to the console every time a method is compiled. This lets you to see which methods actually get compiled. By default, this option is disabled and diagnostic @@ -1986,7 +1986,7 @@ performed by the Java HotSpot VM. You can also log compilation activity to a file by using the `-XX:+LogCompilation` option. -`-XX:+PrintInlining` +[`-XX:+PrintInlining`]{#-XX__PrintInlining} : Enables printing of inlining decisions. This let's you see which methods are getting inlined. @@ -1995,7 +1995,7 @@ performed by the Java HotSpot VM. `-XX:+UnlockDiagnosticVMOptions` option that unlocks diagnostic JVM options. -`-XX:ReservedCodeCacheSize=`*size* +[`-XX:ReservedCodeCacheSize=`]{#-XX_ReservedCodeCacheSize}*size* : Sets the maximum code cache size (in bytes) for JIT-compiled code. Append the letter `k` or `K` to indicate kilobytes, `m` or `M` to indicate megabytes, or `g` or `G` to indicate gigabytes. The default maximum code @@ -2005,7 +2005,7 @@ performed by the Java HotSpot VM. size shouldn't be less than the initial code cache size; see the option `-XX:InitialCodeCacheSize`. -`-XX:+SegmentedCodeCache` +[`-XX:+SegmentedCodeCache`]{#-XX__SegmentedCodeCache} : Enables segmentation of the code cache, without which the code cache consists of one large segment. With `-XX:+SegmentedCodeCache`, separate segments will be used for non-method, profiled method, and non-profiled @@ -2018,29 +2018,29 @@ performed by the Java HotSpot VM. (`-XX:+TieredCompilation` ) and the reserved code cache size (`-XX:ReservedCodeCacheSize`) is at least 240 MB. -`-XX:StartAggressiveSweepingAt=`*percent* +[`-XX:StartAggressiveSweepingAt=`]{#-XX_StartAggressiveSweepingAt}*percent* : Forces stack scanning of active methods to aggressively remove unused code when only the given percentage of the code cache is free. The default value is 10%. -`-XX:-TieredCompilation` +[`-XX:-TieredCompilation`]{#-XX__TieredCompilation} : Disables the use of tiered compilation. By default, this option is enabled. -`-XX:UseSSE=`*version* +[`-XX:UseSSE=`]{#-XX_UseSSE}*version* : Enables the use of SSE instruction set of a specified version. Is set by default to the highest supported version available (x86 only). -`-XX:UseAVX=`*version* +[`-XX:UseAVX=`]{#-XX_UseAVX}*version* : Enables the use of AVX instruction set of a specified version. Is set by default to the highest supported version available (x86 only). -`-XX:+UseAES` +[`-XX:+UseAES`]{#-XX__UseAES} : Enables hardware-based AES intrinsics for hardware that supports it. This option is on by default on hardware that has the necessary instructions. The `-XX:+UseAES` is used in conjunction with `UseAESIntrinsics`. Flags that control intrinsics now require the option `-XX:+UnlockDiagnosticVMOptions`. -`-XX:+UseAESIntrinsics` +[`-XX:+UseAESIntrinsics`]{#-XX__UseAESIntrinsics} : Enables AES intrinsics. Specifying `-XX:+UseAESIntrinsics` is equivalent to also enabling `-XX:+UseAES`. To disable hardware-based AES intrinsics, specify `-XX:-UseAES -XX:-UseAESIntrinsics`. For example, to enable hardware @@ -2051,47 +2051,47 @@ performed by the Java HotSpot VM. Flags that control intrinsics now require the option `-XX:+UnlockDiagnosticVMOptions`. -`-XX:+UseAESCTRIntrinsics` +[`-XX:+UseAESCTRIntrinsics`]{#-XX__UseAESCTRIntrinsics} : Analogous to `-XX:+UseAESIntrinsics` enables AES/CTR intrinsics. -`-XX:+UseGHASHIntrinsics` +[`-XX:+UseGHASHIntrinsics`]{#-XX__UseGHASHIntrinsics} : Controls the use of GHASH intrinsics. Enabled by default on platforms that support the corresponding instructions. Flags that control intrinsics now require the option `-XX:+UnlockDiagnosticVMOptions`. -`-XX:+UseChaCha20Intrinsics` +[`-XX:+UseChaCha20Intrinsics`]{#-XX__UseChaCha20Intrinsics} : Enable ChaCha20 intrinsics. This option is on by default for supported platforms. To disable ChaCha20 intrinsics, specify `-XX:-UseChaCha20Intrinsics`. Flags that control intrinsics now require the option `-XX:+UnlockDiagnosticVMOptions`. -`-XX:+UsePoly1305Intrinsics` +[`-XX:+UsePoly1305Intrinsics`]{#-XX__UsePoly1305Intrinsics} : Enable Poly1305 intrinsics. This option is on by default for supported platforms. To disable Poly1305 intrinsics, specify `-XX:-UsePoly1305Intrinsics`. Flags that control intrinsics now require the option `-XX:+UnlockDiagnosticVMOptions`. -`-XX:+UseBASE64Intrinsics` +[`-XX:+UseBASE64Intrinsics`]{#-XX__UseBASE64Intrinsics} : Controls the use of accelerated BASE64 encoding routines for `java.util.Base64`. Enabled by default on platforms that support it. Flags that control intrinsics now require the option `-XX:+UnlockDiagnosticVMOptions`. -`-XX:+UseAdler32Intrinsics` +[`-XX:+UseAdler32Intrinsics`]{#-XX__UseAdler32Intrinsics} : Controls the use of Adler32 checksum algorithm intrinsic for `java.util.zip.Adler32`. Enabled by default on platforms that support it. Flags that control intrinsics now require the option `-XX:+UnlockDiagnosticVMOptions`. -`-XX:+UseCRC32Intrinsics` +[`-XX:+UseCRC32Intrinsics`]{#-XX__UseCRC32Intrinsics} : Controls the use of CRC32 intrinsics for `java.util.zip.CRC32`. Enabled by default on platforms that support it. Flags that control intrinsics now require the option `-XX:+UnlockDiagnosticVMOptions`. -`-XX:+UseCRC32CIntrinsics` +[`-XX:+UseCRC32CIntrinsics`]{#-XX__UseCRC32CIntrinsics} : Controls the use of CRC32C intrinsics for `java.util.zip.CRC32C`. Enabled by default on platforms that support it. Flags that control intrinsics now require the option `-XX:+UnlockDiagnosticVMOptions`. -`-XX:+UseSHA` +[`-XX:+UseSHA`]{#-XX__UseSHA} : Enables hardware-based intrinsics for SHA crypto hash functions for some hardware. The `UseSHA` option is used in conjunction with the `UseSHA1Intrinsics`, `UseSHA256Intrinsics`, and `UseSHA512Intrinsics` @@ -2108,26 +2108,26 @@ performed by the Java HotSpot VM. disable only a particular SHA intrinsic, use the appropriate corresponding option. For example: `-XX:-UseSHA256Intrinsics`. -`-XX:+UseSHA1Intrinsics` +[`-XX:+UseSHA1Intrinsics`]{#-XX__UseSHA1Intrinsics} : Enables intrinsics for SHA-1 crypto hash function. Flags that control intrinsics now require the option `-XX:+UnlockDiagnosticVMOptions`. -`-XX:+UseSHA256Intrinsics` +[`-XX:+UseSHA256Intrinsics`]{#-XX__UseSHA256Intrinsics} : Enables intrinsics for SHA-224 and SHA-256 crypto hash functions. Flags that control intrinsics now require the option `-XX:+UnlockDiagnosticVMOptions`. -`-XX:+UseSHA512Intrinsics` +[`-XX:+UseSHA512Intrinsics`]{#-XX__UseSHA512Intrinsics} : Enables intrinsics for SHA-384 and SHA-512 crypto hash functions. Flags that control intrinsics now require the option `-XX:+UnlockDiagnosticVMOptions`. -`-XX:+UseMathExactIntrinsics` +[`-XX:+UseMathExactIntrinsics`]{#-XX__UseMathExactIntrinsics} : Enables intrinsification of various `java.lang.Math.*Exact()` functions. Enabled by default. Flags that control intrinsics now require the option `-XX:+UnlockDiagnosticVMOptions`. -`-XX:+UseMultiplyToLenIntrinsic` +[`-XX:+UseMultiplyToLenIntrinsic`]{#-XX__UseMultiplyToLenIntrinsic} : Enables intrinsification of `BigInteger.multiplyToLen()`. Enabled by default on platforms that support it. Flags that control intrinsics now require the option `-XX:+UnlockDiagnosticVMOptions`. @@ -2152,44 +2152,44 @@ performed by the Java HotSpot VM. Enabled by default on platforms that support it. Flags that control intrinsics now require the option `-XX:+UnlockDiagnosticVMOptions`. -`-XX:+UseCMoveUnconditionally` +[`-XX:+UseCMoveUnconditionally`]{#-XX__UseCMoveUnconditionally} : Generates CMove (scalar and vector) instructions regardless of profitability analysis. -`-XX:+UseCodeCacheFlushing` +[`-XX:+UseCodeCacheFlushing`]{#-XX__UseCodeCacheFlushing} : Enables flushing of the code cache before shutting down the compiler. This option is enabled by default. To disable flushing of the code cache before shutting down the compiler, specify `-XX:-UseCodeCacheFlushing`. -`-XX:+UseCondCardMark` +[`-XX:+UseCondCardMark`]{#-XX__UseCondCardMark} : Enables checking if the card is already marked before updating the card table. This option is disabled by default. It should be used only on machines with multiple sockets, where it increases the performance of Java applications that rely on concurrent operations. -`-XX:+UseCountedLoopSafepoints` +[`-XX:+UseCountedLoopSafepoints`]{#-XX__UseCountedLoopSafepoints} : Keeps safepoints in counted loops. Its default value depends on whether the selected garbage collector requires low latency safepoints. -`-XX:LoopStripMiningIter=`*number_of_iterations* +[`-XX:LoopStripMiningIter=`]{#-XX_LoopStripMiningIter}*number_of_iterations* : Controls the number of iterations in the inner strip mined loop. Strip mining transforms counted loops into two level nested loops. Safepoints are kept in the outer loop while the inner loop can execute at full speed. This option controls the maximum number of iterations in the inner loop. The default value is 1,000. -`-XX:LoopStripMiningIterShortLoop=`*number_of_iterations* +[`-XX:LoopStripMiningIterShortLoop=`]{#-XX_LoopStripMiningIterShortLoop}*number_of_iterations* : Controls loop strip mining optimization. Loops with the number of iterations less than specified will not have safepoints in them. Default value is 1/10th of `-XX:LoopStripMiningIter`. -`-XX:+UseFMA` +[`-XX:+UseFMA`]{#-XX__UseFMA} : Enables hardware-based FMA intrinsics for hardware where FMA instructions are available (such as, Intel and ARM64). FMA intrinsics are generated for the `java.lang.Math.fma(`*a*`,` *b*`,` *c*`)` methods that calculate the value of `(` *a* `*` *b* `+` *c* `)` expressions. -`-XX:+UseSuperWord` +[`-XX:+UseSuperWord`]{#-XX__UseSuperWord} : Enables the transformation of scalar operations into superword operations. Superword is a vectorization optimization. This option is enabled by default. To disable the transformation of scalar operations into superword @@ -2200,7 +2200,7 @@ performed by the Java HotSpot VM. These `java` options provide the ability to gather system information and perform extensive debugging. -`-XX:+DisableAttachMechanism` +[`-XX:+DisableAttachMechanism`]{#-XX__DisableAttachMechanism} : Disables the mechanism that lets tools attach to the JVM. By default, this option is disabled, meaning that the attach mechanism is enabled and you can use diagnostics and troubleshooting tools such as `jcmd`, `jstack`, @@ -2211,17 +2211,17 @@ perform extensive debugging. supported when using the tools from one JDK version to troubleshoot a different JDK version. -`-XX:+DTraceAllocProbes` +[`-XX:+DTraceAllocProbes`]{#-XX__DTraceAllocProbes} : **Linux and macOS:** Enable `dtrace` tool probes for object allocation. -`-XX:+DTraceMethodProbes` +[`-XX:+DTraceMethodProbes`]{#-XX__DTraceMethodProbes} : **Linux and macOS:** Enable `dtrace` tool probes for method-entry and method-exit. -`-XX:+DTraceMonitorProbes` +[`-XX:+DTraceMonitorProbes`]{#-XX__DTraceMonitorProbes} : **Linux and macOS:** Enable `dtrace` tool probes for monitor events. -`-XX:+HeapDumpOnOutOfMemoryError` +[`-XX:+HeapDumpOnOutOfMemoryError`]{#-XX__HeapDumpOnOutOfMemoryError} : Enables the dumping of the Java heap to a file in the current directory by using the heap profiler (HPROF) when a `java.lang.OutOfMemoryError` exception is thrown by the JVM. You can explicitly set the heap dump file path and @@ -2233,7 +2233,7 @@ perform extensive debugging. directly from Java code, nor by the JVM for other types of resource exhaustion (such as native thread creation errors). -`-XX:HeapDumpPath=`*path* +[`-XX:HeapDumpPath=`]{#-XX_HeapDumpPath}*path* : Sets the path and file name for writing the heap dump provided by the heap profiler (HPROF) when the `-XX:+HeapDumpOnOutOfMemoryError` option is set. By default, the file is created in the current working directory, and it's @@ -2253,7 +2253,7 @@ perform extensive debugging. > `-XX:HeapDumpPath=C:/log/java/java_heapdump.log` -`-XX:LogFile=`*path* +[`-XX:LogFile=`]{#-XX_LogFile}*path* : Sets the path and file name to where log data is written. By default, the file is created in the current working directory, and it's named `hotspot.log`. @@ -2268,7 +2268,7 @@ perform extensive debugging. > `-XX:LogFile=C:/log/java/hotspot.log` -`-XX:+PrintClassHistogram` +[`-XX:+PrintClassHistogram`]{#-XX__PrintClassHistogram} : Enables printing of a class instance histogram after one of the following events: @@ -2282,7 +2282,7 @@ perform extensive debugging. the `jcmd` *pid* `GC.class_histogram` command, where *pid* is the current Java process identifier. -`-XX:+PrintConcurrentLocks` +[`-XX:+PrintConcurrentLocks`]{#-XX__PrintConcurrentLocks} : Enables printing of `java.util.concurrent` locks after one of the following events: @@ -2296,12 +2296,12 @@ perform extensive debugging. `jcmd` *pid* `Thread.print -l` command, where *pid* is the current Java process identifier. -`-XX:+PrintFlagsRanges` +[`-XX:+PrintFlagsRanges`]{#-XX__PrintFlagsRanges} : Prints the range specified and allows automatic testing of the values. See [Validate Java Virtual Machine Flag Arguments]. -`-XX:+PerfDataSaveToFile` +[`-XX:+PerfDataSaveToFile`]{#-XX__PerfDataSaveToFile} : If enabled, saves [jstat](jstat.html) binary data when the Java application exits. This binary data is saved in a file named `hsperfdata_`*pid*, where *pid* is the process identifier of the Java application that you ran. Use @@ -2312,7 +2312,7 @@ perform extensive debugging. > `jstat -gc file:///`*path*`/hsperfdata_`*pid* -`-XX:+UsePerfData` +[`-XX:+UsePerfData`]{#-XX__UsePerfData} : Enables the `perfdata` feature. This option is enabled by default to allow JVM monitoring and performance testing. Disabling it suppresses the creation of the `hsperfdata_userid` directories. To disable the `perfdata` @@ -2323,13 +2323,13 @@ perform extensive debugging. These `java` options control how garbage collection (GC) is performed by the Java HotSpot VM. -`-XX:+AlwaysPreTouch` +[`-XX:+AlwaysPreTouch`]{#-XX__AlwaysPreTouch} : Requests the VM to touch every page on the Java heap after requesting it from the operating system and before handing memory out to the application. By default, this option is disabled and all pages are committed as the application uses the heap space. -`-XX:ConcGCThreads=`*threads* +[`-XX:ConcGCThreads=`]{#-XX_ConcGCThreads}*threads* : Sets the number of threads used for concurrent GC. Sets *`threads`* to approximately 1/4 of the number of parallel garbage collection threads. The default value depends on the number of CPUs available to the JVM. @@ -2339,24 +2339,24 @@ Java HotSpot VM. > `-XX:ConcGCThreads=2` -`-XX:+DisableExplicitGC` +[`-XX:+DisableExplicitGC`]{#-XX__DisableExplicitGC} : Enables the option that disables processing of calls to the `System.gc()` method. This option is disabled by default, meaning that calls to `System.gc()` are processed. If processing of calls to `System.gc()` is disabled, then the JVM still performs GC when necessary. -`-XX:+ExplicitGCInvokesConcurrent` +[`-XX:+ExplicitGCInvokesConcurrent`]{#-XX__ExplicitGCInvokesConcurrent} : Enables invoking of concurrent GC by using the `System.gc()` request. This option is disabled by default and can be enabled only with the `-XX:+UseG1GC` option. -`-XX:G1AdaptiveIHOPNumInitialSamples=`*number* +[`-XX:G1AdaptiveIHOPNumInitialSamples=`]{#-XX_G1AdaptiveIHOPNumInitialSamples}*number* : When `-XX:UseAdaptiveIHOP` is enabled, this option sets the number of completed marking cycles used to gather samples until G1 adaptively determines the optimum value of `-XX:InitiatingHeapOccupancyPercent`. Before, G1 uses the value of `-XX:InitiatingHeapOccupancyPercent` directly for this purpose. The default value is 3. -`-XX:G1HeapRegionSize=`*size* +[`-XX:G1HeapRegionSize=`]{#-XX_G1HeapRegionSize}*size* : Sets the size of the regions into which the Java heap is subdivided when using the garbage-first (G1) collector. The value is a power of 2 and can range from 1 MB to 32 MB. The default region size is determined @@ -2367,44 +2367,44 @@ Java HotSpot VM. > `-XX:G1HeapRegionSize=16m` -`-XX:G1HeapWastePercent=`*percent* +[`-XX:G1HeapWastePercent=`]{#-XX_G1HeapWastePercent}*percent* : Sets the percentage of heap that you're willing to waste. The Java HotSpot VM doesn't initiate the mixed garbage collection cycle when the reclaimable percentage is less than the heap waste percentage. The default is 5 percent. -`-XX:G1MaxNewSizePercent=`*percent* +[`-XX:G1MaxNewSizePercent=`]{#-XX_G1MaxNewSizePercent}*percent* : Sets the percentage of the heap size to use as the maximum for the young generation size. The default value is 60 percent of your Java heap. This is an experimental flag. This setting replaces the `-XX:DefaultMaxNewGenPercent` setting. -`-XX:G1MixedGCCountTarget=`*number* +[`-XX:G1MixedGCCountTarget=`]{#-XX_G1MixedGCCountTarget}*number* : Sets the target number of mixed garbage collections after a marking cycle to collect old regions with at most `G1MixedGCLIveThresholdPercent` live data. The default is 8 mixed garbage collections. The goal for mixed collections is to be within this target number. -`-XX:G1MixedGCLiveThresholdPercent=`*percent* +[`-XX:G1MixedGCLiveThresholdPercent=`]{#-XX_G1MixedGCLiveThresholdPercent}*percent* : Sets the occupancy threshold for an old region to be included in a mixed garbage collection cycle. The default occupancy is 85 percent. This is an experimental flag. This setting replaces the `-XX:G1OldCSetRegionLiveThresholdPercent` setting. -`-XX:G1NewSizePercent=`*percent* +[`-XX:G1NewSizePercent=`]{#-XX_G1NewSizePercent}*percent* : Sets the percentage of the heap to use as the minimum for the young generation size. The default value is 5 percent of your Java heap. This is an experimental flag. This setting replaces the `-XX:DefaultMinNewGenPercent` setting. -`-XX:G1OldCSetRegionThresholdPercent=`*percent* +[`-XX:G1OldCSetRegionThresholdPercent=`]{#-XX_G1OldCSetRegionThresholdPercent}*percent* : Sets an upper limit on the number of old regions to be collected during a mixed garbage collection cycle. The default is 10 percent of the Java heap. -`-XX:G1ReservePercent=`*percent* +[`-XX:G1ReservePercent=`]{#-XX_G1ReservePercent}*percent* : Sets the percentage of the heap (0 to 50) that's reserved as a false ceiling to reduce the possibility of promotion failure for the G1 collector. When you increase or decrease the percentage, ensure that you @@ -2415,7 +2415,7 @@ Java HotSpot VM. > `-XX:G1ReservePercent=20` -`-XX:+G1UseAdaptiveIHOP` +[`-XX:+G1UseAdaptiveIHOP`]{#-XX__G1UseAdaptiveIHOP} : Controls adaptive calculation of the old generation occupancy to start background work preparing for an old generation collection. If enabled, G1 uses `-XX:InitiatingHeapOccupancyPercent` for the first few times as @@ -2428,7 +2428,7 @@ Java HotSpot VM. The default is enabled. -`-XX:InitialHeapSize=`*size* +[`-XX:InitialHeapSize=`]{#-XX_InitialHeapSize}*size* : Sets the initial size (in bytes) of the memory allocation pool. This value must be either 0, or a multiple of 1024 and greater than 1 MB. Append the letter `k` or `K` to indicate kilobytes, `m` or `M` to indicate megabytes, @@ -2452,7 +2452,7 @@ Java HotSpot VM. command line, then the initial heap size gets set to the value specified with `-Xms`. -`-XX:InitialRAMPercentage=`*percent* +[`-XX:InitialRAMPercentage=`]{#-XX_InitialRAMPercentage}*percent* : Sets the initial amount of memory that the JVM will use for the Java heap before applying ergonomics heuristics as a percentage of the maximum amount determined as described in the `-XX:MaxRAM` option. @@ -2462,7 +2462,7 @@ Java HotSpot VM. > `-XX:InitialRAMPercentage=5` -`-XX:InitialSurvivorRatio=`*ratio* +[`-XX:InitialSurvivorRatio=`]{#-XX_InitialSurvivorRatio}*ratio* : Sets the initial survivor space ratio used by the throughput garbage collector (which is enabled by the `-XX:+UseParallelGC` option). Adaptive sizing is enabled by default with the throughput garbage collector by @@ -2491,7 +2491,7 @@ Java HotSpot VM. > `-XX:InitialSurvivorRatio=4` -`-XX:InitiatingHeapOccupancyPercent=`*percent* +[`-XX:InitiatingHeapOccupancyPercent=`]{#-XX_InitiatingHeapOccupancyPercent}*percent* : Sets the percentage of the old generation occupancy (0 to 100) at which to start the first few concurrent marking cycles for the G1 garbage collector. @@ -2506,7 +2506,7 @@ Java HotSpot VM. > `-XX:InitiatingHeapOccupancyPercent=75` -`-XX:MaxGCPauseMillis=`*time* +[`-XX:MaxGCPauseMillis=`]{#-XX_MaxGCPauseMillis}*time* : Sets a target for the maximum GC pause time (in milliseconds). This is a soft goal, and the JVM will make its best effort to achieve it. Only G1 and Parallel support a maximum GC pause time target. For G1, the default @@ -2517,7 +2517,7 @@ Java HotSpot VM. > `-XX:MaxGCPauseMillis=500` -`-XX:MaxHeapSize=`*size* +[`-XX:MaxHeapSize=`]{#-XX_MaxHeapSize}*size* : Sets the maximum size (in byes) of the memory allocation pool. This value must be a multiple of 1024 and greater than 2 MB. Append the letter `k` or `K` to indicate kilobytes, `m` or `M` to indicate megabytes, or `g` or `G` @@ -2537,7 +2537,7 @@ Java HotSpot VM. The `-XX:MaxHeapSize` option is equivalent to `-Xmx`. -`-XX:MaxHeapFreeRatio=`*percent* +[`-XX:MaxHeapFreeRatio=`]{#-XX_MaxHeapFreeRatio}*percent* : Sets the maximum allowed percentage of free heap space (0 to 100) after a GC event. If free heap space expands above this value, then the heap is shrunk. By default, this value is set to 70%. @@ -2558,7 +2558,7 @@ Java HotSpot VM. description of using this option to keep the Java heap small by reducing the dynamic footprint for embedded applications. -`-XX:MaxMetaspaceSize=`*size* +[`-XX:MaxMetaspaceSize=`]{#-XX_MaxMetaspaceSize}*size* : Sets the maximum amount of native memory that can be allocated for class metadata. By default, the size isn't limited. The amount of metadata for an application depends on the application itself, other running applications, @@ -2569,11 +2569,11 @@ Java HotSpot VM. > `-XX:MaxMetaspaceSize=256m` -`-XX:MaxNewSize=`*size* +[`-XX:MaxNewSize=`]{#-XX_MaxNewSize}*size* : Sets the maximum size (in bytes) of the heap for the young generation (nursery). The default value is set ergonomically. -`-XX:MaxRAMPercentage=`*percent* +[`-XX:MaxRAMPercentage=`]{#-XX_MaxRAMPercentage}*percent* : Sets the maximum amount of memory that the JVM may use for the Java heap before applying ergonomics heuristics as a percentage of the maximum amount determined as described in the `-XX:MaxRAM` option. The default value is 25 @@ -2589,7 +2589,7 @@ Java HotSpot VM. > `-XX:MaxRAMPercentage=75` -`-XX:MinRAMPercentage=`*percent* +[`-XX:MinRAMPercentage=`]{#-XX_MinRAMPercentage}*percent* : Sets the maximum amount of memory that the JVM may use for the Java heap before applying ergonomics heuristics as a percentage of the maximum amount determined as described in the `-XX:MaxRAM` option for small heaps. A small @@ -2600,7 +2600,7 @@ Java HotSpot VM. > `-XX:MinRAMPercentage=75` -`-XX:MaxTenuringThreshold=`*threshold* +[`-XX:MaxTenuringThreshold=`]{#-XX_MaxTenuringThreshold}*threshold* : Sets the maximum tenuring threshold for use in adaptive GC sizing. The largest value is 15. The default value is 15 for the parallel (throughput) collector. @@ -2610,13 +2610,13 @@ Java HotSpot VM. > `-XX:MaxTenuringThreshold=10` -`-XX:MetaspaceSize=`*size* +[`-XX:MetaspaceSize=`]{#-XX_MetaspaceSize}*size* : Sets the size of the allocated class metadata space that triggers a garbage collection the first time it's exceeded. This threshold for a garbage collection is increased or decreased depending on the amount of metadata used. The default size depends on the platform. -`-XX:MinHeapFreeRatio=`*percent* +[`-XX:MinHeapFreeRatio=`]{#-XX_MinHeapFreeRatio}*percent* : Sets the minimum allowed percentage of free heap space (0 to 100) after a GC event. If free heap space falls below this value, then the heap is expanded. By default, this value is set to 40%. @@ -2637,7 +2637,7 @@ Java HotSpot VM. description of using this option to keep the Java heap small by reducing the dynamic footprint for embedded applications. -`-XX:MinHeapSize=`*size* +[`-XX:MinHeapSize=`]{#-XX_MinHeapSize}*size* : Sets the minimum size (in bytes) of the memory allocation pool. This value must be either 0, or a multiple of 1024 and greater than 1 MB. Append the letter `k` or `K` to indicate kilobytes, `m` or `M` to indicate megabytes, @@ -2656,14 +2656,14 @@ Java HotSpot VM. If you set this option to 0, then the minimum size is set to the same value as the initial size. -`-XX:NewRatio=`*ratio* +[`-XX:NewRatio=`]{#-XX_NewRatio}*ratio* : Sets the ratio between young and old generation sizes. By default, this option is set to 2. The following example shows how to set the young-to-old ratio to 1: > `-XX:NewRatio=1` -`-XX:NewSize=`*size* +[`-XX:NewSize=`]{#-XX_NewSize}*size* : Sets the initial size (in bytes) of the heap for the young generation (nursery). Append the letter `k` or `K` to indicate kilobytes, `m` or `M` to indicate megabytes, or `g` or `G` to indicate gigabytes. @@ -2687,7 +2687,7 @@ Java HotSpot VM. The `-XX:NewSize` option is equivalent to `-Xmn`. -`-XX:ParallelGCThreads=`*threads* +[`-XX:ParallelGCThreads=`]{#-XX_ParallelGCThreads}*threads* : Sets the number of the stop-the-world (STW) worker threads. The default value depends on the number of CPUs available to the JVM and the garbage collector selected. @@ -2697,11 +2697,11 @@ Java HotSpot VM. > `-XX:ParallelGCThreads=2` -`-XX:+PrintAdaptiveSizePolicy` +[`-XX:+PrintAdaptiveSizePolicy`]{#-XX__PrintAdaptiveSizePolicy} : Enables printing of information about adaptive-generation sizing. By default, this option is disabled. -`-XX:SoftRefLRUPolicyMSPerMB=`*time* +[`-XX:SoftRefLRUPolicyMSPerMB=`]{#-XX_SoftRefLRUPolicyMSPerMB}*time* : Sets the amount of time (in milliseconds) a softly reachable object is kept active on the heap after the last time it was referenced. The default value is one second of lifetime per free megabyte in the heap. The @@ -2718,7 +2718,7 @@ Java HotSpot VM. `-XX:SoftRefLRUPolicyMSPerMB=2500` -`-XX:-ShrinkHeapInSteps` +[`-XX:-ShrinkHeapInSteps`]{#-XX__ShrinkHeapInSteps} : Incrementally reduces the Java heap to the target size, specified by the option `-XX:MaxHeapFreeRatio`. This option is enabled by default. If disabled, then it immediately reduces the Java heap to the target size @@ -2730,7 +2730,7 @@ Java HotSpot VM. `MaxHeapFreeRatio` option to keep the Java heap small by reducing the dynamic footprint for embedded applications. -`-XX:StringDeduplicationAgeThreshold=`*threshold* +[`-XX:StringDeduplicationAgeThreshold=`]{#-XX_StringDeduplicationAgeThreshold}*threshold* : Identifies `String` objects reaching the specified age that are considered candidates for deduplication. An object's age is a measure of how many times it has survived garbage collection. This is sometimes referred to as @@ -2741,14 +2741,14 @@ Java HotSpot VM. default value for this option is `3`. See the `-XX:+UseStringDeduplication` option. -`-XX:SurvivorRatio=`*ratio* +[`-XX:SurvivorRatio=`]{#-XX_SurvivorRatio}*ratio* : Sets the ratio between eden space size and survivor space size. By default, this option is set to 8. The following example shows how to set the eden/survivor space ratio to 4: > `-XX:SurvivorRatio=4` -`-XX:TargetSurvivorRatio=`*percent* +[`-XX:TargetSurvivorRatio=`]{#-XX_TargetSurvivorRatio}*percent* : Sets the desired percentage of survivor space (0 to 100) used after young garbage collection. By default, this option is set to 50%. @@ -2757,7 +2757,7 @@ Java HotSpot VM. > `-XX:TargetSurvivorRatio=30` -`-XX:TLABSize=`*size* +[`-XX:TLABSize=`]{#-XX_TLABSize}*size* : Sets the initial size (in bytes) of a thread-local allocation buffer (TLAB). Append the letter `k` or `K` to indicate kilobytes, `m` or `M` to indicate megabytes, or `g` or `G` to indicate gigabytes. If this option is @@ -2767,13 +2767,13 @@ Java HotSpot VM. > `-XX:TLABSize=512k` -`-XX:+UseAdaptiveSizePolicy` +[`-XX:+UseAdaptiveSizePolicy`]{#-XX__UseAdaptiveSizePolicy} : Enables the use of adaptive generation sizing. This option is enabled by default. To disable adaptive generation sizing, specify `-XX:-UseAdaptiveSizePolicy` and set the size of the memory allocation pool explicitly. See the `-XX:SurvivorRatio` option. -`-XX:+UseG1GC` +[`-XX:+UseG1GC`]{#-XX__UseG1GC} : Enables the use of the garbage-first (G1) garbage collector. It's a server-style garbage collector, targeted for multiprocessor machines with a large amount of RAM. This option meets GC pause time goals with high @@ -2783,7 +2783,7 @@ Java HotSpot VM. pause time below 0.5 seconds). By default, this option is enabled and G1 is used as the default garbage collector. -`-XX:+UseGCOverheadLimit` +[`-XX:+UseGCOverheadLimit`]{#-XX__UseGCOverheadLimit} : Enables the use of a policy that limits the proportion of time spent by the JVM on GC before an `OutOfMemoryError` exception is thrown. This option is enabled, by default, and the parallel GC will throw an `OutOfMemoryError` @@ -2793,26 +2793,26 @@ Java HotSpot VM. little or no progress. To disable this option, specify the option `-XX:-UseGCOverheadLimit`. -`-XX:+UseNUMA` +[`-XX:+UseNUMA`]{#-XX__UseNUMA} : Enables performance optimization of an application on a machine with nonuniform memory architecture (NUMA) by increasing the application's use of lower latency memory. The default value for this option depends on the garbage collector. -`-XX:+UseParallelGC` +[`-XX:+UseParallelGC`]{#-XX__UseParallelGC} : Enables the use of the parallel scavenge garbage collector (also known as the throughput collector) to improve the performance of your application by leveraging multiple processors. By default, this option is disabled and the default collector is used. -`-XX:+UseSerialGC` +[`-XX:+UseSerialGC`]{#-XX__UseSerialGC} : Enables the use of the serial garbage collector. This is generally the best choice for small and simple applications that don't require any special functionality from garbage collection. By default, this option is disabled and the default collector is used. -`-XX:+UseStringDeduplication` +[`-XX:+UseStringDeduplication`]{#-XX__UseStringDeduplication} : Enables string deduplication. By default, this option is disabled. To use this option, you must enable the garbage-first (G1) garbage collector. @@ -2822,34 +2822,34 @@ Java HotSpot VM. character array, identical `String` objects can point to and share the same character array. -`-XX:+UseTLAB` +[`-XX:+UseTLAB`]{#-XX__UseTLAB} : Enables the use of thread-local allocation blocks (TLABs) in the young generation space. This option is enabled by default. To disable the use of TLABs, specify the option `-XX:-UseTLAB`. -`-XX:+UseZGC` +[`-XX:+UseZGC`]{#-XX__UseZGC} : Enables the use of the Z garbage collector (ZGC). This is a low latency garbage collector, providing max pause times of a few milliseconds, at some throughput cost. Pause times are independent of what heap size is used. Supports heap sizes from 8MB to 16TB. -`-XX:ZAllocationSpikeTolerance=`*factor* +[`-XX:ZAllocationSpikeTolerance=`]{#-XX_ZAllocationSpikeTolerance}*factor* : Sets the allocation spike tolerance for ZGC. By default, this option is set to 2.0. This factor describes the level of allocation spikes to expect. For example, using a factor of 3.0 means the current allocation rate can be expected to triple at any time. -`-XX:ZCollectionInterval=`*seconds* +[`-XX:ZCollectionInterval=`]{#-XX_ZCollectionInterval}*seconds* : Sets the maximum interval (in seconds) between two GC cycles when using ZGC. By default, this option is set to 0 (disabled). -`-XX:ZFragmentationLimit=`*percent* +[`-XX:ZFragmentationLimit=`]{#-XX_ZFragmentationLimit}*percent* : Sets the maximum acceptable heap fragmentation (in percent) for ZGC. By default, this option is set to 25. Using a lower value will cause the heap to be compacted more aggressively, to reclaim more memory at the cost of using more CPU time. -`-XX:+ZProactive` +[`-XX:+ZProactive`]{#-XX__ZProactive} : Enables proactive GC cycles when using ZGC. By default, this option is enabled. ZGC will start a proactive GC cycle if doing so is expected to have minimal impact on the running application. This is useful if the @@ -2857,27 +2857,27 @@ Java HotSpot VM. want to keep the heap size down and allow reference processing to happen even when there are a lot of free space on the heap. -`-XX:+ZUncommit` +[`-XX:+ZUncommit`]{#-XX__ZUncommit} : Enables uncommitting of unused heap memory when using ZGC. By default, this option is enabled. Uncommitting unused heap memory will lower the memory footprint of the JVM, and make that memory available for other processes to use. -`-XX:ZUncommitDelay=`*seconds* +[`-XX:ZUncommitDelay=`]{#-XX_ZUncommitDelay}*seconds* : Sets the amount of time (in seconds) that heap memory must have been unused before being uncommitted. By default, this option is set to 300 (5 minutes). Committing and uncommitting memory are relatively expensive operations. Using a lower value will cause heap memory to be uncommitted earlier, at the risk of soon having to commit it again. -`-XX:+UseShenandoahGC` +[`-XX:+UseShenandoahGC`]{#-XX__UseShenandoahGC} : Enables the use of the Shenandoah garbage collector. This is a low pause time, concurrent garbage collector. Its pause times are not proportional to the size of the heap. Shenandoah garbage collector can work with compressed pointers. See `-XX:UseCompressedOops` for further information about compressed pointers. -`-XX:ShenandoahGCMode=`*mode* +[`-XX:ShenandoahGCMode=`]{#-XX_ShenandoahGCMode}*mode* : Sets the GC mode for Shenandoah GC to use. By default, this option is set to `satb`. Among other things, this defines which barriers are in use. Possible mode values include the following: @@ -2891,7 +2891,7 @@ Java HotSpot VM. generational. Please see [JEP 404](https://openjdk.org/jeps/404) and [JEP 521](https://openjdk.org/jeps/521) for its advantages and risks. -`-XX:ShenandoahGCHeuristics=`*heuristics* +[`-XX:ShenandoahGCHeuristics=`]{#-XX_ShenandoahGCHeuristics}*heuristics* : Sets the heuristics for Shenandoah GC to use. By default, this option is set to `adaptive`. This fine-tunes the GC mode selected, by choosing when to start the GC, how much to process on each cycle, and what other features @@ -2916,7 +2916,7 @@ These `java` options are deprecated and might be removed in a future JDK release. They're still accepted and acted upon, but a warning is issued when they're used. -`-Xloggc:`*filename* +[`-Xloggc:`]{#-Xloggc_}*filename* : Sets the file to which verbose GC events information should be redirected for logging. The `-Xloggc` option overrides `-verbose:gc` if both are given with the same java command. `-Xloggc:`*filename* is replaced by @@ -2927,11 +2927,11 @@ they're used. `-Xlog:gc:garbage-collection.log` -`-XX:+FlightRecorder` +[`-XX:+FlightRecorder`]{#-XX__FlightRecorder} : Enables the use of Java Flight Recorder (JFR) during the runtime of the application. Since JDK 8u40 this option has not been required to use JFR. -`-XX:+ParallelRefProcEnabled` +[`-XX:+ParallelRefProcEnabled`]{#-XX__ParallelRefProcEnabled} : Enables parallel reference processing. By default, collectors employing multiple threads perform parallel reference processing if the number of parallel threads to use is larger than one. @@ -2939,7 +2939,7 @@ they're used. (`-XX:+UseParallelGC` or `-XX:+UseG1GC`). Other collectors employing multiple threads always perform reference processing in parallel. -`-XX:MaxRAM=`*size* +[`-XX:MaxRAM=`]{#-XX_MaxRAM}*size* : Sets the maximum amount of memory that the JVM may use for the Java heap before applying ergonomics heuristics. The default value is the amount of available memory to the JVM process. @@ -2958,13 +2958,13 @@ they're used. > `-XX:MaxRAM=2G` -`-XX:+AggressiveHeap` +[`-XX:+AggressiveHeap`]{#-XX__AggressiveHeap} : Enables Java heap optimization. This sets various parameters to be optimal for long-running jobs with intensive memory allocation, based on the configuration of the computer (RAM and CPU). By default, the option is disabled and the heap sizes are configured less aggressively. -`-XX:+NeverActAsServerClassMachine` +[`-XX:+NeverActAsServerClassMachine`]{#-XX__NeverActAsServerClassMachine} : Enable the "Client VM emulation" mode which only uses the C1 JIT compiler, a 32Mb CodeCache and the Serial GC. The maximum amount of memory that the JVM may use (controlled by the `-XX:MaxRAM=n` flag) is set to 1GB by default. @@ -2989,7 +2989,7 @@ they're used. These `java` options are still accepted but ignored, and a warning is issued when they're used. -`--illegal-access=`*parameter* +[`--illegal-access=`]{#--illegal-access}*parameter* : Controlled _relaxed strong encapsulation_, as defined in [JEP 261](https://openjdk.org/jeps/261#Relaxed-strong-encapsulation). This option was deprecated in JDK 16 by [JEP @@ -3570,7 +3570,7 @@ Legacy Runtime Flag Xlog Configuration Comment The following are `-Xlog` examples. -`-Xlog` +[`-Xlog`]{#-Xlog} : Logs all messages by using the `info` level to `stdout` with `uptime`, `levels`, and `tags` decorations. This is equivalent to using: @@ -4075,7 +4075,7 @@ The deployment of the AOT cache is divided into three phases: The AOT cache can be used with the following command-line options: -`-XX:AOTCache=`*cachefile* +[`-XX:AOTCache=`]{#-XX_AOTCache}*cachefile* : Specifies the location of the AOT cache. The standard extension for *cachefile* is `.aot`. This option cannot be used together with `-XX:AOTCacheOutput`. @@ -4084,13 +4084,13 @@ The AOT cache can be used with the following command-line options: The *cachefile* is written by AOT mode `create`. In that case, this option is equivalent to `-XX:AOTCacheOutput=`*cachefile*. -`-XX:AOTCacheOutput=`*cachefile* +[`-XX:AOTCacheOutput=`]{#-XX_AOTCacheOutput}*cachefile* : Specifies the location of the AOT cache to write. The standard extension for *cachefile* is `.aot`. This option cannot be used together with `-XX:AOTCache`. This option is compatible with `AOTMode` settings of `record`, `create`, or `auto` (the default). -`-XX:AOTConfiguration=`*configfile* +[`-XX:AOTConfiguration=`]{#-XX_AOTConfiguration}*configfile* : Specifies the AOT Configuration file for the JVM to write to or read from. The standard extension for *configfile* is `.aotconfig`. @@ -4098,7 +4098,7 @@ The AOT cache can be used with the following command-line options: The *configfile* is read by AOT mode `create`, and written by the other applicable modes. If the AOT mode is `auto`, then `AOTCacheOutput` must also be present. -`-XX:AOTMode=`*mode* +[`-XX:AOTMode=`]{#-XX_AOTMode}*mode* : Specifies the AOT Mode for this run. *mode* must be one of the following: `auto`, `off`, `record`, `create`, or `on`. @@ -4170,7 +4170,7 @@ The AOT cache can be used with the following command-line options: options are compatible with the AOT cache. An alternative is to run your application with `-XX:AOTMode=auto -Xlog:aot` to see if the AOT cache can be used or not. -`-XX:+AOTClassLinking` +[`-XX:+AOTClassLinking`]{#-XX__AOTClassLinking} : If this option is enabled, the JVM will perform more advanced optimizations (such as ahead-of-time resolution of invokedynamic instructions) when creating the AOT cache. As a result, the application will see further improvements diff --git a/src/java.base/share/man/keytool.md b/src/java.base/share/man/keytool.md index 19ba8c34912..1d70bd2f5f8 100644 --- a/src/java.base/share/man/keytool.md +++ b/src/java.base/share/man/keytool.md @@ -1,5 +1,5 @@ --- -# Copyright (c) 1998, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 1998, 2026, 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 @@ -211,7 +211,7 @@ perform. ## Commands for Creating or Adding Data to the Keystore -`-gencert` +[`-gencert`]{#command-gencert} : The following are the available options for the `-gencert` command: - {`-rfc`}: Output in RFC (Request For Comment) style @@ -328,7 +328,7 @@ perform. > `keytool -alias e1 -certreq | keytool -alias ca2 -gencert > e1.cert` -`-genkeypair` +[`-genkeypair`]{#option-genkeypair} : The following are the available options for the `-genkeypair` command: - {`-alias` *alias*}: Alias name of the entry to process @@ -478,7 +478,7 @@ perform. specified by `-startdate`, or the current date when `-startdate` isn't specified) for which the certificate should be considered valid. -`-genseckey` +[`-genseckey`]{#command-genseckey} : The following are the available options for the `-genseckey` command: - {`-alias` *alias*}: Alias name of the entry to process @@ -521,7 +521,7 @@ perform. the same password that is used for the `-keystore`. The `-keypass` value must contain at least six characters. -`-importcert` +[`-importcert`]{#command-importcert} : The following are the available options for the `-importcert` command: - {`-noprompt`}: Do not prompt @@ -586,7 +586,7 @@ perform. entry, then the `keytool` command assumes that you're importing a certificate reply. -`-importpass` +[`-importpass`]{#command-importpass} : The following are the available options for the `-importpass` command: - {`-alias` *alias*}: Alias name of the entry to process @@ -629,7 +629,7 @@ perform. ## Commands for Importing Contents from Another Keystore -`-importkeystore` +[`-importkeystore`]{#command-importkeystore} : The following are the available options for the `-importkeystore` command: - `-srckeystore` *keystore*: Source keystore name @@ -724,7 +724,7 @@ perform. ## Commands for Generating a Certificate Request -`-certreq` +[`-certreq`]{#command-certreq} : The following are the available options for the `-certreq` command: - {`-alias` *alias*}: Alias name of the entry to process @@ -786,7 +786,7 @@ perform. ## Commands for Exporting Data -`-exportcert` +[`-exportcert`]{#command-exportcert} : The following are the available options for the `-exportcert` command: - {`-rfc`}: Output in RFC style @@ -834,7 +834,7 @@ perform. ## Commands for Displaying Data -`-list` +[`-list`]{#command-list} : The following are the available options for the `-list` command: - {`-rfc`}: Output in RFC style @@ -881,7 +881,7 @@ perform. You can't specify both `-v` and `-rfc` in the same command. Otherwise, an error is reported. -`-printcert` +[`-printcert`]{#command-printcert} : The following are the available options for the `-printcert` command: - {`-rfc`}: Output in RFC style @@ -946,7 +946,7 @@ perform. trusted certificate in the user keystore (specified by `-keystore`) or in the `cacerts` keystore (if `-trustcacerts` is specified). -`-printcertreq` +[`-printcertreq`]{#command-printcertreq} : The following are the available options for the `-printcertreq` command: - {`-file` *file*}: Input file name @@ -958,7 +958,7 @@ perform. command. The command reads the request from file. If there is no file, then the request is read from the standard input. -`-printcrl` +[`-printcrl`]{#command-printcrl} : The following are the available options for the `-printcrl` command: - {`-file crl`}: Input file name @@ -999,7 +999,7 @@ perform. ## Commands for Managing the Keystore -`-storepasswd` +[`-storepasswd`]{#command-storepasswd} : The following are the available options for the `-storepasswd` command: - \[`-new` *arg*\]: New password @@ -1029,7 +1029,7 @@ perform. integrity of the keystore contents. The new password is set by `-new` *arg* and must contain at least six characters. -`-keypasswd` +[`-keypasswd`]{#command-keypasswd} : The following are the available options for the `-keypasswd` command: - {`-alias` *alias*}: Alias name of the entry to process @@ -1069,7 +1069,7 @@ perform. If the `-new` option isn't provided at the command line, then the user is prompted for it. -`-delete` +[`-delete`]{#command-delete} : The following are the available options for the `-delete` command: - \[`-alias` *alias*\]: Alias name of the entry to process @@ -1101,7 +1101,7 @@ perform. keystore. When not provided at the command line, the user is prompted for the `alias`. -`-changealias` +[`-changealias`]{#command-changealias} : The following are the available options for the `-changealias` command: - {`-alias` *alias*}: Alias name of the entry to process @@ -1143,7 +1143,7 @@ perform. ## Commands for Displaying Security-related Information -`-showinfo` +[`-showinfo`]{#command-showinfo} : The following are the available options for the `-showinfo` command: - {`-tls`}: Displays TLS configuration information @@ -1185,10 +1185,10 @@ environment or memory usage. For a list of possible interpreter options, enter These options can appear for all commands operating on a keystore: -`-storetype` *storetype* +[`-storetype`]{#option-storetype} *storetype* : This qualifier specifies the type of keystore to be instantiated. -`-keystore` *keystore* +[`-keystore`]{#option-keystore} *keystore* : The keystore location. If the JKS `storetype` is used and a keystore file doesn't yet exist, then @@ -1206,13 +1206,13 @@ These options can appear for all commands operating on a keystore: if the keystore isn't file-based. For example, when the keystore resides on a hardware token device. -`-cacerts` *cacerts* +[`-cacerts`]{#option-cacerts} *cacerts* : Operates on the *cacerts* keystore . This option is equivalent to `-keystore` *path\_to\_cacerts* `-storetype` *type\_of\_cacerts*. An error is reported if the `-keystore` or `-storetype` option is used with the `-cacerts` option. -`-storepass` \[`:env` \| `:file` \] *argument* +[`-storepass`]{#option-storepass} \[`:env` \| `:file` \] *argument* : The password that is used to protect the integrity of the keystore. If the modifier `env` or `file` isn't specified, then the password has the @@ -1237,22 +1237,22 @@ These options can appear for all commands operating on a keystore: a password is not specified, then the integrity of the retrieved information can't be verified and a warning is displayed. -`-providername` *name* +[`-providername`]{#option-providername} *name* : Used to identify a cryptographic service provider's name when listed in the security properties file. -`-addprovider` *name* +[`-addprovider`]{#option-addprovider} *name* : Used to add a security provider by name (such as SunPKCS11) . -`-providerclass` *class* +[`-providerclass`]{#option-providerclass} *class* : Used to specify the name of a cryptographic service provider's master class file when the service provider isn't listed in the security properties file. -`-providerpath` *list* +[`-providerpath`]{#option-providerpath} *list* : Used to specify the provider classpath. -`-providerarg` *arg* +[`-providerarg`]{#option-providerarg} *arg* : Used with the `-addprovider` or `-providerclass` option to represent an optional string input argument for the constructor of *class* name. @@ -1263,7 +1263,7 @@ These options can appear for all commands operating on a keystore: following two options, `-srcprotected` and `-destprotected`, are provided for the source keystore and the destination keystore respectively. -`-ext` {*name*{`:critical`} {`=`*value*}} +[`-ext`]{#option-ext} {*name*{`:critical`} {`=`*value*}} : Denotes an X.509 certificate extension. The option can be used in `-genkeypair` and `-gencert` to embed extensions into the generated certificate, or in `-certreq` to show what extensions are requested in the @@ -1276,7 +1276,7 @@ These options can appear for all commands operating on a keystore: `isCritical` attribute is `true`; otherwise, it is `false`. You can use `:c` in place of `:critical`. -`-conf` *file* +[`-conf`]{#option-conf} *file* : Specifies a pre-configured options file. ## Pre-configured options file diff --git a/src/java.base/share/native/libjimage/imageDecompressor.cpp b/src/java.base/share/native/libjimage/imageDecompressor.cpp index 748bbf8203f..4946e645c55 100644 --- a/src/java.base/share/native/libjimage/imageDecompressor.cpp +++ b/src/java.base/share/native/libjimage/imageDecompressor.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -85,10 +85,6 @@ void ImageDecompressor::image_decompressor_init() { } } -void ImageDecompressor::image_decompressor_close() { - delete[] _decompressors; -} - /* * Locate decompressor. */ diff --git a/src/java.base/share/native/libjimage/imageDecompressor.hpp b/src/java.base/share/native/libjimage/imageDecompressor.hpp index 16f354935c3..057fa15917c 100644 --- a/src/java.base/share/native/libjimage/imageDecompressor.hpp +++ b/src/java.base/share/native/libjimage/imageDecompressor.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -105,6 +105,7 @@ private: protected: ImageDecompressor(const char* name) : _name(name) { } + virtual void decompress_resource(u1* data, u1* uncompressed, ResourceHeader* header, const ImageStrings* strings) = 0; @@ -166,6 +167,6 @@ private: public: SharedStringDecompressor(const char* sym) : ImageDecompressor(sym){} void decompress_resource(u1* data, u1* uncompressed, ResourceHeader* header, - const ImageStrings* strings); + const ImageStrings* strings); }; #endif // LIBJIMAGE_IMAGEDECOMPRESSOR_HPP diff --git a/src/java.base/share/native/libjli/java.c b/src/java.base/share/native/libjli/java.c index 6072bff50c6..4621ab588d1 100644 --- a/src/java.base/share/native/libjli/java.c +++ b/src/java.base/share/native/libjli/java.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1995, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1995, 2026, 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 @@ -1505,6 +1505,7 @@ InitializeJVM(JavaVM **pvm, JNIEnv **penv, InvocationFunctions *ifn) r = ifn->CreateJavaVM(pvm, (void **)penv, &args); JLI_MemFree(options); + options = NULL; return r == JNI_OK; } @@ -2203,6 +2204,7 @@ FreeKnownVMs() knownVMs[i].name = NULL; } JLI_MemFree(knownVMs); + knownVMs = NULL; } /* @@ -2276,8 +2278,9 @@ ShowSplashScreen() (void)UnsetEnv(SPLASH_JAR_ENV_ENTRY); JLI_MemFree(splash_jar_entry); + splash_jar_entry = NULL; JLI_MemFree(splash_file_entry); - + splash_file_entry = NULL; } static const char* GetFullVersion() diff --git a/src/java.base/unix/classes/sun/nio/fs/UnixFileAttributes.java b/src/java.base/unix/classes/sun/nio/fs/UnixFileAttributes.java index 55661ff12a3..d41df6dd5be 100644 --- a/src/java.base/unix/classes/sun/nio/fs/UnixFileAttributes.java +++ b/src/java.base/unix/classes/sun/nio/fs/UnixFileAttributes.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2026, 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 @@ -98,7 +98,8 @@ class UnixFileAttributes path, flag, attrs); if (errno == 0) return attrs; - else if (errno == UnixConstants.ENOENT) + else if (errno == UnixConstants.ENOENT || + errno == UnixConstants.ENOTDIR) return null; else throw new UnixException(errno); diff --git a/src/java.base/unix/classes/sun/nio/fs/UnixSecureDirectoryStream.java b/src/java.base/unix/classes/sun/nio/fs/UnixSecureDirectoryStream.java index 5c0693870e6..bafcd06d9e7 100644 --- a/src/java.base/unix/classes/sun/nio/fs/UnixSecureDirectoryStream.java +++ b/src/java.base/unix/classes/sun/nio/fs/UnixSecureDirectoryStream.java @@ -202,21 +202,21 @@ class UnixSecureDirectoryStream { UnixPath from = getName(fromObj); UnixPath to = getName(toObj); - if (dir == null) - throw new NullPointerException(); - if (!(dir instanceof UnixSecureDirectoryStream)) + if (dir != null && !(dir instanceof UnixSecureDirectoryStream)) throw new ProviderMismatchException(); UnixSecureDirectoryStream that = (UnixSecureDirectoryStream)dir; + int todfd = that != null ? that.dfd : AT_FDCWD; // lock ordering doesn't matter this.ds.readLock().lock(); try { - that.ds.readLock().lock(); + if (that != null) + that.ds.readLock().lock(); try { - if (!this.ds.isOpen() || !that.ds.isOpen()) + if (!this.ds.isOpen() || (that != null && !that.ds.isOpen())) throw new ClosedDirectoryStreamException(); try { - renameat(this.dfd, from.asByteArray(), that.dfd, to.asByteArray()); + renameat(this.dfd, from.asByteArray(), todfd, to.asByteArray()); } catch (UnixException x) { if (x.errno() == EXDEV) { throw new AtomicMoveNotSupportedException( @@ -225,7 +225,8 @@ class UnixSecureDirectoryStream x.rethrowAsIOException(from, to); } } finally { - that.ds.readLock().unlock(); + if (that != null) + that.ds.readLock().unlock(); } } finally { this.ds.readLock().unlock(); diff --git a/src/java.base/unix/native/libjava/ProcessImpl_md.c b/src/java.base/unix/native/libjava/ProcessImpl_md.c index f7531ad5abe..12597fbb650 100644 --- a/src/java.base/unix/native/libjava/ProcessImpl_md.c +++ b/src/java.base/unix/native/libjava/ProcessImpl_md.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1995, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1995, 2026, 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 @@ -729,12 +729,13 @@ Java_java_lang_ProcessImpl_forkAndExec(JNIEnv *env, if ((fds[0] == -1 && pipe(in) < 0) || (fds[1] == -1 && pipe(out) < 0) || - (fds[2] == -1 && pipe(err) < 0) || + (fds[2] == -1 && !redirectErrorStream && pipe(err) < 0) || // if not redirecting create the pipe (pipe(childenv) < 0) || (pipe(fail) < 0)) { throwInternalIOException(env, errno, "Bad file descriptor", mode); goto Catch; } + c->fds[0] = fds[0]; c->fds[1] = fds[1]; c->fds[2] = fds[2]; @@ -764,17 +765,19 @@ Java_java_lang_ProcessImpl_forkAndExec(JNIEnv *env, assert(resultPid != 0); if (resultPid < 0) { + char * failMessage = "unknown"; switch (c->mode) { case MODE_VFORK: - throwInternalIOException(env, errno, "vfork failed", c->mode); + failMessage = "vfork failed"; break; case MODE_FORK: - throwInternalIOException(env, errno, "fork failed", c->mode); + failMessage = "fork failed"; break; case MODE_POSIX_SPAWN: - throwInternalIOException(env, errno, "posix_spawn failed", c->mode); + failMessage = "posix_spawn failed"; break; } + throwInternalIOException(env, errno, failMessage, c->mode); goto Catch; } close(fail[1]); fail[1] = -1; /* See: WhyCantJohnnyExec (childproc.c) */ diff --git a/src/java.base/unix/native/libjava/io_util_md.c b/src/java.base/unix/native/libjava/io_util_md.c index 2e81cbd05c2..bcac334191c 100644 --- a/src/java.base/unix/native/libjava/io_util_md.c +++ b/src/java.base/unix/native/libjava/io_util_md.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 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,6 +27,7 @@ #include "jvm.h" #include "io_util.h" #include "io_util_md.h" +#include #include #include @@ -75,20 +76,26 @@ FD handleOpen(const char *path, int oflag, int mode) { FD fd; RESTARTABLE(open(path, oflag, mode), fd); - if (fd != -1) { - struct stat buf; - int result; - RESTARTABLE(fstat(fd, &buf), result); - if (result != -1) { - if (S_ISDIR(buf.st_mode)) { - close(fd); - errno = EISDIR; - fd = -1; - } - } else { + // No further checking is needed if the file is not a + // directory or open returned an error + if (fd == -1 || ((oflag & O_ACCMODE) != O_RDONLY) != 0) { + return fd; + } + + // FileInputStream is specified to throw if the + // file is a directory + struct stat buf; + int result; + RESTARTABLE(fstat(fd, &buf), result); + if (result != -1) { + if (S_ISDIR(buf.st_mode)) { close(fd); + errno = EISDIR; fd = -1; } + } else { + close(fd); + fd = -1; } return fd; } diff --git a/src/java.base/unix/native/libnet/net_util_md.c b/src/java.base/unix/native/libnet/net_util_md.c index b0915615d96..48cc1a7bb02 100644 --- a/src/java.base/unix/native/libnet/net_util_md.c +++ b/src/java.base/unix/native/libnet/net_util_md.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, 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 @@ -68,13 +68,14 @@ NET_ThrowByNameWithLastError(JNIEnv *env, const char *name, void NET_ThrowNew(JNIEnv *env, int errorNumber, char *msg) { char fullMsg[512]; - if (!msg) { - msg = "no further information"; - } switch(errorNumber) { case EBADF: - jio_snprintf(fullMsg, sizeof(fullMsg), "socket closed: %s", msg); - JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", fullMsg); + if (msg == NULL) { + JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "socket closed"); + } else { + jio_snprintf(fullMsg, sizeof(fullMsg), "socket closed: %s", msg); + JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", fullMsg); + } break; default: errno = errorNumber; @@ -279,7 +280,7 @@ NET_InetAddressToSockaddr(JNIEnv *env, jobject iaObj, int port, } else { jint address; if (family != java_net_InetAddress_IPv4) { - JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Protocol family unavailable"); + JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "IPv6 protocol family unavailable"); return -1; } address = getInetAddress_addr(env, iaObj); diff --git a/src/java.base/windows/native/libjava/WinCAPISeedGenerator.c b/src/java.base/windows/native/libjava/WinCAPISeedGenerator.c index e447ecc43a9..deb79c7e4cb 100644 --- a/src/java.base/windows/native/libjava/WinCAPISeedGenerator.c +++ b/src/java.base/windows/native/libjava/WinCAPISeedGenerator.c @@ -31,10 +31,6 @@ /* * Get a random seed from the MS CryptoAPI. Return true if successful, false * otherwise. - * - * Some early versions of Windows 95 do not support the required functions. - * Use runtime linking to avoid problems. - * */ JNIEXPORT jboolean JNICALL Java_sun_security_provider_NativeSeedGenerator_nativeGenerateSeed (JNIEnv *env, jclass clazz, jbyteArray randArray) diff --git a/src/java.base/windows/native/libnet/net_util_md.c b/src/java.base/windows/native/libnet/net_util_md.c index 410d141e790..5abdd8d4c2e 100644 --- a/src/java.base/windows/native/libnet/net_util_md.c +++ b/src/java.base/windows/native/libnet/net_util_md.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, 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 @@ -139,13 +139,6 @@ NET_ThrowNew(JNIEnv *env, int errorNum, char *msg) return; } - /* - * Default message text if not provided - */ - if (!msg) { - msg = "no further information"; - } - /* * Check table for known winsock errors */ @@ -163,13 +156,22 @@ NET_ThrowNew(JNIEnv *env, int errorNum, char *msg) */ if (i < table_size) { excP = (char *)winsock_errors[i].exc; - jio_snprintf(fullMsg, sizeof(fullMsg), "%s: %s", - (char *)winsock_errors[i].errString, msg); + if (msg == NULL) { + jio_snprintf(fullMsg, sizeof(fullMsg), "%s", + (char *)winsock_errors[i].errString); + } else { + jio_snprintf(fullMsg, sizeof(fullMsg), "%s: %s", + (char *)winsock_errors[i].errString, msg); + } } else { - jio_snprintf(fullMsg, sizeof(fullMsg), - "Unrecognized Windows Sockets error: %d: %s", - errorNum, msg); - + if (msg == NULL) { + jio_snprintf(fullMsg, sizeof(fullMsg), + "Unrecognized Windows Sockets error: %d", errorNum); + } else { + jio_snprintf(fullMsg, sizeof(fullMsg), + "Unrecognized Windows Sockets error: %d: %s", + errorNum, msg); + } } /* @@ -513,7 +515,7 @@ NET_InetAddressToSockaddr(JNIEnv *env, jobject iaObj, int port, } else { jint address; if (family != java_net_InetAddress_IPv4) { - JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "Protocol family unavailable"); + JNU_ThrowByName(env, JNU_JAVANETPKG "SocketException", "IPv6 protocol family unavailable"); return -1; } address = getInetAddress_addr(env, iaObj); diff --git a/src/java.base/windows/native/libnio/ch/Net.c b/src/java.base/windows/native/libnio/ch/Net.c index 814f502c48a..adfd67b5017 100644 --- a/src/java.base/windows/native/libnio/ch/Net.c +++ b/src/java.base/windows/native/libnio/ch/Net.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, 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 @@ -733,7 +733,7 @@ Java_sun_nio_ch_Net_pollConnect(JNIEnv* env, jclass this, jobject fdo, jlong tim NET_ThrowNew(env, lastError, "getsockopt"); } } else if (optError != NO_ERROR) { - NET_ThrowNew(env, optError, "getsockopt"); + NET_ThrowNew(env, optError, NULL); } return JNI_FALSE; } diff --git a/src/java.compiler/share/classes/javax/lang/model/element/AnnotationMirror.java b/src/java.compiler/share/classes/javax/lang/model/element/AnnotationMirror.java index 2d92504edb6..2ffed3f7942 100644 --- a/src/java.compiler/share/classes/javax/lang/model/element/AnnotationMirror.java +++ b/src/java.compiler/share/classes/javax/lang/model/element/AnnotationMirror.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, 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 @@ -36,6 +36,8 @@ import javax.lang.model.type.DeclaredType; * method. There is no guarantee that any particular annotation will * always be represented by the same object. * + * @jls 9.6 Annotation Interfaces + * @jls 9.7 Annotations * @since 1.6 */ public interface AnnotationMirror { diff --git a/src/java.compiler/share/classes/javax/lang/model/element/AnnotationValue.java b/src/java.compiler/share/classes/javax/lang/model/element/AnnotationValue.java index 77521dac191..71594d31150 100644 --- a/src/java.compiler/share/classes/javax/lang/model/element/AnnotationValue.java +++ b/src/java.compiler/share/classes/javax/lang/model/element/AnnotationValue.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, 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,14 +27,16 @@ package javax.lang.model.element; /** * Represents a value of an annotation interface element. - * A value is of one of the following types: - *

    • a wrapper class (such as {@link Integer}) for a primitive type - *
    • {@code String} - *
    • {@code TypeMirror} - *
    • {@code VariableElement} (representing an enum constant) - *
    • {@code AnnotationMirror} + * A value is of one of the following types (JLS {@jls 9.6.1}): + *
      • a {@linkplain java.lang##wrapperClass wrapper class} to hold a + * primitive type, such as an {@code Integer} object to hold an + * {@code int} + *
      • {@code String} representing a {@code String} + *
      • {@link javax.lang.model.type.TypeMirror TypeMirror} representing a {@code Class} literal + *
      • {@link VariableElement} representing an enum constant + *
      • {@link AnnotationMirror} representing an annotation *
      • {@code List} - * (representing the elements, in declared order, if the value is an array) + * representing the elements, in declared order, if the value is an array *
      * * @since 1.6 diff --git a/src/java.compiler/share/classes/javax/lang/model/element/ExecutableElement.java b/src/java.compiler/share/classes/javax/lang/model/element/ExecutableElement.java index 18923463248..cc0924fda61 100644 --- a/src/java.compiler/share/classes/javax/lang/model/element/ExecutableElement.java +++ b/src/java.compiler/share/classes/javax/lang/model/element/ExecutableElement.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, 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,6 +37,13 @@ import javax.lang.model.type.*; * clause, among other restrictions; see JLS {@jls 9.6.1} for details. * * @see ExecutableType + * @jls 8.4 Method Declarations + * @jls 8.6 Instance Initializers + * @jls 8.7 Static Initializers + * @jls 8.8 Constructor Declarations + * @jls 9.4 Method Declarations + * @jls 9.6.1 Annotation Interface Elements + * * @since 1.6 */ public interface ExecutableElement extends Element, Parameterizable { diff --git a/src/java.compiler/share/classes/javax/lang/model/element/PackageElement.java b/src/java.compiler/share/classes/javax/lang/model/element/PackageElement.java index 651bbe70eee..79b1fb84683 100644 --- a/src/java.compiler/share/classes/javax/lang/model/element/PackageElement.java +++ b/src/java.compiler/share/classes/javax/lang/model/element/PackageElement.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, 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 @@ -58,6 +58,8 @@ import javax.lang.model.type.TypeMirror; *
    * * @see javax.lang.model.util.Elements#getPackageOf + * @jls 7.4 Package Declarations + * * @since 1.6 */ public interface PackageElement extends Element, QualifiedNameable { @@ -87,6 +89,7 @@ public interface PackageElement extends Element, QualifiedNameable { * @return the fully qualified name of this package, or an * empty name if this is an unnamed package * @jls 6.7 Fully Qualified Names and Canonical Names + * @jls 7.4.1 Named Packages */ Name getQualifiedName(); diff --git a/src/java.compiler/share/classes/javax/lang/model/element/Parameterizable.java b/src/java.compiler/share/classes/javax/lang/model/element/Parameterizable.java index cf0bbdfba37..a4280fbcf29 100644 --- a/src/java.compiler/share/classes/javax/lang/model/element/Parameterizable.java +++ b/src/java.compiler/share/classes/javax/lang/model/element/Parameterizable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2009, 2026, 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,6 +30,10 @@ import java.util.List; /** * A mixin interface for an element that has type parameters. * + * @jls 4.5 Parameterized Types + * @jls 8.4.4 Generic Methods + * @jls 8.8.4 Generic Constructors + * * @since 1.7 */ public interface Parameterizable extends Element { diff --git a/src/java.compiler/share/classes/javax/lang/model/element/QualifiedNameable.java b/src/java.compiler/share/classes/javax/lang/model/element/QualifiedNameable.java index 1b0316f893f..555d90c1b13 100644 --- a/src/java.compiler/share/classes/javax/lang/model/element/QualifiedNameable.java +++ b/src/java.compiler/share/classes/javax/lang/model/element/QualifiedNameable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2009, 2026, 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,6 +28,10 @@ package javax.lang.model.element; /** * A mixin interface for an element that has a qualified name. * + * @jls 6.5.3.2 Qualified Package Names + * @jls 6.5.5.2 Qualified Type Names + * @jls 6.7 Fully Qualified Names and Canonical Names + * * @since 1.7 */ public interface QualifiedNameable extends Element { diff --git a/src/java.compiler/share/classes/javax/lang/model/element/TypeElement.java b/src/java.compiler/share/classes/javax/lang/model/element/TypeElement.java index abf95bcefad..3dcc8e8c7fd 100644 --- a/src/java.compiler/share/classes/javax/lang/model/element/TypeElement.java +++ b/src/java.compiler/share/classes/javax/lang/model/element/TypeElement.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, 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 @@ -80,7 +80,16 @@ import javax.lang.model.util.*; * javax.lang.model.util.Elements#getAllTypeElements(CharSequence) * queried for} in the configured environment * + * * @see DeclaredType + * @jls 8.1 Class Declarations + * @jls 8.5 Member Class and Interface Declarations + * @jls 8.9 Enum Classes + * @jls 8.10 Record Classes + * @jls 9.1 Interface Declarations + * @jls 9.5 Member Class and Interface Declarations + * @jls 9.6 Annotation Interfaces + * * @since 1.6 */ public interface TypeElement extends Element, Parameterizable, QualifiedNameable { diff --git a/src/java.compiler/share/classes/javax/lang/model/element/TypeParameterElement.java b/src/java.compiler/share/classes/javax/lang/model/element/TypeParameterElement.java index cbaa8969126..abd06a3e038 100644 --- a/src/java.compiler/share/classes/javax/lang/model/element/TypeParameterElement.java +++ b/src/java.compiler/share/classes/javax/lang/model/element/TypeParameterElement.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, 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 @@ -35,6 +35,11 @@ import javax.lang.model.type.TypeVariable; * A type parameter declares a {@link TypeVariable}. * * @see TypeVariable + * @jls 8.1.2 Generic Classes and Type Parameters + * @jls 8.4.4 Generic Methods + * @jls 8.8.4 Generic Constructors + * @jls 9.1.2 Generic Interfaces and Type Parameters + * * @since 1.6 */ public interface TypeParameterElement extends Element { diff --git a/src/java.compiler/share/classes/javax/lang/model/element/VariableElement.java b/src/java.compiler/share/classes/javax/lang/model/element/VariableElement.java index 4d2b2582901..b8f42c46b1e 100644 --- a/src/java.compiler/share/classes/javax/lang/model/element/VariableElement.java +++ b/src/java.compiler/share/classes/javax/lang/model/element/VariableElement.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, 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,6 +34,14 @@ import javax.lang.model.type.TypeKind; * parameter, local variable, resource variable, or exception * parameter. * + * @jls 8.3 Field Declaration + * @jls 8.9.1 Enum Constants + * @jls 8.4.1 Formal Parameters + * @jls 8.8.1 Formal Parameters + * @jls 14.4 Local Variable Declarations + * @jls 14.20 The {@code try} statement + * @jls 14.20.3 {@code try}-with-resources + * @since 1.6 */ public interface VariableElement extends Element { diff --git a/src/java.compiler/share/classes/javax/lang/model/type/TypeMirror.java b/src/java.compiler/share/classes/javax/lang/model/type/TypeMirror.java index 5bd205a6c4b..facdbe405dd 100644 --- a/src/java.compiler/share/classes/javax/lang/model/type/TypeMirror.java +++ b/src/java.compiler/share/classes/javax/lang/model/type/TypeMirror.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, 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 @@ -120,6 +120,15 @@ public interface TypeMirror extends AnnotatedConstruct { * The results of {@code t1.equals(t2)} and * {@code Types.isSameType(t1, t2)} may differ. * + * @apiNote The identity of a {@code TypeMirror} involves implicit + * state not directly accessible from its methods, including state + * about the presence of unrelated types. {@code TypeMirror} + * objects created by different implementations of these + * interfaces should not be expected to compare as equal + * even if "the same" type is being modeled; this is + * analogous to the inequality of {@code Class} objects for the + * same class file loaded through different class loaders. + * * @param obj the object to be compared with this type * @return {@code true} if the specified object is equal to this one */ diff --git a/src/java.compiler/share/classes/javax/lang/model/util/Types.java b/src/java.compiler/share/classes/javax/lang/model/util/Types.java index 951b56ed214..e7212a7e0be 100644 --- a/src/java.compiler/share/classes/javax/lang/model/util/Types.java +++ b/src/java.compiler/share/classes/javax/lang/model/util/Types.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, 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 @@ -106,6 +106,15 @@ public interface Types { * {@code TypeMirror} objects can have different annotations and * still be considered the same. * + * @apiNote The identity of a {@code TypeMirror} involves implicit + * state not directly accessible from its methods, including state + * about the presence of unrelated types. {@code TypeMirror} + * objects created by different implementations of these + * interfaces should not be expected to compare as equal + * even if "the same" type is being modeled; this is + * analogous to the inequality of {@code Class} objects for the + * same class file loaded through different class loaders. + * * @param t1 the first type * @param t2 the second type * @return {@code true} if and only if the two types are the same diff --git a/src/java.desktop/macosx/classes/com/apple/laf/AquaUtils.java b/src/java.desktop/macosx/classes/com/apple/laf/AquaUtils.java index da1785a9a63..5e68cc7a82c 100644 --- a/src/java.desktop/macosx/classes/com/apple/laf/AquaUtils.java +++ b/src/java.desktop/macosx/classes/com/apple/laf/AquaUtils.java @@ -35,8 +35,6 @@ import javax.swing.*; import javax.swing.border.Border; import javax.swing.plaf.UIResource; -import sun.awt.AppContext; - import sun.lwawt.macosx.CPlatformWindow; import sun.swing.SwingUtilities2; @@ -150,13 +148,34 @@ final class AquaUtils { protected abstract T create(); } - abstract static class RecyclableSingleton { - final T get() { - return AppContext.getSoftReferenceValue(this, () -> getInstance()); - } + abstract static class LazySingleton { + T instance; - void reset() { - AppContext.getAppContext().remove(this); + final T get() { + if (instance == null) { + instance = getInstance(); + } + return instance; + } + + abstract T getInstance(); + } + + abstract static class RecyclableSingleton { + + SoftReference ref; + + final T get() { + T instance; + if (ref != null) { + instance = ref.get(); + if (instance != null) { + return instance; + } + } + instance = getInstance(); + ref = new SoftReference<>(instance); + return instance; } abstract T getInstance(); @@ -197,11 +216,11 @@ final class AquaUtils { protected abstract V getInstance(K key); } - private static final RecyclableSingleton enableAnimations = new RecyclableSingleton() { + private static final LazySingleton enableAnimations = new LazySingleton() { @Override protected Boolean getInstance() { - final String sizeProperty = System.getProperty(ANIMATIONS_PROPERTY); - return !"false".equals(sizeProperty); // should be true by default + final String animationsProperty = System.getProperty(ANIMATIONS_PROPERTY); + return !"false".equals(animationsProperty); // should be true by default } }; private static boolean animationsEnabled() { diff --git a/src/java.desktop/macosx/classes/sun/lwawt/macosx/CDesktopPeer.java b/src/java.desktop/macosx/classes/sun/lwawt/macosx/CDesktopPeer.java index a4ec0767298..cc0e253f23b 100644 --- a/src/java.desktop/macosx/classes/sun/lwawt/macosx/CDesktopPeer.java +++ b/src/java.desktop/macosx/classes/sun/lwawt/macosx/CDesktopPeer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 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,10 @@ import java.awt.peer.DesktopPeer; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; +import java.lang.annotation.Native; import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; /** @@ -44,6 +47,12 @@ import java.net.URI; */ public final class CDesktopPeer implements DesktopPeer { + @Native private static final int OPEN = 0; + @Native private static final int BROWSE = 1; + @Native private static final int EDIT = 2; + @Native private static final int PRINT = 3; + @Native private static final int MAIL = 4; + @Override public boolean isSupported(Action action) { return true; @@ -51,27 +60,27 @@ public final class CDesktopPeer implements DesktopPeer { @Override public void open(File file) throws IOException { - this.lsOpenFile(file, false); + this.lsOpenFile(file, OPEN); } @Override public void edit(File file) throws IOException { - this.lsOpenFile(file, false); + this.lsOpenFile(file, EDIT); } @Override public void print(File file) throws IOException { - this.lsOpenFile(file, true); + this.lsOpenFile(file, PRINT); } @Override public void mail(URI uri) throws IOException { - this.lsOpen(uri); + this.lsOpen(uri, MAIL); } @Override public void browse(URI uri) throws IOException { - this.lsOpen(uri); + this.lsOpen(uri, BROWSE); } @Override @@ -162,24 +171,44 @@ public final class CDesktopPeer implements DesktopPeer { } } - private void lsOpen(URI uri) throws IOException { - int status = _lsOpenURI(uri.toString()); + private void lsOpen(URI uri, int action) throws IOException { + int status = _lsOpenURI(uri.toString(), action); if (status != 0 /* noErr */) { - throw new IOException("Failed to mail or browse " + uri + ". Error code: " + status); + String actionString = (action == MAIL) ? "mail" : "browse"; + throw new IOException("Failed to " + actionString + " " + uri + + ". Error code: " + status); } } - private void lsOpenFile(File file, boolean print) throws IOException { - int status = _lsOpenFile(file.getCanonicalPath(), print); + private void lsOpenFile(File file, int action) throws IOException { + int status = -1; + Path tmpFile = null; + String tmpTxtPath = null; + try { + if (action == EDIT) { + tmpFile = Files.createTempFile("TmpFile", ".txt"); + tmpTxtPath = tmpFile.toAbsolutePath().toString(); + } + status = _lsOpenFile(file.getCanonicalPath(), action, tmpTxtPath); + } catch (Exception e) { + throw new IOException("Failed to create tmp file: ", e); + } finally { + if (tmpFile != null) { + Files.deleteIfExists(tmpFile); + } + } if (status != 0 /* noErr */) { - throw new IOException("Failed to open, edit or print " + file + ". Error code: " + status); + String actionString = (action == OPEN) ? "open" + : (action == EDIT) ? "edit" : "print"; + throw new IOException("Failed to " + actionString + " " + file + + ". Error code: " + status); } } - private static native int _lsOpenURI(String uri); + private static native int _lsOpenURI(String uri, int action); - private static native int _lsOpenFile(String path, boolean print); + private static native int _lsOpenFile(String path, int action, String tmpTxtPath); } diff --git a/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPrinterJob.java b/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPrinterJob.java index 979eeb36239..508b1a843ef 100644 --- a/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPrinterJob.java +++ b/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPrinterJob.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2026, 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 @@ -355,20 +355,9 @@ public final class CPrinterJob extends RasterPrinterJob { validateDestination(destinationAttr); } - /* Get the range of pages we are to print. If the - * last page to print is unknown, then we print to - * the end of the document. Note that firstPage - * and lastPage are 0 based page indices. - */ - + // Note that firstPage is 0 based page index. int firstPage = getFirstPage(); - int lastPage = getLastPage(); - if(lastPage == Pageable.UNKNOWN_NUMBER_OF_PAGES) { - int totalPages = mDocument.getNumberOfPages(); - if (totalPages != Pageable.UNKNOWN_NUMBER_OF_PAGES) { - lastPage = mDocument.getNumberOfPages() - 1; - } - } + int totalPages = mDocument.getNumberOfPages(); try { synchronized (this) { @@ -393,7 +382,7 @@ public final class CPrinterJob extends RasterPrinterJob { try { // Fire off the print rendering loop on the AppKit thread, and don't have // it wait and block this thread. - if (printLoop(false, firstPage, lastPage)) { + if (printLoop(false, firstPage, totalPages)) { // Start a secondary loop on EDT until printing operation is finished or cancelled printingLoop.enter(); } @@ -407,7 +396,7 @@ public final class CPrinterJob extends RasterPrinterJob { onEventThread = false; try { - printLoop(true, firstPage, lastPage); + printLoop(true, firstPage, totalPages); } catch (Exception e) { e.printStackTrace(); } @@ -417,7 +406,6 @@ public final class CPrinterJob extends RasterPrinterJob { } if (++loopi < prMembers.length) { firstPage = prMembers[loopi][0]-1; - lastPage = prMembers[loopi][1] -1; } } while (loopi < prMembers.length); } finally { @@ -693,7 +681,7 @@ public final class CPrinterJob extends RasterPrinterJob { } } - private native boolean printLoop(boolean waitUntilDone, int firstPage, int lastPage) throws PrinterException; + private native boolean printLoop(boolean waitUntilDone, int firstPage, int totalPages) throws PrinterException; private PageFormat getPageFormat(int pageIndex) { // This is called from the native side. diff --git a/src/java.desktop/macosx/native/libawt_lwawt/awt/CDesktopPeer.m b/src/java.desktop/macosx/native/libawt_lwawt/awt/CDesktopPeer.m index 7555c7990c4..e1841c9398c 100644 --- a/src/java.desktop/macosx/native/libawt_lwawt/awt/CDesktopPeer.m +++ b/src/java.desktop/macosx/native/libawt_lwawt/awt/CDesktopPeer.m @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 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,27 +27,60 @@ #import "JNIUtilities.h" #import #import +#import "sun_lwawt_macosx_CDesktopPeer.h" /* * Class: sun_lwawt_macosx_CDesktopPeer * Method: _lsOpenURI - * Signature: (Ljava/lang/String;)I; + * Signature: (Ljava/lang/String;I)I */ JNIEXPORT jint JNICALL Java_sun_lwawt_macosx_CDesktopPeer__1lsOpenURI -(JNIEnv *env, jclass clz, jstring uri) +(JNIEnv *env, jclass clz, jstring uri, jint action) { - OSStatus status = noErr; + __block OSStatus status = noErr; JNI_COCOA_ENTER(env); - // I would love to use NSWorkspace here, but it's not thread safe. Why? I don't know. - // So we use LaunchServices directly. + NSURL *urlToOpen = [NSURL URLWithString:JavaStringToNSString(env, uri)]; + NSURL *appURI = nil; - NSURL *url = [NSURL URLWithString:JavaStringToNSString(env, uri)]; + if (action == sun_lwawt_macosx_CDesktopPeer_BROWSE) { + // To get the defaultBrowser + NSURL *httpsURL = [NSURL URLWithString:@"https://"]; + NSWorkspace *workspace = [NSWorkspace sharedWorkspace]; + appURI = [workspace URLForApplicationToOpenURL:httpsURL]; + } else if (action == sun_lwawt_macosx_CDesktopPeer_MAIL) { + // To get the default mailer + NSURL *mailtoURL = [NSURL URLWithString:@"mailto://"]; + NSWorkspace *workspace = [NSWorkspace sharedWorkspace]; + appURI = [workspace URLForApplicationToOpenURL:mailtoURL]; + } - LSLaunchFlags flags = kLSLaunchDefaults; + if (appURI == nil) { + return -1; + } - LSApplicationParameters params = {0, flags, NULL, NULL, NULL, NULL, NULL}; - status = LSOpenURLsWithRole((CFArrayRef)[NSArray arrayWithObject:url], kLSRolesAll, NULL, ¶ms, NULL, 0); + // Prepare NSOpenConfig object + NSArray *urls = @[urlToOpen]; + NSWorkspaceOpenConfiguration *configuration = [NSWorkspaceOpenConfiguration configuration]; + configuration.activates = YES; // To bring app to foreground + configuration.promptsUserIfNeeded = YES; // To allow macOS desktop prompts + + // dispatch semaphores used to wait for the completion handler to update and return status + dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); + dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(NSEC_PER_SEC)); // 1 second timeout + + // Asynchronous call to openURL + [[NSWorkspace sharedWorkspace] openURLs:urls + withApplicationAtURL:appURI + configuration:configuration + completionHandler:^(NSRunningApplication *app, NSError *error) { + if (error) { + status = (OSStatus) error.code; + } + dispatch_semaphore_signal(semaphore); + }]; + + dispatch_semaphore_wait(semaphore, timeout); JNI_COCOA_EXIT(env); return status; @@ -56,32 +89,73 @@ JNI_COCOA_EXIT(env); /* * Class: sun_lwawt_macosx_CDesktopPeer * Method: _lsOpenFile - * Signature: (Ljava/lang/String;Z)I; + * Signature: (Ljava/lang/String;I;Ljava/lang/String;)I; */ JNIEXPORT jint JNICALL Java_sun_lwawt_macosx_CDesktopPeer__1lsOpenFile -(JNIEnv *env, jclass clz, jstring jpath, jboolean print) +(JNIEnv *env, jclass clz, jstring jpath, jint action, jstring jtmpTxtPath) { - OSStatus status = noErr; + __block OSStatus status = noErr; JNI_COCOA_ENTER(env); - // I would love to use NSWorkspace here, but it's not thread safe. Why? I don't know. - // So we use LaunchServices directly. - NSString *path = NormalizedPathNSStringFromJavaString(env, jpath); - - NSURL *url = [NSURL fileURLWithPath:(NSString *)path]; + NSURL *urlToOpen = [NSURL fileURLWithPath:(NSString *)path]; // This byzantine workaround is necessary, or else directories won't open in Finder - url = (NSURL *)CFURLCreateWithFileSystemPath(NULL, (CFStringRef)[url path], kCFURLPOSIXPathStyle, false); + urlToOpen = (NSURL *)CFURLCreateWithFileSystemPath(NULL, (CFStringRef)[urlToOpen path], + kCFURLPOSIXPathStyle, false); - LSLaunchFlags flags = kLSLaunchDefaults; - if (print) flags |= kLSLaunchAndPrint; + NSWorkspace *workspace = [NSWorkspace sharedWorkspace]; + NSURL *appURI = [workspace URLForApplicationToOpenURL:urlToOpen]; + NSURL *defaultTerminalApp = [workspace URLForApplicationToOpenURL:[NSURL URLWithString:@"file:///bin/sh"]]; - LSApplicationParameters params = {0, flags, NULL, NULL, NULL, NULL, NULL}; - status = LSOpenURLsWithRole((CFArrayRef)[NSArray arrayWithObject:url], kLSRolesAll, NULL, ¶ms, NULL, 0); - [url release]; + // Prepare NSOpenConfig object + NSArray *urls = @[urlToOpen]; + NSWorkspaceOpenConfiguration *configuration = [NSWorkspaceOpenConfiguration configuration]; + configuration.activates = YES; // To bring app to foreground + configuration.promptsUserIfNeeded = YES; // To allow macOS desktop prompts + + // pre-checks for open/print/edit before calling openURLs API + if (action == sun_lwawt_macosx_CDesktopPeer_OPEN + || action == sun_lwawt_macosx_CDesktopPeer_PRINT) { + if (appURI == nil + || [[urlToOpen absoluteString] containsString:[appURI absoluteString]] + || [[defaultTerminalApp absoluteString] containsString:[appURI absoluteString]]) { + return -1; + } + // Additionally set forPrinting=TRUE for print + if (action == sun_lwawt_macosx_CDesktopPeer_PRINT) { + configuration.forPrinting = YES; + } + } else if (action == sun_lwawt_macosx_CDesktopPeer_EDIT) { + if (appURI == nil + || [[urlToOpen absoluteString] containsString:[appURI absoluteString]]) { + return -1; + } + // for EDIT: if (defaultApp = TerminalApp) then set appURI = DefaultTextEditor + if ([[defaultTerminalApp absoluteString] containsString:[appURI absoluteString]]) { + NSString *path = NormalizedPathNSStringFromJavaString(env, jtmpTxtPath); + NSURL *tempFilePath = [NSURL fileURLWithPath:(NSString *)path]; + appURI = [workspace URLForApplicationToOpenURL:tempFilePath]; + } + } + + // dispatch semaphores used to wait for the completion handler to update and return status + dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); + dispatch_time_t timeout = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(NSEC_PER_SEC)); // 1 second timeout + + // Asynchronous call - openURLs:withApplicationAtURL + [[NSWorkspace sharedWorkspace] openURLs:urls + withApplicationAtURL:appURI + configuration:configuration + completionHandler:^(NSRunningApplication *app, NSError *error) { + if (error) { + status = (OSStatus) error.code; + } + dispatch_semaphore_signal(semaphore); + }]; + + dispatch_semaphore_wait(semaphore, timeout); JNI_COCOA_EXIT(env); return status; } - diff --git a/src/java.desktop/macosx/native/libawt_lwawt/awt/CPrinterJob.m b/src/java.desktop/macosx/native/libawt_lwawt/awt/CPrinterJob.m index 9cc0a18564f..555a2746f43 100644 --- a/src/java.desktop/macosx/native/libawt_lwawt/awt/CPrinterJob.m +++ b/src/java.desktop/macosx/native/libawt_lwawt/awt/CPrinterJob.m @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2026, 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 @@ -656,7 +656,7 @@ JNI_COCOA_EXIT(env); * Signature: ()V */ JNIEXPORT jboolean JNICALL Java_sun_lwawt_macosx_CPrinterJob_printLoop - (JNIEnv *env, jobject jthis, jboolean blocks, jint firstPage, jint lastPage) + (JNIEnv *env, jobject jthis, jboolean blocks, jint firstPage, jint totalPages) { AWT_ASSERT_NOT_APPKIT_THREAD; @@ -672,14 +672,14 @@ JNIEXPORT jboolean JNICALL Java_sun_lwawt_macosx_CPrinterJob_printLoop JNI_COCOA_ENTER(env); // Get the first page's PageFormat for setting things up (This introduces // and is a facet of the same problem in Radar 2818593/2708932). - jobject page = (*env)->CallObjectMethod(env, jthis, jm_getPageFormat, 0); // AWT_THREADING Safe (!appKit) + jobject page = (*env)->CallObjectMethod(env, jthis, jm_getPageFormat, firstPage); // AWT_THREADING Safe (!appKit) CHECK_EXCEPTION(); if (page != NULL) { jobject pageFormatArea = (*env)->CallObjectMethod(env, jthis, jm_getPageFormatArea, page); // AWT_THREADING Safe (!appKit) CHECK_EXCEPTION(); PrinterView* printerView = [[PrinterView alloc] initWithFrame:JavaToNSRect(env, pageFormatArea) withEnv:env withPrinterJob:jthis]; - [printerView setFirstPage:firstPage lastPage:lastPage]; + [printerView setTotalPages:totalPages]; GET_NSPRINTINFO_METHOD_RETURN(NO) NSPrintInfo* printInfo = (NSPrintInfo*)jlong_to_ptr((*env)->CallLongMethod(env, jthis, sjm_getNSPrintInfo)); // AWT_THREADING Safe (known object) diff --git a/src/java.desktop/macosx/native/libawt_lwawt/awt/PrinterView.h b/src/java.desktop/macosx/native/libawt_lwawt/awt/PrinterView.h index 43472bee920..95a8055cdb0 100644 --- a/src/java.desktop/macosx/native/libawt_lwawt/awt/PrinterView.h +++ b/src/java.desktop/macosx/native/libawt_lwawt/awt/PrinterView.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2026, 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 @@ -32,12 +32,12 @@ jobject fCurPainter; jobject fCurPeekGraphics; - jint fFirstPage, fLastPage; + jint fTotalPages; } - (id)initWithFrame:(NSRect)aRect withEnv:(JNIEnv*)env withPrinterJob:(jobject)printerJob; -- (void)setFirstPage:(jint)firstPage lastPage:(jint)lastPage; +- (void)setTotalPages:(jint)totalPages; - (void)releaseReferences:(JNIEnv*)env; diff --git a/src/java.desktop/macosx/native/libawt_lwawt/awt/PrinterView.m b/src/java.desktop/macosx/native/libawt_lwawt/awt/PrinterView.m index d19948d9f0f..f219e8082b4 100644 --- a/src/java.desktop/macosx/native/libawt_lwawt/awt/PrinterView.m +++ b/src/java.desktop/macosx/native/libawt_lwawt/awt/PrinterView.m @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2026, 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 @@ -72,9 +72,8 @@ static jclass sjc_PAbortEx = NULL; } } -- (void)setFirstPage:(jint)firstPage lastPage:(jint)lastPage { - fFirstPage = firstPage; - fLastPage = lastPage; +- (void)setTotalPages:(jint)totalPages { + fTotalPages = totalPages; } - (void)drawRect:(NSRect)aRect @@ -156,15 +155,15 @@ static jclass sjc_PAbortEx = NULL; return NO; } - aRange->location = fFirstPage + 1; + aRange->location = 1; - if (fLastPage == java_awt_print_Pageable_UNKNOWN_NUMBER_OF_PAGES) + if (fTotalPages == java_awt_print_Pageable_UNKNOWN_NUMBER_OF_PAGES) { aRange->length = NSIntegerMax; } else { - aRange->length = (fLastPage + 1) - fFirstPage; + aRange->length = fTotalPages; } return YES; diff --git a/src/java.desktop/macosx/native/libawt_lwawt/awt/a11y/NavigableTextAccessibility.h b/src/java.desktop/macosx/native/libawt_lwawt/awt/a11y/NavigableTextAccessibility.h index ebf314c7394..9a528879a5d 100644 --- a/src/java.desktop/macosx/native/libawt_lwawt/awt/a11y/NavigableTextAccessibility.h +++ b/src/java.desktop/macosx/native/libawt_lwawt/awt/a11y/NavigableTextAccessibility.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2021, JetBrains s.r.o.. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -29,5 +29,6 @@ @interface NavigableTextAccessibility : CommonComponentAccessibility @property(readonly) BOOL accessibleIsPasswordText; +@property BOOL announceEditUpdates; @end diff --git a/src/java.desktop/macosx/native/libawt_lwawt/awt/a11y/NavigableTextAccessibility.m b/src/java.desktop/macosx/native/libawt_lwawt/awt/a11y/NavigableTextAccessibility.m index 138d502f10f..8e241e65b96 100644 --- a/src/java.desktop/macosx/native/libawt_lwawt/awt/a11y/NavigableTextAccessibility.m +++ b/src/java.desktop/macosx/native/libawt_lwawt/awt/a11y/NavigableTextAccessibility.m @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2021, JetBrains s.r.o.. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -60,6 +60,22 @@ static jmethodID sjm_getAccessibleEditableText = NULL; return [fJavaRole isEqualToString:@"passwordtext"]; } +- (id)init { + self = [super init]; + if (self) { + _announceEditUpdates = YES; + } + return self; +} + +- (void)suppressEditUpdates { + _announceEditUpdates = NO; +} + +- (void)resumeEditUpdates { + _announceEditUpdates = YES; +} + // NSAccessibilityElement protocol methods - (NSRect)accessibilityFrameForRange:(NSRange)range @@ -117,6 +133,9 @@ static jmethodID sjm_getAccessibleEditableText = NULL; - (NSString *)accessibilityStringForRange:(NSRange)range { + if (!_announceEditUpdates) { + return @""; + } JNIEnv *env = [ThreadUtilities getJNIEnv]; GET_CACCESSIBLETEXT_CLASS_RETURN(nil); DECLARE_STATIC_METHOD_RETURN(jm_getStringForRange, sjc_CAccessibleText, "getStringForRange", @@ -306,6 +325,12 @@ static jmethodID sjm_getAccessibleEditableText = NULL; return [super accessibilityParent]; } +- (void)postSelectedTextChanged +{ + [super postSelectedTextChanged]; + [self resumeEditUpdates]; +} + /* * Other text methods - (NSRange)accessibilitySharedCharacterRange; diff --git a/src/java.desktop/macosx/native/libawt_lwawt/awt/a11y/SpinboxAccessibility.m b/src/java.desktop/macosx/native/libawt_lwawt/awt/a11y/SpinboxAccessibility.m index 4dac6bd93f9..0cec7f3eb2c 100644 --- a/src/java.desktop/macosx/native/libawt_lwawt/awt/a11y/SpinboxAccessibility.m +++ b/src/java.desktop/macosx/native/libawt_lwawt/awt/a11y/SpinboxAccessibility.m @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, 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,6 +24,7 @@ */ #import "SpinboxAccessibility.h" +#import "ThreadUtilities.h" #define INCREMENT 0 #define DECREMENT 1 @@ -44,7 +45,15 @@ - (id _Nullable)accessibilityValue { - return [super accessibilityValue]; + id val = [super accessibilityValue]; + NSArray *clist = [super accessibilityChildren]; + for (NSUInteger i = 0; i < [clist count]; i++) { + id child = [clist objectAtIndex:i]; + if ([child conformsToProtocol:@protocol(NSAccessibilityNavigableStaticText)]) { + val = [child accessibilityValue]; + } + } + return val; } - (BOOL)accessibilityPerformIncrement @@ -68,4 +77,18 @@ return [super accessibilityParent]; } +- (void)postValueChanged +{ + AWT_ASSERT_APPKIT_THREAD; + NSAccessibilityPostNotification(self, NSAccessibilityValueChangedNotification); + NSArray *clist = [super accessibilityChildren]; + for (NSUInteger i = 0; i < [clist count]; i++) { + id child = [clist objectAtIndex:i]; + if ([child conformsToProtocol:@protocol(NSAccessibilityNavigableStaticText)]) { + NSAccessibilityPostNotification(child, NSAccessibilityLayoutChangedNotification); + [child suppressEditUpdates]; + } + } +} + @end diff --git a/src/java.desktop/macosx/native/libjsound/PLATFORM_API_MacOSX_PCM.cpp b/src/java.desktop/macosx/native/libjsound/PLATFORM_API_MacOSX_PCM.cpp index 441a71f5c50..bae16cb0a9c 100644 --- a/src/java.desktop/macosx/native/libjsound/PLATFORM_API_MacOSX_PCM.cpp +++ b/src/java.desktop/macosx/native/libjsound/PLATFORM_API_MacOSX_PCM.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2002, 2026, 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 @@ -162,8 +162,7 @@ void DAUDIO_GetFormats(INT32 mixerIndex, INT32 deviceID, int isSource, void* cre sampleRate, // sample rate DAUDIO_PCM, // only accept PCM bits == 8 ? FALSE : TRUE, // signed - bits == 8 ? FALSE // little-endian for 8bit - : UTIL_IsBigEndianPlatform()); + FALSE); // all supported macOS versions run on LE } } // add default format @@ -175,7 +174,7 @@ void DAUDIO_GetFormats(INT32 mixerIndex, INT32 deviceID, int isSource, void* cre defSampleRate, // sample rate DAUDIO_PCM, // PCM TRUE, // signed - UTIL_IsBigEndianPlatform()); // native endianness + FALSE); // native endianness; all supported macOS versions run on LE } TRACE0("<