From 8357de88aa35ee998fefe321ee6dae9eb4993fa6 Mon Sep 17 00:00:00 2001 From: Alexey Ivanov Date: Fri, 10 Apr 2026 13:07:00 +0000 Subject: [PATCH 001/108] 8334868: Ensure CheckUninstallModalHook is called in WPageDialogPeer._show Ensure AwtDialog::CheckInstallModalHook and AwtDialog::ModalActivateNextWindow are always called after ::PageSetupDlg returns. Reverse the condition of the if statement and bail out if ::PageSetupDlg returns an error. Remove the doIt flag and use explicit returns: * JNI_FALSE if an error detected; * JNI_TRUE if the function reached its end without errors. Reviewed-by: dmarkov, prr --- .../native/libawt/windows/awt_PrintJob.cpp | 169 +++++++++--------- .../PrinterJob/PageDialogCancelTest.java | 9 +- .../PrinterJob/PageDialogFormatTest.java | 63 +++++++ 3 files changed, 152 insertions(+), 89 deletions(-) create mode 100644 test/jdk/java/awt/print/PrinterJob/PageDialogFormatTest.java diff --git a/src/java.desktop/windows/native/libawt/windows/awt_PrintJob.cpp b/src/java.desktop/windows/native/libawt/windows/awt_PrintJob.cpp index 8d016d8b39f..b18fa5a7e2c 100644 --- a/src/java.desktop/windows/native/libawt/windows/awt_PrintJob.cpp +++ b/src/java.desktop/windows/native/libawt/windows/awt_PrintJob.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2025, 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 @@ -522,7 +522,6 @@ Java_sun_awt_windows_WPageDialogPeer__1show(JNIEnv *env, jobject peer) AwtComponent *awtParent = (parent != NULL) ? (AwtComponent *)JNI_GET_PDATA(parent) : NULL; HWND hwndOwner = awtParent ? awtParent->GetHWnd() : NULL; - jboolean doIt = JNI_FALSE; PAGESETUPDLG setup; memset(&setup, 0, sizeof(setup)); @@ -578,7 +577,7 @@ Java_sun_awt_windows_WPageDialogPeer__1show(JNIEnv *env, jobject peer) */ if ((setup.hDevMode == NULL) && (setup.hDevNames == NULL)) { CLEANUP_SHOW; - return doIt; + return JNI_FALSE; } } else { int measure = PSD_INTHOUSANDTHSOFINCHES; @@ -606,7 +605,7 @@ Java_sun_awt_windows_WPageDialogPeer__1show(JNIEnv *env, jobject peer) pageFormatToSetup(env, self, page, &setup, AwtPrintControl::getPrintDC(env, self)); if (env->ExceptionCheck()) { CLEANUP_SHOW; - return doIt; + return JNI_FALSE; } setup.lpfnPageSetupHook = reinterpret_cast(pageDlgHook); @@ -615,89 +614,91 @@ Java_sun_awt_windows_WPageDialogPeer__1show(JNIEnv *env, jobject peer) AwtDialog::CheckInstallModalHook(); BOOL ret = ::PageSetupDlg(&setup); - if (ret) { - - jobject paper = getPaper(env, page); - if (paper == NULL) { - CLEANUP_SHOW; - return doIt; - } - int units = setup.Flags & PSD_INTHOUSANDTHSOFINCHES ? - MM_HIENGLISH : - MM_HIMETRIC; - POINT paperSize; - RECT margins; - jint orientation; - - /* The printer may have been changed, and we track that change, - * but then need to get a new DC for the current printer so that - * we validate the paper size correctly - */ - if (setup.hDevNames != NULL) { - DEVNAMES* names = (DEVNAMES*)::GlobalLock(setup.hDevNames); - if (names != NULL) { - LPTSTR printer = (LPTSTR)names+names->wDeviceOffset; - SAVE_CONTROLWORD - HDC newDC = ::CreateDC(TEXT("WINSPOOL"), printer, NULL, NULL); - RESTORE_CONTROLWORD - if (newDC != NULL) { - HDC oldDC = AwtPrintControl::getPrintDC(env, self); - if (oldDC != NULL) { - ::DeleteDC(oldDC); - } - } - AwtPrintControl::setPrintDC(env, self, newDC); - } - ::GlobalUnlock(setup.hDevNames); - } - - /* Get the Windows paper and margins description. - */ - retrievePaperInfo(&setup, &paperSize, &margins, &orientation, - AwtPrintControl::getPrintDC(env, self)); - - /* Convert the Windows' paper and margins description - * and place them into a Paper instance. - */ - setPaperValues(env, paper, &paperSize, &margins, units); - if (env->ExceptionCheck()) { - CLEANUP_SHOW; - return doIt; - } - /* - * Put the updated Paper instance and the orientation into - * the PageFormat. - */ - setPaper(env, page, paper); - if (env->ExceptionCheck()) { - CLEANUP_SHOW; - return doIt; - } - setPageFormatOrientation(env, page, orientation); - if (env->ExceptionCheck()) { - CLEANUP_SHOW; - return JNI_FALSE; - } - if (setup.hDevMode != NULL) { - DEVMODE *devmode = (DEVMODE *)::GlobalLock(setup.hDevMode); - if (devmode != NULL) { - if (devmode->dmFields & DM_PAPERSIZE) { - jboolean err = setPrintPaperSize(env, self, devmode->dmPaperSize); - if (err) { - CLEANUP_SHOW; - return doIt; - } - } - } - ::GlobalUnlock(setup.hDevMode); - } - doIt = JNI_TRUE; - } AwtDialog::CheckUninstallModalHook(); - AwtDialog::ModalActivateNextWindow(NULL, target, peer); + if (!ret) { + CLEANUP_SHOW; + return JNI_FALSE; + } + + jobject paper = getPaper(env, page); + if (paper == NULL) { + CLEANUP_SHOW; + return JNI_FALSE; + } + int units = setup.Flags & PSD_INTHOUSANDTHSOFINCHES ? + MM_HIENGLISH : + MM_HIMETRIC; + POINT paperSize; + RECT margins; + jint orientation; + + /* The printer may have been changed, and we track that change, + * but then need to get a new DC for the current printer so that + * we validate the paper size correctly + */ + if (setup.hDevNames != NULL) { + DEVNAMES* names = (DEVNAMES*)::GlobalLock(setup.hDevNames); + if (names != NULL) { + LPTSTR printer = (LPTSTR)names+names->wDeviceOffset; + SAVE_CONTROLWORD + HDC newDC = ::CreateDC(TEXT("WINSPOOL"), printer, NULL, NULL); + RESTORE_CONTROLWORD + if (newDC != NULL) { + HDC oldDC = AwtPrintControl::getPrintDC(env, self); + if (oldDC != NULL) { + ::DeleteDC(oldDC); + } + } + AwtPrintControl::setPrintDC(env, self, newDC); + } + ::GlobalUnlock(setup.hDevNames); + } + + /* Get the Windows paper and margins description. + */ + retrievePaperInfo(&setup, &paperSize, &margins, &orientation, + AwtPrintControl::getPrintDC(env, self)); + + /* Convert the Windows' paper and margins description + * and place them into a Paper instance. + */ + setPaperValues(env, paper, &paperSize, &margins, units); + if (env->ExceptionCheck()) { + CLEANUP_SHOW; + return JNI_FALSE; + } + /* + * Put the updated Paper instance and the orientation into + * the PageFormat. + */ + setPaper(env, page, paper); + if (env->ExceptionCheck()) { + CLEANUP_SHOW; + return JNI_FALSE; + } + setPageFormatOrientation(env, page, orientation); + if (env->ExceptionCheck()) { + CLEANUP_SHOW; + return JNI_FALSE; + } + if (setup.hDevMode != NULL) { + DEVMODE *devmode = (DEVMODE *)::GlobalLock(setup.hDevMode); + if (devmode != NULL) { + if (devmode->dmFields & DM_PAPERSIZE) { + jboolean err = setPrintPaperSize(env, self, devmode->dmPaperSize); + if (err) { + ::GlobalUnlock(setup.hDevMode); + CLEANUP_SHOW; + return JNI_FALSE; + } + } + } + ::GlobalUnlock(setup.hDevMode); + } + HGLOBAL oldG = AwtPrintControl::getPrintHDMode(env, self); if (setup.hDevMode != oldG) { AwtPrintControl::setPrintHDMode(env, self, setup.hDevMode); @@ -710,7 +711,7 @@ Java_sun_awt_windows_WPageDialogPeer__1show(JNIEnv *env, jobject peer) CLEANUP_SHOW; - return doIt; + return JNI_TRUE; CATCH_BAD_ALLOC_RET(0); } diff --git a/test/jdk/java/awt/print/PrinterJob/PageDialogCancelTest.java b/test/jdk/java/awt/print/PrinterJob/PageDialogCancelTest.java index f9ce1b7c196..fc3d251cb6c 100644 --- a/test/jdk/java/awt/print/PrinterJob/PageDialogCancelTest.java +++ b/test/jdk/java/awt/print/PrinterJob/PageDialogCancelTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, 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 @@ -23,10 +23,10 @@ /* * @test - * @bug 8334366 + * @bug 8334366 8334868 * @key headful printer - * @summary Verifies original pageobject is returned unmodified - * on cancelling pagedialog + * @summary Verifies original PageFormat object is returned unmodified + * if PrinterJob.pageDialog is cancelled * @requires (os.family == "windows") * @run main PageDialogCancelTest */ @@ -55,4 +55,3 @@ public class PageDialogCancelTest { } } } - diff --git a/test/jdk/java/awt/print/PrinterJob/PageDialogFormatTest.java b/test/jdk/java/awt/print/PrinterJob/PageDialogFormatTest.java new file mode 100644 index 00000000000..b727ba12928 --- /dev/null +++ b/test/jdk/java/awt/print/PrinterJob/PageDialogFormatTest.java @@ -0,0 +1,63 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8334366 8334868 + * @key headful printer + * @summary Verifies PageFormat object returned from PrinterJob.pageDialog + * changes to landscape orientation when "Landscape" is selected + * @requires (os.family == "windows") + * @run main PageDialogFormatTest + */ + +import java.awt.Robot; +import java.awt.event.KeyEvent; +import java.awt.print.PageFormat; +import java.awt.print.PrinterJob; + +public class PageDialogFormatTest { + + public static void main(String[] args) throws Exception { + PrinterJob pj = PrinterJob.getPrinterJob(); + PageFormat oldFormat = new PageFormat(); + Robot robot = new Robot(); + Thread t1 = new Thread(() -> { + robot.delay(2000); + // Select Landscape orientation + robot.keyPress(KeyEvent.VK_ALT); + robot.keyPress(KeyEvent.VK_A); + robot.keyRelease(KeyEvent.VK_A); + robot.keyRelease(KeyEvent.VK_ALT); + // Press OK + robot.keyPress(KeyEvent.VK_ENTER); + robot.keyRelease(KeyEvent.VK_ENTER); + robot.waitForIdle(); + }); + t1.start(); + PageFormat newFormat = pj.pageDialog(oldFormat); + if (newFormat.getOrientation() != PageFormat.LANDSCAPE) { + throw new RuntimeException("PageFormat didn't change to landscape"); + } + } +} From d9c5c866340b209189b1e542e9a2b1c477f30157 Mon Sep 17 00:00:00 2001 From: SendaoYan Date: Fri, 10 Apr 2026 15:06:00 +0000 Subject: [PATCH 002/108] 8374943: TestDirectBufferStatisticsEvent failed with too few statistics events Reviewed-by: egahlin --- .../jfr/event/runtime/TestDirectBufferStatisticsEvent.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/jdk/jdk/jfr/event/runtime/TestDirectBufferStatisticsEvent.java b/test/jdk/jdk/jfr/event/runtime/TestDirectBufferStatisticsEvent.java index 581884664a5..13b5d008113 100644 --- a/test/jdk/jdk/jfr/event/runtime/TestDirectBufferStatisticsEvent.java +++ b/test/jdk/jdk/jfr/event/runtime/TestDirectBufferStatisticsEvent.java @@ -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 @@ -24,6 +24,7 @@ package jdk.jfr.event.runtime; import java.nio.ByteBuffer; +import java.util.ArrayList; import java.util.List; import jdk.internal.misc.VM; @@ -48,13 +49,14 @@ public class TestDirectBufferStatisticsEvent { private static final String EVENT_PATH = EventNames.DirectBufferStatistics; public static void main(String[] args) throws Throwable { + ArrayList buffers = new ArrayList<>(); try (Recording recording = new Recording()) { recording.enable(EVENT_PATH); recording.start(); int rounds = 16; int size = 1 * 1024 * 1024; // 1M for (int i = 0; i < rounds; i++) { - ByteBuffer.allocateDirect(size); + buffers.add(ByteBuffer.allocateDirect(size)); } recording.stop(); From ccbbef5561134a346adb083a863fe5149fe29b75 Mon Sep 17 00:00:00 2001 From: Dusan Balek Date: Fri, 10 Apr 2026 15:15:15 +0000 Subject: [PATCH 003/108] 8381654: Tokenizer warnings dropped when annotation processing is present Reviewed-by: jlahoda, liach --- .../com/sun/tools/javac/code/Preview.java | 15 +- .../sun/tools/javac/parser/JavaTokenizer.java | 6 +- .../sun/tools/javac/parser/JavacParser.java | 20 +- .../JavacProcessingEnvironment.java | 1 + .../com/sun/tools/javac/util/AbstractLog.java | 12 +- .../warnings/TestParserWarnings.java | 219 ++++++++++++++++++ 6 files changed, 253 insertions(+), 20 deletions(-) create mode 100644 test/langtools/tools/javac/processing/warnings/TestParserWarnings.java diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Preview.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Preview.java index 1c93c37698a..7a8e6c6f4f1 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Preview.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Preview.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 @@ -34,6 +34,7 @@ import com.sun.tools.javac.resources.CompilerProperties.LintWarnings; import com.sun.tools.javac.resources.CompilerProperties.Warnings; import com.sun.tools.javac.util.Assert; import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.JCDiagnostic.DiagnosticFlag; import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; import com.sun.tools.javac.util.JCDiagnostic.Error; import com.sun.tools.javac.util.JCDiagnostic.LintWarning; @@ -150,24 +151,26 @@ public class Preview { /** * Report usage of a preview feature. Usages reported through this method will affect the * set of sourcefiles with dependencies on preview features. + * @param flag a flag to set on the diagnostic * @param pos the position at which the preview feature was used. * @param feature the preview feature used. */ - public void warnPreview(int pos, Feature feature) { - warnPreview(new SimpleDiagnosticPosition(pos), feature); + public void warnPreview(DiagnosticFlag flag, int pos, Feature feature) { + warnPreview(flag, new SimpleDiagnosticPosition(pos), feature); } /** * Report usage of a preview feature. Usages reported through this method will affect the * set of sourcefiles with dependencies on preview features. + * @param flag a flag to set on the diagnostic * @param pos the position at which the preview feature was used. * @param feature the preview feature used. */ - public void warnPreview(DiagnosticPosition pos, Feature feature) { + public void warnPreview(DiagnosticFlag flag, DiagnosticPosition pos, Feature feature) { Assert.check(isEnabled()); Assert.check(isPreview(feature)); markUsesPreview(pos); - log.warning(pos, + log.warning(flag, pos, feature.isPlural() ? LintWarnings.PreviewFeatureUsePlural(feature.nameFragment()) : LintWarnings.PreviewFeatureUse(feature.nameFragment())); @@ -263,7 +266,7 @@ public class Preview { log.error(pos, feature.error(source.name)); } if (isEnabled() && isPreview(feature)) { - warnPreview(pos, feature); + warnPreview(null, pos, feature); } } } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavaTokenizer.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavaTokenizer.java index 55f2f76e358..d8b5b1ddd6b 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavaTokenizer.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavaTokenizer.java @@ -176,7 +176,7 @@ public class JavaTokenizer extends UnicodeReader { lexError(pos, feature.error(source.name)); } else if (preview.isPreview(feature)) { //use of preview feature, warn - preview.warnPreview(pos, feature); + preview.warnPreview(DiagnosticFlag.SYNTAX, pos, feature); } } @@ -1040,10 +1040,10 @@ public class JavaTokenizer extends UnicodeReader { // Verify that the incidental indentation is consistent. Set checks = TextBlockSupport.checkWhitespace(string); if (checks.contains(TextBlockSupport.WhitespaceChecks.INCONSISTENT)) { - log.warning(pos, LintWarnings.InconsistentWhiteSpaceIndentation); + log.warning(DiagnosticFlag.SYNTAX, pos, LintWarnings.InconsistentWhiteSpaceIndentation); } if (checks.contains(TextBlockSupport.WhitespaceChecks.TRAILING)) { - log.warning(pos, LintWarnings.TrailingWhiteSpaceWillBeRemoved); + log.warning(DiagnosticFlag.SYNTAX, pos, LintWarnings.TrailingWhiteSpaceWillBeRemoved); } // Remove incidental indentation. try { diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java index df5da5cb954..b4dfb04766c 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/JavacParser.java @@ -772,7 +772,7 @@ public class JavacParser implements Parser { } } else if (token.kind == UNDERSCORE) { if (Feature.UNDERSCORE_IDENTIFIER.allowedInSource(source)) { - log.warning(token.pos, Warnings.UnderscoreAsIdentifier); + log.warning(DiagnosticFlag.SYNTAX, token.pos, Warnings.UnderscoreAsIdentifier); } else if (asVariable) { checkSourceLevel(Feature.UNNAMED_VARIABLES); if (peekToken(LBRACKET)) { @@ -2339,7 +2339,7 @@ public class JavacParser implements Parser { if (allowYieldStatement) { return true; } else { - log.warning(pos, Warnings.InvalidYield); + log.warning(DiagnosticFlag.SYNTAX, pos, Warnings.InvalidYield); } } return false; @@ -3858,35 +3858,35 @@ public class JavacParser implements Parser { if (Feature.LOCAL_VARIABLE_TYPE_INFERENCE.allowedInSource(source)) { return Source.JDK10; } else if (shouldWarn) { - log.warning(pos, Warnings.RestrictedTypeNotAllowed(name, Source.JDK10)); + log.warning(DiagnosticFlag.SYNTAX, pos, Warnings.RestrictedTypeNotAllowed(name, Source.JDK10)); } } if (name == names.yield) { if (allowYieldStatement) { return Source.JDK14; } else if (shouldWarn) { - log.warning(pos, Warnings.RestrictedTypeNotAllowed(name, Source.JDK14)); + log.warning(DiagnosticFlag.SYNTAX, pos, Warnings.RestrictedTypeNotAllowed(name, Source.JDK14)); } } if (name == names.record) { if (allowRecords) { return Source.JDK14; } else if (shouldWarn) { - log.warning(pos, Warnings.RestrictedTypeNotAllowedPreview(name, Source.JDK14)); + log.warning(DiagnosticFlag.SYNTAX, pos, Warnings.RestrictedTypeNotAllowedPreview(name, Source.JDK14)); } } if (name == names.sealed) { if (allowSealedTypes) { return Source.JDK15; } else if (shouldWarn) { - log.warning(pos, Warnings.RestrictedTypeNotAllowedPreview(name, Source.JDK15)); + log.warning(DiagnosticFlag.SYNTAX, pos, Warnings.RestrictedTypeNotAllowedPreview(name, Source.JDK15)); } } if (name == names.permits) { if (allowSealedTypes) { return Source.JDK15; } else if (shouldWarn) { - log.warning(pos, Warnings.RestrictedTypeNotAllowedPreview(name, Source.JDK15)); + log.warning(DiagnosticFlag.SYNTAX, pos, Warnings.RestrictedTypeNotAllowedPreview(name, Source.JDK15)); } } return null; @@ -4057,7 +4057,7 @@ public class JavacParser implements Parser { if (source.compareTo(Source.JDK21) >= 0) reportSyntaxError(semiList.first().pos, Errors.ExtraneousSemicolon); else - log.warning(semiList.first().pos, Warnings.ExtraneousSemicolon); + log.warning(DiagnosticFlag.SYNTAX, semiList.first().pos, Warnings.ExtraneousSemicolon); } seenImport = true; defs.append(importDeclaration()); @@ -4074,7 +4074,7 @@ public class JavacParser implements Parser { if (source.compareTo(Source.JDK21) >= 0) reportSyntaxError(semiList.first().pos, Errors.ExtraneousSemicolon); else - log.warning(semiList.first().pos, Warnings.ExtraneousSemicolon); + log.warning(DiagnosticFlag.SYNTAX, semiList.first().pos, Warnings.ExtraneousSemicolon); } ModuleKind kind = ModuleKind.STRONG; if (token.name() == names.open) { @@ -5616,7 +5616,7 @@ public class JavacParser implements Parser { log.error(pos, feature.error(source.name)); } else if (preview.isPreview(feature)) { //use of preview feature, warn - preview.warnPreview(pos, feature); + preview.warnPreview(DiagnosticFlag.SYNTAX, pos, feature); } } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/JavacProcessingEnvironment.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/JavacProcessingEnvironment.java index 809c19d5012..11fa3a5aebf 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/JavacProcessingEnvironment.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/JavacProcessingEnvironment.java @@ -1204,6 +1204,7 @@ public class JavacProcessingEnvironment implements ProcessingEnvironment, Closea //where: private final Predicate ACCEPT_NON_RECOVERABLE_LINTS = d -> !Optional.of(d) + .filter(diag -> !diag.isFlagSet(SYNTAX)) .map(JCDiagnostic::getLintCategory) .map(lc -> lc.annotationSuppression || lc == Lint.LintCategory.INCUBATING) diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/util/AbstractLog.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/util/AbstractLog.java index ce3e56f2f3f..b8b2a3af254 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/util/AbstractLog.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/util/AbstractLog.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2021, 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 @@ -192,6 +192,16 @@ public abstract class AbstractLog { report(diags.warning(null, source, wrap(pos), warningKey)); } + /** Report a warning, unless suppressed by the -nowarn option or the + * maximum number of warnings has been reached. + * @param flag A flag to set on the diagnostic + * @param pos The source position at which to report the warning. + * @param warningKey The key for the localized warning message. + */ + public void warning(DiagnosticFlag flag, int pos, Warning warningKey) { + report(diags.warning(flag, source, wrap(pos), warningKey)); + } + /** Provide a non-fatal notification, unless suppressed by the -nowarn option. * @param noteKey The key for the localized notification message. */ diff --git a/test/langtools/tools/javac/processing/warnings/TestParserWarnings.java b/test/langtools/tools/javac/processing/warnings/TestParserWarnings.java new file mode 100644 index 00000000000..b611e7a8655 --- /dev/null +++ b/test/langtools/tools/javac/processing/warnings/TestParserWarnings.java @@ -0,0 +1,219 @@ +/* + * 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. + */ + +/* + * @test + * @bug 8381654 + * @summary AP interference with tokenizer warnings + * @library /tools/lib + * @modules + * jdk.compiler/com.sun.tools.javac.api + * jdk.compiler/com.sun.tools.javac.main + * @build toolbox.ToolBox toolbox.JavacTask + * @run junit ${test.main.class} + */ + +import java.io.IOException; +import java.io.Writer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Set; + +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.Filer; +import javax.annotation.processing.Messager; +import javax.annotation.processing.ProcessingEnvironment; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.TypeElement; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import toolbox.JavacTask; +import toolbox.Task; +import toolbox.ToolBox; + +public class TestParserWarnings { + + static boolean[] apOptions() { + return new boolean[] {false, true}; + } + + final ToolBox tb = new ToolBox(); + Path base, src, classes; + + @ParameterizedTest @MethodSource("apOptions") + public void testPreviewWarning(boolean useProcessor) throws Exception { + tb.writeJavaFiles(src, """ + public record MyRec() {} + """); + + JavacTask task = new JavacTask(tb) + .options("--enable-preview", + "-source", Integer.toString(Runtime.version().feature()), + "-XDforcePreview", + "-XDrawDiagnostics") + .files(tb.findJavaFiles(src)) + .outdir(classes); + if (useProcessor) { + task.processors(new ProcessorImpl()); + } + List log = task + .run() + .writeAll() + .getOutputLines(Task.OutputKind.DIRECT); + + List expected = List.of( + "- compiler.note.preview.filename: MyRec.java, DEFAULT", + "- compiler.note.preview.recompile" + ); + + tb.checkEqual(expected, log); + } + + @ParameterizedTest @MethodSource("apOptions") + public void testTextBlockWarning(boolean useProcessor) throws Exception { + tb.writeJavaFiles(src, """ + class TextBlockWhitespace { + String m() { + return ""\" + \\u0009\\u0009\\u0009\\u0009tab indentation + \\u0020\\u0020\\u0020\\u0020space indentation and trailing space\\u0020 + \\u0020\\u0020\\u0020\\u0020""\"; + } + } + """); + + JavacTask task = new JavacTask(tb) + .options("-Xlint:text-blocks", + "-XDrawDiagnostics") + .files(tb.findJavaFiles(src)) + .outdir(classes); + if (useProcessor) { + task.processors(new ProcessorImpl()); + } + List log = task + .run() + .writeAll() + .getOutputLines(Task.OutputKind.DIRECT); + + List expected = List.of( + "TextBlockWhitespace.java:3:16: compiler.warn.inconsistent.white.space.indentation", + "TextBlockWhitespace.java:3:16: compiler.warn.trailing.white.space.will.be.removed", + "2 warnings" + ); + + tb.checkEqual(expected, log); + } + + @Test + public void testAPGeneratedSource() throws Exception { + tb.writeJavaFiles(src, """ + import java.lang.annotation.ElementType; + import java.lang.annotation.Target; + + @A + class Test {} + + @Target(ElementType.TYPE) + @interface A {} + """); + + List log = new JavacTask(tb) + .options("-Xlint:text-blocks", + "-XDrawDiagnostics") + .files(tb.findJavaFiles(src)) + .outdir(classes) + .processors(new ProcessorImpl()) + .run() + .writeAll() + .getOutputLines(Task.OutputKind.DIRECT); + + List expected = List.of( + "Generated.java:3:16: compiler.warn.inconsistent.white.space.indentation", + "Generated.java:3:16: compiler.warn.trailing.white.space.will.be.removed", + "2 warnings" + ); + + tb.checkEqual(expected, log); + } + + @SupportedAnnotationTypes("*") + private static class ProcessorImpl extends AbstractProcessor { + private boolean done = false; + private Filer filer; + private Messager msgr; + + @Override + public void init(ProcessingEnvironment env) { + filer = env.getFiler(); + msgr = env.getMessager(); + } + + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + if (!done && !annotations.isEmpty()) { + try (Writer pw = filer.createSourceFile("Generated").openWriter()) { + pw.write(""" + public class Generated { + String m() { + return ""\" + \\u0009\\u0009\\u0009\\u0009tab indentation + \\u0020\\u0020\\u0020\\u0020space indentation and trailing space\\u0020 + \\u0020\\u0020\\u0020\\u0020""\"; + } + } + """); + pw.flush(); + pw.close(); + done = true; + } catch (IOException ioe) { + msgr.printError(ioe.getMessage()); + return false; + } + return true; + } + return false; + } + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latest(); + } + } + + @BeforeEach + public void setUp(TestInfo info) throws Exception { + base = Path.of(".").resolve(info.getTestMethod().get().getName()); + if (Files.exists(base)) { + tb.cleanDirectory(base); + } + src = base.resolve("src"); + classes = base.resolve("classes"); + Files.createDirectories(classes); + } +} From 701d0cb3a2a983ca94dedb7ca4f3554e040dec43 Mon Sep 17 00:00:00 2001 From: Andrew Haley Date: Fri, 10 Apr 2026 17:03:15 +0000 Subject: [PATCH 004/108] 8381969: os::physical_memory is still broken in 32-bit systems when running on 64-bit OSes Reviewed-by: jsikstro, stefank, aartemov --- src/hotspot/share/runtime/arguments.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hotspot/share/runtime/arguments.cpp b/src/hotspot/share/runtime/arguments.cpp index 1d79c4d0488..225e7074076 100644 --- a/src/hotspot/share/runtime/arguments.cpp +++ b/src/hotspot/share/runtime/arguments.cpp @@ -1515,7 +1515,7 @@ void Arguments::set_heap_size() { !FLAG_IS_DEFAULT(MinRAMPercentage) || !FLAG_IS_DEFAULT(InitialRAMPercentage); - const size_t avail_mem = os::physical_memory(); + const physical_memory_size_type avail_mem = os::physical_memory(); // If the maximum heap size has not been set with -Xmx, then set it as // fraction of the size of physical memory, respecting the maximum and From a3309eb6655ff0deb05d824ff80c4c1028683056 Mon Sep 17 00:00:00 2001 From: Vladimir Kozlov Date: Fri, 10 Apr 2026 17:11:33 +0000 Subject: [PATCH 005/108] 8381899: Assertions and crashes in aot/cds tests on Linux Alpine Reviewed-by: adinn --- src/hotspot/share/code/aotCodeCache.cpp | 26 ++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/hotspot/share/code/aotCodeCache.cpp b/src/hotspot/share/code/aotCodeCache.cpp index 6594d94fa91..79211e75db4 100644 --- a/src/hotspot/share/code/aotCodeCache.cpp +++ b/src/hotspot/share/code/aotCodeCache.cpp @@ -1093,11 +1093,13 @@ bool AOTCodeCache::store_code_blob(CodeBlob& blob, AOTCodeEntry::Kind entry_kind // now we have added all the other data we can write details of any // extra the AOT relocations - bool write_ok; + bool write_ok = true; if (AOTCodeEntry::is_multi_stub_blob(entry_kind)) { - CodeSection* cs = code_buffer->code_section(CodeBuffer::SECT_INSTS); - RelocIterator iter(cs); - write_ok = cache->write_relocations(blob, iter); + if (reloc_count > 0) { + CodeSection* cs = code_buffer->code_section(CodeBuffer::SECT_INSTS); + RelocIterator iter(cs); + write_ok = cache->write_relocations(blob, iter); + } } else { RelocIterator iter(&blob); write_ok = cache->write_relocations(blob, iter); @@ -1403,13 +1405,15 @@ void AOTCodeReader::restore(CodeBlob* code_blob) { // reinstate the AOT-load time relocs we saved from the code // buffer that generated this blob in a new code buffer and use // the latter to iterate over them - CodeBuffer code_buffer(code_blob); - relocInfo* locs = (relocInfo*)_reloc_data; - code_buffer.insts()->initialize_shared_locs(locs, _reloc_count); - code_buffer.insts()->set_locs_end(locs + _reloc_count); - CodeSection *cs = code_buffer.code_section(CodeBuffer::SECT_INSTS); - RelocIterator reloc_iter(cs); - fix_relocations(code_blob, reloc_iter); + if (_reloc_count > 0) { + CodeBuffer code_buffer(code_blob); + relocInfo* locs = (relocInfo*)_reloc_data; + code_buffer.insts()->initialize_shared_locs(locs, _reloc_count); + code_buffer.insts()->set_locs_end(locs + _reloc_count); + CodeSection *cs = code_buffer.code_section(CodeBuffer::SECT_INSTS); + RelocIterator reloc_iter(cs); + fix_relocations(code_blob, reloc_iter); + } } else { // the AOT-load time relocs will be in the blob's restored relocs RelocIterator reloc_iter(code_blob); From d3d8a0d90410f7dd0ced55fe719af04c93d46431 Mon Sep 17 00:00:00 2001 From: Ioi Lam Date: Fri, 10 Apr 2026 17:55:02 +0000 Subject: [PATCH 006/108] 8381636: Add built-in AOT support to GrowableArray Co-authored-by: Stefan Karlsson Reviewed-by: matsaave, kvn, coleenp --- src/hotspot/share/cds/aotGrowableArray.cpp | 34 --------- src/hotspot/share/cds/aotGrowableArray.hpp | 76 ------------------- .../share/cds/aotGrowableArray.inline.hpp | 37 --------- src/hotspot/share/cds/cppVtables.cpp | 6 +- src/hotspot/share/classfile/moduleEntry.cpp | 3 +- src/hotspot/share/classfile/moduleEntry.hpp | 7 +- src/hotspot/share/classfile/packageEntry.cpp | 3 +- src/hotspot/share/classfile/packageEntry.hpp | 3 +- src/hotspot/share/memory/metaspaceClosure.cpp | 4 +- src/hotspot/share/memory/metaspaceClosure.hpp | 55 +++++++++++--- src/hotspot/share/utilities/growableArray.cpp | 7 +- src/hotspot/share/utilities/growableArray.hpp | 13 ++-- .../gtest/utilities/test_metaspaceClosure.cpp | 13 ++-- 13 files changed, 72 insertions(+), 189 deletions(-) delete mode 100644 src/hotspot/share/cds/aotGrowableArray.cpp delete mode 100644 src/hotspot/share/cds/aotGrowableArray.hpp delete mode 100644 src/hotspot/share/cds/aotGrowableArray.inline.hpp diff --git a/src/hotspot/share/cds/aotGrowableArray.cpp b/src/hotspot/share/cds/aotGrowableArray.cpp deleted file mode 100644 index ec63e7aa57f..00000000000 --- a/src/hotspot/share/cds/aotGrowableArray.cpp +++ /dev/null @@ -1,34 +0,0 @@ -/* - * 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 deleted file mode 100644 index 0a0c137ed07..00000000000 --- a/src/hotspot/share/cds/aotGrowableArray.hpp +++ /dev/null @@ -1,76 +0,0 @@ -/* - * 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/cds/aotGrowableArray.inline.hpp b/src/hotspot/share/cds/aotGrowableArray.inline.hpp deleted file mode 100644 index 8c6e8cb6503..00000000000 --- a/src/hotspot/share/cds/aotGrowableArray.inline.hpp +++ /dev/null @@ -1,37 +0,0 @@ -/* - * 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_CDS_AOTGROWABLEARRAY_INLINE_HPP -#define SHARE_CDS_AOTGROWABLEARRAY_INLINE_HPP - -#include "cds/aotGrowableArray.hpp" - -#include "memory/metaspaceClosure.hpp" - -template -void AOTGrowableArray::metaspace_pointers_do(MetaspaceClosure* it) { - it->push_c_array(AOTGrowableArray::data_addr(), AOTGrowableArray::capacity()); -} - -#endif // SHARE_CDS_AOTGROWABLEARRAY_INLINE_HPP diff --git a/src/hotspot/share/cds/cppVtables.cpp b/src/hotspot/share/cds/cppVtables.cpp index dc5a777d7b1..57da12dee48 100644 --- a/src/hotspot/share/cds/cppVtables.cpp +++ b/src/hotspot/share/cds/cppVtables.cpp @@ -22,7 +22,6 @@ * */ -#include "cds/aotGrowableArray.hpp" #include "cds/aotMetaspace.hpp" #include "cds/archiveBuilder.hpp" #include "cds/archiveUtils.hpp" @@ -41,6 +40,7 @@ #include "oops/typeArrayKlass.hpp" #include "runtime/arguments.hpp" #include "utilities/globalDefinitions.hpp" +#include "utilities/growableArray.hpp" // Objects of the Metadata types (such as Klass and ConstantPool) have C++ vtables. // (In GCC this is the field ::_vptr, i.e., first word in the object.) @@ -58,10 +58,10 @@ #ifndef PRODUCT -// AOTGrowableArray has a vtable only when in non-product builds (due to +// GrowableArray has a vtable only when in non-product builds (due to // the virtual printing functions in AnyObj). -using GrowableArray_ModuleEntry_ptr = AOTGrowableArray; +using GrowableArray_ModuleEntry_ptr = GrowableArray; #define DEBUG_CPP_VTABLE_TYPES_DO(f) \ f(GrowableArray_ModuleEntry_ptr) \ diff --git a/src/hotspot/share/classfile/moduleEntry.cpp b/src/hotspot/share/classfile/moduleEntry.cpp index b5b8aa4ef55..c7fadeaea9b 100644 --- a/src/hotspot/share/classfile/moduleEntry.cpp +++ b/src/hotspot/share/classfile/moduleEntry.cpp @@ -23,7 +23,6 @@ */ #include "cds/aotClassLocation.hpp" -#include "cds/aotGrowableArray.inline.hpp" #include "cds/archiveBuilder.hpp" #include "cds/archiveUtils.hpp" #include "cds/cdsConfig.hpp" @@ -168,7 +167,7 @@ void ModuleEntry::add_read(ModuleEntry* m) { } else { if (reads() == nullptr) { // Lazily create a module's reads list - AOTGrowableArray* new_reads = new (mtModule) AOTGrowableArray(MODULE_READS_SIZE, mtModule); + GrowableArray* new_reads = new (mtModule) GrowableArray(MODULE_READS_SIZE, mtModule); set_reads(new_reads); } diff --git a/src/hotspot/share/classfile/moduleEntry.hpp b/src/hotspot/share/classfile/moduleEntry.hpp index 1a0251a2c2a..10dec73e9fa 100644 --- a/src/hotspot/share/classfile/moduleEntry.hpp +++ b/src/hotspot/share/classfile/moduleEntry.hpp @@ -25,7 +25,6 @@ #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" @@ -70,7 +69,7 @@ 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 + GrowableArray* _reads; // list of modules that are readable by this module Symbol* _version; // module version number Symbol* _location; // module location @@ -118,10 +117,10 @@ public: bool can_read(ModuleEntry* m) const; bool has_reads_list() const; - AOTGrowableArray* reads() const { + GrowableArray* reads() const { return _reads; } - void set_reads(AOTGrowableArray* r) { + void set_reads(GrowableArray* r) { _reads = r; } void pack_reads() { diff --git a/src/hotspot/share/classfile/packageEntry.cpp b/src/hotspot/share/classfile/packageEntry.cpp index 3e61f2e3a3e..3eb50fcb5a7 100644 --- a/src/hotspot/share/classfile/packageEntry.cpp +++ b/src/hotspot/share/classfile/packageEntry.cpp @@ -22,7 +22,6 @@ * */ -#include "cds/aotGrowableArray.inline.hpp" #include "cds/aotMetaspace.hpp" #include "cds/archiveBuilder.hpp" #include "cds/archiveUtils.hpp" @@ -83,7 +82,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) AOTGrowableArray(QUAL_EXP_SIZE, mtModule); + _qualified_exports = new (mtModule) GrowableArray(QUAL_EXP_SIZE, mtModule); } // Determine, based on this newly established export to module m, diff --git a/src/hotspot/share/classfile/packageEntry.hpp b/src/hotspot/share/classfile/packageEntry.hpp index 7b174a92287..e064e53b263 100644 --- a/src/hotspot/share/classfile/packageEntry.hpp +++ b/src/hotspot/share/classfile/packageEntry.hpp @@ -25,7 +25,6 @@ #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" @@ -116,7 +115,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. - AOTGrowableArray* _qualified_exports; + GrowableArray* _qualified_exports; JFR_ONLY(DEFINE_TRACE_ID_FIELD;) // Initial size of a package entry's list of qualified exports. diff --git a/src/hotspot/share/memory/metaspaceClosure.cpp b/src/hotspot/share/memory/metaspaceClosure.cpp index 0239eadf692..0926b55b9a3 100644 --- a/src/hotspot/share/memory/metaspaceClosure.cpp +++ b/src/hotspot/share/memory/metaspaceClosure.cpp @@ -22,11 +22,11 @@ * */ -#include "cds/aotGrowableArray.hpp" #include "classfile/packageEntry.hpp" #include "memory/metaspaceClosure.hpp" #include "oops/array.hpp" #include "oops/instanceKlass.hpp" +#include "utilities/growableArray.hpp" // Sanity checks static_assert(!HAS_METASPACE_POINTERS_DO(int)); @@ -35,8 +35,6 @@ 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 b6ba69d6f63..ac42dd13c6c 100644 --- a/src/hotspot/share/memory/metaspaceClosure.hpp +++ b/src/hotspot/share/memory/metaspaceClosure.hpp @@ -25,7 +25,6 @@ #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" @@ -90,7 +89,9 @@ public: // int size_in_heapwords() const; // // Currently, the iterable types include all subtypes of MetsapceObj, as well - // as GrowableArray, ModuleEntry and PackageEntry. + // as GrowableArray (C-heap allocated only), ModuleEntry, and PackageEntry. + // + // (Note that GrowableArray is supported specially and does not require the above functions.) // // Calling these functions would be trivial if these were virtual functions. // However, to save space, MetaspaceObj has NO vtable. The vtable is introduced @@ -303,11 +304,38 @@ private: }; //-------------------------------- - // Support for AOTGrowableArray + // Support for GrowableArray //-------------------------------- + // GrowableArrayRef -- iterate an instance of GrowableArray. + template class GrowableArrayRef : public Ref { + GrowableArray** _mpp; + GrowableArray* dereference() const { + return *_mpp; + } + + public: + GrowableArrayRef(GrowableArray** mpp, Writability w) : Ref(w), _mpp(mpp) {} + + virtual void** mpp() const { + return (void**)_mpp; + } + + virtual void metaspace_pointers_do(MetaspaceClosure *it) const { + GrowableArray* array = dereference(); + log_trace(aot)("Iter(GrowableArray): %p [%d]", array, array->length()); + array->assert_on_C_heap(); + it->push_c_array(array->data_addr(), array->capacity()); + } + + 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(sizeof(*dereference())); } + virtual MetaspaceClosureType type() const { return MetaspaceClosureType::GrowableArrayType; } + }; + // Abstract base class for MSOCArrayRef, MSOPointerCArrayRef and OtherCArrayRef. - // These are used for iterating the buffer held by AOTGrowableArray. + // These are used for iterating the buffer held by GrowableArray. template class CArrayRef : public Ref { T** _mpp; int _num_elems; // Number of elements @@ -354,7 +382,7 @@ private: // 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. + // This is for supporting GrowableArray. // // E.g., PackageEntry* _pkg_entry_pointers[2]; // a buffer that has 2 PackageEntry objects // ... @@ -377,7 +405,7 @@ private: // 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. + // This is for supporting GrowableArray. // // E.g., PackageEntry** _pkg_entry_pointers[2]; // a buffer that has 2 PackageEntry pointers // ... @@ -440,11 +468,11 @@ public: // Array*>* a4 = ...; it->push(&a4); => MSOPointerArrayRef // Array* a5 = ...; it->push(&a5); => MSOPointerArrayRef // - // AOTGrowableArrays have a separate "C array" buffer, so they are scanned in two steps: + // GrowableArrays have a separate "C array" buffer, so they are scanned in two steps: // - // AOTGrowableArray* ga1 = ...; it->push(&ga1); => MSORef => OtherCArrayRef - // AOTGrowableArray* ga2 = ...; it->push(&ga2); => MSORef => MSOCArrayRef - // AOTGrowableArray* ga3 = ...; it->push(&ga3); => MSORef => MSOPointerCArrayRef + // GrowableArray* ga1 = ...; it->push(&ga1); => GrowableArrayRef => OtherCArrayRef + // GrowableArray* ga2 = ...; it->push(&ga2); => GrowableArrayRef => MSOCArrayRef + // GrowableArray* ga3 = ...; it->push(&ga3); => GrowableArrayRef => MSOPointerCArrayRef // // Note that the following will fail to compile: // @@ -476,7 +504,12 @@ public: push_with_ref>(mpp, w); } - // --- The buffer of AOTGrowableArray + template + void push(GrowableArray** mpp, Writability w = _default) { + push_with_ref>(mpp, w); + } + + // --- The buffer of GrowableArray template void push_c_array(T** mpp, int num_elems, Writability w = _default) { push_impl(new OtherCArrayRef(mpp, num_elems, w)); diff --git a/src/hotspot/share/utilities/growableArray.cpp b/src/hotspot/share/utilities/growableArray.cpp index 6a1cb0b0414..9cc0813a1f6 100644 --- a/src/hotspot/share/utilities/growableArray.cpp +++ b/src/hotspot/share/utilities/growableArray.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/resourceArea.hpp" #include "runtime/javaThread.hpp" @@ -56,7 +57,9 @@ void* GrowableArrayCHeapAllocator::allocate(int max, int element_size, MemTag me } void GrowableArrayCHeapAllocator::deallocate(void* elements) { - FreeHeap(elements); + if (!AOTMetaspace::in_aot_cache(elements)) { + FreeHeap(elements); + } } #ifdef ASSERT diff --git a/src/hotspot/share/utilities/growableArray.hpp b/src/hotspot/share/utilities/growableArray.hpp index e300bea6993..14b54cfc4ea 100644 --- a/src/hotspot/share/utilities/growableArray.hpp +++ b/src/hotspot/share/utilities/growableArray.hpp @@ -116,12 +116,6 @@ 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) @@ -303,6 +297,11 @@ public: } tty->print("}\n"); } + + // MetaspaceClosure support + E** data_addr() { + return &_data; + } }; template @@ -821,6 +820,8 @@ public: this->clear_and_deallocate(); } } + + void assert_on_C_heap() { assert(on_C_heap(), "must be on C heap"); } }; // Leaner GrowableArray for CHeap backed data arrays, with compile-time decided MemTag. diff --git a/test/hotspot/gtest/utilities/test_metaspaceClosure.cpp b/test/hotspot/gtest/utilities/test_metaspaceClosure.cpp index c98b38b8c3c..091767cc273 100644 --- a/test/hotspot/gtest/utilities/test_metaspaceClosure.cpp +++ b/test/hotspot/gtest/utilities/test_metaspaceClosure.cpp @@ -21,7 +21,6 @@ * questions. */ -#include "cds/aotGrowableArray.inline.hpp" #include "memory/allocation.hpp" #include "memory/metadataFactory.hpp" #include "memory/metaspaceClosure.hpp" @@ -168,9 +167,9 @@ TEST_VM(MetaspaceClosure, OtherArrayRef) { EXPECT_TRUE(closure.has_visited(array)) << "must be"; } -// iterate an AOTGrowableArray +// iterate an GrowableArray TEST_VM(MetaspaceClosure, GrowableArray_MSOPointer) { - AOTGrowableArray* array = new(mtClass) AOTGrowableArray(2, mtClass); + GrowableArray* array = new(mtClass) GrowableArray(2, mtClass); MyMetaData x; MyMetaData y; @@ -190,9 +189,9 @@ TEST_VM(MetaspaceClosure, GrowableArray_MSOPointer) { EXPECT_TRUE(closure.has_visited(&z)) << "must be"; } -// iterate an AOTGrowableArray +// iterate an GrowableArray TEST_VM(MetaspaceClosure, GrowableArray_MSO) { - AOTGrowableArray* array = new(mtClass) AOTGrowableArray(4, mtClass); + GrowableArray* array = new(mtClass) GrowableArray(4, mtClass); for (int i = 0; i < array->length(); i++) { EXPECT_TRUE(array->at(i)._a == nullptr) << "should be initialized to null"; @@ -218,9 +217,9 @@ TEST_VM(MetaspaceClosure, GrowableArray_MSO) { EXPECT_TRUE(closure.has_visited(&z)) << "must be"; } -// iterate an AOTGrowableArray +// iterate an GrowableArray TEST_VM(MetaspaceClosure, GrowableArray_jlong) { - AOTGrowableArray* array = new(mtClass) AOTGrowableArray(4, mtClass); + GrowableArray* array = new(mtClass) GrowableArray(4, mtClass); MyUniqueMetaspaceClosure closure; closure.push(&array); From f7c06959a1af73e4bb481b9c24bb252ad8ca7b4c Mon Sep 17 00:00:00 2001 From: Albert Mingkun Yang Date: Fri, 10 Apr 2026 18:01:16 +0000 Subject: [PATCH 007/108] 8381998: Move UseCompressedClassPointers to obsolete flags section Reviewed-by: stefank, phubner, dholmes --- src/hotspot/share/runtime/arguments.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/hotspot/share/runtime/arguments.cpp b/src/hotspot/share/runtime/arguments.cpp index 225e7074076..2d40ee1822a 100644 --- a/src/hotspot/share/runtime/arguments.cpp +++ b/src/hotspot/share/runtime/arguments.cpp @@ -533,9 +533,6 @@ static SpecialFlag const special_jvm_flags[] = { { "DynamicDumpSharedSpaces", JDK_Version::jdk(18), JDK_Version::jdk(19), JDK_Version::undefined() }, { "RequireSharedSpaces", JDK_Version::jdk(18), JDK_Version::jdk(19), JDK_Version::undefined() }, { "UseSharedSpaces", JDK_Version::jdk(18), JDK_Version::jdk(19), JDK_Version::undefined() }, -#ifdef _LP64 - { "UseCompressedClassPointers", JDK_Version::jdk(25), JDK_Version::jdk(27), JDK_Version::undefined() }, -#endif { "AggressiveHeap", JDK_Version::jdk(26), JDK_Version::jdk(27), JDK_Version::jdk(28) }, // --- Deprecated alias flags (see also aliased_jvm_flags) - sorted by obsolete_in then expired_in: { "CreateMinidumpOnCrash", JDK_Version::jdk(9), JDK_Version::undefined(), JDK_Version::undefined() }, @@ -546,6 +543,9 @@ static SpecialFlag const special_jvm_flags[] = { #if defined(AARCH64) { "NearCpool", JDK_Version::undefined(), JDK_Version::jdk(25), JDK_Version::undefined() }, #endif +#ifdef _LP64 + { "UseCompressedClassPointers", JDK_Version::jdk(25), JDK_Version::jdk(27), JDK_Version::undefined() }, +#endif { "PSChunkLargeArrays", JDK_Version::jdk(26), JDK_Version::jdk(27), JDK_Version::jdk(28) }, { "ParallelRefProcEnabled", JDK_Version::jdk(26), JDK_Version::jdk(27), JDK_Version::jdk(28) }, From 2b716d7b32d745fa135f479c5a236517e87014e2 Mon Sep 17 00:00:00 2001 From: Leonid Mesnik Date: Fri, 10 Apr 2026 18:21:44 +0000 Subject: [PATCH 008/108] 8381658: Update nsk/jvmti/scenarios/sampling to better test virtual threads Reviewed-by: cjplummer, sspitsyn --- .../scenarios/allocation/AP04/ap04t001.java | 7 +- .../scenarios/allocation/AP04/ap04t002.java | 7 +- .../scenarios/allocation/AP04/ap04t003.java | 7 +- .../scenarios/bcinstr/BI04/bi04t002.java | 5 +- .../scenarios/contention/TC04/tc04t001.java | 5 +- .../jvmti/scenarios/events/EM02/em02t001.java | 7 +- .../jvmti/scenarios/events/EM02/em02t003.java | 8 +- .../EM02/em02t003/loadclass/em02t003a.java | 5 +- .../EM02/em02t005/loadclass/em02t005a.java | 5 +- .../jvmti/scenarios/events/EM02/em02t008.java | 9 +- .../jvmti/scenarios/events/EM05/em05t001.java | 5 +- .../jvmti/scenarios/events/EM05/em05t002.java | 5 +- .../jvmti/scenarios/events/EM07/em07t002.java | 10 +- .../EM07/em07t002/loadclass/em07t002a.java | 5 +- .../scenarios/hotswap/HS101/hs101t001.java | 5 +- .../scenarios/hotswap/HS101/hs101t002.java | 5 +- .../scenarios/hotswap/HS101/hs101t003.java | 5 +- .../scenarios/hotswap/HS101/hs101t004.java | 5 +- .../scenarios/hotswap/HS101/hs101t005.java | 5 +- .../scenarios/hotswap/HS101/hs101t006.java | 5 +- .../scenarios/hotswap/HS101/hs101t007.java | 5 +- .../scenarios/hotswap/HS101/hs101t008.java | 5 +- .../scenarios/hotswap/HS102/hs102t001.java | 5 +- .../scenarios/hotswap/HS102/hs102t002.java | 5 +- .../hotswap/HS103/hs103t002/MyThread.java | 6 +- .../HS103/hs103t002/newclass00/MyThread.java | 6 +- .../hotswap/HS104/hs104t002/MyThread.java | 5 +- .../HS104/hs104t002/newclass00/MyThread.java | 5 +- .../scenarios/hotswap/HS201/hs201t001.java | 7 +- .../scenarios/hotswap/HS201/hs201t002.java | 12 +- .../scenarios/hotswap/HS201/hs201t003.java | 6 +- .../hotswap/HS202/hs202t001/MyThread.java | 7 +- .../hotswap/HS202/hs202t001/hs202t001.java | 6 +- .../HS202/hs202t001/newclass00/MyThread.java | 7 +- .../hotswap/HS202/hs202t002/MyThread.java | 7 +- .../hotswap/HS202/hs202t002/hs202t002.java | 8 +- .../HS202/hs202t002/newclass00/MyThread.java | 7 +- .../hotswap/HS203/hs203t001/MyThread.java | 6 +- .../hotswap/HS203/hs203t001/hs203t001.java | 6 +- .../HS203/hs203t001/newclass00/MyThread.java | 6 +- .../hotswap/HS203/hs203t002/MyThread.java | 6 +- .../hotswap/HS203/hs203t002/hs203t002.java | 12 +- .../HS203/hs203t002/newclass00/MyThread.java | 6 +- .../hotswap/HS203/hs203t003/MyThread.java | 6 +- .../hotswap/HS203/hs203t003/hs203t003.java | 8 +- .../HS203/hs203t003/newclass00/MyThread.java | 6 +- .../hotswap/HS203/hs203t004/MyThread.java | 6 +- .../hotswap/HS203/hs203t004/hs203t004.java | 8 +- .../HS203/hs203t004/newclass00/MyThread.java | 6 +- .../hotswap/HS204/hs204t002/MyThread.java | 7 +- .../HS204/hs204t002/newclass00/MyThread.java | 7 +- .../hotswap/HS204/hs204t003/MyThread.java | 6 +- .../hotswap/HS204/hs204t003/hs204t003.java | 7 +- .../HS204/hs204t003/newclass00/MyThread.java | 7 +- .../hotswap/HS204/hs204t004/hs204t004.java | 8 +- .../scenarios/multienv/MA03/ma03t001.java | 5 +- .../scenarios/sampling/SP01/sp01t001.java | 19 +- .../sampling/SP01/sp01t001/sp01t001.cpp | 4 +- .../nsk/share/ExtraClassesBuilder.java | 4 +- test/lib/jdk/test/lib/Utils.java | 7 +- .../jdk/test/lib/thread/ThreadWrapper.java | 246 ++++++++++++++++++ 61 files changed, 489 insertions(+), 149 deletions(-) create mode 100644 test/lib/jdk/test/lib/thread/ThreadWrapper.java diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/allocation/AP04/ap04t001.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/allocation/AP04/ap04t001.java index df80009d315..1a539f4d37b 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/allocation/AP04/ap04t001.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/allocation/AP04/ap04t001.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. * 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 @@ package nsk.jvmti.scenarios.allocation.AP04; import java.io.*; import java.lang.reflect.*; +import jdk.test.lib.thread.ThreadWrapper; import nsk.share.*; import nsk.share.jvmti.*; @@ -114,7 +115,7 @@ public class ap04t001 extends DebugeeClass { log.display("All objects collected"); log.display("Wait for thread to finish"); - joinThread(thread); + joinThread(thread.getThread()); log.display("CASE #" + caseName + " finished.\n"); } @@ -172,7 +173,7 @@ class ap04t001SomeReachachableObjectsIterator implements ap04t001Iterator { } /**************************************************************************/ -class ap04t001Thread extends Thread { +class ap04t001Thread extends ThreadWrapper { String name; ap04t001Iterator iterator; Wicket startLock; diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/allocation/AP04/ap04t002.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/allocation/AP04/ap04t002.java index 9ecadf22f08..962474e0d01 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/allocation/AP04/ap04t002.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/allocation/AP04/ap04t002.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. * 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 @@ package nsk.jvmti.scenarios.allocation.AP04; import java.io.*; import java.lang.reflect.*; +import jdk.test.lib.thread.ThreadWrapper; import nsk.share.*; import nsk.share.jvmti.*; @@ -100,7 +101,7 @@ public class ap04t002 extends DebugeeClass { modified++; log.display("Wait for completion thread to finish"); - joinThread(thread); + joinThread(thread.getThread()); log.display("Cleaning tags and references to objects..."); for (int i = 0; i < OBJ_MAX_COUNT; i++) { if (root[i] != null) { @@ -166,7 +167,7 @@ class ap04t002SomeReachachableObjectsIterator implements ap04t002Iterator { } /**************************************************************************/ -class ap04t002Thread extends Thread { +class ap04t002Thread extends ThreadWrapper { String name; ap04t002Iterator iterator; Wicket startLock; diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/allocation/AP04/ap04t003.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/allocation/AP04/ap04t003.java index 15e7aa1c63a..60ca7cea837 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/allocation/AP04/ap04t003.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/allocation/AP04/ap04t003.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. * 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 @@ package nsk.jvmti.scenarios.allocation.AP04; import java.io.*; import java.lang.reflect.*; +import jdk.test.lib.thread.ThreadWrapper; import nsk.share.*; import nsk.share.jvmti.*; @@ -98,7 +99,7 @@ public class ap04t003 extends DebugeeClass { ap04t003Thread thread = startThread( threadName, iterator); log.display("Wait for thread to finish"); - joinThread(thread); + joinThread(thread.getThread()); log.display("Cleaning tags and references to objects..."); for (int i = 0; i < OBJ_MAX_COUNT; i++) { if (root[i] != null) { @@ -164,7 +165,7 @@ class ap04t003SomeReachachableObjectsIterator implements ap04t003Iterator { } /**************************************************************************/ -class ap04t003Thread extends Thread { +class ap04t003Thread extends ThreadWrapper { String name; ap04t003Iterator iterator; Wicket startLock; diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/bcinstr/BI04/bi04t002.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/bcinstr/BI04/bi04t002.java index 87a0de5d31d..db5ddf45a7c 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/bcinstr/BI04/bi04t002.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/bcinstr/BI04/bi04t002.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. * 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 @@ import nsk.share.Consts; import nsk.share.jvmti.ArgumentHandler; import nsk.share.jvmti.DebugeeClass; +import jdk.test.lib.thread.ThreadWrapper; public class bi04t002 extends DebugeeClass { @@ -118,7 +119,7 @@ public class bi04t002 extends DebugeeClass { } } -class bi04t002b extends Thread { +class bi04t002b extends ThreadWrapper { Object obj = new Object(); static Object started = new Object(); diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/contention/TC04/tc04t001.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/contention/TC04/tc04t001.java index 0707f38b4dd..2aa37d5d4ce 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/contention/TC04/tc04t001.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/contention/TC04/tc04t001.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. * 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 @@ import java.util.concurrent.*; import nsk.share.*; import nsk.share.jvmti.*; +import jdk.test.lib.thread.ThreadWrapper; public class tc04t001 extends DebugeeClass { @@ -92,7 +93,7 @@ public class tc04t001 extends DebugeeClass { /* =================================================================== */ -class tc04t001Thread extends Thread { +class tc04t001Thread extends ThreadWrapper { final static int INCREMENT_LIMIT = 100; final static int DELAY = 1000; diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/events/EM02/em02t001.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/events/EM02/em02t001.java index 9f92bf5423e..c37f0649067 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/events/EM02/em02t001.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/events/EM02/em02t001.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. * 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 @@ import java.io.PrintStream; import nsk.share.*; import nsk.share.jvmti.*; +import jdk.test.lib.thread.ThreadWrapper; public class em02t001 extends DebugeeClass { @@ -65,7 +66,7 @@ public class em02t001 extends DebugeeClass { logger.display("Timeout = " + timeout + " msc."); for (int i = 0; i < 3; i++) { - debuggeeThread = new em02t001Thread("Debuggee Thread"); + debuggeeThread = new em02t001Thread("Debuggee Thread").getThread(); generateEvents(); @@ -113,7 +114,7 @@ public class em02t001 extends DebugeeClass { } // tested threads - class em02t001Thread extends Thread { + class em02t001Thread extends ThreadWrapper { public em02t001Thread(String name) { super(name); diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/events/EM02/em02t003.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/events/EM02/em02t003.java index a23732dc554..0914b0129f2 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/events/EM02/em02t003.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/events/EM02/em02t003.java @@ -33,6 +33,8 @@ import nsk.share.jvmti.*; import java.util.*; import java.math.*; +import jdk.test.lib.thread.ThreadWrapper; + public class em02t003 extends DebugeeClass { // run test from command line @@ -74,7 +76,7 @@ public class em02t003 extends DebugeeClass { } Class loadedClass; - Thread thrd; + ThreadWrapper thrd; ClassUnloader unloader = new ClassUnloader(); for (int i = 0; i < 3; i++) { @@ -90,7 +92,7 @@ public class em02t003 extends DebugeeClass { loadedClass = unloader.getLoadedClass(); try { - thrd = (Thread )loadedClass.newInstance(); + thrd = ((ThreadWrapper)loadedClass.newInstance()); } catch (Exception e) { logger.complain("Unexpected exception " + e); e.printStackTrace(); @@ -139,7 +141,7 @@ public class em02t003 extends DebugeeClass { return status; } - boolean invokeMethod(Class cls, Thread thrd, String methodName) { + boolean invokeMethod(Class cls, ThreadWrapper thrd, String methodName) { Method method; diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/events/EM02/em02t003/loadclass/em02t003a.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/events/EM02/em02t003/loadclass/em02t003a.java index 32150e99ed6..8b11a8cd961 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/events/EM02/em02t003/loadclass/em02t003a.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/events/EM02/em02t003/loadclass/em02t003a.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. * 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 @@ */ package nsk.jvmti.scenarios.events.EM02; +import jdk.test.lib.thread.ThreadWrapper; -public class em02t003a extends Thread { +public class em02t003a extends ThreadWrapper { public void run() { // invoke methods in a loop to provoke compilation diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/events/EM02/em02t005/loadclass/em02t005a.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/events/EM02/em02t005/loadclass/em02t005a.java index ee340084946..89bd00f5f83 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/events/EM02/em02t005/loadclass/em02t005a.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/events/EM02/em02t005/loadclass/em02t005a.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. * 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 @@ */ package nsk.jvmti.scenarios.events.EM02; +import jdk.test.lib.thread.ThreadWrapper; -public class em02t005a extends Thread { +public class em02t005a extends ThreadWrapper { public void em02t005a() { } diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/events/EM02/em02t008.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/events/EM02/em02t008.java index 7e30300c41b..a5a7e4298fe 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/events/EM02/em02t008.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/events/EM02/em02t008.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. * 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 @@ import java.io.PrintStream; import nsk.share.*; import nsk.share.jvmti.*; +import jdk.test.lib.thread.ThreadWrapper; public class em02t008 extends DebugeeClass { @@ -55,7 +56,7 @@ public class em02t008 extends DebugeeClass { int status = Consts.TEST_PASSED; long timeout = argHandler.getWaitTime() * 60000; // milliseconds - Thread thrd1, thrd2; + ThreadWrapper thrd1, thrd2; for (int i = 0; i < STEP_NUMBER; i++) { thrd1 = new em02t008a(); @@ -79,7 +80,7 @@ public class em02t008 extends DebugeeClass { return status; } - class em02t008a extends Thread{ + class em02t008a extends ThreadWrapper { em02t008a() { setName("em02t008a"); @@ -90,7 +91,7 @@ public class em02t008 extends DebugeeClass { } } - class em02t008b extends Thread{ + class em02t008b extends ThreadWrapper { em02t008b() { setName("em02t008b"); diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/events/EM05/em05t001.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/events/EM05/em05t001.java index 3fa8e076729..11a7efd6bd8 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/events/EM05/em05t001.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/events/EM05/em05t001.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2018, 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 @@ -27,6 +27,7 @@ import java.io.PrintStream; import nsk.share.*; import nsk.share.jvmti.*; +import jdk.test.lib.thread.ThreadWrapper; public class em05t001 extends DebugeeClass { @@ -95,7 +96,7 @@ public class em05t001 extends DebugeeClass { /* =================================================================== */ // tested threads -class em05t001Thread extends Thread { +class em05t001Thread extends ThreadWrapper { public void run() { // invoke methods in a loop to provoke compilation for (int i = 0; i < 100; i++) { diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/events/EM05/em05t002.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/events/EM05/em05t002.java index 011f03e8cc0..378d770dd06 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/events/EM05/em05t002.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/events/EM05/em05t002.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2018, 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 @@ -27,6 +27,7 @@ import java.io.PrintStream; import nsk.share.*; import nsk.share.jvmti.*; +import jdk.test.lib.thread.ThreadWrapper; public class em05t002 extends DebugeeClass { @@ -98,7 +99,7 @@ public class em05t002 extends DebugeeClass { /* =================================================================== */ // tested threads -class em05t002Thread extends Thread { +class em05t002Thread extends ThreadWrapper { public void run() { // invoke methods in a loop to provoke compilation for (int i = 0; i < 100; i++) { diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/events/EM07/em07t002.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/events/EM07/em07t002.java index dcaca552073..852549276ec 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/events/EM07/em07t002.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/events/EM07/em07t002.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. * 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 @@ import java.io.File; import nsk.share.*; import nsk.share.jvmti.*; +import jdk.test.lib.thread.ThreadWrapper; + /** * Test executes the following scenario to check events COMPILED_METHOD_LOAD, * COMPILED_METHOD_UNLOAD: @@ -118,7 +120,7 @@ public class em07t002 extends DebugeeClass { String path = args[0]; Class loadedClass; - Thread thrd; + ThreadWrapper thrd; ClassUnloader unloader = new ClassUnloader(); for (int i = 0; i < attempts; i++) { logger.display("======================================"); @@ -136,7 +138,7 @@ public class em07t002 extends DebugeeClass { loadedClass = unloader.getLoadedClass(); try { - thrd = (Thread )loadedClass.newInstance(); + thrd = (ThreadWrapper)loadedClass.newInstance(); } catch (Exception e) { logger.complain("Unexpected exception " + e); e.printStackTrace(); @@ -170,7 +172,7 @@ public class em07t002 extends DebugeeClass { return status; } - boolean invokeMethod(Class cls, Thread thrd, String methodName) { + boolean invokeMethod(Class cls, ThreadWrapper thrd, String methodName) { Method method; diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/events/EM07/em07t002/loadclass/em07t002a.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/events/EM07/em07t002/loadclass/em07t002a.java index 3f03fd95568..dc3abe49296 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/events/EM07/em07t002/loadclass/em07t002a.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/events/EM07/em07t002/loadclass/em07t002a.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. * 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 @@ */ package nsk.jvmti.scenarios.events.EM07; +import jdk.test.lib.thread.ThreadWrapper; -public class em07t002a extends Thread { +public class em07t002a extends ThreadWrapper { public void run() { // invoke methods in a loop to provoke compilation diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS101/hs101t001.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS101/hs101t001.java index 21fc0a0e158..b3c1c25833c 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS101/hs101t001.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS101/hs101t001.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. * 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 @@ package nsk.jvmti.scenarios.hotswap.HS101; import java.io.PrintStream; +import jdk.test.lib.thread.ThreadWrapper; import nsk.share.*; import nsk.share.jvmti.*; @@ -115,7 +116,7 @@ public class hs101t001 extends DebugeeClass { /* =================================================================== */ -class hs101t001Thread extends Thread { +class hs101t001Thread extends ThreadWrapper { public Wicket startingBarrier = new Wicket(); private volatile boolean flag = true; public int i; diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS101/hs101t002.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS101/hs101t002.java index 368ed04a01e..e654dde6db0 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS101/hs101t002.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS101/hs101t002.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. * 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 @@ package nsk.jvmti.scenarios.hotswap.HS101; import java.io.PrintStream; +import jdk.test.lib.thread.ThreadWrapper; import nsk.share.*; import nsk.share.jvmti.*; @@ -115,7 +116,7 @@ public class hs101t002 extends DebugeeClass { /* =================================================================== */ -class hs101t002Thread extends Thread { +class hs101t002Thread extends ThreadWrapper { public Wicket startingBarrier = new Wicket(); private volatile boolean flag = true; public int i; diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS101/hs101t003.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS101/hs101t003.java index 2d554fd73ec..f08d07f2c17 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS101/hs101t003.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS101/hs101t003.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. * 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 @@ package nsk.jvmti.scenarios.hotswap.HS101; import java.io.PrintStream; +import jdk.test.lib.thread.ThreadWrapper; import nsk.share.*; import nsk.share.jvmti.*; @@ -115,7 +116,7 @@ public class hs101t003 extends DebugeeClass { /* =================================================================== */ -class hs101t003Thread extends Thread { +class hs101t003Thread extends ThreadWrapper { public Wicket startingBarrier = new Wicket(); private volatile boolean flag = true; public int i; diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS101/hs101t004.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS101/hs101t004.java index f43609ab042..121cb4a0aac 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS101/hs101t004.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS101/hs101t004.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. * 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 @@ package nsk.jvmti.scenarios.hotswap.HS101; import java.io.PrintStream; +import jdk.test.lib.thread.ThreadWrapper; import nsk.share.*; import nsk.share.jvmti.*; @@ -98,7 +99,7 @@ public class hs101t004 extends DebugeeClass { /* =================================================================== */ -class hs101t004Thread extends Thread { +class hs101t004Thread extends ThreadWrapper { public Wicket startingBarrier = new Wicket(); private volatile boolean flag = true; public int i; diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS101/hs101t005.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS101/hs101t005.java index afee53a2331..31bba4a409a 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS101/hs101t005.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS101/hs101t005.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. * 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 @@ package nsk.jvmti.scenarios.hotswap.HS101; import java.io.PrintStream; +import jdk.test.lib.thread.ThreadWrapper; import nsk.share.*; import nsk.share.jvmti.*; @@ -98,7 +99,7 @@ public class hs101t005 extends DebugeeClass { /* =================================================================== */ -class hs101t005Thread extends Thread { +class hs101t005Thread extends ThreadWrapper { public Wicket startingBarrier = new Wicket(); private volatile boolean flag = true; public int i; diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS101/hs101t006.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS101/hs101t006.java index 98568577032..a27521dcbd9 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS101/hs101t006.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS101/hs101t006.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. * 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 @@ package nsk.jvmti.scenarios.hotswap.HS101; import java.io.PrintStream; +import jdk.test.lib.thread.ThreadWrapper; import nsk.share.*; import nsk.share.jvmti.*; @@ -98,7 +99,7 @@ public class hs101t006 extends DebugeeClass { /* =================================================================== */ -class hs101t006Thread extends Thread { +class hs101t006Thread extends ThreadWrapper { public Wicket startingBarrier = new Wicket(); private volatile boolean flag = true; public int i; diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS101/hs101t007.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS101/hs101t007.java index f19805e9d1a..270fd7751a4 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS101/hs101t007.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS101/hs101t007.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. * 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 @@ package nsk.jvmti.scenarios.hotswap.HS101; import java.io.PrintStream; +import jdk.test.lib.thread.ThreadWrapper; import nsk.share.*; import nsk.share.jvmti.*; @@ -95,7 +96,7 @@ public class hs101t007 extends DebugeeClass { /* =================================================================== */ -class hs101t007Thread extends Thread { +class hs101t007Thread extends ThreadWrapper { public Wicket startingBarrier = new Wicket(); private volatile boolean flag = true; public int i; diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS101/hs101t008.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS101/hs101t008.java index b421cf3c185..9fca9678d0a 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS101/hs101t008.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS101/hs101t008.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. * 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 @@ package nsk.jvmti.scenarios.hotswap.HS101; import java.io.PrintStream; +import jdk.test.lib.thread.ThreadWrapper; import nsk.share.*; import nsk.share.jvmti.*; @@ -95,7 +96,7 @@ public class hs101t008 extends DebugeeClass { /* =================================================================== */ -class hs101t008Thread extends Thread { +class hs101t008Thread extends ThreadWrapper { public Wicket startingBarrier = new Wicket(); private volatile boolean flag = true; public int i; diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS102/hs102t001.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS102/hs102t001.java index 29d7aca6935..09ba44a5c32 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS102/hs102t001.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS102/hs102t001.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. * 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 @@ package nsk.jvmti.scenarios.hotswap.HS102; import java.io.PrintStream; +import jdk.test.lib.thread.ThreadWrapper; import nsk.share.*; import nsk.share.jvmti.*; @@ -150,7 +151,7 @@ public class hs102t001 extends DebugeeClass { /* =================================================================== */ -class hs102t001Thread extends Thread { +class hs102t001Thread extends ThreadWrapper { public Wicket startingBarrier = new Wicket(); private volatile boolean flag = true; public int i; diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS102/hs102t002.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS102/hs102t002.java index a34f3f6c109..c38fb07fb69 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS102/hs102t002.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS102/hs102t002.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. * 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 @@ package nsk.jvmti.scenarios.hotswap.HS102; import java.io.PrintStream; +import jdk.test.lib.thread.ThreadWrapper; import nsk.share.*; import nsk.share.jvmti.*; @@ -150,7 +151,7 @@ public class hs102t002 extends DebugeeClass { /* =================================================================== */ -class hs102t002Thread extends Thread { +class hs102t002Thread extends ThreadWrapper { public Wicket startingBarrier = new Wicket(); private volatile boolean flag = true; public int i; diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS103/hs103t002/MyThread.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS103/hs103t002/MyThread.java index 9f7d6e2f5c8..09fef278ca5 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS103/hs103t002/MyThread.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS103/hs103t002/MyThread.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2018, 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 @@ -22,9 +22,11 @@ */ package nsk.jvmti.scenarios.hotswap.HS103.hs103t002; +import jdk.test.lib.thread.ThreadWrapper; + import java.util.concurrent.atomic.AtomicInteger; -public class MyThread extends Thread { +public class MyThread extends ThreadWrapper { public static AtomicInteger ai = new AtomicInteger(0); public static final int size = 10; diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS103/hs103t002/newclass00/MyThread.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS103/hs103t002/newclass00/MyThread.java index 404ab78ff55..5874a1e11fc 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS103/hs103t002/newclass00/MyThread.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS103/hs103t002/newclass00/MyThread.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2018, 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 @@ -22,9 +22,11 @@ */ package nsk.jvmti.scenarios.hotswap.HS103.hs103t002; +import jdk.test.lib.thread.ThreadWrapper; + import java.util.concurrent.atomic.AtomicInteger; -public class MyThread extends Thread { +public class MyThread extends ThreadWrapper { public static AtomicInteger ai = new AtomicInteger(0); public static final int size = 10; diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS104/hs104t002/MyThread.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS104/hs104t002/MyThread.java index 771dfeeb054..2c5dfcf070c 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS104/hs104t002/MyThread.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS104/hs104t002/MyThread.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2018, 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 @@ -22,9 +22,10 @@ */ package nsk.jvmti.scenarios.hotswap.HS104.hs104t002; +import jdk.test.lib.thread.ThreadWrapper; import nsk.share.Wicket; -public class MyThread extends Thread { +public class MyThread extends ThreadWrapper { int threadState; private Wicket wicket; diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS104/hs104t002/newclass00/MyThread.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS104/hs104t002/newclass00/MyThread.java index 6ebbc04b5c1..409a3f6ffcc 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS104/hs104t002/newclass00/MyThread.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS104/hs104t002/newclass00/MyThread.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2018, 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 @@ -22,9 +22,10 @@ */ package nsk.jvmti.scenarios.hotswap.HS104.hs104t002; +import jdk.test.lib.thread.ThreadWrapper; import nsk.share.Wicket; -public class MyThread extends Thread { +public class MyThread extends ThreadWrapper { int threadState; private Wicket wicket; diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS201/hs201t001.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS201/hs201t001.java index 2f6e2b15db7..2cb0f1bc683 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS201/hs201t001.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS201/hs201t001.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. * 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 @@ import java.io.PrintStream; import nsk.share.*; import nsk.share.jvmti.*; +import jdk.test.lib.thread.ThreadWrapper; public class hs201t001 extends DebugeeClass { @@ -74,7 +75,7 @@ public class hs201t001 extends DebugeeClass { timeout = argHandler.getWaitTime() * 60 * 1000; // milliseconds log.display(">>> starting tested thread"); - Thread thread = new hs201t001Thread(); + Thread thread = new hs201t001Thread().getThread(); // testing sync status = checkStatus(status); @@ -130,7 +131,7 @@ public class hs201t001 extends DebugeeClass { return status; } -class hs201t001Thread extends Thread { +class hs201t001Thread extends ThreadWrapper { hs201t001Thread() { setName("hs201t001Thread"); diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS201/hs201t002.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS201/hs201t002.java index c76a9b005d1..69d243d49b1 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS201/hs201t002.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS201/hs201t002.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. * 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 @@ import java.util.concurrent.CountDownLatch; import nsk.share.*; import nsk.share.jvmti.*; +import jdk.test.lib.thread.ThreadWrapper; public class hs201t002 extends DebugeeClass { @@ -74,7 +75,8 @@ public class hs201t002 extends DebugeeClass { timeout = argHandler.getWaitTime() * 60 * 1000; // milliseconds log.display(">>> starting tested thread"); - hs201t002Thread thread = new hs201t002Thread(); + hs201t002Thread wrappedThread = new hs201t002Thread(); + Thread thread = wrappedThread.getThread(); // testing sync status = checkStatus(status); @@ -84,12 +86,12 @@ public class hs201t002 extends DebugeeClass { // setThread(thread) enables JVMTI events, and that can only be done on a live thread, // so wait until the thread has started. try { - thread.ready.await(); + wrappedThread.ready.await(); } catch (InterruptedException e) { } setThread(thread); - thread.go.countDown(); + wrappedThread.go.countDown(); while (currentStep != 4) { try { @@ -148,7 +150,7 @@ public class hs201t002 extends DebugeeClass { return status; } -class hs201t002Thread extends Thread { +class hs201t002Thread extends ThreadWrapper { CountDownLatch ready = new CountDownLatch(1); CountDownLatch go = new CountDownLatch(1); diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS201/hs201t003.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS201/hs201t003.java index f33afd148e8..fc0680f88eb 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS201/hs201t003.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS201/hs201t003.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. * 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 @@ package nsk.jvmti.scenarios.hotswap.HS201; import java.io.*; import java.util.*; + +import jdk.test.lib.thread.ThreadWrapper; import nsk.share.*; import nsk.share.jvmti.*; @@ -187,7 +189,7 @@ public class hs201t003 extends DebugeeClass { /** * Class executing a class to be redefined. */ - class RedefClassWrapper extends Thread { + class RedefClassWrapper extends ThreadWrapper { boolean stopMe = false; RedefClassWrapper() { diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS202/hs202t001/MyThread.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS202/hs202t001/MyThread.java index ecb702f1873..b94ed1f8faa 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS202/hs202t001/MyThread.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS202/hs202t001/MyThread.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2018, 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 @@ -21,7 +21,10 @@ * questions. */ package nsk.jvmti.scenarios.hotswap.HS202.hs202t001; -public class MyThread extends Thread { + +import jdk.test.lib.thread.ThreadWrapper; + +public class MyThread extends ThreadWrapper { MyObject myObject; public MyThread(MyObject obj) { this.myObject = obj; diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS202/hs202t001/hs202t001.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS202/hs202t001/hs202t001.java index c386a88dc12..788d4114498 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS202/hs202t001/hs202t001.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS202/hs202t001/hs202t001.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2020, 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 @@ -80,8 +80,8 @@ public class hs202t001 extends RedefineAgent { add(myObject, 1); } myObject.stop(true); - if( popThreadFrame(mt)) {; - resumeThread(mt); + if( popThreadFrame(mt.getThread())) {; + resumeThread(mt.getThread()); } // Popoing will not be possible on .. mt.join(); state = myObject.getAge(); diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS202/hs202t001/newclass00/MyThread.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS202/hs202t001/newclass00/MyThread.java index 9fbccbbcfab..7fc9dcf161a 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS202/hs202t001/newclass00/MyThread.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS202/hs202t001/newclass00/MyThread.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2018, 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 @@ -21,7 +21,10 @@ * questions. */ package nsk.jvmti.scenarios.hotswap.HS202.hs202t001; -public class MyThread extends Thread { + +import jdk.test.lib.thread.ThreadWrapper; + +public class MyThread extends ThreadWrapper { MyObject myObject; public MyThread(MyObject obj) { this.myObject = obj; diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS202/hs202t002/MyThread.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS202/hs202t002/MyThread.java index 45ec4af6249..c5e8fd6eb46 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS202/hs202t002/MyThread.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS202/hs202t002/MyThread.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2018, 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 @@ -21,7 +21,10 @@ * questions. */ package nsk.jvmti.scenarios.hotswap.HS202.hs202t002; -public class MyThread extends Thread { + +import jdk.test.lib.thread.ThreadWrapper; + +public class MyThread extends ThreadWrapper { private int val = 100; public void run() { diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS202/hs202t002/hs202t002.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS202/hs202t002/hs202t002.java index e78702cc587..dd295195543 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS202/hs202t002/hs202t002.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS202/hs202t002/hs202t002.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2020, 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 @@ -70,15 +70,15 @@ public class hs202t002 extends RedefineAgent { try { mt.start(); - while (!isThreadSuspended(mt)) { + while (!isThreadSuspended(mt.getThread())) { Thread.yield(); } - if (!popThreadFrame(mt)) { + if (!popThreadFrame(mt.getThread())) { throw new RuntimeException("error in popframe operation!"); } - if (!resumeThread(mt)) { + if (!resumeThread(mt.getThread())) { throw new RuntimeException("error in resuming thread!"); } diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS202/hs202t002/newclass00/MyThread.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS202/hs202t002/newclass00/MyThread.java index 32a7a085a13..1af54872316 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS202/hs202t002/newclass00/MyThread.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS202/hs202t002/newclass00/MyThread.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2018, 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 @@ -21,7 +21,10 @@ * questions. */ package nsk.jvmti.scenarios.hotswap.HS202.hs202t002; -public class MyThread extends Thread { + +import jdk.test.lib.thread.ThreadWrapper; + +public class MyThread extends ThreadWrapper { private int val = 100; public void run() { diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS203/hs203t001/MyThread.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS203/hs203t001/MyThread.java index c5646af4024..109ea8ec918 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS203/hs203t001/MyThread.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS203/hs203t001/MyThread.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2018, 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 @@ -21,8 +21,10 @@ * questions. */ package nsk.jvmti.scenarios.hotswap.HS203.hs203t001; +import jdk.test.lib.thread.ThreadWrapper; + import java.util.concurrent.atomic.AtomicBoolean; -public class MyThread extends Thread { +public class MyThread extends ThreadWrapper { public static AtomicBoolean resume = new AtomicBoolean(false); public int threadState=100; public MyThread() { diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS203/hs203t001/hs203t001.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS203/hs203t001/hs203t001.java index abbe8221444..e42bede4e01 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS203/hs203t001/hs203t001.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS203/hs203t001/hs203t001.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2020, 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 @@ -76,8 +76,8 @@ public class hs203t001 extends RedefineAgent { mt.start(); while(!MyThread.resume.get()); Thread.sleep(10000); - popThreadFrame(mt); - resumeThread(mt); + popThreadFrame(mt.getThread()); + resumeThread(mt.getThread()); mt.join(); log.println(" ..."+mt.threadState); } catch(Exception ie) { diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS203/hs203t001/newclass00/MyThread.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS203/hs203t001/newclass00/MyThread.java index 4ddbfc43c79..c5792c9789d 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS203/hs203t001/newclass00/MyThread.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS203/hs203t001/newclass00/MyThread.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2018, 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 @@ -21,8 +21,10 @@ * questions. */ package nsk.jvmti.scenarios.hotswap.HS203.hs203t001; +import jdk.test.lib.thread.ThreadWrapper; + import java.util.concurrent.atomic.AtomicBoolean; -public class MyThread extends Thread { +public class MyThread extends ThreadWrapper { public static AtomicBoolean resume = new AtomicBoolean(false); public int threadState=10; public MyThread() { diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS203/hs203t002/MyThread.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS203/hs203t002/MyThread.java index 94eee8ebe07..787da2529d6 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS203/hs203t002/MyThread.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS203/hs203t002/MyThread.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2018, 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 @@ -21,8 +21,10 @@ * questions. */ package nsk.jvmti.scenarios.hotswap.HS203.hs203t002; +import jdk.test.lib.thread.ThreadWrapper; + import java.util.concurrent.atomic.AtomicBoolean; -public class MyThread extends Thread { +public class MyThread extends ThreadWrapper { public static AtomicBoolean resume = new AtomicBoolean(false); public static AtomicBoolean resume2 = new AtomicBoolean(false); public int threadState=100; diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS203/hs203t002/hs203t002.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS203/hs203t002/hs203t002.java index d9dd4a88c80..f62bd49f674 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS203/hs203t002/hs203t002.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS203/hs203t002/hs203t002.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2020, 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 @@ -77,14 +77,14 @@ public class hs203t002 extends RedefineAgent { while(!MyThread.resume.get()); MyThread.resume.set(false); Thread.sleep(10000); - popThreadFrame(mt); - resumeThread(mt); + popThreadFrame(mt.getThread()); + resumeThread(mt.getThread()); while(!MyThread.resume2.get()); Thread.sleep(10000); - suspendThread(mt); + suspendThread(mt.getThread()); //mt.suspend(); - popThreadFrame(mt); - resumeThread(mt); + popThreadFrame(mt.getThread()); + resumeThread(mt.getThread()); MyThread.resume.set(true); mt.join(); log.println(" ..."+mt.threadState); diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS203/hs203t002/newclass00/MyThread.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS203/hs203t002/newclass00/MyThread.java index cce4bd7326e..185bb0bd5e1 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS203/hs203t002/newclass00/MyThread.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS203/hs203t002/newclass00/MyThread.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2018, 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 @@ -21,8 +21,10 @@ * questions. */ package nsk.jvmti.scenarios.hotswap.HS203.hs203t002; +import jdk.test.lib.thread.ThreadWrapper; + import java.util.concurrent.atomic.AtomicBoolean; -public class MyThread extends Thread { +public class MyThread extends ThreadWrapper { public static AtomicBoolean resume = new AtomicBoolean(false); public static AtomicBoolean resume2 = new AtomicBoolean(false); public int threadState=100; diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS203/hs203t003/MyThread.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS203/hs203t003/MyThread.java index dd07c191f38..7942e765299 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS203/hs203t003/MyThread.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS203/hs203t003/MyThread.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2018, 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 @@ -22,9 +22,11 @@ */ package nsk.jvmti.scenarios.hotswap.HS203.hs203t003; +import jdk.test.lib.thread.ThreadWrapper; + import java.util.concurrent.atomic.AtomicBoolean; -public class MyThread extends Thread { +public class MyThread extends ThreadWrapper { public static AtomicBoolean resume = new AtomicBoolean(false); public int threadState=0; // field watch is added on. diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS203/hs203t003/hs203t003.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS203/hs203t003/hs203t003.java index 348dc8b5ee4..6c4a018e0b7 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS203/hs203t003/hs203t003.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS203/hs203t003/hs203t003.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2020, 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 @@ -93,7 +93,7 @@ public class hs203t003 extends RedefineAgent { Thread.sleep(100); } // Wait for the thread to be suspended. - while (!isSuspended(mt)) { + while (!isSuspended(mt.getThread())) { if (!agentStatus()) { System.out.println("Failed to suspend thread"); return passed; @@ -101,12 +101,12 @@ public class hs203t003 extends RedefineAgent { Thread.sleep(100); } // Pop the frame. - if (!popThreadFrame(mt)) { + if (!popThreadFrame(mt.getThread())) { System.out.println("Failed to pop a frame = " + mt.threadState); } // Resume the thread. - if(!resumeThread(mt)) { + if(!resumeThread(mt.getThread())) { System.out.println("Failed to resume the thread = " + mt.threadState); } diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS203/hs203t003/newclass00/MyThread.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS203/hs203t003/newclass00/MyThread.java index e60b3da5c4b..105f90e360c 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS203/hs203t003/newclass00/MyThread.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS203/hs203t003/newclass00/MyThread.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2018, 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 @@ -22,8 +22,10 @@ */ package nsk.jvmti.scenarios.hotswap.HS203.hs203t003; +import jdk.test.lib.thread.ThreadWrapper; + import java.util.concurrent.atomic.AtomicBoolean; -public class MyThread extends Thread { +public class MyThread extends ThreadWrapper { public static AtomicBoolean resume = new AtomicBoolean(false); public int threadState=100; diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS203/hs203t004/MyThread.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS203/hs203t004/MyThread.java index 8cf6750917a..84eea67fe4a 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS203/hs203t004/MyThread.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS203/hs203t004/MyThread.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2021, 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 @@ -22,7 +22,9 @@ */ package nsk.jvmti.scenarios.hotswap.HS203.hs203t004; -public class MyThread extends Thread { +import jdk.test.lib.thread.ThreadWrapper; + +public class MyThread extends ThreadWrapper { public static volatile boolean stop = true; diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS203/hs203t004/hs203t004.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS203/hs203t004/hs203t004.java index f9f1aa8a270..c8b1f09bf1e 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS203/hs203t004/hs203t004.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS203/hs203t004/hs203t004.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 @@ -82,11 +82,11 @@ public class hs203t004 extends RedefineAgent { Thread.yield(); } - suspendThread(myThread); + suspendThread(myThread.getThread()); - popThreadFrame(myThread); + popThreadFrame(myThread.getThread()); - resumeThread(myThread); + resumeThread(myThread.getThread()); MyThread.stop = false; myThread.join(); diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS203/hs203t004/newclass00/MyThread.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS203/hs203t004/newclass00/MyThread.java index 7276b8cc5fd..f63dcc4577e 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS203/hs203t004/newclass00/MyThread.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS203/hs203t004/newclass00/MyThread.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2018, 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 @@ -23,7 +23,9 @@ package nsk.jvmti.scenarios.hotswap.HS203.hs203t004; -public class MyThread extends Thread { +import jdk.test.lib.thread.ThreadWrapper; + +public class MyThread extends ThreadWrapper { public static volatile boolean stop = true; diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS204/hs204t002/MyThread.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS204/hs204t002/MyThread.java index c502e124814..5a220345c2d 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS204/hs204t002/MyThread.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS204/hs204t002/MyThread.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2018, 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 @@ -21,7 +21,10 @@ * questions. */ package nsk.jvmti.scenarios.hotswap.HS204.hs204t002; -public class MyThread extends Thread{ + +import jdk.test.lib.thread.ThreadWrapper; + +public class MyThread extends ThreadWrapper { public static int value=100; static { System.out.println(" ... Break Point here.."); diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS204/hs204t002/newclass00/MyThread.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS204/hs204t002/newclass00/MyThread.java index 0556e5dd45e..2fb796f1f56 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS204/hs204t002/newclass00/MyThread.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS204/hs204t002/newclass00/MyThread.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2018, 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 @@ -21,7 +21,10 @@ * questions. */ package nsk.jvmti.scenarios.hotswap.HS204.hs204t002; -public class MyThread extends Thread{ + +import jdk.test.lib.thread.ThreadWrapper; + +public class MyThread extends ThreadWrapper { public static int value=200; static { System.out.println(" ... Break Point here.."); diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS204/hs204t003/MyThread.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS204/hs204t003/MyThread.java index 7e9ba68c85d..51f8af20995 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS204/hs204t003/MyThread.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS204/hs204t003/MyThread.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2018, 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 @@ -22,7 +22,9 @@ */ package nsk.jvmti.scenarios.hotswap.HS204.hs204t003; -public class MyThread extends Thread { +import jdk.test.lib.thread.ThreadWrapper; + +public class MyThread extends ThreadWrapper { private static volatile int intState=100; diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS204/hs204t003/hs204t003.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS204/hs204t003/hs204t003.java index b0f716794dc..6607f76899c 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS204/hs204t003/hs204t003.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS204/hs204t003/hs204t003.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2020, 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 @@ -61,6 +61,7 @@ package nsk.jvmti.scenarios.hotswap.HS204.hs204t003; import nsk.share.jvmti.RedefineAgent; +import jdk.test.lib.thread.ThreadWrapper; public class hs204t003 extends RedefineAgent { public native boolean popFrame(Thread thread) ; @@ -82,7 +83,7 @@ public class hs204t003 extends RedefineAgent { TempThread temp = new TempThread(); temp.start(); Thread.sleep(10000); - popFrame(temp); + popFrame(temp.getThread()); temp.join(); mthread = temp.mthread; mthread.start(); @@ -104,7 +105,7 @@ public class hs204t003 extends RedefineAgent { return passed; } } -class TempThread extends Thread { +class TempThread extends ThreadWrapper { public MyThread mthread; public TempThread() { super("TempThread."); diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS204/hs204t003/newclass00/MyThread.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS204/hs204t003/newclass00/MyThread.java index be2bc229538..b685015a2e3 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS204/hs204t003/newclass00/MyThread.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS204/hs204t003/newclass00/MyThread.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2018, 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 @@ -21,7 +21,10 @@ * questions. */ package nsk.jvmti.scenarios.hotswap.HS204.hs204t003; -public class MyThread extends Thread { + +import jdk.test.lib.thread.ThreadWrapper; + +public class MyThread extends ThreadWrapper { private static volatile int intState=0; public static int count=100; static { diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS204/hs204t004/hs204t004.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS204/hs204t004/hs204t004.java index e06eceeb425..08cd7d22d56 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS204/hs204t004/hs204t004.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/hotswap/HS204/hs204t004/hs204t004.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2020, 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 @@ -51,6 +51,8 @@ package nsk.jvmti.scenarios.hotswap.HS204.hs204t004; import java.util.concurrent.atomic.AtomicBoolean; + +import jdk.test.lib.thread.ThreadWrapper; import nsk.share.jvmti.RedefineAgent; public class hs204t004 extends RedefineAgent { @@ -71,7 +73,7 @@ public class hs204t004 extends RedefineAgent { mt.start(); while(!MyThread.resume.get()) ; Thread.sleep(10000); - popFrame(mt); + popFrame(mt.getThread()); mt.join(); } catch(Exception exp) { exp.printStackTrace(); @@ -88,7 +90,7 @@ public class hs204t004 extends RedefineAgent { public static native boolean popFrame(Thread thread); } -class MyThread extends Thread { +class MyThread extends ThreadWrapper { public static AtomicBoolean resume = new AtomicBoolean(false); public String name="MyThread"; diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/multienv/MA03/ma03t001.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/multienv/MA03/ma03t001.java index 87751c5960f..7aab6551203 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/multienv/MA03/ma03t001.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/multienv/MA03/ma03t001.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2004, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. * 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 @@ import java.io.PrintStream; import nsk.share.*; import nsk.share.jvmti.*; +import jdk.test.lib.thread.ThreadWrapper; public class ma03t001 extends DebugeeClass { @@ -79,7 +80,7 @@ public class ma03t001 extends DebugeeClass { /* =================================================================== */ -class ma03t001Thread extends Thread { +class ma03t001Thread extends ThreadWrapper { public Wicket startingBarrier = new Wicket(); public Wicket endingBarrier = new Wicket(); diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/sampling/SP01/sp01t001.java b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/sampling/SP01/sp01t001.java index 7686e68952e..4c9d9bc65a4 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/sampling/SP01/sp01t001.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/sampling/SP01/sp01t001.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2018, 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 @@ -28,6 +28,8 @@ import java.io.PrintStream; import nsk.share.*; import nsk.share.jvmti.*; +import jdk.test.lib.thread.ThreadWrapper; + public class sp01t001 extends DebugeeClass { // run test from command line @@ -51,7 +53,8 @@ public class sp01t001 extends DebugeeClass { int status = Consts.TEST_PASSED; // tested threads list - static sp01t001Thread threads[] = null; + static sp01t001Thread threadWrappers[] = null; + static Thread threads[] = null; static int indexStartedThread = 0; // run debuggee class @@ -60,21 +63,25 @@ public class sp01t001 extends DebugeeClass { log = new Log(out, argHandler); // create threads list - threads = new sp01t001Thread[] { + threadWrappers = new sp01t001Thread[] { // not started thread new sp01t001ThreadNotStarted(), // started threads new sp01t001ThreadFinished() }; + threads = new Thread[] { + threadWrappers[0].getThread(), + threadWrappers[1].getThread() + }; indexStartedThread = 1; // run threads try { // start threads for (int i = indexStartedThread; i < threads.length; i++) { - synchronized (threads[i].startingMonitor) { + synchronized (threadWrappers[i].startingMonitor) { threads[i].start(); - threads[i].startingMonitor.wait(); + threadWrappers[i].startingMonitor.wait(); } } @@ -98,7 +105,7 @@ public class sp01t001 extends DebugeeClass { /* =================================================================== */ // basic class for tested threads -abstract class sp01t001Thread extends Thread { +abstract class sp01t001Thread extends ThreadWrapper { public Object startingMonitor = new Object(); } diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/sampling/SP01/sp01t001/sp01t001.cpp b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/sampling/SP01/sp01t001/sp01t001.cpp index 40db16504b6..93a4e05b135 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/sampling/SP01/sp01t001/sp01t001.cpp +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/sampling/SP01/sp01t001/sp01t001.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2024, 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 @@ -34,7 +34,7 @@ extern "C" { /* constant names */ #define DEBUGEE_CLASS_NAME "nsk/jvmti/scenarios/sampling/SP01/sp01t001" -#define THREAD_CLASS_NAME "nsk/jvmti/scenarios/sampling/SP01/sp01t001Thread" +#define THREAD_CLASS_NAME "java/lang/Thread" #define THREADS_FIELD_NAME "threads" #define THREADS_FIELD_SIG "[L" THREAD_CLASS_NAME ";" diff --git a/test/hotspot/jtreg/vmTestbase/nsk/share/ExtraClassesBuilder.java b/test/hotspot/jtreg/vmTestbase/nsk/share/ExtraClassesBuilder.java index 162f8fa84c6..d92b1ef6b15 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/share/ExtraClassesBuilder.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/share/ExtraClassesBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 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,6 +63,8 @@ public class ExtraClassesBuilder { JDKToolLauncher javac = JDKToolLauncher.create("javac") .addToolArg("-d") .addToolArg(dst.toString()) + .addToolArg("-sourcepath") + .addToolArg(Utils.TEST_SRC_PATH) .addToolArg("-cp") .addToolArg(Utils.TEST_CLASS_PATH); diff --git a/test/lib/jdk/test/lib/Utils.java b/test/lib/jdk/test/lib/Utils.java index 2f46ed87340..95b7a117b2c 100644 --- a/test/lib/jdk/test/lib/Utils.java +++ b/test/lib/jdk/test/lib/Utils.java @@ -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 @@ -105,6 +105,11 @@ public final class Utils { */ public static final String TEST_SRC = System.getProperty("test.src", "").trim(); + /** + * Returns the value of 'test.src.path' system property. + */ + public static final String TEST_SRC_PATH = System.getProperty("test.src.path", "").trim(); + /** * Returns the value of 'test.root' system property. */ diff --git a/test/lib/jdk/test/lib/thread/ThreadWrapper.java b/test/lib/jdk/test/lib/thread/ThreadWrapper.java new file mode 100644 index 00000000000..ab850bf5a4a --- /dev/null +++ b/test/lib/jdk/test/lib/thread/ThreadWrapper.java @@ -0,0 +1,246 @@ +/* + * 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. + */ + + +package jdk.test.lib.thread; + +import java.time.Duration; +import java.util.Map; + +/* + The ThreadWrapper is a helper class that allows to extend + coverage of virtual threads testing for existing tests with threads. + + Specifically, it is useful for the pattern where Thread is extended + by some class. Example: + + class resumethrd02Thread extends Thread {...} + ... + resumethrd02Thread thr = new resumethrd02Thread(); + + The test can be updated to use this wrapper: + class resumethrd02Thread extends ThreadWrapper {...} + ... + resumethrd02Thread thr = new resumethrd02Thread(); + + So resumethrd02Thread can be run with platform or virtual threads. + + Method getThread() is used to get instance of Thread. + + It is not expected to use this wrapper for new tests or classes that + are not extending Thread. The TestThreadFactory should be used to + create threads in such cases. + */ + +public class ThreadWrapper implements Runnable { + private final Thread thread; + + @SuppressWarnings("this-escape") + public ThreadWrapper() { + // thread is a platform or virtual thread + thread = TestThreadFactory.newThread(this); + } + + @SuppressWarnings("this-escape") + public ThreadWrapper(String name) { + // thread is a platform or virtual thread + thread = TestThreadFactory.newThread(this, name); + } + + public Thread getThread() { + return thread; + } + + public static Thread currentThread() { + return Thread.currentThread(); + } + + public static void yield() { + Thread.yield(); + } + + public static void sleep(long millis) throws InterruptedException { + Thread.sleep(millis); + } + + public static void sleep(long millis, int nanos) throws InterruptedException { + Thread.sleep(millis, nanos); + } + + public static void sleep(Duration duration) throws InterruptedException { + Thread.sleep(duration); + } + + public static void onSpinWait() { + Thread.onSpinWait(); + } + + public static Thread.Builder.OfPlatform ofPlatform() { + return Thread.ofPlatform(); + } + + public static Thread.Builder.OfVirtual ofVirtual() { + return Thread.ofVirtual(); + } + + public static Thread startVirtualThread(Runnable task) { + return Thread.startVirtualThread(task); + } + + public boolean isVirtual() { + return thread.isVirtual(); + } + + public void start() { + thread.start(); + } + + public void run() { + } + + public void interrupt() { + thread.interrupt(); + } + + public static boolean interrupted() { + return Thread.interrupted(); + } + + public boolean isInterrupted() { + return thread.isInterrupted(); + } + + public boolean isAlive() { + return thread.isAlive(); + } + + public void setPriority(int newPriority) { + thread.setPriority(newPriority); + } + + public int getPriority() { + return thread.getPriority(); + } + + public void setName(String name) { + thread.setName(name); + } + + public String getName() { + return thread.getName(); + } + + public ThreadGroup getThreadGroup() { + return thread.getThreadGroup(); + } + + public static int activeCount() { + return Thread.activeCount(); + } + + public static int enumerate(Thread[] tarray) { + return Thread.enumerate(tarray); + } + + public void join(long millis) throws InterruptedException { + thread.join(millis); + } + + public void join(long millis, int nanos) throws InterruptedException { + thread.join(millis, nanos); + } + + public void join() throws InterruptedException { + thread.join(); + } + + public boolean join(Duration duration) throws InterruptedException { + return thread.join(duration); + } + + public static void dumpStack() { + Thread.dumpStack(); + } + + public void setDaemon(boolean on) { + thread.setDaemon(on); + } + + public boolean isDaemon() { + return thread.isDaemon(); + } + + @Override + public String toString() { + return thread.toString(); + } + + public ClassLoader getContextClassLoader() { + return thread.getContextClassLoader(); + } + + public void setContextClassLoader(ClassLoader cl) { + thread.setContextClassLoader(cl); + } + + public static boolean holdsLock(Object obj) { + return Thread.holdsLock(obj); + } + + public StackTraceElement[] getStackTrace() { + return thread.getStackTrace(); + } + + public static Map getAllStackTraces() { + return Thread.getAllStackTraces(); + } + + @Deprecated(since = "19") + public long getId() { + return thread.getId(); + } + + public long threadId() { + return thread.threadId(); + } + + public Thread.State getState() { + return thread.getState(); + } + + public static void setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler ueh) { + Thread.setDefaultUncaughtExceptionHandler(ueh); + } + + public static Thread.UncaughtExceptionHandler getDefaultUncaughtExceptionHandler() { + return Thread.getDefaultUncaughtExceptionHandler(); + } + + public Thread.UncaughtExceptionHandler getUncaughtExceptionHandler() { + return thread.getUncaughtExceptionHandler(); + } + + public void setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler ueh) { + thread.setUncaughtExceptionHandler(ueh); + } +} From aa5677afcbb4c42248168c364889cbd910327c0c Mon Sep 17 00:00:00 2001 From: Vladimir Ivanov Date: Fri, 10 Apr 2026 18:36:58 +0000 Subject: [PATCH 009/108] 8374496: C2: assert(val->find_edge(con) > 0) failed Reviewed-by: chagedorn, dfenacci --- src/hotspot/share/opto/parse2.cpp | 2 +- .../types/TestSubTypeCheckMismatch.java | 50 +++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 test/hotspot/jtreg/compiler/types/TestSubTypeCheckMismatch.java diff --git a/src/hotspot/share/opto/parse2.cpp b/src/hotspot/share/opto/parse2.cpp index a7c5398171b..ae20418942d 100644 --- a/src/hotspot/share/opto/parse2.cpp +++ b/src/hotspot/share/opto/parse2.cpp @@ -1803,8 +1803,8 @@ static bool match_type_check(PhaseGVN& gvn, assert(idx == 1 || idx == 2, ""); Node* vcon = val->in(idx); - assert(val->find_edge(con) > 0, ""); if ((btest == BoolTest::eq && vcon == con) || (btest == BoolTest::ne && vcon != con)) { + assert(val->find_edge(con) > 0, "mismatch"); SubTypeCheckNode* sub = b1->in(1)->as_SubTypeCheck(); Node* obj_or_subklass = sub->in(SubTypeCheckNode::ObjOrSubKlass); Node* superklass = sub->in(SubTypeCheckNode::SuperKlass); diff --git a/test/hotspot/jtreg/compiler/types/TestSubTypeCheckMismatch.java b/test/hotspot/jtreg/compiler/types/TestSubTypeCheckMismatch.java new file mode 100644 index 00000000000..5fcf93625a1 --- /dev/null +++ b/test/hotspot/jtreg/compiler/types/TestSubTypeCheckMismatch.java @@ -0,0 +1,50 @@ +/* + * 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. + */ + +/** + * @test + * @bug 8374496 + * @summary "C2: assert(val->find_edge(con) > 0) failed" + * + * @run main/othervm -Xbatch ${test.main.class} + */ +package compiler.types; + +public class TestSubTypeCheckMismatch { + static class A {} + + static Integer test(Object o, int a, int b) { + int i = -1; + if (o instanceof A) { // results in SubTypeCheck node and diamond-shape if + i = Math.min(a, b); + } + return Integer.valueOf(i); // late inlined + } + + public static void main(String[] args) { + for (int i = 0; i < 20_000; i++) { + test(new A(), 0, 0); + test(new Object(), 0, 0); + } + } +} From 322f3a3447419ae661eb83be6b1ae07dc41562ce Mon Sep 17 00:00:00 2001 From: Andrew Dinn Date: Fri, 10 Apr 2026 19:16:46 +0000 Subject: [PATCH 010/108] 8381932: Publish stubgen entries when -XX:-AOTStubCaching configured Reviewed-by: asmehra, kvn --- src/hotspot/share/code/aotCodeCache.cpp | 10 +++--- src/hotspot/share/code/aotCodeCache.hpp | 8 +++-- src/hotspot/share/runtime/stubRoutines.cpp | 41 +++++++++++++--------- 3 files changed, 33 insertions(+), 26 deletions(-) diff --git a/src/hotspot/share/code/aotCodeCache.cpp b/src/hotspot/share/code/aotCodeCache.cpp index 79211e75db4..a3dd3d2bfd0 100644 --- a/src/hotspot/share/code/aotCodeCache.cpp +++ b/src/hotspot/share/code/aotCodeCache.cpp @@ -1862,11 +1862,8 @@ void AOTCodeReader::read_dbg_strings(DbgStrings& dbg_strings) { // addresses, respectively, keyed by the relevant address void AOTCodeAddressTable::hash_address(address addr, int idx) { - // only do this if we are caching stubs and we have a non-null - // address to record - if (!AOTStubCaching) { - return; - } + // only do this if we have a non-null address to record and the + // cache is open for dumping if (addr == nullptr) { return; } @@ -2515,10 +2512,11 @@ AOTStubData::AOTStubData(BlobId blob_id) : // cannot be accessed before initialising the universe if (blob_id == BlobId::stubgen_preuniverse_id) { // invalidate any attempt to use this - _flags |= INVALID; + _flags = INVALID; return; } if (AOTCodeCache::is_on()) { + _flags = OPEN; // allow update of stub entry addresses if (AOTCodeCache::is_using_stub()) { // allow stub loading diff --git a/src/hotspot/share/code/aotCodeCache.hpp b/src/hotspot/share/code/aotCodeCache.hpp index c4ebe271767..5b773a986f1 100644 --- a/src/hotspot/share/code/aotCodeCache.hpp +++ b/src/hotspot/share/code/aotCodeCache.hpp @@ -236,9 +236,10 @@ private: // whether we are loading or storing stubs or have encountered any // invalid stubs. enum Flags { - USING = 1 << 0, // open and loading stubs - DUMPING = 1 << 1, // open and storing stubs - INVALID = 1 << 2, // found invalid stub when loading + OPEN = 1 << 0, // cache is open + USING = 1 << 1, // open and loading stubs + DUMPING = 1 << 2, // open and storing stubs + INVALID = 1 << 3, // found invalid stub when loading }; uint32_t _flags; @@ -253,6 +254,7 @@ public: ~AOTStubData() CDS_ONLY({FREE_C_HEAP_ARRAY(StubAddrRange, _ranges);}) NOT_CDS({}) + bool is_open() CDS_ONLY({ return (_flags & OPEN) != 0; }) NOT_CDS_RETURN_(false); bool is_using() CDS_ONLY({ return (_flags & USING) != 0; }) NOT_CDS_RETURN_(false); bool is_dumping() CDS_ONLY({ return (_flags & DUMPING) != 0; }) NOT_CDS_RETURN_(false); bool is_invalid() CDS_ONLY({ return (_flags & INVALID) != 0; }) NOT_CDS_RETURN_(false); diff --git a/src/hotspot/share/runtime/stubRoutines.cpp b/src/hotspot/share/runtime/stubRoutines.cpp index 0bc71c65471..f5509b9d996 100644 --- a/src/hotspot/share/runtime/stubRoutines.cpp +++ b/src/hotspot/share/runtime/stubRoutines.cpp @@ -178,18 +178,23 @@ static BufferBlob* initialize_stubs(BlobId blob_id, AOTStubData* stub_data_p = nullptr; LogTarget(Info, stubs) lt; + // we need to track and publish details of stubs in a stubgen blob + // when we are 1) using stubs from the cache 2) dumping stubs to the + // cache 3) generating stubs that may be needed by other cache + // elements. + + if (stub_data.is_open()) { + stub_data_p = &stub_data; + } if (code_size > 0 && stub_data.is_using()) { - // AOTCodeEntry tracks and logs status of any cached blob - bool loaded = stub_data.load_code_blob(); - if (loaded) { + // try to load the blob and details of its stubs from cache. if + // that fails we will still generate all necessary stubs + if (stub_data.load_code_blob()) { if (lt.is_enabled()) { LogStream ls(lt); ls.print_cr("Found blob %s in AOT cache", StubInfo::name(blob_id)); } - stub_data_p = &stub_data; } - } else if (stub_data.is_dumping()) { - stub_data_p = &stub_data; } // Even if we managed to load a blob from the AOT cache we still @@ -236,17 +241,8 @@ static BufferBlob* initialize_stubs(BlobId blob_id, "increase %s, code_size: %d, used: %d, free: %d", assert_msg, code_size, buffer.total_content_size(), buffer.insts_remaining()); - if (stub_data.is_using()) { - // we generated some new entries so republish all entries TODO - - // ensure we publish collect and publish the preuniverse stubs but - // don't try to save them - AOTCodeCache::publish_stub_addresses(*stubs_code, blob_id, &stub_data); - if (lt.is_enabled()) { - LogStream ls(lt); - ls.print_cr("Republished entries for blob '%s'", buffer_name); - } - } else if (stub_data.is_dumping()) { - // save the blob and publihs the entry addresses + if (stub_data.is_dumping()) { + // save the blob and publish the entry addresses if (stub_data.store_code_blob(*stubs_code, &buffer)) { if (lt.is_enabled()) { LogStream ls(lt); @@ -258,6 +254,17 @@ static BufferBlob* initialize_stubs(BlobId blob_id, ls.print_cr("Failed to store blob '%s' to Startup Code Cache", buffer_name); } } + } else if (stub_data.is_open()) { + // we either loaded some entries or generated new entries so + // publish all entries + // + // TODO - ensure we publish collect and publish the preuniverse + // stubs but don't try to save them + AOTCodeCache::publish_stub_addresses(*stubs_code, blob_id, &stub_data); + if (lt.is_enabled()) { + LogStream ls(lt); + ls.print_cr("Republished entries for blob '%s'", buffer_name); + } } // close off recording of any further stubgen generation From 88bd42d0350c126581b740bc9044aebcdb0138da Mon Sep 17 00:00:00 2001 From: William Kemper Date: Fri, 10 Apr 2026 19:39:39 +0000 Subject: [PATCH 011/108] 8314599: [GenShen] Couple adaptive tenuring and generation size budgeting Reviewed-by: kdnilsen, xpeng, ruili --- .../shenandoahGenerationalHeuristics.cpp | 124 ++++++++---------- .../shenandoahGenerationalHeuristics.hpp | 13 +- .../gc/shenandoah/shenandoahAgeCensus.cpp | 9 ++ .../gc/shenandoah/shenandoahAgeCensus.hpp | 6 + .../shenandoah/test_shenandoahAgeCensus.cpp | 9 +- 5 files changed, 93 insertions(+), 68 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.cpp index 45ba2740ea5..a12f48c0877 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.cpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.cpp @@ -38,11 +38,6 @@ using idx_t = ShenandoahSimpleBitMap::idx_t; -typedef struct { - ShenandoahHeapRegion* _region; - size_t _live_data; -} AgedRegionData; - static int compare_by_aged_live(AgedRegionData a, AgedRegionData b) { if (a._live_data < b._live_data) return -1; @@ -321,12 +316,48 @@ void ShenandoahGenerationalHeuristics::filter_regions(ShenandoahCollectionSet* c immediate_garbage); } -// Select for inclusion into the collection set all regions whose age is at or above tenure age and for which the -// garbage percentage exceeds a dynamically adjusted threshold (known as the old-garbage threshold percentage). We -// identify these regions by setting the appropriate entry of the collection set's preselected regions array to true. -// All entries are initialized to false before calling this function. +void ShenandoahGenerationalHeuristics::add_tenured_regions_to_collection_set(const size_t old_promotion_reserve, + ShenandoahGenerationalHeap *const heap, + size_t candidates, AgedRegionData* sorted_regions) { + size_t old_consumed = 0; + if (candidates > 0) { + // Sort in increasing order according to live data bytes. Note that + // candidates represents the number of regions that qualify to be promoted + // by evacuation. + QuickSort::sort(sorted_regions, candidates, + compare_by_aged_live); + + size_t selected_regions = 0; + size_t selected_live = 0; + for (size_t i = 0; i < candidates; i++) { + ShenandoahHeapRegion *const region = sorted_regions[i]._region; + const size_t region_live_data = sorted_regions[i]._live_data; + const size_t promotion_need = (size_t)(region_live_data * ShenandoahPromoEvacWaste); + if (old_consumed + promotion_need > old_promotion_reserve) { + // We rejected the remaining promotable regions from the collection set + // because we have no room to hold their evacuees. We do not need to + // iterate the remaining regions to estimate the amount we expect to + // promote because we know it directly form the census we computed + // during the preceding mark phase. + break; + } + + old_consumed += promotion_need; + heap->collection_set()->add_region(region); + selected_regions++; + selected_live += region_live_data; + } + log_debug(gc, ergo)( "Preselected %zu regions containing " PROPERFMT " live data," + " consuming: " PROPERFMT " of budgeted: " PROPERFMT, + selected_regions, PROPERFMTARGS(selected_live), + PROPERFMTARGS(old_consumed), PROPERFMTARGS(old_promotion_reserve)); + } +} + +// Select for inclusion into the collection set all regions whose age is at or +// above tenure age and for which the +// garbage percentage exceeds a dynamically adjusted threshold (known as the old-garbage threshold percentage). // -// During the subsequent selection of the collection set, we give priority to these promotion set candidates. // Without this prioritization, we found that the aged regions tend to be ignored because they typically have // much less garbage and much more live data than the recently allocated "eden" regions. When aged regions are // repeatedly excluded from the collection set, the amount of live memory within the young generation tends to @@ -334,8 +365,8 @@ void ShenandoahGenerationalHeuristics::filter_regions(ShenandoahCollectionSet* c // CPU and wall-clock time. // // A second benefit of treating aged regions differently than other regions during collection set selection is -// that this allows us to more accurately budget memory to hold the results of evacuation. Memory for evacuation -// of aged regions must be reserved in the old generation. Memory for evacuation of all other regions must be +// that this allows us to more accurately budget memory to hold the results of evacuation. Memory for evacuation +// of aged regions must be reserved in the old generation. Memory for evacuation of all other regions must be // reserved in the young generation. size_t ShenandoahGenerationalHeuristics::select_aged_regions(ShenandoahInPlacePromotionPlanner& in_place_promotions, const size_t old_promotion_reserve) { @@ -345,7 +376,6 @@ size_t ShenandoahGenerationalHeuristics::select_aged_regions(ShenandoahInPlacePr auto const heap = ShenandoahGenerationalHeap::heap(); - size_t promo_potential = 0; size_t candidates = 0; // Sort the promotion-eligible regions in order of increasing live-data-bytes so that we can first reclaim regions that require @@ -386,66 +416,28 @@ size_t ShenandoahGenerationalHeuristics::select_aged_regions(ShenandoahInPlacePr sorted_regions[candidates]._live_data = r->get_live_data_bytes(); candidates++; } - } else { - // We only evacuate & promote objects from regular regions whose garbage() is above old-garbage-threshold. - // Objects in tenure-worthy regions with less garbage are promoted in place. These take a different path to - // old-gen. Regions excluded from promotion because their garbage content is too low (causing us to anticipate that - // the region would be promoted in place) may be eligible for evacuation promotion by the time promotion takes - // place during a subsequent GC pass because more garbage is found within the region between now and then. This - // should not happen if we are properly adapting the tenure age. The theory behind adaptive tenuring threshold - // is to choose the youngest age that demonstrates no "significant" further loss of population since the previous - // age. If not this, we expect the tenure age to demonstrate linear population decay for at least two population - // samples, whereas we expect to observe exponential population decay for ages younger than the tenure age. - // - // In the case that certain regions which were anticipated to be promoted in place need to be promoted by - // evacuation, it may be the case that there is not sufficient reserve within old-gen to hold evacuation of - // these regions. The likely outcome is that these regions will not be selected for evacuation or promotion - // in the current cycle and we will anticipate that they will be promoted in the next cycle. This will cause - // us to reserve more old-gen memory so that these objects can be promoted in the subsequent cycle. - if (heap->is_aging_cycle() && heap->age_census()->is_tenurable(r->age() + 1)) { - if (r->garbage() >= in_place_promotions.old_garbage_threshold()) { - promo_potential += r->get_live_data_bytes(); - } - } } - // Note that we keep going even if one region is excluded from selection. - // Subsequent regions may be selected if they have smaller live data. } in_place_promotions.complete_planning(); - // Sort in increasing order according to live data bytes. Note that candidates represents the number of regions - // that qualify to be promoted by evacuation. - size_t old_consumed = 0; - if (candidates > 0) { - size_t selected_regions = 0; - size_t selected_live = 0; - QuickSort::sort(sorted_regions, candidates, compare_by_aged_live); - for (size_t i = 0; i < candidates; i++) { - ShenandoahHeapRegion* const region = sorted_regions[i]._region; - const size_t region_live_data = sorted_regions[i]._live_data; - const size_t promotion_need = (size_t) (region_live_data * ShenandoahPromoEvacWaste); - if (old_consumed + promotion_need <= old_promotion_reserve) { - old_consumed += promotion_need; - heap->collection_set()->add_region(region); - selected_regions++; - selected_live += region_live_data; - } else { - // We rejected this promotable region from the collection set because we had no room to hold its copy. - // Add this region to promo potential for next GC. - promo_potential += region_live_data; - assert(!heap->collection_set()->is_in(region), "Region %zu shouldn't be in the collection set", region->index()); - } - // We keep going even if one region is excluded from selection because we need to accumulate all eligible - // regions that are not preselected into promo_potential - } - log_debug(gc, ergo)("Preselected %zu regions containing " PROPERFMT " live data," - " consuming: " PROPERFMT " of budgeted: " PROPERFMT, - selected_regions, PROPERFMTARGS(selected_live), PROPERFMTARGS(old_consumed), PROPERFMTARGS(old_promotion_reserve)); - } + add_tenured_regions_to_collection_set(old_promotion_reserve, heap, candidates, sorted_regions); - log_info(gc, ergo)("Promotion potential of aged regions with sufficient garbage: " PROPERFMT, PROPERFMTARGS(promo_potential)); + const uint tenuring_threshold = heap->age_census()->tenuring_threshold(); + const size_t tenurable_this_cycle = heap->age_census()->get_tenurable_bytes(tenuring_threshold); + const size_t tenurable_next_cycle = heap->age_census()->get_tenurable_bytes(tenuring_threshold - 1); + assert(tenurable_next_cycle >= tenurable_this_cycle, + "Tenurable next cycle (" PROPERFMT ") should include tenurable this cycle (" PROPERFMT ")", + PROPERFMTARGS(tenurable_next_cycle), PROPERFMTARGS(tenurable_this_cycle)); + + const size_t max_promotions = tenurable_this_cycle * ShenandoahPromoEvacWaste; + const size_t old_consumed = MIN2(max_promotions, old_promotion_reserve); + + // Don't include the bytes we expect to promote in this cycle in the next cycle + const size_t promo_potential = (tenurable_next_cycle - tenurable_this_cycle) * ShenandoahPromoEvacWaste; heap->old_generation()->set_promotion_potential(promo_potential); + log_info(gc, ergo)("Promotion potential of aged regions with sufficient garbage: " PROPERFMT, PROPERFMTARGS(promo_potential)); + return old_consumed; } diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.hpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.hpp index d6551cffb73..1a47cb756f4 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.hpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.hpp @@ -34,6 +34,11 @@ class ShenandoahHeap; class ShenandoahCollectionSet; class RegionData; +typedef struct { + ShenandoahHeapRegion* _region; + size_t _live_data; +} AgedRegionData; + /* * This class serves as the base class for heuristics used to trigger and * choose the collection sets for young and global collections. It leans @@ -50,7 +55,7 @@ public: void choose_collection_set(ShenandoahCollectionSet* collection_set) override; - virtual void post_initialize() override; + void post_initialize() override; private: // Compute evacuation budgets prior to choosing collection set. @@ -73,6 +78,12 @@ private: // to false. size_t select_aged_regions(ShenandoahInPlacePromotionPlanner& in_place_promotions, const size_t old_promotion_reserve); + // Select regions for inclusion in the collection set that are tenured, but do + // not hold enough live data to warrant promotion in place. + void add_tenured_regions_to_collection_set(size_t old_promotion_reserve, + ShenandoahGenerationalHeap *const heap, + size_t candidates, AgedRegionData* sorted_regions); + // Filter and sort remaining regions before adding to collection set. void filter_regions(ShenandoahCollectionSet* collection_set); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp index 71fd6e37614..a81efa99d70 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.cpp @@ -171,6 +171,15 @@ void ShenandoahAgeCensus::update_census(size_t age0_pop) { NOT_PRODUCT(update_total();) } +size_t ShenandoahAgeCensus::get_tenurable_bytes(const uint tenuring_threshold) const { + assert(_epoch < MAX_SNAPSHOTS, "Out of bounds"); + size_t total = 0; + const AgeTable* pv = _global_age_tables[_epoch]; + for (uint i = tenuring_threshold; i < MAX_COHORTS; i++) { + total += pv->sizes[i]; + } + return total * HeapWordSize; +} // Reset the epoch for the global age tables, // clearing all history. diff --git a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp index 9c5baaedcd6..c140f445e21 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahAgeCensus.hpp @@ -216,6 +216,12 @@ class ShenandoahAgeCensus: public CHeapObj { // allocated when the concurrent marking was in progress. void update_census(size_t age0_pop); + // Return the total size of the population at or above the given threshold for the current epoch + size_t get_tenurable_bytes(uint tenuring_threshold) const; + + // As above, but use the current tenuring threshold + size_t get_tenurable_bytes() const { return get_tenurable_bytes(tenuring_threshold()); } + // Reset the epoch, clearing accumulated census history // Note: this isn't currently used, but reserved for planned // future usage. diff --git a/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp b/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp index c53d0a15554..0b89c59634a 100644 --- a/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp +++ b/test/hotspot/gtest/gc/shenandoah/test_shenandoahAgeCensus.cpp @@ -63,7 +63,7 @@ protected: total += _cohort_populations[i]; } } - return total; + return total * HeapWordSize; } void promote_all_tenurable(const size_t tenuring_threshold) { @@ -87,6 +87,13 @@ TEST_F(ShenandoahAgeCensusTest, initialize) { EXPECT_EQ(census.tenuring_threshold(), ShenandoahAgeCensus::MAX_COHORTS); } +TEST_F(ShenandoahAgeCensusTest, get_tenurable_bytes) { + ShenandoahAgeCensus census(1); + update(census); + EXPECT_EQ(get_total_population_older_than(1), census.get_tenurable_bytes(1)); + EXPECT_LT(census.get_tenurable_bytes(2), census.get_tenurable_bytes(1)); +} + TEST_F(ShenandoahAgeCensusTest, ignore_small_populations) { // Small populations are ignored so we do not return early before reaching the youngest cohort. ShenandoahAgeCensus census(1); From aa6db8d06e59bb91630be6d7f75da195d39d3190 Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Fri, 10 Apr 2026 23:44:17 +0000 Subject: [PATCH 012/108] 8382022: jpackage: enhance CompositeProxy Reviewed-by: almatvee --- .../internal/util/CompositeProxy.java | 737 +++++++----- .../internal/util/CompositeProxyTest.java | 1062 +++++++++++++++-- .../tools/jdk/jpackage/test/JUnitUtils.java | 14 + 3 files changed, 1464 insertions(+), 349 deletions(-) diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/CompositeProxy.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/CompositeProxy.java index 39a9d319468..e4257c87306 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/CompositeProxy.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/CompositeProxy.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 @@ -31,17 +31,17 @@ import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.lang.reflect.Proxy; +import java.util.ArrayList; import java.util.Arrays; +import java.util.BitSet; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Set; -import java.util.function.BinaryOperator; +import java.util.Optional; import java.util.function.Predicate; +import java.util.stream.Collectors; import java.util.stream.Stream; /** @@ -95,11 +95,9 @@ import java.util.stream.Stream; * } * }; * - * Sloop sloop = CompositeProxy.create(Sloop.class, new Sailboat() { - * }, withMain, withJib); + * Sloop sloop = CompositeProxy.create(Sloop.class, withMain, withJib); * - * Catboat catboat = CompositeProxy.create(Catboat.class, new Sailboat() { - * }, withMain); + * Catboat catboat = CompositeProxy.create(Catboat.class, withMain); * * sloop.trimSails(); * catboat.trimSails(); @@ -137,20 +135,60 @@ public final class CompositeProxy { * dispatching the interface method invocations to the given handlers */ public T create(Class interfaceType, Object... slices) { - return CompositeProxy.createCompositeProxy(interfaceType, conflictResolver, invokeTunnel, slices); + return CompositeProxy.createCompositeProxy( + interfaceType, + Optional.ofNullable(methodConflictResolver).orElse(JPACKAGE_METHOD_CONFLICT_RESOLVER), + Optional.ofNullable(objectConflictResolver).orElse(JPACKAGE_OBJECT_CONFLICT_RESOLVER), + invokeTunnel, + allowUnreferencedSlices, + slices); } /** * Sets the method dispatch conflict resolver for this builder. The conflict * resolver is used by composite proxy to select a method call handler from - * multiple candidates. + * several candidates. * - * @param v the conflict resolver for this builder or null if the - * default conflict resolver should be used + * @param v the method conflict resolver for this builder or null + * if the default conflict resolver should be used * @return this */ - public Builder conflictResolver(BinaryOperator v) { - conflictResolver = v; + public Builder methodConflictResolver(MethodConflictResolver v) { + methodConflictResolver = v; + return this; + } + + /** + * Sets the object dispatch conflict resolver for this builder. The conflict + * resolver is used by the composite proxy to select an object from several + * candidates. + * + * @param v the object conflict resolver for this builder or null + * if the default conflict resolver should be used + * @return this + */ + public Builder objectConflictResolver(ObjectConflictResolver v) { + objectConflictResolver = v; + return this; + } + + /** + * Configures if this builder allows unreferenced slices in the + * {@link #create(Class, Object...)}. + *

+ * By default, if the builder happens to create such a composite proxy that one + * or more slices passed in the {@link #create(Class, Object...)} method happen + * to be unreferenced, it will throw {@code IllegalArgumentException}. Passing + * true disables this throw cause. + * + * @param v true to disable throwing of + * {@code IllegalArgumentException} from + * {@link #create(Class, Object...)} if some of the passed in slices + * happen to be unreferenced and false otherwise + * @return this + */ + public Builder allowUnreferencedSlices(boolean v) { + allowUnreferencedSlices = v; return this; } @@ -168,8 +206,65 @@ public final class CompositeProxy { private Builder() {} - private BinaryOperator conflictResolver = STANDARD_CONFLICT_RESOLVER; + private MethodConflictResolver methodConflictResolver; + private ObjectConflictResolver objectConflictResolver; private InvokeTunnel invokeTunnel; + private boolean allowUnreferencedSlices; + } + + /** + * Method conflict resolver. Used when the composite proxy needs to decide if + * the default method of the interface it implements should be overridden by an + * implementing object. + */ + @FunctionalInterface + public interface MethodConflictResolver { + + /** + * Returns {@code true} if the composite proxy should override the default + * method {@code method} in {@code interfaceType} type with the corresponding + * method form the {@code obj}. + * + * @param interfaceType the interface type composite proxy instance should + * implement + * @param slices all objects passed to the calling composite proxy. The + * value is a copy of the last parameter passed in the + * {@link Builder#create(Class, Object...)} + * @param method default method in {@code interfaceType} type + * @param obj object providing a usable method with the same signature + * (the name and parameter types) as the signature of the + * {@code method} method + */ + boolean isOverrideDefault(Class interfaceType, Object[] slices, Method method, Object obj); + } + + /** + * Object conflict resolver. Used when several objects have methods that are + * candidates to implement some method in an interface and the composite proxy + * needs to choose one of these objects. + */ + @FunctionalInterface + public interface ObjectConflictResolver { + + /** + * Returns the object that should be used in a composite proxy to implement + * abstract method {@code method}. + * + * @param interfaceType the interface type composite proxy instance should + * implement + * @param slices all objects passed to the calling composite proxy. The + * value is a copy of the last parameter passed in the + * {@link Builder#create(Class, Object...)} + * @param method abstract method + * @param candidates objects with a method with the same signature (the name + * and parameter types) as the signature of the + * {@code method} method. The array is unordered, doesn't + * contain duplicates, and is a subset of the + * {@code slices} array + * @return either one of items from the {@code candidates} or {@code null} if + * can't choose one + */ + Object choose(Class interfaceType, Object[] slices, Method method, Object[] candidates); } /** @@ -263,233 +358,231 @@ public final class CompositeProxy { private CompositeProxy() { } - private static T createCompositeProxy(Class interfaceType, BinaryOperator conflictResolver, - InvokeTunnel invokeTunnel, Object... slices) { + private static T createCompositeProxy( + Class interfaceType, + MethodConflictResolver methodConflictResolver, + ObjectConflictResolver objectConflictResolver, + InvokeTunnel invokeTunnel, + boolean allowUnreferencedSlices, + Object... slices) { - validateTypeIsInterface(interfaceType); + Objects.requireNonNull(interfaceType); + Objects.requireNonNull(methodConflictResolver); + Objects.requireNonNull(objectConflictResolver); + Stream.of(slices).forEach(Objects::requireNonNull); - final var interfaces = interfaceType.getInterfaces(); - List.of(interfaces).forEach(CompositeProxy::validateTypeIsInterface); - - if (interfaces.length != slices.length) { - throw new IllegalArgumentException( - String.format("type %s must extend %d interfaces", interfaceType.getName(), slices.length)); + if (!interfaceType.isInterface()) { + throw new IllegalArgumentException(String.format("Type %s must be an interface", interfaceType.getName())); } - final Map, Object> interfaceDispatch = createInterfaceDispatch(interfaces, slices); + final var uniqueSlices = Stream.of(slices).map(IdentityWrapper::new).collect(toSet()); + + final var unreferencedSlicesBuilder = SetBuilder.>build().emptyAllowed(true); + + if (!allowUnreferencedSlices) { + unreferencedSlicesBuilder.add(uniqueSlices); + } final Map methodDispatch = getProxyableMethods(interfaceType).map(method -> { - var handler = createHandler(interfaceType, method, interfaceDispatch, conflictResolver, invokeTunnel); - if (handler != null) { - return Map.entry(method, handler); - } else { - return null; + return Map.entry(method, uniqueSlices.stream().flatMap(slice -> { + var sliceMethods = getImplementerMethods(slice.value()).filter(sliceMethod -> { + return signatureEquals(sliceMethod, method); + }).toList(); + + if (sliceMethods.size() > 1) { + throw new AssertionError(); + } + + return sliceMethods.stream().findFirst().map(sliceMethod -> { + return Map.entry(slice, sliceMethod); + }).stream(); + }).toList()); + }).flatMap(e -> { + final Method method = e.getKey(); + final List, Method>> slicesWithMethods = e.getValue(); + + final Map.Entry, Method> sliceWithMethods; + switch (slicesWithMethods.size()) { + case 0 -> { + if (!method.isDefault()) { + throw new IllegalArgumentException(String.format("None of the slices can handle %s", method)); + } else { + return Optional.ofNullable(createHandlerForDefaultMethod(method, invokeTunnel)).map(handler -> { + return Map.entry(method, handler); + }).stream(); + } + } + case 1 -> { + sliceWithMethods = slicesWithMethods.getFirst(); + } + default -> { + var candidates = slicesWithMethods.stream().map(sliceEntry -> { + return sliceEntry.getKey().value(); + }).toList(); + + var candidate = objectConflictResolver.choose( + interfaceType, Arrays.copyOf(slices, slices.length), method, candidates.toArray()); + if (candidate == null) { + throw new IllegalArgumentException(String.format( + "Ambiguous choice between %s for %s", candidates, method)); + } + + var candidateIdentity = IdentityWrapper.wrapIdentity(candidate); + + if (candidates.stream().map(IdentityWrapper::new).noneMatch(Predicate.isEqual(candidateIdentity))) { + throw new UnsupportedOperationException(); + } + + sliceWithMethods = slicesWithMethods.stream().filter(v -> { + return candidateIdentity.equals(v.getKey()); + }).findFirst().orElseThrow(); + } } - }).filter(Objects::nonNull).collect(toMap(Map.Entry::getKey, Map.Entry::getValue)); + + final var slice = sliceWithMethods.getKey().value(); + final var sliceMethod = sliceWithMethods.getValue(); + final Handler handler; + if (!method.isDefault() + || (method.equals(sliceMethod) + && getUnfilteredImplementerMethods(slice) + .map(FullMethodSignature::new) + .anyMatch(Predicate.isEqual(new FullMethodSignature(sliceMethod)))) + || ( method.getReturnType().equals(sliceMethod.getReturnType()) + && !sliceMethod.isDefault() + && methodConflictResolver.isOverrideDefault(interfaceType, Arrays.copyOf(slices, slices.length), method, slice))) { + // Use implementation from the slice if one of the statements is "true": + // - The target method is abstract (not default) + // - The target method is default and it is the same method in the slice which overrides it. + // This is a special case when default method must not be invoked via InvocationHandler.invokeDefault(). + // - The target method is default and the matching slice method has the same return type, + // is not default, and the method conflict resolver approves the use of the slice method + if (!allowUnreferencedSlices) { + unreferencedSlicesBuilder.remove(sliceWithMethods.getKey()); + } + handler = createHandlerForMethod(slice, sliceMethod, invokeTunnel); + } else { + handler = createHandlerForDefaultMethod(method, invokeTunnel); + } + + return Optional.ofNullable(handler).map(h -> { + return Map.entry(method, h); + }).stream(); + + }).collect(toMap(Map.Entry::getKey, Map.Entry::getValue)); + + if (!allowUnreferencedSlices) { + var unreferencedSlices = unreferencedSlicesBuilder.create().stream().map(IdentityWrapper::value).toList(); + if (!unreferencedSlices.isEmpty()) { + throw new IllegalArgumentException(String.format("Unreferenced slices: %s", unreferencedSlices)); + } + } @SuppressWarnings("unchecked") T proxy = (T) Proxy.newProxyInstance(interfaceType.getClassLoader(), new Class[] { interfaceType }, - new CompositeProxyInvocationHandler(methodDispatch)); + new CompositeProxyInvocationHandler(Collections.unmodifiableMap(methodDispatch))); return proxy; } - private record InterfaceDispatchBuilder(Set> interfaces, Collection slices) { - - InterfaceDispatchBuilder { - Objects.requireNonNull(interfaces); - Objects.requireNonNull(slices); - - if (interfaces.isEmpty()) { - throw new IllegalArgumentException("No interfaces to dispatch"); - } - - if (slices.isEmpty()) { - throw createInterfaceNotImplementedException(interfaces); - } - } - - InterfaceDispatchBuilder(Result result) { - this(result.unservedInterfaces(), result.unusedSlices()); - } - - Map, List> createDispatchGroups() { - return interfaces.stream().collect(toMap(x -> x, iface -> { - return slices.stream().filter(obj -> { - return Stream.of(obj.getClass().getInterfaces()).flatMap(sliceIface -> { - return unfoldInterface(sliceIface); - }).anyMatch(Predicate.isEqual(iface)); - }).toList(); - })); - } - - Result createDispatch() { - var groups = createDispatchGroups(); - - var dispatch = groups.entrySet().stream().filter(e -> { - return e.getValue().size() == 1; - }).collect(toMap(Map.Entry::getKey, e -> { - return e.getValue().getFirst(); - })); - - var unservedInterfaces = groups.entrySet().stream().filter(e -> { - return e.getValue().size() != 1; - }).map(Map.Entry::getKey).collect(toSet()); - - var usedSliceIdentities = dispatch.values().stream() - .map(IdentityWrapper::new) - .collect(toSet()); - - var unusedSliceIdentities = new HashSet<>(toIdentitySet(slices)); - unusedSliceIdentities.removeAll(usedSliceIdentities); - - return new Result(dispatch, unservedInterfaces, unusedSliceIdentities.stream().map(IdentityWrapper::value).toList()); - } - - private record Result(Map, Object> dispatch, Set> unservedInterfaces, Collection unusedSlices) { - - Result { - Objects.requireNonNull(dispatch); - Objects.requireNonNull(unservedInterfaces); - Objects.requireNonNull(unusedSlices); - - if (!Collections.disjoint(dispatch.keySet(), unservedInterfaces)) { - throw new IllegalArgumentException(); - } - - if (!Collections.disjoint(toIdentitySet(dispatch.values()), toIdentitySet(unusedSlices))) { - throw new IllegalArgumentException(); - } - } - } - - private static Collection> toIdentitySet(Collection v) { - return v.stream().map(IdentityWrapper::new).collect(toSet()); - } - } - - private static Map, Object> createInterfaceDispatch(Class[] interfaces, Object[] slices) { - - if (interfaces.length == 0) { - return Collections.emptyMap(); - } - - Map, Object> dispatch = new HashMap<>(); - - var builder = new InterfaceDispatchBuilder(Set.of(interfaces), List.of(slices)); - for (;;) { - var result = builder.createDispatch(); - if (result.dispatch().isEmpty()) { - var unserved = builder.createDispatchGroups(); - for (var e : unserved.entrySet()) { - var iface = e.getKey(); - var ifaceSlices = e.getValue(); - if (ifaceSlices.size() > 1) { - throw new IllegalArgumentException( - String.format("multiple slices %s implement %s", ifaceSlices, iface)); - } - } - - var unservedInterfaces = unserved.entrySet().stream().filter(e -> { - return e.getValue().isEmpty(); - }).map(Map.Entry::getKey).toList(); - throw createInterfaceNotImplementedException(unservedInterfaces); - } else { - dispatch.putAll(result.dispatch()); - if (result.unservedInterfaces().isEmpty()) { - break; - } - } - - builder = new InterfaceDispatchBuilder(result); - } - - return dispatch.keySet().stream().flatMap(iface -> { - return unfoldInterface(iface).map(unfoldedIface -> { - return Map.entry(unfoldedIface, dispatch.get(iface)); - }); - }).collect(toMap(Map.Entry::getKey, Map.Entry::getValue)); - } - private static Stream> unfoldInterface(Class interfaceType) { - return Stream.concat(Stream.of(interfaceType), - Stream.of(interfaceType.getInterfaces()).flatMap(CompositeProxy::unfoldInterface)); + return Stream.concat( + Stream.of(interfaceType), + Stream.of(interfaceType.getInterfaces() + ).flatMap(CompositeProxy::unfoldInterface)); } - private static IllegalArgumentException createInterfaceNotImplementedException( - Collection> missingInterfaces) { - return new IllegalArgumentException(String.format("none of the slices implement %s", missingInterfaces)); - } + private static List> getSuperclasses(Class type) { + List> superclasses = new ArrayList<>(); - private static void validateTypeIsInterface(Class type) { - if (!type.isInterface()) { - throw new IllegalArgumentException(String.format("type %s must be an interface", type.getName())); + var current = type.getSuperclass(); + + while (current != null) { + superclasses.add(current); + current = current.getSuperclass(); } + + return superclasses; } - private static Handler createHandler(Class interfaceType, Method method, Map, Object> interfaceDispatch, - BinaryOperator conflictResolver, InvokeTunnel invokeTunnel) { - - final var methodDeclaringClass = method.getDeclaringClass(); - - if (!methodDeclaringClass.equals(interfaceType)) { - // The method is declared in one of the superinterfaces. - final var slice = interfaceDispatch.get(methodDeclaringClass); - - if (isInvokeDefault(method, slice)) { - return createHandlerForDefaultMethod(method, invokeTunnel); - } else { - return createHandlerForMethod(slice, method, invokeTunnel); - } - } else if (method.isDefault()) { - return createHandlerForDefaultMethod(method, invokeTunnel); - } else { - // Find a slice handling the method. - var handler = interfaceDispatch.entrySet().stream().map(e -> { - try { - Class iface = e.getKey(); - Object slice = e.getValue(); - return createHandlerForMethod(slice, iface.getMethod(method.getName(), method.getParameterTypes()), - invokeTunnel); - } catch (NoSuchMethodException ex) { - return null; - } - }).filter(Objects::nonNull).reduce(new ConflictResolverAdapter(conflictResolver)).orElseThrow(() -> { - return new IllegalArgumentException(String.format("none of the slices can handle %s", method)); - }); - - return handler; - } + private static Stream getUnfilteredProxyableMethods(Class interfaceType) { + return unfoldInterface(interfaceType).flatMap(type -> { + return Stream.of(type.getMethods()); + }).filter(method -> { + return !Modifier.isStatic(method.getModifiers()) + && !method.isBridge(); + }); } private static Stream getProxyableMethods(Class interfaceType) { - return Stream.of(interfaceType.getMethods()).filter(method -> !Modifier.isStatic(method.getModifiers())); + return removeRedundancy(getUnfilteredProxyableMethods(interfaceType)); } - private static boolean isInvokeDefault(Method method, Object slice) { - if (!method.isDefault()) { - return false; - } + private static Stream getUnfilteredImplementerMethods(Object slice) { + var sliceType = slice.getClass(); - // The "method" is default. - // See if is overridden by any non-abstract method in the "slice". - // If it is, InvocationHandler.invokeDefault() should not be used to call it. + return Stream.of( + Stream.of(sliceType), + getSuperclasses(sliceType).stream() + ).flatMap(x -> x).flatMap(type -> { + return Stream.of(type.getMethods()); + }).filter(method -> { + return !Modifier.isStatic(method.getModifiers()) + && !method.isBridge() + && !method.isDefault() + && !Modifier.isPrivate(method.getModifiers()); + }); + } - final var sliceClass = slice.getClass(); + private static Stream getImplementerMethods(Object slice) { + var sliceType = slice.getClass(); - final var methodOverriden = Stream.of(sliceClass.getMethods()).filter(Predicate.not(Predicate.isEqual(method))) - .filter(sliceMethod -> !Modifier.isAbstract(sliceMethod.getModifiers())) - .anyMatch(sliceMethod -> signatureEquals(sliceMethod, method)); + var proxyableMethods = Stream.of( + Stream.of(sliceType), + getSuperclasses(sliceType).stream() + ).flatMap(x -> x) + .map(Class::getInterfaces) + .flatMap(Stream::of) + .flatMap(CompositeProxy::unfoldInterface) + .flatMap(CompositeProxy::getUnfilteredProxyableMethods) + .toList(); - return !methodOverriden; + var proxyableMethodSignatures = proxyableMethods.stream() + .map(FullMethodSignature::new) + .collect(toSet()); + + var methods = getUnfilteredImplementerMethods(slice).filter(method -> { + return !proxyableMethodSignatures.contains(new FullMethodSignature(method)); + }); + + return removeRedundancy(Stream.concat(methods, proxyableMethods.stream())); + } + + private static Stream removeRedundancy(Stream methods) { + var groups = methods.distinct().collect(Collectors.groupingBy(MethodSignature::new)).values(); + return groups.stream().map(group -> { + // All but a single method should be filtered out from the group. + return group.stream().reduce((a, b) -> { + var ac = a.getDeclaringClass(); + var bc = b.getDeclaringClass(); + if (ac.equals(bc)) { + // Both methods don't fit: they are declared in the same class and have the same signatures. + // That is possible only with code generation bypassing compiler checks. + throw new AssertionError(); + } else if (ac.isAssignableFrom(bc)) { + return b; + } else if (bc.isAssignableFrom(ac)) { + return a; + } else if (a.isDefault()) { + return b; + } else { + return a; + } + }).orElseThrow(); + }); } private static boolean signatureEquals(Method a, Method b) { - if (!Objects.equals(a.getName(), b.getName()) || !Arrays.equals(a.getParameterTypes(), b.getParameterTypes())) { - return false; - } - - return Objects.equals(a.getReturnType(), b.getReturnType()); + return Objects.equals(new MethodSignature(a), new MethodSignature(b)); } private record CompositeProxyInvocationHandler(Map dispatch) implements InvocationHandler { @@ -515,22 +608,14 @@ public final class CompositeProxy { return obj.getClass().getName() + '@' + Integer.toHexString(System.identityHashCode(obj)); } - private static boolean objectEquals(Object obj, Object other) { + private static boolean objectIsSame(Object obj, Object other) { return obj == other; } - private static Method getMethod(Class type, String methodName, Class...paramaterTypes) { - try { - return type.getDeclaredMethod(methodName, paramaterTypes); - } catch (NoSuchMethodException|SecurityException ex) { - throw new InternalError(ex); - } - } + private record ObjectMethodHandler(Method method) implements Handler { - static class ObjectMethodHandler extends HandlerOfMethod { - - ObjectMethodHandler(Method method) { - super(method); + ObjectMethodHandler { + Objects.requireNonNull(method); } @Override @@ -546,43 +631,47 @@ public final class CompositeProxy { } } - private static final Map OBJECT_METHOD_DISPATCH = Map.of( - getMethod(Object.class, "toString"), - new ObjectMethodHandler(getMethod(CompositeProxyInvocationHandler.class, "objectToString", Object.class)), - getMethod(Object.class, "equals", Object.class), - new ObjectMethodHandler(getMethod(CompositeProxyInvocationHandler.class, "objectEquals", Object.class, Object.class)), - getMethod(Object.class, "hashCode"), - new ObjectMethodHandler(getMethod(System.class, "identityHashCode", Object.class)) - ); + private static final Map OBJECT_METHOD_DISPATCH; + + static { + try { + OBJECT_METHOD_DISPATCH = Map.of( + Object.class.getMethod("toString"), + new ObjectMethodHandler(CompositeProxyInvocationHandler.class.getDeclaredMethod("objectToString", Object.class)), + + Object.class.getMethod("equals", Object.class), + new ObjectMethodHandler(CompositeProxyInvocationHandler.class.getDeclaredMethod("objectIsSame", Object.class, Object.class)), + + Object.class.getMethod("hashCode"), + new ObjectMethodHandler(System.class.getMethod("identityHashCode", Object.class)) + ); + } catch (NoSuchMethodException | SecurityException ex) { + throw new InternalError(ex); + } + } } - private static HandlerOfMethod createHandlerForDefaultMethod(Method method, InvokeTunnel invokeTunnel) { + private static Handler createHandlerForDefaultMethod(Method method, InvokeTunnel invokeTunnel) { + Objects.requireNonNull(method); if (invokeTunnel != null) { - return new HandlerOfMethod(method) { - @Override - public Object invoke(Object proxy, Object[] args) throws Throwable { - return invokeTunnel.invokeDefault(proxy, this.method, args); - } + return (proxy, args) -> { + return invokeTunnel.invokeDefault(proxy, method, args); }; } else { return null; } } - private static HandlerOfMethod createHandlerForMethod(Object obj, Method method, InvokeTunnel invokeTunnel) { + private static Handler createHandlerForMethod(Object obj, Method method, InvokeTunnel invokeTunnel) { + Objects.requireNonNull(obj); + Objects.requireNonNull(method); if (invokeTunnel != null) { - return new HandlerOfMethod(method) { - @Override - public Object invoke(Object proxy, Object[] args) throws Throwable { - return invokeTunnel.invoke(obj, this.method, args); - } + return (proxy, args) -> { + return invokeTunnel.invoke(obj, method, args); }; } else { - return new HandlerOfMethod(method) { - @Override - public Object invoke(Object proxy, Object[] args) throws Throwable { - return this.method.invoke(obj, args); - } + return (proxy, args) -> { + return method.invoke(obj, args); }; } } @@ -593,37 +682,141 @@ public final class CompositeProxy { Object invoke(Object proxy, Object[] args) throws Throwable; } - private abstract static class HandlerOfMethod implements Handler { - HandlerOfMethod(Method method) { - this.method = method; + private record MethodSignature(String name, List> parameterTypes) { + MethodSignature { + Objects.requireNonNull(name); + parameterTypes.forEach(Objects::requireNonNull); } - protected final Method method; + MethodSignature(Method m) { + this(m.getName(), List.of(m.getParameterTypes())); + } } - private record ConflictResolverAdapter(BinaryOperator conflictResolver) - implements BinaryOperator { + private record FullMethodSignature(MethodSignature signature, Class returnType) { + FullMethodSignature { + Objects.requireNonNull(signature); + Objects.requireNonNull(returnType); + } - @Override - public HandlerOfMethod apply(HandlerOfMethod a, HandlerOfMethod b) { - var m = conflictResolver.apply(a.method, b.method); - if (m == a.method) { - return a; - } else if (m == b.method) { - return b; - } else { - throw new UnsupportedOperationException(); + FullMethodSignature(Method m) { + this(new MethodSignature(m), m.getReturnType()); + } + } + + /** + * Returns the standard jpackage configuration if the values of + * {@code interfaceType} and {@code slices} parameters comprise such or an empty + * {@code Optional} otherwise. + *

+ * Standard jpackage configuration is: + *

    + *
  • The proxy implements an interface comprised of two direct + * superinterfaces. + *
  • The superinterfaces are distinct, i.e. they are not superinterfaces of + * each other. + *
  • Each supplied slice implements one of the superinterfaces. + *
+ * + * @param interfaceType the interface type composite proxy instance should + * implement + * @param slices all objects passed to the calling composite proxy. The + * value is a copy of the last parameter passed in the + * {@link Builder#create(Class, Object...)} + */ + static Optional, Class>> detectJPackageConfiguration(Class interfaceType, Object... slices) { + var interfaces = interfaceType.getInterfaces(); + + if (interfaces.length != 2) { + return Optional.empty(); + } + + if (interfaces[0].isAssignableFrom(interfaces[1]) || interfaces[1].isAssignableFrom(interfaces[0])) { + return Optional.empty(); + } + + var uniqueSlices = Stream.of(slices).map(IdentityWrapper::new).distinct().toList(); + if (uniqueSlices.size() != interfaces.length) { + return Optional.empty(); + } + + Map, List>> dispatch = Stream.of(interfaces).collect(toMap(x -> x, iface -> { + return uniqueSlices.stream().filter(slice -> { + return iface.isInstance(slice.value()); + }).toList(); + })); + + return dispatch.values().stream().filter(v -> { + return v.size() == 1; + }).findFirst().map(anambiguous -> { + return dispatch.entrySet().stream().collect(toMap(e -> { + var ifaceSlices = e.getValue(); + if (ifaceSlices.size() == 1) { + return ifaceSlices.getFirst(); + } else { + if (anambiguous.size() != 1) { + throw new AssertionError(); + } + return ifaceSlices.stream().filter(Predicate.isEqual(anambiguous.getFirst()).negate()).findFirst().orElseThrow(); + } + }, Map.Entry::getKey)); + }); + } + + // jpackage-specific object conflict resolver + private static final ObjectConflictResolver JPACKAGE_OBJECT_CONFLICT_RESOLVER = (interfaceType, slices, method, candidates) -> { + return detectJPackageConfiguration(interfaceType, slices).map(dispatch -> { + // In this configuration, if one slice contains matching default method and + // another contains matching implemented method, + // the latter slice is selected as a supplier of this method for the composite proxy. + + var nonDefaultImplementations = new BitSet(candidates.length); + var defaultImplementations = new BitSet(candidates.length); + for (int i = 0; i != candidates.length; i++) { + var slice = candidates[i]; + + var limitSignatures = new Predicate() { + + @Override + public boolean test(Method m) { + return limitSignatures.contains(new MethodSignature(m)); + } + + private final Collection limitSignatures = + getProxyableMethods(dispatch.get(IdentityWrapper.wrapIdentity(slice))) + .map(MethodSignature::new) + .toList(); + }; + + int cur = i; + + getImplementerMethods(slice).filter(limitSignatures).filter(sliceMethod -> { + return signatureEquals(sliceMethod, method); + }).findFirst().ifPresent(sliceMethod -> { + if (!sliceMethod.isDefault() || + getUnfilteredImplementerMethods(slice) + .filter(limitSignatures) + .map(FullMethodSignature::new) + .anyMatch(Predicate.isEqual(new FullMethodSignature(sliceMethod)))) { + nonDefaultImplementations.set(cur); + } else { + defaultImplementations.set(cur); + } + }); } - } - } - private static final BinaryOperator STANDARD_CONFLICT_RESOLVER = (a, b) -> { - if (a.isDefault() == b.isDefault()) { - throw new IllegalArgumentException(String.format("ambiguous choice between %s and %s", a, b)); - } else if (!a.isDefault()) { - return a; - } else { - return b; - } + if (nonDefaultImplementations.cardinality() == 1) { + return candidates[nonDefaultImplementations.nextSetBit(0)]; + } else if (nonDefaultImplementations.cardinality() == 0 && defaultImplementations.cardinality() == 1) { + return candidates[defaultImplementations.nextSetBit(0)]; + } else { + throw new AssertionError(); + } + }).orElse(null); + }; + + // jpackage-specific method conflict resolver + private static final MethodConflictResolver JPACKAGE_METHOD_CONFLICT_RESOLVER = (interfaceType, slices, method, obj) -> { + return false; }; } diff --git a/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/util/CompositeProxyTest.java b/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/util/CompositeProxyTest.java index 1ece83be0a6..b8b1325529d 100644 --- a/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/util/CompositeProxyTest.java +++ b/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/util/CompositeProxyTest.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 @@ -22,17 +22,35 @@ */ package jdk.jpackage.internal.util; +import static jdk.jpackage.internal.util.PathUtils.mapNullablePath; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertThrowsExactly; +import static org.junit.jupiter.api.Assertions.assertTrue; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; +import java.util.function.UnaryOperator; +import java.util.stream.Stream; +import jdk.jpackage.internal.util.CompositeProxy.InvokeTunnel; +import jdk.jpackage.test.JUnitUtils.StringArrayConverter; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.converter.ConvertWith; +import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.ValueSource; -public class CompositeProxyTest { +class CompositeProxyTest { static interface Smalltalk { @@ -86,24 +104,24 @@ public class CompositeProxyTest { } @Test - public void testSmalltalk() { + void testSmalltalk() { var convo = CompositeProxy.create(Smalltalk.class); assertEquals("Hello", convo.sayHello()); assertEquals("Bye", convo.sayBye()); } @Test - public void testConvo() { + void testConvo() { final var otherThings = "How is your day?"; var convo = CompositeProxy.create(Convo.class, - new Smalltalk() {}, new ConvoMixin.Stub(otherThings)); + new ConvoMixin.Stub(otherThings)); assertEquals("Hello", convo.sayHello()); assertEquals("Bye", convo.sayBye()); assertEquals(otherThings, convo.sayThings()); } @Test - public void testConvoWithDuke() { + void testConvoWithDuke() { final var otherThings = "How is your day?"; var convo = CompositeProxy.create(Convo.class, new Smalltalk() { @Override @@ -116,34 +134,47 @@ public class CompositeProxyTest { assertEquals(otherThings, convo.sayThings()); } - @Test - public void testConvoWithCustomSayBye() { + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void testConvoWithCustomSayBye(boolean allowUnreferencedSlices) { var mixin = new ConvoMixinWithOverrideSayBye.Stub("How is your day?", "See you"); - var convo = CompositeProxy.create(ConvoWithOverrideSayBye.class, new Smalltalk() {}, mixin); + var smalltalk = new Smalltalk() {}; - var expectedConvo = new ConvoWithOverrideSayBye() { - @Override - public String sayBye() { - return mixin.sayBye; - } + var proxyBuilder = CompositeProxy.build().allowUnreferencedSlices(allowUnreferencedSlices); - @Override - public String sayThings() { - return mixin.sayThings; - } - }; + if (!allowUnreferencedSlices) { + var ex = assertThrowsExactly(IllegalArgumentException.class, () -> { + proxyBuilder.create(ConvoWithOverrideSayBye.class, smalltalk, mixin); + }); - assertEquals(expectedConvo.sayHello(), convo.sayHello()); - assertEquals(expectedConvo.sayBye(), convo.sayBye()); - assertEquals(expectedConvo.sayThings(), convo.sayThings()); + assertEquals(String.format("Unreferenced slices: %s", List.of(smalltalk)), ex.getMessage()); + } else { + var convo = proxyBuilder.create(ConvoWithOverrideSayBye.class, smalltalk, mixin); + + var expectedConvo = new ConvoWithOverrideSayBye() { + @Override + public String sayBye() { + return mixin.sayBye; + } + + @Override + public String sayThings() { + return mixin.sayThings; + } + }; + + assertEquals(expectedConvo.sayHello(), convo.sayHello()); + assertEquals(expectedConvo.sayBye(), convo.sayBye()); + assertEquals(expectedConvo.sayThings(), convo.sayThings()); + } } @Test - public void testConvoWithCustomSayHelloAndSayBye() { + void testConvoWithCustomSayHelloAndSayBye() { var mixin = new ConvoMixinWithOverrideSayBye.Stub("How is your day?", "See you"); - var convo = CompositeProxy.create(ConvoWithDefaultSayHelloWithOverrideSayBye.class, new Smalltalk() {}, mixin); + var convo = CompositeProxy.create(ConvoWithDefaultSayHelloWithOverrideSayBye.class, mixin); var expectedConvo = new ConvoWithDefaultSayHelloWithOverrideSayBye() { @Override @@ -164,7 +195,7 @@ public class CompositeProxyTest { } @Test - public void testInherited() { + void testInherited() { interface Base { String doSome(); } @@ -193,7 +224,7 @@ public class CompositeProxyTest { } @Test - public void testNestedProxy() { + void testNestedProxy() { interface AddM { String m(); } @@ -231,7 +262,7 @@ public class CompositeProxyTest { } @Test - public void testComposite() { + void testComposite() { interface A { String sayHello(); String sayBye(); @@ -262,54 +293,13 @@ public class CompositeProxyTest { assertEquals("ciao,bye", proxy.talk()); } - @ParameterizedTest - @ValueSource(booleans = {true, false}) - public void testBasicObjectMethods(boolean withOverrides) { - interface A { - default void foo() {} + @Test + void testBasicObjectMethods() { + interface Foo { } - interface B { - default void bar() {} - } - - interface C extends A, B { - } - - final A aImpl; - final B bImpl; - - if (withOverrides) { - aImpl = new A() { - @Override - public String toString() { - return "theA"; - } - - @Override - public boolean equals(Object other) { - return true; - } - - @Override - public int hashCode() { - return 7; - } - }; - - bImpl = new B() { - @Override - public String toString() { - return "theB"; - } - }; - } else { - aImpl = new A() {}; - bImpl = new B() {}; - } - - var proxy = CompositeProxy.create(C.class, aImpl, bImpl); - var proxy2 = CompositeProxy.create(C.class, aImpl, bImpl); + var proxy = CompositeProxy.create(Foo.class); + var proxy2 = CompositeProxy.create(Foo.class); assertNotEquals(proxy.toString(), proxy2.toString()); assertNotEquals(proxy.hashCode(), proxy2.hashCode()); @@ -320,7 +310,925 @@ public class CompositeProxyTest { } @Test - public void testJavadocExample() { + void testAutoMethodConflictResolver() { + + interface A { + String getString(); + } + + interface B { + String getString(); + } + + interface AB extends A, B { + } + + var foo = new Object() { + public String getString() { + return "foo"; + } + }; + + var proxy = CompositeProxy.create(AB.class, foo); + assertEquals("foo", proxy.getString()); + } + + @Test + void testAutoMethodConflictResolver2() { + + interface A { + String getString(); + } + + interface B { + String getString(); + } + + interface AB extends A, B { + String getString(); + } + + var foo = new Object() { + public String getString() { + return "foo"; + } + }; + + var proxy = CompositeProxy.create(AB.class, foo); + assertEquals("foo", proxy.getString()); + } + + @Test + void testUnreferencedSlices() { + + interface A { + String getString(); + } + + interface B { + String getString(); + } + + interface AB extends A, B { + default String getString() { + throw new AssertionError(); + } + } + + var foo = new Object() { + public String getString() { + throw new AssertionError(); + } + }; + + var ex = assertThrowsExactly(IllegalArgumentException.class, () -> { + CompositeProxy.create(AB.class, foo); + }); + + assertEquals(String.format("Unreferenced slices: %s", List.of(foo)), ex.getMessage()); + } + + @Test + void testAutoMethodConflictResolver4() { + + interface A { + String getString(); + } + + interface B { + String getString(); + } + + interface AB extends A, B { + default String getString() { + return "AB"; + } + } + + var proxy = CompositeProxy.create(AB.class); + assertEquals("AB", proxy.getString()); + } + + @Test + void testAutoMethodConflictResolver4_1() { + + interface A { + String foo(); + String bar(); + } + + interface B { + String foo(); + String bar(); + } + + interface AB extends A, B { + default String foo() { + return "AB.foo"; + } + } + + var proxy = CompositeProxy.create(AB.class, new AB() { + @Override + public String bar() { + return "Obj.bar"; + } + }); + assertEquals("AB.foo", proxy.foo()); + assertEquals("Obj.bar", proxy.bar()); + } + + @Test + void testAutoMethodConflictResolver5() { + + interface A { + default String getString() { + throw new AssertionError(); + } + } + + interface B { + String getString(); + } + + interface AB extends A, B { + String getString(); + } + + var foo = new Object() { + public String getString() { + return "foo"; + } + }; + + var proxy = CompositeProxy.create(AB.class, foo); + assertEquals("foo", proxy.getString()); + } + + @Test + void testAutoMethodConflictResolver6() { + + interface A { + default String getString() { + return "A"; + } + } + + interface B { + String getString(); + } + + interface AB extends A, B { + default String getString() { + return A.super.getString() + "!"; + } + } + + var proxy = CompositeProxy.create(AB.class); + assertEquals("A!", proxy.getString()); + } + + @Test + void testAutoMethodConflictResolver7() { + + interface A { + String getString(); + } + + interface B extends A { + default String getString() { + return "B"; + } + } + + interface AB extends A, B { + default String getString() { + return B.super.getString() + "!"; + } + } + + var proxy = CompositeProxy.create(AB.class); + assertEquals("B!", proxy.getString()); + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void testAutoMethodConflictResolver8(boolean override) { + + interface A { + String getString(); + } + + interface B extends A { + default String getString() { + return "B"; + } + } + + interface AB extends A, B { + } + + if (override) { + var foo = new Object() { + public String getString() { + return "foo"; + } + }; + + var proxy = CompositeProxy.build().methodConflictResolver((_, _, _, _) -> { + return true; + }).create(AB.class, foo); + assertEquals("foo", proxy.getString()); + } else { + var proxy = CompositeProxy.create(AB.class); + assertEquals("B", proxy.getString()); + } + } + + @Test + void testAutoMethodConflictResolver9() { + + interface A { + String getString(); + } + + interface B extends A { + default String getString() { + throw new AssertionError(); + } + } + + var foo = new Object() { + public String getString() { + return "foo"; + } + }; + + interface AB extends A, B { + String getString(); + } + + var ab = CompositeProxy.create(AB.class, foo); + assertEquals("foo", ab.getString()); + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void testAutoMethodConflictResolver10(boolean override) { + + interface A { + String getString(); + } + + interface B extends A { + default String getString() { + return "B"; + } + } + + interface AB extends A, B { + String getString(); + } + + if (override) { + var foo = new B() { + @Override + public String getString() { + return B.super.getString() + "!"; + } + }; + + var proxy = CompositeProxy.create(AB.class, foo); + assertEquals("B!", proxy.getString()); + } else { + var proxy = CompositeProxy.create(AB.class, new B() {}); + assertEquals("B", proxy.getString()); + } + } + + @Test + void testAutoMethodConflictResolver11() { + + interface A { + String getString(); + } + + class Foo implements A { + @Override + public String getString() { + throw new AssertionError(); + } + } + + class Bar extends Foo { + @Override + public String getString() { + throw new AssertionError(); + } + } + + class Buz extends Bar { + @Override + public String getString() { + return "buz"; + } + } + + var proxy = CompositeProxy.create(A.class, new Buz()); + assertEquals("buz", proxy.getString()); + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void testAutoMethodConflictResolver12(boolean override) { + + interface A { + String getString(); + } + + interface B { + default String getString() { + return "foo"; + } + } + + if (override) { + class BImpl implements B { + @Override + public String getString() { + return "bar"; + } + } + + var proxy = CompositeProxy.create(A.class, new BImpl() {}); + assertEquals("bar", proxy.getString()); + } else { + class BImpl implements B { + } + + var proxy = CompositeProxy.create(A.class, new BImpl() {}); + assertEquals("foo", proxy.getString()); + } + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void testAutoMethodConflictResolver13(boolean override) { + + interface A { + String getString(); + } + + interface Foo { + default String getString() { + return "foo"; + } + } + + if (override) { + class B { + public String getString() { + return "B"; + } + } + + class C extends B implements Foo { + } + + for (var slice : List.of(new C(), new C() {})) { + var proxy = CompositeProxy.create(A.class, slice); + assertEquals("B", proxy.getString()); + } + + var proxy = CompositeProxy.create(A.class, new C() { + @Override + public String getString() { + return "C"; + } + }); + assertEquals("C", proxy.getString()); + } else { + class B { + } + + class C extends B implements Foo { + } + + for (var slice : List.of(new C(), new C() {})) { + var proxy = CompositeProxy.create(A.class, slice); + assertEquals("foo", proxy.getString()); + } + } + } + + @Test + void testAutoMethodConflictResolver14() { + + interface Launcher { + + String name(); + + Map extraAppImageFileData(); + + record Stub(String name, Map extraAppImageFileData) implements Launcher {} + } + + interface WinLauncherMixin { + + boolean shortcut(); + + record Stub(boolean shortcut) implements WinLauncherMixin {} + } + + interface WinLauncher extends Launcher, WinLauncherMixin { + + default Map extraAppImageFileData() { + return Map.of("shortcut", Boolean.toString(shortcut())); + } + } + + var proxy = CompositeProxy.create(WinLauncher.class, new Launcher.Stub("foo", Map.of()), new WinLauncherMixin.Stub(true)); + + assertEquals("foo", proxy.name()); + assertEquals(Map.of("shortcut", "true"), proxy.extraAppImageFileData()); + } + + @ParameterizedTest + @CsvSource({ + "a,b", + "b,a", + }) + void testObjectConflictResolver(String fooResolve, String barResolve) { + + interface I { + String foo(); + String bar(); + } + + var a = new I() { + @Override + public String foo() { + return "a-foo"; + } + + @Override + public String bar() { + return "a-bar"; + } + }; + + var b = new Object() { + public String foo() { + return "b-foo"; + } + + public String bar() { + return "b-bar"; + } + }; + + Function resolver = tag -> { + return switch (tag) { + case "a" -> a; + case "b" -> b; + default -> { + throw new AssertionError(); + } + }; + }; + + var proxy = CompositeProxy.build().objectConflictResolver((_, _, method, _) -> { + return switch (method.getName()) { + case "foo" -> resolver.apply(fooResolve); + case "bar" -> resolver.apply(barResolve); + default -> { + throw new AssertionError(); + } + }; + }).create(I.class, a, b); + + assertEquals(fooResolve + "-foo", proxy.foo()); + assertEquals(barResolve + "-bar", proxy.bar()); + } + + @Test + void testObjectConflictResolverInvalid() { + + interface I { + String foo(); + } + + var a = new I() { + @Override + public String foo() { + throw new AssertionError(); + } + }; + + var b = new Object() { + public String foo() { + throw new AssertionError(); + } + }; + + assertThrowsExactly(UnsupportedOperationException.class, () -> { + CompositeProxy.build().objectConflictResolver((_, _, _, _) -> { + return new Object(); + }).create(I.class, a, b); + }); + } + + @ParameterizedTest + @ValueSource( strings = { + "no-foo", + "private-foo", + "protected-foo", + "package-foo", + "static-foo", + "static-foo,private-foo,no-foo", + }) + void testMissingImplementer(@ConvertWith(StringArrayConverter.class) String[] slicesSpec) throws NoSuchMethodException, SecurityException { + + interface A { + void foo(); + } + + var slices = Stream.of(slicesSpec).map(slice -> { + return switch (slice) { + case "no-foo" -> new Object(); + case "private-foo" -> new Object() { + private void foo() { + throw new AssertionError(); + } + }; + case "protected-foo" -> new Object() { + protected void foo() { + throw new AssertionError(); + } + }; + case "package-foo" -> new Object() { + void foo() { + throw new AssertionError(); + } + }; + case "static-foo" -> new Object() { + public static void foo() { + throw new AssertionError(); + } + }; + default -> { throw new AssertionError(); } + }; + }).toList(); + + var ex = assertThrowsExactly(IllegalArgumentException.class, () -> { + CompositeProxy.create(A.class, slices.toArray()); + }); + + assertEquals(String.format("None of the slices can handle %s", A.class.getMethod("foo")), ex.getMessage()); + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void testUnusedSlice(boolean all) { + + interface A { + default void foo() { + throw new AssertionError(); + } + } + + A a = new A() {}; + var obj = new Object(); + + if (all) { + var messages = Set.of( + String.format("Unreferenced slices: %s", List.of(a, obj)), + String.format("Unreferenced slices: %s", List.of(obj, a)) + ); + + var ex = assertThrowsExactly(IllegalArgumentException.class, () -> { + CompositeProxy.create(A.class, a, obj); + }); + + assertTrue(messages.contains(ex.getMessage())); + } else { + interface B extends A { + void foo(); + } + + var ex = assertThrowsExactly(IllegalArgumentException.class, () -> { + CompositeProxy.create(B.class, a, obj); + }); + + assertEquals(String.format("Unreferenced slices: %s", List.of(obj)), ex.getMessage()); + } + } + + @ParameterizedTest + @CsvSource({ + "'a,b,a',false", + "'a,b,a',true", + "'a,b',true", + "'b,a',true", + "'a,b',false", + "'b,a',false", + }) + void testAmbiguousImplementers( + @ConvertWith(StringArrayConverter.class) String[] slicesSpec, + boolean withObjectConflictResolver) throws NoSuchMethodException, SecurityException { + + interface A { + String foo(); + String bar(); + } + + var a = new Object() { + public String foo() { + return "a-foo"; + } + public String bar() { + throw new AssertionError(); + } + }; + + var b = new Object() { + public String bar() { + return "b-bar"; + } + }; + + var ambiguousMethod = A.class.getMethod("bar"); + + var slices = Stream.of(slicesSpec).map(slice -> { + return switch (slice) { + case "a" -> a; + case "b" -> b; + default -> { throw new AssertionError(); } + }; + }).toArray(); + + if (withObjectConflictResolver) { + var proxy = CompositeProxy.build().objectConflictResolver((_, _, _, _) -> { + return b; + }).create(A.class, slices); + + assertEquals("a-foo", proxy.foo()); + assertEquals("b-bar", proxy.bar()); + } else { + var ex = assertThrowsExactly(IllegalArgumentException.class, () -> { + CompositeProxy.create(A.class, slices); + }); + + var messages = Set.of( + String.format("Ambiguous choice between %s for %s", List.of(a, b), ambiguousMethod), + String.format("Ambiguous choice between %s for %s", List.of(b, a), ambiguousMethod) + ); + + assertTrue(messages.contains(ex.getMessage())); + } + } + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void testDifferentReturnTypes(boolean compatible) { + + interface A { + Number foo(); + } + + Object obj; + if (compatible) { + obj = new Object() { + public Integer foo() { + return 123; + } + }; + } else { + obj = new Object() { + public String foo() { + return "123"; + } + }; + } + + var proxy = CompositeProxy.create(A.class, obj); + + if (compatible) { + assertEquals(123, proxy.foo()); + } else { + assertThrows(ClassCastException.class, proxy::foo); + } + } + + @Test + void testCovariantReturnType() { + + interface A { + Number foo(); + } + + interface Mixin { + String bar(); + } + + interface AWithMixin extends A, Mixin { + Integer foo(); + } + + var proxy = CompositeProxy.create(AWithMixin.class, new A() { + @Override + public Number foo() { + return 123; + } + }, new Mixin() { + @Override + public String bar() { + return "bar"; + } + }); + + assertEquals(123, proxy.foo()); + assertEquals("bar", proxy.bar()); + } + + @Test + void testNotInterface() { + var ex = assertThrowsExactly(IllegalArgumentException.class, () -> { + CompositeProxy.create(Integer.class); + }); + + assertEquals(String.format("Type %s must be an interface", Integer.class.getName()), ex.getMessage()); + } + + @Test + void testExcessiveInterfaces() { + + interface Launcher { + String name(); + + default String executableResource() { + return "jpackageapplauncher"; + } + + record Stub(String name) implements Launcher { + } + } + + interface WinLauncherMixin { + String version(); + + record Stub(String version) implements WinLauncherMixin { + } + } + + interface WinLauncher extends Launcher, WinLauncherMixin { + + default String executableResource() { + return "jpackageapplauncher.exe"; + } + } + + var winLauncher = CompositeProxy.create(WinLauncher.class, new Launcher.Stub("foo"), new WinLauncherMixin.Stub("1.0")); + + var winLauncher2 = CompositeProxy.create(WinLauncher.class, new Launcher.Stub("bar"), winLauncher); + + assertEquals("foo", winLauncher.name()); + assertEquals("1.0", winLauncher.version()); + assertEquals("jpackageapplauncher.exe", winLauncher.executableResource()); + + assertEquals("bar", winLauncher2.name()); + assertEquals("1.0", winLauncher2.version()); + assertEquals("jpackageapplauncher.exe", winLauncher2.executableResource()); + } + + @Test + void testInvokeTunnel() { + + interface A { + default String foo() { + return "foo"; + } + String bar(); + } + + var obj = new Object() { + public String bar() { + return "bar"; + } + }; + + Slot invokeCalled = Slot.createEmpty(); + invokeCalled.set(false); + + Slot invokeDefaultCalled = Slot.createEmpty(); + invokeDefaultCalled.set(false); + + var proxy = CompositeProxy.build().invokeTunnel(new InvokeTunnel() { + + @Override + public Object invoke(Object obj, Method method, Object[] args) throws Throwable { + invokeCalled.set(true); + return method.invoke(obj, args); + } + + @Override + public Object invokeDefault(Object proxy, Method method, Object[] args) throws Throwable { + invokeDefaultCalled.set(true); + return InvocationHandler.invokeDefault(proxy, method, args); + } + + }).create(A.class, obj); + + assertFalse(invokeCalled.get()); + assertFalse(invokeDefaultCalled.get()); + assertEquals("foo", proxy.foo()); + assertFalse(invokeCalled.get()); + assertTrue(invokeDefaultCalled.get()); + + invokeDefaultCalled.set(false); + assertEquals("bar", proxy.bar()); + assertTrue(invokeCalled.get()); + assertFalse(invokeDefaultCalled.get()); + } + + @Test + void testDefaultOverride() { + + interface AppImageLayout { + + Path runtimeDirectory(); + + Path rootDirectory(); + + default boolean isResolved() { + return !rootDirectory().equals(Path.of("")); + } + + default AppImageLayout unresolve() { + if (isResolved()) { + final var root = rootDirectory(); + return map(root::relativize); + } else { + return this; + } + } + + AppImageLayout map(UnaryOperator mapper); + + record Stub(Path rootDirectory, Path runtimeDirectory) implements AppImageLayout { + + public Stub { + Objects.requireNonNull(rootDirectory); + } + + public Stub(Path runtimeDirectory) { + this(Path.of(""), runtimeDirectory); + } + + @Override + public AppImageLayout map(UnaryOperator mapper) { + return new Stub(mapNullablePath(mapper, rootDirectory), mapNullablePath(mapper, runtimeDirectory)); + } + } + } + + interface ApplicationLayoutMixin { + + Path appDirectory(); + + record Stub(Path appDirectory) implements ApplicationLayoutMixin { + } + } + + interface ApplicationLayout extends AppImageLayout, ApplicationLayoutMixin { + + @Override + default ApplicationLayout unresolve() { + return (ApplicationLayout)AppImageLayout.super.unresolve(); + } + + @Override + default ApplicationLayout map(UnaryOperator mapper) { + return CompositeProxy.create(ApplicationLayout.class, + new AppImageLayout.Stub(rootDirectory(), runtimeDirectory()).map(mapper), + new ApplicationLayoutMixin.Stub(mapper.apply(appDirectory()))); + } + } + + var proxy = CompositeProxy.create(ApplicationLayout.class, + new AppImageLayout.Stub(Path.of(""), Path.of("runtime")), + new ApplicationLayoutMixin.Stub(Path.of("app"))); + + assertSame(proxy, proxy.unresolve()); + + var mapped = proxy.map(Path.of("a")::resolve); + assertEquals(Path.of("a"), mapped.rootDirectory()); + assertEquals(Path.of("a/runtime"), mapped.runtimeDirectory()); + assertEquals(Path.of("a/app"), mapped.appDirectory()); + } + + @Test + void testJavadocExample() { interface Sailboat { default void trimSails() {} } @@ -364,9 +1272,9 @@ public class CompositeProxyTest { } }; - Sloop sloop = CompositeProxy.create(Sloop.class, new Sailboat() {}, withMain, withJib); + Sloop sloop = CompositeProxy.create(Sloop.class, withMain, withJib); - Catboat catboat = CompositeProxy.create(Catboat.class, new Sailboat() {}, withMain); + Catboat catboat = CompositeProxy.create(Catboat.class, withMain); sloop.trimSails(); catboat.trimSails(); diff --git a/test/jdk/tools/jpackage/junit/tools/jdk/jpackage/test/JUnitUtils.java b/test/jdk/tools/jpackage/junit/tools/jdk/jpackage/test/JUnitUtils.java index 530a0d5cb1f..97041ea1a0e 100644 --- a/test/jdk/tools/jpackage/junit/tools/jdk/jpackage/test/JUnitUtils.java +++ b/test/jdk/tools/jpackage/junit/tools/jdk/jpackage/test/JUnitUtils.java @@ -27,6 +27,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import java.util.Map; import java.util.Objects; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.converter.SimpleArgumentConverter; public final class JUnitUtils { @@ -121,6 +122,19 @@ public final class JUnitUtils { } + public static class StringArrayConverter extends SimpleArgumentConverter { + + @Override + protected Object convert(Object source, Class targetType) { + if (source instanceof String && String[].class.isAssignableFrom(targetType)) { + return ((String) source).split("\\s*,\\s*"); + } else { + throw new IllegalArgumentException(); + } + } + } + + private static final class ExceptionCauseRemover extends Exception { ExceptionCauseRemover(Exception ex) { From e893f4cf8465bf428b49191158c80060932215fc Mon Sep 17 00:00:00 2001 From: Alan Bateman Date: Sat, 11 Apr 2026 04:49:31 +0000 Subject: [PATCH 013/108] 8196182: ServiceLoader.iterator().hasNext()/.next() may throw a LinkageError 8350481: ServiceLoader throws NCDEF instead of ServiceConfigurationError Reviewed-by: liach, jpai --- .../classes/java/util/ServiceLoader.java | 10 +- .../util/ServiceLoader/LinkageErrorsTest.java | 214 ++++++++++++++++++ .../MalformedAnnotationProcessorTests.java | 10 +- 3 files changed, 224 insertions(+), 10 deletions(-) create mode 100644 test/jdk/java/util/ServiceLoader/LinkageErrorsTest.java diff --git a/src/java.base/share/classes/java/util/ServiceLoader.java b/src/java.base/share/classes/java/util/ServiceLoader.java index 5137adc1c08..5e4fa4ed2ef 100644 --- a/src/java.base/share/classes/java/util/ServiceLoader.java +++ b/src/java.base/share/classes/java/util/ServiceLoader.java @@ -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 @@ -621,9 +621,9 @@ public final class ServiceLoader Constructor ctor = null; try { ctor = clazz.getConstructor(); - } catch (NoSuchMethodException ex) { + } catch (NoSuchMethodException | LinkageError e) { String cn = clazz.getName(); - fail(service, cn + " Unable to get public no-arg constructor", ex); + fail(service, cn + " Unable to get public no-arg constructor", e); } if (inExplicitModule(clazz)) ctor.setAccessible(true); @@ -1086,8 +1086,8 @@ public final class ServiceLoader String cn = pending.next(); try { return Class.forName(cn, false, loader); - } catch (ClassNotFoundException x) { - fail(service, "Provider " + cn + " not found"); + } catch (ClassNotFoundException | LinkageError e) { + fail(service, "Provider " + cn + " not found", e); return null; } } diff --git a/test/jdk/java/util/ServiceLoader/LinkageErrorsTest.java b/test/jdk/java/util/ServiceLoader/LinkageErrorsTest.java new file mode 100644 index 00000000000..209dce75267 --- /dev/null +++ b/test/jdk/java/util/ServiceLoader/LinkageErrorsTest.java @@ -0,0 +1,214 @@ +/* + * 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. + */ + +/* + * @test + * @bug 8196182 8350481 + * @summary Test ServiceLoader iterating over service providers when linkage error is thrown + * @library /test/lib + * @run junit/othervm ${test.main.class} + */ + +import java.io.File; +import java.io.IOException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Iterator; +import java.util.Map; +import java.util.ServiceConfigurationError; +import java.util.ServiceLoader; +import java.util.function.Consumer; +import java.util.function.Predicate; +import jdk.test.lib.compiler.InMemoryJavaCompiler; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +class LinkageErrorsTest { + + /** + * Test iteration over service providers when loading a service provider class + * fails with a linkage error. + */ + @Test + void testLoadClassThrows() throws Exception { + Map sources = Map.of( + "Service", + """ + public interface Service {} + """, + "Super", + """ + class Super {} + """, + "Provider1", + """ + public class Provider1 implements Service { + public Provider1() {} + } + """, + "Provider2", + """ + public class Provider2 extends Super implements Service { + public Provider2() {} + } + """ + ); + Path classesDir = compile(sources); + + // delete Provider2's super class to prevent Provider2 from loading + Files.delete(classesDir.resolve("Super.class")); + + // create services configuration file that lists two providers + createServicesConfigFile(classesDir, "Service", "Provider1", "Provider2"); + + // load the service interface + var loader = new URLClassLoader(new URL[] { classesDir.toUri().toURL() }); + Class service = loader.loadClass("Service"); + assertSame(loader, service.getClassLoader()); + + // find and collect all service providers and ServiceConfigurationErrors + var providers = new ArrayList(); + var errors = new ArrayList(); + forEachProvider(loader, service, providers::add, errors::add); + + // Provider1 should be found + assertEquals(1, providers.size()); + assertEquals("Provider1", providers.get(0).getClass().getName()); + + // loading Provider2 expected to fail with LinkageError + assertEquals(1, errors.size()); + assertInstanceOf(LinkageError.class, errors.get(0).getCause()); + } + + /** + * Test iteration over service providers when finding the public no-arg constructor + * of a provider fails with a linkage error. + */ + @Test + void testFindConstructorThrows() throws Exception { + Map sources = Map.of( + "Service", + """ + public interface Service {} + """, + "Param", + """ + class Param {} + """, + "Provider1", + """ + public class Provider1 implements Service { + public Provider1() {} + } + """, + "Provider2", + """ + public class Provider2 implements Service { + public Provider2() {} + public Provider2(Param p) { } + } + """ + ); + Path classesDir = compile(sources); + + // delete the class file for the parameter of Provider's 1-param ctor + Files.delete(classesDir.resolve("Param.class")); + + // create services configuration file that lists two providers + createServicesConfigFile(classesDir, "Service", "Provider1", "Provider2"); + + // load the service interface + var loader = new URLClassLoader(new URL[] { classesDir.toUri().toURL() }); + Class service = loader.loadClass("Service"); + assertSame(loader, service.getClassLoader()); + + // find and collect all service providers and ServiceConfigurationErrors + var providers = new ArrayList(); + var errors = new ArrayList(); + forEachProvider(loader, service, providers::add, errors::add); + + // Provider1 should be found + assertEquals(1, providers.size()); + assertEquals("Provider1", providers.get(0).getClass().getName()); + + // loading Provider2 expected to fail with LinkageError + assertEquals(1, errors.size()); + assertInstanceOf(LinkageError.class, errors.get(0).getCause()); + } + + /** + * Compile the given java source files to a temporary directory. + */ + private Path compile(Map sources) throws IOException { + Map classes = InMemoryJavaCompiler.compile(sources); + Path dir = Files.createTempDirectory(Path.of("."), "classes"); + for (String cn : classes.keySet()) { + Path file = dir.resolve(cn.replace('.', File.separatorChar) + ".class"); + Files.createDirectories(file.getParent()); + Files.write(file, classes.get(cn)); + } + return dir; + } + + /** + * Create services configuration file for the given service and providers. + */ + private void createServicesConfigFile(Path dir, + String serviceName, + String... providerNames) throws IOException { + Path configFile = dir.resolve("META-INF", "services", serviceName); + Files.createDirectories(configFile.getParent()); + Files.write(configFile, Arrays.asList(providerNames)); + } + + /** + * Uses ServiceLoader to iterate over all service providers of the given service, + * invoking {@code providerConsumer} for each provider instantiated, and + * {@code errorConsumer} for each ServiceConfigurationError encountered. + */ + private void forEachProvider(ClassLoader loader, + Class service, + Consumer providerConsumer, + Consumer errorConsumer) { + Iterator iterator = ServiceLoader.load(service, loader).iterator(); + boolean done = false; + while (!done) { + try { + if (iterator.hasNext()) { + T provider = iterator.next(); + providerConsumer.accept(provider); + } else { + done = true; + } + } catch (ServiceConfigurationError e) { + errorConsumer.accept(e); + } + } + } +} diff --git a/test/langtools/tools/javac/annotations/8218152/MalformedAnnotationProcessorTests.java b/test/langtools/tools/javac/annotations/8218152/MalformedAnnotationProcessorTests.java index 68e4aea0ab2..85095cc5537 100644 --- a/test/langtools/tools/javac/annotations/8218152/MalformedAnnotationProcessorTests.java +++ b/test/langtools/tools/javac/annotations/8218152/MalformedAnnotationProcessorTests.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2023, 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 @@ -91,8 +91,8 @@ public class MalformedAnnotationProcessorTests extends TestRunner{ .getOutputLines(Task.OutputKind.DIRECT); System.out.println(actualErrors.get(0)); - if (!actualErrors.get(0).contains("- compiler.err.proc.cant.load.class: " + - "Incompatible magic value")) { + if (!actualErrors.get(0).contains("- compiler.err.proc.bad.config.file: " + + "javax.annotation.processing.Processor: Provider BadAnnoProcessor not found")) { throw new AssertionError("Unexpected errors reported: " + actualErrors); } } @@ -162,8 +162,8 @@ public class MalformedAnnotationProcessorTests extends TestRunner{ .writeAll() .getOutputLines(Task.OutputKind.DIRECT); - if (!actualErrors.get(0).contains("- compiler.err.proc.cant.load.class: " + - "WrongClassFileVersion has been compiled by a more recent version")) { + if (!actualErrors.get(0).contains("- compiler.err.proc.bad.config.file: " + + "javax.annotation.processing.Processor: Provider WrongClassFileVersion not found")) { throw new AssertionError("Unexpected errors reported: " + actualErrors); } } From e7da7376cd5299df3c70bdd3e47d62e75b9a3ae7 Mon Sep 17 00:00:00 2001 From: SendaoYan Date: Sat, 11 Apr 2026 05:30:38 +0000 Subject: [PATCH 014/108] 8370648: TestOldGrowthTriggers.java fails 'Trigger (Old): Old has overgrown' missing from stdout/stderr Reviewed-by: xpeng, wkemper, kdnilsen, ysr --- .../generational/TestOldGrowthTriggers.java | 58 +++++++++++-------- 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/test/hotspot/jtreg/gc/shenandoah/generational/TestOldGrowthTriggers.java b/test/hotspot/jtreg/gc/shenandoah/generational/TestOldGrowthTriggers.java index 7af81fabe13..2af784fd034 100644 --- a/test/hotspot/jtreg/gc/shenandoah/generational/TestOldGrowthTriggers.java +++ b/test/hotspot/jtreg/gc/shenandoah/generational/TestOldGrowthTriggers.java @@ -1,5 +1,6 @@ /* * Copyright Amazon.com Inc. 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 @@ -25,15 +26,14 @@ /* * @test id=generational * @summary Test that growth of old-gen triggers old-gen marking - * @key intermittent * @requires vm.gc.Shenandoah * @requires vm.flagless * @library /test/lib * @run driver TestOldGrowthTriggers */ -import java.util.*; -import java.math.BigInteger; +import java.util.Arrays; +import java.util.BitSet; import jdk.test.lib.process.ProcessTools; import jdk.test.lib.process.OutputAnalyzer; @@ -41,36 +41,40 @@ import jdk.test.lib.process.OutputAnalyzer; public class TestOldGrowthTriggers { public static void makeOldAllocations() { - // Expect most of the BigInteger entries placed into array to be promoted, and most will eventually become garbage within old + // Expect most of the BitSet entries placed into array to be promoted, and most will eventually become garbage within old - final int ArraySize = 512 * 1024; // 512K entries - final int BitsInBigInteger = 128; - final int RefillIterations = 64; - BigInteger array[] = new BigInteger[ArraySize]; - Random r = new Random(46); + final int ArraySize = 1024; // 1K entries + final int RefillIterations = 128; + BitSet[] array = new BitSet[ArraySize]; for (int i = 0; i < ArraySize; i++) { - array[i] = new BigInteger(BitsInBigInteger, r); + array[i] = new BitSet(256 * 1024); } for (int refillCount = 0; refillCount < RefillIterations; refillCount++) { - // Each refill repopulates ArraySize randomly selected elements within array - for (int i = 0; i < ArraySize; i++) { - int replaceIndex = r.nextInt(ArraySize); - int deriveIndex = r.nextInt(ArraySize); + // Each refill repopulates ArraySize + for (int i = 1; i < ArraySize; i++) { + int replaceIndex = i; + int deriveIndex = i-1; + switch (i & 0x7) { - case 0,1,2: - // creates new old BigInteger, releases old BigInteger, - // may create ephemeral data while computing gcd - array[replaceIndex] = array[replaceIndex].gcd(array[deriveIndex]); - break; - case 3,4: - // creates new old BigInteger, releases old BigInteger - array[replaceIndex] = array[replaceIndex].multiply(array[deriveIndex]); - break; - case 5,6,7: + case 0,1,2 -> { + // creates new BitSet, releases old BitSet, + // create ephemeral data while computing + BitSet result = (BitSet) array[deriveIndex].clone(); + for (int j=0; j<10; j++) { + result = (BitSet) array[deriveIndex].clone(); + } + array[replaceIndex] = result; + } + case 3,4 -> { + // creates new BitSet, releases old BitSet + BitSet result = (BitSet) array[deriveIndex].clone(); + array[replaceIndex] = result; + } + case 5,6,7 -> { // do nothing, let all objects in the array age to increase pressure on old generation - break; + } } } } @@ -93,6 +97,8 @@ public class TestOldGrowthTriggers { } testOld("-Xlog:gc", + "-XX:ConcGCThreads=1", + "-XX:ParallelGCThreads=1", "-Xms96m", "-Xmx96m", "-XX:+UnlockDiagnosticVMOptions", @@ -108,6 +114,8 @@ public class TestOldGrowthTriggers { ); testOld("-Xlog:gc", + "-XX:ConcGCThreads=1", + "-XX:ParallelGCThreads=1", "-Xms96m", "-Xmx96m", "-XX:+UnlockDiagnosticVMOptions", From 1e2b0d2b67c7ae0843c9adbc641471040fbe7d71 Mon Sep 17 00:00:00 2001 From: Arno Zeller Date: Sat, 11 Apr 2026 19:42:35 +0000 Subject: [PATCH 015/108] 8382018: test/jdk/java/nio/file/spi/SetDefaultProvider.java leaves a directory in /tmp Reviewed-by: alanb, mbaesken --- test/jdk/java/nio/file/spi/testapp/testapp/Main.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/jdk/java/nio/file/spi/testapp/testapp/Main.java b/test/jdk/java/nio/file/spi/testapp/testapp/Main.java index 6f61d431707..5a48ff47384 100644 --- a/test/jdk/java/nio/file/spi/testapp/testapp/Main.java +++ b/test/jdk/java/nio/file/spi/testapp/testapp/Main.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 @@ -41,7 +41,7 @@ public class Main { throw new RuntimeException("FileSystemProvider not overridden"); // exercise the file system - Path dir = Files.createTempDirectory("tmp"); + Path dir = Files.createTempDirectory(Path.of(""), "tmp"); if (dir.getFileSystem() != fs) throw new RuntimeException("'dir' not in default file system"); System.out.println("created: " + dir); From fb2460663c68dc909107b3bb39d322db2038ae79 Mon Sep 17 00:00:00 2001 From: Daniel Gredler Date: Sun, 12 Apr 2026 23:16:05 +0000 Subject: [PATCH 016/108] 8381623: Additional immutability in sun.font: ExtendedTextSourceLabel, GlyphLayout Reviewed-by: serb, prr --- .../sun/font/ExtendedTextSourceLabel.java | 39 +++++++------------ .../share/classes/sun/font/GlyphLayout.java | 8 ++-- 2 files changed, 17 insertions(+), 30 deletions(-) diff --git a/src/java.desktop/share/classes/sun/font/ExtendedTextSourceLabel.java b/src/java.desktop/share/classes/sun/font/ExtendedTextSourceLabel.java index f3569941321..78ae70629b6 100644 --- a/src/java.desktop/share/classes/sun/font/ExtendedTextSourceLabel.java +++ b/src/java.desktop/share/classes/sun/font/ExtendedTextSourceLabel.java @@ -57,15 +57,16 @@ import java.util.Map; * Align bounds is a rect that defines how to align this to margins. * it generally allows some overhang that logical bounds would prevent. */ -class ExtendedTextSourceLabel implements TextLineComponent, Decoration.Label { +final class ExtendedTextSourceLabel implements TextLineComponent, Decoration.Label { private final TextSource source; private final Decoration decorator; // caches - private Font font; - private AffineTransform baseTX; - private CoreMetrics cm; + private final Font font; + private final AffineTransform baseTX; + private final CoreMetrics cm; + private final float advTracking; private Rectangle2D lb; private Rectangle2D ab; @@ -74,34 +75,18 @@ class ExtendedTextSourceLabel implements TextLineComponent, Decoration.Label { private StandardGlyphVector gv; private float[] charinfo; - private float advTracking; - /** * Create from a TextSource. */ public ExtendedTextSourceLabel(TextSource source, Decoration decorator) { this.source = source; this.decorator = decorator; - finishInit(); - } - - /** - * Create from a TextSource, optionally using cached data from oldLabel starting at the offset. - * If present oldLabel must have been created from a run of text that includes the text used in - * the new label. Start in source corresponds to logical character offset in oldLabel. - */ - public ExtendedTextSourceLabel(TextSource source, ExtendedTextSourceLabel oldLabel, int offset) { - // currently no optimization. - this.source = source; - this.decorator = oldLabel.decorator; - finishInit(); - } - - private void finishInit() { - font = source.getFont(); + Font font = source.getFont(); Map atts = font.getAttributes(); - baseTX = AttributeValues.getBaselineTransform(atts); + AffineTransform baseTX = AttributeValues.getBaselineTransform(atts); + + CoreMetrics cm; if (baseTX == null){ cm = source.getCoreMetrics(); } else { @@ -110,13 +95,15 @@ class ExtendedTextSourceLabel implements TextLineComponent, Decoration.Label { charTX = new AffineTransform(); } font = font.deriveFont(charTX); - LineMetrics lm = font.getLineMetrics(source.getChars(), source.getStart(), source.getStart() + source.getLength(), source.getFRC()); cm = CoreMetrics.get(lm); } - advTracking = font.getSize() * AttributeValues.getTracking(atts); + this.font = font; + this.baseTX = baseTX; + this.cm = cm; + this.advTracking = font.getSize() * AttributeValues.getTracking(atts); } /** diff --git a/src/java.desktop/share/classes/sun/font/GlyphLayout.java b/src/java.desktop/share/classes/sun/font/GlyphLayout.java index 5bff127f143..851201fe347 100644 --- a/src/java.desktop/share/classes/sun/font/GlyphLayout.java +++ b/src/java.desktop/share/classes/sun/font/GlyphLayout.java @@ -125,10 +125,10 @@ public final class GlyphLayout { } private static final class SDCache { - public AffineTransform dtx; - public AffineTransform gtx; - public Point2D.Float delta; - public FontStrikeDesc sd; + private final AffineTransform dtx; + private final AffineTransform gtx; + private final Point2D.Float delta; + private final FontStrikeDesc sd; private SDCache(Font font, FontRenderContext frc) { // !!! add getVectorTransform and hasVectorTransform to frc? then From d75bb86ca69d5b547c4f46217387849333afa1f3 Mon Sep 17 00:00:00 2001 From: Arno Zeller Date: Mon, 13 Apr 2026 05:36:15 +0000 Subject: [PATCH 017/108] 8380896: Reduce runtime for MonitorVmStartTerminate.java on hosts with a lot of VMs Reviewed-by: lmesnik, sspitsyn --- .../MonitoredVm/MonitorVmStartTerminate.java | 36 +++++++++++-------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/test/jdk/sun/jvmstat/monitor/MonitoredVm/MonitorVmStartTerminate.java b/test/jdk/sun/jvmstat/monitor/MonitoredVm/MonitorVmStartTerminate.java index 88f1ed22e35..f436bd32e1b 100644 --- a/test/jdk/sun/jvmstat/monitor/MonitoredVm/MonitorVmStartTerminate.java +++ b/test/jdk/sun/jvmstat/monitor/MonitoredVm/MonitorVmStartTerminate.java @@ -148,8 +148,17 @@ public final class MonitorVmStartTerminate { } private void releaseStarted(Integer id) { + String monitoredArgs = readMainArgs(id); + if (monitoredArgs == null || monitoredArgs.equals("Unknown")) { + System.out.println("releaseStarted: not a test pid: " + id); + return; + } + for (JavaProcess jp : processes) { - if (hasMainArgs(id, jp.getMainArgsIdentifier())) { + if (jp.getId() != null) { + continue; + } + if (monitoredArgs.contains(jp.getMainArgsIdentifier())) { // store id for terminated identification jp.setId(id); System.out.println("RELEASED started (id=" + jp.getId() + ", args=" + jp.getMainArgsIdentifier() + ")"); @@ -177,40 +186,39 @@ public final class MonitorVmStartTerminate { } } - private boolean hasMainArgs(Integer id, String args) { - VmIdentifier vmid = null; + private String readMainArgs(Integer id) { + VmIdentifier vmid; try { vmid = new VmIdentifier("//" + id.intValue()); } catch (URISyntaxException e) { - System.out.println("hasMainArgs(" + id + "): " + e); - return false; + System.out.println("readMainArgs(" + id + "): " + e); + return null; } - // Retry a failing attempt to check arguments for a match, + // Retry a failing attempt to read arguments, // as not recognizing a test process will cause timeout and failure. for (int i = 0; i < ARGS_ATTEMPTS; i++) { try { MonitoredVm target = host.getMonitoredVm(vmid); String monitoredArgs = MonitoredVmUtil.mainArgs(target); - System.out.println("hasMainArgs(" + id + "): has main args: '" + monitoredArgs + "'"); + System.out.println("readMainArgs(" + id + "): has main args: '" + monitoredArgs + "'"); if (monitoredArgs == null || monitoredArgs.equals("Unknown")) { - System.out.println("hasMainArgs(" + id + "): retry" ); + System.out.println("readMainArgs(" + id + "): retry"); takeNap(); continue; - } else if (monitoredArgs.contains(args)) { - return true; } else { - return false; + return monitoredArgs; } } catch (MonitorException e) { // Process probably not running or not ours, e.g. // sun.jvmstat.monitor.MonitorException: Could not attach to PID // Only log if something else, to avoid filling log: - if (!e.getMessage().contains("Could not attach")) { - System.out.println("hasMainArgs(" + id + "): " + e); + String message = e.getMessage(); + if (message == null || !message.contains("Could not attach")) { + System.out.println("readMainArgs(" + id + "): " + e); } } } - return false; + return null; } } From 6371da9a44bf1feb487ed6bea67bddd3344d9aee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20Walln=C3=B6fer?= Date: Mon, 13 Apr 2026 06:14:13 +0000 Subject: [PATCH 018/108] 8378228: Replace jQuery UI autocomplete component in JavaDoc search Reviewed-by: nbenalla --- .../doclets/formats/html/HtmlDoclet.java | 13 +- .../doclets/formats/html/HtmlIds.java | 7 +- .../doclets/formats/html/Navigation.java | 40 +- .../doclets/formats/html/SearchWriter.java | 14 +- .../doclets/formats/html/markup/Head.java | 9 +- .../formats/html/markup/HtmlStyles.java | 12 +- .../html/resources/jquery/jquery-3.7.1.js | 10716 ---------------- .../html/resources/jquery/jquery-3.7.1.min.js | 2 - .../html/resources/jquery/jquery-ui.css | 148 - .../html/resources/jquery/jquery-ui.js | 2653 ---- .../html/resources/jquery/jquery-ui.min.css | 6 - .../html/resources/jquery/jquery-ui.min.js | 6 - .../formats/html/resources/script.js.template | 16 +- .../formats/html/resources/search-page.js | 348 - .../formats/html/resources/search.js.template | 603 +- .../formats/html/resources/stylesheet.css | 280 +- .../toolkit/resources/doclets.properties | 11 +- .../doclets/toolkit/util/DocPaths.java | 21 +- src/jdk.javadoc/share/legal/jquery.md | 26 - src/jdk.javadoc/share/legal/jqueryUI.md | 49 - .../CheckLibraryVersions.java | 6 +- .../CheckStylesheetClasses.java | 15 +- .../javadoc/doclet/testFonts/TestFonts.java | 3 +- .../testPassthruFiles/TestPassThruFiles.java | 10 +- .../javadoc/doclet/testSearch/TestSearch.java | 53 +- .../doclet/testStylesheet/TestStylesheet.java | 3 +- .../jdk/javadoc/tool/api/basic/APITest.java | 9 +- 27 files changed, 747 insertions(+), 14332 deletions(-) delete mode 100644 src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/jquery/jquery-3.7.1.js delete mode 100644 src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/jquery/jquery-3.7.1.min.js delete mode 100644 src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/jquery/jquery-ui.css delete mode 100644 src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/jquery/jquery-ui.js delete mode 100644 src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/jquery/jquery-ui.min.css delete mode 100644 src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/jquery/jquery-ui.min.js delete mode 100644 src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/search-page.js delete mode 100644 src/jdk.javadoc/share/legal/jquery.md delete mode 100644 src/jdk.javadoc/share/legal/jqueryUI.md diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlDoclet.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlDoclet.java index 66fcd90e2e8..17d56ff11ba 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlDoclet.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlDoclet.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 @@ -335,16 +335,9 @@ public class HtmlDoclet extends AbstractDoclet { if (options.createIndex()) { copyResource(DocPaths.SEARCH_JS_TEMPLATE, DocPaths.SCRIPT_FILES.resolve(DocPaths.SEARCH_JS), true); - copyResource(DocPaths.SEARCH_PAGE_JS, DocPaths.SCRIPT_FILES.resolve(DocPaths.SEARCH_PAGE_JS), true); copyResource(DocPaths.GLASS_SVG, DocPaths.RESOURCE_FILES.resolve(DocPaths.GLASS_SVG), false); copyResource(DocPaths.X_SVG, DocPaths.RESOURCE_FILES.resolve(DocPaths.X_SVG), false); - // No newline replacement for JQuery files - copyResource(DocPaths.JQUERY_DIR.resolve(DocPaths.JQUERY_JS), - DocPaths.SCRIPT_FILES.resolve(DocPaths.JQUERY_JS), false); - copyResource(DocPaths.JQUERY_DIR.resolve(DocPaths.JQUERY_UI_JS), - DocPaths.SCRIPT_FILES.resolve(DocPaths.JQUERY_UI_JS), false); - copyResource(DocPaths.JQUERY_DIR.resolve(DocPaths.JQUERY_UI_CSS), - DocPaths.RESOURCE_FILES.resolve(DocPaths.JQUERY_UI_CSS), false); } + } copyLegalFiles(options.createIndex(), options.syntaxHighlight()); // Print a notice if the documentation contains diagnostic markers @@ -369,7 +362,7 @@ public class HtmlDoclet extends AbstractDoclet { case "", "default" -> { // use a known resource as a stand-in, because we cannot get the URL for a resources directory var url = HtmlDoclet.class.getResource( - DocPaths.RESOURCES.resolve(DocPaths.LEGAL).resolve(DocPaths.JQUERY_MD).getPath()); + DocPaths.RESOURCES.resolve(DocPaths.LEGAL).resolve(DocPaths.DEJAVU_MD).getPath()); if (url != null) { try { legalNoticesDir = Path.of(url.toURI()).getParent(); diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlIds.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlIds.java index a3fba7eca14..50e6207b833 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlIds.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlIds.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 @@ -112,6 +112,11 @@ public class HtmlIds { static final HtmlId RELATED_PACKAGE_SUMMARY = HtmlId.of("related-package-summary"); static final HtmlId RESET_SEARCH = HtmlId.of("reset-search"); static final HtmlId SEARCH_INPUT = HtmlId.of("search-input"); + static final HtmlId SEARCH_INPUT_CONTAINER = HtmlId.of("search-input-container"); + static final HtmlId SEARCH_MODULES = HtmlId.of("search-modules"); + static final HtmlId SEARCH_PAGE_LINK = HtmlId.of("search-page-link"); + static final HtmlId SEARCH_RESULT_CONTAINER = HtmlId.of("search-result-container"); + static final HtmlId SEARCH_RESULT_SECTION = HtmlId.of("search-result-section"); static final HtmlId SERVICES = HtmlId.of("services-summary"); static final HtmlId SKIP_NAVBAR_TOP = HtmlId.of("skip-navbar-top"); static final HtmlId THEME_BUTTON = HtmlId.of("theme-button"); diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/Navigation.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/Navigation.java index 30318bbaeea..cde5b287e25 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/Navigation.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/Navigation.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 @@ -45,6 +45,8 @@ import jdk.javadoc.internal.html.Content; import jdk.javadoc.internal.html.ContentBuilder; import jdk.javadoc.internal.html.Entity; import jdk.javadoc.internal.html.HtmlAttr; +import jdk.javadoc.internal.html.HtmlId; +import jdk.javadoc.internal.html.HtmlTag; import jdk.javadoc.internal.html.HtmlTree; import jdk.javadoc.internal.html.Text; @@ -535,6 +537,42 @@ public class Navigation { .add(inputText) .add(inputReset); target.add(searchDiv); + target.add(HtmlTree.DIV(HtmlIds.SEARCH_RESULT_SECTION) + .add(HtmlTree.DIV(HtmlStyles.searchForm) + .add(HtmlTree.DIV(HtmlTree.LABEL(HtmlIds.SEARCH_INPUT.name(), + contents.getContent("doclet.search.for")))) + .add(HtmlTree.DIV(HtmlIds.SEARCH_INPUT_CONTAINER).addUnchecked(Text.EMPTY)) + .add(createModuleSelector())) + .add(HtmlTree.DIV(HtmlIds.SEARCH_RESULT_CONTAINER).addUnchecked(Text.EMPTY)) + .add(HtmlTree.DIV(HtmlStyles.searchLinks) + .add(HtmlTree.DIV(links.createLink(pathToRoot.resolve(DocPaths.SEARCH_PAGE), + contents.getContent("doclet.search.linkSearchPageLabel")) + .setId(HtmlIds.SEARCH_PAGE_LINK))) + .add(options.noHelp() || !options.helpFile().isEmpty() + ? HtmlTree.DIV(Text.EMPTY).addUnchecked(Text.EMPTY) + : HtmlTree.DIV(links.createLink(pathToRoot.resolve(DocPaths.HELP_DOC).fragment("search"), + contents.getContent("doclet.search.linkSearchHelpLabel")))))); + } + + private Content createModuleSelector() { + if (!configuration.showModules || configuration.modules.size() < 2) { + return Text.EMPTY; + } + var content = new ContentBuilder(HtmlTree.DIV(HtmlTree.LABEL(HtmlIds.SEARCH_MODULES.name(), + contents.getContent("doclet.search.in_modules")))); + var select = HtmlTree.of(HtmlTag.SELECT) + .setId(HtmlIds.SEARCH_MODULES) + .put(HtmlAttr.ARIA_LABEL, configuration.getDocResources().getText("doclet.selectModule")) + .add(HtmlTree.of(HtmlTag.OPTION) + .put(HtmlAttr.VALUE, "") + .add(contents.getContent("doclet.search.all_modules"))); + + for (ModuleElement module : configuration.modules) { + select.add(HtmlTree.of(HtmlTag.OPTION) + .put(HtmlAttr.VALUE, module.getQualifiedName().toString()) + .add(Text.of(module.getQualifiedName().toString()))); + } + return content.add(HtmlTree.DIV(select)); } /** diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/SearchWriter.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/SearchWriter.java index 433a641530d..5fa0daacf98 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/SearchWriter.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/SearchWriter.java @@ -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 @@ -92,17 +92,15 @@ public class SearchWriter extends HtmlDocletWriter { .add(resourceSection) .add(HtmlTree.P(contents.getContent("doclet.search.loading")) .setId(HtmlId.of("page-search-notify"))) - .add(HtmlTree.DIV(HtmlTree.DIV(HtmlId.of("result-container")) + .add(HtmlTree.DIV(HtmlTree.DIV(HtmlIds.SEARCH_RESULT_CONTAINER) .addUnchecked(Text.EMPTY)) - .setId(HtmlId.of("result-section")) - .put(HtmlAttr.STYLE, "display: none;") - .add(HtmlTree.SCRIPT(pathToRoot.resolve(DocPaths.SCRIPT_FILES) - .resolve(DocPaths.SEARCH_PAGE_JS).getPath()))); + .setId(HtmlIds.SEARCH_RESULT_SECTION) + .put(HtmlAttr.STYLE, "display: none;")); } private Content createModuleSelector() { - if (!configuration.showModules) { + if (!configuration.showModules || configuration.modules.size() < 2) { return Text.EMPTY; } @@ -118,7 +116,7 @@ public class SearchWriter extends HtmlDocletWriter { .put(HtmlAttr.VALUE, module.getQualifiedName().toString()) .add(Text.of(module.getQualifiedName().toString()))); } - return new ContentBuilder(contents.getContent("doclet.search.in", select)); + return new ContentBuilder(contents.getContent("doclet.search.in_modules"), select); } private Content createResourceSection() { diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/Head.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/Head.java index cda4bc9a5be..bff32cbd7bf 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/Head.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/Head.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 @@ -336,11 +336,6 @@ public class Head extends Content { } private void addStylesheets(HtmlTree head) { - if (index) { - // Add JQuery-UI stylesheet first so its rules can be overridden. - addStylesheet(head, DocPaths.RESOURCE_FILES.resolve(DocPaths.JQUERY_UI_CSS)); - } - if (mainStylesheet == null) { mainStylesheet = DocPaths.STYLESHEET; } @@ -381,8 +376,6 @@ public class Head extends Content { .append("loadScripts();\n") .append("initTheme();\n"); } - addScriptElement(head, DocPaths.JQUERY_JS); - addScriptElement(head, DocPaths.JQUERY_UI_JS); } for (HtmlConfiguration.JavaScriptFile javaScriptFile : additionalScripts) { addScriptElement(head, javaScriptFile); diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/HtmlStyles.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/HtmlStyles.java index 9b59cb0cb47..2b154db7de7 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/HtmlStyles.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/markup/HtmlStyles.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -734,6 +734,16 @@ public enum HtmlStyles implements HtmlStyle { */ pageSearchInfo, + /** + * The class for a {@code div} element in the search widget containing the search form inputs. + */ + searchForm, + + /** + * The class for a {@code div} element in the search widget containing search-related links. + */ + searchLinks, + /** * The class for a link in the static "Index" pages to a custom searchable item, * such as defined with an {@code @index} tag. diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/jquery/jquery-3.7.1.js b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/jquery/jquery-3.7.1.js deleted file mode 100644 index 1a86433c223..00000000000 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/resources/jquery/jquery-3.7.1.js +++ /dev/null @@ -1,10716 +0,0 @@ -/*! - * jQuery JavaScript Library v3.7.1 - * https://jquery.com/ - * - * Copyright OpenJS Foundation and other contributors - * Released under the MIT license - * https://jquery.org/license - * - * Date: 2023-08-28T13:37Z - */ -( function( global, factory ) { - - "use strict"; - - if ( typeof module === "object" && typeof module.exports === "object" ) { - - // For CommonJS and CommonJS-like environments where a proper `window` - // is present, execute the factory and get jQuery. - // For environments that do not have a `window` with a `document` - // (such as Node.js), expose a factory as module.exports. - // This accentuates the need for the creation of a real `window`. - // e.g. var jQuery = require("jquery")(window); - // See ticket trac-14549 for more info. - module.exports = global.document ? - factory( global, true ) : - function( w ) { - if ( !w.document ) { - throw new Error( "jQuery requires a window with a document" ); - } - return factory( w ); - }; - } else { - factory( global ); - } - -// Pass this if window is not defined yet -} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) { - -// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1 -// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode -// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common -// enough that all such attempts are guarded in a try block. -"use strict"; - -var arr = []; - -var getProto = Object.getPrototypeOf; - -var slice = arr.slice; - -var flat = arr.flat ? function( array ) { - return arr.flat.call( array ); -} : function( array ) { - return arr.concat.apply( [], array ); -}; - - -var push = arr.push; - -var indexOf = arr.indexOf; - -var class2type = {}; - -var toString = class2type.toString; - -var hasOwn = class2type.hasOwnProperty; - -var fnToString = hasOwn.toString; - -var ObjectFunctionString = fnToString.call( Object ); - -var support = {}; - -var isFunction = function isFunction( obj ) { - - // Support: Chrome <=57, Firefox <=52 - // In some browsers, typeof returns "function" for HTML elements - // (i.e., `typeof document.createElement( "object" ) === "function"`). - // We don't want to classify *any* DOM node as a function. - // Support: QtWeb <=3.8.5, WebKit <=534.34, wkhtmltopdf tool <=0.12.5 - // Plus for old WebKit, typeof returns "function" for HTML collections - // (e.g., `typeof document.getElementsByTagName("div") === "function"`). (gh-4756) - return typeof obj === "function" && typeof obj.nodeType !== "number" && - typeof obj.item !== "function"; - }; - - -var isWindow = function isWindow( obj ) { - return obj != null && obj === obj.window; - }; - - -var document = window.document; - - - - var preservedScriptAttributes = { - type: true, - src: true, - nonce: true, - noModule: true - }; - - function DOMEval( code, node, doc ) { - doc = doc || document; - - var i, val, - script = doc.createElement( "script" ); - - script.text = code; - if ( node ) { - for ( i in preservedScriptAttributes ) { - - // Support: Firefox 64+, Edge 18+ - // Some browsers don't support the "nonce" property on scripts. - // On the other hand, just using `getAttribute` is not enough as - // the `nonce` attribute is reset to an empty string whenever it - // becomes browsing-context connected. - // See https://github.com/whatwg/html/issues/2369 - // See https://html.spec.whatwg.org/#nonce-attributes - // The `node.getAttribute` check was added for the sake of - // `jQuery.globalEval` so that it can fake a nonce-containing node - // via an object. - val = node[ i ] || node.getAttribute && node.getAttribute( i ); - if ( val ) { - script.setAttribute( i, val ); - } - } - } - doc.head.appendChild( script ).parentNode.removeChild( script ); - } - - -function toType( obj ) { - if ( obj == null ) { - return obj + ""; - } - - // Support: Android <=2.3 only (functionish RegExp) - return typeof obj === "object" || typeof obj === "function" ? - class2type[ toString.call( obj ) ] || "object" : - typeof obj; -} -/* global Symbol */ -// Defining this global in .eslintrc.json would create a danger of using the global -// unguarded in another place, it seems safer to define global only for this module - - - -var version = "3.7.1", - - rhtmlSuffix = /HTML$/i, - - // Define a local copy of jQuery - jQuery = function( selector, context ) { - - // The jQuery object is actually just the init constructor 'enhanced' - // Need init if jQuery is called (just allow error to be thrown if not included) - return new jQuery.fn.init( selector, context ); - }; - -jQuery.fn = jQuery.prototype = { - - // The current version of jQuery being used - jquery: version, - - constructor: jQuery, - - // The default length of a jQuery object is 0 - length: 0, - - toArray: function() { - return slice.call( this ); - }, - - // Get the Nth element in the matched element set OR - // Get the whole matched element set as a clean array - get: function( num ) { - - // Return all the elements in a clean array - if ( num == null ) { - return slice.call( this ); - } - - // Return just the one element from the set - return num < 0 ? this[ num + this.length ] : this[ num ]; - }, - - // Take an array of elements and push it onto the stack - // (returning the new matched element set) - pushStack: function( elems ) { - - // Build a new jQuery matched element set - var ret = jQuery.merge( this.constructor(), elems ); - - // Add the old object onto the stack (as a reference) - ret.prevObject = this; - - // Return the newly-formed element set - return ret; - }, - - // Execute a callback for every element in the matched set. - each: function( callback ) { - return jQuery.each( this, callback ); - }, - - map: function( callback ) { - return this.pushStack( jQuery.map( this, function( elem, i ) { - return callback.call( elem, i, elem ); - } ) ); - }, - - slice: function() { - return this.pushStack( slice.apply( this, arguments ) ); - }, - - first: function() { - return this.eq( 0 ); - }, - - last: function() { - return this.eq( -1 ); - }, - - even: function() { - return this.pushStack( jQuery.grep( this, function( _elem, i ) { - return ( i + 1 ) % 2; - } ) ); - }, - - odd: function() { - return this.pushStack( jQuery.grep( this, function( _elem, i ) { - return i % 2; - } ) ); - }, - - eq: function( i ) { - var len = this.length, - j = +i + ( i < 0 ? len : 0 ); - return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] ); - }, - - end: function() { - return this.prevObject || this.constructor(); - }, - - // For internal use only. - // Behaves like an Array's method, not like a jQuery method. - push: push, - sort: arr.sort, - splice: arr.splice -}; - -jQuery.extend = jQuery.fn.extend = function() { - var options, name, src, copy, copyIsArray, clone, - target = arguments[ 0 ] || {}, - i = 1, - length = arguments.length, - deep = false; - - // Handle a deep copy situation - if ( typeof target === "boolean" ) { - deep = target; - - // Skip the boolean and the target - target = arguments[ i ] || {}; - i++; - } - - // Handle case when target is a string or something (possible in deep copy) - if ( typeof target !== "object" && !isFunction( target ) ) { - target = {}; - } - - // Extend jQuery itself if only one argument is passed - if ( i === length ) { - target = this; - i--; - } - - for ( ; i < length; i++ ) { - - // Only deal with non-null/undefined values - if ( ( options = arguments[ i ] ) != null ) { - - // Extend the base object - for ( name in options ) { - copy = options[ name ]; - - // Prevent Object.prototype pollution - // Prevent never-ending loop - if ( name === "__proto__" || target === copy ) { - continue; - } - - // Recurse if we're merging plain objects or arrays - if ( deep && copy && ( jQuery.isPlainObject( copy ) || - ( copyIsArray = Array.isArray( copy ) ) ) ) { - src = target[ name ]; - - // Ensure proper type for the source value - if ( copyIsArray && !Array.isArray( src ) ) { - clone = []; - } else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) { - clone = {}; - } else { - clone = src; - } - copyIsArray = false; - - // Never move original objects, clone them - target[ name ] = jQuery.extend( deep, clone, copy ); - - // Don't bring in undefined values - } else if ( copy !== undefined ) { - target[ name ] = copy; - } - } - } - } - - // Return the modified object - return target; -}; - -jQuery.extend( { - - // Unique for each copy of jQuery on the page - expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), - - // Assume jQuery is ready without the ready module - isReady: true, - - error: function( msg ) { - throw new Error( msg ); - }, - - noop: function() {}, - - isPlainObject: function( obj ) { - var proto, Ctor; - - // Detect obvious negatives - // Use toString instead of jQuery.type to catch host objects - if ( !obj || toString.call( obj ) !== "[object Object]" ) { - return false; - } - - proto = getProto( obj ); - - // Objects with no prototype (e.g., `Object.create( null )`) are plain - if ( !proto ) { - return true; - } - - // Objects with prototype are plain iff they were constructed by a global Object function - Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor; - return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString; - }, - - isEmptyObject: function( obj ) { - var name; - - for ( name in obj ) { - return false; - } - return true; - }, - - // Evaluates a script in a provided context; falls back to the global one - // if not specified. - globalEval: function( code, options, doc ) { - DOMEval( code, { nonce: options && options.nonce }, doc ); - }, - - each: function( obj, callback ) { - var length, i = 0; - - if ( isArrayLike( obj ) ) { - length = obj.length; - for ( ; i < length; i++ ) { - if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { - break; - } - } - } else { - for ( i in obj ) { - if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) { - break; - } - } - } - - return obj; - }, - - - // Retrieve the text value of an array of DOM nodes - text: function( elem ) { - var node, - ret = "", - i = 0, - nodeType = elem.nodeType; - - if ( !nodeType ) { - - // If no nodeType, this is expected to be an array - while ( ( node = elem[ i++ ] ) ) { - - // Do not traverse comment nodes - ret += jQuery.text( node ); - } - } - if ( nodeType === 1 || nodeType === 11 ) { - return elem.textContent; - } - if ( nodeType === 9 ) { - return elem.documentElement.textContent; - } - if ( nodeType === 3 || nodeType === 4 ) { - return elem.nodeValue; - } - - // Do not include comment or processing instruction nodes - - return ret; - }, - - // results is for internal usage only - makeArray: function( arr, results ) { - var ret = results || []; - - if ( arr != null ) { - if ( isArrayLike( Object( arr ) ) ) { - jQuery.merge( ret, - typeof arr === "string" ? - [ arr ] : arr - ); - } else { - push.call( ret, arr ); - } - } - - return ret; - }, - - inArray: function( elem, arr, i ) { - return arr == null ? -1 : indexOf.call( arr, elem, i ); - }, - - isXMLDoc: function( elem ) { - var namespace = elem && elem.namespaceURI, - docElem = elem && ( elem.ownerDocument || elem ).documentElement; - - // Assume HTML when documentElement doesn't yet exist, such as inside - // document fragments. - return !rhtmlSuffix.test( namespace || docElem && docElem.nodeName || "HTML" ); - }, - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - merge: function( first, second ) { - var len = +second.length, - j = 0, - i = first.length; - - for ( ; j < len; j++ ) { - first[ i++ ] = second[ j ]; - } - - first.length = i; - - return first; - }, - - grep: function( elems, callback, invert ) { - var callbackInverse, - matches = [], - i = 0, - length = elems.length, - callbackExpect = !invert; - - // Go through the array, only saving the items - // that pass the validator function - for ( ; i < length; i++ ) { - callbackInverse = !callback( elems[ i ], i ); - if ( callbackInverse !== callbackExpect ) { - matches.push( elems[ i ] ); - } - } - - return matches; - }, - - // arg is for internal usage only - map: function( elems, callback, arg ) { - var length, value, - i = 0, - ret = []; - - // Go through the array, translating each of the items to their new values - if ( isArrayLike( elems ) ) { - length = elems.length; - for ( ; i < length; i++ ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret.push( value ); - } - } - - // Go through every key on the object, - } else { - for ( i in elems ) { - value = callback( elems[ i ], i, arg ); - - if ( value != null ) { - ret.push( value ); - } - } - } - - // Flatten any nested arrays - return flat( ret ); - }, - - // A global GUID counter for objects - guid: 1, - - // jQuery.support is not used in Core but other projects attach their - // properties to it so it needs to exist. - support: support -} ); - -if ( typeof Symbol === "function" ) { - jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ]; -} - -// Populate the class2type map -jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ), - function( _i, name ) { - class2type[ "[object " + name + "]" ] = name.toLowerCase(); - } ); - -function isArrayLike( obj ) { - - // Support: real iOS 8.2 only (not reproducible in simulator) - // `in` check used to prevent JIT error (gh-2145) - // hasOwn isn't used here due to false negatives - // regarding Nodelist length in IE - var length = !!obj && "length" in obj && obj.length, - type = toType( obj ); - - if ( isFunction( obj ) || isWindow( obj ) ) { - return false; - } - - return type === "array" || length === 0 || - typeof length === "number" && length > 0 && ( length - 1 ) in obj; -} - - -function nodeName( elem, name ) { - - return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase(); - -} -var pop = arr.pop; - - -var sort = arr.sort; - - -var splice = arr.splice; - - -var whitespace = "[\\x20\\t\\r\\n\\f]"; - - -var rtrimCSS = new RegExp( - "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", - "g" -); - - - - -// Note: an element does not contain itself -jQuery.contains = function( a, b ) { - var bup = b && b.parentNode; - - return a === bup || !!( bup && bup.nodeType === 1 && ( - - // Support: IE 9 - 11+ - // IE doesn't have `contains` on SVG. - a.contains ? - a.contains( bup ) : - a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16 - ) ); -}; - - - - -// CSS string/identifier serialization -// https://drafts.csswg.org/cssom/#common-serializing-idioms -var rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g; - -function fcssescape( ch, asCodePoint ) { - if ( asCodePoint ) { - - // U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER - if ( ch === "\0" ) { - return "\uFFFD"; - } - - // Control characters and (dependent upon position) numbers get escaped as code points - return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " "; - } - - // Other potentially-special ASCII characters get backslash-escaped - return "\\" + ch; -} - -jQuery.escapeSelector = function( sel ) { - return ( sel + "" ).replace( rcssescape, fcssescape ); -}; - - - - -var preferredDoc = document, - pushNative = push; - -( function() { - -var i, - Expr, - outermostContext, - sortInput, - hasDuplicate, - push = pushNative, - - // Local document vars - document, - documentElement, - documentIsHTML, - rbuggyQSA, - matches, - - // Instance-specific data - expando = jQuery.expando, - dirruns = 0, - done = 0, - classCache = createCache(), - tokenCache = createCache(), - compilerCache = createCache(), - nonnativeSelectorCache = createCache(), - sortOrder = function( a, b ) { - if ( a === b ) { - hasDuplicate = true; - } - return 0; - }, - - booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|" + - "loop|multiple|open|readonly|required|scoped", - - // Regular expressions - - // https://www.w3.org/TR/css-syntax-3/#ident-token-diagram - identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace + - "?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+", - - // Attribute selectors: https://www.w3.org/TR/selectors/#attribute-selectors - attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace + - - // Operator (capture 2) - "*([*^$|!~]?=)" + whitespace + - - // "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]" - "*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + - whitespace + "*\\]", - - pseudos = ":(" + identifier + ")(?:\\((" + - - // To reduce the number of selectors needing tokenize in the preFilter, prefer arguments: - // 1. quoted (capture 3; capture 4 or capture 5) - "('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" + - - // 2. simple (capture 6) - "((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" + - - // 3. anything else (capture 2) - ".*" + - ")\\)|)", - - // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter - rwhitespace = new RegExp( whitespace + "+", "g" ), - - rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ), - rleadingCombinator = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + - whitespace + "*" ), - rdescend = new RegExp( whitespace + "|>" ), - - rpseudo = new RegExp( pseudos ), - ridentifier = new RegExp( "^" + identifier + "$" ), - - matchExpr = { - ID: new RegExp( "^#(" + identifier + ")" ), - CLASS: new RegExp( "^\\.(" + identifier + ")" ), - TAG: new RegExp( "^(" + identifier + "|[*])" ), - ATTR: new RegExp( "^" + attributes ), - PSEUDO: new RegExp( "^" + pseudos ), - CHILD: new RegExp( - "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + - whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + - whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ), - bool: new RegExp( "^(?:" + booleans + ")$", "i" ), - - // For use in libraries implementing .is() - // We use this for POS matching in `select` - needsContext: new RegExp( "^" + whitespace + - "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace + - "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" ) - }, - - rinputs = /^(?:input|select|textarea|button)$/i, - rheader = /^h\d$/i, - - // Easily-parseable/retrievable ID or TAG or CLASS selectors - rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/, - - rsibling = /[+~]/, - - // CSS escapes - // https://www.w3.org/TR/CSS21/syndata.html#escaped-characters - runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace + - "?|\\\\([^\\r\\n\\f])", "g" ), - funescape = function( escape, nonHex ) { - var high = "0x" + escape.slice( 1 ) - 0x10000; - - if ( nonHex ) { - - // Strip the backslash prefix from a non-hex escape sequence - return nonHex; - } - - // Replace a hexadecimal escape sequence with the encoded Unicode code point - // Support: IE <=11+ - // For values outside the Basic Multilingual Plane (BMP), manually construct a - // surrogate pair - return high < 0 ? - String.fromCharCode( high + 0x10000 ) : - String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 ); - }, - - // Used for iframes; see `setDocument`. - // Support: IE 9 - 11+, Edge 12 - 18+ - // Removing the function wrapper causes a "Permission Denied" - // error in IE/Edge. - unloadHandler = function() { - setDocument(); - }, - - inDisabledFieldset = addCombinator( - function( elem ) { - return elem.disabled === true && nodeName( elem, "fieldset" ); - }, - { dir: "parentNode", next: "legend" } - ); - -// Support: IE <=9 only -// Accessing document.activeElement can throw unexpectedly -// https://bugs.jquery.com/ticket/13393 -function safeActiveElement() { - try { - return document.activeElement; - } catch ( err ) { } -} - -// Optimize for push.apply( _, NodeList ) -try { - push.apply( - ( arr = slice.call( preferredDoc.childNodes ) ), - preferredDoc.childNodes - ); - - // Support: Android <=4.0 - // Detect silently failing push.apply - // eslint-disable-next-line no-unused-expressions - arr[ preferredDoc.childNodes.length ].nodeType; -} catch ( e ) { - push = { - apply: function( target, els ) { - pushNative.apply( target, slice.call( els ) ); - }, - call: function( target ) { - pushNative.apply( target, slice.call( arguments, 1 ) ); - } - }; -} - -function find( selector, context, results, seed ) { - var m, i, elem, nid, match, groups, newSelector, - newContext = context && context.ownerDocument, - - // nodeType defaults to 9, since context defaults to document - nodeType = context ? context.nodeType : 9; - - results = results || []; - - // Return early from calls with invalid selector or context - if ( typeof selector !== "string" || !selector || - nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) { - - return results; - } - - // Try to shortcut find operations (as opposed to filters) in HTML documents - if ( !seed ) { - setDocument( context ); - context = context || document; - - if ( documentIsHTML ) { - - // If the selector is sufficiently simple, try using a "get*By*" DOM method - // (excepting DocumentFragment context, where the methods don't exist) - if ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) { - - // ID selector - if ( ( m = match[ 1 ] ) ) { - - // Document context - if ( nodeType === 9 ) { - if ( ( elem = context.getElementById( m ) ) ) { - - // Support: IE 9 only - // getElementById can match elements by name instead of ID - if ( elem.id === m ) { - push.call( results, elem ); - return results; - } - } else { - return results; - } - - // Element context - } else { - - // Support: IE 9 only - // getElementById can match elements by name instead of ID - if ( newContext && ( elem = newContext.getElementById( m ) ) && - find.contains( context, elem ) && - elem.id === m ) { - - push.call( results, elem ); - return results; - } - } - - // Type selector - } else if ( match[ 2 ] ) { - push.apply( results, context.getElementsByTagName( selector ) ); - return results; - - // Class selector - } else if ( ( m = match[ 3 ] ) && context.getElementsByClassName ) { - push.apply( results, context.getElementsByClassName( m ) ); - return results; - } - } - - // Take advantage of querySelectorAll - if ( !nonnativeSelectorCache[ selector + " " ] && - ( !rbuggyQSA || !rbuggyQSA.test( selector ) ) ) { - - newSelector = selector; - newContext = context; - - // qSA considers elements outside a scoping root when evaluating child or - // descendant combinators, which is not what we want. - // In such cases, we work around the behavior by prefixing every selector in the - // list with an ID selector referencing the scope context. - // The technique has to be used as well when a leading combinator is used - // as such selectors are not recognized by querySelectorAll. - // Thanks to Andrew Dupont for this technique. - if ( nodeType === 1 && - ( rdescend.test( selector ) || rleadingCombinator.test( selector ) ) ) { - - // Expand context for sibling selectors - newContext = rsibling.test( selector ) && testContext( context.parentNode ) || - context; - - // We can use :scope instead of the ID hack if the browser - // supports it & if we're not changing the context. - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when - // strict-comparing two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( newContext != context || !support.scope ) { - - // Capture the context ID, setting it first if necessary - if ( ( nid = context.getAttribute( "id" ) ) ) { - nid = jQuery.escapeSelector( nid ); - } else { - context.setAttribute( "id", ( nid = expando ) ); - } - } - - // Prefix every selector in the list - groups = tokenize( selector ); - i = groups.length; - while ( i-- ) { - groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " + - toSelector( groups[ i ] ); - } - newSelector = groups.join( "," ); - } - - try { - push.apply( results, - newContext.querySelectorAll( newSelector ) - ); - return results; - } catch ( qsaError ) { - nonnativeSelectorCache( selector, true ); - } finally { - if ( nid === expando ) { - context.removeAttribute( "id" ); - } - } - } - } - } - - // All others - return select( selector.replace( rtrimCSS, "$1" ), context, results, seed ); -} - -/** - * Create key-value caches of limited size - * @returns {function(string, object)} Returns the Object data after storing it on itself with - * property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength) - * deleting the oldest entry - */ -function createCache() { - var keys = []; - - function cache( key, value ) { - - // Use (key + " ") to avoid collision with native prototype properties - // (see https://github.com/jquery/sizzle/issues/157) - if ( keys.push( key + " " ) > Expr.cacheLength ) { - - // Only keep the most recent entries - delete cache[ keys.shift() ]; - } - return ( cache[ key + " " ] = value ); - } - return cache; -} - -/** - * Mark a function for special use by jQuery selector module - * @param {Function} fn The function to mark - */ -function markFunction( fn ) { - fn[ expando ] = true; - return fn; -} - -/** - * Support testing using an element - * @param {Function} fn Passed the created element and returns a boolean result - */ -function assert( fn ) { - var el = document.createElement( "fieldset" ); - - try { - return !!fn( el ); - } catch ( e ) { - return false; - } finally { - - // Remove from its parent by default - if ( el.parentNode ) { - el.parentNode.removeChild( el ); - } - - // release memory in IE - el = null; - } -} - -/** - * Returns a function to use in pseudos for input types - * @param {String} type - */ -function createInputPseudo( type ) { - return function( elem ) { - return nodeName( elem, "input" ) && elem.type === type; - }; -} - -/** - * Returns a function to use in pseudos for buttons - * @param {String} type - */ -function createButtonPseudo( type ) { - return function( elem ) { - return ( nodeName( elem, "input" ) || nodeName( elem, "button" ) ) && - elem.type === type; - }; -} - -/** - * Returns a function to use in pseudos for :enabled/:disabled - * @param {Boolean} disabled true for :disabled; false for :enabled - */ -function createDisabledPseudo( disabled ) { - - // Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable - return function( elem ) { - - // Only certain elements can match :enabled or :disabled - // https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled - // https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled - if ( "form" in elem ) { - - // Check for inherited disabledness on relevant non-disabled elements: - // * listed form-associated elements in a disabled fieldset - // https://html.spec.whatwg.org/multipage/forms.html#category-listed - // https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled - // * option elements in a disabled optgroup - // https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled - // All such elements have a "form" property. - if ( elem.parentNode && elem.disabled === false ) { - - // Option elements defer to a parent optgroup if present - if ( "label" in elem ) { - if ( "label" in elem.parentNode ) { - return elem.parentNode.disabled === disabled; - } else { - return elem.disabled === disabled; - } - } - - // Support: IE 6 - 11+ - // Use the isDisabled shortcut property to check for disabled fieldset ancestors - return elem.isDisabled === disabled || - - // Where there is no isDisabled, check manually - elem.isDisabled !== !disabled && - inDisabledFieldset( elem ) === disabled; - } - - return elem.disabled === disabled; - - // Try to winnow out elements that can't be disabled before trusting the disabled property. - // Some victims get caught in our net (label, legend, menu, track), but it shouldn't - // even exist on them, let alone have a boolean value. - } else if ( "label" in elem ) { - return elem.disabled === disabled; - } - - // Remaining elements are neither :enabled nor :disabled - return false; - }; -} - -/** - * Returns a function to use in pseudos for positionals - * @param {Function} fn - */ -function createPositionalPseudo( fn ) { - return markFunction( function( argument ) { - argument = +argument; - return markFunction( function( seed, matches ) { - var j, - matchIndexes = fn( [], seed.length, argument ), - i = matchIndexes.length; - - // Match elements found at the specified indexes - while ( i-- ) { - if ( seed[ ( j = matchIndexes[ i ] ) ] ) { - seed[ j ] = !( matches[ j ] = seed[ j ] ); - } - } - } ); - } ); -} - -/** - * Checks a node for validity as a jQuery selector context - * @param {Element|Object=} context - * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value - */ -function testContext( context ) { - return context && typeof context.getElementsByTagName !== "undefined" && context; -} - -/** - * Sets document-related variables once based on the current document - * @param {Element|Object} [node] An element or document object to use to set the document - * @returns {Object} Returns the current document - */ -function setDocument( node ) { - var subWindow, - doc = node ? node.ownerDocument || node : preferredDoc; - - // Return early if doc is invalid or already selected - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) { - return document; - } - - // Update global variables - document = doc; - documentElement = document.documentElement; - documentIsHTML = !jQuery.isXMLDoc( document ); - - // Support: iOS 7 only, IE 9 - 11+ - // Older browsers didn't support unprefixed `matches`. - matches = documentElement.matches || - documentElement.webkitMatchesSelector || - documentElement.msMatchesSelector; - - // Support: IE 9 - 11+, Edge 12 - 18+ - // Accessing iframe documents after unload throws "permission denied" errors - // (see trac-13936). - // Limit the fix to IE & Edge Legacy; despite Edge 15+ implementing `matches`, - // all IE 9+ and Edge Legacy versions implement `msMatchesSelector` as well. - if ( documentElement.msMatchesSelector && - - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - preferredDoc != document && - ( subWindow = document.defaultView ) && subWindow.top !== subWindow ) { - - // Support: IE 9 - 11+, Edge 12 - 18+ - subWindow.addEventListener( "unload", unloadHandler ); - } - - // Support: IE <10 - // Check if getElementById returns elements by name - // The broken getElementById methods don't pick up programmatically-set names, - // so use a roundabout getElementsByName test - support.getById = assert( function( el ) { - documentElement.appendChild( el ).id = jQuery.expando; - return !document.getElementsByName || - !document.getElementsByName( jQuery.expando ).length; - } ); - - // Support: IE 9 only - // Check to see if it's possible to do matchesSelector - // on a disconnected node. - support.disconnectedMatch = assert( function( el ) { - return matches.call( el, "*" ); - } ); - - // Support: IE 9 - 11+, Edge 12 - 18+ - // IE/Edge don't support the :scope pseudo-class. - support.scope = assert( function() { - return document.querySelectorAll( ":scope" ); - } ); - - // Support: Chrome 105 - 111 only, Safari 15.4 - 16.3 only - // Make sure the `:has()` argument is parsed unforgivingly. - // We include `*` in the test to detect buggy implementations that are - // _selectively_ forgiving (specifically when the list includes at least - // one valid selector). - // Note that we treat complete lack of support for `:has()` as if it were - // spec-compliant support, which is fine because use of `:has()` in such - // environments will fail in the qSA path and fall back to jQuery traversal - // anyway. - support.cssHas = assert( function() { - try { - document.querySelector( ":has(*,:jqfake)" ); - return false; - } catch ( e ) { - return true; - } - } ); - - // ID filter and find - if ( support.getById ) { - Expr.filter.ID = function( id ) { - var attrId = id.replace( runescape, funescape ); - return function( elem ) { - return elem.getAttribute( "id" ) === attrId; - }; - }; - Expr.find.ID = function( id, context ) { - if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { - var elem = context.getElementById( id ); - return elem ? [ elem ] : []; - } - }; - } else { - Expr.filter.ID = function( id ) { - var attrId = id.replace( runescape, funescape ); - return function( elem ) { - var node = typeof elem.getAttributeNode !== "undefined" && - elem.getAttributeNode( "id" ); - return node && node.value === attrId; - }; - }; - - // Support: IE 6 - 7 only - // getElementById is not reliable as a find shortcut - Expr.find.ID = function( id, context ) { - if ( typeof context.getElementById !== "undefined" && documentIsHTML ) { - var node, i, elems, - elem = context.getElementById( id ); - - if ( elem ) { - - // Verify the id attribute - node = elem.getAttributeNode( "id" ); - if ( node && node.value === id ) { - return [ elem ]; - } - - // Fall back on getElementsByName - elems = context.getElementsByName( id ); - i = 0; - while ( ( elem = elems[ i++ ] ) ) { - node = elem.getAttributeNode( "id" ); - if ( node && node.value === id ) { - return [ elem ]; - } - } - } - - return []; - } - }; - } - - // Tag - Expr.find.TAG = function( tag, context ) { - if ( typeof context.getElementsByTagName !== "undefined" ) { - return context.getElementsByTagName( tag ); - - // DocumentFragment nodes don't have gEBTN - } else { - return context.querySelectorAll( tag ); - } - }; - - // Class - Expr.find.CLASS = function( className, context ) { - if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) { - return context.getElementsByClassName( className ); - } - }; - - /* QSA/matchesSelector - ---------------------------------------------------------------------- */ - - // QSA and matchesSelector support - - rbuggyQSA = []; - - // Build QSA regex - // Regex strategy adopted from Diego Perini - assert( function( el ) { - - var input; - - documentElement.appendChild( el ).innerHTML = - "" + - ""; - - // Support: iOS <=7 - 8 only - // Boolean attributes and "value" are not treated correctly in some XML documents - if ( !el.querySelectorAll( "[selected]" ).length ) { - rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" ); - } - - // Support: iOS <=7 - 8 only - if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) { - rbuggyQSA.push( "~=" ); - } - - // Support: iOS 8 only - // https://bugs.webkit.org/show_bug.cgi?id=136851 - // In-page `selector#id sibling-combinator selector` fails - if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) { - rbuggyQSA.push( ".#.+[+~]" ); - } - - // Support: Chrome <=105+, Firefox <=104+, Safari <=15.4+ - // In some of the document kinds, these selectors wouldn't work natively. - // This is probably OK but for backwards compatibility we want to maintain - // handling them through jQuery traversal in jQuery 3.x. - if ( !el.querySelectorAll( ":checked" ).length ) { - rbuggyQSA.push( ":checked" ); - } - - // Support: Windows 8 Native Apps - // The type and name attributes are restricted during .innerHTML assignment - input = document.createElement( "input" ); - input.setAttribute( "type", "hidden" ); - el.appendChild( input ).setAttribute( "name", "D" ); - - // Support: IE 9 - 11+ - // IE's :disabled selector does not pick up the children of disabled fieldsets - // Support: Chrome <=105+, Firefox <=104+, Safari <=15.4+ - // In some of the document kinds, these selectors wouldn't work natively. - // This is probably OK but for backwards compatibility we want to maintain - // handling them through jQuery traversal in jQuery 3.x. - documentElement.appendChild( el ).disabled = true; - if ( el.querySelectorAll( ":disabled" ).length !== 2 ) { - rbuggyQSA.push( ":enabled", ":disabled" ); - } - - // Support: IE 11+, Edge 15 - 18+ - // IE 11/Edge don't find elements on a `[name='']` query in some cases. - // Adding a temporary attribute to the document before the selection works - // around the issue. - // Interestingly, IE 10 & older don't seem to have the issue. - input = document.createElement( "input" ); - input.setAttribute( "name", "" ); - el.appendChild( input ); - if ( !el.querySelectorAll( "[name='']" ).length ) { - rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" + - whitespace + "*(?:''|\"\")" ); - } - } ); - - if ( !support.cssHas ) { - - // Support: Chrome 105 - 110+, Safari 15.4 - 16.3+ - // Our regular `try-catch` mechanism fails to detect natively-unsupported - // pseudo-classes inside `:has()` (such as `:has(:contains("Foo"))`) - // in browsers that parse the `:has()` argument as a forgiving selector list. - // https://drafts.csswg.org/selectors/#relational now requires the argument - // to be parsed unforgivingly, but browsers have not yet fully adjusted. - rbuggyQSA.push( ":has" ); - } - - rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) ); - - /* Sorting - ---------------------------------------------------------------------- */ - - // Document order sorting - sortOrder = function( a, b ) { - - // Flag for duplicate removal - if ( a === b ) { - hasDuplicate = true; - return 0; - } - - // Sort on method existence if only one input has compareDocumentPosition - var compare = !a.compareDocumentPosition - !b.compareDocumentPosition; - if ( compare ) { - return compare; - } - - // Calculate position if both inputs belong to the same document - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - compare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ? - a.compareDocumentPosition( b ) : - - // Otherwise we know they are disconnected - 1; - - // Disconnected nodes - if ( compare & 1 || - ( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) { - - // Choose the first element that is related to our preferred document - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( a === document || a.ownerDocument == preferredDoc && - find.contains( preferredDoc, a ) ) { - return -1; - } - - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( b === document || b.ownerDocument == preferredDoc && - find.contains( preferredDoc, b ) ) { - return 1; - } - - // Maintain original order - return sortInput ? - ( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) : - 0; - } - - return compare & 4 ? -1 : 1; - }; - - return document; -} - -find.matches = function( expr, elements ) { - return find( expr, null, null, elements ); -}; - -find.matchesSelector = function( elem, expr ) { - setDocument( elem ); - - if ( documentIsHTML && - !nonnativeSelectorCache[ expr + " " ] && - ( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) { - - try { - var ret = matches.call( elem, expr ); - - // IE 9's matchesSelector returns false on disconnected nodes - if ( ret || support.disconnectedMatch || - - // As well, disconnected nodes are said to be in a document - // fragment in IE 9 - elem.document && elem.document.nodeType !== 11 ) { - return ret; - } - } catch ( e ) { - nonnativeSelectorCache( expr, true ); - } - } - - return find( expr, document, null, [ elem ] ).length > 0; -}; - -find.contains = function( context, elem ) { - - // Set document vars if needed - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( ( context.ownerDocument || context ) != document ) { - setDocument( context ); - } - return jQuery.contains( context, elem ); -}; - - -find.attr = function( elem, name ) { - - // Set document vars if needed - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( ( elem.ownerDocument || elem ) != document ) { - setDocument( elem ); - } - - var fn = Expr.attrHandle[ name.toLowerCase() ], - - // Don't get fooled by Object.prototype properties (see trac-13807) - val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ? - fn( elem, name, !documentIsHTML ) : - undefined; - - if ( val !== undefined ) { - return val; - } - - return elem.getAttribute( name ); -}; - -find.error = function( msg ) { - throw new Error( "Syntax error, unrecognized expression: " + msg ); -}; - -/** - * Document sorting and removing duplicates - * @param {ArrayLike} results - */ -jQuery.uniqueSort = function( results ) { - var elem, - duplicates = [], - j = 0, - i = 0; - - // Unless we *know* we can detect duplicates, assume their presence - // - // Support: Android <=4.0+ - // Testing for detecting duplicates is unpredictable so instead assume we can't - // depend on duplicate detection in all browsers without a stable sort. - hasDuplicate = !support.sortStable; - sortInput = !support.sortStable && slice.call( results, 0 ); - sort.call( results, sortOrder ); - - if ( hasDuplicate ) { - while ( ( elem = results[ i++ ] ) ) { - if ( elem === results[ i ] ) { - j = duplicates.push( i ); - } - } - while ( j-- ) { - splice.call( results, duplicates[ j ], 1 ); - } - } - - // Clear input after sorting to release objects - // See https://github.com/jquery/sizzle/pull/225 - sortInput = null; - - return results; -}; - -jQuery.fn.uniqueSort = function() { - return this.pushStack( jQuery.uniqueSort( slice.apply( this ) ) ); -}; - -Expr = jQuery.expr = { - - // Can be adjusted by the user - cacheLength: 50, - - createPseudo: markFunction, - - match: matchExpr, - - attrHandle: {}, - - find: {}, - - relative: { - ">": { dir: "parentNode", first: true }, - " ": { dir: "parentNode" }, - "+": { dir: "previousSibling", first: true }, - "~": { dir: "previousSibling" } - }, - - preFilter: { - ATTR: function( match ) { - match[ 1 ] = match[ 1 ].replace( runescape, funescape ); - - // Move the given value to match[3] whether quoted or unquoted - match[ 3 ] = ( match[ 3 ] || match[ 4 ] || match[ 5 ] || "" ) - .replace( runescape, funescape ); - - if ( match[ 2 ] === "~=" ) { - match[ 3 ] = " " + match[ 3 ] + " "; - } - - return match.slice( 0, 4 ); - }, - - CHILD: function( match ) { - - /* matches from matchExpr["CHILD"] - 1 type (only|nth|...) - 2 what (child|of-type) - 3 argument (even|odd|\d*|\d*n([+-]\d+)?|...) - 4 xn-component of xn+y argument ([+-]?\d*n|) - 5 sign of xn-component - 6 x of xn-component - 7 sign of y-component - 8 y of y-component - */ - match[ 1 ] = match[ 1 ].toLowerCase(); - - if ( match[ 1 ].slice( 0, 3 ) === "nth" ) { - - // nth-* requires argument - if ( !match[ 3 ] ) { - find.error( match[ 0 ] ); - } - - // numeric x and y parameters for Expr.filter.CHILD - // remember that false/true cast respectively to 0/1 - match[ 4 ] = +( match[ 4 ] ? - match[ 5 ] + ( match[ 6 ] || 1 ) : - 2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" ) - ); - match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" ); - - // other types prohibit arguments - } else if ( match[ 3 ] ) { - find.error( match[ 0 ] ); - } - - return match; - }, - - PSEUDO: function( match ) { - var excess, - unquoted = !match[ 6 ] && match[ 2 ]; - - if ( matchExpr.CHILD.test( match[ 0 ] ) ) { - return null; - } - - // Accept quoted arguments as-is - if ( match[ 3 ] ) { - match[ 2 ] = match[ 4 ] || match[ 5 ] || ""; - - // Strip excess characters from unquoted arguments - } else if ( unquoted && rpseudo.test( unquoted ) && - - // Get excess from tokenize (recursively) - ( excess = tokenize( unquoted, true ) ) && - - // advance to the next closing parenthesis - ( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) { - - // excess is a negative index - match[ 0 ] = match[ 0 ].slice( 0, excess ); - match[ 2 ] = unquoted.slice( 0, excess ); - } - - // Return only captures needed by the pseudo filter method (type and argument) - return match.slice( 0, 3 ); - } - }, - - filter: { - - TAG: function( nodeNameSelector ) { - var expectedNodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase(); - return nodeNameSelector === "*" ? - function() { - return true; - } : - function( elem ) { - return nodeName( elem, expectedNodeName ); - }; - }, - - CLASS: function( className ) { - var pattern = classCache[ className + " " ]; - - return pattern || - ( pattern = new RegExp( "(^|" + whitespace + ")" + className + - "(" + whitespace + "|$)" ) ) && - classCache( className, function( elem ) { - return pattern.test( - typeof elem.className === "string" && elem.className || - typeof elem.getAttribute !== "undefined" && - elem.getAttribute( "class" ) || - "" - ); - } ); - }, - - ATTR: function( name, operator, check ) { - return function( elem ) { - var result = find.attr( elem, name ); - - if ( result == null ) { - return operator === "!="; - } - if ( !operator ) { - return true; - } - - result += ""; - - if ( operator === "=" ) { - return result === check; - } - if ( operator === "!=" ) { - return result !== check; - } - if ( operator === "^=" ) { - return check && result.indexOf( check ) === 0; - } - if ( operator === "*=" ) { - return check && result.indexOf( check ) > -1; - } - if ( operator === "$=" ) { - return check && result.slice( -check.length ) === check; - } - if ( operator === "~=" ) { - return ( " " + result.replace( rwhitespace, " " ) + " " ) - .indexOf( check ) > -1; - } - if ( operator === "|=" ) { - return result === check || result.slice( 0, check.length + 1 ) === check + "-"; - } - - return false; - }; - }, - - CHILD: function( type, what, _argument, first, last ) { - var simple = type.slice( 0, 3 ) !== "nth", - forward = type.slice( -4 ) !== "last", - ofType = what === "of-type"; - - return first === 1 && last === 0 ? - - // Shortcut for :nth-*(n) - function( elem ) { - return !!elem.parentNode; - } : - - function( elem, _context, xml ) { - var cache, outerCache, node, nodeIndex, start, - dir = simple !== forward ? "nextSibling" : "previousSibling", - parent = elem.parentNode, - name = ofType && elem.nodeName.toLowerCase(), - useCache = !xml && !ofType, - diff = false; - - if ( parent ) { - - // :(first|last|only)-(child|of-type) - if ( simple ) { - while ( dir ) { - node = elem; - while ( ( node = node[ dir ] ) ) { - if ( ofType ? - nodeName( node, name ) : - node.nodeType === 1 ) { - - return false; - } - } - - // Reverse direction for :only-* (if we haven't yet done so) - start = dir = type === "only" && !start && "nextSibling"; - } - return true; - } - - start = [ forward ? parent.firstChild : parent.lastChild ]; - - // non-xml :nth-child(...) stores cache data on `parent` - if ( forward && useCache ) { - - // Seek `elem` from a previously-cached index - outerCache = parent[ expando ] || ( parent[ expando ] = {} ); - cache = outerCache[ type ] || []; - nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; - diff = nodeIndex && cache[ 2 ]; - node = nodeIndex && parent.childNodes[ nodeIndex ]; - - while ( ( node = ++nodeIndex && node && node[ dir ] || - - // Fallback to seeking `elem` from the start - ( diff = nodeIndex = 0 ) || start.pop() ) ) { - - // When found, cache indexes on `parent` and break - if ( node.nodeType === 1 && ++diff && node === elem ) { - outerCache[ type ] = [ dirruns, nodeIndex, diff ]; - break; - } - } - - } else { - - // Use previously-cached element index if available - if ( useCache ) { - outerCache = elem[ expando ] || ( elem[ expando ] = {} ); - cache = outerCache[ type ] || []; - nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ]; - diff = nodeIndex; - } - - // xml :nth-child(...) - // or :nth-last-child(...) or :nth(-last)?-of-type(...) - if ( diff === false ) { - - // Use the same loop as above to seek `elem` from the start - while ( ( node = ++nodeIndex && node && node[ dir ] || - ( diff = nodeIndex = 0 ) || start.pop() ) ) { - - if ( ( ofType ? - nodeName( node, name ) : - node.nodeType === 1 ) && - ++diff ) { - - // Cache the index of each encountered element - if ( useCache ) { - outerCache = node[ expando ] || - ( node[ expando ] = {} ); - outerCache[ type ] = [ dirruns, diff ]; - } - - if ( node === elem ) { - break; - } - } - } - } - } - - // Incorporate the offset, then check against cycle size - diff -= last; - return diff === first || ( diff % first === 0 && diff / first >= 0 ); - } - }; - }, - - PSEUDO: function( pseudo, argument ) { - - // pseudo-class names are case-insensitive - // https://www.w3.org/TR/selectors/#pseudo-classes - // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters - // Remember that setFilters inherits from pseudos - var args, - fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] || - find.error( "unsupported pseudo: " + pseudo ); - - // The user may use createPseudo to indicate that - // arguments are needed to create the filter function - // just as jQuery does - if ( fn[ expando ] ) { - return fn( argument ); - } - - // But maintain support for old signatures - if ( fn.length > 1 ) { - args = [ pseudo, pseudo, "", argument ]; - return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ? - markFunction( function( seed, matches ) { - var idx, - matched = fn( seed, argument ), - i = matched.length; - while ( i-- ) { - idx = indexOf.call( seed, matched[ i ] ); - seed[ idx ] = !( matches[ idx ] = matched[ i ] ); - } - } ) : - function( elem ) { - return fn( elem, 0, args ); - }; - } - - return fn; - } - }, - - pseudos: { - - // Potentially complex pseudos - not: markFunction( function( selector ) { - - // Trim the selector passed to compile - // to avoid treating leading and trailing - // spaces as combinators - var input = [], - results = [], - matcher = compile( selector.replace( rtrimCSS, "$1" ) ); - - return matcher[ expando ] ? - markFunction( function( seed, matches, _context, xml ) { - var elem, - unmatched = matcher( seed, null, xml, [] ), - i = seed.length; - - // Match elements unmatched by `matcher` - while ( i-- ) { - if ( ( elem = unmatched[ i ] ) ) { - seed[ i ] = !( matches[ i ] = elem ); - } - } - } ) : - function( elem, _context, xml ) { - input[ 0 ] = elem; - matcher( input, null, xml, results ); - - // Don't keep the element - // (see https://github.com/jquery/sizzle/issues/299) - input[ 0 ] = null; - return !results.pop(); - }; - } ), - - has: markFunction( function( selector ) { - return function( elem ) { - return find( selector, elem ).length > 0; - }; - } ), - - contains: markFunction( function( text ) { - text = text.replace( runescape, funescape ); - return function( elem ) { - return ( elem.textContent || jQuery.text( elem ) ).indexOf( text ) > -1; - }; - } ), - - // "Whether an element is represented by a :lang() selector - // is based solely on the element's language value - // being equal to the identifier C, - // or beginning with the identifier C immediately followed by "-". - // The matching of C against the element's language value is performed case-insensitively. - // The identifier C does not have to be a valid language name." - // https://www.w3.org/TR/selectors/#lang-pseudo - lang: markFunction( function( lang ) { - - // lang value must be a valid identifier - if ( !ridentifier.test( lang || "" ) ) { - find.error( "unsupported lang: " + lang ); - } - lang = lang.replace( runescape, funescape ).toLowerCase(); - return function( elem ) { - var elemLang; - do { - if ( ( elemLang = documentIsHTML ? - elem.lang : - elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) { - - elemLang = elemLang.toLowerCase(); - return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0; - } - } while ( ( elem = elem.parentNode ) && elem.nodeType === 1 ); - return false; - }; - } ), - - // Miscellaneous - target: function( elem ) { - var hash = window.location && window.location.hash; - return hash && hash.slice( 1 ) === elem.id; - }, - - root: function( elem ) { - return elem === documentElement; - }, - - focus: function( elem ) { - return elem === safeActiveElement() && - document.hasFocus() && - !!( elem.type || elem.href || ~elem.tabIndex ); - }, - - // Boolean properties - enabled: createDisabledPseudo( false ), - disabled: createDisabledPseudo( true ), - - checked: function( elem ) { - - // In CSS3, :checked should return both checked and selected elements - // https://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked - return ( nodeName( elem, "input" ) && !!elem.checked ) || - ( nodeName( elem, "option" ) && !!elem.selected ); - }, - - selected: function( elem ) { - - // Support: IE <=11+ - // Accessing the selectedIndex property - // forces the browser to treat the default option as - // selected when in an optgroup. - if ( elem.parentNode ) { - // eslint-disable-next-line no-unused-expressions - elem.parentNode.selectedIndex; - } - - return elem.selected === true; - }, - - // Contents - empty: function( elem ) { - - // https://www.w3.org/TR/selectors/#empty-pseudo - // :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5), - // but not by others (comment: 8; processing instruction: 7; etc.) - // nodeType < 6 works because attributes (2) do not appear as children - for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) { - if ( elem.nodeType < 6 ) { - return false; - } - } - return true; - }, - - parent: function( elem ) { - return !Expr.pseudos.empty( elem ); - }, - - // Element/input types - header: function( elem ) { - return rheader.test( elem.nodeName ); - }, - - input: function( elem ) { - return rinputs.test( elem.nodeName ); - }, - - button: function( elem ) { - return nodeName( elem, "input" ) && elem.type === "button" || - nodeName( elem, "button" ); - }, - - text: function( elem ) { - var attr; - return nodeName( elem, "input" ) && elem.type === "text" && - - // Support: IE <10 only - // New HTML5 attribute values (e.g., "search") appear - // with elem.type === "text" - ( ( attr = elem.getAttribute( "type" ) ) == null || - attr.toLowerCase() === "text" ); - }, - - // Position-in-collection - first: createPositionalPseudo( function() { - return [ 0 ]; - } ), - - last: createPositionalPseudo( function( _matchIndexes, length ) { - return [ length - 1 ]; - } ), - - eq: createPositionalPseudo( function( _matchIndexes, length, argument ) { - return [ argument < 0 ? argument + length : argument ]; - } ), - - even: createPositionalPseudo( function( matchIndexes, length ) { - var i = 0; - for ( ; i < length; i += 2 ) { - matchIndexes.push( i ); - } - return matchIndexes; - } ), - - odd: createPositionalPseudo( function( matchIndexes, length ) { - var i = 1; - for ( ; i < length; i += 2 ) { - matchIndexes.push( i ); - } - return matchIndexes; - } ), - - lt: createPositionalPseudo( function( matchIndexes, length, argument ) { - var i; - - if ( argument < 0 ) { - i = argument + length; - } else if ( argument > length ) { - i = length; - } else { - i = argument; - } - - for ( ; --i >= 0; ) { - matchIndexes.push( i ); - } - return matchIndexes; - } ), - - gt: createPositionalPseudo( function( matchIndexes, length, argument ) { - var i = argument < 0 ? argument + length : argument; - for ( ; ++i < length; ) { - matchIndexes.push( i ); - } - return matchIndexes; - } ) - } -}; - -Expr.pseudos.nth = Expr.pseudos.eq; - -// Add button/input type pseudos -for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) { - Expr.pseudos[ i ] = createInputPseudo( i ); -} -for ( i in { submit: true, reset: true } ) { - Expr.pseudos[ i ] = createButtonPseudo( i ); -} - -// Easy API for creating new setFilters -function setFilters() {} -setFilters.prototype = Expr.filters = Expr.pseudos; -Expr.setFilters = new setFilters(); - -function tokenize( selector, parseOnly ) { - var matched, match, tokens, type, - soFar, groups, preFilters, - cached = tokenCache[ selector + " " ]; - - if ( cached ) { - return parseOnly ? 0 : cached.slice( 0 ); - } - - soFar = selector; - groups = []; - preFilters = Expr.preFilter; - - while ( soFar ) { - - // Comma and first run - if ( !matched || ( match = rcomma.exec( soFar ) ) ) { - if ( match ) { - - // Don't consume trailing commas as valid - soFar = soFar.slice( match[ 0 ].length ) || soFar; - } - groups.push( ( tokens = [] ) ); - } - - matched = false; - - // Combinators - if ( ( match = rleadingCombinator.exec( soFar ) ) ) { - matched = match.shift(); - tokens.push( { - value: matched, - - // Cast descendant combinators to space - type: match[ 0 ].replace( rtrimCSS, " " ) - } ); - soFar = soFar.slice( matched.length ); - } - - // Filters - for ( type in Expr.filter ) { - if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] || - ( match = preFilters[ type ]( match ) ) ) ) { - matched = match.shift(); - tokens.push( { - value: matched, - type: type, - matches: match - } ); - soFar = soFar.slice( matched.length ); - } - } - - if ( !matched ) { - break; - } - } - - // Return the length of the invalid excess - // if we're just parsing - // Otherwise, throw an error or return tokens - if ( parseOnly ) { - return soFar.length; - } - - return soFar ? - find.error( selector ) : - - // Cache the tokens - tokenCache( selector, groups ).slice( 0 ); -} - -function toSelector( tokens ) { - var i = 0, - len = tokens.length, - selector = ""; - for ( ; i < len; i++ ) { - selector += tokens[ i ].value; - } - return selector; -} - -function addCombinator( matcher, combinator, base ) { - var dir = combinator.dir, - skip = combinator.next, - key = skip || dir, - checkNonElements = base && key === "parentNode", - doneName = done++; - - return combinator.first ? - - // Check against closest ancestor/preceding element - function( elem, context, xml ) { - while ( ( elem = elem[ dir ] ) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - return matcher( elem, context, xml ); - } - } - return false; - } : - - // Check against all ancestor/preceding elements - function( elem, context, xml ) { - var oldCache, outerCache, - newCache = [ dirruns, doneName ]; - - // We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching - if ( xml ) { - while ( ( elem = elem[ dir ] ) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - if ( matcher( elem, context, xml ) ) { - return true; - } - } - } - } else { - while ( ( elem = elem[ dir ] ) ) { - if ( elem.nodeType === 1 || checkNonElements ) { - outerCache = elem[ expando ] || ( elem[ expando ] = {} ); - - if ( skip && nodeName( elem, skip ) ) { - elem = elem[ dir ] || elem; - } else if ( ( oldCache = outerCache[ key ] ) && - oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) { - - // Assign to newCache so results back-propagate to previous elements - return ( newCache[ 2 ] = oldCache[ 2 ] ); - } else { - - // Reuse newcache so results back-propagate to previous elements - outerCache[ key ] = newCache; - - // A match means we're done; a fail means we have to keep checking - if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) { - return true; - } - } - } - } - } - return false; - }; -} - -function elementMatcher( matchers ) { - return matchers.length > 1 ? - function( elem, context, xml ) { - var i = matchers.length; - while ( i-- ) { - if ( !matchers[ i ]( elem, context, xml ) ) { - return false; - } - } - return true; - } : - matchers[ 0 ]; -} - -function multipleContexts( selector, contexts, results ) { - var i = 0, - len = contexts.length; - for ( ; i < len; i++ ) { - find( selector, contexts[ i ], results ); - } - return results; -} - -function condense( unmatched, map, filter, context, xml ) { - var elem, - newUnmatched = [], - i = 0, - len = unmatched.length, - mapped = map != null; - - for ( ; i < len; i++ ) { - if ( ( elem = unmatched[ i ] ) ) { - if ( !filter || filter( elem, context, xml ) ) { - newUnmatched.push( elem ); - if ( mapped ) { - map.push( i ); - } - } - } - } - - return newUnmatched; -} - -function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) { - if ( postFilter && !postFilter[ expando ] ) { - postFilter = setMatcher( postFilter ); - } - if ( postFinder && !postFinder[ expando ] ) { - postFinder = setMatcher( postFinder, postSelector ); - } - return markFunction( function( seed, results, context, xml ) { - var temp, i, elem, matcherOut, - preMap = [], - postMap = [], - preexisting = results.length, - - // Get initial elements from seed or context - elems = seed || - multipleContexts( selector || "*", - context.nodeType ? [ context ] : context, [] ), - - // Prefilter to get matcher input, preserving a map for seed-results synchronization - matcherIn = preFilter && ( seed || !selector ) ? - condense( elems, preMap, preFilter, context, xml ) : - elems; - - if ( matcher ) { - - // If we have a postFinder, or filtered seed, or non-seed postFilter - // or preexisting results, - matcherOut = postFinder || ( seed ? preFilter : preexisting || postFilter ) ? - - // ...intermediate processing is necessary - [] : - - // ...otherwise use results directly - results; - - // Find primary matches - matcher( matcherIn, matcherOut, context, xml ); - } else { - matcherOut = matcherIn; - } - - // Apply postFilter - if ( postFilter ) { - temp = condense( matcherOut, postMap ); - postFilter( temp, [], context, xml ); - - // Un-match failing elements by moving them back to matcherIn - i = temp.length; - while ( i-- ) { - if ( ( elem = temp[ i ] ) ) { - matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem ); - } - } - } - - if ( seed ) { - if ( postFinder || preFilter ) { - if ( postFinder ) { - - // Get the final matcherOut by condensing this intermediate into postFinder contexts - temp = []; - i = matcherOut.length; - while ( i-- ) { - if ( ( elem = matcherOut[ i ] ) ) { - - // Restore matcherIn since elem is not yet a final match - temp.push( ( matcherIn[ i ] = elem ) ); - } - } - postFinder( null, ( matcherOut = [] ), temp, xml ); - } - - // Move matched elements from seed to results to keep them synchronized - i = matcherOut.length; - while ( i-- ) { - if ( ( elem = matcherOut[ i ] ) && - ( temp = postFinder ? indexOf.call( seed, elem ) : preMap[ i ] ) > -1 ) { - - seed[ temp ] = !( results[ temp ] = elem ); - } - } - } - - // Add elements to results, through postFinder if defined - } else { - matcherOut = condense( - matcherOut === results ? - matcherOut.splice( preexisting, matcherOut.length ) : - matcherOut - ); - if ( postFinder ) { - postFinder( null, results, matcherOut, xml ); - } else { - push.apply( results, matcherOut ); - } - } - } ); -} - -function matcherFromTokens( tokens ) { - var checkContext, matcher, j, - len = tokens.length, - leadingRelative = Expr.relative[ tokens[ 0 ].type ], - implicitRelative = leadingRelative || Expr.relative[ " " ], - i = leadingRelative ? 1 : 0, - - // The foundational matcher ensures that elements are reachable from top-level context(s) - matchContext = addCombinator( function( elem ) { - return elem === checkContext; - }, implicitRelative, true ), - matchAnyContext = addCombinator( function( elem ) { - return indexOf.call( checkContext, elem ) > -1; - }, implicitRelative, true ), - matchers = [ function( elem, context, xml ) { - - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - var ret = ( !leadingRelative && ( xml || context != outermostContext ) ) || ( - ( checkContext = context ).nodeType ? - matchContext( elem, context, xml ) : - matchAnyContext( elem, context, xml ) ); - - // Avoid hanging onto element - // (see https://github.com/jquery/sizzle/issues/299) - checkContext = null; - return ret; - } ]; - - for ( ; i < len; i++ ) { - if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) { - matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ]; - } else { - matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches ); - - // Return special upon seeing a positional matcher - if ( matcher[ expando ] ) { - - // Find the next relative operator (if any) for proper handling - j = ++i; - for ( ; j < len; j++ ) { - if ( Expr.relative[ tokens[ j ].type ] ) { - break; - } - } - return setMatcher( - i > 1 && elementMatcher( matchers ), - i > 1 && toSelector( - - // If the preceding token was a descendant combinator, insert an implicit any-element `*` - tokens.slice( 0, i - 1 ) - .concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } ) - ).replace( rtrimCSS, "$1" ), - matcher, - i < j && matcherFromTokens( tokens.slice( i, j ) ), - j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ), - j < len && toSelector( tokens ) - ); - } - matchers.push( matcher ); - } - } - - return elementMatcher( matchers ); -} - -function matcherFromGroupMatchers( elementMatchers, setMatchers ) { - var bySet = setMatchers.length > 0, - byElement = elementMatchers.length > 0, - superMatcher = function( seed, context, xml, results, outermost ) { - var elem, j, matcher, - matchedCount = 0, - i = "0", - unmatched = seed && [], - setMatched = [], - contextBackup = outermostContext, - - // We must always have either seed elements or outermost context - elems = seed || byElement && Expr.find.TAG( "*", outermost ), - - // Use integer dirruns iff this is the outermost matcher - dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ), - len = elems.length; - - if ( outermost ) { - - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - outermostContext = context == document || context || outermost; - } - - // Add elements passing elementMatchers directly to results - // Support: iOS <=7 - 9 only - // Tolerate NodeList properties (IE: "length"; Safari: ) matching - // elements by id. (see trac-14142) - for ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) { - if ( byElement && elem ) { - j = 0; - - // Support: IE 11+, Edge 17 - 18+ - // IE/Edge sometimes throw a "Permission denied" error when strict-comparing - // two documents; shallow comparisons work. - // eslint-disable-next-line eqeqeq - if ( !context && elem.ownerDocument != document ) { - setDocument( elem ); - xml = !documentIsHTML; - } - while ( ( matcher = elementMatchers[ j++ ] ) ) { - if ( matcher( elem, context || document, xml ) ) { - push.call( results, elem ); - break; - } - } - if ( outermost ) { - dirruns = dirrunsUnique; - } - } - - // Track unmatched elements for set filters - if ( bySet ) { - - // They will have gone through all possible matchers - if ( ( elem = !matcher && elem ) ) { - matchedCount--; - } - - // Lengthen the array for every element, matched or not - if ( seed ) { - unmatched.push( elem ); - } - } - } - - // `i` is now the count of elements visited above, and adding it to `matchedCount` - // makes the latter nonnegative. - matchedCount += i; - - // Apply set filters to unmatched elements - // NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount` - // equals `i`), unless we didn't visit _any_ elements in the above loop because we have - // no element matchers and no seed. - // Incrementing an initially-string "0" `i` allows `i` to remain a string only in that - // case, which will result in a "00" `matchedCount` that differs from `i` but is also - // numerically zero. - if ( bySet && i !== matchedCount ) { - j = 0; - while ( ( matcher = setMatchers[ j++ ] ) ) { - matcher( unmatched, setMatched, context, xml ); - } - - if ( seed ) { - - // Reintegrate element matches to eliminate the need for sorting - if ( matchedCount > 0 ) { - while ( i-- ) { - if ( !( unmatched[ i ] || setMatched[ i ] ) ) { - setMatched[ i ] = pop.call( results ); - } - } - } - - // Discard index placeholder values to get only actual matches - setMatched = condense( setMatched ); - } - - // Add matches to results - push.apply( results, setMatched ); - - // Seedless set matches succeeding multiple successful matchers stipulate sorting - if ( outermost && !seed && setMatched.length > 0 && - ( matchedCount + setMatchers.length ) > 1 ) { - - jQuery.uniqueSort( results ); - } - } - - // Override manipulation of globals by nested matchers - if ( outermost ) { - dirruns = dirrunsUnique; - outermostContext = contextBackup; - } - - return unmatched; - }; - - return bySet ? - markFunction( superMatcher ) : - superMatcher; -} - -function compile( selector, match /* Internal Use Only */ ) { - var i, - setMatchers = [], - elementMatchers = [], - cached = compilerCache[ selector + " " ]; - - if ( !cached ) { - - // Generate a function of recursive functions that can be used to check each element - if ( !match ) { - match = tokenize( selector ); - } - i = match.length; - while ( i-- ) { - cached = matcherFromTokens( match[ i ] ); - if ( cached[ expando ] ) { - setMatchers.push( cached ); - } else { - elementMatchers.push( cached ); - } - } - - // Cache the compiled function - cached = compilerCache( selector, - matcherFromGroupMatchers( elementMatchers, setMatchers ) ); - - // Save selector and tokenization - cached.selector = selector; - } - return cached; -} - -/** - * A low-level selection function that works with jQuery's compiled - * selector functions - * @param {String|Function} selector A selector or a pre-compiled - * selector function built with jQuery selector compile - * @param {Element} context - * @param {Array} [results] - * @param {Array} [seed] A set of elements to match against - */ -function select( selector, context, results, seed ) { - var i, tokens, token, type, find, - compiled = typeof selector === "function" && selector, - match = !seed && tokenize( ( selector = compiled.selector || selector ) ); - - results = results || []; - - // Try to minimize operations if there is only one selector in the list and no seed - // (the latter of which guarantees us context) - if ( match.length === 1 ) { - - // Reduce context if the leading compound selector is an ID - tokens = match[ 0 ] = match[ 0 ].slice( 0 ); - if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" && - context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) { - - context = ( Expr.find.ID( - token.matches[ 0 ].replace( runescape, funescape ), - context - ) || [] )[ 0 ]; - if ( !context ) { - return results; - - // Precompiled matchers will still verify ancestry, so step up a level - } else if ( compiled ) { - context = context.parentNode; - } - - selector = selector.slice( tokens.shift().value.length ); - } - - // Fetch a seed set for right-to-left matching - i = matchExpr.needsContext.test( selector ) ? 0 : tokens.length; - while ( i-- ) { - token = tokens[ i ]; - - // Abort if we hit a combinator - if ( Expr.relative[ ( type = token.type ) ] ) { - break; - } - if ( ( find = Expr.find[ type ] ) ) { - - // Search, expanding context for leading sibling combinators - if ( ( seed = find( - token.matches[ 0 ].replace( runescape, funescape ), - rsibling.test( tokens[ 0 ].type ) && - testContext( context.parentNode ) || context - ) ) ) { - - // If seed is empty or no tokens remain, we can return early - tokens.splice( i, 1 ); - selector = seed.length && toSelector( tokens ); - if ( !selector ) { - push.apply( results, seed ); - return results; - } - - break; - } - } - } - } - - // Compile and execute a filtering function if one is not provided - // Provide `match` to avoid retokenization if we modified the selector above - ( compiled || compile( selector, match ) )( - seed, - context, - !documentIsHTML, - results, - !context || rsibling.test( selector ) && testContext( context.parentNode ) || context - ); - return results; -} - -// One-time assignments - -// Support: Android <=4.0 - 4.1+ -// Sort stability -support.sortStable = expando.split( "" ).sort( sortOrder ).join( "" ) === expando; - -// Initialize against the default document -setDocument(); - -// Support: Android <=4.0 - 4.1+ -// Detached nodes confoundingly follow *each other* -support.sortDetached = assert( function( el ) { - - // Should return 1, but returns 4 (following) - return el.compareDocumentPosition( document.createElement( "fieldset" ) ) & 1; -} ); - -jQuery.find = find; - -// Deprecated -jQuery.expr[ ":" ] = jQuery.expr.pseudos; -jQuery.unique = jQuery.uniqueSort; - -// These have always been private, but they used to be documented as part of -// Sizzle so let's maintain them for now for backwards compatibility purposes. -find.compile = compile; -find.select = select; -find.setDocument = setDocument; -find.tokenize = tokenize; - -find.escape = jQuery.escapeSelector; -find.getText = jQuery.text; -find.isXML = jQuery.isXMLDoc; -find.selectors = jQuery.expr; -find.support = jQuery.support; -find.uniqueSort = jQuery.uniqueSort; - - /* eslint-enable */ - -} )(); - - -var dir = function( elem, dir, until ) { - var matched = [], - truncate = until !== undefined; - - while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) { - if ( elem.nodeType === 1 ) { - if ( truncate && jQuery( elem ).is( until ) ) { - break; - } - matched.push( elem ); - } - } - return matched; -}; - - -var siblings = function( n, elem ) { - var matched = []; - - for ( ; n; n = n.nextSibling ) { - if ( n.nodeType === 1 && n !== elem ) { - matched.push( n ); - } - } - - return matched; -}; - - -var rneedsContext = jQuery.expr.match.needsContext; - -var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i ); - - - -// Implement the identical functionality for filter and not -function winnow( elements, qualifier, not ) { - if ( isFunction( qualifier ) ) { - return jQuery.grep( elements, function( elem, i ) { - return !!qualifier.call( elem, i, elem ) !== not; - } ); - } - - // Single element - if ( qualifier.nodeType ) { - return jQuery.grep( elements, function( elem ) { - return ( elem === qualifier ) !== not; - } ); - } - - // Arraylike of elements (jQuery, arguments, Array) - if ( typeof qualifier !== "string" ) { - return jQuery.grep( elements, function( elem ) { - return ( indexOf.call( qualifier, elem ) > -1 ) !== not; - } ); - } - - // Filtered directly for both simple and complex selectors - return jQuery.filter( qualifier, elements, not ); -} - -jQuery.filter = function( expr, elems, not ) { - var elem = elems[ 0 ]; - - if ( not ) { - expr = ":not(" + expr + ")"; - } - - if ( elems.length === 1 && elem.nodeType === 1 ) { - return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : []; - } - - return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) { - return elem.nodeType === 1; - } ) ); -}; - -jQuery.fn.extend( { - find: function( selector ) { - var i, ret, - len = this.length, - self = this; - - if ( typeof selector !== "string" ) { - return this.pushStack( jQuery( selector ).filter( function() { - for ( i = 0; i < len; i++ ) { - if ( jQuery.contains( self[ i ], this ) ) { - return true; - } - } - } ) ); - } - - ret = this.pushStack( [] ); - - for ( i = 0; i < len; i++ ) { - jQuery.find( selector, self[ i ], ret ); - } - - return len > 1 ? jQuery.uniqueSort( ret ) : ret; - }, - filter: function( selector ) { - return this.pushStack( winnow( this, selector || [], false ) ); - }, - not: function( selector ) { - return this.pushStack( winnow( this, selector || [], true ) ); - }, - is: function( selector ) { - return !!winnow( - this, - - // If this is a positional/relative selector, check membership in the returned set - // so $("p:first").is("p:last") won't return true for a doc with two "p". - typeof selector === "string" && rneedsContext.test( selector ) ? - jQuery( selector ) : - selector || [], - false - ).length; - } -} ); - - -// Initialize a jQuery object - - -// A central reference to the root jQuery(document) -var rootjQuery, - - // A simple way to check for HTML strings - // Prioritize #id over to avoid XSS via location.hash (trac-9521) - // Strict HTML recognition (trac-11290: must start with <) - // Shortcut simple #id case for speed - rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/, - - init = jQuery.fn.init = function( selector, context, root ) { - var match, elem; - - // HANDLE: $(""), $(null), $(undefined), $(false) - if ( !selector ) { - return this; - } - - // Method init() accepts an alternate rootjQuery - // so migrate can support jQuery.sub (gh-2101) - root = root || rootjQuery; - - // Handle HTML strings - if ( typeof selector === "string" ) { - if ( selector[ 0 ] === "<" && - selector[ selector.length - 1 ] === ">" && - selector.length >= 3 ) { - - // Assume that strings that start and end with <> are HTML and skip the regex check - match = [ null, selector, null ]; - - } else { - match = rquickExpr.exec( selector ); - } - - // Match html or make sure no context is specified for #id - if ( match && ( match[ 1 ] || !context ) ) { - - // HANDLE: $(html) -> $(array) - if ( match[ 1 ] ) { - context = context instanceof jQuery ? context[ 0 ] : context; - - // Option to run scripts is true for back-compat - // Intentionally let the error be thrown if parseHTML is not present - jQuery.merge( this, jQuery.parseHTML( - match[ 1 ], - context && context.nodeType ? context.ownerDocument || context : document, - true - ) ); - - // HANDLE: $(html, props) - if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) { - for ( match in context ) { - - // Properties of context are called as methods if possible - if ( isFunction( this[ match ] ) ) { - this[ match ]( context[ match ] ); - - // ...and otherwise set as attributes - } else { - this.attr( match, context[ match ] ); - } - } - } - - return this; - - // HANDLE: $(#id) - } else { - elem = document.getElementById( match[ 2 ] ); - - if ( elem ) { - - // Inject the element directly into the jQuery object - this[ 0 ] = elem; - this.length = 1; - } - return this; - } - - // HANDLE: $(expr, $(...)) - } else if ( !context || context.jquery ) { - return ( context || root ).find( selector ); - - // HANDLE: $(expr, context) - // (which is just equivalent to: $(context).find(expr) - } else { - return this.constructor( context ).find( selector ); - } - - // HANDLE: $(DOMElement) - } else if ( selector.nodeType ) { - this[ 0 ] = selector; - this.length = 1; - return this; - - // HANDLE: $(function) - // Shortcut for document ready - } else if ( isFunction( selector ) ) { - return root.ready !== undefined ? - root.ready( selector ) : - - // Execute immediately if ready is not present - selector( jQuery ); - } - - return jQuery.makeArray( selector, this ); - }; - -// Give the init function the jQuery prototype for later instantiation -init.prototype = jQuery.fn; - -// Initialize central reference -rootjQuery = jQuery( document ); - - -var rparentsprev = /^(?:parents|prev(?:Until|All))/, - - // Methods guaranteed to produce a unique set when starting from a unique set - guaranteedUnique = { - children: true, - contents: true, - next: true, - prev: true - }; - -jQuery.fn.extend( { - has: function( target ) { - var targets = jQuery( target, this ), - l = targets.length; - - return this.filter( function() { - var i = 0; - for ( ; i < l; i++ ) { - if ( jQuery.contains( this, targets[ i ] ) ) { - return true; - } - } - } ); - }, - - closest: function( selectors, context ) { - var cur, - i = 0, - l = this.length, - matched = [], - targets = typeof selectors !== "string" && jQuery( selectors ); - - // Positional selectors never match, since there's no _selection_ context - if ( !rneedsContext.test( selectors ) ) { - for ( ; i < l; i++ ) { - for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) { - - // Always skip document fragments - if ( cur.nodeType < 11 && ( targets ? - targets.index( cur ) > -1 : - - // Don't pass non-elements to jQuery#find - cur.nodeType === 1 && - jQuery.find.matchesSelector( cur, selectors ) ) ) { - - matched.push( cur ); - break; - } - } - } - } - - return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched ); - }, - - // Determine the position of an element within the set - index: function( elem ) { - - // No argument, return index in parent - if ( !elem ) { - return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1; - } - - // Index in selector - if ( typeof elem === "string" ) { - return indexOf.call( jQuery( elem ), this[ 0 ] ); - } - - // Locate the position of the desired element - return indexOf.call( this, - - // If it receives a jQuery object, the first element is used - elem.jquery ? elem[ 0 ] : elem - ); - }, - - add: function( selector, context ) { - return this.pushStack( - jQuery.uniqueSort( - jQuery.merge( this.get(), jQuery( selector, context ) ) - ) - ); - }, - - addBack: function( selector ) { - return this.add( selector == null ? - this.prevObject : this.prevObject.filter( selector ) - ); - } -} ); - -function sibling( cur, dir ) { - while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {} - return cur; -} - -jQuery.each( { - parent: function( elem ) { - var parent = elem.parentNode; - return parent && parent.nodeType !== 11 ? parent : null; - }, - parents: function( elem ) { - return dir( elem, "parentNode" ); - }, - parentsUntil: function( elem, _i, until ) { - return dir( elem, "parentNode", until ); - }, - next: function( elem ) { - return sibling( elem, "nextSibling" ); - }, - prev: function( elem ) { - return sibling( elem, "previousSibling" ); - }, - nextAll: function( elem ) { - return dir( elem, "nextSibling" ); - }, - prevAll: function( elem ) { - return dir( elem, "previousSibling" ); - }, - nextUntil: function( elem, _i, until ) { - return dir( elem, "nextSibling", until ); - }, - prevUntil: function( elem, _i, until ) { - return dir( elem, "previousSibling", until ); - }, - siblings: function( elem ) { - return siblings( ( elem.parentNode || {} ).firstChild, elem ); - }, - children: function( elem ) { - return siblings( elem.firstChild ); - }, - contents: function( elem ) { - if ( elem.contentDocument != null && - - // Support: IE 11+ - // elements with no `data` attribute has an object - // `contentDocument` with a `null` prototype. - getProto( elem.contentDocument ) ) { - - return elem.contentDocument; - } - - // Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only - // Treat the template element as a regular one in browsers that - // don't support it. - if ( nodeName( elem, "template" ) ) { - elem = elem.content || elem; - } - - return jQuery.merge( [], elem.childNodes ); - } -}, function( name, fn ) { - jQuery.fn[ name ] = function( until, selector ) { - var matched = jQuery.map( this, fn, until ); - - if ( name.slice( -5 ) !== "Until" ) { - selector = until; - } - - if ( selector && typeof selector === "string" ) { - matched = jQuery.filter( selector, matched ); - } - - if ( this.length > 1 ) { - - // Remove duplicates - if ( !guaranteedUnique[ name ] ) { - jQuery.uniqueSort( matched ); - } - - // Reverse order for parents* and prev-derivatives - if ( rparentsprev.test( name ) ) { - matched.reverse(); - } - } - - return this.pushStack( matched ); - }; -} ); -var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g ); - - - -// Convert String-formatted options into Object-formatted ones -function createOptions( options ) { - var object = {}; - jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) { - object[ flag ] = true; - } ); - return object; -} - -/* - * Create a callback list using the following parameters: - * - * options: an optional list of space-separated options that will change how - * the callback list behaves or a more traditional option object - * - * By default a callback list will act like an event callback list and can be - * "fired" multiple times. - * - * Possible options: - * - * once: will ensure the callback list can only be fired once (like a Deferred) - * - * memory: will keep track of previous values and will call any callback added - * after the list has been fired right away with the latest "memorized" - * values (like a Deferred) - * - * unique: will ensure a callback can only be added once (no duplicate in the list) - * - * stopOnFalse: interrupt callings when a callback returns false - * - */ -jQuery.Callbacks = function( options ) { - - // Convert options from String-formatted to Object-formatted if needed - // (we check in cache first) - options = typeof options === "string" ? - createOptions( options ) : - jQuery.extend( {}, options ); - - var // Flag to know if list is currently firing - firing, - - // Last fire value for non-forgettable lists - memory, - - // Flag to know if list was already fired - fired, - - // Flag to prevent firing - locked, - - // Actual callback list - list = [], - - // Queue of execution data for repeatable lists - queue = [], - - // Index of currently firing callback (modified by add/remove as needed) - firingIndex = -1, - - // Fire callbacks - fire = function() { - - // Enforce single-firing - locked = locked || options.once; - - // Execute callbacks for all pending executions, - // respecting firingIndex overrides and runtime changes - fired = firing = true; - for ( ; queue.length; firingIndex = -1 ) { - memory = queue.shift(); - while ( ++firingIndex < list.length ) { - - // Run callback and check for early termination - if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false && - options.stopOnFalse ) { - - // Jump to end and forget the data so .add doesn't re-fire - firingIndex = list.length; - memory = false; - } - } - } - - // Forget the data if we're done with it - if ( !options.memory ) { - memory = false; - } - - firing = false; - - // Clean up if we're done firing for good - if ( locked ) { - - // Keep an empty list if we have data for future add calls - if ( memory ) { - list = []; - - // Otherwise, this object is spent - } else { - list = ""; - } - } - }, - - // Actual Callbacks object - self = { - - // Add a callback or a collection of callbacks to the list - add: function() { - if ( list ) { - - // If we have memory from a past run, we should fire after adding - if ( memory && !firing ) { - firingIndex = list.length - 1; - queue.push( memory ); - } - - ( function add( args ) { - jQuery.each( args, function( _, arg ) { - if ( isFunction( arg ) ) { - if ( !options.unique || !self.has( arg ) ) { - list.push( arg ); - } - } else if ( arg && arg.length && toType( arg ) !== "string" ) { - - // Inspect recursively - add( arg ); - } - } ); - } )( arguments ); - - if ( memory && !firing ) { - fire(); - } - } - return this; - }, - - // Remove a callback from the list - remove: function() { - jQuery.each( arguments, function( _, arg ) { - var index; - while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) { - list.splice( index, 1 ); - - // Handle firing indexes - if ( index <= firingIndex ) { - firingIndex--; - } - } - } ); - return this; - }, - - // Check if a given callback is in the list. - // If no argument is given, return whether or not list has callbacks attached. - has: function( fn ) { - return fn ? - jQuery.inArray( fn, list ) > -1 : - list.length > 0; - }, - - // Remove all callbacks from the list - empty: function() { - if ( list ) { - list = []; - } - return this; - }, - - // Disable .fire and .add - // Abort any current/pending executions - // Clear all callbacks and values - disable: function() { - locked = queue = []; - list = memory = ""; - return this; - }, - disabled: function() { - return !list; - }, - - // Disable .fire - // Also disable .add unless we have memory (since it would have no effect) - // Abort any pending executions - lock: function() { - locked = queue = []; - if ( !memory && !firing ) { - list = memory = ""; - } - return this; - }, - locked: function() { - return !!locked; - }, - - // Call all callbacks with the given context and arguments - fireWith: function( context, args ) { - if ( !locked ) { - args = args || []; - args = [ context, args.slice ? args.slice() : args ]; - queue.push( args ); - if ( !firing ) { - fire(); - } - } - return this; - }, - - // Call all the callbacks with the given arguments - fire: function() { - self.fireWith( this, arguments ); - return this; - }, - - // To know if the callbacks have already been called at least once - fired: function() { - return !!fired; - } - }; - - return self; -}; - - -function Identity( v ) { - return v; -} -function Thrower( ex ) { - throw ex; -} - -function adoptValue( value, resolve, reject, noValue ) { - var method; - - try { - - // Check for promise aspect first to privilege synchronous behavior - if ( value && isFunction( ( method = value.promise ) ) ) { - method.call( value ).done( resolve ).fail( reject ); - - // Other thenables - } else if ( value && isFunction( ( method = value.then ) ) ) { - method.call( value, resolve, reject ); - - // Other non-thenables - } else { - - // Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer: - // * false: [ value ].slice( 0 ) => resolve( value ) - // * true: [ value ].slice( 1 ) => resolve() - resolve.apply( undefined, [ value ].slice( noValue ) ); - } - - // For Promises/A+, convert exceptions into rejections - // Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in - // Deferred#then to conditionally suppress rejection. - } catch ( value ) { - - // Support: Android 4.0 only - // Strict mode functions invoked without .call/.apply get global-object context - reject.apply( undefined, [ value ] ); - } -} - -jQuery.extend( { - - Deferred: function( func ) { - var tuples = [ - - // action, add listener, callbacks, - // ... .then handlers, argument index, [final state] - [ "notify", "progress", jQuery.Callbacks( "memory" ), - jQuery.Callbacks( "memory" ), 2 ], - [ "resolve", "done", jQuery.Callbacks( "once memory" ), - jQuery.Callbacks( "once memory" ), 0, "resolved" ], - [ "reject", "fail", jQuery.Callbacks( "once memory" ), - jQuery.Callbacks( "once memory" ), 1, "rejected" ] - ], - state = "pending", - promise = { - state: function() { - return state; - }, - always: function() { - deferred.done( arguments ).fail( arguments ); - return this; - }, - "catch": function( fn ) { - return promise.then( null, fn ); - }, - - // Keep pipe for back-compat - pipe: function( /* fnDone, fnFail, fnProgress */ ) { - var fns = arguments; - - return jQuery.Deferred( function( newDefer ) { - jQuery.each( tuples, function( _i, tuple ) { - - // Map tuples (progress, done, fail) to arguments (done, fail, progress) - var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ]; - - // deferred.progress(function() { bind to newDefer or newDefer.notify }) - // deferred.done(function() { bind to newDefer or newDefer.resolve }) - // deferred.fail(function() { bind to newDefer or newDefer.reject }) - deferred[ tuple[ 1 ] ]( function() { - var returned = fn && fn.apply( this, arguments ); - if ( returned && isFunction( returned.promise ) ) { - returned.promise() - .progress( newDefer.notify ) - .done( newDefer.resolve ) - .fail( newDefer.reject ); - } else { - newDefer[ tuple[ 0 ] + "With" ]( - this, - fn ? [ returned ] : arguments - ); - } - } ); - } ); - fns = null; - } ).promise(); - }, - then: function( onFulfilled, onRejected, onProgress ) { - var maxDepth = 0; - function resolve( depth, deferred, handler, special ) { - return function() { - var that = this, - args = arguments, - mightThrow = function() { - var returned, then; - - // Support: Promises/A+ section 2.3.3.3.3 - // https://promisesaplus.com/#point-59 - // Ignore double-resolution attempts - if ( depth < maxDepth ) { - return; - } - - returned = handler.apply( that, args ); - - // Support: Promises/A+ section 2.3.1 - // https://promisesaplus.com/#point-48 - if ( returned === deferred.promise() ) { - throw new TypeError( "Thenable self-resolution" ); - } - - // Support: Promises/A+ sections 2.3.3.1, 3.5 - // https://promisesaplus.com/#point-54 - // https://promisesaplus.com/#point-75 - // Retrieve `then` only once - then = returned && - - // Support: Promises/A+ section 2.3.4 - // https://promisesaplus.com/#point-64 - // Only check objects and functions for thenability - ( typeof returned === "object" || - typeof returned === "function" ) && - returned.then; - - // Handle a returned thenable - if ( isFunction( then ) ) { - - // Special processors (notify) just wait for resolution - if ( special ) { - then.call( - returned, - resolve( maxDepth, deferred, Identity, special ), - resolve( maxDepth, deferred, Thrower, special ) - ); - - // Normal processors (resolve) also hook into progress - } else { - - // ...and disregard older resolution values - maxDepth++; - - then.call( - returned, - resolve( maxDepth, deferred, Identity, special ), - resolve( maxDepth, deferred, Thrower, special ), - resolve( maxDepth, deferred, Identity, - deferred.notifyWith ) - ); - } - - // Handle all other returned values - } else { - - // Only substitute handlers pass on context - // and multiple values (non-spec behavior) - if ( handler !== Identity ) { - that = undefined; - args = [ returned ]; - } - - // Process the value(s) - // Default process is resolve - ( special || deferred.resolveWith )( that, args ); - } - }, - - // Only normal processors (resolve) catch and reject exceptions - process = special ? - mightThrow : - function() { - try { - mightThrow(); - } catch ( e ) { - - if ( jQuery.Deferred.exceptionHook ) { - jQuery.Deferred.exceptionHook( e, - process.error ); - } - - // Support: Promises/A+ section 2.3.3.3.4.1 - // https://promisesaplus.com/#point-61 - // Ignore post-resolution exceptions - if ( depth + 1 >= maxDepth ) { - - // Only substitute handlers pass on context - // and multiple values (non-spec behavior) - if ( handler !== Thrower ) { - that = undefined; - args = [ e ]; - } - - deferred.rejectWith( that, args ); - } - } - }; - - // Support: Promises/A+ section 2.3.3.3.1 - // https://promisesaplus.com/#point-57 - // Re-resolve promises immediately to dodge false rejection from - // subsequent errors - if ( depth ) { - process(); - } else { - - // Call an optional hook to record the error, in case of exception - // since it's otherwise lost when execution goes async - if ( jQuery.Deferred.getErrorHook ) { - process.error = jQuery.Deferred.getErrorHook(); - - // The deprecated alias of the above. While the name suggests - // returning the stack, not an error instance, jQuery just passes - // it directly to `console.warn` so both will work; an instance - // just better cooperates with source maps. - } else if ( jQuery.Deferred.getStackHook ) { - process.error = jQuery.Deferred.getStackHook(); - } - window.setTimeout( process ); - } - }; - } - - return jQuery.Deferred( function( newDefer ) { - - // progress_handlers.add( ... ) - tuples[ 0 ][ 3 ].add( - resolve( - 0, - newDefer, - isFunction( onProgress ) ? - onProgress : - Identity, - newDefer.notifyWith - ) - ); - - // fulfilled_handlers.add( ... ) - tuples[ 1 ][ 3 ].add( - resolve( - 0, - newDefer, - isFunction( onFulfilled ) ? - onFulfilled : - Identity - ) - ); - - // rejected_handlers.add( ... ) - tuples[ 2 ][ 3 ].add( - resolve( - 0, - newDefer, - isFunction( onRejected ) ? - onRejected : - Thrower - ) - ); - } ).promise(); - }, - - // Get a promise for this deferred - // If obj is provided, the promise aspect is added to the object - promise: function( obj ) { - return obj != null ? jQuery.extend( obj, promise ) : promise; - } - }, - deferred = {}; - - // Add list-specific methods - jQuery.each( tuples, function( i, tuple ) { - var list = tuple[ 2 ], - stateString = tuple[ 5 ]; - - // promise.progress = list.add - // promise.done = list.add - // promise.fail = list.add - promise[ tuple[ 1 ] ] = list.add; - - // Handle state - if ( stateString ) { - list.add( - function() { - - // state = "resolved" (i.e., fulfilled) - // state = "rejected" - state = stateString; - }, - - // rejected_callbacks.disable - // fulfilled_callbacks.disable - tuples[ 3 - i ][ 2 ].disable, - - // rejected_handlers.disable - // fulfilled_handlers.disable - tuples[ 3 - i ][ 3 ].disable, - - // progress_callbacks.lock - tuples[ 0 ][ 2 ].lock, - - // progress_handlers.lock - tuples[ 0 ][ 3 ].lock - ); - } - - // progress_handlers.fire - // fulfilled_handlers.fire - // rejected_handlers.fire - list.add( tuple[ 3 ].fire ); - - // deferred.notify = function() { deferred.notifyWith(...) } - // deferred.resolve = function() { deferred.resolveWith(...) } - // deferred.reject = function() { deferred.rejectWith(...) } - deferred[ tuple[ 0 ] ] = function() { - deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments ); - return this; - }; - - // deferred.notifyWith = list.fireWith - // deferred.resolveWith = list.fireWith - // deferred.rejectWith = list.fireWith - deferred[ tuple[ 0 ] + "With" ] = list.fireWith; - } ); - - // Make the deferred a promise - promise.promise( deferred ); - - // Call given func if any - if ( func ) { - func.call( deferred, deferred ); - } - - // All done! - return deferred; - }, - - // Deferred helper - when: function( singleValue ) { - var - - // count of uncompleted subordinates - remaining = arguments.length, - - // count of unprocessed arguments - i = remaining, - - // subordinate fulfillment data - resolveContexts = Array( i ), - resolveValues = slice.call( arguments ), - - // the primary Deferred - primary = jQuery.Deferred(), - - // subordinate callback factory - updateFunc = function( i ) { - return function( value ) { - resolveContexts[ i ] = this; - resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value; - if ( !( --remaining ) ) { - primary.resolveWith( resolveContexts, resolveValues ); - } - }; - }; - - // Single- and empty arguments are adopted like Promise.resolve - if ( remaining <= 1 ) { - adoptValue( singleValue, primary.done( updateFunc( i ) ).resolve, primary.reject, - !remaining ); - - // Use .then() to unwrap secondary thenables (cf. gh-3000) - if ( primary.state() === "pending" || - isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) { - - return primary.then(); - } - } - - // Multiple arguments are aggregated like Promise.all array elements - while ( i-- ) { - adoptValue( resolveValues[ i ], updateFunc( i ), primary.reject ); - } - - return primary.promise(); - } -} ); - - -// These usually indicate a programmer mistake during development, -// warn about them ASAP rather than swallowing them by default. -var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/; - -// If `jQuery.Deferred.getErrorHook` is defined, `asyncError` is an error -// captured before the async barrier to get the original error cause -// which may otherwise be hidden. -jQuery.Deferred.exceptionHook = function( error, asyncError ) { - - // Support: IE 8 - 9 only - // Console exists when dev tools are open, which can happen at any time - if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) { - window.console.warn( "jQuery.Deferred exception: " + error.message, - error.stack, asyncError ); - } -}; - - - - -jQuery.readyException = function( error ) { - window.setTimeout( function() { - throw error; - } ); -}; - - - - -// The deferred used on DOM ready -var readyList = jQuery.Deferred(); - -jQuery.fn.ready = function( fn ) { - - readyList - .then( fn ) - - // Wrap jQuery.readyException in a function so that the lookup - // happens at the time of error handling instead of callback - // registration. - .catch( function( error ) { - jQuery.readyException( error ); - } ); - - return this; -}; - -jQuery.extend( { - - // Is the DOM ready to be used? Set to true once it occurs. - isReady: false, - - // A counter to track how many items to wait for before - // the ready event fires. See trac-6781 - readyWait: 1, - - // Handle when the DOM is ready - ready: function( wait ) { - - // Abort if there are pending holds or we're already ready - if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) { - return; - } - - // Remember that the DOM is ready - jQuery.isReady = true; - - // If a normal DOM Ready event fired, decrement, and wait if need be - if ( wait !== true && --jQuery.readyWait > 0 ) { - return; - } - - // If there are functions bound, to execute - readyList.resolveWith( document, [ jQuery ] ); - } -} ); - -jQuery.ready.then = readyList.then; - -// The ready event handler and self cleanup method -function completed() { - document.removeEventListener( "DOMContentLoaded", completed ); - window.removeEventListener( "load", completed ); - jQuery.ready(); -} - -// Catch cases where $(document).ready() is called -// after the browser event has already occurred. -// Support: IE <=9 - 10 only -// Older IE sometimes signals "interactive" too soon -if ( document.readyState === "complete" || - ( document.readyState !== "loading" && !document.documentElement.doScroll ) ) { - - // Handle it asynchronously to allow scripts the opportunity to delay ready - window.setTimeout( jQuery.ready ); - -} else { - - // Use the handy event callback - document.addEventListener( "DOMContentLoaded", completed ); - - // A fallback to window.onload, that will always work - window.addEventListener( "load", completed ); -} - - - - -// Multifunctional method to get and set values of a collection -// The value/s can optionally be executed if it's a function -var access = function( elems, fn, key, value, chainable, emptyGet, raw ) { - var i = 0, - len = elems.length, - bulk = key == null; - - // Sets many values - if ( toType( key ) === "object" ) { - chainable = true; - for ( i in key ) { - access( elems, fn, i, key[ i ], true, emptyGet, raw ); - } - - // Sets one value - } else if ( value !== undefined ) { - chainable = true; - - if ( !isFunction( value ) ) { - raw = true; - } - - if ( bulk ) { - - // Bulk operations run against the entire set - if ( raw ) { - fn.call( elems, value ); - fn = null; - - // ...except when executing function values - } else { - bulk = fn; - fn = function( elem, _key, value ) { - return bulk.call( jQuery( elem ), value ); - }; - } - } - - if ( fn ) { - for ( ; i < len; i++ ) { - fn( - elems[ i ], key, raw ? - value : - value.call( elems[ i ], i, fn( elems[ i ], key ) ) - ); - } - } - } - - if ( chainable ) { - return elems; - } - - // Gets - if ( bulk ) { - return fn.call( elems ); - } - - return len ? fn( elems[ 0 ], key ) : emptyGet; -}; - - -// Matches dashed string for camelizing -var rmsPrefix = /^-ms-/, - rdashAlpha = /-([a-z])/g; - -// Used by camelCase as callback to replace() -function fcamelCase( _all, letter ) { - return letter.toUpperCase(); -} - -// Convert dashed to camelCase; used by the css and data modules -// Support: IE <=9 - 11, Edge 12 - 15 -// Microsoft forgot to hump their vendor prefix (trac-9572) -function camelCase( string ) { - return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); -} -var acceptData = function( owner ) { - - // Accepts only: - // - Node - // - Node.ELEMENT_NODE - // - Node.DOCUMENT_NODE - // - Object - // - Any - return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType ); -}; - - - - -function Data() { - this.expando = jQuery.expando + Data.uid++; -} - -Data.uid = 1; - -Data.prototype = { - - cache: function( owner ) { - - // Check if the owner object already has a cache - var value = owner[ this.expando ]; - - // If not, create one - if ( !value ) { - value = {}; - - // We can accept data for non-element nodes in modern browsers, - // but we should not, see trac-8335. - // Always return an empty object. - if ( acceptData( owner ) ) { - - // If it is a node unlikely to be stringify-ed or looped over - // use plain assignment - if ( owner.nodeType ) { - owner[ this.expando ] = value; - - // Otherwise secure it in a non-enumerable property - // configurable must be true to allow the property to be - // deleted when data is removed - } else { - Object.defineProperty( owner, this.expando, { - value: value, - configurable: true - } ); - } - } - } - - return value; - }, - set: function( owner, data, value ) { - var prop, - cache = this.cache( owner ); - - // Handle: [ owner, key, value ] args - // Always use camelCase key (gh-2257) - if ( typeof data === "string" ) { - cache[ camelCase( data ) ] = value; - - // Handle: [ owner, { properties } ] args - } else { - - // Copy the properties one-by-one to the cache object - for ( prop in data ) { - cache[ camelCase( prop ) ] = data[ prop ]; - } - } - return cache; - }, - get: function( owner, key ) { - return key === undefined ? - this.cache( owner ) : - - // Always use camelCase key (gh-2257) - owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ]; - }, - access: function( owner, key, value ) { - - // In cases where either: - // - // 1. No key was specified - // 2. A string key was specified, but no value provided - // - // Take the "read" path and allow the get method to determine - // which value to return, respectively either: - // - // 1. The entire cache object - // 2. The data stored at the key - // - if ( key === undefined || - ( ( key && typeof key === "string" ) && value === undefined ) ) { - - return this.get( owner, key ); - } - - // When the key is not a string, or both a key and value - // are specified, set or extend (existing objects) with either: - // - // 1. An object of properties - // 2. A key and value - // - this.set( owner, key, value ); - - // Since the "set" path can have two possible entry points - // return the expected data based on which path was taken[*] - return value !== undefined ? value : key; - }, - remove: function( owner, key ) { - var i, - cache = owner[ this.expando ]; - - if ( cache === undefined ) { - return; - } - - if ( key !== undefined ) { - - // Support array or space separated string of keys - if ( Array.isArray( key ) ) { - - // If key is an array of keys... - // We always set camelCase keys, so remove that. - key = key.map( camelCase ); - } else { - key = camelCase( key ); - - // If a key with the spaces exists, use it. - // Otherwise, create an array by matching non-whitespace - key = key in cache ? - [ key ] : - ( key.match( rnothtmlwhite ) || [] ); - } - - i = key.length; - - while ( i-- ) { - delete cache[ key[ i ] ]; - } - } - - // Remove the expando if there's no more data - if ( key === undefined || jQuery.isEmptyObject( cache ) ) { - - // Support: Chrome <=35 - 45 - // Webkit & Blink performance suffers when deleting properties - // from DOM nodes, so set to undefined instead - // https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted) - if ( owner.nodeType ) { - owner[ this.expando ] = undefined; - } else { - delete owner[ this.expando ]; - } - } - }, - hasData: function( owner ) { - var cache = owner[ this.expando ]; - return cache !== undefined && !jQuery.isEmptyObject( cache ); - } -}; -var dataPriv = new Data(); - -var dataUser = new Data(); - - - -// Implementation Summary -// -// 1. Enforce API surface and semantic compatibility with 1.9.x branch -// 2. Improve the module's maintainability by reducing the storage -// paths to a single mechanism. -// 3. Use the same single mechanism to support "private" and "user" data. -// 4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData) -// 5. Avoid exposing implementation details on user objects (eg. expando properties) -// 6. Provide a clear path for implementation upgrade to WeakMap in 2014 - -var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/, - rmultiDash = /[A-Z]/g; - -function getData( data ) { - if ( data === "true" ) { - return true; - } - - if ( data === "false" ) { - return false; - } - - if ( data === "null" ) { - return null; - } - - // Only convert to a number if it doesn't change the string - if ( data === +data + "" ) { - return +data; - } - - if ( rbrace.test( data ) ) { - return JSON.parse( data ); - } - - return data; -} - -function dataAttr( elem, key, data ) { - var name; - - // If nothing was found internally, try to fetch any - // data from the HTML5 data-* attribute - if ( data === undefined && elem.nodeType === 1 ) { - name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase(); - data = elem.getAttribute( name ); - - if ( typeof data === "string" ) { - try { - data = getData( data ); - } catch ( e ) {} - - // Make sure we set the data so it isn't changed later - dataUser.set( elem, key, data ); - } else { - data = undefined; - } - } - return data; -} - -jQuery.extend( { - hasData: function( elem ) { - return dataUser.hasData( elem ) || dataPriv.hasData( elem ); - }, - - data: function( elem, name, data ) { - return dataUser.access( elem, name, data ); - }, - - removeData: function( elem, name ) { - dataUser.remove( elem, name ); - }, - - // TODO: Now that all calls to _data and _removeData have been replaced - // with direct calls to dataPriv methods, these can be deprecated. - _data: function( elem, name, data ) { - return dataPriv.access( elem, name, data ); - }, - - _removeData: function( elem, name ) { - dataPriv.remove( elem, name ); - } -} ); - -jQuery.fn.extend( { - data: function( key, value ) { - var i, name, data, - elem = this[ 0 ], - attrs = elem && elem.attributes; - - // Gets all values - if ( key === undefined ) { - if ( this.length ) { - data = dataUser.get( elem ); - - if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) { - i = attrs.length; - while ( i-- ) { - - // Support: IE 11 only - // The attrs elements can be null (trac-14894) - if ( attrs[ i ] ) { - name = attrs[ i ].name; - if ( name.indexOf( "data-" ) === 0 ) { - name = camelCase( name.slice( 5 ) ); - dataAttr( elem, name, data[ name ] ); - } - } - } - dataPriv.set( elem, "hasDataAttrs", true ); - } - } - - return data; - } - - // Sets multiple values - if ( typeof key === "object" ) { - return this.each( function() { - dataUser.set( this, key ); - } ); - } - - return access( this, function( value ) { - var data; - - // The calling jQuery object (element matches) is not empty - // (and therefore has an element appears at this[ 0 ]) and the - // `value` parameter was not undefined. An empty jQuery object - // will result in `undefined` for elem = this[ 0 ] which will - // throw an exception if an attempt to read a data cache is made. - if ( elem && value === undefined ) { - - // Attempt to get data from the cache - // The key will always be camelCased in Data - data = dataUser.get( elem, key ); - if ( data !== undefined ) { - return data; - } - - // Attempt to "discover" the data in - // HTML5 custom data-* attrs - data = dataAttr( elem, key ); - if ( data !== undefined ) { - return data; - } - - // We tried really hard, but the data doesn't exist. - return; - } - - // Set the data... - this.each( function() { - - // We always store the camelCased key - dataUser.set( this, key, value ); - } ); - }, null, value, arguments.length > 1, null, true ); - }, - - removeData: function( key ) { - return this.each( function() { - dataUser.remove( this, key ); - } ); - } -} ); - - -jQuery.extend( { - queue: function( elem, type, data ) { - var queue; - - if ( elem ) { - type = ( type || "fx" ) + "queue"; - queue = dataPriv.get( elem, type ); - - // Speed up dequeue by getting out quickly if this is just a lookup - if ( data ) { - if ( !queue || Array.isArray( data ) ) { - queue = dataPriv.access( elem, type, jQuery.makeArray( data ) ); - } else { - queue.push( data ); - } - } - return queue || []; - } - }, - - dequeue: function( elem, type ) { - type = type || "fx"; - - var queue = jQuery.queue( elem, type ), - startLength = queue.length, - fn = queue.shift(), - hooks = jQuery._queueHooks( elem, type ), - next = function() { - jQuery.dequeue( elem, type ); - }; - - // If the fx queue is dequeued, always remove the progress sentinel - if ( fn === "inprogress" ) { - fn = queue.shift(); - startLength--; - } - - if ( fn ) { - - // Add a progress sentinel to prevent the fx queue from being - // automatically dequeued - if ( type === "fx" ) { - queue.unshift( "inprogress" ); - } - - // Clear up the last queue stop function - delete hooks.stop; - fn.call( elem, next, hooks ); - } - - if ( !startLength && hooks ) { - hooks.empty.fire(); - } - }, - - // Not public - generate a queueHooks object, or return the current one - _queueHooks: function( elem, type ) { - var key = type + "queueHooks"; - return dataPriv.get( elem, key ) || dataPriv.access( elem, key, { - empty: jQuery.Callbacks( "once memory" ).add( function() { - dataPriv.remove( elem, [ type + "queue", key ] ); - } ) - } ); - } -} ); - -jQuery.fn.extend( { - queue: function( type, data ) { - var setter = 2; - - if ( typeof type !== "string" ) { - data = type; - type = "fx"; - setter--; - } - - if ( arguments.length < setter ) { - return jQuery.queue( this[ 0 ], type ); - } - - return data === undefined ? - this : - this.each( function() { - var queue = jQuery.queue( this, type, data ); - - // Ensure a hooks for this queue - jQuery._queueHooks( this, type ); - - if ( type === "fx" && queue[ 0 ] !== "inprogress" ) { - jQuery.dequeue( this, type ); - } - } ); - }, - dequeue: function( type ) { - return this.each( function() { - jQuery.dequeue( this, type ); - } ); - }, - clearQueue: function( type ) { - return this.queue( type || "fx", [] ); - }, - - // Get a promise resolved when queues of a certain type - // are emptied (fx is the type by default) - promise: function( type, obj ) { - var tmp, - count = 1, - defer = jQuery.Deferred(), - elements = this, - i = this.length, - resolve = function() { - if ( !( --count ) ) { - defer.resolveWith( elements, [ elements ] ); - } - }; - - if ( typeof type !== "string" ) { - obj = type; - type = undefined; - } - type = type || "fx"; - - while ( i-- ) { - tmp = dataPriv.get( elements[ i ], type + "queueHooks" ); - if ( tmp && tmp.empty ) { - count++; - tmp.empty.add( resolve ); - } - } - resolve(); - return defer.promise( obj ); - } -} ); -var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source; - -var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ); - - -var cssExpand = [ "Top", "Right", "Bottom", "Left" ]; - -var documentElement = document.documentElement; - - - - var isAttached = function( elem ) { - return jQuery.contains( elem.ownerDocument, elem ); - }, - composed = { composed: true }; - - // Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only - // Check attachment across shadow DOM boundaries when possible (gh-3504) - // Support: iOS 10.0-10.2 only - // Early iOS 10 versions support `attachShadow` but not `getRootNode`, - // leading to errors. We need to check for `getRootNode`. - if ( documentElement.getRootNode ) { - isAttached = function( elem ) { - return jQuery.contains( elem.ownerDocument, elem ) || - elem.getRootNode( composed ) === elem.ownerDocument; - }; - } -var isHiddenWithinTree = function( elem, el ) { - - // isHiddenWithinTree might be called from jQuery#filter function; - // in that case, element will be second argument - elem = el || elem; - - // Inline style trumps all - return elem.style.display === "none" || - elem.style.display === "" && - - // Otherwise, check computed style - // Support: Firefox <=43 - 45 - // Disconnected elements can have computed display: none, so first confirm that elem is - // in the document. - isAttached( elem ) && - - jQuery.css( elem, "display" ) === "none"; - }; - - - -function adjustCSS( elem, prop, valueParts, tween ) { - var adjusted, scale, - maxIterations = 20, - currentValue = tween ? - function() { - return tween.cur(); - } : - function() { - return jQuery.css( elem, prop, "" ); - }, - initial = currentValue(), - unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ), - - // Starting value computation is required for potential unit mismatches - initialInUnit = elem.nodeType && - ( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) && - rcssNum.exec( jQuery.css( elem, prop ) ); - - if ( initialInUnit && initialInUnit[ 3 ] !== unit ) { - - // Support: Firefox <=54 - // Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144) - initial = initial / 2; - - // Trust units reported by jQuery.css - unit = unit || initialInUnit[ 3 ]; - - // Iteratively approximate from a nonzero starting point - initialInUnit = +initial || 1; - - while ( maxIterations-- ) { - - // Evaluate and update our best guess (doubling guesses that zero out). - // Finish if the scale equals or crosses 1 (making the old*new product non-positive). - jQuery.style( elem, prop, initialInUnit + unit ); - if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) { - maxIterations = 0; - } - initialInUnit = initialInUnit / scale; - - } - - initialInUnit = initialInUnit * 2; - jQuery.style( elem, prop, initialInUnit + unit ); - - // Make sure we update the tween properties later on - valueParts = valueParts || []; - } - - if ( valueParts ) { - initialInUnit = +initialInUnit || +initial || 0; - - // Apply relative offset (+=/-=) if specified - adjusted = valueParts[ 1 ] ? - initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] : - +valueParts[ 2 ]; - if ( tween ) { - tween.unit = unit; - tween.start = initialInUnit; - tween.end = adjusted; - } - } - return adjusted; -} - - -var defaultDisplayMap = {}; - -function getDefaultDisplay( elem ) { - var temp, - doc = elem.ownerDocument, - nodeName = elem.nodeName, - display = defaultDisplayMap[ nodeName ]; - - if ( display ) { - return display; - } - - temp = doc.body.appendChild( doc.createElement( nodeName ) ); - display = jQuery.css( temp, "display" ); - - temp.parentNode.removeChild( temp ); - - if ( display === "none" ) { - display = "block"; - } - defaultDisplayMap[ nodeName ] = display; - - return display; -} - -function showHide( elements, show ) { - var display, elem, - values = [], - index = 0, - length = elements.length; - - // Determine new display value for elements that need to change - for ( ; index < length; index++ ) { - elem = elements[ index ]; - if ( !elem.style ) { - continue; - } - - display = elem.style.display; - if ( show ) { - - // Since we force visibility upon cascade-hidden elements, an immediate (and slow) - // check is required in this first loop unless we have a nonempty display value (either - // inline or about-to-be-restored) - if ( display === "none" ) { - values[ index ] = dataPriv.get( elem, "display" ) || null; - if ( !values[ index ] ) { - elem.style.display = ""; - } - } - if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) { - values[ index ] = getDefaultDisplay( elem ); - } - } else { - if ( display !== "none" ) { - values[ index ] = "none"; - - // Remember what we're overwriting - dataPriv.set( elem, "display", display ); - } - } - } - - // Set the display of the elements in a second loop to avoid constant reflow - for ( index = 0; index < length; index++ ) { - if ( values[ index ] != null ) { - elements[ index ].style.display = values[ index ]; - } - } - - return elements; -} - -jQuery.fn.extend( { - show: function() { - return showHide( this, true ); - }, - hide: function() { - return showHide( this ); - }, - toggle: function( state ) { - if ( typeof state === "boolean" ) { - return state ? this.show() : this.hide(); - } - - return this.each( function() { - if ( isHiddenWithinTree( this ) ) { - jQuery( this ).show(); - } else { - jQuery( this ).hide(); - } - } ); - } -} ); -var rcheckableType = ( /^(?:checkbox|radio)$/i ); - -var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i ); - -var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i ); - - - -( function() { - var fragment = document.createDocumentFragment(), - div = fragment.appendChild( document.createElement( "div" ) ), - input = document.createElement( "input" ); - - // Support: Android 4.0 - 4.3 only - // Check state lost if the name is set (trac-11217) - // Support: Windows Web Apps (WWA) - // `name` and `type` must use .setAttribute for WWA (trac-14901) - input.setAttribute( "type", "radio" ); - input.setAttribute( "checked", "checked" ); - input.setAttribute( "name", "t" ); - - div.appendChild( input ); - - // Support: Android <=4.1 only - // Older WebKit doesn't clone checked state correctly in fragments - support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked; - - // Support: IE <=11 only - // Make sure textarea (and checkbox) defaultValue is properly cloned - div.innerHTML = ""; - support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue; - - // Support: IE <=9 only - // IE <=9 replaces "; - support.option = !!div.lastChild; -} )(); - - -// We have to close these tags to support XHTML (trac-13200) -var wrapMap = { - - // XHTML parsers do not magically insert elements in the - // same way that tag soup parsers do. So we cannot shorten - // this by omitting or other required elements. - thead: [ 1, "", "
" ], - col: [ 2, "", "
" ], - tr: [ 2, "", "
" ], - td: [ 3, "", "
" ], - - _default: [ 0, "", "" ] -}; - -wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; -wrapMap.th = wrapMap.td; - -// Support: IE <=9 only -if ( !support.option ) { - wrapMap.optgroup = wrapMap.option = [ 1, "" ]; -} - - -function getAll( context, tag ) { - - // Support: IE <=9 - 11 only - // Use typeof to avoid zero-argument method invocation on host objects (trac-15151) - var ret; - - if ( typeof context.getElementsByTagName !== "undefined" ) { - ret = context.getElementsByTagName( tag || "*" ); - - } else if ( typeof context.querySelectorAll !== "undefined" ) { - ret = context.querySelectorAll( tag || "*" ); - - } else { - ret = []; - } - - if ( tag === undefined || tag && nodeName( context, tag ) ) { - return jQuery.merge( [ context ], ret ); - } - - return ret; -} - - -// Mark scripts as having already been evaluated -function setGlobalEval( elems, refElements ) { - var i = 0, - l = elems.length; - - for ( ; i < l; i++ ) { - dataPriv.set( - elems[ i ], - "globalEval", - !refElements || dataPriv.get( refElements[ i ], "globalEval" ) - ); - } -} - - -var rhtml = /<|&#?\w+;/; - -function buildFragment( elems, context, scripts, selection, ignored ) { - var elem, tmp, tag, wrap, attached, j, - fragment = context.createDocumentFragment(), - nodes = [], - i = 0, - l = elems.length; - - for ( ; i < l; i++ ) { - elem = elems[ i ]; - - if ( elem || elem === 0 ) { - - // Add nodes directly - if ( toType( elem ) === "object" ) { - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem ); - - // Convert non-html into a text node - } else if ( !rhtml.test( elem ) ) { - nodes.push( context.createTextNode( elem ) ); - - // Convert html into DOM nodes - } else { - tmp = tmp || fragment.appendChild( context.createElement( "div" ) ); - - // Deserialize a standard representation - tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase(); - wrap = wrapMap[ tag ] || wrapMap._default; - tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ]; - - // Descend through wrappers to the right content - j = wrap[ 0 ]; - while ( j-- ) { - tmp = tmp.lastChild; - } - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( nodes, tmp.childNodes ); - - // Remember the top-level container - tmp = fragment.firstChild; - - // Ensure the created nodes are orphaned (trac-12392) - tmp.textContent = ""; - } - } - } - - // Remove wrapper from fragment - fragment.textContent = ""; - - i = 0; - while ( ( elem = nodes[ i++ ] ) ) { - - // Skip elements already in the context collection (trac-4087) - if ( selection && jQuery.inArray( elem, selection ) > -1 ) { - if ( ignored ) { - ignored.push( elem ); - } - continue; - } - - attached = isAttached( elem ); - - // Append to fragment - tmp = getAll( fragment.appendChild( elem ), "script" ); - - // Preserve script evaluation history - if ( attached ) { - setGlobalEval( tmp ); - } - - // Capture executables - if ( scripts ) { - j = 0; - while ( ( elem = tmp[ j++ ] ) ) { - if ( rscriptType.test( elem.type || "" ) ) { - scripts.push( elem ); - } - } - } - } - - return fragment; -} - - -var rtypenamespace = /^([^.]*)(?:\.(.+)|)/; - -function returnTrue() { - return true; -} - -function returnFalse() { - return false; -} - -function on( elem, types, selector, data, fn, one ) { - var origFn, type; - - // Types can be a map of types/handlers - if ( typeof types === "object" ) { - - // ( types-Object, selector, data ) - if ( typeof selector !== "string" ) { - - // ( types-Object, data ) - data = data || selector; - selector = undefined; - } - for ( type in types ) { - on( elem, type, selector, data, types[ type ], one ); - } - return elem; - } - - if ( data == null && fn == null ) { - - // ( types, fn ) - fn = selector; - data = selector = undefined; - } else if ( fn == null ) { - if ( typeof selector === "string" ) { - - // ( types, selector, fn ) - fn = data; - data = undefined; - } else { - - // ( types, data, fn ) - fn = data; - data = selector; - selector = undefined; - } - } - if ( fn === false ) { - fn = returnFalse; - } else if ( !fn ) { - return elem; - } - - if ( one === 1 ) { - origFn = fn; - fn = function( event ) { - - // Can use an empty set, since event contains the info - jQuery().off( event ); - return origFn.apply( this, arguments ); - }; - - // Use same guid so caller can remove using origFn - fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); - } - return elem.each( function() { - jQuery.event.add( this, types, fn, data, selector ); - } ); -} - -/* - * Helper functions for managing events -- not part of the public interface. - * Props to Dean Edwards' addEvent library for many of the ideas. - */ -jQuery.event = { - - global: {}, - - add: function( elem, types, handler, data, selector ) { - - var handleObjIn, eventHandle, tmp, - events, t, handleObj, - special, handlers, type, namespaces, origType, - elemData = dataPriv.get( elem ); - - // Only attach events to objects that accept data - if ( !acceptData( elem ) ) { - return; - } - - // Caller can pass in an object of custom data in lieu of the handler - if ( handler.handler ) { - handleObjIn = handler; - handler = handleObjIn.handler; - selector = handleObjIn.selector; - } - - // Ensure that invalid selectors throw exceptions at attach time - // Evaluate against documentElement in case elem is a non-element node (e.g., document) - if ( selector ) { - jQuery.find.matchesSelector( documentElement, selector ); - } - - // Make sure that the handler has a unique ID, used to find/remove it later - if ( !handler.guid ) { - handler.guid = jQuery.guid++; - } - - // Init the element's event structure and main handler, if this is the first - if ( !( events = elemData.events ) ) { - events = elemData.events = Object.create( null ); - } - if ( !( eventHandle = elemData.handle ) ) { - eventHandle = elemData.handle = function( e ) { - - // Discard the second event of a jQuery.event.trigger() and - // when an event is called after a page has unloaded - return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ? - jQuery.event.dispatch.apply( elem, arguments ) : undefined; - }; - } - - // Handle multiple events separated by a space - types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; - t = types.length; - while ( t-- ) { - tmp = rtypenamespace.exec( types[ t ] ) || []; - type = origType = tmp[ 1 ]; - namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); - - // There *must* be a type, no attaching namespace-only handlers - if ( !type ) { - continue; - } - - // If event changes its type, use the special event handlers for the changed type - special = jQuery.event.special[ type ] || {}; - - // If selector defined, determine special event api type, otherwise given type - type = ( selector ? special.delegateType : special.bindType ) || type; - - // Update special based on newly reset type - special = jQuery.event.special[ type ] || {}; - - // handleObj is passed to all event handlers - handleObj = jQuery.extend( { - type: type, - origType: origType, - data: data, - handler: handler, - guid: handler.guid, - selector: selector, - needsContext: selector && jQuery.expr.match.needsContext.test( selector ), - namespace: namespaces.join( "." ) - }, handleObjIn ); - - // Init the event handler queue if we're the first - if ( !( handlers = events[ type ] ) ) { - handlers = events[ type ] = []; - handlers.delegateCount = 0; - - // Only use addEventListener if the special events handler returns false - if ( !special.setup || - special.setup.call( elem, data, namespaces, eventHandle ) === false ) { - - if ( elem.addEventListener ) { - elem.addEventListener( type, eventHandle ); - } - } - } - - if ( special.add ) { - special.add.call( elem, handleObj ); - - if ( !handleObj.handler.guid ) { - handleObj.handler.guid = handler.guid; - } - } - - // Add to the element's handler list, delegates in front - if ( selector ) { - handlers.splice( handlers.delegateCount++, 0, handleObj ); - } else { - handlers.push( handleObj ); - } - - // Keep track of which events have ever been used, for event optimization - jQuery.event.global[ type ] = true; - } - - }, - - // Detach an event or set of events from an element - remove: function( elem, types, handler, selector, mappedTypes ) { - - var j, origCount, tmp, - events, t, handleObj, - special, handlers, type, namespaces, origType, - elemData = dataPriv.hasData( elem ) && dataPriv.get( elem ); - - if ( !elemData || !( events = elemData.events ) ) { - return; - } - - // Once for each type.namespace in types; type may be omitted - types = ( types || "" ).match( rnothtmlwhite ) || [ "" ]; - t = types.length; - while ( t-- ) { - tmp = rtypenamespace.exec( types[ t ] ) || []; - type = origType = tmp[ 1 ]; - namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort(); - - // Unbind all events (on this namespace, if provided) for the element - if ( !type ) { - for ( type in events ) { - jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); - } - continue; - } - - special = jQuery.event.special[ type ] || {}; - type = ( selector ? special.delegateType : special.bindType ) || type; - handlers = events[ type ] || []; - tmp = tmp[ 2 ] && - new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ); - - // Remove matching events - origCount = j = handlers.length; - while ( j-- ) { - handleObj = handlers[ j ]; - - if ( ( mappedTypes || origType === handleObj.origType ) && - ( !handler || handler.guid === handleObj.guid ) && - ( !tmp || tmp.test( handleObj.namespace ) ) && - ( !selector || selector === handleObj.selector || - selector === "**" && handleObj.selector ) ) { - handlers.splice( j, 1 ); - - if ( handleObj.selector ) { - handlers.delegateCount--; - } - if ( special.remove ) { - special.remove.call( elem, handleObj ); - } - } - } - - // Remove generic event handler if we removed something and no more handlers exist - // (avoids potential for endless recursion during removal of special event handlers) - if ( origCount && !handlers.length ) { - if ( !special.teardown || - special.teardown.call( elem, namespaces, elemData.handle ) === false ) { - - jQuery.removeEvent( elem, type, elemData.handle ); - } - - delete events[ type ]; - } - } - - // Remove data and the expando if it's no longer used - if ( jQuery.isEmptyObject( events ) ) { - dataPriv.remove( elem, "handle events" ); - } - }, - - dispatch: function( nativeEvent ) { - - var i, j, ret, matched, handleObj, handlerQueue, - args = new Array( arguments.length ), - - // Make a writable jQuery.Event from the native event object - event = jQuery.event.fix( nativeEvent ), - - handlers = ( - dataPriv.get( this, "events" ) || Object.create( null ) - )[ event.type ] || [], - special = jQuery.event.special[ event.type ] || {}; - - // Use the fix-ed jQuery.Event rather than the (read-only) native event - args[ 0 ] = event; - - for ( i = 1; i < arguments.length; i++ ) { - args[ i ] = arguments[ i ]; - } - - event.delegateTarget = this; - - // Call the preDispatch hook for the mapped type, and let it bail if desired - if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { - return; - } - - // Determine handlers - handlerQueue = jQuery.event.handlers.call( this, event, handlers ); - - // Run delegates first; they may want to stop propagation beneath us - i = 0; - while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) { - event.currentTarget = matched.elem; - - j = 0; - while ( ( handleObj = matched.handlers[ j++ ] ) && - !event.isImmediatePropagationStopped() ) { - - // If the event is namespaced, then each handler is only invoked if it is - // specially universal or its namespaces are a superset of the event's. - if ( !event.rnamespace || handleObj.namespace === false || - event.rnamespace.test( handleObj.namespace ) ) { - - event.handleObj = handleObj; - event.data = handleObj.data; - - ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle || - handleObj.handler ).apply( matched.elem, args ); - - if ( ret !== undefined ) { - if ( ( event.result = ret ) === false ) { - event.preventDefault(); - event.stopPropagation(); - } - } - } - } - } - - // Call the postDispatch hook for the mapped type - if ( special.postDispatch ) { - special.postDispatch.call( this, event ); - } - - return event.result; - }, - - handlers: function( event, handlers ) { - var i, handleObj, sel, matchedHandlers, matchedSelectors, - handlerQueue = [], - delegateCount = handlers.delegateCount, - cur = event.target; - - // Find delegate handlers - if ( delegateCount && - - // Support: IE <=9 - // Black-hole SVG instance trees (trac-13180) - cur.nodeType && - - // Support: Firefox <=42 - // Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861) - // https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click - // Support: IE 11 only - // ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343) - !( event.type === "click" && event.button >= 1 ) ) { - - for ( ; cur !== this; cur = cur.parentNode || this ) { - - // Don't check non-elements (trac-13208) - // Don't process clicks on disabled elements (trac-6911, trac-8165, trac-11382, trac-11764) - if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) { - matchedHandlers = []; - matchedSelectors = {}; - for ( i = 0; i < delegateCount; i++ ) { - handleObj = handlers[ i ]; - - // Don't conflict with Object.prototype properties (trac-13203) - sel = handleObj.selector + " "; - - if ( matchedSelectors[ sel ] === undefined ) { - matchedSelectors[ sel ] = handleObj.needsContext ? - jQuery( sel, this ).index( cur ) > -1 : - jQuery.find( sel, this, null, [ cur ] ).length; - } - if ( matchedSelectors[ sel ] ) { - matchedHandlers.push( handleObj ); - } - } - if ( matchedHandlers.length ) { - handlerQueue.push( { elem: cur, handlers: matchedHandlers } ); - } - } - } - } - - // Add the remaining (directly-bound) handlers - cur = this; - if ( delegateCount < handlers.length ) { - handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } ); - } - - return handlerQueue; - }, - - addProp: function( name, hook ) { - Object.defineProperty( jQuery.Event.prototype, name, { - enumerable: true, - configurable: true, - - get: isFunction( hook ) ? - function() { - if ( this.originalEvent ) { - return hook( this.originalEvent ); - } - } : - function() { - if ( this.originalEvent ) { - return this.originalEvent[ name ]; - } - }, - - set: function( value ) { - Object.defineProperty( this, name, { - enumerable: true, - configurable: true, - writable: true, - value: value - } ); - } - } ); - }, - - fix: function( originalEvent ) { - return originalEvent[ jQuery.expando ] ? - originalEvent : - new jQuery.Event( originalEvent ); - }, - - special: { - load: { - - // Prevent triggered image.load events from bubbling to window.load - noBubble: true - }, - click: { - - // Utilize native event to ensure correct state for checkable inputs - setup: function( data ) { - - // For mutual compressibility with _default, replace `this` access with a local var. - // `|| data` is dead code meant only to preserve the variable through minification. - var el = this || data; - - // Claim the first handler - if ( rcheckableType.test( el.type ) && - el.click && nodeName( el, "input" ) ) { - - // dataPriv.set( el, "click", ... ) - leverageNative( el, "click", true ); - } - - // Return false to allow normal processing in the caller - return false; - }, - trigger: function( data ) { - - // For mutual compressibility with _default, replace `this` access with a local var. - // `|| data` is dead code meant only to preserve the variable through minification. - var el = this || data; - - // Force setup before triggering a click - if ( rcheckableType.test( el.type ) && - el.click && nodeName( el, "input" ) ) { - - leverageNative( el, "click" ); - } - - // Return non-false to allow normal event-path propagation - return true; - }, - - // For cross-browser consistency, suppress native .click() on links - // Also prevent it if we're currently inside a leveraged native-event stack - _default: function( event ) { - var target = event.target; - return rcheckableType.test( target.type ) && - target.click && nodeName( target, "input" ) && - dataPriv.get( target, "click" ) || - nodeName( target, "a" ); - } - }, - - beforeunload: { - postDispatch: function( event ) { - - // Support: Firefox 20+ - // Firefox doesn't alert if the returnValue field is not set. - if ( event.result !== undefined && event.originalEvent ) { - event.originalEvent.returnValue = event.result; - } - } - } - } -}; - -// Ensure the presence of an event listener that handles manually-triggered -// synthetic events by interrupting progress until reinvoked in response to -// *native* events that it fires directly, ensuring that state changes have -// already occurred before other listeners are invoked. -function leverageNative( el, type, isSetup ) { - - // Missing `isSetup` indicates a trigger call, which must force setup through jQuery.event.add - if ( !isSetup ) { - if ( dataPriv.get( el, type ) === undefined ) { - jQuery.event.add( el, type, returnTrue ); - } - return; - } - - // Register the controller as a special universal handler for all event namespaces - dataPriv.set( el, type, false ); - jQuery.event.add( el, type, { - namespace: false, - handler: function( event ) { - var result, - saved = dataPriv.get( this, type ); - - if ( ( event.isTrigger & 1 ) && this[ type ] ) { - - // Interrupt processing of the outer synthetic .trigger()ed event - if ( !saved ) { - - // Store arguments for use when handling the inner native event - // There will always be at least one argument (an event object), so this array - // will not be confused with a leftover capture object. - saved = slice.call( arguments ); - dataPriv.set( this, type, saved ); - - // Trigger the native event and capture its result - this[ type ](); - result = dataPriv.get( this, type ); - dataPriv.set( this, type, false ); - - if ( saved !== result ) { - - // Cancel the outer synthetic event - event.stopImmediatePropagation(); - event.preventDefault(); - - return result; - } - - // If this is an inner synthetic event for an event with a bubbling surrogate - // (focus or blur), assume that the surrogate already propagated from triggering - // the native event and prevent that from happening again here. - // This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the - // bubbling surrogate propagates *after* the non-bubbling base), but that seems - // less bad than duplication. - } else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) { - event.stopPropagation(); - } - - // If this is a native event triggered above, everything is now in order - // Fire an inner synthetic event with the original arguments - } else if ( saved ) { - - // ...and capture the result - dataPriv.set( this, type, jQuery.event.trigger( - saved[ 0 ], - saved.slice( 1 ), - this - ) ); - - // Abort handling of the native event by all jQuery handlers while allowing - // native handlers on the same element to run. On target, this is achieved - // by stopping immediate propagation just on the jQuery event. However, - // the native event is re-wrapped by a jQuery one on each level of the - // propagation so the only way to stop it for jQuery is to stop it for - // everyone via native `stopPropagation()`. This is not a problem for - // focus/blur which don't bubble, but it does also stop click on checkboxes - // and radios. We accept this limitation. - event.stopPropagation(); - event.isImmediatePropagationStopped = returnTrue; - } - } - } ); -} - -jQuery.removeEvent = function( elem, type, handle ) { - - // This "if" is needed for plain objects - if ( elem.removeEventListener ) { - elem.removeEventListener( type, handle ); - } -}; - -jQuery.Event = function( src, props ) { - - // Allow instantiation without the 'new' keyword - if ( !( this instanceof jQuery.Event ) ) { - return new jQuery.Event( src, props ); - } - - // Event object - if ( src && src.type ) { - this.originalEvent = src; - this.type = src.type; - - // Events bubbling up the document may have been marked as prevented - // by a handler lower down the tree; reflect the correct value. - this.isDefaultPrevented = src.defaultPrevented || - src.defaultPrevented === undefined && - - // Support: Android <=2.3 only - src.returnValue === false ? - returnTrue : - returnFalse; - - // Create target properties - // Support: Safari <=6 - 7 only - // Target should not be a text node (trac-504, trac-13143) - this.target = ( src.target && src.target.nodeType === 3 ) ? - src.target.parentNode : - src.target; - - this.currentTarget = src.currentTarget; - this.relatedTarget = src.relatedTarget; - - // Event type - } else { - this.type = src; - } - - // Put explicitly provided properties onto the event object - if ( props ) { - jQuery.extend( this, props ); - } - - // Create a timestamp if incoming event doesn't have one - this.timeStamp = src && src.timeStamp || Date.now(); - - // Mark it as fixed - this[ jQuery.expando ] = true; -}; - -// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding -// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html -jQuery.Event.prototype = { - constructor: jQuery.Event, - isDefaultPrevented: returnFalse, - isPropagationStopped: returnFalse, - isImmediatePropagationStopped: returnFalse, - isSimulated: false, - - preventDefault: function() { - var e = this.originalEvent; - - this.isDefaultPrevented = returnTrue; - - if ( e && !this.isSimulated ) { - e.preventDefault(); - } - }, - stopPropagation: function() { - var e = this.originalEvent; - - this.isPropagationStopped = returnTrue; - - if ( e && !this.isSimulated ) { - e.stopPropagation(); - } - }, - stopImmediatePropagation: function() { - var e = this.originalEvent; - - this.isImmediatePropagationStopped = returnTrue; - - if ( e && !this.isSimulated ) { - e.stopImmediatePropagation(); - } - - this.stopPropagation(); - } -}; - -// Includes all common event props including KeyEvent and MouseEvent specific props -jQuery.each( { - altKey: true, - bubbles: true, - cancelable: true, - changedTouches: true, - ctrlKey: true, - detail: true, - eventPhase: true, - metaKey: true, - pageX: true, - pageY: true, - shiftKey: true, - view: true, - "char": true, - code: true, - charCode: true, - key: true, - keyCode: true, - button: true, - buttons: true, - clientX: true, - clientY: true, - offsetX: true, - offsetY: true, - pointerId: true, - pointerType: true, - screenX: true, - screenY: true, - targetTouches: true, - toElement: true, - touches: true, - which: true -}, jQuery.event.addProp ); - -jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) { - - function focusMappedHandler( nativeEvent ) { - if ( document.documentMode ) { - - // Support: IE 11+ - // Attach a single focusin/focusout handler on the document while someone wants - // focus/blur. This is because the former are synchronous in IE while the latter - // are async. In other browsers, all those handlers are invoked synchronously. - - // `handle` from private data would already wrap the event, but we need - // to change the `type` here. - var handle = dataPriv.get( this, "handle" ), - event = jQuery.event.fix( nativeEvent ); - event.type = nativeEvent.type === "focusin" ? "focus" : "blur"; - event.isSimulated = true; - - // First, handle focusin/focusout - handle( nativeEvent ); - - // ...then, handle focus/blur - // - // focus/blur don't bubble while focusin/focusout do; simulate the former by only - // invoking the handler at the lower level. - if ( event.target === event.currentTarget ) { - - // The setup part calls `leverageNative`, which, in turn, calls - // `jQuery.event.add`, so event handle will already have been set - // by this point. - handle( event ); - } - } else { - - // For non-IE browsers, attach a single capturing handler on the document - // while someone wants focusin/focusout. - jQuery.event.simulate( delegateType, nativeEvent.target, - jQuery.event.fix( nativeEvent ) ); - } - } - - jQuery.event.special[ type ] = { - - // Utilize native event if possible so blur/focus sequence is correct - setup: function() { - - var attaches; - - // Claim the first handler - // dataPriv.set( this, "focus", ... ) - // dataPriv.set( this, "blur", ... ) - leverageNative( this, type, true ); - - if ( document.documentMode ) { - - // Support: IE 9 - 11+ - // We use the same native handler for focusin & focus (and focusout & blur) - // so we need to coordinate setup & teardown parts between those events. - // Use `delegateType` as the key as `type` is already used by `leverageNative`. - attaches = dataPriv.get( this, delegateType ); - if ( !attaches ) { - this.addEventListener( delegateType, focusMappedHandler ); - } - dataPriv.set( this, delegateType, ( attaches || 0 ) + 1 ); - } else { - - // Return false to allow normal processing in the caller - return false; - } - }, - trigger: function() { - - // Force setup before trigger - leverageNative( this, type ); - - // Return non-false to allow normal event-path propagation - return true; - }, - - teardown: function() { - var attaches; - - if ( document.documentMode ) { - attaches = dataPriv.get( this, delegateType ) - 1; - if ( !attaches ) { - this.removeEventListener( delegateType, focusMappedHandler ); - dataPriv.remove( this, delegateType ); - } else { - dataPriv.set( this, delegateType, attaches ); - } - } else { - - // Return false to indicate standard teardown should be applied - return false; - } - }, - - // Suppress native focus or blur if we're currently inside - // a leveraged native-event stack - _default: function( event ) { - return dataPriv.get( event.target, type ); - }, - - delegateType: delegateType - }; - - // Support: Firefox <=44 - // Firefox doesn't have focus(in | out) events - // Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787 - // - // Support: Chrome <=48 - 49, Safari <=9.0 - 9.1 - // focus(in | out) events fire after focus & blur events, - // which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order - // Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857 - // - // Support: IE 9 - 11+ - // To preserve relative focusin/focus & focusout/blur event order guaranteed on the 3.x branch, - // attach a single handler for both events in IE. - jQuery.event.special[ delegateType ] = { - setup: function() { - - // Handle: regular nodes (via `this.ownerDocument`), window - // (via `this.document`) & document (via `this`). - var doc = this.ownerDocument || this.document || this, - dataHolder = document.documentMode ? this : doc, - attaches = dataPriv.get( dataHolder, delegateType ); - - // Support: IE 9 - 11+ - // We use the same native handler for focusin & focus (and focusout & blur) - // so we need to coordinate setup & teardown parts between those events. - // Use `delegateType` as the key as `type` is already used by `leverageNative`. - if ( !attaches ) { - if ( document.documentMode ) { - this.addEventListener( delegateType, focusMappedHandler ); - } else { - doc.addEventListener( type, focusMappedHandler, true ); - } - } - dataPriv.set( dataHolder, delegateType, ( attaches || 0 ) + 1 ); - }, - teardown: function() { - var doc = this.ownerDocument || this.document || this, - dataHolder = document.documentMode ? this : doc, - attaches = dataPriv.get( dataHolder, delegateType ) - 1; - - if ( !attaches ) { - if ( document.documentMode ) { - this.removeEventListener( delegateType, focusMappedHandler ); - } else { - doc.removeEventListener( type, focusMappedHandler, true ); - } - dataPriv.remove( dataHolder, delegateType ); - } else { - dataPriv.set( dataHolder, delegateType, attaches ); - } - } - }; -} ); - -// Create mouseenter/leave events using mouseover/out and event-time checks -// so that event delegation works in jQuery. -// Do the same for pointerenter/pointerleave and pointerover/pointerout -// -// Support: Safari 7 only -// Safari sends mouseenter too often; see: -// https://bugs.chromium.org/p/chromium/issues/detail?id=470258 -// for the description of the bug (it existed in older Chrome versions as well). -jQuery.each( { - mouseenter: "mouseover", - mouseleave: "mouseout", - pointerenter: "pointerover", - pointerleave: "pointerout" -}, function( orig, fix ) { - jQuery.event.special[ orig ] = { - delegateType: fix, - bindType: fix, - - handle: function( event ) { - var ret, - target = this, - related = event.relatedTarget, - handleObj = event.handleObj; - - // For mouseenter/leave call the handler if related is outside the target. - // NB: No relatedTarget if the mouse left/entered the browser window - if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) { - event.type = handleObj.origType; - ret = handleObj.handler.apply( this, arguments ); - event.type = fix; - } - return ret; - } - }; -} ); - -jQuery.fn.extend( { - - on: function( types, selector, data, fn ) { - return on( this, types, selector, data, fn ); - }, - one: function( types, selector, data, fn ) { - return on( this, types, selector, data, fn, 1 ); - }, - off: function( types, selector, fn ) { - var handleObj, type; - if ( types && types.preventDefault && types.handleObj ) { - - // ( event ) dispatched jQuery.Event - handleObj = types.handleObj; - jQuery( types.delegateTarget ).off( - handleObj.namespace ? - handleObj.origType + "." + handleObj.namespace : - handleObj.origType, - handleObj.selector, - handleObj.handler - ); - return this; - } - if ( typeof types === "object" ) { - - // ( types-object [, selector] ) - for ( type in types ) { - this.off( type, selector, types[ type ] ); - } - return this; - } - if ( selector === false || typeof selector === "function" ) { - - // ( types [, fn] ) - fn = selector; - selector = undefined; - } - if ( fn === false ) { - fn = returnFalse; - } - return this.each( function() { - jQuery.event.remove( this, types, fn, selector ); - } ); - } -} ); - - -var - - // Support: IE <=10 - 11, Edge 12 - 13 only - // In IE/Edge using regex groups here causes severe slowdowns. - // See https://connect.microsoft.com/IE/feedback/details/1736512/ - rnoInnerhtml = /\s*$/g; - -// Prefer a tbody over its parent table for containing new rows -function manipulationTarget( elem, content ) { - if ( nodeName( elem, "table" ) && - nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) { - - return jQuery( elem ).children( "tbody" )[ 0 ] || elem; - } - - return elem; -} - -// Replace/restore the type attribute of script elements for safe DOM manipulation -function disableScript( elem ) { - elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type; - return elem; -} -function restoreScript( elem ) { - if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) { - elem.type = elem.type.slice( 5 ); - } else { - elem.removeAttribute( "type" ); - } - - return elem; -} - -function cloneCopyEvent( src, dest ) { - var i, l, type, pdataOld, udataOld, udataCur, events; - - if ( dest.nodeType !== 1 ) { - return; - } - - // 1. Copy private data: events, handlers, etc. - if ( dataPriv.hasData( src ) ) { - pdataOld = dataPriv.get( src ); - events = pdataOld.events; - - if ( events ) { - dataPriv.remove( dest, "handle events" ); - - for ( type in events ) { - for ( i = 0, l = events[ type ].length; i < l; i++ ) { - jQuery.event.add( dest, type, events[ type ][ i ] ); - } - } - } - } - - // 2. Copy user data - if ( dataUser.hasData( src ) ) { - udataOld = dataUser.access( src ); - udataCur = jQuery.extend( {}, udataOld ); - - dataUser.set( dest, udataCur ); - } -} - -// Fix IE bugs, see support tests -function fixInput( src, dest ) { - var nodeName = dest.nodeName.toLowerCase(); - - // Fails to persist the checked state of a cloned checkbox or radio button. - if ( nodeName === "input" && rcheckableType.test( src.type ) ) { - dest.checked = src.checked; - - // Fails to return the selected option to the default selected state when cloning options - } else if ( nodeName === "input" || nodeName === "textarea" ) { - dest.defaultValue = src.defaultValue; - } -} - -function domManip( collection, args, callback, ignored ) { - - // Flatten any nested arrays - args = flat( args ); - - var fragment, first, scripts, hasScripts, node, doc, - i = 0, - l = collection.length, - iNoClone = l - 1, - value = args[ 0 ], - valueIsFunction = isFunction( value ); - - // We can't cloneNode fragments that contain checked, in WebKit - if ( valueIsFunction || - ( l > 1 && typeof value === "string" && - !support.checkClone && rchecked.test( value ) ) ) { - return collection.each( function( index ) { - var self = collection.eq( index ); - if ( valueIsFunction ) { - args[ 0 ] = value.call( this, index, self.html() ); - } - domManip( self, args, callback, ignored ); - } ); - } - - if ( l ) { - fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored ); - first = fragment.firstChild; - - if ( fragment.childNodes.length === 1 ) { - fragment = first; - } - - // Require either new content or an interest in ignored elements to invoke the callback - if ( first || ignored ) { - scripts = jQuery.map( getAll( fragment, "script" ), disableScript ); - hasScripts = scripts.length; - - // Use the original fragment for the last item - // instead of the first because it can end up - // being emptied incorrectly in certain situations (trac-8070). - for ( ; i < l; i++ ) { - node = fragment; - - if ( i !== iNoClone ) { - node = jQuery.clone( node, true, true ); - - // Keep references to cloned scripts for later restoration - if ( hasScripts ) { - - // Support: Android <=4.0 only, PhantomJS 1 only - // push.apply(_, arraylike) throws on ancient WebKit - jQuery.merge( scripts, getAll( node, "script" ) ); - } - } - - callback.call( collection[ i ], node, i ); - } - - if ( hasScripts ) { - doc = scripts[ scripts.length - 1 ].ownerDocument; - - // Re-enable scripts - jQuery.map( scripts, restoreScript ); - - // Evaluate executable scripts on first document insertion - for ( i = 0; i < hasScripts; i++ ) { - node = scripts[ i ]; - if ( rscriptType.test( node.type || "" ) && - !dataPriv.access( node, "globalEval" ) && - jQuery.contains( doc, node ) ) { - - if ( node.src && ( node.type || "" ).toLowerCase() !== "module" ) { - - // Optional AJAX dependency, but won't run scripts if not present - if ( jQuery._evalUrl && !node.noModule ) { - jQuery._evalUrl( node.src, { - nonce: node.nonce || node.getAttribute( "nonce" ) - }, doc ); - } - } else { - - // Unwrap a CDATA section containing script contents. This shouldn't be - // needed as in XML documents they're already not visible when - // inspecting element contents and in HTML documents they have no - // meaning but we're preserving that logic for backwards compatibility. - // This will be removed completely in 4.0. See gh-4904. - DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc ); - } - } - } - } - } - } - - return collection; -} - -function remove( elem, selector, keepData ) { - var node, - nodes = selector ? jQuery.filter( selector, elem ) : elem, - i = 0; - - for ( ; ( node = nodes[ i ] ) != null; i++ ) { - if ( !keepData && node.nodeType === 1 ) { - jQuery.cleanData( getAll( node ) ); - } - - if ( node.parentNode ) { - if ( keepData && isAttached( node ) ) { - setGlobalEval( getAll( node, "script" ) ); - } - node.parentNode.removeChild( node ); - } - } - - return elem; -} - -jQuery.extend( { - htmlPrefilter: function( html ) { - return html; - }, - - clone: function( elem, dataAndEvents, deepDataAndEvents ) { - var i, l, srcElements, destElements, - clone = elem.cloneNode( true ), - inPage = isAttached( elem ); - - // Fix IE cloning issues - if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) && - !jQuery.isXMLDoc( elem ) ) { - - // We eschew jQuery#find here for performance reasons: - // https://jsperf.com/getall-vs-sizzle/2 - destElements = getAll( clone ); - srcElements = getAll( elem ); - - for ( i = 0, l = srcElements.length; i < l; i++ ) { - fixInput( srcElements[ i ], destElements[ i ] ); - } - } - - // Copy the events from the original to the clone - if ( dataAndEvents ) { - if ( deepDataAndEvents ) { - srcElements = srcElements || getAll( elem ); - destElements = destElements || getAll( clone ); - - for ( i = 0, l = srcElements.length; i < l; i++ ) { - cloneCopyEvent( srcElements[ i ], destElements[ i ] ); - } - } else { - cloneCopyEvent( elem, clone ); - } - } - - // Preserve script evaluation history - destElements = getAll( clone, "script" ); - if ( destElements.length > 0 ) { - setGlobalEval( destElements, !inPage && getAll( elem, "script" ) ); - } - - // Return the cloned set - return clone; - }, - - cleanData: function( elems ) { - var data, elem, type, - special = jQuery.event.special, - i = 0; - - for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) { - if ( acceptData( elem ) ) { - if ( ( data = elem[ dataPriv.expando ] ) ) { - if ( data.events ) { - for ( type in data.events ) { - if ( special[ type ] ) { - jQuery.event.remove( elem, type ); - - // This is a shortcut to avoid jQuery.event.remove's overhead - } else { - jQuery.removeEvent( elem, type, data.handle ); - } - } - } - - // Support: Chrome <=35 - 45+ - // Assign undefined instead of using delete, see Data#remove - elem[ dataPriv.expando ] = undefined; - } - if ( elem[ dataUser.expando ] ) { - - // Support: Chrome <=35 - 45+ - // Assign undefined instead of using delete, see Data#remove - elem[ dataUser.expando ] = undefined; - } - } - } - } -} ); - -jQuery.fn.extend( { - detach: function( selector ) { - return remove( this, selector, true ); - }, - - remove: function( selector ) { - return remove( this, selector ); - }, - - text: function( value ) { - return access( this, function( value ) { - return value === undefined ? - jQuery.text( this ) : - this.empty().each( function() { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - this.textContent = value; - } - } ); - }, null, value, arguments.length ); - }, - - append: function() { - return domManip( this, arguments, function( elem ) { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - var target = manipulationTarget( this, elem ); - target.appendChild( elem ); - } - } ); - }, - - prepend: function() { - return domManip( this, arguments, function( elem ) { - if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) { - var target = manipulationTarget( this, elem ); - target.insertBefore( elem, target.firstChild ); - } - } ); - }, - - before: function() { - return domManip( this, arguments, function( elem ) { - if ( this.parentNode ) { - this.parentNode.insertBefore( elem, this ); - } - } ); - }, - - after: function() { - return domManip( this, arguments, function( elem ) { - if ( this.parentNode ) { - this.parentNode.insertBefore( elem, this.nextSibling ); - } - } ); - }, - - empty: function() { - var elem, - i = 0; - - for ( ; ( elem = this[ i ] ) != null; i++ ) { - if ( elem.nodeType === 1 ) { - - // Prevent memory leaks - jQuery.cleanData( getAll( elem, false ) ); - - // Remove any remaining nodes - elem.textContent = ""; - } - } - - return this; - }, - - clone: function( dataAndEvents, deepDataAndEvents ) { - dataAndEvents = dataAndEvents == null ? false : dataAndEvents; - deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; - - return this.map( function() { - return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); - } ); - }, - - html: function( value ) { - return access( this, function( value ) { - var elem = this[ 0 ] || {}, - i = 0, - l = this.length; - - if ( value === undefined && elem.nodeType === 1 ) { - return elem.innerHTML; - } - - // See if we can take a shortcut and just use innerHTML - if ( typeof value === "string" && !rnoInnerhtml.test( value ) && - !wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) { - - value = jQuery.htmlPrefilter( value ); - - try { - for ( ; i < l; i++ ) { - elem = this[ i ] || {}; - - // Remove element nodes and prevent memory leaks - if ( elem.nodeType === 1 ) { - jQuery.cleanData( getAll( elem, false ) ); - elem.innerHTML = value; - } - } - - elem = 0; - - // If using innerHTML throws an exception, use the fallback method - } catch ( e ) {} - } - - if ( elem ) { - this.empty().append( value ); - } - }, null, value, arguments.length ); - }, - - replaceWith: function() { - var ignored = []; - - // Make the changes, replacing each non-ignored context element with the new content - return domManip( this, arguments, function( elem ) { - var parent = this.parentNode; - - if ( jQuery.inArray( this, ignored ) < 0 ) { - jQuery.cleanData( getAll( this ) ); - if ( parent ) { - parent.replaceChild( elem, this ); - } - } - - // Force callback invocation - }, ignored ); - } -} ); - -jQuery.each( { - appendTo: "append", - prependTo: "prepend", - insertBefore: "before", - insertAfter: "after", - replaceAll: "replaceWith" -}, function( name, original ) { - jQuery.fn[ name ] = function( selector ) { - var elems, - ret = [], - insert = jQuery( selector ), - last = insert.length - 1, - i = 0; - - for ( ; i <= last; i++ ) { - elems = i === last ? this : this.clone( true ); - jQuery( insert[ i ] )[ original ]( elems ); - - // Support: Android <=4.0 only, PhantomJS 1 only - // .get() because push.apply(_, arraylike) throws on ancient WebKit - push.apply( ret, elems.get() ); - } - - return this.pushStack( ret ); - }; -} ); -var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" ); - -var rcustomProp = /^--/; - - -var getStyles = function( elem ) { - - // Support: IE <=11 only, Firefox <=30 (trac-15098, trac-14150) - // IE throws on elements created in popups - // FF meanwhile throws on frame elements through "defaultView.getComputedStyle" - var view = elem.ownerDocument.defaultView; - - if ( !view || !view.opener ) { - view = window; - } - - return view.getComputedStyle( elem ); - }; - -var swap = function( elem, options, callback ) { - var ret, name, - old = {}; - - // Remember the old values, and insert the new ones - for ( name in options ) { - old[ name ] = elem.style[ name ]; - elem.style[ name ] = options[ name ]; - } - - ret = callback.call( elem ); - - // Revert the old values - for ( name in options ) { - elem.style[ name ] = old[ name ]; - } - - return ret; -}; - - -var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" ); - - - -( function() { - - // Executing both pixelPosition & boxSizingReliable tests require only one layout - // so they're executed at the same time to save the second computation. - function computeStyleTests() { - - // This is a singleton, we need to execute it only once - if ( !div ) { - return; - } - - container.style.cssText = "position:absolute;left:-11111px;width:60px;" + - "margin-top:1px;padding:0;border:0"; - div.style.cssText = - "position:relative;display:block;box-sizing:border-box;overflow:scroll;" + - "margin:auto;border:1px;padding:1px;" + - "width:60%;top:1%"; - documentElement.appendChild( container ).appendChild( div ); - - var divStyle = window.getComputedStyle( div ); - pixelPositionVal = divStyle.top !== "1%"; - - // Support: Android 4.0 - 4.3 only, Firefox <=3 - 44 - reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12; - - // Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3 - // Some styles come back with percentage values, even though they shouldn't - div.style.right = "60%"; - pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36; - - // Support: IE 9 - 11 only - // Detect misreporting of content dimensions for box-sizing:border-box elements - boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36; - - // Support: IE 9 only - // Detect overflow:scroll screwiness (gh-3699) - // Support: Chrome <=64 - // Don't get tricked when zoom affects offsetWidth (gh-4029) - div.style.position = "absolute"; - scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12; - - documentElement.removeChild( container ); - - // Nullify the div so it wouldn't be stored in the memory and - // it will also be a sign that checks already performed - div = null; - } - - function roundPixelMeasures( measure ) { - return Math.round( parseFloat( measure ) ); - } - - var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal, - reliableTrDimensionsVal, reliableMarginLeftVal, - container = document.createElement( "div" ), - div = document.createElement( "div" ); - - // Finish early in limited (non-browser) environments - if ( !div.style ) { - return; - } - - // Support: IE <=9 - 11 only - // Style of cloned element affects source element cloned (trac-8908) - div.style.backgroundClip = "content-box"; - div.cloneNode( true ).style.backgroundClip = ""; - support.clearCloneStyle = div.style.backgroundClip === "content-box"; - - jQuery.extend( support, { - boxSizingReliable: function() { - computeStyleTests(); - return boxSizingReliableVal; - }, - pixelBoxStyles: function() { - computeStyleTests(); - return pixelBoxStylesVal; - }, - pixelPosition: function() { - computeStyleTests(); - return pixelPositionVal; - }, - reliableMarginLeft: function() { - computeStyleTests(); - return reliableMarginLeftVal; - }, - scrollboxSize: function() { - computeStyleTests(); - return scrollboxSizeVal; - }, - - // Support: IE 9 - 11+, Edge 15 - 18+ - // IE/Edge misreport `getComputedStyle` of table rows with width/height - // set in CSS while `offset*` properties report correct values. - // Behavior in IE 9 is more subtle than in newer versions & it passes - // some versions of this test; make sure not to make it pass there! - // - // Support: Firefox 70+ - // Only Firefox includes border widths - // in computed dimensions. (gh-4529) - reliableTrDimensions: function() { - var table, tr, trChild, trStyle; - if ( reliableTrDimensionsVal == null ) { - table = document.createElement( "table" ); - tr = document.createElement( "tr" ); - trChild = document.createElement( "div" ); - - table.style.cssText = "position:absolute;left:-11111px;border-collapse:separate"; - tr.style.cssText = "box-sizing:content-box;border:1px solid"; - - // Support: Chrome 86+ - // Height set through cssText does not get applied. - // Computed height then comes back as 0. - tr.style.height = "1px"; - trChild.style.height = "9px"; - - // Support: Android 8 Chrome 86+ - // In our bodyBackground.html iframe, - // display for all div elements is set to "inline", - // which causes a problem only in Android 8 Chrome 86. - // Ensuring the div is `display: block` - // gets around this issue. - trChild.style.display = "block"; - - documentElement - .appendChild( table ) - .appendChild( tr ) - .appendChild( trChild ); - - trStyle = window.getComputedStyle( tr ); - reliableTrDimensionsVal = ( parseInt( trStyle.height, 10 ) + - parseInt( trStyle.borderTopWidth, 10 ) + - parseInt( trStyle.borderBottomWidth, 10 ) ) === tr.offsetHeight; - - documentElement.removeChild( table ); - } - return reliableTrDimensionsVal; - } - } ); -} )(); - - -function curCSS( elem, name, computed ) { - var width, minWidth, maxWidth, ret, - isCustomProp = rcustomProp.test( name ), - - // Support: Firefox 51+ - // Retrieving style before computed somehow - // fixes an issue with getting wrong values - // on detached elements - style = elem.style; - - computed = computed || getStyles( elem ); - - // getPropertyValue is needed for: - // .css('filter') (IE 9 only, trac-12537) - // .css('--customProperty) (gh-3144) - if ( computed ) { - - // Support: IE <=9 - 11+ - // IE only supports `"float"` in `getPropertyValue`; in computed styles - // it's only available as `"cssFloat"`. We no longer modify properties - // sent to `.css()` apart from camelCasing, so we need to check both. - // Normally, this would create difference in behavior: if - // `getPropertyValue` returns an empty string, the value returned - // by `.css()` would be `undefined`. This is usually the case for - // disconnected elements. However, in IE even disconnected elements - // with no styles return `"none"` for `getPropertyValue( "float" )` - ret = computed.getPropertyValue( name ) || computed[ name ]; - - if ( isCustomProp && ret ) { - - // Support: Firefox 105+, Chrome <=105+ - // Spec requires trimming whitespace for custom properties (gh-4926). - // Firefox only trims leading whitespace. Chrome just collapses - // both leading & trailing whitespace to a single space. - // - // Fall back to `undefined` if empty string returned. - // This collapses a missing definition with property defined - // and set to an empty string but there's no standard API - // allowing us to differentiate them without a performance penalty - // and returning `undefined` aligns with older jQuery. - // - // rtrimCSS treats U+000D CARRIAGE RETURN and U+000C FORM FEED - // as whitespace while CSS does not, but this is not a problem - // because CSS preprocessing replaces them with U+000A LINE FEED - // (which *is* CSS whitespace) - // https://www.w3.org/TR/css-syntax-3/#input-preprocessing - ret = ret.replace( rtrimCSS, "$1" ) || undefined; - } - - if ( ret === "" && !isAttached( elem ) ) { - ret = jQuery.style( elem, name ); - } - - // A tribute to the "awesome hack by Dean Edwards" - // Android Browser returns percentage for some values, - // but width seems to be reliably pixels. - // This is against the CSSOM draft spec: - // https://drafts.csswg.org/cssom/#resolved-values - if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) { - - // Remember the original values - width = style.width; - minWidth = style.minWidth; - maxWidth = style.maxWidth; - - // Put in the new values to get a computed value out - style.minWidth = style.maxWidth = style.width = ret; - ret = computed.width; - - // Revert the changed values - style.width = width; - style.minWidth = minWidth; - style.maxWidth = maxWidth; - } - } - - return ret !== undefined ? - - // Support: IE <=9 - 11 only - // IE returns zIndex value as an integer. - ret + "" : - ret; -} - - -function addGetHookIf( conditionFn, hookFn ) { - - // Define the hook, we'll check on the first run if it's really needed. - return { - get: function() { - if ( conditionFn() ) { - - // Hook not needed (or it's not possible to use it due - // to missing dependency), remove it. - delete this.get; - return; - } - - // Hook needed; redefine it so that the support test is not executed again. - return ( this.get = hookFn ).apply( this, arguments ); - } - }; -} - - -var cssPrefixes = [ "Webkit", "Moz", "ms" ], - emptyStyle = document.createElement( "div" ).style, - vendorProps = {}; - -// Return a vendor-prefixed property or undefined -function vendorPropName( name ) { - - // Check for vendor prefixed names - var capName = name[ 0 ].toUpperCase() + name.slice( 1 ), - i = cssPrefixes.length; - - while ( i-- ) { - name = cssPrefixes[ i ] + capName; - if ( name in emptyStyle ) { - return name; - } - } -} - -// Return a potentially-mapped jQuery.cssProps or vendor prefixed property -function finalPropName( name ) { - var final = jQuery.cssProps[ name ] || vendorProps[ name ]; - - if ( final ) { - return final; - } - if ( name in emptyStyle ) { - return name; - } - return vendorProps[ name ] = vendorPropName( name ) || name; -} - - -var - - // Swappable if display is none or starts with table - // except "table", "table-cell", or "table-caption" - // See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display - rdisplayswap = /^(none|table(?!-c[ea]).+)/, - cssShow = { position: "absolute", visibility: "hidden", display: "block" }, - cssNormalTransform = { - letterSpacing: "0", - fontWeight: "400" - }; - -function setPositiveNumber( _elem, value, subtract ) { - - // Any relative (+/-) values have already been - // normalized at this point - var matches = rcssNum.exec( value ); - return matches ? - - // Guard against undefined "subtract", e.g., when used as in cssHooks - Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) : - value; -} - -function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) { - var i = dimension === "width" ? 1 : 0, - extra = 0, - delta = 0, - marginDelta = 0; - - // Adjustment may not be necessary - if ( box === ( isBorderBox ? "border" : "content" ) ) { - return 0; - } - - for ( ; i < 4; i += 2 ) { - - // Both box models exclude margin - // Count margin delta separately to only add it after scroll gutter adjustment. - // This is needed to make negative margins work with `outerHeight( true )` (gh-3982). - if ( box === "margin" ) { - marginDelta += jQuery.css( elem, box + cssExpand[ i ], true, styles ); - } - - // If we get here with a content-box, we're seeking "padding" or "border" or "margin" - if ( !isBorderBox ) { - - // Add padding - delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); - - // For "border" or "margin", add border - if ( box !== "padding" ) { - delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); - - // But still keep track of it otherwise - } else { - extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); - } - - // If we get here with a border-box (content + padding + border), we're seeking "content" or - // "padding" or "margin" - } else { - - // For "content", subtract padding - if ( box === "content" ) { - delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles ); - } - - // For "content" or "padding", subtract border - if ( box !== "margin" ) { - delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles ); - } - } - } - - // Account for positive content-box scroll gutter when requested by providing computedVal - if ( !isBorderBox && computedVal >= 0 ) { - - // offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border - // Assuming integer scroll gutter, subtract the rest and round down - delta += Math.max( 0, Math.ceil( - elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - - computedVal - - delta - - extra - - 0.5 - - // If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter - // Use an explicit zero to avoid NaN (gh-3964) - ) ) || 0; - } - - return delta + marginDelta; -} - -function getWidthOrHeight( elem, dimension, extra ) { - - // Start with computed style - var styles = getStyles( elem ), - - // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322). - // Fake content-box until we know it's needed to know the true value. - boxSizingNeeded = !support.boxSizingReliable() || extra, - isBorderBox = boxSizingNeeded && - jQuery.css( elem, "boxSizing", false, styles ) === "border-box", - valueIsBorderBox = isBorderBox, - - val = curCSS( elem, dimension, styles ), - offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ); - - // Support: Firefox <=54 - // Return a confounding non-pixel value or feign ignorance, as appropriate. - if ( rnumnonpx.test( val ) ) { - if ( !extra ) { - return val; - } - val = "auto"; - } - - - // Support: IE 9 - 11 only - // Use offsetWidth/offsetHeight for when box sizing is unreliable. - // In those cases, the computed value can be trusted to be border-box. - if ( ( !support.boxSizingReliable() && isBorderBox || - - // Support: IE 10 - 11+, Edge 15 - 18+ - // IE/Edge misreport `getComputedStyle` of table rows with width/height - // set in CSS while `offset*` properties report correct values. - // Interestingly, in some cases IE 9 doesn't suffer from this issue. - !support.reliableTrDimensions() && nodeName( elem, "tr" ) || - - // Fall back to offsetWidth/offsetHeight when value is "auto" - // This happens for inline elements with no explicit setting (gh-3571) - val === "auto" || - - // Support: Android <=4.1 - 4.3 only - // Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602) - !parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) && - - // Make sure the element is visible & connected - elem.getClientRects().length ) { - - isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box"; - - // Where available, offsetWidth/offsetHeight approximate border box dimensions. - // Where not available (e.g., SVG), assume unreliable box-sizing and interpret the - // retrieved value as a content box dimension. - valueIsBorderBox = offsetProp in elem; - if ( valueIsBorderBox ) { - val = elem[ offsetProp ]; - } - } - - // Normalize "" and auto - val = parseFloat( val ) || 0; - - // Adjust for the element's box model - return ( val + - boxModelAdjustment( - elem, - dimension, - extra || ( isBorderBox ? "border" : "content" ), - valueIsBorderBox, - styles, - - // Provide the current computed size to request scroll gutter calculation (gh-3589) - val - ) - ) + "px"; -} - -jQuery.extend( { - - // Add in style property hooks for overriding the default - // behavior of getting and setting a style property - cssHooks: { - opacity: { - get: function( elem, computed ) { - if ( computed ) { - - // We should always get a number back from opacity - var ret = curCSS( elem, "opacity" ); - return ret === "" ? "1" : ret; - } - } - } - }, - - // Don't automatically add "px" to these possibly-unitless properties - cssNumber: { - animationIterationCount: true, - aspectRatio: true, - borderImageSlice: true, - columnCount: true, - flexGrow: true, - flexShrink: true, - fontWeight: true, - gridArea: true, - gridColumn: true, - gridColumnEnd: true, - gridColumnStart: true, - gridRow: true, - gridRowEnd: true, - gridRowStart: true, - lineHeight: true, - opacity: true, - order: true, - orphans: true, - scale: true, - widows: true, - zIndex: true, - zoom: true, - - // SVG-related - fillOpacity: true, - floodOpacity: true, - stopOpacity: true, - strokeMiterlimit: true, - strokeOpacity: true - }, - - // Add in properties whose names you wish to fix before - // setting or getting the value - cssProps: {}, - - // Get and set the style property on a DOM Node - style: function( elem, name, value, extra ) { - - // Don't set styles on text and comment nodes - if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { - return; - } - - // Make sure that we're working with the right name - var ret, type, hooks, - origName = camelCase( name ), - isCustomProp = rcustomProp.test( name ), - style = elem.style; - - // Make sure that we're working with the right name. We don't - // want to query the value if it is a CSS custom property - // since they are user-defined. - if ( !isCustomProp ) { - name = finalPropName( origName ); - } - - // Gets hook for the prefixed version, then unprefixed version - hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; - - // Check if we're setting a value - if ( value !== undefined ) { - type = typeof value; - - // Convert "+=" or "-=" to relative numbers (trac-7345) - if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) { - value = adjustCSS( elem, name, ret ); - - // Fixes bug trac-9237 - type = "number"; - } - - // Make sure that null and NaN values aren't set (trac-7116) - if ( value == null || value !== value ) { - return; - } - - // If a number was passed in, add the unit (except for certain CSS properties) - // The isCustomProp check can be removed in jQuery 4.0 when we only auto-append - // "px" to a few hardcoded values. - if ( type === "number" && !isCustomProp ) { - value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" ); - } - - // background-* props affect original clone's values - if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) { - style[ name ] = "inherit"; - } - - // If a hook was provided, use that value, otherwise just set the specified value - if ( !hooks || !( "set" in hooks ) || - ( value = hooks.set( elem, value, extra ) ) !== undefined ) { - - if ( isCustomProp ) { - style.setProperty( name, value ); - } else { - style[ name ] = value; - } - } - - } else { - - // If a hook was provided get the non-computed value from there - if ( hooks && "get" in hooks && - ( ret = hooks.get( elem, false, extra ) ) !== undefined ) { - - return ret; - } - - // Otherwise just get the value from the style object - return style[ name ]; - } - }, - - css: function( elem, name, extra, styles ) { - var val, num, hooks, - origName = camelCase( name ), - isCustomProp = rcustomProp.test( name ); - - // Make sure that we're working with the right name. We don't - // want to modify the value if it is a CSS custom property - // since they are user-defined. - if ( !isCustomProp ) { - name = finalPropName( origName ); - } - - // Try prefixed name followed by the unprefixed name - hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ]; - - // If a hook was provided get the computed value from there - if ( hooks && "get" in hooks ) { - val = hooks.get( elem, true, extra ); - } - - // Otherwise, if a way to get the computed value exists, use that - if ( val === undefined ) { - val = curCSS( elem, name, styles ); - } - - // Convert "normal" to computed value - if ( val === "normal" && name in cssNormalTransform ) { - val = cssNormalTransform[ name ]; - } - - // Make numeric if forced or a qualifier was provided and val looks numeric - if ( extra === "" || extra ) { - num = parseFloat( val ); - return extra === true || isFinite( num ) ? num || 0 : val; - } - - return val; - } -} ); - -jQuery.each( [ "height", "width" ], function( _i, dimension ) { - jQuery.cssHooks[ dimension ] = { - get: function( elem, computed, extra ) { - if ( computed ) { - - // Certain elements can have dimension info if we invisibly show them - // but it must have a current display style that would benefit - return rdisplayswap.test( jQuery.css( elem, "display" ) ) && - - // Support: Safari 8+ - // Table columns in Safari have non-zero offsetWidth & zero - // getBoundingClientRect().width unless display is changed. - // Support: IE <=11 only - // Running getBoundingClientRect on a disconnected node - // in IE throws an error. - ( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ? - swap( elem, cssShow, function() { - return getWidthOrHeight( elem, dimension, extra ); - } ) : - getWidthOrHeight( elem, dimension, extra ); - } - }, - - set: function( elem, value, extra ) { - var matches, - styles = getStyles( elem ), - - // Only read styles.position if the test has a chance to fail - // to avoid forcing a reflow. - scrollboxSizeBuggy = !support.scrollboxSize() && - styles.position === "absolute", - - // To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991) - boxSizingNeeded = scrollboxSizeBuggy || extra, - isBorderBox = boxSizingNeeded && - jQuery.css( elem, "boxSizing", false, styles ) === "border-box", - subtract = extra ? - boxModelAdjustment( - elem, - dimension, - extra, - isBorderBox, - styles - ) : - 0; - - // Account for unreliable border-box dimensions by comparing offset* to computed and - // faking a content-box to get border and padding (gh-3699) - if ( isBorderBox && scrollboxSizeBuggy ) { - subtract -= Math.ceil( - elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] - - parseFloat( styles[ dimension ] ) - - boxModelAdjustment( elem, dimension, "border", false, styles ) - - 0.5 - ); - } - - // Convert to pixels if value adjustment is needed - if ( subtract && ( matches = rcssNum.exec( value ) ) && - ( matches[ 3 ] || "px" ) !== "px" ) { - - elem.style[ dimension ] = value; - value = jQuery.css( elem, dimension ); - } - - return setPositiveNumber( elem, value, subtract ); - } - }; -} ); - -jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft, - function( elem, computed ) { - if ( computed ) { - return ( parseFloat( curCSS( elem, "marginLeft" ) ) || - elem.getBoundingClientRect().left - - swap( elem, { marginLeft: 0 }, function() { - return elem.getBoundingClientRect().left; - } ) - ) + "px"; - } - } -); - -// These hooks are used by animate to expand properties -jQuery.each( { - margin: "", - padding: "", - border: "Width" -}, function( prefix, suffix ) { - jQuery.cssHooks[ prefix + suffix ] = { - expand: function( value ) { - var i = 0, - expanded = {}, - - // Assumes a single number if not a string - parts = typeof value === "string" ? value.split( " " ) : [ value ]; - - for ( ; i < 4; i++ ) { - expanded[ prefix + cssExpand[ i ] + suffix ] = - parts[ i ] || parts[ i - 2 ] || parts[ 0 ]; - } - - return expanded; - } - }; - - if ( prefix !== "margin" ) { - jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber; - } -} ); - -jQuery.fn.extend( { - css: function( name, value ) { - return access( this, function( elem, name, value ) { - var styles, len, - map = {}, - i = 0; - - if ( Array.isArray( name ) ) { - styles = getStyles( elem ); - len = name.length; - - for ( ; i < len; i++ ) { - map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles ); - } - - return map; - } - - return value !== undefined ? - jQuery.style( elem, name, value ) : - jQuery.css( elem, name ); - }, name, value, arguments.length > 1 ); - } -} ); - - -function Tween( elem, options, prop, end, easing ) { - return new Tween.prototype.init( elem, options, prop, end, easing ); -} -jQuery.Tween = Tween; - -Tween.prototype = { - constructor: Tween, - init: function( elem, options, prop, end, easing, unit ) { - this.elem = elem; - this.prop = prop; - this.easing = easing || jQuery.easing._default; - this.options = options; - this.start = this.now = this.cur(); - this.end = end; - this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" ); - }, - cur: function() { - var hooks = Tween.propHooks[ this.prop ]; - - return hooks && hooks.get ? - hooks.get( this ) : - Tween.propHooks._default.get( this ); - }, - run: function( percent ) { - var eased, - hooks = Tween.propHooks[ this.prop ]; - - if ( this.options.duration ) { - this.pos = eased = jQuery.easing[ this.easing ]( - percent, this.options.duration * percent, 0, 1, this.options.duration - ); - } else { - this.pos = eased = percent; - } - this.now = ( this.end - this.start ) * eased + this.start; - - if ( this.options.step ) { - this.options.step.call( this.elem, this.now, this ); - } - - if ( hooks && hooks.set ) { - hooks.set( this ); - } else { - Tween.propHooks._default.set( this ); - } - return this; - } -}; - -Tween.prototype.init.prototype = Tween.prototype; - -Tween.propHooks = { - _default: { - get: function( tween ) { - var result; - - // Use a property on the element directly when it is not a DOM element, - // or when there is no matching style property that exists. - if ( tween.elem.nodeType !== 1 || - tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) { - return tween.elem[ tween.prop ]; - } - - // Passing an empty string as a 3rd parameter to .css will automatically - // attempt a parseFloat and fallback to a string if the parse fails. - // Simple values such as "10px" are parsed to Float; - // complex values such as "rotate(1rad)" are returned as-is. - result = jQuery.css( tween.elem, tween.prop, "" ); - - // Empty strings, null, undefined and "auto" are converted to 0. - return !result || result === "auto" ? 0 : result; - }, - set: function( tween ) { - - // Use step hook for back compat. - // Use cssHook if its there. - // Use .style if available and use plain properties where available. - if ( jQuery.fx.step[ tween.prop ] ) { - jQuery.fx.step[ tween.prop ]( tween ); - } else if ( tween.elem.nodeType === 1 && ( - jQuery.cssHooks[ tween.prop ] || - tween.elem.style[ finalPropName( tween.prop ) ] != null ) ) { - jQuery.style( tween.elem, tween.prop, tween.now + tween.unit ); - } else { - tween.elem[ tween.prop ] = tween.now; - } - } - } -}; - -// Support: IE <=9 only -// Panic based approach to setting things on disconnected nodes -Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = { - set: function( tween ) { - if ( tween.elem.nodeType && tween.elem.parentNode ) { - tween.elem[ tween.prop ] = tween.now; - } - } -}; - -jQuery.easing = { - linear: function( p ) { - return p; - }, - swing: function( p ) { - return 0.5 - Math.cos( p * Math.PI ) / 2; - }, - _default: "swing" -}; - -jQuery.fx = Tween.prototype.init; - -// Back compat <1.8 extension point -jQuery.fx.step = {}; - - - - -var - fxNow, inProgress, - rfxtypes = /^(?:toggle|show|hide)$/, - rrun = /queueHooks$/; - -function schedule() { - if ( inProgress ) { - if ( document.hidden === false && window.requestAnimationFrame ) { - window.requestAnimationFrame( schedule ); - } else { - window.setTimeout( schedule, jQuery.fx.interval ); - } - - jQuery.fx.tick(); - } -} - -// Animations created synchronously will run synchronously -function createFxNow() { - window.setTimeout( function() { - fxNow = undefined; - } ); - return ( fxNow = Date.now() ); -} - -// Generate parameters to create a standard animation -function genFx( type, includeWidth ) { - var which, - i = 0, - attrs = { height: type }; - - // If we include width, step value is 1 to do all cssExpand values, - // otherwise step value is 2 to skip over Left and Right - includeWidth = includeWidth ? 1 : 0; - for ( ; i < 4; i += 2 - includeWidth ) { - which = cssExpand[ i ]; - attrs[ "margin" + which ] = attrs[ "padding" + which ] = type; - } - - if ( includeWidth ) { - attrs.opacity = attrs.width = type; - } - - return attrs; -} - -function createTween( value, prop, animation ) { - var tween, - collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ), - index = 0, - length = collection.length; - for ( ; index < length; index++ ) { - if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) { - - // We're done with this property - return tween; - } - } -} - -function defaultPrefilter( elem, props, opts ) { - var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display, - isBox = "width" in props || "height" in props, - anim = this, - orig = {}, - style = elem.style, - hidden = elem.nodeType && isHiddenWithinTree( elem ), - dataShow = dataPriv.get( elem, "fxshow" ); - - // Queue-skipping animations hijack the fx hooks - if ( !opts.queue ) { - hooks = jQuery._queueHooks( elem, "fx" ); - if ( hooks.unqueued == null ) { - hooks.unqueued = 0; - oldfire = hooks.empty.fire; - hooks.empty.fire = function() { - if ( !hooks.unqueued ) { - oldfire(); - } - }; - } - hooks.unqueued++; - - anim.always( function() { - - // Ensure the complete handler is called before this completes - anim.always( function() { - hooks.unqueued--; - if ( !jQuery.queue( elem, "fx" ).length ) { - hooks.empty.fire(); - } - } ); - } ); - } - - // Detect show/hide animations - for ( prop in props ) { - value = props[ prop ]; - if ( rfxtypes.test( value ) ) { - delete props[ prop ]; - toggle = toggle || value === "toggle"; - if ( value === ( hidden ? "hide" : "show" ) ) { - - // Pretend to be hidden if this is a "show" and - // there is still data from a stopped show/hide - if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) { - hidden = true; - - // Ignore all other no-op show/hide data - } else { - continue; - } - } - orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop ); - } - } - - // Bail out if this is a no-op like .hide().hide() - propTween = !jQuery.isEmptyObject( props ); - if ( !propTween && jQuery.isEmptyObject( orig ) ) { - return; - } - - // Restrict "overflow" and "display" styles during box animations - if ( isBox && elem.nodeType === 1 ) { - - // Support: IE <=9 - 11, Edge 12 - 15 - // Record all 3 overflow attributes because IE does not infer the shorthand - // from identically-valued overflowX and overflowY and Edge just mirrors - // the overflowX value there. - opts.overflow = [ style.overflow, style.overflowX, style.overflowY ]; - - // Identify a display type, preferring old show/hide data over the CSS cascade - restoreDisplay = dataShow && dataShow.display; - if ( restoreDisplay == null ) { - restoreDisplay = dataPriv.get( elem, "display" ); - } - display = jQuery.css( elem, "display" ); - if ( display === "none" ) { - if ( restoreDisplay ) { - display = restoreDisplay; - } else { - - // Get nonempty value(s) by temporarily forcing visibility - showHide( [ elem ], true ); - restoreDisplay = elem.style.display || restoreDisplay; - display = jQuery.css( elem, "display" ); - showHide( [ elem ] ); - } - } - - // Animate inline elements as inline-block - if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) { - if ( jQuery.css( elem, "float" ) === "none" ) { - - // Restore the original display value at the end of pure show/hide animations - if ( !propTween ) { - anim.done( function() { - style.display = restoreDisplay; - } ); - if ( restoreDisplay == null ) { - display = style.display; - restoreDisplay = display === "none" ? "" : display; - } - } - style.display = "inline-block"; - } - } - } - - if ( opts.overflow ) { - style.overflow = "hidden"; - anim.always( function() { - style.overflow = opts.overflow[ 0 ]; - style.overflowX = opts.overflow[ 1 ]; - style.overflowY = opts.overflow[ 2 ]; - } ); - } - - // Implement show/hide animations - propTween = false; - for ( prop in orig ) { - - // General show/hide setup for this element animation - if ( !propTween ) { - if ( dataShow ) { - if ( "hidden" in dataShow ) { - hidden = dataShow.hidden; - } - } else { - dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } ); - } - - // Store hidden/visible for toggle so `.stop().toggle()` "reverses" - if ( toggle ) { - dataShow.hidden = !hidden; - } - - // Show elements before animating them - if ( hidden ) { - showHide( [ elem ], true ); - } - - /* eslint-disable no-loop-func */ - - anim.done( function() { - - /* eslint-enable no-loop-func */ - - // The final step of a "hide" animation is actually hiding the element - if ( !hidden ) { - showHide( [ elem ] ); - } - dataPriv.remove( elem, "fxshow" ); - for ( prop in orig ) { - jQuery.style( elem, prop, orig[ prop ] ); - } - } ); - } - - // Per-property setup - propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim ); - if ( !( prop in dataShow ) ) { - dataShow[ prop ] = propTween.start; - if ( hidden ) { - propTween.end = propTween.start; - propTween.start = 0; - } - } - } -} - -function propFilter( props, specialEasing ) { - var index, name, easing, value, hooks; - - // camelCase, specialEasing and expand cssHook pass - for ( index in props ) { - name = camelCase( index ); - easing = specialEasing[ name ]; - value = props[ index ]; - if ( Array.isArray( value ) ) { - easing = value[ 1 ]; - value = props[ index ] = value[ 0 ]; - } - - if ( index !== name ) { - props[ name ] = value; - delete props[ index ]; - } - - hooks = jQuery.cssHooks[ name ]; - if ( hooks && "expand" in hooks ) { - value = hooks.expand( value ); - delete props[ name ]; - - // Not quite $.extend, this won't overwrite existing keys. - // Reusing 'index' because we have the correct "name" - for ( index in value ) { - if ( !( index in props ) ) { - props[ index ] = value[ index ]; - specialEasing[ index ] = easing; - } - } - } else { - specialEasing[ name ] = easing; - } - } -} - -function Animation( elem, properties, options ) { - var result, - stopped, - index = 0, - length = Animation.prefilters.length, - deferred = jQuery.Deferred().always( function() { - - // Don't match elem in the :animated selector - delete tick.elem; - } ), - tick = function() { - if ( stopped ) { - return false; - } - var currentTime = fxNow || createFxNow(), - remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ), - - // Support: Android 2.3 only - // Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (trac-12497) - temp = remaining / animation.duration || 0, - percent = 1 - temp, - index = 0, - length = animation.tweens.length; - - for ( ; index < length; index++ ) { - animation.tweens[ index ].run( percent ); - } - - deferred.notifyWith( elem, [ animation, percent, remaining ] ); - - // If there's more to do, yield - if ( percent < 1 && length ) { - return remaining; - } - - // If this was an empty animation, synthesize a final progress notification - if ( !length ) { - deferred.notifyWith( elem, [ animation, 1, 0 ] ); - } - - // Resolve the animation and report its conclusion - deferred.resolveWith( elem, [ animation ] ); - return false; - }, - animation = deferred.promise( { - elem: elem, - props: jQuery.extend( {}, properties ), - opts: jQuery.extend( true, { - specialEasing: {}, - easing: jQuery.easing._default - }, options ), - originalProperties: properties, - originalOptions: options, - startTime: fxNow || createFxNow(), - duration: options.duration, - tweens: [], - createTween: function( prop, end ) { - var tween = jQuery.Tween( elem, animation.opts, prop, end, - animation.opts.specialEasing[ prop ] || animation.opts.easing ); - animation.tweens.push( tween ); - return tween; - }, - stop: function( gotoEnd ) { - var index = 0, - - // If we are going to the end, we want to run all the tweens - // otherwise we skip this part - length = gotoEnd ? animation.tweens.length : 0; - if ( stopped ) { - return this; - } - stopped = true; - for ( ; index < length; index++ ) { - animation.tweens[ index ].run( 1 ); - } - - // Resolve when we played the last frame; otherwise, reject - if ( gotoEnd ) { - deferred.notifyWith( elem, [ animation, 1, 0 ] ); - deferred.resolveWith( elem, [ animation, gotoEnd ] ); - } else { - deferred.rejectWith( elem, [ animation, gotoEnd ] ); - } - return this; - } - } ), - props = animation.props; - - propFilter( props, animation.opts.specialEasing ); - - for ( ; index < length; index++ ) { - result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts ); - if ( result ) { - if ( isFunction( result.stop ) ) { - jQuery._queueHooks( animation.elem, animation.opts.queue ).stop = - result.stop.bind( result ); - } - return result; - } - } - - jQuery.map( props, createTween, animation ); - - if ( isFunction( animation.opts.start ) ) { - animation.opts.start.call( elem, animation ); - } - - // Attach callbacks from options - animation - .progress( animation.opts.progress ) - .done( animation.opts.done, animation.opts.complete ) - .fail( animation.opts.fail ) - .always( animation.opts.always ); - - jQuery.fx.timer( - jQuery.extend( tick, { - elem: elem, - anim: animation, - queue: animation.opts.queue - } ) - ); - - return animation; -} - -jQuery.Animation = jQuery.extend( Animation, { - - tweeners: { - "*": [ function( prop, value ) { - var tween = this.createTween( prop, value ); - adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween ); - return tween; - } ] - }, - - tweener: function( props, callback ) { - if ( isFunction( props ) ) { - callback = props; - props = [ "*" ]; - } else { - props = props.match( rnothtmlwhite ); - } - - var prop, - index = 0, - length = props.length; - - for ( ; index < length; index++ ) { - prop = props[ index ]; - Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || []; - Animation.tweeners[ prop ].unshift( callback ); - } - }, - - prefilters: [ defaultPrefilter ], - - prefilter: function( callback, prepend ) { - if ( prepend ) { - Animation.prefilters.unshift( callback ); - } else { - Animation.prefilters.push( callback ); - } - } -} ); - -jQuery.speed = function( speed, easing, fn ) { - var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : { - complete: fn || !fn && easing || - isFunction( speed ) && speed, - duration: speed, - easing: fn && easing || easing && !isFunction( easing ) && easing - }; - - // Go to the end state if fx are off - if ( jQuery.fx.off ) { - opt.duration = 0; - - } else { - if ( typeof opt.duration !== "number" ) { - if ( opt.duration in jQuery.fx.speeds ) { - opt.duration = jQuery.fx.speeds[ opt.duration ]; - - } else { - opt.duration = jQuery.fx.speeds._default; - } - } - } - - // Normalize opt.queue - true/undefined/null -> "fx" - if ( opt.queue == null || opt.queue === true ) { - opt.queue = "fx"; - } - - // Queueing - opt.old = opt.complete; - - opt.complete = function() { - if ( isFunction( opt.old ) ) { - opt.old.call( this ); - } - - if ( opt.queue ) { - jQuery.dequeue( this, opt.queue ); - } - }; - - return opt; -}; - -jQuery.fn.extend( { - fadeTo: function( speed, to, easing, callback ) { - - // Show any hidden elements after setting opacity to 0 - return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show() - - // Animate to the value specified - .end().animate( { opacity: to }, speed, easing, callback ); - }, - animate: function( prop, speed, easing, callback ) { - var empty = jQuery.isEmptyObject( prop ), - optall = jQuery.speed( speed, easing, callback ), - doAnimation = function() { - - // Operate on a copy of prop so per-property easing won't be lost - var anim = Animation( this, jQuery.extend( {}, prop ), optall ); - - // Empty animations, or finishing resolves immediately - if ( empty || dataPriv.get( this, "finish" ) ) { - anim.stop( true ); - } - }; - - doAnimation.finish = doAnimation; - - return empty || optall.queue === false ? - this.each( doAnimation ) : - this.queue( optall.queue, doAnimation ); - }, - stop: function( type, clearQueue, gotoEnd ) { - var stopQueue = function( hooks ) { - var stop = hooks.stop; - delete hooks.stop; - stop( gotoEnd ); - }; - - if ( typeof type !== "string" ) { - gotoEnd = clearQueue; - clearQueue = type; - type = undefined; - } - if ( clearQueue ) { - this.queue( type || "fx", [] ); - } - - return this.each( function() { - var dequeue = true, - index = type != null && type + "queueHooks", - timers = jQuery.timers, - data = dataPriv.get( this ); - - if ( index ) { - if ( data[ index ] && data[ index ].stop ) { - stopQueue( data[ index ] ); - } - } else { - for ( index in data ) { - if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) { - stopQueue( data[ index ] ); - } - } - } - - for ( index = timers.length; index--; ) { - if ( timers[ index ].elem === this && - ( type == null || timers[ index ].queue === type ) ) { - - timers[ index ].anim.stop( gotoEnd ); - dequeue = false; - timers.splice( index, 1 ); - } - } - - // Start the next in the queue if the last step wasn't forced. - // Timers currently will call their complete callbacks, which - // will dequeue but only if they were gotoEnd. - if ( dequeue || !gotoEnd ) { - jQuery.dequeue( this, type ); - } - } ); - }, - finish: function( type ) { - if ( type !== false ) { - type = type || "fx"; - } - return this.each( function() { - var index, - data = dataPriv.get( this ), - queue = data[ type + "queue" ], - hooks = data[ type + "queueHooks" ], - timers = jQuery.timers, - length = queue ? queue.length : 0; - - // Enable finishing flag on private data - data.finish = true; - - // Empty the queue first - jQuery.queue( this, type, [] ); - - if ( hooks && hooks.stop ) { - hooks.stop.call( this, true ); - } - - // Look for any active animations, and finish them - for ( index = timers.length; index--; ) { - if ( timers[ index ].elem === this && timers[ index ].queue === type ) { - timers[ index ].anim.stop( true ); - timers.splice( index, 1 ); - } - } - - // Look for any animations in the old queue and finish them - for ( index = 0; index < length; index++ ) { - if ( queue[ index ] && queue[ index ].finish ) { - queue[ index ].finish.call( this ); - } - } - - // Turn off finishing flag - delete data.finish; - } ); - } -} ); - -jQuery.each( [ "toggle", "show", "hide" ], function( _i, name ) { - var cssFn = jQuery.fn[ name ]; - jQuery.fn[ name ] = function( speed, easing, callback ) { - return speed == null || typeof speed === "boolean" ? - cssFn.apply( this, arguments ) : - this.animate( genFx( name, true ), speed, easing, callback ); - }; -} ); - -// Generate shortcuts for custom animations -jQuery.each( { - slideDown: genFx( "show" ), - slideUp: genFx( "hide" ), - slideToggle: genFx( "toggle" ), - fadeIn: { opacity: "show" }, - fadeOut: { opacity: "hide" }, - fadeToggle: { opacity: "toggle" } -}, function( name, props ) { - jQuery.fn[ name ] = function( speed, easing, callback ) { - return this.animate( props, speed, easing, callback ); - }; -} ); - -jQuery.timers = []; -jQuery.fx.tick = function() { - var timer, - i = 0, - timers = jQuery.timers; - - fxNow = Date.now(); - - for ( ; i < timers.length; i++ ) { - timer = timers[ i ]; - - // Run the timer and safely remove it when done (allowing for external removal) - if ( !timer() && timers[ i ] === timer ) { - timers.splice( i--, 1 ); - } - } - - if ( !timers.length ) { - jQuery.fx.stop(); - } - fxNow = undefined; -}; - -jQuery.fx.timer = function( timer ) { - jQuery.timers.push( timer ); - jQuery.fx.start(); -}; - -jQuery.fx.interval = 13; -jQuery.fx.start = function() { - if ( inProgress ) { - return; - } - - inProgress = true; - schedule(); -}; - -jQuery.fx.stop = function() { - inProgress = null; -}; - -jQuery.fx.speeds = { - slow: 600, - fast: 200, - - // Default speed - _default: 400 -}; - - -// Based off of the plugin by Clint Helfers, with permission. -jQuery.fn.delay = function( time, type ) { - time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; - type = type || "fx"; - - return this.queue( type, function( next, hooks ) { - var timeout = window.setTimeout( next, time ); - hooks.stop = function() { - window.clearTimeout( timeout ); - }; - } ); -}; - - -( function() { - var input = document.createElement( "input" ), - select = document.createElement( "select" ), - opt = select.appendChild( document.createElement( "option" ) ); - - input.type = "checkbox"; - - // Support: Android <=4.3 only - // Default value for a checkbox should be "on" - support.checkOn = input.value !== ""; - - // Support: IE <=11 only - // Must access selectedIndex to make default options select - support.optSelected = opt.selected; - - // Support: IE <=11 only - // An input loses its value after becoming a radio - input = document.createElement( "input" ); - input.value = "t"; - input.type = "radio"; - support.radioValue = input.value === "t"; -} )(); - - -var boolHook, - attrHandle = jQuery.expr.attrHandle; - -jQuery.fn.extend( { - attr: function( name, value ) { - return access( this, jQuery.attr, name, value, arguments.length > 1 ); - }, - - removeAttr: function( name ) { - return this.each( function() { - jQuery.removeAttr( this, name ); - } ); - } -} ); - -jQuery.extend( { - attr: function( elem, name, value ) { - var ret, hooks, - nType = elem.nodeType; - - // Don't get/set attributes on text, comment and attribute nodes - if ( nType === 3 || nType === 8 || nType === 2 ) { - return; - } - - // Fallback to prop when attributes are not supported - if ( typeof elem.getAttribute === "undefined" ) { - return jQuery.prop( elem, name, value ); - } - - // Attribute hooks are determined by the lowercase version - // Grab necessary hook if one is defined - if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { - hooks = jQuery.attrHooks[ name.toLowerCase() ] || - ( jQuery.expr.match.bool.test( name ) ? boolHook : undefined ); - } - - if ( value !== undefined ) { - if ( value === null ) { - jQuery.removeAttr( elem, name ); - return; - } - - if ( hooks && "set" in hooks && - ( ret = hooks.set( elem, value, name ) ) !== undefined ) { - return ret; - } - - elem.setAttribute( name, value + "" ); - return value; - } - - if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { - return ret; - } - - ret = jQuery.find.attr( elem, name ); - - // Non-existent attributes return null, we normalize to undefined - return ret == null ? undefined : ret; - }, - - attrHooks: { - type: { - set: function( elem, value ) { - if ( !support.radioValue && value === "radio" && - nodeName( elem, "input" ) ) { - var val = elem.value; - elem.setAttribute( "type", value ); - if ( val ) { - elem.value = val; - } - return value; - } - } - } - }, - - removeAttr: function( elem, value ) { - var name, - i = 0, - - // Attribute names can contain non-HTML whitespace characters - // https://html.spec.whatwg.org/multipage/syntax.html#attributes-2 - attrNames = value && value.match( rnothtmlwhite ); - - if ( attrNames && elem.nodeType === 1 ) { - while ( ( name = attrNames[ i++ ] ) ) { - elem.removeAttribute( name ); - } - } - } -} ); - -// Hooks for boolean attributes -boolHook = { - set: function( elem, value, name ) { - if ( value === false ) { - - // Remove boolean attributes when set to false - jQuery.removeAttr( elem, name ); - } else { - elem.setAttribute( name, name ); - } - return name; - } -}; - -jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( _i, name ) { - var getter = attrHandle[ name ] || jQuery.find.attr; - - attrHandle[ name ] = function( elem, name, isXML ) { - var ret, handle, - lowercaseName = name.toLowerCase(); - - if ( !isXML ) { - - // Avoid an infinite loop by temporarily removing this function from the getter - handle = attrHandle[ lowercaseName ]; - attrHandle[ lowercaseName ] = ret; - ret = getter( elem, name, isXML ) != null ? - lowercaseName : - null; - attrHandle[ lowercaseName ] = handle; - } - return ret; - }; -} ); - - - - -var rfocusable = /^(?:input|select|textarea|button)$/i, - rclickable = /^(?:a|area)$/i; - -jQuery.fn.extend( { - prop: function( name, value ) { - return access( this, jQuery.prop, name, value, arguments.length > 1 ); - }, - - removeProp: function( name ) { - return this.each( function() { - delete this[ jQuery.propFix[ name ] || name ]; - } ); - } -} ); - -jQuery.extend( { - prop: function( elem, name, value ) { - var ret, hooks, - nType = elem.nodeType; - - // Don't get/set properties on text, comment and attribute nodes - if ( nType === 3 || nType === 8 || nType === 2 ) { - return; - } - - if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) { - - // Fix name and attach hooks - name = jQuery.propFix[ name ] || name; - hooks = jQuery.propHooks[ name ]; - } - - if ( value !== undefined ) { - if ( hooks && "set" in hooks && - ( ret = hooks.set( elem, value, name ) ) !== undefined ) { - return ret; - } - - return ( elem[ name ] = value ); - } - - if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) { - return ret; - } - - return elem[ name ]; - }, - - propHooks: { - tabIndex: { - get: function( elem ) { - - // Support: IE <=9 - 11 only - // elem.tabIndex doesn't always return the - // correct value when it hasn't been explicitly set - // Use proper attribute retrieval (trac-12072) - var tabindex = jQuery.find.attr( elem, "tabindex" ); - - if ( tabindex ) { - return parseInt( tabindex, 10 ); - } - - if ( - rfocusable.test( elem.nodeName ) || - rclickable.test( elem.nodeName ) && - elem.href - ) { - return 0; - } - - return -1; - } - } - }, - - propFix: { - "for": "htmlFor", - "class": "className" - } -} ); - -// Support: IE <=11 only -// Accessing the selectedIndex property -// forces the browser to respect setting selected -// on the option -// The getter ensures a default option is selected -// when in an optgroup -// eslint rule "no-unused-expressions" is disabled for this code -// since it considers such accessions noop -if ( !support.optSelected ) { - jQuery.propHooks.selected = { - get: function( elem ) { - - /* eslint no-unused-expressions: "off" */ - - var parent = elem.parentNode; - if ( parent && parent.parentNode ) { - parent.parentNode.selectedIndex; - } - return null; - }, - set: function( elem ) { - - /* eslint no-unused-expressions: "off" */ - - var parent = elem.parentNode; - if ( parent ) { - parent.selectedIndex; - - if ( parent.parentNode ) { - parent.parentNode.selectedIndex; - } - } - } - }; -} - -jQuery.each( [ - "tabIndex", - "readOnly", - "maxLength", - "cellSpacing", - "cellPadding", - "rowSpan", - "colSpan", - "useMap", - "frameBorder", - "contentEditable" -], function() { - jQuery.propFix[ this.toLowerCase() ] = this; -} ); - - - - - // Strip and collapse whitespace according to HTML spec - // https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace - function stripAndCollapse( value ) { - var tokens = value.match( rnothtmlwhite ) || []; - return tokens.join( " " ); - } - - -function getClass( elem ) { - return elem.getAttribute && elem.getAttribute( "class" ) || ""; -} - -function classesToArray( value ) { - if ( Array.isArray( value ) ) { - return value; - } - if ( typeof value === "string" ) { - return value.match( rnothtmlwhite ) || []; - } - return []; -} - -jQuery.fn.extend( { - addClass: function( value ) { - var classNames, cur, curValue, className, i, finalValue; - - if ( isFunction( value ) ) { - return this.each( function( j ) { - jQuery( this ).addClass( value.call( this, j, getClass( this ) ) ); - } ); - } - - classNames = classesToArray( value ); - - if ( classNames.length ) { - return this.each( function() { - curValue = getClass( this ); - cur = this.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); - - if ( cur ) { - for ( i = 0; i < classNames.length; i++ ) { - className = classNames[ i ]; - if ( cur.indexOf( " " + className + " " ) < 0 ) { - cur += className + " "; - } - } - - // Only assign if different to avoid unneeded rendering. - finalValue = stripAndCollapse( cur ); - if ( curValue !== finalValue ) { - this.setAttribute( "class", finalValue ); - } - } - } ); - } - - return this; - }, - - removeClass: function( value ) { - var classNames, cur, curValue, className, i, finalValue; - - if ( isFunction( value ) ) { - return this.each( function( j ) { - jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) ); - } ); - } - - if ( !arguments.length ) { - return this.attr( "class", "" ); - } - - classNames = classesToArray( value ); - - if ( classNames.length ) { - return this.each( function() { - curValue = getClass( this ); - - // This expression is here for better compressibility (see addClass) - cur = this.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); - - if ( cur ) { - for ( i = 0; i < classNames.length; i++ ) { - className = classNames[ i ]; - - // Remove *all* instances - while ( cur.indexOf( " " + className + " " ) > -1 ) { - cur = cur.replace( " " + className + " ", " " ); - } - } - - // Only assign if different to avoid unneeded rendering. - finalValue = stripAndCollapse( cur ); - if ( curValue !== finalValue ) { - this.setAttribute( "class", finalValue ); - } - } - } ); - } - - return this; - }, - - toggleClass: function( value, stateVal ) { - var classNames, className, i, self, - type = typeof value, - isValidValue = type === "string" || Array.isArray( value ); - - if ( isFunction( value ) ) { - return this.each( function( i ) { - jQuery( this ).toggleClass( - value.call( this, i, getClass( this ), stateVal ), - stateVal - ); - } ); - } - - if ( typeof stateVal === "boolean" && isValidValue ) { - return stateVal ? this.addClass( value ) : this.removeClass( value ); - } - - classNames = classesToArray( value ); - - return this.each( function() { - if ( isValidValue ) { - - // Toggle individual class names - self = jQuery( this ); - - for ( i = 0; i < classNames.length; i++ ) { - className = classNames[ i ]; - - // Check each className given, space separated list - if ( self.hasClass( className ) ) { - self.removeClass( className ); - } else { - self.addClass( className ); - } - } - - // Toggle whole class name - } else if ( value === undefined || type === "boolean" ) { - className = getClass( this ); - if ( className ) { - - // Store className if set - dataPriv.set( this, "__className__", className ); - } - - // If the element has a class name or if we're passed `false`, - // then remove the whole classname (if there was one, the above saved it). - // Otherwise bring back whatever was previously saved (if anything), - // falling back to the empty string if nothing was stored. - if ( this.setAttribute ) { - this.setAttribute( "class", - className || value === false ? - "" : - dataPriv.get( this, "__className__" ) || "" - ); - } - } - } ); - }, - - hasClass: function( selector ) { - var className, elem, - i = 0; - - className = " " + selector + " "; - while ( ( elem = this[ i++ ] ) ) { - if ( elem.nodeType === 1 && - ( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) { - return true; - } - } - - return false; - } -} ); - - - - -var rreturn = /\r/g; - -jQuery.fn.extend( { - val: function( value ) { - var hooks, ret, valueIsFunction, - elem = this[ 0 ]; - - if ( !arguments.length ) { - if ( elem ) { - hooks = jQuery.valHooks[ elem.type ] || - jQuery.valHooks[ elem.nodeName.toLowerCase() ]; - - if ( hooks && - "get" in hooks && - ( ret = hooks.get( elem, "value" ) ) !== undefined - ) { - return ret; - } - - ret = elem.value; - - // Handle most common string cases - if ( typeof ret === "string" ) { - return ret.replace( rreturn, "" ); - } - - // Handle cases where value is null/undef or number - return ret == null ? "" : ret; - } - - return; - } - - valueIsFunction = isFunction( value ); - - return this.each( function( i ) { - var val; - - if ( this.nodeType !== 1 ) { - return; - } - - if ( valueIsFunction ) { - val = value.call( this, i, jQuery( this ).val() ); - } else { - val = value; - } - - // Treat null/undefined as ""; convert numbers to string - if ( val == null ) { - val = ""; - - } else if ( typeof val === "number" ) { - val += ""; - - } else if ( Array.isArray( val ) ) { - val = jQuery.map( val, function( value ) { - return value == null ? "" : value + ""; - } ); - } - - hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; - - // If set returns undefined, fall back to normal setting - if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) { - this.value = val; - } - } ); - } -} ); - -jQuery.extend( { - valHooks: { - option: { - get: function( elem ) { - - var val = jQuery.find.attr( elem, "value" ); - return val != null ? - val : - - // Support: IE <=10 - 11 only - // option.text throws exceptions (trac-14686, trac-14858) - // Strip and collapse whitespace - // https://html.spec.whatwg.org/#strip-and-collapse-whitespace - stripAndCollapse( jQuery.text( elem ) ); - } - }, - select: { - get: function( elem ) { - var value, option, i, - options = elem.options, - index = elem.selectedIndex, - one = elem.type === "select-one", - values = one ? null : [], - max = one ? index + 1 : options.length; - - if ( index < 0 ) { - i = max; - - } else { - i = one ? index : 0; - } - - // Loop through all the selected options - for ( ; i < max; i++ ) { - option = options[ i ]; - - // Support: IE <=9 only - // IE8-9 doesn't update selected after form reset (trac-2551) - if ( ( option.selected || i === index ) && - - // Don't return options that are disabled or in a disabled optgroup - !option.disabled && - ( !option.parentNode.disabled || - !nodeName( option.parentNode, "optgroup" ) ) ) { - - // Get the specific value for the option - value = jQuery( option ).val(); - - // We don't need an array for one selects - if ( one ) { - return value; - } - - // Multi-Selects return an array - values.push( value ); - } - } - - return values; - }, - - set: function( elem, value ) { - var optionSet, option, - options = elem.options, - values = jQuery.makeArray( value ), - i = options.length; - - while ( i-- ) { - option = options[ i ]; - - /* eslint-disable no-cond-assign */ - - if ( option.selected = - jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1 - ) { - optionSet = true; - } - - /* eslint-enable no-cond-assign */ - } - - // Force browsers to behave consistently when non-matching value is set - if ( !optionSet ) { - elem.selectedIndex = -1; - } - return values; - } - } - } -} ); - -// Radios and checkboxes getter/setter -jQuery.each( [ "radio", "checkbox" ], function() { - jQuery.valHooks[ this ] = { - set: function( elem, value ) { - if ( Array.isArray( value ) ) { - return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 ); - } - } - }; - if ( !support.checkOn ) { - jQuery.valHooks[ this ].get = function( elem ) { - return elem.getAttribute( "value" ) === null ? "on" : elem.value; - }; - } -} ); - - - - -// Return jQuery for attributes-only inclusion -var location = window.location; - -var nonce = { guid: Date.now() }; - -var rquery = ( /\?/ ); - - - -// Cross-browser xml parsing -jQuery.parseXML = function( data ) { - var xml, parserErrorElem; - if ( !data || typeof data !== "string" ) { - return null; - } - - // Support: IE 9 - 11 only - // IE throws on parseFromString with invalid input. - try { - xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" ); - } catch ( e ) {} - - parserErrorElem = xml && xml.getElementsByTagName( "parsererror" )[ 0 ]; - if ( !xml || parserErrorElem ) { - jQuery.error( "Invalid XML: " + ( - parserErrorElem ? - jQuery.map( parserErrorElem.childNodes, function( el ) { - return el.textContent; - } ).join( "\n" ) : - data - ) ); - } - return xml; -}; - - -var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, - stopPropagationCallback = function( e ) { - e.stopPropagation(); - }; - -jQuery.extend( jQuery.event, { - - trigger: function( event, data, elem, onlyHandlers ) { - - var i, cur, tmp, bubbleType, ontype, handle, special, lastElement, - eventPath = [ elem || document ], - type = hasOwn.call( event, "type" ) ? event.type : event, - namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : []; - - cur = lastElement = tmp = elem = elem || document; - - // Don't do events on text and comment nodes - if ( elem.nodeType === 3 || elem.nodeType === 8 ) { - return; - } - - // focus/blur morphs to focusin/out; ensure we're not firing them right now - if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { - return; - } - - if ( type.indexOf( "." ) > -1 ) { - - // Namespaced trigger; create a regexp to match event type in handle() - namespaces = type.split( "." ); - type = namespaces.shift(); - namespaces.sort(); - } - ontype = type.indexOf( ":" ) < 0 && "on" + type; - - // Caller can pass in a jQuery.Event object, Object, or just an event type string - event = event[ jQuery.expando ] ? - event : - new jQuery.Event( type, typeof event === "object" && event ); - - // Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true) - event.isTrigger = onlyHandlers ? 2 : 3; - event.namespace = namespaces.join( "." ); - event.rnamespace = event.namespace ? - new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) : - null; - - // Clean up the event in case it is being reused - event.result = undefined; - if ( !event.target ) { - event.target = elem; - } - - // Clone any incoming data and prepend the event, creating the handler arg list - data = data == null ? - [ event ] : - jQuery.makeArray( data, [ event ] ); - - // Allow special events to draw outside the lines - special = jQuery.event.special[ type ] || {}; - if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) { - return; - } - - // Determine event propagation path in advance, per W3C events spec (trac-9951) - // Bubble up to document, then to window; watch for a global ownerDocument var (trac-9724) - if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) { - - bubbleType = special.delegateType || type; - if ( !rfocusMorph.test( bubbleType + type ) ) { - cur = cur.parentNode; - } - for ( ; cur; cur = cur.parentNode ) { - eventPath.push( cur ); - tmp = cur; - } - - // Only add window if we got to document (e.g., not plain obj or detached DOM) - if ( tmp === ( elem.ownerDocument || document ) ) { - eventPath.push( tmp.defaultView || tmp.parentWindow || window ); - } - } - - // Fire handlers on the event path - i = 0; - while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) { - lastElement = cur; - event.type = i > 1 ? - bubbleType : - special.bindType || type; - - // jQuery handler - handle = ( dataPriv.get( cur, "events" ) || Object.create( null ) )[ event.type ] && - dataPriv.get( cur, "handle" ); - if ( handle ) { - handle.apply( cur, data ); - } - - // Native handler - handle = ontype && cur[ ontype ]; - if ( handle && handle.apply && acceptData( cur ) ) { - event.result = handle.apply( cur, data ); - if ( event.result === false ) { - event.preventDefault(); - } - } - } - event.type = type; - - // If nobody prevented the default action, do it now - if ( !onlyHandlers && !event.isDefaultPrevented() ) { - - if ( ( !special._default || - special._default.apply( eventPath.pop(), data ) === false ) && - acceptData( elem ) ) { - - // Call a native DOM method on the target with the same name as the event. - // Don't do default actions on window, that's where global variables be (trac-6170) - if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) { - - // Don't re-trigger an onFOO event when we call its FOO() method - tmp = elem[ ontype ]; - - if ( tmp ) { - elem[ ontype ] = null; - } - - // Prevent re-triggering of the same event, since we already bubbled it above - jQuery.event.triggered = type; - - if ( event.isPropagationStopped() ) { - lastElement.addEventListener( type, stopPropagationCallback ); - } - - elem[ type ](); - - if ( event.isPropagationStopped() ) { - lastElement.removeEventListener( type, stopPropagationCallback ); - } - - jQuery.event.triggered = undefined; - - if ( tmp ) { - elem[ ontype ] = tmp; - } - } - } - } - - return event.result; - }, - - // Piggyback on a donor event to simulate a different one - // Used only for `focus(in | out)` events - simulate: function( type, elem, event ) { - var e = jQuery.extend( - new jQuery.Event(), - event, - { - type: type, - isSimulated: true - } - ); - - jQuery.event.trigger( e, null, elem ); - } - -} ); - -jQuery.fn.extend( { - - trigger: function( type, data ) { - return this.each( function() { - jQuery.event.trigger( type, data, this ); - } ); - }, - triggerHandler: function( type, data ) { - var elem = this[ 0 ]; - if ( elem ) { - return jQuery.event.trigger( type, data, elem, true ); - } - } -} ); - - -var - rbracket = /\[\]$/, - rCRLF = /\r?\n/g, - rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i, - rsubmittable = /^(?:input|select|textarea|keygen)/i; - -function buildParams( prefix, obj, traditional, add ) { - var name; - - if ( Array.isArray( obj ) ) { - - // Serialize array item. - jQuery.each( obj, function( i, v ) { - if ( traditional || rbracket.test( prefix ) ) { - - // Treat each array item as a scalar. - add( prefix, v ); - - } else { - - // Item is non-scalar (array or object), encode its numeric index. - buildParams( - prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]", - v, - traditional, - add - ); - } - } ); - - } else if ( !traditional && toType( obj ) === "object" ) { - - // Serialize object item. - for ( name in obj ) { - buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add ); - } - - } else { - - // Serialize scalar item. - add( prefix, obj ); - } -} - -// Serialize an array of form elements or a set of -// key/values into a query string -jQuery.param = function( a, traditional ) { - var prefix, - s = [], - add = function( key, valueOrFunction ) { - - // If value is a function, invoke it and use its return value - var value = isFunction( valueOrFunction ) ? - valueOrFunction() : - valueOrFunction; - - s[ s.length ] = encodeURIComponent( key ) + "=" + - encodeURIComponent( value == null ? "" : value ); - }; - - if ( a == null ) { - return ""; - } - - // If an array was passed in, assume that it is an array of form elements. - if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) { - - // Serialize the form elements - jQuery.each( a, function() { - add( this.name, this.value ); - } ); - - } else { - - // If traditional, encode the "old" way (the way 1.3.2 or older - // did it), otherwise encode params recursively. - for ( prefix in a ) { - buildParams( prefix, a[ prefix ], traditional, add ); - } - } - - // Return the resulting serialization - return s.join( "&" ); -}; - -jQuery.fn.extend( { - serialize: function() { - return jQuery.param( this.serializeArray() ); - }, - serializeArray: function() { - return this.map( function() { - - // Can add propHook for "elements" to filter or add form elements - var elements = jQuery.prop( this, "elements" ); - return elements ? jQuery.makeArray( elements ) : this; - } ).filter( function() { - var type = this.type; - - // Use .is( ":disabled" ) so that fieldset[disabled] works - return this.name && !jQuery( this ).is( ":disabled" ) && - rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) && - ( this.checked || !rcheckableType.test( type ) ); - } ).map( function( _i, elem ) { - var val = jQuery( this ).val(); - - if ( val == null ) { - return null; - } - - if ( Array.isArray( val ) ) { - return jQuery.map( val, function( val ) { - return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; - } ); - } - - return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; - } ).get(); - } -} ); - - -var - r20 = /%20/g, - rhash = /#.*$/, - rantiCache = /([?&])_=[^&]*/, - rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg, - - // trac-7653, trac-8125, trac-8152: local protocol detection - rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/, - rnoContent = /^(?:GET|HEAD)$/, - rprotocol = /^\/\//, - - /* Prefilters - * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) - * 2) These are called: - * - BEFORE asking for a transport - * - AFTER param serialization (s.data is a string if s.processData is true) - * 3) key is the dataType - * 4) the catchall symbol "*" can be used - * 5) execution will start with transport dataType and THEN continue down to "*" if needed - */ - prefilters = {}, - - /* Transports bindings - * 1) key is the dataType - * 2) the catchall symbol "*" can be used - * 3) selection will start with transport dataType and THEN go to "*" if needed - */ - transports = {}, - - // Avoid comment-prolog char sequence (trac-10098); must appease lint and evade compression - allTypes = "*/".concat( "*" ), - - // Anchor tag for parsing the document origin - originAnchor = document.createElement( "a" ); - -originAnchor.href = location.href; - -// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport -function addToPrefiltersOrTransports( structure ) { - - // dataTypeExpression is optional and defaults to "*" - return function( dataTypeExpression, func ) { - - if ( typeof dataTypeExpression !== "string" ) { - func = dataTypeExpression; - dataTypeExpression = "*"; - } - - var dataType, - i = 0, - dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || []; - - if ( isFunction( func ) ) { - - // For each dataType in the dataTypeExpression - while ( ( dataType = dataTypes[ i++ ] ) ) { - - // Prepend if requested - if ( dataType[ 0 ] === "+" ) { - dataType = dataType.slice( 1 ) || "*"; - ( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func ); - - // Otherwise append - } else { - ( structure[ dataType ] = structure[ dataType ] || [] ).push( func ); - } - } - } - }; -} - -// Base inspection function for prefilters and transports -function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) { - - var inspected = {}, - seekingTransport = ( structure === transports ); - - function inspect( dataType ) { - var selected; - inspected[ dataType ] = true; - jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) { - var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR ); - if ( typeof dataTypeOrTransport === "string" && - !seekingTransport && !inspected[ dataTypeOrTransport ] ) { - - options.dataTypes.unshift( dataTypeOrTransport ); - inspect( dataTypeOrTransport ); - return false; - } else if ( seekingTransport ) { - return !( selected = dataTypeOrTransport ); - } - } ); - return selected; - } - - return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" ); -} - -// A special extend for ajax options -// that takes "flat" options (not to be deep extended) -// Fixes trac-9887 -function ajaxExtend( target, src ) { - var key, deep, - flatOptions = jQuery.ajaxSettings.flatOptions || {}; - - for ( key in src ) { - if ( src[ key ] !== undefined ) { - ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ]; - } - } - if ( deep ) { - jQuery.extend( true, target, deep ); - } - - return target; -} - -/* Handles responses to an ajax request: - * - finds the right dataType (mediates between content-type and expected dataType) - * - returns the corresponding response - */ -function ajaxHandleResponses( s, jqXHR, responses ) { - - var ct, type, finalDataType, firstDataType, - contents = s.contents, - dataTypes = s.dataTypes; - - // Remove auto dataType and get content-type in the process - while ( dataTypes[ 0 ] === "*" ) { - dataTypes.shift(); - if ( ct === undefined ) { - ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" ); - } - } - - // Check if we're dealing with a known content-type - if ( ct ) { - for ( type in contents ) { - if ( contents[ type ] && contents[ type ].test( ct ) ) { - dataTypes.unshift( type ); - break; - } - } - } - - // Check to see if we have a response for the expected dataType - if ( dataTypes[ 0 ] in responses ) { - finalDataType = dataTypes[ 0 ]; - } else { - - // Try convertible dataTypes - for ( type in responses ) { - if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) { - finalDataType = type; - break; - } - if ( !firstDataType ) { - firstDataType = type; - } - } - - // Or just use first one - finalDataType = finalDataType || firstDataType; - } - - // If we found a dataType - // We add the dataType to the list if needed - // and return the corresponding response - if ( finalDataType ) { - if ( finalDataType !== dataTypes[ 0 ] ) { - dataTypes.unshift( finalDataType ); - } - return responses[ finalDataType ]; - } -} - -/* Chain conversions given the request and the original response - * Also sets the responseXXX fields on the jqXHR instance - */ -function ajaxConvert( s, response, jqXHR, isSuccess ) { - var conv2, current, conv, tmp, prev, - converters = {}, - - // Work with a copy of dataTypes in case we need to modify it for conversion - dataTypes = s.dataTypes.slice(); - - // Create converters map with lowercased keys - if ( dataTypes[ 1 ] ) { - for ( conv in s.converters ) { - converters[ conv.toLowerCase() ] = s.converters[ conv ]; - } - } - - current = dataTypes.shift(); - - // Convert to each sequential dataType - while ( current ) { - - if ( s.responseFields[ current ] ) { - jqXHR[ s.responseFields[ current ] ] = response; - } - - // Apply the dataFilter if provided - if ( !prev && isSuccess && s.dataFilter ) { - response = s.dataFilter( response, s.dataType ); - } - - prev = current; - current = dataTypes.shift(); - - if ( current ) { - - // There's only work to do if current dataType is non-auto - if ( current === "*" ) { - - current = prev; - - // Convert response if prev dataType is non-auto and differs from current - } else if ( prev !== "*" && prev !== current ) { - - // Seek a direct converter - conv = converters[ prev + " " + current ] || converters[ "* " + current ]; - - // If none found, seek a pair - if ( !conv ) { - for ( conv2 in converters ) { - - // If conv2 outputs current - tmp = conv2.split( " " ); - if ( tmp[ 1 ] === current ) { - - // If prev can be converted to accepted input - conv = converters[ prev + " " + tmp[ 0 ] ] || - converters[ "* " + tmp[ 0 ] ]; - if ( conv ) { - - // Condense equivalence converters - if ( conv === true ) { - conv = converters[ conv2 ]; - - // Otherwise, insert the intermediate dataType - } else if ( converters[ conv2 ] !== true ) { - current = tmp[ 0 ]; - dataTypes.unshift( tmp[ 1 ] ); - } - break; - } - } - } - } - - // Apply converter (if not an equivalence) - if ( conv !== true ) { - - // Unless errors are allowed to bubble, catch and return them - if ( conv && s.throws ) { - response = conv( response ); - } else { - try { - response = conv( response ); - } catch ( e ) { - return { - state: "parsererror", - error: conv ? e : "No conversion from " + prev + " to " + current - }; - } - } - } - } - } - } - - return { state: "success", data: response }; -} - -jQuery.extend( { - - // Counter for holding the number of active queries - active: 0, - - // Last-Modified header cache for next request - lastModified: {}, - etag: {}, - - ajaxSettings: { - url: location.href, - type: "GET", - isLocal: rlocalProtocol.test( location.protocol ), - global: true, - processData: true, - async: true, - contentType: "application/x-www-form-urlencoded; charset=UTF-8", - - /* - timeout: 0, - data: null, - dataType: null, - username: null, - password: null, - cache: null, - throws: false, - traditional: false, - headers: {}, - */ - - accepts: { - "*": allTypes, - text: "text/plain", - html: "text/html", - xml: "application/xml, text/xml", - json: "application/json, text/javascript" - }, - - contents: { - xml: /\bxml\b/, - html: /\bhtml/, - json: /\bjson\b/ - }, - - responseFields: { - xml: "responseXML", - text: "responseText", - json: "responseJSON" - }, - - // Data converters - // Keys separate source (or catchall "*") and destination types with a single space - converters: { - - // Convert anything to text - "* text": String, - - // Text to html (true = no transformation) - "text html": true, - - // Evaluate text as a json expression - "text json": JSON.parse, - - // Parse text as xml - "text xml": jQuery.parseXML - }, - - // For options that shouldn't be deep extended: - // you can add your own custom options here if - // and when you create one that shouldn't be - // deep extended (see ajaxExtend) - flatOptions: { - url: true, - context: true - } - }, - - // Creates a full fledged settings object into target - // with both ajaxSettings and settings fields. - // If target is omitted, writes into ajaxSettings. - ajaxSetup: function( target, settings ) { - return settings ? - - // Building a settings object - ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) : - - // Extending ajaxSettings - ajaxExtend( jQuery.ajaxSettings, target ); - }, - - ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), - ajaxTransport: addToPrefiltersOrTransports( transports ), - - // Main method - ajax: function( url, options ) { - - // If url is an object, simulate pre-1.5 signature - if ( typeof url === "object" ) { - options = url; - url = undefined; - } - - // Force options to be an object - options = options || {}; - - var transport, - - // URL without anti-cache param - cacheURL, - - // Response headers - responseHeadersString, - responseHeaders, - - // timeout handle - timeoutTimer, - - // Url cleanup var - urlAnchor, - - // Request state (becomes false upon send and true upon completion) - completed, - - // To know if global events are to be dispatched - fireGlobals, - - // Loop variable - i, - - // uncached part of the url - uncached, - - // Create the final options object - s = jQuery.ajaxSetup( {}, options ), - - // Callbacks context - callbackContext = s.context || s, - - // Context for global events is callbackContext if it is a DOM node or jQuery collection - globalEventContext = s.context && - ( callbackContext.nodeType || callbackContext.jquery ) ? - jQuery( callbackContext ) : - jQuery.event, - - // Deferreds - deferred = jQuery.Deferred(), - completeDeferred = jQuery.Callbacks( "once memory" ), - - // Status-dependent callbacks - statusCode = s.statusCode || {}, - - // Headers (they are sent all at once) - requestHeaders = {}, - requestHeadersNames = {}, - - // Default abort message - strAbort = "canceled", - - // Fake xhr - jqXHR = { - readyState: 0, - - // Builds headers hashtable if needed - getResponseHeader: function( key ) { - var match; - if ( completed ) { - if ( !responseHeaders ) { - responseHeaders = {}; - while ( ( match = rheaders.exec( responseHeadersString ) ) ) { - responseHeaders[ match[ 1 ].toLowerCase() + " " ] = - ( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] ) - .concat( match[ 2 ] ); - } - } - match = responseHeaders[ key.toLowerCase() + " " ]; - } - return match == null ? null : match.join( ", " ); - }, - - // Raw string - getAllResponseHeaders: function() { - return completed ? responseHeadersString : null; - }, - - // Caches the header - setRequestHeader: function( name, value ) { - if ( completed == null ) { - name = requestHeadersNames[ name.toLowerCase() ] = - requestHeadersNames[ name.toLowerCase() ] || name; - requestHeaders[ name ] = value; - } - return this; - }, - - // Overrides response content-type header - overrideMimeType: function( type ) { - if ( completed == null ) { - s.mimeType = type; - } - return this; - }, - - // Status-dependent callbacks - statusCode: function( map ) { - var code; - if ( map ) { - if ( completed ) { - - // Execute the appropriate callbacks - jqXHR.always( map[ jqXHR.status ] ); - } else { - - // Lazy-add the new callbacks in a way that preserves old ones - for ( code in map ) { - statusCode[ code ] = [ statusCode[ code ], map[ code ] ]; - } - } - } - return this; - }, - - // Cancel the request - abort: function( statusText ) { - var finalText = statusText || strAbort; - if ( transport ) { - transport.abort( finalText ); - } - done( 0, finalText ); - return this; - } - }; - - // Attach deferreds - deferred.promise( jqXHR ); - - // Add protocol if not provided (prefilters might expect it) - // Handle falsy url in the settings object (trac-10093: consistency with old signature) - // We also use the url parameter if available - s.url = ( ( url || s.url || location.href ) + "" ) - .replace( rprotocol, location.protocol + "//" ); - - // Alias method option to type as per ticket trac-12004 - s.type = options.method || options.type || s.method || s.type; - - // Extract dataTypes list - s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ]; - - // A cross-domain request is in order when the origin doesn't match the current origin. - if ( s.crossDomain == null ) { - urlAnchor = document.createElement( "a" ); - - // Support: IE <=8 - 11, Edge 12 - 15 - // IE throws exception on accessing the href property if url is malformed, - // e.g. http://example.com:80x/ - try { - urlAnchor.href = s.url; - - // Support: IE <=8 - 11 only - // Anchor's host property isn't correctly set when s.url is relative - urlAnchor.href = urlAnchor.href; - s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !== - urlAnchor.protocol + "//" + urlAnchor.host; - } catch ( e ) { - - // If there is an error parsing the URL, assume it is crossDomain, - // it can be rejected by the transport if it is invalid - s.crossDomain = true; - } - } - - // Convert data if not already a string - if ( s.data && s.processData && typeof s.data !== "string" ) { - s.data = jQuery.param( s.data, s.traditional ); - } - - // Apply prefilters - inspectPrefiltersOrTransports( prefilters, s, options, jqXHR ); - - // If request was aborted inside a prefilter, stop there - if ( completed ) { - return jqXHR; - } - - // We can fire global events as of now if asked to - // Don't fire events if jQuery.event is undefined in an AMD-usage scenario (trac-15118) - fireGlobals = jQuery.event && s.global; - - // Watch for a new set of requests - if ( fireGlobals && jQuery.active++ === 0 ) { - jQuery.event.trigger( "ajaxStart" ); - } - - // Uppercase the type - s.type = s.type.toUpperCase(); - - // Determine if request has content - s.hasContent = !rnoContent.test( s.type ); - - // Save the URL in case we're toying with the If-Modified-Since - // and/or If-None-Match header later on - // Remove hash to simplify url manipulation - cacheURL = s.url.replace( rhash, "" ); - - // More options handling for requests with no content - if ( !s.hasContent ) { - - // Remember the hash so we can put it back - uncached = s.url.slice( cacheURL.length ); - - // If data is available and should be processed, append data to url - if ( s.data && ( s.processData || typeof s.data === "string" ) ) { - cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data; - - // trac-9682: remove data so that it's not used in an eventual retry - delete s.data; - } - - // Add or update anti-cache param if needed - if ( s.cache === false ) { - cacheURL = cacheURL.replace( rantiCache, "$1" ); - uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce.guid++ ) + - uncached; - } - - // Put hash and anti-cache on the URL that will be requested (gh-1732) - s.url = cacheURL + uncached; - - // Change '%20' to '+' if this is encoded form body content (gh-2658) - } else if ( s.data && s.processData && - ( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) { - s.data = s.data.replace( r20, "+" ); - } - - // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. - if ( s.ifModified ) { - if ( jQuery.lastModified[ cacheURL ] ) { - jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] ); - } - if ( jQuery.etag[ cacheURL ] ) { - jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] ); - } - } - - // Set the correct header, if data is being sent - if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { - jqXHR.setRequestHeader( "Content-Type", s.contentType ); - } - - // Set the Accepts header for the server, depending on the dataType - jqXHR.setRequestHeader( - "Accept", - s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ? - s.accepts[ s.dataTypes[ 0 ] ] + - ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) : - s.accepts[ "*" ] - ); - - // Check for headers option - for ( i in s.headers ) { - jqXHR.setRequestHeader( i, s.headers[ i ] ); - } - - // Allow custom headers/mimetypes and early abort - if ( s.beforeSend && - ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) { - - // Abort if not done already and return - return jqXHR.abort(); - } - - // Aborting is no longer a cancellation - strAbort = "abort"; - - // Install callbacks on deferreds - completeDeferred.add( s.complete ); - jqXHR.done( s.success ); - jqXHR.fail( s.error ); - - // Get transport - transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR ); - - // If no transport, we auto-abort - if ( !transport ) { - done( -1, "No Transport" ); - } else { - jqXHR.readyState = 1; - - // Send global event - if ( fireGlobals ) { - globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] ); - } - - // If request was aborted inside ajaxSend, stop there - if ( completed ) { - return jqXHR; - } - - // Timeout - if ( s.async && s.timeout > 0 ) { - timeoutTimer = window.setTimeout( function() { - jqXHR.abort( "timeout" ); - }, s.timeout ); - } - - try { - completed = false; - transport.send( requestHeaders, done ); - } catch ( e ) { - - // Rethrow post-completion exceptions - if ( completed ) { - throw e; - } - - // Propagate others as results - done( -1, e ); - } - } - - // Callback for when everything is done - function done( status, nativeStatusText, responses, headers ) { - var isSuccess, success, error, response, modified, - statusText = nativeStatusText; - - // Ignore repeat invocations - if ( completed ) { - return; - } - - completed = true; - - // Clear timeout if it exists - if ( timeoutTimer ) { - window.clearTimeout( timeoutTimer ); - } - - // Dereference transport for early garbage collection - // (no matter how long the jqXHR object will be used) - transport = undefined; - - // Cache response headers - responseHeadersString = headers || ""; - - // Set readyState - jqXHR.readyState = status > 0 ? 4 : 0; - - // Determine if successful - isSuccess = status >= 200 && status < 300 || status === 304; - - // Get response data - if ( responses ) { - response = ajaxHandleResponses( s, jqXHR, responses ); - } - - // Use a noop converter for missing script but not if jsonp - if ( !isSuccess && - jQuery.inArray( "script", s.dataTypes ) > -1 && - jQuery.inArray( "json", s.dataTypes ) < 0 ) { - s.converters[ "text script" ] = function() {}; - } - - // Convert no matter what (that way responseXXX fields are always set) - response = ajaxConvert( s, response, jqXHR, isSuccess ); - - // If successful, handle type chaining - if ( isSuccess ) { - - // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. - if ( s.ifModified ) { - modified = jqXHR.getResponseHeader( "Last-Modified" ); - if ( modified ) { - jQuery.lastModified[ cacheURL ] = modified; - } - modified = jqXHR.getResponseHeader( "etag" ); - if ( modified ) { - jQuery.etag[ cacheURL ] = modified; - } - } - - // if no content - if ( status === 204 || s.type === "HEAD" ) { - statusText = "nocontent"; - - // if not modified - } else if ( status === 304 ) { - statusText = "notmodified"; - - // If we have data, let's convert it - } else { - statusText = response.state; - success = response.data; - error = response.error; - isSuccess = !error; - } - } else { - - // Extract error from statusText and normalize for non-aborts - error = statusText; - if ( status || !statusText ) { - statusText = "error"; - if ( status < 0 ) { - status = 0; - } - } - } - - // Set data for the fake xhr object - jqXHR.status = status; - jqXHR.statusText = ( nativeStatusText || statusText ) + ""; - - // Success/Error - if ( isSuccess ) { - deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] ); - } else { - deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] ); - } - - // Status-dependent callbacks - jqXHR.statusCode( statusCode ); - statusCode = undefined; - - if ( fireGlobals ) { - globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError", - [ jqXHR, s, isSuccess ? success : error ] ); - } - - // Complete - completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] ); - - if ( fireGlobals ) { - globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] ); - - // Handle the global AJAX counter - if ( !( --jQuery.active ) ) { - jQuery.event.trigger( "ajaxStop" ); - } - } - } - - return jqXHR; - }, - - getJSON: function( url, data, callback ) { - return jQuery.get( url, data, callback, "json" ); - }, - - getScript: function( url, callback ) { - return jQuery.get( url, undefined, callback, "script" ); - } -} ); - -jQuery.each( [ "get", "post" ], function( _i, method ) { - jQuery[ method ] = function( url, data, callback, type ) { - - // Shift arguments if data argument was omitted - if ( isFunction( data ) ) { - type = type || callback; - callback = data; - data = undefined; - } - - // The url can be an options object (which then must have .url) - return jQuery.ajax( jQuery.extend( { - url: url, - type: method, - dataType: type, - data: data, - success: callback - }, jQuery.isPlainObject( url ) && url ) ); - }; -} ); - -jQuery.ajaxPrefilter( function( s ) { - var i; - for ( i in s.headers ) { - if ( i.toLowerCase() === "content-type" ) { - s.contentType = s.headers[ i ] || ""; - } - } -} ); - - -jQuery._evalUrl = function( url, options, doc ) { - return jQuery.ajax( { - url: url, - - // Make this explicit, since user can override this through ajaxSetup (trac-11264) - type: "GET", - dataType: "script", - cache: true, - async: false, - global: false, - - // Only evaluate the response if it is successful (gh-4126) - // dataFilter is not invoked for failure responses, so using it instead - // of the default converter is kludgy but it works. - converters: { - "text script": function() {} - }, - dataFilter: function( response ) { - jQuery.globalEval( response, options, doc ); - } - } ); -}; - - -jQuery.fn.extend( { - wrapAll: function( html ) { - var wrap; - - if ( this[ 0 ] ) { - if ( isFunction( html ) ) { - html = html.call( this[ 0 ] ); - } - - // The elements to wrap the target around - wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true ); - - if ( this[ 0 ].parentNode ) { - wrap.insertBefore( this[ 0 ] ); - } - - wrap.map( function() { - var elem = this; - - while ( elem.firstElementChild ) { - elem = elem.firstElementChild; - } - - return elem; - } ).append( this ); - } - - return this; - }, - - wrapInner: function( html ) { - if ( isFunction( html ) ) { - return this.each( function( i ) { - jQuery( this ).wrapInner( html.call( this, i ) ); - } ); - } - - return this.each( function() { - var self = jQuery( this ), - contents = self.contents(); - - if ( contents.length ) { - contents.wrapAll( html ); - - } else { - self.append( html ); - } - } ); - }, - - wrap: function( html ) { - var htmlIsFunction = isFunction( html ); - - return this.each( function( i ) { - jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html ); - } ); - }, - - unwrap: function( selector ) { - this.parent( selector ).not( "body" ).each( function() { - jQuery( this ).replaceWith( this.childNodes ); - } ); - return this; - } -} ); - - -jQuery.expr.pseudos.hidden = function( elem ) { - return !jQuery.expr.pseudos.visible( elem ); -}; -jQuery.expr.pseudos.visible = function( elem ) { - return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length ); -}; - - - - -jQuery.ajaxSettings.xhr = function() { - try { - return new window.XMLHttpRequest(); - } catch ( e ) {} -}; - -var xhrSuccessStatus = { - - // File protocol always yields status code 0, assume 200 - 0: 200, - - // Support: IE <=9 only - // trac-1450: sometimes IE returns 1223 when it should be 204 - 1223: 204 - }, - xhrSupported = jQuery.ajaxSettings.xhr(); - -support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported ); -support.ajax = xhrSupported = !!xhrSupported; - -jQuery.ajaxTransport( function( options ) { - var callback, errorCallback; - - // Cross domain only allowed if supported through XMLHttpRequest - if ( support.cors || xhrSupported && !options.crossDomain ) { - return { - send: function( headers, complete ) { - var i, - xhr = options.xhr(); - - xhr.open( - options.type, - options.url, - options.async, - options.username, - options.password - ); - - // Apply custom fields if provided - if ( options.xhrFields ) { - for ( i in options.xhrFields ) { - xhr[ i ] = options.xhrFields[ i ]; - } - } - - // Override mime type if needed - if ( options.mimeType && xhr.overrideMimeType ) { - xhr.overrideMimeType( options.mimeType ); - } - - // X-Requested-With header - // For cross-domain requests, seeing as conditions for a preflight are - // akin to a jigsaw puzzle, we simply never set it to be sure. - // (it can always be set on a per-request basis or even using ajaxSetup) - // For same-domain requests, won't change header if already provided. - if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) { - headers[ "X-Requested-With" ] = "XMLHttpRequest"; - } - - // Set headers - for ( i in headers ) { - xhr.setRequestHeader( i, headers[ i ] ); - } - - // Callback - callback = function( type ) { - return function() { - if ( callback ) { - callback = errorCallback = xhr.onload = - xhr.onerror = xhr.onabort = xhr.ontimeout = - xhr.onreadystatechange = null; - - if ( type === "abort" ) { - xhr.abort(); - } else if ( type === "error" ) { - - // Support: IE <=9 only - // On a manual native abort, IE9 throws - // errors on any property access that is not readyState - if ( typeof xhr.status !== "number" ) { - complete( 0, "error" ); - } else { - complete( - - // File: protocol always yields status 0; see trac-8605, trac-14207 - xhr.status, - xhr.statusText - ); - } - } else { - complete( - xhrSuccessStatus[ xhr.status ] || xhr.status, - xhr.statusText, - - // Support: IE <=9 only - // IE9 has no XHR2 but throws on binary (trac-11426) - // For XHR2 non-text, let the caller handle it (gh-2498) - ( xhr.responseType || "text" ) !== "text" || - typeof xhr.responseText !== "string" ? - { binary: xhr.response } : - { text: xhr.responseText }, - xhr.getAllResponseHeaders() - ); - } - } - }; - }; - - // Listen to events - xhr.onload = callback(); - errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" ); - - // Support: IE 9 only - // Use onreadystatechange to replace onabort - // to handle uncaught aborts - if ( xhr.onabort !== undefined ) { - xhr.onabort = errorCallback; - } else { - xhr.onreadystatechange = function() { - - // Check readyState before timeout as it changes - if ( xhr.readyState === 4 ) { - - // Allow onerror to be called first, - // but that will not handle a native abort - // Also, save errorCallback to a variable - // as xhr.onerror cannot be accessed - window.setTimeout( function() { - if ( callback ) { - errorCallback(); - } - } ); - } - }; - } - - // Create the abort callback - callback = callback( "abort" ); - - try { - - // Do send the request (this may raise an exception) - xhr.send( options.hasContent && options.data || null ); - } catch ( e ) { - - // trac-14683: Only rethrow if this hasn't been notified as an error yet - if ( callback ) { - throw e; - } - } - }, - - abort: function() { - if ( callback ) { - callback(); - } - } - }; - } -} ); - - - - -// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432) -jQuery.ajaxPrefilter( function( s ) { - if ( s.crossDomain ) { - s.contents.script = false; - } -} ); - -// Install script dataType -jQuery.ajaxSetup( { - accepts: { - script: "text/javascript, application/javascript, " + - "application/ecmascript, application/x-ecmascript" - }, - contents: { - script: /\b(?:java|ecma)script\b/ - }, - converters: { - "text script": function( text ) { - jQuery.globalEval( text ); - return text; - } - } -} ); - -// Handle cache's special case and crossDomain -jQuery.ajaxPrefilter( "script", function( s ) { - if ( s.cache === undefined ) { - s.cache = false; - } - if ( s.crossDomain ) { - s.type = "GET"; - } -} ); - -// Bind script tag hack transport -jQuery.ajaxTransport( "script", function( s ) { - - // This transport only deals with cross domain or forced-by-attrs requests - if ( s.crossDomain || s.scriptAttrs ) { - var script, callback; - return { - send: function( _, complete ) { - script = jQuery( " - """, - """ - """, """ const pathtoroot = "./"; loadScripts(); @@ -437,6 +429,10 @@ public class TestSearch extends JavadocTester { holder="Search documentation (type /)" aria-label="Search in documentation" auto\ complete="off" spellcheck="false">"""); + checkOutput(fileName, false, + "jquery-ui.min.css", + "jquery-3.7.1.min.js", + "jquery-ui.min.js"); } void checkSingleIndex() { @@ -669,14 +665,15 @@ public class TestSearch extends JavadocTester { "AnotherClass.java:68: warning: invalid usage of tag {@index"); } - void checkJqueryAndImageFiles(boolean expectedOutput) { + void checkImageFiles(boolean expectedOutput) { checkFiles(expectedOutput, "script-files/search.js", - "script-files/jquery-3.7.1.min.js", - "script-files/jquery-ui.min.js", - "resource-files/jquery-ui.min.css", "resource-files/x.svg", "resource-files/glass.svg"); + checkFiles(false, + "script-files/jquery-3.7.1.min.js", + "script-files/jquery-ui.min.js", + "resource-files/jquery-ui.min.css"); } void checkSearchJS() { @@ -689,9 +686,7 @@ public class TestSearch extends JavadocTester { "function getURLPrefix(item, category) {", "url += item.l;"); - checkOutput("script-files/search-page.js", true, - "function renderResults(result) {", - "function selectTab(category) {"); + checkFiles(false, "script-files/search-page.js"); checkCssClasses("script-files/search.js", "resource-files/stylesheet.css"); } @@ -701,8 +696,8 @@ public class TestSearch extends JavadocTester { // are also defined as class selectors somewhere in the stylesheet file. String js = readOutputFile(jsFile); Set cssClasses = new TreeSet<>(); - addMatches(js, Pattern.compile("class=\\\\*\"([^\\\\\"]+)\\\\*\""), cssClasses); - addMatches(js, Pattern.compile("attr\\(\"class\", \"([^\"]+)\"\\)"), cssClasses); + addMatches(js, Pattern.compile("class=[\"']([-\\w]+)[\"']"), cssClasses); + addMatches(js, Pattern.compile("classList.add\\([\"']([-\\w]+)[\"']\\)"), cssClasses); // verify that the regex did find use of CSS class names checking("Checking CSS classes found"); if (cssClasses.isEmpty()) { diff --git a/test/langtools/jdk/javadoc/doclet/testStylesheet/TestStylesheet.java b/test/langtools/jdk/javadoc/doclet/testStylesheet/TestStylesheet.java index 012e9ce00de..a2c2a603212 100644 --- a/test/langtools/jdk/javadoc/doclet/testStylesheet/TestStylesheet.java +++ b/test/langtools/jdk/javadoc/doclet/testStylesheet/TestStylesheet.java @@ -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 @@ -132,6 +132,7 @@ public class TestStylesheet extends JavadocTester { min-height:12px; font-size:0; visibility:hidden; + cursor: pointer; }""", """ ::placeholder { diff --git a/test/langtools/jdk/javadoc/tool/api/basic/APITest.java b/test/langtools/jdk/javadoc/tool/api/basic/APITest.java index 71908f34e99..bd8d0cf7953 100644 --- a/test/langtools/jdk/javadoc/tool/api/basic/APITest.java +++ b/test/langtools/jdk/javadoc/tool/api/basic/APITest.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 @@ -207,7 +207,6 @@ class APITest { "resource-files/copy.svg", "resource-files/down.svg", "resource-files/glass.svg", - "resource-files/jquery-ui.min.css", "resource-files/left.svg", "resource-files/link.svg", "resource-files/moon.svg", @@ -241,11 +240,8 @@ class APITest { "resource-files/fonts/DejaVuLGCSerif-Italic.woff2", "resource-files/fonts/DejaVuLGCSerif.woff", "resource-files/fonts/DejaVuLGCSerif.woff2", - "script-files/jquery-3.7.1.min.js", - "script-files/jquery-ui.min.js", "script-files/script.js", "script-files/search.js", - "script-files/search-page.js", "tag-search-index.js", "type-search-index.js" )); @@ -255,11 +251,8 @@ class APITest { !s.endsWith("-search-index.js") && !s.equals("index-all.html") && !s.equals("resource-files/glass.svg") - && !s.equals("resource-files/jquery-ui.min.css") && !s.equals("resource-files/x.svg") - && !s.startsWith("script-files/jquery-") && !s.equals("script-files/search.js") - && !s.equals("script-files/search-page.js") && !s.equals("search.html") && !s.equals("allclasses-index.html") && !s.equals("allpackages-index.html") From 0acc1d1e4fac4de6b48ed05ba0d83c1170bc4389 Mon Sep 17 00:00:00 2001 From: Quan Anh Mai Date: Mon, 13 Apr 2026 06:55:13 +0000 Subject: [PATCH 019/108] 8377163: C2: Iteration split must take into consideration sunk stores Reviewed-by: chagedorn, dfenacci --- src/hotspot/share/opto/loopTransform.cpp | 36 +++++- .../TestIterationSplitWithSunkStores.java | 112 ++++++++++++++++++ 2 files changed, 144 insertions(+), 4 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/loopopts/TestIterationSplitWithSunkStores.java diff --git a/src/hotspot/share/opto/loopTransform.cpp b/src/hotspot/share/opto/loopTransform.cpp index 80b17efb998..60a61b6bb4e 100644 --- a/src/hotspot/share/opto/loopTransform.cpp +++ b/src/hotspot/share/opto/loopTransform.cpp @@ -35,7 +35,9 @@ #include "opto/loopnode.hpp" #include "opto/movenode.hpp" #include "opto/mulnode.hpp" +#include "opto/node.hpp" #include "opto/opaquenode.hpp" +#include "opto/opcodes.hpp" #include "opto/phase.hpp" #include "opto/predicates.hpp" #include "opto/rootnode.hpp" @@ -1774,13 +1776,39 @@ Node *PhaseIdealLoop::insert_post_loop(IdealLoopTree* loop, Node_List& old_new, for (DUIterator i = main_head->outs(); main_head->has_out(i); i++) { Node* main_phi = main_head->out(i); if (main_phi->is_Phi() && main_phi->in(0) == main_head && main_phi->outcnt() > 0) { - Node* cur_phi = old_new[main_phi->_idx]; + Node* post_phi = old_new[main_phi->_idx]; + Node* loopback_input = main_phi->in(LoopNode::LoopBackControl); Node* fallnew = clone_up_backedge_goo(main_head->back_control(), post_head->init_control(), - main_phi->in(LoopNode::LoopBackControl), + loopback_input, visited, clones); - _igvn.hash_delete(cur_phi); - cur_phi->set_req(LoopNode::EntryControl, fallnew); + // Technically, the entry value of post_phi must be the loop back input of the corresponding + // Phi of the outer loop, not the Phi of the inner loop (i.e. main_phi). However, we have not + // constructed the Phis for the OuterStripMinedLoop yet, so the input must be inferred from + // the loop back input of main_phi. + // - If post_phi is a data Phi, then we can use the loop back input of main_phi. + // - If post_phi is a memory Phi, since Stores can be sunk below the inner loop, but still + // inside the outer loop, we have 2 cases: + // + If the loop back input of main_phi is on the backedge, then the entry input of + // post_phi is the clone of the node on the entry of post_head, similar to when post_phi + // is a data Phi. + // + If the loop back input of main_phi is not on the backedge, we need to find whether + // there is a sunk Store corresponding to post_phi, if there is any, the latest such + // store will be the entry input of post_phi. Fortunately, the safepoint at the exit of + // the outer loop captures all memory states, so we can use it as the entry input of + // post_phi. + // Another way to see it is that, the memory phi should capture the latest state at the + // post-loop entry. If loopback_input is cloned by clone_up_backedge_goo, it is pinned at + // the post-loop entry, and is surely the latest state. Otherwise, the latest memory state + // corresponding to post_phi is the memory state at the exit of the outer main-loop, which + // is captured by the safepoint there. + if (main_head->is_strip_mined() && fallnew == loopback_input && post_phi->is_memory_phi()) { + SafePointNode* main_safepoint = main_head->outer_safepoint(); + assert(main_safepoint != nullptr, "outer loop must have a safepoint"); + fallnew = main_safepoint->memory(); + } + _igvn.hash_delete(post_phi); + post_phi->set_req(LoopNode::EntryControl, fallnew); } } // Store nodes that were moved to the outer loop by PhaseIdealLoop::try_move_store_after_loop diff --git a/test/hotspot/jtreg/compiler/loopopts/TestIterationSplitWithSunkStores.java b/test/hotspot/jtreg/compiler/loopopts/TestIterationSplitWithSunkStores.java new file mode 100644 index 00000000000..7138f22beec --- /dev/null +++ b/test/hotspot/jtreg/compiler/loopopts/TestIterationSplitWithSunkStores.java @@ -0,0 +1,112 @@ +/* + * 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. + */ +package compiler.loopopts; + +import java.util.Objects; +import jdk.internal.misc.Unsafe; + +/* + * @test + * @bug 8377163 + * @summary Iteration splitting a counted loop with sunk stores should connect the memory phi of + * the post loop to the sunk store in the main loop, not the store at the loop back input + * of the corresponding phi of the main loop. + * @modules java.base/jdk.internal.misc + * @run main ${test.main.class} + * @run main/othervm -Xbatch -XX:-TieredCompilation ${test.main.class} + */ +public class TestIterationSplitWithSunkStores { + private static final Unsafe U = Unsafe.getUnsafe(); + + public static void main(String[] args) { + test1(); + + int[] array = new int[1000]; + MyInteger v = new MyInteger(0); + for (int i = 0; i < 100; i++) { + test2(array, v, v, v, v); + } + } + + private static void test1() { + int[] dst = new int[5]; + for (long i = 0L; i < 20_000; i++) { + test1(dst, 1); + for (int j = 1; j < 5; j++) { + if (dst[j] != j) { + throw new RuntimeException("Bad copy"); + } + } + } + } + + private static void test1(int[] dst, int dstPos) { + int[] src = new int[4]; + src[0] = new MyInteger(1).v(); + src[1] = 2; + src[2] = 3; + src[3] = 4; + System.arraycopy(src, 0, dst, dstPos, 4); + } + + private static void test2(int[] array, MyInteger v1, MyInteger v2, MyInteger v3, MyInteger v4) { + Objects.requireNonNull(array); + Objects.requireNonNull(v1); + Objects.requireNonNull(v2); + Objects.requireNonNull(v3); + Objects.requireNonNull(v4); + + // Using Unsafe to access the array so that the stores can be sunk without loop + // predication. This is because store sinking is only attempted during the first and the + // last loop opt passes, and we need it to occur before iteration splitting. + for (int i = 0; i < array.length; i++) { + long elemOffset = Unsafe.ARRAY_INT_BASE_OFFSET + (long) i * Unsafe.ARRAY_INT_INDEX_SCALE; + int e = U.getInt(array, elemOffset); + U.putInt(array, elemOffset, e + 1); + + // These 4 stores can all be sunk, but depending on the order in which they are + // visited, it is most likely that only some of them are actually sunk + v1.v = e + 1; + v2.v = e + 2; + v3.v = e + 3; + v4.v = e + 4; + } + } + + static class MyInteger { + public int v; + + public MyInteger(int v) { + for (int i = 0; i < 32; i++) { + if (i < 10) { + this.v = v; + } + } + this.v = v; + } + + public int v() { + return v; + } + } +} From 78a6aa9c7a907fe3375cd20e45bb293b5d15732e Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Mon, 13 Apr 2026 07:13:23 +0000 Subject: [PATCH 020/108] 8381937: Make exceptions in Java_sun_security_mscapi_CKeyPairGenerator generateCKeyPair more specific Reviewed-by: mdoerr, lucy --- .../windows/native/libsunmscapi/security.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/jdk.crypto.mscapi/windows/native/libsunmscapi/security.cpp b/src/jdk.crypto.mscapi/windows/native/libsunmscapi/security.cpp index ff011dca889..5c84b929ef1 100644 --- a/src/jdk.crypto.mscapi/windows/native/libsunmscapi/security.cpp +++ b/src/jdk.crypto.mscapi/windows/native/libsunmscapi/security.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 @@ -1375,7 +1375,7 @@ JNIEXPORT jobject JNICALL Java_sun_security_mscapi_CKeyPairGenerator_00024RSA_ge PROV_RSA_FULL, CRYPT_NEWKEYSET) == FALSE) { - ThrowException(env, KEY_EXCEPTION, GetLastError()); + ThrowExceptionWithMessageAndErrcode(env, KEY_EXCEPTION, "CryptAcquireContext failure", GetLastError()); __leave; } } @@ -1387,7 +1387,7 @@ JNIEXPORT jobject JNICALL Java_sun_security_mscapi_CKeyPairGenerator_00024RSA_ge dwFlags, &hKeyPair) == FALSE) { - ThrowException(env, KEY_EXCEPTION, GetLastError()); + ThrowExceptionWithMessageAndErrcode(env, KEY_EXCEPTION, "CryptGenKey failure", GetLastError()); __leave; } From 909d4e758c045a0d8cea52f6a1333839f7d6c43e Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Mon, 13 Apr 2026 07:20:16 +0000 Subject: [PATCH 021/108] 8378838: Fix issues with "dead" code elimination and serviceability agent in libjvm.so on Linux Reviewed-by: lucy, erikj --- make/autoconf/flags-cflags.m4 | 5 +---- make/autoconf/flags-ldflags.m4 | 13 ++++++------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/make/autoconf/flags-cflags.m4 b/make/autoconf/flags-cflags.m4 index ab9cd8be19b..423654cd50a 100644 --- a/make/autoconf/flags-cflags.m4 +++ b/make/autoconf/flags-cflags.m4 @@ -544,12 +544,9 @@ AC_DEFUN([FLAGS_SETUP_CFLAGS_HELPER], TOOLCHAIN_CFLAGS_JVM="$TOOLCHAIN_CFLAGS_JVM -fstack-protector" TOOLCHAIN_CFLAGS_JDK="-fvisibility=hidden -pipe -fstack-protector" # reduce lib size on linux in link step, this needs also special compile flags - # do this on s390x also for libjvm (where serviceability agent is not supported) if test "x$ENABLE_LINKTIME_GC" = xtrue; then TOOLCHAIN_CFLAGS_JDK="$TOOLCHAIN_CFLAGS_JDK -ffunction-sections -fdata-sections" - if test "x$OPENJDK_TARGET_CPU" = xs390x && test "x$DEBUG_LEVEL" == xrelease; then - TOOLCHAIN_CFLAGS_JVM="$TOOLCHAIN_CFLAGS_JVM -ffunction-sections -fdata-sections" - fi + TOOLCHAIN_CFLAGS_JVM="$TOOLCHAIN_CFLAGS_JVM -ffunction-sections -fdata-sections" fi # technically NOT for CXX (but since this gives *worse* performance, use # no-strict-aliasing everywhere!) diff --git a/make/autoconf/flags-ldflags.m4 b/make/autoconf/flags-ldflags.m4 index 7782735be25..ff10828731e 100644 --- a/make/autoconf/flags-ldflags.m4 +++ b/make/autoconf/flags-ldflags.m4 @@ -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 @@ -53,16 +53,15 @@ AC_DEFUN([FLAGS_SETUP_LDFLAGS_HELPER], # add --icf=all (Identical Code Folding — merges identical functions) BASIC_LDFLAGS="-Wl,-z,defs -Wl,-z,relro -Wl,-z,now -Wl,--no-as-needed -Wl,--exclude-libs,ALL" + BASIC_LDFLAGS_JVM_ONLY="" # Linux : remove unused code+data in link step if test "x$ENABLE_LINKTIME_GC" = xtrue; then - if test "x$OPENJDK_TARGET_CPU" = xs390x; then - BASIC_LDFLAGS="$BASIC_LDFLAGS -Wl,--gc-sections" - else - BASIC_LDFLAGS_JDK_ONLY="$BASIC_LDFLAGS_JDK_ONLY -Wl,--gc-sections" - fi + # keep vtables : -Wl,--undefined-glob=_ZTV* (but this seems not to work with gold ld) + # so keep at least the Metadata vtable that is used in the serviceability agent + BASIC_LDFLAGS_JVM_ONLY="$BASIC_LDFLAGS_JVM_ONLY -Wl,--gc-sections -Wl,--undefined=_ZTV8Metadata" + BASIC_LDFLAGS_JDK_ONLY="$BASIC_LDFLAGS_JDK_ONLY -Wl,--gc-sections" fi - BASIC_LDFLAGS_JVM_ONLY="" LDFLAGS_LTO="-flto=auto -fuse-linker-plugin -fno-strict-aliasing $DEBUG_PREFIX_CFLAGS" LDFLAGS_CXX_PARTIAL_LINKING="$MACHINE_FLAG -r" From efe7fd8683d1185a48b7000b132fd97241040ba8 Mon Sep 17 00:00:00 2001 From: Leo Korinth Date: Mon, 13 Apr 2026 08:14:47 +0000 Subject: [PATCH 022/108] 8380163: Fix implicit conversion in macroAssembler_aarch64.hpp Reviewed-by: aph, fyang --- .../cpu/aarch64/macroAssembler_aarch64.cpp | 37 ++++++++++++++++--- .../cpu/aarch64/macroAssembler_aarch64.hpp | 21 +++-------- 2 files changed, 37 insertions(+), 21 deletions(-) diff --git a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp index 7fa2e8086ad..7bec0a3c0ca 100644 --- a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp @@ -55,6 +55,7 @@ #include "runtime/sharedRuntime.hpp" #include "runtime/stubRoutines.hpp" #include "utilities/globalDefinitions.hpp" +#include "utilities/integerCast.hpp" #include "utilities/powerOfTwo.hpp" #ifdef COMPILER1 #include "c1/c1_LIRAssembler.hpp" @@ -2916,7 +2917,11 @@ void MacroAssembler::increment(Address dst, int value) // Push lots of registers in the bit set supplied. Don't push sp. // Return the number of words pushed -int MacroAssembler::push(unsigned int bitset, Register stack) { +int MacroAssembler::push(RegSet regset, Register stack) { + if (regset.bits() == 0) { + return 0; + } + auto bitset = integer_cast(regset.bits()); int words_pushed = 0; // Scan bitset to accumulate register pairs @@ -2946,7 +2951,11 @@ int MacroAssembler::push(unsigned int bitset, Register stack) { return count; } -int MacroAssembler::pop(unsigned int bitset, Register stack) { +int MacroAssembler::pop(RegSet regset, Register stack) { + if (regset.bits() == 0) { + return 0; + } + auto bitset = integer_cast(regset.bits()); int words_pushed = 0; // Scan bitset to accumulate register pairs @@ -2978,7 +2987,11 @@ int MacroAssembler::pop(unsigned int bitset, Register stack) { // Push lots of registers in the bit set supplied. Don't push sp. // Return the number of dwords pushed -int MacroAssembler::push_fp(unsigned int bitset, Register stack, FpPushPopMode mode) { +int MacroAssembler::push_fp(FloatRegSet regset, Register stack, FpPushPopMode mode) { + if (regset.bits() == 0) { + return 0; + } + auto bitset = integer_cast(regset.bits()); int words_pushed = 0; bool use_sve = false; int sve_vector_size_in_bytes = 0; @@ -3091,7 +3104,11 @@ int MacroAssembler::push_fp(unsigned int bitset, Register stack, FpPushPopMode m } // Return the number of dwords popped -int MacroAssembler::pop_fp(unsigned int bitset, Register stack, FpPushPopMode mode) { +int MacroAssembler::pop_fp(FloatRegSet regset, Register stack, FpPushPopMode mode) { + if (regset.bits() == 0) { + return 0; + } + auto bitset = integer_cast(regset.bits()); int words_pushed = 0; bool use_sve = false; int sve_vector_size_in_bytes = 0; @@ -3201,7 +3218,11 @@ int MacroAssembler::pop_fp(unsigned int bitset, Register stack, FpPushPopMode mo } // Return the number of dwords pushed -int MacroAssembler::push_p(unsigned int bitset, Register stack) { +int MacroAssembler::push_p(PRegSet regset, Register stack) { + if (regset.bits() == 0) { + return 0; + } + auto bitset = integer_cast(regset.bits()); bool use_sve = false; int sve_predicate_size_in_slots = 0; @@ -3238,7 +3259,11 @@ int MacroAssembler::push_p(unsigned int bitset, Register stack) { } // Return the number of dwords popped -int MacroAssembler::pop_p(unsigned int bitset, Register stack) { +int MacroAssembler::pop_p(PRegSet regset, Register stack) { + if (regset.bits() == 0) { + return 0; + } + auto bitset = integer_cast(regset.bits()); bool use_sve = false; int sve_predicate_size_in_slots = 0; diff --git a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp index 994fbe3c80f..dfba2dcff46 100644 --- a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp @@ -499,29 +499,20 @@ private: void mov_immediate64(Register dst, uint64_t imm64); void mov_immediate32(Register dst, uint32_t imm32); - int push(unsigned int bitset, Register stack); - int pop(unsigned int bitset, Register stack); - - int push_fp(unsigned int bitset, Register stack, FpPushPopMode mode); - int pop_fp(unsigned int bitset, Register stack, FpPushPopMode mode); - - int push_p(unsigned int bitset, Register stack); - int pop_p(unsigned int bitset, Register stack); - void mov(Register dst, Address a); public: - void push(RegSet regs, Register stack) { if (regs.bits()) push(regs.bits(), stack); } - void pop(RegSet regs, Register stack) { if (regs.bits()) pop(regs.bits(), stack); } + int push(RegSet regset, Register stack); + int pop(RegSet regset, Register stack); - void push_fp(FloatRegSet regs, Register stack, FpPushPopMode mode = PushPopFull) { if (regs.bits()) push_fp(regs.bits(), stack, mode); } - void pop_fp(FloatRegSet regs, Register stack, FpPushPopMode mode = PushPopFull) { if (regs.bits()) pop_fp(regs.bits(), stack, mode); } + int push_fp(FloatRegSet regset, Register stack, FpPushPopMode mode = PushPopFull); + int pop_fp(FloatRegSet regset, Register stack, FpPushPopMode mode = PushPopFull); static RegSet call_clobbered_gp_registers(); - void push_p(PRegSet regs, Register stack) { if (regs.bits()) push_p(regs.bits(), stack); } - void pop_p(PRegSet regs, Register stack) { if (regs.bits()) pop_p(regs.bits(), stack); } + int push_p(PRegSet regset, Register stack); + int pop_p(PRegSet regset, Register stack); // Push and pop everything that might be clobbered by a native // runtime call except rscratch1 and rscratch2. (They are always From e8c77d16c41b9e4569c888f87e97fa48a24f1498 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joel=20Sikstr=C3=B6m?= Date: Mon, 13 Apr 2026 08:44:51 +0000 Subject: [PATCH 023/108] 8381900: Test vmTestbase/nsk/jvmti/scenarios/allocation/AP03/ap03t001 does not handle frequent GC gracefully Reviewed-by: aboldtch, sspitsyn --- .../scenarios/allocation/AP03/ap03t001/ap03t001.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/allocation/AP03/ap03t001/ap03t001.cpp b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/allocation/AP03/ap03t001/ap03t001.cpp index e50e1ff537b..0e5ff69fe6c 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/allocation/AP03/ap03t001/ap03t001.cpp +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/scenarios/allocation/AP03/ap03t001/ap03t001.cpp @@ -42,6 +42,7 @@ static jvmtiCapabilities caps; static volatile int obj_free = 0; static volatile long obj_count = 0; +static volatile bool check_object_free = true; static jlong timeout = 0; static int user_data = 0; @@ -50,6 +51,9 @@ static const jlong DEBUGEE_CLASS_TAG = (jlong)1024; void JNICALL ObjectFree(jvmtiEnv *jvmti_env, jlong tag) { + if (!check_object_free) { + return; + } NSK_COMPLAIN1("Received unexpected ObjectFree event for an object with tag %ld\n\n", (long)tag); nsk_jvmti_setFailStatus(); obj_free++; @@ -190,6 +194,11 @@ agentProc(jvmtiEnv* jvmti, JNIEnv* jni, void* arg) { } else { NSK_DISPLAY1("Number of objects IterateOverObjectsReachableFromObject has found: %d\n\n", obj_count); } + + // Ignore late ObjectFree notifications after the resurrected object has + // been validated. At this point the test has already proven that the + // tagged object survived finalization and is reachable through the catcher. + check_object_free = false; } while (0); NSK_DISPLAY0("Let debugee to finish\n"); From fa612824239ac1f997dd0d526995bd13e5c1bdd1 Mon Sep 17 00:00:00 2001 From: Dingli Zhang Date: Mon, 13 Apr 2026 09:06:37 +0000 Subject: [PATCH 024/108] 8381926: Fix implicit conversion in macroAssembler_riscv.hpp Reviewed-by: fyang, wenanjian --- .../cpu/riscv/macroAssembler_riscv.cpp | 43 ++++++++++++++----- .../cpu/riscv/macroAssembler_riscv.hpp | 25 +++++------ 2 files changed, 43 insertions(+), 25 deletions(-) diff --git a/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp b/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp index a2b7970f9f6..0e32c602d95 100644 --- a/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp +++ b/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp @@ -49,6 +49,7 @@ #include "runtime/sharedRuntime.hpp" #include "runtime/stubRoutines.hpp" #include "utilities/globalDefinitions.hpp" +#include "utilities/integerCast.hpp" #include "utilities/powerOfTwo.hpp" #ifdef COMPILER2 #include "opto/compile.hpp" @@ -1947,14 +1948,12 @@ void MacroAssembler::restore_cpu_control_state_after_jni(Register tmp) { } } -void MacroAssembler::push_reg(Register Rs) -{ +void MacroAssembler::push_reg(Register Rs) { subi(esp, esp, wordSize); sd(Rs, Address(esp, 0)); } -void MacroAssembler::pop_reg(Register Rd) -{ +void MacroAssembler::pop_reg(Register Rd) { ld(Rd, Address(esp, 0)); addi(esp, esp, wordSize); } @@ -1973,7 +1972,11 @@ int MacroAssembler::bitset_to_regs(unsigned int bitset, unsigned char* regs) { // Push integer registers in the bitset supplied. Don't push sp. // Return the number of words pushed -int MacroAssembler::push_reg(unsigned int bitset, Register stack) { +int MacroAssembler::push_reg(RegSet regset, Register stack) { + if (regset.bits() == 0) { + return 0; + } + auto bitset = integer_cast(regset.bits()); DEBUG_ONLY(int words_pushed = 0;) unsigned char regs[32]; int count = bitset_to_regs(bitset, regs); @@ -1993,7 +1996,11 @@ int MacroAssembler::push_reg(unsigned int bitset, Register stack) { return count; } -int MacroAssembler::pop_reg(unsigned int bitset, Register stack) { +int MacroAssembler::pop_reg(RegSet regset, Register stack) { + if (regset.bits() == 0) { + return 0; + } + auto bitset = integer_cast(regset.bits()); DEBUG_ONLY(int words_popped = 0;) unsigned char regs[32]; int count = bitset_to_regs(bitset, regs); @@ -2015,7 +2022,11 @@ int MacroAssembler::pop_reg(unsigned int bitset, Register stack) { // Push floating-point registers in the bitset supplied. // Return the number of words pushed -int MacroAssembler::push_fp(unsigned int bitset, Register stack) { +int MacroAssembler::push_fp(FloatRegSet regset, Register stack) { + if (regset.bits() == 0) { + return 0; + } + auto bitset = integer_cast(regset.bits()); DEBUG_ONLY(int words_pushed = 0;) unsigned char regs[32]; int count = bitset_to_regs(bitset, regs); @@ -2035,7 +2046,11 @@ int MacroAssembler::push_fp(unsigned int bitset, Register stack) { return count; } -int MacroAssembler::pop_fp(unsigned int bitset, Register stack) { +int MacroAssembler::pop_fp(FloatRegSet regset, Register stack) { + if (regset.bits() == 0) { + return 0; + } + auto bitset = integer_cast(regset.bits()); DEBUG_ONLY(int words_popped = 0;) unsigned char regs[32]; int count = bitset_to_regs(bitset, regs); @@ -2721,7 +2736,11 @@ void MacroAssembler::kernel_crc32(Register crc, Register buf, Register len, #ifdef COMPILER2 // Push vector registers in the bitset supplied. // Return the number of words pushed -int MacroAssembler::push_v(unsigned int bitset, Register stack) { +int MacroAssembler::push_v(VectorRegSet regset, Register stack) { + if (regset.bits() == 0) { + return 0; + } + auto bitset = integer_cast(regset.bits()); int vector_size_in_bytes = Matcher::scalable_vector_reg_size(T_BYTE); // Scan bitset to accumulate register pairs @@ -2736,7 +2755,11 @@ int MacroAssembler::push_v(unsigned int bitset, Register stack) { return count * vector_size_in_bytes / wordSize; } -int MacroAssembler::pop_v(unsigned int bitset, Register stack) { +int MacroAssembler::pop_v(VectorRegSet regset, Register stack) { + if (regset.bits() == 0) { + return 0; + } + auto bitset = integer_cast(regset.bits()); int vector_size_in_bytes = Matcher::scalable_vector_reg_size(T_BYTE); // Scan bitset to accumulate register pairs diff --git a/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp b/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp index a51a6aea468..4cc55e7ae23 100644 --- a/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp +++ b/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp @@ -818,15 +818,6 @@ class MacroAssembler: public Assembler { void double_bgt(FloatRegister Rs1, FloatRegister Rs2, Label &l, bool is_far = false, bool is_unordered = false); private: - int push_reg(unsigned int bitset, Register stack); - int pop_reg(unsigned int bitset, Register stack); - int push_fp(unsigned int bitset, Register stack); - int pop_fp(unsigned int bitset, Register stack); -#ifdef COMPILER2 - int push_v(unsigned int bitset, Register stack); - int pop_v(unsigned int bitset, Register stack); -#endif // COMPILER2 - // The signed 20-bit upper imm can materialize at most negative 0xF...F80000000, two G. // The following signed 12-bit imm can at max subtract 0x800, two K, from that previously loaded two G. bool is_valid_32bit_offset(int64_t x) { @@ -844,15 +835,19 @@ private: } public: + // Stack push and pop individual 64 bit registers void push_reg(Register Rs); void pop_reg(Register Rd); - void push_reg(RegSet regs, Register stack) { if (regs.bits()) push_reg(regs.bits(), stack); } - void pop_reg(RegSet regs, Register stack) { if (regs.bits()) pop_reg(regs.bits(), stack); } - void push_fp(FloatRegSet regs, Register stack) { if (regs.bits()) push_fp(regs.bits(), stack); } - void pop_fp(FloatRegSet regs, Register stack) { if (regs.bits()) pop_fp(regs.bits(), stack); } + + int push_reg(RegSet regset, Register stack); + int pop_reg(RegSet regset, Register stack); + + int push_fp(FloatRegSet regset, Register stack); + int pop_fp(FloatRegSet regset, Register stack); + #ifdef COMPILER2 - void push_v(VectorRegSet regs, Register stack) { if (regs.bits()) push_v(regs.bits(), stack); } - void pop_v(VectorRegSet regs, Register stack) { if (regs.bits()) pop_v(regs.bits(), stack); } + int push_v(VectorRegSet regset, Register stack); + int pop_v(VectorRegSet regset, Register stack); #endif // COMPILER2 // Push and pop everything that might be clobbered by a native From d605b5709a43f87a01a650fcd8aed2fb6d2d3f8a Mon Sep 17 00:00:00 2001 From: Dusan Balek Date: Mon, 13 Apr 2026 09:08:45 +0000 Subject: [PATCH 025/108] 8179187: Misleading compilation error on annotated fully-qualified classname Reviewed-by: jlahoda, vromero --- .../share/classes/com/sun/tools/javac/comp/Attr.java | 12 ++++++++---- .../failures/CantAnnotatePackages.java | 2 +- .../failures/CantAnnotatePackages.out | 6 +++--- .../failures/CantAnnotateScoping.java | 2 +- .../typeAnnotations/failures/CantAnnotateScoping.out | 9 +++++---- 5 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java index 444530c7266..89ae68e85ba 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java @@ -5270,11 +5270,15 @@ public class Attr extends JCTree.Visitor { public void visitAnnotatedType(JCAnnotatedType tree) { attribAnnotationTypes(tree.annotations, env); - Type underlyingType = attribType(tree.underlyingType, env); - Type annotatedType = underlyingType.preannotatedType(); + Type underlyingType = attribTree(tree.underlyingType, env, resultInfo); + if (underlyingType.getTag() == PACKAGE) { + result = tree.type = underlyingType; + } else { + Type annotatedType = underlyingType.preannotatedType(); - annotate.annotateTypeSecondStage(tree, tree.annotations, annotatedType); - result = tree.type = annotatedType; + annotate.annotateTypeSecondStage(tree, tree.annotations, annotatedType); + result = tree.type = annotatedType; + } } public void visitErroneous(JCErroneous tree) { diff --git a/test/langtools/tools/javac/annotations/typeAnnotations/failures/CantAnnotatePackages.java b/test/langtools/tools/javac/annotations/typeAnnotations/failures/CantAnnotatePackages.java index d35351b6f40..5031a74bc5a 100644 --- a/test/langtools/tools/javac/annotations/typeAnnotations/failures/CantAnnotatePackages.java +++ b/test/langtools/tools/javac/annotations/typeAnnotations/failures/CantAnnotatePackages.java @@ -1,6 +1,6 @@ /* * @test /nodynamiccopyright/ - * @bug 8026564 8043226 8334055 + * @bug 8026564 8043226 8334055 8179187 * @summary The parts of a fully-qualified type can't be annotated. * @author Werner Dietl * @compile/fail/ref=CantAnnotatePackages.out -XDrawDiagnostics CantAnnotatePackages.java diff --git a/test/langtools/tools/javac/annotations/typeAnnotations/failures/CantAnnotatePackages.out b/test/langtools/tools/javac/annotations/typeAnnotations/failures/CantAnnotatePackages.out index b91d65828b9..6e2b9cb93b0 100644 --- a/test/langtools/tools/javac/annotations/typeAnnotations/failures/CantAnnotatePackages.out +++ b/test/langtools/tools/javac/annotations/typeAnnotations/failures/CantAnnotatePackages.out @@ -1,5 +1,5 @@ -CantAnnotatePackages.java:16:14: compiler.err.cant.resolve.location: kindname.class, java, , , (compiler.misc.location: kindname.class, CantAnnotatePackages, null) -CantAnnotatePackages.java:17:9: compiler.err.cant.resolve.location: kindname.class, lang, , , (compiler.misc.location: kindname.package, java, null) -CantAnnotatePackages.java:18:14: compiler.err.cant.resolve.location: kindname.class, lang, , , (compiler.misc.location: kindname.package, java, null) CantAnnotatePackages.java:14:18: compiler.err.type.annotation.inadmissible: (compiler.misc.type.annotation.1: @TA), java.lang, @TA java.lang.Object +CantAnnotatePackages.java:16:14: compiler.err.type.annotation.inadmissible: (compiler.misc.type.annotation.1: @TA), java.lang, @TA java.lang.Object +CantAnnotatePackages.java:17:9: compiler.err.type.annotation.inadmissible: (compiler.misc.type.annotation.1: @TA), java.lang, @TA java.lang.Object +CantAnnotatePackages.java:18:14: compiler.err.type.annotation.inadmissible: (compiler.misc.type.annotation.1: @TA), java.lang, @TA java.lang.Object 4 errors diff --git a/test/langtools/tools/javac/annotations/typeAnnotations/failures/CantAnnotateScoping.java b/test/langtools/tools/javac/annotations/typeAnnotations/failures/CantAnnotateScoping.java index 4bdd791909c..427c1fef3a8 100644 --- a/test/langtools/tools/javac/annotations/typeAnnotations/failures/CantAnnotateScoping.java +++ b/test/langtools/tools/javac/annotations/typeAnnotations/failures/CantAnnotateScoping.java @@ -1,6 +1,6 @@ /* * @test /nodynamiccopyright/ - * @bug 8006733 8006775 8043226 8334055 + * @bug 8006733 8006775 8043226 8334055 8179187 * @summary Ensure behavior for nested types is correct. * @author Werner Dietl * @compile/fail/ref=CantAnnotateScoping.out -XDrawDiagnostics CantAnnotateScoping.java diff --git a/test/langtools/tools/javac/annotations/typeAnnotations/failures/CantAnnotateScoping.out b/test/langtools/tools/javac/annotations/typeAnnotations/failures/CantAnnotateScoping.out index ade5333a446..2ae736ad315 100644 --- a/test/langtools/tools/javac/annotations/typeAnnotations/failures/CantAnnotateScoping.out +++ b/test/langtools/tools/javac/annotations/typeAnnotations/failures/CantAnnotateScoping.out @@ -1,12 +1,13 @@ -CantAnnotateScoping.java:63:9: compiler.err.cant.resolve.location: kindname.class, lang, , , (compiler.misc.location: kindname.package, java, null) -CantAnnotateScoping.java:68:9: compiler.err.cant.resolve.location: kindname.class, XXX, , , (compiler.misc.location: kindname.package, java, null) -CantAnnotateScoping.java:71:9: compiler.err.cant.resolve.location: kindname.class, lang, , , (compiler.misc.location: kindname.package, java, null) +CantAnnotateScoping.java:68:18: compiler.err.doesnt.exist: java.XXX CantAnnotateScoping.java:38:14: compiler.err.type.annotation.inadmissible: (compiler.misc.type.annotation.1: @TA), Test.Outer, @TA Test.Outer.SInner CantAnnotateScoping.java:51:18: compiler.err.type.annotation.inadmissible: (compiler.misc.type.annotation.1: @TA), java.lang, @TA java.lang.Object CantAnnotateScoping.java:60:37: compiler.err.type.annotation.inadmissible: (compiler.misc.type.annotation: @TA,@TA2), java.lang, @DTA @TA @TA2 java.lang.Object CantAnnotateScoping.java:40:14: compiler.err.type.annotation.inadmissible: (compiler.misc.type.annotation.1: @TA), Test.Outer, @TA Test.Outer.SInner +CantAnnotateScoping.java:63:11: compiler.err.annotation.type.not.applicable.to.type: DA +CantAnnotateScoping.java:68:11: compiler.err.annotation.type.not.applicable.to.type: DA +CantAnnotateScoping.java:71:9: compiler.err.type.annotation.inadmissible: (compiler.misc.type.annotation.1: @TA), java.lang, @TA java.lang.Object CantAnnotateScoping.java:44:34: compiler.err.type.annotation.inadmissible: (compiler.misc.type.annotation: @TA,@TA2), Test.Outer, @TA @TA2 Test.Outer.SInner CantAnnotateScoping.java:44:25: compiler.err.annotation.type.not.applicable.to.type: DA CantAnnotateScoping.java:48:38: compiler.err.type.annotation.inadmissible: (compiler.misc.type.annotation.1: @TA), Test.Outer, @TA Test.Outer.SInner CantAnnotateScoping.java:48:34: compiler.err.annotation.type.not.applicable.to.type: DA -11 errors +12 errors From a76e38c7eb135dcd613a29fa63ab4761d5d81d19 Mon Sep 17 00:00:00 2001 From: Daniel Fuchs Date: Mon, 13 Apr 2026 09:24:41 +0000 Subject: [PATCH 026/108] 8381359: Refactor java/net/DatagramSocket TestNG tests to use JUnit Reviewed-by: vyazici --- .../net/DatagramSocket/ConnectPortZero.java | 85 ++++++++--------- .../java/net/DatagramSocket/Constructor.java | 10 +- .../net/DatagramSocket/DatagramTimeout.java | 90 +++++++++--------- .../OldDatagramSocketImplTest.java | 64 ++++++------- .../java/net/DatagramSocket/SendCheck.java | 93 +++++++++---------- .../java/net/DatagramSocket/SendPortZero.java | 46 ++++----- .../DatagramSocket/SendReceiveMaxSize.java | 57 ++++++------ .../SetGetReceiveBufferSize.java | 35 +++---- .../DatagramSocket/SetGetSendBufferSize.java | 41 ++++---- .../DatagramSocket/SupportedOptionsCheck.java | 8 +- 10 files changed, 262 insertions(+), 267 deletions(-) diff --git a/test/jdk/java/net/DatagramSocket/ConnectPortZero.java b/test/jdk/java/net/DatagramSocket/ConnectPortZero.java index 7f61d146bc0..342dae4c02b 100644 --- a/test/jdk/java/net/DatagramSocket/ConnectPortZero.java +++ b/test/jdk/java/net/DatagramSocket/ConnectPortZero.java @@ -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 @@ -21,11 +21,6 @@ * questions. */ -import org.testng.annotations.AfterTest; -import org.testng.annotations.BeforeTest; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; - import java.io.IOException; import java.io.UncheckedIOException; import java.net.DatagramSocket; @@ -34,62 +29,64 @@ import java.net.InetSocketAddress; import java.net.MulticastSocket; import java.net.SocketException; import java.nio.channels.DatagramChannel; +import java.util.List; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertThrows; -import static org.testng.Assert.expectThrows; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; /* * @test * @bug 8240533 * @summary Check that DatagramSocket, MulticastSocket and DatagramSocketAdaptor * throw expected Exception when connecting to port 0 - * @run testng/othervm ConnectPortZero + * @run junit/othervm ${test.main.class} */ public class ConnectPortZero { - private InetAddress loopbackAddr, wildcardAddr; - private DatagramSocket datagramSocket, datagramSocketAdaptor; - private MulticastSocket multicastSocket; - + private static InetAddress loopbackAddr, wildcardAddr; private static final Class SE = SocketException.class; private static final Class UCIOE = UncheckedIOException.class; - @BeforeTest - public void setUp() throws IOException { + @BeforeAll + public static void setUp() throws IOException { loopbackAddr = InetAddress.getLoopbackAddress(); wildcardAddr = new InetSocketAddress(0).getAddress(); - - datagramSocket = new DatagramSocket(); - multicastSocket = new MulticastSocket(); - datagramSocketAdaptor = DatagramChannel.open().socket(); } - @DataProvider(name = "data") - public Object[][] variants() { - return new Object[][]{ - { datagramSocket, loopbackAddr }, - { datagramSocketAdaptor, loopbackAddr }, - { multicastSocket, loopbackAddr }, - { datagramSocket, wildcardAddr }, - { datagramSocketAdaptor, wildcardAddr }, - { multicastSocket, wildcardAddr } - }; + public static List testCases() throws IOException { + // Note that Closeable arguments passed to a ParameterizedTest are automatically + // closed by JUnit. We do not want to rely on this, but we do need to + // create a new set of sockets for each invocation of this method, so that + // the next test method invoked doesn't get a closed socket. + return List.of( + Arguments.of(new DatagramSocket(), loopbackAddr), + Arguments.of(DatagramChannel.open().socket(), loopbackAddr), + Arguments.of(new MulticastSocket(), loopbackAddr), + Arguments.of(new DatagramSocket(), wildcardAddr), + Arguments.of(DatagramChannel.open().socket(), wildcardAddr), + Arguments.of(new MulticastSocket(), wildcardAddr) + ); } - @Test(dataProvider = "data") - public void testConnect(DatagramSocket ds, InetAddress addr) { - Throwable t = expectThrows(UCIOE, () -> ds.connect(addr, 0)); - assertEquals(t.getCause().getClass(), SE); - - assertThrows(SE, () -> ds - .connect(new InetSocketAddress(addr, 0))); - } - - @AfterTest - public void tearDown() { - datagramSocket.close(); - multicastSocket.close(); - datagramSocketAdaptor.close(); + @ParameterizedTest + @MethodSource("testCases") + public void testConnect(DatagramSocket socket, InetAddress addr) { + try (var ds = socket) { + assertFalse(ds.isConnected()); + assertFalse(ds.isClosed()); + Throwable t = assertThrows(UCIOE, () -> ds.connect(addr, 0)); + assertSame(SE, t.getCause().getClass()); + assertFalse(ds.isConnected()); + assertFalse(ds.isClosed()); + assertThrows(SE, () -> ds + .connect(new InetSocketAddress(addr, 0))); + assertFalse(ds.isConnected()); + assertFalse(ds.isClosed()); + } } } diff --git a/test/jdk/java/net/DatagramSocket/Constructor.java b/test/jdk/java/net/DatagramSocket/Constructor.java index 6fecf8dc732..6e5cd944297 100644 --- a/test/jdk/java/net/DatagramSocket/Constructor.java +++ b/test/jdk/java/net/DatagramSocket/Constructor.java @@ -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,18 +24,18 @@ /* @test * @bug 8243507 8243999 * @summary Checks to ensure that DatagramSocket constructors behave as expected - * @run testng Constructor + * @run junit ${test.main.class} */ -import org.testng.annotations.Test; import java.io.IOException; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketAddress; -import static org.testng.Assert.assertThrows; -import static org.testng.Assert.assertTrue; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; public class Constructor { private static final InetAddress LOOPBACK = InetAddress.getLoopbackAddress(); diff --git a/test/jdk/java/net/DatagramSocket/DatagramTimeout.java b/test/jdk/java/net/DatagramSocket/DatagramTimeout.java index 4d979f9dc8d..a8d35a64dc8 100644 --- a/test/jdk/java/net/DatagramSocket/DatagramTimeout.java +++ b/test/jdk/java/net/DatagramSocket/DatagramTimeout.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2021, 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 @@ -26,23 +26,24 @@ * @bug 4163126 8222829 * @summary Test to see if timeout hangs. Also checks that * negative timeout value fails as expected. - * @run testng DatagramTimeout + * @run junit ${test.main.class} */ +import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.MulticastSocket; import java.net.SocketException; import java.net.SocketTimeoutException; import java.nio.channels.DatagramChannel; +import java.util.List; -import org.testng.annotations.AfterTest; -import org.testng.annotations.BeforeTest; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; - -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertThrows; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; public class DatagramTimeout { private static final Class IAE = @@ -51,49 +52,46 @@ public class DatagramTimeout { SocketTimeoutException.class; private static final Class SE = SocketException.class; - private DatagramSocket datagramSocket, multicastSocket, - datagramSocketAdaptor; - - @BeforeTest - public void setUp() throws Exception { - datagramSocket = new DatagramSocket(); - multicastSocket = new MulticastSocket(); - datagramSocketAdaptor = DatagramChannel.open().socket(); + public static List sockets() throws IOException { + // Note that Closeable arguments passed to a ParameterizedTest are automatically + // closed by JUnit. We do not want to rely on this, but we do need to + // create a new set of sockets for each invocation of this method, so that + // the next test method invoked doesn't get a closed socket. + return List.of( + new DatagramSocket(), + new MulticastSocket(), + DatagramChannel.open().socket()); } - @DataProvider(name = "data") - public Object[][] variants() { - return new Object[][]{ - { datagramSocket }, - { datagramSocketAdaptor }, - { multicastSocket }, - }; + @ParameterizedTest + @MethodSource("sockets") + public void testSetNegTimeout(DatagramSocket socket) { + try (var ds = socket) { + assertFalse(ds.isClosed()); + assertThrows(IAE, () -> ds.setSoTimeout(-1)); + } } - @Test(dataProvider = "data") - public void testSetNegTimeout(DatagramSocket ds) { - assertThrows(IAE, () -> ds.setSoTimeout(-1)); + @ParameterizedTest + @MethodSource("sockets") + public void testSetTimeout(DatagramSocket socket) throws Exception { + try (var ds = socket) { + assertFalse(ds.isClosed()); + byte[] buffer = new byte[50]; + DatagramPacket pkt = new DatagramPacket(buffer, buffer.length); + ds.setSoTimeout(2); + assertThrows(STE, () -> ds.receive(pkt)); + } } - @Test(dataProvider = "data") - public void testSetTimeout(DatagramSocket ds) throws Exception { - byte[] buffer = new byte[50]; - DatagramPacket pkt = new DatagramPacket(buffer, buffer.length); - ds.setSoTimeout(2); - assertThrows(STE, () -> ds.receive(pkt)); - } - - @Test(dataProvider = "data") - public void testGetTimeout(DatagramSocket ds) throws Exception { - ds.setSoTimeout(10); - assertEquals(10, ds.getSoTimeout()); - } - - @AfterTest - public void tearDown() { - datagramSocket.close(); - multicastSocket.close(); - datagramSocketAdaptor.close(); + @ParameterizedTest + @MethodSource("sockets") + public void testGetTimeout(DatagramSocket socket) throws Exception { + try (var ds = socket) { + assertFalse(ds.isClosed()); + ds.setSoTimeout(10); + assertEquals(10, ds.getSoTimeout()); + } } @Test diff --git a/test/jdk/java/net/DatagramSocket/OldDatagramSocketImplTest.java b/test/jdk/java/net/DatagramSocket/OldDatagramSocketImplTest.java index 7f8eea874ec..1d3c39ffd71 100644 --- a/test/jdk/java/net/DatagramSocket/OldDatagramSocketImplTest.java +++ b/test/jdk/java/net/DatagramSocket/OldDatagramSocketImplTest.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 @@ -25,15 +25,24 @@ * @test * @bug 8260428 * @summary Drop support for pre JDK 1.4 DatagramSocketImpl implementations - * @run testng/othervm OldDatagramSocketImplTest + * @run junit/othervm ${test.main.class} */ -import org.testng.annotations.Test; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.DatagramSocketImpl; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.NetworkInterface; +import java.net.SocketAddress; +import java.net.SocketException; -import java.net.*; -import java.io.*; - -import static org.testng.Assert.assertEquals; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; public class OldDatagramSocketImplTest { InetAddress LOOPBACK = InetAddress.getLoopbackAddress(); @@ -41,22 +50,18 @@ public class OldDatagramSocketImplTest { @Test public void testOldImplConnect() { try (var ds = new DatagramSocket(new OldDatagramSocketImpl()) {}) { - ds.connect(new InetSocketAddress(LOOPBACK, 6667)); - throw new RuntimeException("ERROR: test failed"); - } catch (SocketException ex) { - assertEquals(ex.getMessage(), "connect not implemented"); - System.out.println("PASSED: default implementation of connect has thrown as expected"); + SocketException ex = assertThrows(SocketException.class, + () -> ds.connect(new InetSocketAddress(LOOPBACK, 6667))); + assertEquals("connect not implemented", ex.getMessage()); } } @Test public void testOldImplConnectTwoArgs() { try (var ds = new DatagramSocket(new OldDatagramSocketImpl()) {}) { - ds.connect(LOOPBACK, 6667); - throw new RuntimeException("ERROR: test failed"); - } catch (UncheckedIOException ex) { - assertEquals(ex.getMessage(), "connect failed"); - System.out.println("PASSED: default implementation of connect has thrown as expected"); + UncheckedIOException ex = assertThrows(UncheckedIOException.class, + () -> ds.connect(LOOPBACK, 6667)); + assertEquals("connect failed", ex.getMessage()); } } @@ -64,36 +69,27 @@ public class OldDatagramSocketImplTest { public void testOldImplDisconnect() { try (var ds = new DatagramSocket(new OldDatagramSocketImplWithValidConnect()) { }){ ds.connect(LOOPBACK, 6667); - ds.disconnect(); - throw new RuntimeException("ERROR: test failed"); - } catch (UncheckedIOException ex) { + UncheckedIOException ex = assertThrows(UncheckedIOException.class, () -> ds.disconnect()); var innerException = ex.getCause(); - assertEquals(innerException.getClass(), SocketException.class); - assertEquals(innerException.getMessage(), "disconnect not implemented"); - System.out.println("PASSED: default implementation of disconnect has thrown as expected"); + assertSame(SocketException.class, innerException.getClass()); + assertEquals("disconnect not implemented", innerException.getMessage()); } } @Test public void testOldImplPublic() { try (var ds = new PublicOldDatagramSocketImpl()) { - ds.connect(LOOPBACK, 0); - throw new RuntimeException("ERROR: test failed"); - } catch (SocketException ex) { - assertEquals(ex.getMessage(), "connect not implemented"); - System.out.println("PASSED: default implementation of disconnect has thrown as expected"); + SocketException ex = assertThrows(SocketException.class, () -> ds.connect(LOOPBACK, 0)); + assertEquals("connect not implemented", ex.getMessage()); } } @Test public void testOldImplPublicDisconnect() { try (var ds = new PublicOldDatagramSocketImplWithValidConnect()) { - ds.disconnect(); - throw new RuntimeException("ERROR: test failed"); - } catch (UncheckedIOException ex) { + UncheckedIOException ex = assertThrows(UncheckedIOException.class, () -> ds.disconnect()); var innerException = ex.getCause(); - assertEquals(innerException.getClass(), SocketException.class); - assertEquals(innerException.getMessage(), "disconnect not implemented"); - System.out.println("PASSED: default implementation of disconnect has thrown as expected"); + assertSame(SocketException.class, innerException.getClass()); + assertEquals("disconnect not implemented", innerException.getMessage()); } } diff --git a/test/jdk/java/net/DatagramSocket/SendCheck.java b/test/jdk/java/net/DatagramSocket/SendCheck.java index 482eadb3cf9..5cefb0fd0e6 100644 --- a/test/jdk/java/net/DatagramSocket/SendCheck.java +++ b/test/jdk/java/net/DatagramSocket/SendCheck.java @@ -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,12 +33,14 @@ import java.nio.channels.DatagramChannel; import java.util.ArrayList; import java.util.List; -import org.testng.annotations.AfterTest; -import org.testng.annotations.BeforeTest; -import org.testng.annotations.Test; -import org.testng.annotations.DataProvider; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; -import static org.testng.Assert.expectThrows; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; /* * @test @@ -47,28 +49,24 @@ import static org.testng.Assert.expectThrows; * DatagramSocketAdaptor and DatagramChannel all * throw expected Exception when passed a DatagramPacket * with invalid details - * @run testng SendCheck + * @run junit ${test.main.class} */ public class SendCheck { - private InetAddress loopbackAddr, wildcardAddr; + private static InetAddress loopbackAddr, wildcardAddr; static final Class IOE = IOException.class; static final Class SE = SocketException.class; static final byte[] buf = {0, 1, 2}; static DatagramSocket socket; - @BeforeTest - public void setUp() { - try { - socket = new DatagramSocket(); - } catch (Exception e) { - throw new ExceptionInInitializerError(e); - } + @BeforeAll + public static void setUp() throws Exception { + socket = new DatagramSocket(); } - @AfterTest - public void closeDown() { + @AfterAll + public static void closeDown() { socket.close(); } @@ -92,7 +90,7 @@ public class SendCheck { if (address == null) { description = ":" + port; } else if (port < 0) { - description = packet.getAddress().toString() + ":" + port; + description = packet.getAddress() + ":" + port; } else { description = packet.getSocketAddress().toString(); } @@ -100,8 +98,7 @@ public class SendCheck { } } - @DataProvider(name = "packets") - Object[][] providerIO() throws IOException { + static List providerIO() throws IOException { loopbackAddr = InetAddress.getLoopbackAddress(); wildcardAddr = new InetSocketAddress(0).getAddress(); @@ -124,44 +121,46 @@ public class SendCheck { List Packets = List.of(Packet.of(pkt1), Packet.of(pkt2)); - List senders = List.of( - Sender.of(new DatagramSocket(null)), - Sender.of(new MulticastSocket(null)), - Sender.of(DatagramChannel.open()), - Sender.of(DatagramChannel.open().socket()) - ); - - List testcases = new ArrayList<>(); + List testcases = new ArrayList<>(); for (var packet : Packets) { + // Note that Closeable arguments passed to a ParameterizedTest are automatically + // closed by JUnit. We do not want to rely on this, but we do need to + // create a new set of sockets for each invocation of this method, so that + // the next test method invoked doesn't get a closed socket. + List senders = List.of( + Sender.of(new DatagramSocket(null)), + Sender.of(new MulticastSocket(null)), + Sender.of(DatagramChannel.open()), + Sender.of(DatagramChannel.open().socket()) + ); addTestCaseFor(testcases, senders, packet); } - return testcases.toArray(new Object[0][0]); + return testcases; } - static void addTestCaseFor(List testcases, + static void addTestCaseFor(List testcases, List senders, Packet p) { for (var s : senders) { - Object[] testcase = new Object[]{s, p, s.expectedException()}; - testcases.add(testcase); + testcases.add(Arguments.of(s, p, s.expectedException())); } } - @Test(dataProvider = "packets") - public static void sendCheck(Sender sender, - Packet packet, - Class exception) { - DatagramPacket pkt = packet.packet; - if (exception != null) { - Throwable t = expectThrows(exception, () -> sender.send(pkt)); - System.out.printf("%s got expected exception %s%n", - packet.toString(), t); - } else { - try { - sender.send(pkt); - } catch (IOException e) { - throw new AssertionError("Unexpected exception for " - + sender + " / " + packet, e); + @ParameterizedTest + @MethodSource("providerIO") + public void sendCheck(Sender socket, + Packet packet, + Class exception) + throws IOException + { + try (var sender = socket) { + DatagramPacket pkt = packet.packet; + if (exception != null) { + Throwable t = assertThrows(exception, () -> sender.send(pkt)); + System.out.printf("%s got expected exception %s%n", packet, t); + } else { + assertDoesNotThrow(() -> sender.send(pkt), + "Unexpected exception for " + sender + " / " + packet); } } } diff --git a/test/jdk/java/net/DatagramSocket/SendPortZero.java b/test/jdk/java/net/DatagramSocket/SendPortZero.java index 03c78c3e54d..149424232ee 100644 --- a/test/jdk/java/net/DatagramSocket/SendPortZero.java +++ b/test/jdk/java/net/DatagramSocket/SendPortZero.java @@ -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 @@ -21,39 +21,40 @@ * questions. */ -import org.testng.annotations.AfterTest; -import org.testng.annotations.BeforeTest; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetSocketAddress; import java.net.InetAddress; -import java.net.MulticastSocket; import java.net.SocketException; import java.nio.channels.DatagramChannel; -import static org.testng.Assert.assertThrows; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; /* * @test * @bug 8236105 8240533 * @summary Check that DatagramSocket throws expected * Exception when sending a DatagramPacket with port 0 - * @run testng/othervm SendPortZero + * @run junit/othervm ${test.main.class} */ public class SendPortZero { - private InetAddress loopbackAddr, wildcardAddr; - private DatagramSocket datagramSocket, datagramSocketAdaptor; - private DatagramPacket loopbackZeroPkt, wildcardZeroPkt, wildcardValidPkt; + private static InetAddress loopbackAddr, wildcardAddr; + private static DatagramSocket datagramSocket, datagramSocketAdaptor; + private static DatagramPacket loopbackZeroPkt, wildcardZeroPkt, wildcardValidPkt; private static final Class SE = SocketException.class; - @BeforeTest - public void setUp() throws IOException { + @BeforeAll + public static void setUp() throws IOException { datagramSocket = new DatagramSocket(); datagramSocketAdaptor = DatagramChannel.open().socket(); @@ -81,8 +82,13 @@ public class SendPortZero { wildcardValidPkt.setPort(datagramSocket.getLocalPort()); } - @DataProvider(name = "data") - public Object[][] variants() { + @AfterAll + public static void tearDown() { + datagramSocket.close(); + datagramSocketAdaptor.close(); + } + + public static Object[][] testCases() { return new Object[][]{ { datagramSocket, loopbackZeroPkt }, { datagramSocket, wildcardZeroPkt }, @@ -96,14 +102,10 @@ public class SendPortZero { }; } - @Test(dataProvider = "data") + @ParameterizedTest(autoCloseArguments = false) // closed in tearDown + @MethodSource("testCases") public void testSend(DatagramSocket ds, DatagramPacket pkt) { + assertFalse(ds.isClosed()); assertThrows(SE, () -> ds.send(pkt)); } - - @AfterTest - public void tearDown() { - datagramSocket.close(); - datagramSocketAdaptor.close(); - } } diff --git a/test/jdk/java/net/DatagramSocket/SendReceiveMaxSize.java b/test/jdk/java/net/DatagramSocket/SendReceiveMaxSize.java index 36eae0b5c32..d85817337f4 100644 --- a/test/jdk/java/net/DatagramSocket/SendReceiveMaxSize.java +++ b/test/jdk/java/net/DatagramSocket/SendReceiveMaxSize.java @@ -33,7 +33,7 @@ * limit. * @library /test/lib * @build jdk.test.lib.net.IPSupport - * @run testng/othervm SendReceiveMaxSize + * @run junit/othervm ${test.main.class} */ /* * @test id=preferIPv4Stack @@ -42,7 +42,7 @@ * maximum size on macOS, using an IPv4 only socket. * @library /test/lib * @build jdk.test.lib.net.IPSupport - * @run testng/othervm -Djava.net.preferIPv4Stack=true SendReceiveMaxSize + * @run junit/othervm -Djava.net.preferIPv4Stack=true ${test.main.class} */ /* * @test id=preferIPv6Addresses @@ -52,7 +52,7 @@ * IPv6 addresses. * @library /test/lib * @build jdk.test.lib.net.IPSupport - * @run testng/othervm -Djava.net.preferIPv6Addresses=true SendReceiveMaxSize + * @run junit/othervm -Djava.net.preferIPv6Addresses=true ${test.main.class} */ /* * @test id=preferLoopback @@ -62,7 +62,7 @@ * interface. * @library /test/lib * @build jdk.test.lib.net.IPSupport - * @run testng/othervm -Dtest.preferLoopback=true SendReceiveMaxSize + * @run junit/othervm -Dtest.preferLoopback=true ${test.main.class} */ /* * @test id=preferIPv6Loopback @@ -72,7 +72,7 @@ * interface. * @library /test/lib * @build jdk.test.lib.net.IPSupport - * @run testng/othervm -Dtest.preferLoopback=true -Djava.net.preferIPv6Addresses=true SendReceiveMaxSize + * @run junit/othervm -Dtest.preferLoopback=true -Djava.net.preferIPv6Addresses=true ${test.main.class} */ /* * @test id=preferIPv4Loopback @@ -82,16 +82,12 @@ * loopback interface * @library /test/lib * @build jdk.test.lib.net.IPSupport - * @run testng/othervm -Dtest.preferLoopback=true -Djava.net.preferIPv4Stack=true SendReceiveMaxSize + * @run junit/othervm -Dtest.preferLoopback=true -Djava.net.preferIPv4Stack=true ${test.main.class} */ import jdk.test.lib.RandomFactory; import jdk.test.lib.Platform; import jdk.test.lib.net.IPSupport; -import org.testng.SkipException; -import org.testng.annotations.BeforeTest; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; import java.io.IOException; import java.net.DatagramPacket; @@ -101,21 +97,27 @@ import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.MulticastSocket; import java.nio.channels.DatagramChannel; -import java.util.Optional; import java.util.Random; import static java.net.StandardSocketOptions.SO_RCVBUF; import static jdk.test.lib.net.IPSupport.diagnoseConfigurationIssue; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.expectThrows; + +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; public class SendReceiveMaxSize { private final static boolean PREFER_LOOPBACK = Boolean.getBoolean("test.preferLoopback"); - private int BUF_LIMIT; - private InetAddress HOST_ADDR; + private static int BUF_LIMIT; + private static InetAddress HOST_ADDR; private final static int IPV4_SNDBUF = IPSupport.getMaxUDPSendBufSizeIPv4(); private final static int IPV6_SNDBUF = IPSupport.getMaxUDPSendBufSizeIPv6(); private final static Class IOE = IOException.class; @@ -126,20 +128,16 @@ public class SendReceiveMaxSize { } static DatagramSocketSupplier supplier(DatagramSocketSupplier supplier) { return supplier; } - @BeforeTest - public void setUp() throws IOException { - Optional configurationIssue = diagnoseConfigurationIssue(); - configurationIssue.map(SkipException::new).ifPresent(x -> { - throw x; - }); - + @BeforeAll + public static void setUp() throws IOException { + // skip test if the configuration is not operational + diagnoseConfigurationIssue().ifPresent(Assumptions::abort); HOST_ADDR = PREFER_LOOPBACK ? InetAddress.getLoopbackAddress() : InetAddress.getLocalHost(); BUF_LIMIT = (HOST_ADDR instanceof Inet6Address) ? IPV6_SNDBUF : IPV4_SNDBUF; System.out.printf("Host address: %s, Buffer limit: %d%n", HOST_ADDR, BUF_LIMIT); } - @DataProvider - public Object[][] invariants() { + public static Object[][] testCases() { var ds = supplier(() -> new DatagramSocket()); var ms = supplier(() -> new MulticastSocket()); var dsa = supplier(() -> DatagramChannel.open().socket()); @@ -156,7 +154,8 @@ public class SendReceiveMaxSize { }; } - @Test(dataProvider = "invariants") + @ParameterizedTest + @MethodSource("testCases") public void testSendReceiveMaxSize(String name, int capacity, DatagramSocketSupplier supplier, Class exception) throws IOException { @@ -177,7 +176,7 @@ public class SendReceiveMaxSize { var sendPkt = new DatagramPacket(testData, capacity, addr); if (exception != null) { - Exception ex = expectThrows(IOE, () -> sender.send(sendPkt)); + Exception ex = assertThrows(exception, () -> sender.send(sendPkt)); System.out.println(name + " got expected exception: " + ex); } else { sender.send(sendPkt); @@ -185,8 +184,8 @@ public class SendReceiveMaxSize { receiver.receive(receivePkt); // check packet data has been fragmented and re-assembled correctly at receiver - assertEquals(receivePkt.getLength(), capacity); - assertEquals(receivePkt.getData(), testData); + assertEquals(capacity, receivePkt.getLength()); + assertArrayEquals(testData, receivePkt.getData()); } } } diff --git a/test/jdk/java/net/DatagramSocket/SetGetReceiveBufferSize.java b/test/jdk/java/net/DatagramSocket/SetGetReceiveBufferSize.java index 67c890cd2e2..96fa4d68d2e 100644 --- a/test/jdk/java/net/DatagramSocket/SetGetReceiveBufferSize.java +++ b/test/jdk/java/net/DatagramSocket/SetGetReceiveBufferSize.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2021, 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 @@ -25,21 +25,20 @@ * @test * @bug 4173717 8243488 * @summary Check that setReceiveBufferSize and getReceiveBufferSize work as expected - * @run testng SetGetReceiveBufferSize - * @run testng/othervm -Djava.net.preferIPv4Stack=true SetGetReceiveBufferSize + * @run junit ${test.main.class} + * @run junit/othervm -Djava.net.preferIPv4Stack=true ${test.main.class} */ -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; - import java.io.IOException; import java.net.DatagramSocket; import java.net.MulticastSocket; import java.net.SocketException; import java.nio.channels.DatagramChannel; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.expectThrows; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; public class SetGetReceiveBufferSize { static final Class SE = SocketException.class; @@ -51,8 +50,7 @@ public class SetGetReceiveBufferSize { } static DatagramSocketSupplier supplier(DatagramSocketSupplier supplier) { return supplier; } - @DataProvider - public Object[][] invariants() { + public static Object[][] suppliers() { return new Object[][]{ {"DatagramSocket", supplier(() -> new DatagramSocket())}, {"MulticastSocket", supplier(() -> new MulticastSocket())}, @@ -60,39 +58,42 @@ public class SetGetReceiveBufferSize { }; } - @Test(dataProvider = "invariants") + @ParameterizedTest + @MethodSource("suppliers") public void testSetInvalidBufferSize(String name, DatagramSocketSupplier supplier) throws IOException { var invalidArgs = new int[]{ -1, 0 }; try (var socket = supplier.open()) { for (int i : invalidArgs) { - Exception ex = expectThrows(IAE, () -> socket.setReceiveBufferSize(i)); + Exception ex = assertThrows(IAE, () -> socket.setReceiveBufferSize(i)); System.out.println(name + " got expected exception: " + ex); } } } - @Test(dataProvider = "invariants") + @ParameterizedTest + @MethodSource("suppliers") public void testSetAndGetBufferSize(String name, DatagramSocketSupplier supplier) throws IOException { var validArgs = new int[]{ 1234, 2345, 3456 }; try (var socket = supplier.open()) { for (int i : validArgs) { socket.setReceiveBufferSize(i); - assertEquals(socket.getReceiveBufferSize(), i, name); + assertEquals(i, socket.getReceiveBufferSize(), name); } } } - @Test(dataProvider = "invariants") + @ParameterizedTest + @MethodSource("suppliers") public void testSetGetAfterClose(String name, DatagramSocketSupplier supplier) throws IOException { var socket = supplier.open(); socket.close(); - Exception setException = expectThrows(SE, () -> socket.setReceiveBufferSize(2345)); + Exception setException = assertThrows(SE, () -> socket.setReceiveBufferSize(2345)); System.out.println(name + " got expected exception: " + setException); - Exception getException = expectThrows(SE, () -> socket.getReceiveBufferSize()); + Exception getException = assertThrows(SE, () -> socket.getReceiveBufferSize()); System.out.println(name + " got expected exception: " + getException); } } diff --git a/test/jdk/java/net/DatagramSocket/SetGetSendBufferSize.java b/test/jdk/java/net/DatagramSocket/SetGetSendBufferSize.java index 0f6f9af4c60..07ae4bfad59 100644 --- a/test/jdk/java/net/DatagramSocket/SetGetSendBufferSize.java +++ b/test/jdk/java/net/DatagramSocket/SetGetSendBufferSize.java @@ -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 @@ -27,14 +27,12 @@ * @library /test/lib * @build jdk.test.lib.Platform * @summary Check that setSendBufferSize and getSendBufferSize work as expected - * @run testng SetGetSendBufferSize - * @run testng/othervm -Djava.net.preferIPv4Stack=true SetGetSendBufferSize + * @run junit ${test.main.class} + * @run junit/othervm -Djava.net.preferIPv4Stack=true ${test.main.class} */ import jdk.test.lib.Platform; import jdk.test.lib.net.IPSupport; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; import java.io.IOException; import java.net.DatagramSocket; @@ -42,9 +40,11 @@ import java.net.MulticastSocket; import java.net.SocketException; import java.nio.channels.DatagramChannel; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.expectThrows; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; public class SetGetSendBufferSize { static final Class SE = SocketException.class; @@ -55,8 +55,7 @@ public class SetGetSendBufferSize { } static DatagramSocketSupplier supplier(DatagramSocketSupplier supplier) { return supplier; } - @DataProvider - public Object[][] invariants() { + public static Object[][] suppliers() { return new Object[][]{ {"DatagramSocket", supplier(() -> new DatagramSocket())}, {"MulticastSocket", supplier(() -> new MulticastSocket())}, @@ -64,51 +63,55 @@ public class SetGetSendBufferSize { }; } - @Test(dataProvider = "invariants") + @ParameterizedTest + @MethodSource("suppliers") public void testSetInvalidBufferSize(String name, DatagramSocketSupplier supplier) throws IOException { var invalidArgs = new int[]{ -1, 0 }; try (var socket = supplier.open()) { for (int i : invalidArgs) { - Exception ex = expectThrows(IAE, () -> socket.setSendBufferSize(i)); + Exception ex = assertThrows(IAE, () -> socket.setSendBufferSize(i)); System.out.println(name + " got expected exception: " + ex); } } } - @Test(dataProvider = "invariants") + @ParameterizedTest + @MethodSource("suppliers") public void testSetAndGetBufferSize(String name, DatagramSocketSupplier supplier) throws IOException { var validArgs = new int[]{ 2345, 3456 }; try (var socket = supplier.open()) { for (int i : validArgs) { socket.setSendBufferSize(i); - assertEquals(socket.getSendBufferSize(), i, name); + assertEquals(i, socket.getSendBufferSize(), name); } } } - @Test(dataProvider = "invariants") + @ParameterizedTest + @MethodSource("suppliers") public void testInitialSendBufferSize(String name, DatagramSocketSupplier supplier) throws IOException { if (Platform.isOSX()) { try (var socket = supplier.open()){ assertTrue(socket.getSendBufferSize() >= 65507, name); if (IPSupport.hasIPv6() && !IPSupport.preferIPv4Stack()) { - assertEquals(socket.getSendBufferSize(), 65527, name); + assertEquals(65527, socket.getSendBufferSize(), name); } } } } - @Test(dataProvider = "invariants") + @ParameterizedTest + @MethodSource("suppliers") public void testSetGetAfterClose(String name, DatagramSocketSupplier supplier) throws IOException { var socket = supplier.open(); socket.close(); - Exception setException = expectThrows(SE, () -> socket.setSendBufferSize(2345)); + Exception setException = assertThrows(SE, () -> socket.setSendBufferSize(2345)); System.out.println(name + " got expected exception: " + setException); - Exception getException = expectThrows(SE, () -> socket.getSendBufferSize()); + Exception getException = assertThrows(SE, () -> socket.getSendBufferSize()); System.out.println(name + " got expected exception: " + getException); } } diff --git a/test/jdk/java/net/DatagramSocket/SupportedOptionsCheck.java b/test/jdk/java/net/DatagramSocket/SupportedOptionsCheck.java index 9ad381be73c..0186e3e4d46 100644 --- a/test/jdk/java/net/DatagramSocket/SupportedOptionsCheck.java +++ b/test/jdk/java/net/DatagramSocket/SupportedOptionsCheck.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 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 @@ -27,17 +27,17 @@ * @library /test/lib * @summary checks that the DatagramSocket supportedOptions set contains all * MulticastSocket socket options - * @run testng SupportedOptionsCheck + * @run junit ${test.main.class} */ import jdk.test.lib.Platform; -import org.testng.annotations.Test; import java.net.DatagramSocket; import java.net.StandardSocketOptions; import java.util.Set; -import static org.testng.Assert.assertTrue; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertTrue; public class SupportedOptionsCheck { From ac74e6f9433f1e0ad177e579bde7f2338189d6c6 Mon Sep 17 00:00:00 2001 From: Albert Mingkun Yang Date: Mon, 13 Apr 2026 10:56:38 +0000 Subject: [PATCH 027/108] 8382055: G1: Remove unused unused args in mark_evac_failure_object Reviewed-by: tschatzl --- src/hotspot/share/gc/g1/g1CollectedHeap.cpp | 2 +- src/hotspot/share/gc/g1/g1CollectedHeap.hpp | 2 +- src/hotspot/share/gc/g1/g1ParScanThreadState.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp index fe286793ae7..2709e6b3008 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp @@ -3219,7 +3219,7 @@ void G1CollectedHeap::retire_gc_alloc_region(G1HeapRegion* alloc_region, G1HeapRegionPrinter::retire(alloc_region); } -void G1CollectedHeap::mark_evac_failure_object(uint worker_id, const oop obj, size_t obj_size) const { +void G1CollectedHeap::mark_evac_failure_object(const oop obj) const { assert(!_cm->is_marked_in_bitmap(obj), "must be"); _cm->raw_mark_in_bitmap(obj); diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.hpp b/src/hotspot/share/gc/g1/g1CollectedHeap.hpp index b5cb9167d92..3a47453819e 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.hpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.hpp @@ -1275,7 +1275,7 @@ public: inline bool is_obj_dead_full(const oop obj) const; // Mark the live object that failed evacuation in the bitmap. - void mark_evac_failure_object(uint worker_id, oop obj, size_t obj_size) const; + void mark_evac_failure_object(oop obj) const; G1ConcurrentMark* concurrent_mark() const { return _cm; } diff --git a/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp b/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp index cb857dc6eab..2b0e6ce9cf4 100644 --- a/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp +++ b/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp @@ -650,7 +650,7 @@ oop G1ParScanThreadState::handle_evacuation_failure_par(oop old, markWord m, Kla // Mark the failing object in the marking bitmap and later use the bitmap to handle // evacuation failure recovery. - _g1h->mark_evac_failure_object(_worker_id, old, word_sz); + _g1h->mark_evac_failure_object(old); _evacuation_failed_info.register_copy_failure(word_sz); From 03b46a308ce944b515939db613f5250a5b84b844 Mon Sep 17 00:00:00 2001 From: Daniel Fuchs Date: Mon, 13 Apr 2026 12:44:27 +0000 Subject: [PATCH 028/108] 8381767: Refactor various java/net/*[URL/Http]*/ TestNG tests to use JUnit Reviewed-by: vyazici, myankelevich --- .../whitebox/MaxAgeExpiresDriver.java | 6 +- .../java.base/java/net/MaxAgeExpires.java | 42 ++++---- .../HttpURLConnectionHeadersOrder.java | 54 ++++++----- .../HttpURLProxySelectionTest.java | 55 +++++------ .../HttpURLConnection/Response1xxTest.java | 39 ++++---- .../JarURLConnection/TestDefaultBehavior.java | 95 ++++++++++--------- .../definePackage/SplitPackage.java | 29 +++--- .../net/URLConnection/RequestProperties.java | 48 +++++----- .../URLConnection/SetDefaultUseCaches.java | 31 +++--- .../URLConnectionHeadersOrder.java | 19 ++-- .../jdk/java/net/URLDecoder/EncodingTest.java | 44 +++++---- .../jdk/java/net/URLEncoder/EncodingTest.java | 25 ++--- .../net/URLPermission/EmptyAuthorityTest.java | 26 ++--- .../URLPermission/InvalidCharacterTest.java | 19 ++-- .../URLStreamHandler/TestDefaultBehavior.java | 25 ++--- 15 files changed, 297 insertions(+), 260 deletions(-) diff --git a/test/jdk/java/net/HttpCookie/whitebox/MaxAgeExpiresDriver.java b/test/jdk/java/net/HttpCookie/whitebox/MaxAgeExpiresDriver.java index 7f535fa0c23..c3cc3eddc68 100644 --- a/test/jdk/java/net/HttpCookie/whitebox/MaxAgeExpiresDriver.java +++ b/test/jdk/java/net/HttpCookie/whitebox/MaxAgeExpiresDriver.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 @@ -25,7 +25,5 @@ * @test * @bug 8351983 * @summary HttpCookie Parser Incorrectly Handles Cookies with Expires Attribute - * @run testng java.base/java.net.MaxAgeExpires + * @run junit java.base/java.net.MaxAgeExpires */ -public class MaxAgeExpiresDriver { -} \ No newline at end of file diff --git a/test/jdk/java/net/HttpCookie/whitebox/java.base/java/net/MaxAgeExpires.java b/test/jdk/java/net/HttpCookie/whitebox/java.base/java/net/MaxAgeExpires.java index 5c4f8f66b3d..6704a290836 100644 --- a/test/jdk/java/net/HttpCookie/whitebox/java.base/java/net/MaxAgeExpires.java +++ b/test/jdk/java/net/HttpCookie/whitebox/java.base/java/net/MaxAgeExpires.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 @@ -23,12 +23,16 @@ package java.net; -import java.time.*; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; -import java.util.*; -import org.testng.Assert; -import org.testng.annotations.Test; -import org.testng.annotations.DataProvider; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.junit.jupiter.api.Assertions.assertEquals; public class MaxAgeExpires { @@ -54,8 +58,7 @@ public class MaxAgeExpires { return zdt.toInstant().getEpochSecond() * 1000; // always exact seconds } - @DataProvider(name = "testData") - public Object[][] testData() { + public static Object[][] testData() { return new Object[][] { // Date string in past. But checking based on current time. {-1L, -1L, -1L, DT1, 0, true}, @@ -79,10 +82,11 @@ public class MaxAgeExpires { {zdtToMillis(zdt1), zdtToMillis(zdt2), -1L, DT3,120, false} }; - }; + } - @Test(dataProvider = "testData") + @ParameterizedTest + @MethodSource("testData") public void test1(long creationInstant, // if -1, then current time is used long expiryCheckInstant, // if -1 then current time is used long maxAge, // if -1, then not included in String @@ -99,7 +103,7 @@ public class MaxAgeExpires { sb.append("; max-age=" + Long.toString(maxAge)); String s = sb.toString(); - System.out.println(s); + System.err.println(s); HttpCookie cookie; if (creationInstant == -1) cookie = HttpCookie.parse(s).get(0); @@ -107,8 +111,8 @@ public class MaxAgeExpires { cookie = HttpCookie.parse(s, false, creationInstant).get(0); if (expectedAge != -1 && cookie.getMaxAge() != expectedAge) { - System.out.println("getMaxAge returned " + cookie.getMaxAge()); - System.out.println("expectedAge was " + expectedAge); + System.err.println("getMaxAge returned " + cookie.getMaxAge()); + System.err.println("expectedAge was " + expectedAge); throw new RuntimeException("Test failed: wrong age"); } @@ -117,9 +121,9 @@ public class MaxAgeExpires { : cookie.hasExpired(expiryCheckInstant); if (expired != hasExpired) { - System.out.println("cookie.hasExpired() returned " + expired); - System.out.println("hasExpired was " + hasExpired); - System.out.println("getMaxAge() returned " + cookie.getMaxAge()); + System.err.println("cookie.hasExpired() returned " + expired); + System.err.println("hasExpired was " + hasExpired); + System.err.println("getMaxAge() returned " + cookie.getMaxAge()); throw new RuntimeException("Test failed: wrong hasExpired"); } } @@ -128,10 +132,10 @@ public class MaxAgeExpires { public void test2() { // Miscellaneous tests that setMaxAge() overrides whatever was set already HttpCookie cookie = HttpCookie.parse("Set-Cookie: name=value; max-age=100").get(0); - Assert.assertEquals(cookie.getMaxAge(), 100); + assertEquals(100, cookie.getMaxAge()); cookie.setMaxAge(200); - Assert.assertEquals(cookie.getMaxAge(), 200); + assertEquals(200, cookie.getMaxAge()); cookie.setMaxAge(-2); - Assert.assertEquals(cookie.getMaxAge(), -2); + assertEquals(-2, cookie.getMaxAge()); } } diff --git a/test/jdk/java/net/HttpURLConnection/HttpURLConnectionHeadersOrder.java b/test/jdk/java/net/HttpURLConnection/HttpURLConnectionHeadersOrder.java index a44eef9c3f5..9a490ce18e7 100644 --- a/test/jdk/java/net/HttpURLConnection/HttpURLConnectionHeadersOrder.java +++ b/test/jdk/java/net/HttpURLConnection/HttpURLConnectionHeadersOrder.java @@ -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 @@ -21,30 +21,36 @@ * questions. */ -/** +/* * @test * @bug 8133686 * @summary Ensuring that multiple header values for a given field-name are returned in * the order they were added for HttpURLConnection.getRequestProperties * and HttpURLConnection.getHeaderFields * @library /test/lib - * @run testng HttpURLConnectionHeadersOrder + * @run junit ${test.main.class} */ import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; import jdk.test.lib.net.URIBuilder; -import org.testng.Assert; -import org.testng.annotations.AfterTest; -import org.testng.annotations.BeforeTest; -import org.testng.annotations.Test; import java.io.IOException; -import java.net.*; +import java.net.HttpURLConnection; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.URL; import java.util.Arrays; import java.util.List; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + public class HttpURLConnectionHeadersOrder { private static final String LOCAL_TEST_ENDPOINT = "/headertest"; private static final String ERROR_MESSAGE_TEMPLATE = "Expected Request Properties = %s, Actual Request Properties = %s"; @@ -53,8 +59,8 @@ public class HttpURLConnectionHeadersOrder { private static URL serverUrl; - @BeforeTest - public void beforeTest() throws Exception { + @BeforeAll + public static void beforeTest() throws Exception { SimpleHandler handler = new SimpleHandler(); server = createSimpleHttpServer(handler); serverUrl = URIBuilder.newBuilder() @@ -65,8 +71,8 @@ public class HttpURLConnectionHeadersOrder { .toURL(); } - @AfterTest - public void afterTest() { + @AfterAll + public static void afterTest() { if (server != null) server.stop(0); } @@ -77,9 +83,9 @@ public class HttpURLConnectionHeadersOrder { * - request to a "dummy" server with additional * - custom request properties * - * @throws Exception + * @throws Exception if failed */ - @Test (priority = 1) + @Test public void testRequestPropertiesOrder() throws Exception { final var conn = (HttpURLConnection) serverUrl.openConnection(); @@ -93,8 +99,9 @@ public class HttpURLConnectionHeadersOrder { var customRequestProps = requestProperties.get("test_req_prop"); conn.disconnect(); - Assert.assertNotNull(customRequestProps); - Assert.assertEquals(customRequestProps, EXPECTED_HEADER_VALUES, String.format(ERROR_MESSAGE_TEMPLATE, EXPECTED_HEADER_VALUES.toString(), customRequestProps.toString())); + assertNotNull(customRequestProps); + assertEquals(EXPECTED_HEADER_VALUES, customRequestProps, + "Unexpected value for request header \"test_req_prop\""); } /** @@ -105,7 +112,7 @@ public class HttpURLConnectionHeadersOrder { * * @throws Exception */ - @Test (priority = 2) + @Test public void testServerSideRequestHeadersOrder() throws Exception { final var conn = (HttpURLConnection) serverUrl.openConnection(); conn.addRequestProperty("test_server_handling", "a"); @@ -114,17 +121,19 @@ public class HttpURLConnectionHeadersOrder { int statusCode = conn.getResponseCode(); conn.disconnect(); - Assert.assertEquals(statusCode, 999, "The insertion-order was not preserved on the server-side response headers handling"); + assertEquals(999, statusCode, + "The insertion-order was not preserved on the server-side response headers handling"); } - @Test (priority = 3) + @Test public void testClientSideResponseHeadersOrder() throws Exception { final var conn = (HttpURLConnection) serverUrl.openConnection(); conn.setRequestMethod("GET"); var actualCustomResponseHeaders = conn.getHeaderFields().get("Test_response"); - Assert.assertNotNull(actualCustomResponseHeaders, "Error in reading custom response headers"); - Assert.assertEquals(EXPECTED_HEADER_VALUES, actualCustomResponseHeaders, String.format(ERROR_MESSAGE_TEMPLATE, EXPECTED_HEADER_VALUES.toString(), actualCustomResponseHeaders.toString())); + assertNotNull(actualCustomResponseHeaders, "Error in reading custom response headers"); + assertEquals(EXPECTED_HEADER_VALUES, actualCustomResponseHeaders, + "Unexpected value for response header field \"Test_response\""); } private static HttpServer createSimpleHttpServer(SimpleHandler handler) throws IOException { @@ -153,7 +162,8 @@ public class HttpURLConnectionHeadersOrder { } if (!actualTestRequestHeaders.equals(EXPECTED_HEADER_VALUES)) { - System.out.println("Error: " + String.format(ERROR_MESSAGE_TEMPLATE, EXPECTED_HEADER_VALUES.toString(), actualTestRequestHeaders.toString())); + System.out.printf("Error for \"test_server_handling\" " + + String.format(ERROR_MESSAGE_TEMPLATE, EXPECTED_HEADER_VALUES, actualTestRequestHeaders)); return -1; } return 999; diff --git a/test/jdk/java/net/HttpURLConnection/HttpURLProxySelectionTest.java b/test/jdk/java/net/HttpURLConnection/HttpURLProxySelectionTest.java index a6f2fbc1ae6..be922608c80 100644 --- a/test/jdk/java/net/HttpURLConnection/HttpURLProxySelectionTest.java +++ b/test/jdk/java/net/HttpURLConnection/HttpURLProxySelectionTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 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,10 +25,6 @@ import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; import jdk.test.lib.net.URIBuilder; -import org.testng.Assert; -import org.testng.annotations.AfterTest; -import org.testng.annotations.BeforeTest; -import org.testng.annotations.Test; import sun.net.spi.DefaultProxySelector; import java.io.IOException; @@ -42,35 +38,43 @@ import java.net.URISyntaxException; import java.net.URL; import java.util.List; -/** +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/* * @test * @bug 6563286 6797318 8177648 8230220 * @summary Tests that sun.net.www.protocol.http.HttpURLConnection when dealing with * sun.net.spi.DefaultProxySelector#select() handles any IllegalArgumentException * correctly * @library /test/lib - * @run testng HttpURLProxySelectionTest + * @run junit ${test.main.class} * @modules java.base/sun.net.spi:+open */ public class HttpURLProxySelectionTest { private static final String WEB_APP_CONTEXT = "/httpurlproxytest"; - private HttpServer server; - private SimpleHandler handler; - private ProxySelector previousDefault; - private CustomProxySelector ourProxySelector = new CustomProxySelector(); + private static HttpServer server; + private static SimpleHandler handler; + private static ProxySelector previousDefault; + private static final CustomProxySelector ourProxySelector = new CustomProxySelector(); - @BeforeTest - public void beforeTest() throws Exception { + @BeforeAll + public static void beforeTest() throws Exception { previousDefault = ProxySelector.getDefault(); ProxySelector.setDefault(ourProxySelector); handler = new SimpleHandler(); server = createServer(handler); } - @AfterTest - public void afterTest() { + @AfterAll + public static void afterTest() { try { if (server != null) { final int delaySeconds = 0; @@ -86,7 +90,7 @@ public class HttpURLProxySelectionTest { * - Server receives request and sends a 301 redirect to an URI which doesn't have a "host" * - Redirect is expected to fail with IOException (caused by IllegalArgumentException from DefaultProxySelector) * - * @throws Exception + * @throws Exception if failed */ @Test public void test() throws Exception { @@ -98,19 +102,16 @@ public class HttpURLProxySelectionTest { .toURL(); System.out.println("Sending request to " + targetURL); final HttpURLConnection conn = (HttpURLConnection) targetURL.openConnection(); - try { - conn.getResponseCode(); - Assert.fail("Request to " + targetURL + " was expected to fail during redirect"); - } catch (IOException ioe) { - // expected because of the redirect to an invalid URL, for which a proxy can't be selected + // expected because of the redirect to an invalid URL, for which a proxy can't be selected + IOException ioe = assertThrows(IOException.class, conn::getResponseCode, + "Request to " + targetURL + " was expected to fail during redirect"); - // make sure the it was indeed a redirect - Assert.assertTrue(handler.redirectSent, "Server was expected to send a redirect, but didn't"); - Assert.assertTrue(ourProxySelector.selectorUsedForRedirect, "Proxy selector wasn't used for redirect"); + // make sure the IOException was indeed a redirect + assertTrue(handler.redirectSent, "Server was expected to send a redirect, but didn't"); + assertTrue(ourProxySelector.selectorUsedForRedirect, "Proxy selector wasn't used for redirect"); - // make sure the IOException was caused by an IllegalArgumentException - Assert.assertTrue(ioe.getCause() instanceof IllegalArgumentException, "Unexpected cause in the IOException"); - } + // make sure the IOException was caused by an IllegalArgumentException + assertInstanceOf(IllegalArgumentException.class, ioe.getCause(), "Unexpected cause in the IOException"); } diff --git a/test/jdk/java/net/HttpURLConnection/Response1xxTest.java b/test/jdk/java/net/HttpURLConnection/Response1xxTest.java index 2be9772ed29..bdda7821bf8 100644 --- a/test/jdk/java/net/HttpURLConnection/Response1xxTest.java +++ b/test/jdk/java/net/HttpURLConnection/Response1xxTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 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 @@ -33,28 +33,31 @@ import java.net.URI; import java.nio.charset.StandardCharsets; import jdk.test.lib.net.URIBuilder; -import org.testng.Assert; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; -/** +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/* * @test * @bug 8170305 * @summary Tests behaviour of HttpURLConnection when server responds with 1xx interim response status codes * @library /test/lib - * @run testng Response1xxTest + * @run junit ${test.main.class} */ public class Response1xxTest { private static final String EXPECTED_RSP_BODY = "Hello World"; - private ServerSocket serverSocket; - private Http11Server server; - private String requestURIBase; + private static ServerSocket serverSocket; + private static Http11Server server; + private static String requestURIBase; - @BeforeClass - public void setup() throws Exception { + @BeforeAll + public static void setup() throws Exception { serverSocket = new ServerSocket(0, 0, InetAddress.getLoopbackAddress()); server = new Http11Server(serverSocket); new Thread(server).start(); @@ -62,8 +65,8 @@ public class Response1xxTest { .port(serverSocket.getLocalPort()).build().toString(); } - @AfterClass - public void teardown() throws Exception { + @AfterAll + public static void teardown() throws Exception { if (server != null) { server.stop = true; System.out.println("(HTTP 1.1) Server stop requested"); @@ -166,7 +169,7 @@ public class Response1xxTest { static String readRequestLine(final Socket sock) throws IOException { final InputStream is = sock.getInputStream(); - final StringBuilder sb = new StringBuilder(""); + final StringBuilder sb = new StringBuilder(); byte[] buf = new byte[1024]; while (!sb.toString().endsWith("\r\n\r\n")) { final int numRead = is.read(buf); @@ -203,13 +206,13 @@ public class Response1xxTest { System.out.println("Issuing request to " + requestURI); final HttpURLConnection urlConnection = (HttpURLConnection) requestURI.toURL().openConnection(); final int responseCode = urlConnection.getResponseCode(); - Assert.assertEquals(responseCode, 200, "Unexpected response code"); + assertEquals(200, responseCode, "Unexpected response code"); final String body; try (final InputStream is = urlConnection.getInputStream()) { final byte[] bytes = is.readAllBytes(); body = new String(bytes, StandardCharsets.UTF_8); } - Assert.assertEquals(body, EXPECTED_RSP_BODY, "Unexpected response body"); + assertEquals(EXPECTED_RSP_BODY, body, "Unexpected response body"); } } @@ -223,6 +226,6 @@ public class Response1xxTest { System.out.println("Issuing request to " + requestURI); final HttpURLConnection urlConnection = (HttpURLConnection) requestURI.toURL().openConnection(); // we expect the request to fail because the server unexpectedly sends a 101 response - Assert.assertThrows(ProtocolException.class, () -> urlConnection.getResponseCode()); + assertThrows(ProtocolException.class, urlConnection::getResponseCode); } } diff --git a/test/jdk/java/net/JarURLConnection/TestDefaultBehavior.java b/test/jdk/java/net/JarURLConnection/TestDefaultBehavior.java index 69b6c588af8..6a0173ac692 100644 --- a/test/jdk/java/net/JarURLConnection/TestDefaultBehavior.java +++ b/test/jdk/java/net/JarURLConnection/TestDefaultBehavior.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 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,7 +26,7 @@ * @bug 8225037 * @library /test/lib * @summary Basic test for java.net.JarURLConnection default behavior - * @run testng/othervm TestDefaultBehavior + * @run junit/othervm ${test.main.class} */ import java.io.IOException; @@ -39,10 +39,13 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.jar.JarFile; import jdk.test.lib.util.JarUtils; -import org.testng.annotations.BeforeTest; -import org.testng.annotations.Test; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; public class TestDefaultBehavior { @@ -50,8 +53,8 @@ public class TestDefaultBehavior { // 1. jar without a manifest // 2. jar with a manifest // 3. jar with manifest that includes an entry attribute - @BeforeTest - public void setup() throws Exception { + @BeforeAll + public static void setup() throws Exception { URLConnection.setDefaultUseCaches("jar", false); URLConnection.setDefaultUseCaches("file", false); @@ -76,14 +79,14 @@ public class TestDefaultBehavior { URL jarFileURL = URI.create("jar:" + fileURI + "!/").toURL(); JarURLConnection jarURLConnection = new CustomJarURLConnection(jarFileURL); - assertEquals(jarURLConnection.getAttributes(), null); - assertEquals(jarURLConnection.getCertificates(), null); - assertEquals(jarURLConnection.getEntryName(), null); - assertEquals(jarURLConnection.getJarEntry(), null); + assertNull(jarURLConnection.getAttributes()); + assertNull(jarURLConnection.getCertificates()); + assertNull(jarURLConnection.getEntryName()); + assertNull(jarURLConnection.getJarEntry()); assertNotNull(jarURLConnection.getJarFile()); - assertEquals(jarURLConnection.getJarFileURL(), fileURI.toURL()); - assertEquals(jarURLConnection.getMainAttributes(), null); - assertEquals(jarURLConnection.getManifest(), null); + assertEquals(fileURI.toURL(), jarURLConnection.getJarFileURL()); + assertNull(jarURLConnection.getMainAttributes()); + assertNull(jarURLConnection.getManifest()); } @Test @@ -92,14 +95,14 @@ public class TestDefaultBehavior { URL jarFileURL = URI.create("jar:" + fileURI + "!/foo.txt").toURL(); JarURLConnection jarURLConnection = new CustomJarURLConnection(jarFileURL); - assertEquals(jarURLConnection.getAttributes(), null); - assertEquals(jarURLConnection.getCertificates(), null); - assertEquals(jarURLConnection.getEntryName(), "foo.txt"); - assertEquals(jarURLConnection.getJarEntry().getName(), "foo.txt"); + assertNull(jarURLConnection.getAttributes()); + assertNull(jarURLConnection.getCertificates()); + assertEquals("foo.txt", jarURLConnection.getEntryName()); + assertEquals("foo.txt", jarURLConnection.getJarEntry().getName()); assertNotNull(jarURLConnection.getJarFile()); - assertEquals(jarURLConnection.getJarFileURL(), fileURI.toURL()); - assertEquals(jarURLConnection.getMainAttributes(), null); - assertEquals(jarURLConnection.getManifest(), null); + assertEquals(fileURI.toURL(), jarURLConnection.getJarFileURL()); + assertNull(jarURLConnection.getMainAttributes()); + assertNull(jarURLConnection.getManifest()); } @Test @@ -108,13 +111,13 @@ public class TestDefaultBehavior { URL jarFileURL = URI.create("jar:" + fileURI + "!/").toURL(); JarURLConnection jarURLConnection = new CustomJarURLConnection(jarFileURL); - assertEquals(jarURLConnection.getAttributes(), null); - assertEquals(jarURLConnection.getCertificates(), null); - assertEquals(jarURLConnection.getEntryName(), null); - assertEquals(jarURLConnection.getJarEntry(), null); + assertNull(jarURLConnection.getAttributes()); + assertNull(jarURLConnection.getCertificates()); + assertNull(jarURLConnection.getEntryName()); + assertNull(jarURLConnection.getJarEntry()); assertNotNull(jarURLConnection.getJarFile()); - assertEquals(jarURLConnection.getJarFileURL(), fileURI.toURL()); - assertEquals(jarURLConnection.getMainAttributes().getValue("Manifest-Version"), "5.5"); + assertEquals(fileURI.toURL(), jarURLConnection.getJarFileURL()); + assertEquals("5.5", jarURLConnection.getMainAttributes().getValue("Manifest-Version")); assertNotNull(jarURLConnection.getManifest()); } @@ -124,13 +127,13 @@ public class TestDefaultBehavior { URL jarFileURL = URI.create("jar:" + fileURI + "!/foo.txt").toURL(); JarURLConnection jarURLConnection = new CustomJarURLConnection(jarFileURL); - assertEquals(jarURLConnection.getAttributes(), null); - assertEquals(jarURLConnection.getCertificates(), null); - assertEquals(jarURLConnection.getEntryName(), "foo.txt"); - assertEquals(jarURLConnection.getJarEntry().getName(), "foo.txt"); + assertNull(jarURLConnection.getAttributes()); + assertNull(jarURLConnection.getCertificates()); + assertEquals("foo.txt", jarURLConnection.getEntryName()); + assertEquals("foo.txt", jarURLConnection.getJarEntry().getName()); assertNotNull(jarURLConnection.getJarFile()); - assertEquals(jarURLConnection.getJarFileURL(), fileURI.toURL()); - assertEquals(jarURLConnection.getMainAttributes().getValue("Manifest-Version"), "5.5"); + assertEquals(fileURI.toURL(), jarURLConnection.getJarFileURL()); + assertEquals("5.5", jarURLConnection.getMainAttributes().getValue("Manifest-Version")); assertNotNull(jarURLConnection.getManifest()); } @@ -140,13 +143,13 @@ public class TestDefaultBehavior { URL jarFileURL = URI.create("jar:" + fileURI + "!/").toURL(); JarURLConnection jarURLConnection = new CustomJarURLConnection(jarFileURL); - assertEquals(jarURLConnection.getAttributes(), null); - assertEquals(jarURLConnection.getCertificates(), null); - assertEquals(jarURLConnection.getEntryName(), null); - assertEquals(jarURLConnection.getJarEntry(), null); + assertNull(jarURLConnection.getAttributes()); + assertNull(jarURLConnection.getCertificates()); + assertNull(jarURLConnection.getEntryName()); + assertNull(jarURLConnection.getJarEntry()); assertNotNull(jarURLConnection.getJarFile()); - assertEquals(jarURLConnection.getJarFileURL(), fileURI.toURL()); - assertEquals(jarURLConnection.getMainAttributes().getValue("Manifest-Version"), "7.7"); + assertEquals(fileURI.toURL(), jarURLConnection.getJarFileURL()); + assertEquals("7.7", jarURLConnection.getMainAttributes().getValue("Manifest-Version")); assertNotNull(jarURLConnection.getManifest()); } @@ -156,13 +159,13 @@ public class TestDefaultBehavior { URL jarFileURL = URI.create("jar:" + fileURI + "!/foo.txt").toURL(); JarURLConnection jarURLConnection = new CustomJarURLConnection(jarFileURL); - assertEquals(jarURLConnection.getAttributes().getValue("Greeting"), "true"); - assertEquals(jarURLConnection.getCertificates(), null); - assertEquals(jarURLConnection.getEntryName(), "foo.txt"); - assertEquals(jarURLConnection.getJarEntry().getName(), "foo.txt"); + assertEquals("true", jarURLConnection.getAttributes().getValue("Greeting")); + assertNull(jarURLConnection.getCertificates()); + assertEquals("foo.txt", jarURLConnection.getEntryName()); + assertEquals("foo.txt", jarURLConnection.getJarEntry().getName()); assertNotNull(jarURLConnection.getJarFile()); - assertEquals(jarURLConnection.getJarFileURL(), fileURI.toURL()); - assertEquals(jarURLConnection.getMainAttributes().getValue("Manifest-Version"), "7.7"); + assertEquals(fileURI.toURL(), jarURLConnection.getJarFileURL()); + assertEquals("7.7", jarURLConnection.getMainAttributes().getValue("Manifest-Version")); assertNotNull(jarURLConnection.getManifest()); } diff --git a/test/jdk/java/net/URLClassLoader/definePackage/SplitPackage.java b/test/jdk/java/net/URLClassLoader/definePackage/SplitPackage.java index e8698dddf6b..47e32b39a4b 100644 --- a/test/jdk/java/net/URLClassLoader/definePackage/SplitPackage.java +++ b/test/jdk/java/net/URLClassLoader/definePackage/SplitPackage.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2017, 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 @@ -28,7 +28,7 @@ * @library /test/lib * @build jdk.test.lib.compiler.CompilerUtils * @modules jdk.compiler - * @run testng SplitPackage + * @run junit ${test.main.class} */ import java.net.URL; @@ -40,17 +40,21 @@ import java.nio.file.StandardCopyOption; import java.util.jar.Manifest; import jdk.test.lib.compiler.CompilerUtils; -import org.testng.annotations.BeforeTest; -import org.testng.annotations.Test; -import static org.testng.Assert.*; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; public class SplitPackage { private static final Path SRC_DIR = Paths.get(System.getProperty("test.src", ".")); private static final Path FOO_DIR = Paths.get("foo"); private static final Path BAR_DIR = Paths.get("bar"); - @BeforeTest - private void setup() throws Exception { + @BeforeAll + public static void setup() throws Exception { Files.createDirectory(BAR_DIR); Path pkgDir = Paths.get("p"); @@ -80,14 +84,11 @@ public class SplitPackage { Package pForFoo = loader1.getDefinedPackage("p"); Package pForBar = loader2.getDefinedPackage("p"); - assertEquals(pForFoo.getName(), pForBar.getName()); - assertTrue(pForFoo != pForBar); + assertEquals(pForBar.getName(), pForFoo.getName()); + assertNotSame(pForBar, pForFoo); - try { - loader2.defineSplitPackage("p"); - } catch (IllegalArgumentException e) { - - } + assertThrows(IllegalArgumentException.class, + () -> loader2.defineSplitPackage("p")); } static class Loader extends URLClassLoader { diff --git a/test/jdk/java/net/URLConnection/RequestProperties.java b/test/jdk/java/net/URLConnection/RequestProperties.java index d052f1aeaf2..84206d89868 100644 --- a/test/jdk/java/net/URLConnection/RequestProperties.java +++ b/test/jdk/java/net/URLConnection/RequestProperties.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2020, 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 @@ -21,17 +21,14 @@ * questions. */ -/** +/* * @test * @bug 4485208 8252767 * @summary Validate various request property methods on java.net.URLConnection * throw NullPointerException and IllegalStateException when expected - * @run testng RequestProperties + * @run junit ${test.main.class} */ -import org.testng.Assert; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; import java.io.IOException; import java.net.URL; @@ -40,13 +37,19 @@ import java.nio.file.Path; import java.util.ArrayList; import java.util.List; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + public class RequestProperties { private static final Class NPE = NullPointerException.class; private static final Class ISE = IllegalStateException.class; - @DataProvider(name = "urls") - private Object[][] urls() { + private static List urls() { final List urls = new ArrayList<>(); urls.add("http://foo.com/bar/"); urls.add("jar:http://foo.com/bar.html!/foo/bar"); @@ -54,11 +57,7 @@ public class RequestProperties { if (hasFtp()) { urls.add("ftp://foo:bar@foobar.com/etc/passwd"); } - final Object[][] data = new Object[urls.size()][1]; - for (int i = 0; i < urls.size(); i++) { - data[i][0] = urls.get(i); - } - return data; + return List.copyOf(urls); } @@ -66,10 +65,11 @@ public class RequestProperties { * Test that {@link java.net.URLConnection#setRequestProperty(String, String)} throws * a {@link NullPointerException} when passed null key */ - @Test(dataProvider = "urls") + @ParameterizedTest + @MethodSource("urls") public void testSetRequestPropertyNullPointerException(final String url) throws Exception { final URLConnection conn = new URL(url).openConnection(); - Assert.assertThrows(NPE, () -> conn.setRequestProperty(null, "bar")); + assertThrows(NPE, () -> conn.setRequestProperty(null, "bar")); // expected to pass conn.setRequestProperty("key", null); } @@ -78,10 +78,11 @@ public class RequestProperties { * Test that {@link java.net.URLConnection#addRequestProperty(String, String)} throws * a {@link NullPointerException} when passed null key */ - @Test(dataProvider = "urls") + @ParameterizedTest + @MethodSource("urls") public void testAddRequestPropertyNullPointerException(final String url) throws Exception { final URLConnection conn = new URL(url).openConnection(); - Assert.assertThrows(NPE, () -> conn.addRequestProperty(null, "hello")); + assertThrows(NPE, () -> conn.addRequestProperty(null, "hello")); // expected to pass conn.addRequestProperty("key", null); } @@ -90,10 +91,11 @@ public class RequestProperties { * Test that {@link java.net.URLConnection#getRequestProperty(String)} returns * null when the passed key is null */ - @Test(dataProvider = "urls") + @ParameterizedTest + @MethodSource("urls") public void testGetRequestPropertyReturnsNull(final String url) throws Exception { final URLConnection conn = new URL(url).openConnection(); - Assert.assertNull(conn.getRequestProperty(null), + assertNull(conn.getRequestProperty(null), "getRequestProperty was expected to return null for null key"); } @@ -105,7 +107,7 @@ public class RequestProperties { public void testSetRequestPropertyIllegalStateException() throws Exception { final URLConnection conn = createAndConnectURLConnection(); try { - Assert.assertThrows(ISE, () -> conn.setRequestProperty("foo", "bar")); + assertThrows(ISE, () -> conn.setRequestProperty("foo", "bar")); } finally { safeClose(conn); } @@ -119,7 +121,7 @@ public class RequestProperties { public void testAddRequestPropertyIllegalStateException() throws Exception { final URLConnection conn = createAndConnectURLConnection(); try { - Assert.assertThrows(ISE, () -> conn.addRequestProperty("foo", "bar")); + assertThrows(ISE, () -> conn.addRequestProperty("foo", "bar")); } finally { safeClose(conn); } @@ -133,7 +135,7 @@ public class RequestProperties { public void testGetRequestPropertyIllegalStateException() throws Exception { final URLConnection conn = createAndConnectURLConnection(); try { - Assert.assertThrows(ISE, () -> conn.getRequestProperty("hello")); + assertThrows(ISE, () -> conn.getRequestProperty("hello")); } finally { safeClose(conn); } @@ -147,7 +149,7 @@ public class RequestProperties { public void testGetRequestPropertiesIllegalStateException() throws Exception { final URLConnection conn = createAndConnectURLConnection(); try { - Assert.assertThrows(ISE, () -> conn.getRequestProperties()); + assertThrows(ISE, () -> conn.getRequestProperties()); } finally { safeClose(conn); } diff --git a/test/jdk/java/net/URLConnection/SetDefaultUseCaches.java b/test/jdk/java/net/URLConnection/SetDefaultUseCaches.java index 73c822ac2c9..224e5217d33 100644 --- a/test/jdk/java/net/URLConnection/SetDefaultUseCaches.java +++ b/test/jdk/java/net/URLConnection/SetDefaultUseCaches.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 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 @@ -24,15 +24,18 @@ /* @test * @bug 8163449 8175261 * @summary Allow per protocol setting for URLConnection defaultUseCaches - * @run testng/othervm SetDefaultUseCaches + * @run junit/othervm ${test.main.class} */ import java.io.IOException; import java.io.UncheckedIOException; import java.net.URL; import java.net.URLConnection; -import org.testng.annotations.Test; -import static org.testng.Assert.*; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; public class SetDefaultUseCaches { @@ -85,28 +88,28 @@ public class SetDefaultUseCaches { void checkJAR(boolean defaultValue) throws IOException { URLConnection.setDefaultUseCaches("JAR", defaultValue); - assertEquals(URLConnection.getDefaultUseCaches("JAr"), defaultValue); + assertEquals(defaultValue, URLConnection.getDefaultUseCaches("JAr")); URLConnection jarFileURLConn = jarFileURL.openConnection(); URLConnection jarHttpURLConn = jarHttpURL.openConnection(); - assertEquals(jarFileURLConn.getUseCaches(), defaultValue); - assertEquals(jarHttpURLConn.getUseCaches(), defaultValue); + assertEquals(defaultValue, jarFileURLConn.getUseCaches()); + assertEquals(defaultValue, jarHttpURLConn.getUseCaches()); jarFileURLConn.setUseCaches(!defaultValue); jarHttpURLConn.setUseCaches(!defaultValue); - assertEquals(jarFileURLConn.getUseCaches(), !defaultValue); - assertEquals(jarHttpURLConn.getUseCaches(), !defaultValue); + assertEquals(!defaultValue, jarFileURLConn.getUseCaches()); + assertEquals(!defaultValue, jarHttpURLConn.getUseCaches()); URLConnection.setDefaultUseCaches("JaR", !defaultValue); // case-insensitive - assertEquals(URLConnection.getDefaultUseCaches("jAR"), !defaultValue); + assertEquals(!defaultValue, URLConnection.getDefaultUseCaches("jAR")); jarFileURLConn = jarFileURL.openConnection(); jarHttpURLConn = jarHttpURL.openConnection(); - assertEquals(jarFileURLConn.getUseCaches(), !defaultValue); - assertEquals(jarHttpURLConn.getUseCaches(), !defaultValue); + assertEquals(!defaultValue, jarFileURLConn.getUseCaches()); + assertEquals(!defaultValue, jarHttpURLConn.getUseCaches()); jarFileURLConn.setUseCaches(defaultValue); jarHttpURLConn.setUseCaches(defaultValue); - assertEquals(jarFileURLConn.getUseCaches(), defaultValue); - assertEquals(jarHttpURLConn.getUseCaches(), defaultValue); + assertEquals(defaultValue, jarFileURLConn.getUseCaches()); + assertEquals(defaultValue, jarHttpURLConn.getUseCaches()); } static URL uncheckURL(String url) { diff --git a/test/jdk/java/net/URLConnection/URLConnectionHeadersOrder.java b/test/jdk/java/net/URLConnection/URLConnectionHeadersOrder.java index 6f16c79f520..5f680a04283 100644 --- a/test/jdk/java/net/URLConnection/URLConnectionHeadersOrder.java +++ b/test/jdk/java/net/URLConnection/URLConnectionHeadersOrder.java @@ -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 @@ -21,19 +21,17 @@ * questions. */ -/** +/* * @test * @bug 8133686 * @summary Ensuring that multiple header values for a given field-name are returned in * the order they were added for HttpURLConnection.getRequestProperties * and HttpURLConnection.getHeaderFields * @library /test/lib - * @run testng URLConnectionHeadersOrder + * @run junit ${test.main.class} */ import jdk.test.lib.net.URIBuilder; -import org.testng.Assert; -import org.testng.annotations.Test; import java.io.IOException; import java.net.InetAddress; @@ -41,6 +39,11 @@ import java.net.URL; import java.net.URLConnection; import java.util.Arrays; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; + public class URLConnectionHeadersOrder { @Test public void testRequestPropertiesOrder() throws Exception { @@ -58,10 +61,10 @@ public class URLConnectionHeadersOrder { var expectedRequestProps = Arrays.asList("a", "b", "c"); var actualRequestProps = conn.getRequestProperties().get("test"); - Assert.assertNotNull(actualRequestProps); + assertNotNull(actualRequestProps); - String errorMessageTemplate = "Expected Request Properties = %s, Actual Request Properties = %s"; - Assert.assertEquals(actualRequestProps, expectedRequestProps, String.format(errorMessageTemplate, expectedRequestProps.toString(), actualRequestProps.toString())); + assertEquals(expectedRequestProps, actualRequestProps, + "Unexpected value for request header \"test\""); } } diff --git a/test/jdk/java/net/URLDecoder/EncodingTest.java b/test/jdk/java/net/URLDecoder/EncodingTest.java index b01be15a446..c49720545ed 100644 --- a/test/jdk/java/net/URLDecoder/EncodingTest.java +++ b/test/jdk/java/net/URLDecoder/EncodingTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 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,16 +24,20 @@ import java.net.URLDecoder; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; -import org.testng.Assert; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; -/** +import org.junit.jupiter.api.function.Executable; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/* * @test * @bug 8183743 * @summary Test to verify the new overload method with Charset functions the * same as the existing method that takes a charset name. - * @run testng EncodingTest + * @run junit ${test.main.class} */ public class EncodingTest { public static enum ParameterType { @@ -41,16 +45,14 @@ public class EncodingTest { CHARSET } - @DataProvider(name = "illegalArgument") - public Object[][] getParameters() { + public static Object[][] getParameters() { return new Object[][]{ {ParameterType.STRING}, {ParameterType.CHARSET} }; } - @DataProvider(name = "decode") - public Object[][] getDecodeParameters() { + public static Object[][] getDecodeParameters() { return new Object[][]{ {"The string \u00FC@foo-bar"}, // the string from javadoc example @@ -82,17 +84,16 @@ public class EncodingTest { * @param type the type of the argument, e.g a String charset name or * charset */ - @Test(dataProvider = "illegalArgument", expectedExceptions = IllegalArgumentException.class) + @ParameterizedTest + @MethodSource("getParameters") public void testIllegalArgument(ParameterType type) throws Exception { String encoded = URLEncoder.encode("http://www.xyz.com/find?key=\u0100\u0101", StandardCharsets.UTF_8.name()); String illegal = "%" + encoded; - String returned; - if (type == ParameterType.STRING) { - returned = URLDecoder.decode(illegal, StandardCharsets.UTF_8.name()); - } else { - returned = URLDecoder.decode(illegal, StandardCharsets.UTF_8); - } + Executable decoded = type == ParameterType.STRING + ? () -> URLDecoder.decode(illegal, StandardCharsets.UTF_8.name()) + : () -> URLDecoder.decode(illegal, StandardCharsets.UTF_8); + assertThrows(IllegalArgumentException.class, decoded); } /** @@ -101,17 +102,18 @@ public class EncodingTest { * * @param s the string to be encoded and then decoded with both existing * and the overload methods. - * @throws Exception + * @throws Exception if failed */ - @Test(dataProvider = "decode") + @ParameterizedTest + @MethodSource("getDecodeParameters") public void decode(String s) throws Exception { String encoded = URLEncoder.encode(s, StandardCharsets.UTF_8.name()); String returned1 = URLDecoder.decode(encoded, StandardCharsets.UTF_8.name()); String returned2 = URLDecoder.decode(encoded, StandardCharsets.UTF_8); - Assert.assertEquals(returned1, returned2); + assertEquals(returned2, returned1); } - String charactersRange(char c1, char c2) { + private static String charactersRange(char c1, char c2) { StringBuilder sb = new StringBuilder(c2 - c1); for (char c = c1; c < c2; c++) { sb.append(c); diff --git a/test/jdk/java/net/URLEncoder/EncodingTest.java b/test/jdk/java/net/URLEncoder/EncodingTest.java index f1b4f3ce3e9..f992fb4e8ee 100644 --- a/test/jdk/java/net/URLEncoder/EncodingTest.java +++ b/test/jdk/java/net/URLEncoder/EncodingTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 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,16 +24,17 @@ import java.net.URLDecoder; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; -import org.testng.Assert; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; -/** +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import static org.junit.jupiter.api.Assertions.assertEquals; + +/* * @test * @bug 8183743 * @summary Test to verify the new overload method with Charset functions the same * as the existing method that takes a charset name. - * @run testng EncodingTest + * @run junit ${test.main.class} */ public class EncodingTest { public static enum ParameterType { @@ -41,8 +42,7 @@ public class EncodingTest { CHARSET } - @DataProvider(name = "encode") - public Object[][] getDecodeParameters() { + public static Object[][] getDecodeParameters() { return new Object[][]{ {"The string \u00FC@foo-bar"}, // the string from javadoc example @@ -74,19 +74,20 @@ public class EncodingTest { * @param s the string to be encoded * @throws Exception if the test fails */ - @Test(dataProvider = "encode") + @ParameterizedTest + @MethodSource("getDecodeParameters") public void encode(String s) throws Exception { String encoded1 = URLEncoder.encode(s, StandardCharsets.UTF_8.name()); String encoded2 = URLEncoder.encode(s, StandardCharsets.UTF_8); - Assert.assertEquals(encoded1, encoded2); + assertEquals(encoded2, encoded1); // cross check String returned1 = URLDecoder.decode(encoded1, StandardCharsets.UTF_8.name()); String returned2 = URLDecoder.decode(encoded2, StandardCharsets.UTF_8); - Assert.assertEquals(returned1, returned2); + assertEquals(returned2, returned1); } - String charactersRange(char c1, char c2) { + private static String charactersRange(char c1, char c2) { StringBuilder sb = new StringBuilder(c2 - c1); for (char c = c1; c < c2; c++) { sb.append(c); diff --git a/test/jdk/java/net/URLPermission/EmptyAuthorityTest.java b/test/jdk/java/net/URLPermission/EmptyAuthorityTest.java index 06dd21b71ea..9dce36dbb27 100644 --- a/test/jdk/java/net/URLPermission/EmptyAuthorityTest.java +++ b/test/jdk/java/net/URLPermission/EmptyAuthorityTest.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 @@ -25,18 +25,19 @@ * @test * @bug 8367049 * @summary URLPermission must reject empty/missing host authority with IAE (no SIOOBE) -* @run testng EmptyAuthorityTest +* @run junit ${test.main.class} */ import java.net.URLPermission; -import org.testng.Assert; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.junit.jupiter.api.Assertions.assertThrows; public class EmptyAuthorityTest { - @DataProvider(name = "badUrls") - public Object[][] badUrls() { + public static Object[][] badUrls() { return new Object[][]{ { "http:///path" }, // empty authority { "https:///x" }, // empty authority @@ -46,8 +47,7 @@ public class EmptyAuthorityTest { }; } - @DataProvider(name = "goodUrls") - public Object[][] goodUrls() { + public static Object[][] goodUrls() { return new Object[][]{ { "http://example.com/x" }, { "http://example.com:80/x" }, @@ -56,12 +56,14 @@ public class EmptyAuthorityTest { }; } - @Test(dataProvider = "badUrls") + @ParameterizedTest + @MethodSource("badUrls") public void rejectsEmptyOrMalformedAuthority(String url) { - Assert.expectThrows(IllegalArgumentException.class, () -> new URLPermission(url)); + assertThrows(IllegalArgumentException.class, () -> new URLPermission(url)); } - @Test(dataProvider = "goodUrls") + @ParameterizedTest + @MethodSource("goodUrls") public void acceptsValidAuthorities(String url) { new URLPermission(url); // should not throw } diff --git a/test/jdk/java/net/URLPermission/InvalidCharacterTest.java b/test/jdk/java/net/URLPermission/InvalidCharacterTest.java index 37179f5dfe2..ae49353df0f 100644 --- a/test/jdk/java/net/URLPermission/InvalidCharacterTest.java +++ b/test/jdk/java/net/URLPermission/InvalidCharacterTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 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 @@ -23,15 +23,18 @@ import java.net.URLPermission; -import org.testng.Assert; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; -/** +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/* * @test * @bug 8297311 * @summary Verify that the exception thrown by URLPermission class, for invalid host name, * contains expected exception message - * @run testng InvalidCharacterTest + * @run junit InvalidCharacterTest */ public class InvalidCharacterTest { @@ -45,13 +48,13 @@ public class InvalidCharacterTest { // we expect this string in the exception message final String expectedStringInMessage = String.format("\\u%04x", (int) invalidChar); final String url = "http://foo" + invalidChar + "bar.com:12345"; - final IllegalArgumentException iae = Assert.expectThrows(IllegalArgumentException.class, + final IllegalArgumentException iae = assertThrows(IllegalArgumentException.class, () -> new URLPermission(url)); // additionally check the error message contains the invalid char final String exMessage = iae.getMessage(); System.out.println("Got exception message: " + exMessage); - Assert.assertNotNull(exMessage, "Exception message is null"); - Assert.assertTrue(exMessage.contains(expectedStringInMessage), + assertNotNull(exMessage, "Exception message is null"); + assertTrue(exMessage.contains(expectedStringInMessage), expectedStringInMessage + " missing from exception message: " + exMessage); } } diff --git a/test/jdk/java/net/URLStreamHandler/TestDefaultBehavior.java b/test/jdk/java/net/URLStreamHandler/TestDefaultBehavior.java index 9fe4eff4150..ed01791acdd 100644 --- a/test/jdk/java/net/URLStreamHandler/TestDefaultBehavior.java +++ b/test/jdk/java/net/URLStreamHandler/TestDefaultBehavior.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 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,7 +25,7 @@ * @test * @bug 8224973 * @summary Basic test for the default behavior of openConnection(URL,Proxy) - * @run testng TestDefaultBehavior + * @run junit ${test.main.class} */ import java.io.IOException; @@ -35,9 +35,10 @@ import java.net.URI; import java.net.URL; import java.net.URLConnection; import java.net.URLStreamHandler; -import org.testng.annotations.Test; import static java.net.Proxy.*; -import static org.testng.Assert.expectThrows; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertThrows; public class TestDefaultBehavior { @@ -51,15 +52,15 @@ public class TestDefaultBehavior { public void testDefaultBehavior() { CustomURLStreamHandler handler = new CustomURLStreamHandler(); - expectThrows(IAE, () -> handler.openConnection(null, null)); - expectThrows(IAE, () -> handler.openConnection(null, NO_PROXY)); - expectThrows(IAE, () -> handler.openConnection(null, new Proxy(Type.SOCKS, ADDR))); - expectThrows(IAE, () -> handler.openConnection(null, new Proxy(Type.HTTP, ADDR))); - expectThrows(IAE, () -> handler.openConnection(uri.toURL(), null)); + assertThrows(IAE, () -> handler.openConnection(null, null)); + assertThrows(IAE, () -> handler.openConnection(null, NO_PROXY)); + assertThrows(IAE, () -> handler.openConnection(null, new Proxy(Type.SOCKS, ADDR))); + assertThrows(IAE, () -> handler.openConnection(null, new Proxy(Type.HTTP, ADDR))); + assertThrows(IAE, () -> handler.openConnection(uri.toURL(), null)); - expectThrows(UOE, () -> handler.openConnection(uri.toURL(), NO_PROXY)); - expectThrows(UOE, () -> handler.openConnection(uri.toURL(), new Proxy(Type.SOCKS, ADDR))); - expectThrows(UOE, () -> handler.openConnection(uri.toURL(), new Proxy(Type.HTTP, ADDR))); + assertThrows(UOE, () -> handler.openConnection(uri.toURL(), NO_PROXY)); + assertThrows(UOE, () -> handler.openConnection(uri.toURL(), new Proxy(Type.SOCKS, ADDR))); + assertThrows(UOE, () -> handler.openConnection(uri.toURL(), new Proxy(Type.HTTP, ADDR))); } // A URLStreamHandler that delegates the overloaded openConnection that From dd76306903a869752d955a45f0b5a7ea7e680c68 Mon Sep 17 00:00:00 2001 From: Erik Gahlin Date: Mon, 13 Apr 2026 13:55:27 +0000 Subject: [PATCH 029/108] 8382048: JFR: RecordingFile::write doesn't truncate when overwriting Reviewed-by: mgronlun --- .../jdk/jfr/internal/consumer/filter/RecordingOutput.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/filter/RecordingOutput.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/filter/RecordingOutput.java index 9aebb27238c..9c8187c7a46 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/filter/RecordingOutput.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/filter/RecordingOutput.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 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 @@ -26,6 +26,7 @@ package jdk.jfr.internal.consumer.filter; import java.io.Closeable; import java.io.File; +import java.io.FileOutputStream; import java.io.IOException; import java.io.RandomAccessFile; @@ -39,6 +40,9 @@ final class RecordingOutput implements Closeable { private long position; public RecordingOutput(File file) throws IOException { + // Truncates existing file if it exists + var fos = new FileOutputStream(file); + fos.close(); this.file = new RandomAccessFile(file, "rw"); } From 8882e7b64a6e47db906920d9509b24a67277a028 Mon Sep 17 00:00:00 2001 From: Erik Gahlin Date: Mon, 13 Apr 2026 16:42:22 +0000 Subject: [PATCH 030/108] 8382034: JFR: jdk-agents view is missing information Reviewed-by: mgronlun --- .../share/jfr/periodic/jfrPeriodic.cpp | 18 +++++++----- .../classes/jdk/jfr/internal/query/view.ini | 4 +-- .../jdk/jfr/event/runtime/TestAgentEvent.java | 29 ++++++++++++++++++- 3 files changed, 41 insertions(+), 10 deletions(-) diff --git a/src/hotspot/share/jfr/periodic/jfrPeriodic.cpp b/src/hotspot/share/jfr/periodic/jfrPeriodic.cpp index 426ba4e7650..969c9ca60c1 100644 --- a/src/hotspot/share/jfr/periodic/jfrPeriodic.cpp +++ b/src/hotspot/share/jfr/periodic/jfrPeriodic.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 @@ -281,7 +281,9 @@ TRACE_REQUEST_FUNC(SystemProcess) { #if INCLUDE_JVMTI template -static void send_agent_event(AgentEvent& event, const JvmtiAgent* agent) { +static void send_agent_event(AgentEvent& event, const JvmtiAgent* agent, Ticks& timestamp) { + event.set_starttime(timestamp); + event.set_endtime(timestamp); event.set_name(agent->name()); event.set_options(agent->options()); event.set_dynamic(agent->is_dynamic()); @@ -292,29 +294,31 @@ static void send_agent_event(AgentEvent& event, const JvmtiAgent* agent) { TRACE_REQUEST_FUNC(JavaAgent) { JvmtiAgentList::Iterator it = JvmtiAgentList::java_agents(); + Ticks ticks = timestamp(); while (it.has_next()) { const JvmtiAgent* agent = it.next(); assert(agent->is_jplis(), "invariant"); EventJavaAgent event; - send_agent_event(event, agent); + send_agent_event(event, agent, ticks); } } -static void send_native_agent_events(JvmtiAgentList::Iterator& it) { +static void send_native_agent_events(JvmtiAgentList::Iterator& it, Ticks& timestamp) { while (it.has_next()) { const JvmtiAgent* agent = it.next(); assert(!agent->is_jplis(), "invariant"); EventNativeAgent event; event.set_path(agent->os_lib_path()); - send_agent_event(event, agent); + send_agent_event(event, agent, timestamp); } } TRACE_REQUEST_FUNC(NativeAgent) { + Ticks ticks = timestamp(); JvmtiAgentList::Iterator native_agents_it = JvmtiAgentList::native_agents(); - send_native_agent_events(native_agents_it); + send_native_agent_events(native_agents_it, ticks); JvmtiAgentList::Iterator xrun_agents_it = JvmtiAgentList::xrun_agents(); - send_native_agent_events(xrun_agents_it); + send_native_agent_events(xrun_agents_it, ticks); } #else // INCLUDE_JVMTI TRACE_REQUEST_FUNC(JavaAgent) {} diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/query/view.ini b/src/jdk.jfr/share/classes/jdk/jfr/internal/query/view.ini index 5d72a9f85dc..d8b740e1a66 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/query/view.ini +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/query/view.ini @@ -433,8 +433,8 @@ form = "COLUMN 'Successful Samples', 'Failed Samples', 'Biased Samples', 'Total label = "JDK Agents" table = "COLUMN 'Time', 'Initialization', 'Name', 'Options' FORMAT none, none, truncate-beginning;cell-height:10, cell-height:10 - SELECT LAST(initializationTime) AS t, LAST(initializationDuration), LAST(name), LAST(JavaAgent.options) - FROM JavaAgent, NativeAgent + SELECT LAST_BATCH(initializationTime) AS t, LAST_BATCH(initializationDuration), LAST_BATCH(name), LAST_BATCH(options) + FROM JavaAgent, NativeAgent GROUP BY initializationTime ORDER BY t" [environment.jvm-flags] diff --git a/test/jdk/jdk/jfr/event/runtime/TestAgentEvent.java b/test/jdk/jdk/jfr/event/runtime/TestAgentEvent.java index ca7b6dfc0f1..6e168153947 100644 --- a/test/jdk/jdk/jfr/event/runtime/TestAgentEvent.java +++ b/test/jdk/jdk/jfr/event/runtime/TestAgentEvent.java @@ -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 @@ -68,6 +68,12 @@ import jdk.test.lib.jfr.TestClassLoader; * @run main/othervm -XX:+UnlockExperimentalVMOptions -XX:-UseFastUnorderedTimeStamps -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=0 * jdk.jfr.event.runtime.TestAgentEvent * testNativeStatic + * + * @run main/othervm -XX:+UnlockExperimentalVMOptions -XX:-UseFastUnorderedTimeStamps + * -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=0 + * -javaagent:JavaAgent.jar=foo=bar + * jdk.jfr.event.runtime.TestAgentEvent + * testMultipleAgents */ public final class TestAgentEvent { @StackTrace(false) @@ -179,4 +185,25 @@ public final class TestAgentEvent { Events.assertField(events.get(4), "options").equal("="); } } + + private static void testMultipleAgents() throws Exception { + try (Recording r = new Recording()) { + r.enable(EventNames.NativeAgent).with("period", "endChunk"); + r.enable(EventNames.JavaAgent).with("period", "endChunk"); + r.start(); + r.stop(); + List events = Events.fromRecording(r); + if (events.size() != 2) { + throw new Exception("Expected two agents"); + } + Instant timestamp = events.getFirst().getStartTime(); + for (RecordedEvent event : events) { + if (!event.getStartTime().equals(timestamp) || + !event.getEndTime().equals(timestamp)) { + System.out.println(events); + throw new Exception("Expected agent to have the same start and end time"); + } + } + } + } } From 5cbc5653f4a331a0c976c7db44c3160f7ee6e84e Mon Sep 17 00:00:00 2001 From: Andrew Dinn Date: Mon, 13 Apr 2026 16:45:08 +0000 Subject: [PATCH 031/108] 8382081: AOT tests fail in add_stub_entries without JVMCI Reviewed-by: chagedorn, mhaessig, kvn --- src/hotspot/share/code/codeBlob.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hotspot/share/code/codeBlob.hpp b/src/hotspot/share/code/codeBlob.hpp index 1c6904e7446..d372e72fc23 100644 --- a/src/hotspot/share/code/codeBlob.hpp +++ b/src/hotspot/share/code/codeBlob.hpp @@ -604,7 +604,7 @@ class DeoptimizationBlob: public SingletonBlob { ); public: - static const int ENTRY_COUNT = 4 JVMTI_ONLY(+ 2); + static const int ENTRY_COUNT = 4 JVMCI_ONLY(+ 2); // Creation static DeoptimizationBlob* create( CodeBuffer* cb, From 121165ec91134dbeab0a243246624e935a916955 Mon Sep 17 00:00:00 2001 From: Vladimir Ivanov Date: Mon, 13 Apr 2026 16:49:57 +0000 Subject: [PATCH 032/108] 8290892: C2: Intrinsify Reference.reachabilityFence Co-authored-by: Tobias Holenstein Co-authored-by: Vladimir Ivanov Reviewed-by: dlong, epeter --- src/hotspot/share/classfile/vmIntrinsics.hpp | 3 + src/hotspot/share/opto/block.cpp | 4 +- src/hotspot/share/opto/c2_globals.hpp | 11 + src/hotspot/share/opto/c2compiler.cpp | 1 + src/hotspot/share/opto/callGenerator.cpp | 14 + src/hotspot/share/opto/callnode.cpp | 57 +- src/hotspot/share/opto/callnode.hpp | 67 ++- src/hotspot/share/opto/classes.cpp | 1 + src/hotspot/share/opto/classes.hpp | 1 + src/hotspot/share/opto/compile.cpp | 57 +- src/hotspot/share/opto/compile.hpp | 20 +- src/hotspot/share/opto/escape.cpp | 29 +- src/hotspot/share/opto/gcm.cpp | 9 +- src/hotspot/share/opto/graphKit.cpp | 10 + src/hotspot/share/opto/graphKit.hpp | 1 + src/hotspot/share/opto/library_call.cpp | 9 + src/hotspot/share/opto/library_call.hpp | 1 + src/hotspot/share/opto/loopTransform.cpp | 69 ++- src/hotspot/share/opto/loopnode.cpp | 61 ++- src/hotspot/share/opto/loopnode.hpp | 27 + src/hotspot/share/opto/macro.cpp | 30 +- src/hotspot/share/opto/memnode.cpp | 2 +- src/hotspot/share/opto/node.cpp | 26 + src/hotspot/share/opto/node.hpp | 6 + src/hotspot/share/opto/parse.hpp | 1 + src/hotspot/share/opto/parse1.cpp | 54 ++ src/hotspot/share/opto/phase.cpp | 3 + src/hotspot/share/opto/phase.hpp | 3 + src/hotspot/share/opto/phasetype.hpp | 1 + src/hotspot/share/opto/reachability.cpp | 512 ++++++++++++++++++ src/hotspot/share/opto/reachability.hpp | 83 +++ .../classes/java/lang/ref/Reference.java | 9 +- .../c2/ReachabilityFenceFlagsTest.java | 52 ++ .../c2/ReachabilityFenceOnConstantTest.java | 117 ++++ .../compiler/c2/ReachabilityFenceTest.java | 353 ++++++++++++ .../c2/irTests/ReachabilityFenceTest.java | 125 +++++ .../lib/ir_framework/CompilePhase.java | 1 + .../compiler/lib/ir_framework/IRNode.java | 5 + 38 files changed, 1769 insertions(+), 66 deletions(-) create mode 100644 src/hotspot/share/opto/reachability.cpp create mode 100644 src/hotspot/share/opto/reachability.hpp create mode 100644 test/hotspot/jtreg/compiler/c2/ReachabilityFenceFlagsTest.java create mode 100644 test/hotspot/jtreg/compiler/c2/ReachabilityFenceOnConstantTest.java create mode 100644 test/hotspot/jtreg/compiler/c2/ReachabilityFenceTest.java create mode 100644 test/hotspot/jtreg/compiler/c2/irTests/ReachabilityFenceTest.java diff --git a/src/hotspot/share/classfile/vmIntrinsics.hpp b/src/hotspot/share/classfile/vmIntrinsics.hpp index e84acd62284..3f85fd16b61 100644 --- a/src/hotspot/share/classfile/vmIntrinsics.hpp +++ b/src/hotspot/share/classfile/vmIntrinsics.hpp @@ -469,6 +469,9 @@ class methodHandle; do_intrinsic(_Reference_clear0, java_lang_ref_Reference, clear0_name, void_method_signature, F_RN) \ do_intrinsic(_PhantomReference_clear0, java_lang_ref_PhantomReference, clear0_name, void_method_signature, F_RN) \ \ + do_intrinsic(_Reference_reachabilityFence, java_lang_ref_Reference, reachabilityFence_name, object_void_signature, F_S) \ + do_name(reachabilityFence_name, "reachabilityFence") \ + \ /* support for com.sun.crypto.provider.AES_Crypt and some of its callers */ \ do_class(com_sun_crypto_provider_aescrypt, "com/sun/crypto/provider/AES_Crypt") \ do_intrinsic(_aescrypt_encryptBlock, com_sun_crypto_provider_aescrypt, encryptBlock_name, byteArray_int_byteArray_int_signature, F_R) \ diff --git a/src/hotspot/share/opto/block.cpp b/src/hotspot/share/opto/block.cpp index 7d3d4ec16f4..a93e2e43a29 100644 --- a/src/hotspot/share/opto/block.cpp +++ b/src/hotspot/share/opto/block.cpp @@ -179,9 +179,11 @@ int Block::is_Empty() const { // Ideal nodes (except BoxLock) are allowable in empty blocks: skip them. Only // Mach and BoxLock nodes turn directly into code via emit(). + // Keep ReachabilityFence for diagnostic purposes. while ((end_idx > 0) && !get_node(end_idx)->is_Mach() && - !get_node(end_idx)->is_BoxLock()) { + !get_node(end_idx)->is_BoxLock() && + !get_node(end_idx)->is_ReachabilityFence()) { end_idx--; } diff --git a/src/hotspot/share/opto/c2_globals.hpp b/src/hotspot/share/opto/c2_globals.hpp index 6974d50741e..dacc8ce9c26 100644 --- a/src/hotspot/share/opto/c2_globals.hpp +++ b/src/hotspot/share/opto/c2_globals.hpp @@ -76,6 +76,17 @@ develop(bool, StressBailout, false, \ "Perform bailouts randomly at C2 failing() checks") \ \ + product(bool, OptimizeReachabilityFences, true, DIAGNOSTIC, \ + "Optimize reachability fences " \ + "(leave reachability fence nodes intact when turned off)") \ + \ + product(bool, PreserveReachabilityFencesOnConstants, false, DIAGNOSTIC, \ + "Keep reachability fences on compile-time constants") \ + \ + product(bool, StressReachabilityFences, false, DIAGNOSTIC, \ + "Aggressively insert reachability fences " \ + "for all oop method arguments") \ + \ develop(uint, StressBailoutMean, 100000, \ "The expected number of failing() checks made until " \ "a random bailout.") \ diff --git a/src/hotspot/share/opto/c2compiler.cpp b/src/hotspot/share/opto/c2compiler.cpp index ead1b78cdea..5d170f919c8 100644 --- a/src/hotspot/share/opto/c2compiler.cpp +++ b/src/hotspot/share/opto/c2compiler.cpp @@ -775,6 +775,7 @@ bool C2Compiler::is_intrinsic_supported(vmIntrinsics::ID id) { case vmIntrinsics::_longBitsToDouble: case vmIntrinsics::_Reference_get0: case vmIntrinsics::_Reference_refersTo0: + case vmIntrinsics::_Reference_reachabilityFence: case vmIntrinsics::_PhantomReference_refersTo0: case vmIntrinsics::_Reference_clear0: case vmIntrinsics::_PhantomReference_clear0: diff --git a/src/hotspot/share/opto/callGenerator.cpp b/src/hotspot/share/opto/callGenerator.cpp index 1465da02ac8..49897ca3c17 100644 --- a/src/hotspot/share/opto/callGenerator.cpp +++ b/src/hotspot/share/opto/callGenerator.cpp @@ -611,6 +611,20 @@ void CallGenerator::do_late_inline_helper() { } Compile* C = Compile::current(); + + uint endoff = call->jvms()->endoff(); + if (C->inlining_incrementally()) { + // No reachability edges should be present when incremental inlining takes place. + // Inlining logic doesn't expect any extra edges past debug info and fails with + // an assert in SafePointNode::grow_stack. + assert(endoff == call->req(), "reachability edges not supported"); + } else { + if (call->req() > endoff) { // reachability edges present + assert(OptimizeReachabilityFences, "required"); + return; // keep the original call node as the holder of reachability info + } + } + // Remove inlined methods from Compiler's lists. if (call->is_macro()) { C->remove_macro_node(call); diff --git a/src/hotspot/share/opto/callnode.cpp b/src/hotspot/share/opto/callnode.cpp index b2b01079379..eb4f506d14f 100644 --- a/src/hotspot/share/opto/callnode.cpp +++ b/src/hotspot/share/opto/callnode.cpp @@ -898,7 +898,7 @@ bool CallNode::may_modify(const TypeOopPtr* t_oop, PhaseValues* phase) const { } // Does this call have a direct reference to n other than debug information? -bool CallNode::has_non_debug_use(Node *n) { +bool CallNode::has_non_debug_use(const Node *n) { const TypeTuple * d = tf()->domain(); for (uint i = TypeFunc::Parms; i < d->cnt(); i++) { Node *arg = in(i); @@ -940,7 +940,7 @@ Node *CallNode::result_cast() { } -void CallNode::extract_projections(CallProjections* projs, bool separate_io_proj, bool do_asserts) const { +void CallNode::extract_projections(CallProjections* projs, bool separate_io_proj, bool do_asserts, bool allow_handlers) const { projs->fallthrough_proj = nullptr; projs->fallthrough_catchproj = nullptr; projs->fallthrough_ioproj = nullptr; @@ -961,14 +961,13 @@ void CallNode::extract_projections(CallProjections* projs, bool separate_io_proj projs->fallthrough_proj = pn; const Node* cn = pn->unique_ctrl_out_or_null(); if (cn != nullptr && cn->is_Catch()) { - ProjNode *cpn = nullptr; for (DUIterator_Fast kmax, k = cn->fast_outs(kmax); k < kmax; k++) { - cpn = cn->fast_out(k)->as_Proj(); - assert(cpn->is_CatchProj(), "must be a CatchProjNode"); - if (cpn->_con == CatchProjNode::fall_through_index) + CatchProjNode* cpn = cn->fast_out(k)->as_CatchProj(); + assert(allow_handlers || !cpn->is_handler_proj(), "not allowed"); + if (cpn->_con == CatchProjNode::fall_through_index) { + assert(cpn->handler_bci() == CatchProjNode::no_handler_bci, ""); projs->fallthrough_catchproj = cpn; - else { - assert(cpn->_con == CatchProjNode::catch_all_index, "must be correct index."); + } else if (!cpn->is_handler_proj()) { projs->catchall_catchproj = cpn; } } @@ -976,15 +975,20 @@ void CallNode::extract_projections(CallProjections* projs, bool separate_io_proj break; } case TypeFunc::I_O: - if (pn->_is_io_use) + if (pn->_is_io_use) { projs->catchall_ioproj = pn; - else + } else { projs->fallthrough_ioproj = pn; + } for (DUIterator j = pn->outs(); pn->has_out(j); j++) { Node* e = pn->out(j); - if (e->Opcode() == Op_CreateEx && e->in(0)->is_CatchProj() && e->outcnt() > 0) { - assert(projs->exobj == nullptr, "only one"); - projs->exobj = e; + if (e->Opcode() == Op_CreateEx && e->outcnt() > 0) { + CatchProjNode* ecpn = e->in(0)->isa_CatchProj(); + assert(allow_handlers || ecpn == nullptr || !ecpn->is_handler_proj(), "not allowed"); + if (ecpn != nullptr && ecpn->_con != CatchProjNode::fall_through_index && !ecpn->is_handler_proj()) { + assert(projs->exobj == nullptr, "only one"); + projs->exobj = e; + } } } break; @@ -1609,6 +1613,33 @@ void SafePointNode::disconnect_from_root(PhaseIterGVN *igvn) { } } +void SafePointNode::remove_non_debug_edges(NodeEdgeTempStorage& non_debug_edges) { + assert(non_debug_edges._state == NodeEdgeTempStorage::state_initial, "not processed"); + assert(non_debug_edges.is_empty(), "edges not processed"); + + while (req() > jvms()->endoff()) { + uint last = req() - 1; + non_debug_edges.push(in(last)); + del_req(last); + } + + assert(jvms()->endoff() == req(), "no extra edges past debug info allowed"); + DEBUG_ONLY(non_debug_edges._state = NodeEdgeTempStorage::state_populated); +} + +void SafePointNode::restore_non_debug_edges(NodeEdgeTempStorage& non_debug_edges) { + assert(non_debug_edges._state == NodeEdgeTempStorage::state_populated, "not populated"); + assert(jvms()->endoff() == req(), "no extra edges past debug info allowed"); + + while (!non_debug_edges.is_empty()) { + Node* non_debug_edge = non_debug_edges.pop(); + add_req(non_debug_edge); + } + + assert(non_debug_edges.is_empty(), "edges not processed"); + DEBUG_ONLY(non_debug_edges._state = NodeEdgeTempStorage::state_processed); +} + //============== SafePointScalarObjectNode ============== SafePointScalarObjectNode::SafePointScalarObjectNode(const TypeOopPtr* tp, Node* alloc, uint first_index, uint depth, uint n_fields) : diff --git a/src/hotspot/share/opto/callnode.hpp b/src/hotspot/share/opto/callnode.hpp index 5241ae6cd2b..e4c548fc744 100644 --- a/src/hotspot/share/opto/callnode.hpp +++ b/src/hotspot/share/opto/callnode.hpp @@ -503,6 +503,66 @@ public: return _has_ea_local_in_scope; } + // A temporary storge for node edges. + // Intended for a single use. + class NodeEdgeTempStorage : public StackObj { + friend class SafePointNode; + + PhaseIterGVN& _igvn; + Node* _node_hook; + +#ifdef ASSERT + enum State { state_initial, state_populated, state_processed }; + + State _state; // monotonically transitions from initial to processed state. +#endif // ASSERT + + bool is_empty() const { + return _node_hook == nullptr || _node_hook->req() == 1; + } + void push(Node* n) { + assert(n != nullptr, ""); + if (_node_hook == nullptr) { + _node_hook = new Node(nullptr); + } + _node_hook->add_req(n); + } + Node* pop() { + assert(!is_empty(), ""); + int idx = _node_hook->req()-1; + Node* r = _node_hook->in(idx); + _node_hook->del_req(idx); + assert(r != nullptr, ""); + return r; + } + + public: + NodeEdgeTempStorage(PhaseIterGVN &igvn) : _igvn(igvn), _node_hook(nullptr) + DEBUG_ONLY(COMMA _state(state_initial)) { + assert(is_empty(), ""); + } + + ~NodeEdgeTempStorage() { + assert(_state == state_processed, "not processed"); + assert(is_empty(), ""); + if (_node_hook != nullptr) { + _node_hook->destruct(&_igvn); + } + } + + void remove_edge_if_present(Node* n) { + if (!is_empty()) { + int idx = _node_hook->find_edge(n); + if (idx > 0) { + _node_hook->del_req(idx); + } + } + } + }; + + void remove_non_debug_edges(NodeEdgeTempStorage& non_debug_edges); + void restore_non_debug_edges(NodeEdgeTempStorage& non_debug_edges); + void disconnect_from_root(PhaseIterGVN *igvn); // Standard Node stuff @@ -736,7 +796,7 @@ public: // Returns true if the call may modify n virtual bool may_modify(const TypeOopPtr* t_oop, PhaseValues* phase) const; // Does this node have a use of n other than in debug information? - bool has_non_debug_use(Node* n); + bool has_non_debug_use(const Node* n); // Returns the unique CheckCastPP of a call // or result projection is there are several CheckCastPP // or returns null if there is no one. @@ -751,7 +811,10 @@ public: // Collect all the interesting edges from a call for use in // replacing the call by something else. Used by macro expansion // and the late inlining support. - void extract_projections(CallProjections* projs, bool separate_io_proj, bool do_asserts = true) const; + void extract_projections(CallProjections* projs, + bool separate_io_proj, + bool do_asserts = true, + bool allow_handlers = false) const; virtual uint match_edge(uint idx) const; diff --git a/src/hotspot/share/opto/classes.cpp b/src/hotspot/share/opto/classes.cpp index b760a179b57..1cd6c52393b 100644 --- a/src/hotspot/share/opto/classes.cpp +++ b/src/hotspot/share/opto/classes.cpp @@ -43,6 +43,7 @@ #include "opto/narrowptrnode.hpp" #include "opto/node.hpp" #include "opto/opaquenode.hpp" +#include "opto/reachability.hpp" #include "opto/rootnode.hpp" #include "opto/subnode.hpp" #include "opto/subtypenode.hpp" diff --git a/src/hotspot/share/opto/classes.hpp b/src/hotspot/share/opto/classes.hpp index d9290492337..3c1a68d6224 100644 --- a/src/hotspot/share/opto/classes.hpp +++ b/src/hotspot/share/opto/classes.hpp @@ -547,3 +547,4 @@ macro(MaskAll) macro(AndVMask) macro(OrVMask) macro(XorVMask) +macro(ReachabilityFence) diff --git a/src/hotspot/share/opto/compile.cpp b/src/hotspot/share/opto/compile.cpp index 7b5f78a8ad3..db3cbd4109c 100644 --- a/src/hotspot/share/opto/compile.cpp +++ b/src/hotspot/share/opto/compile.cpp @@ -74,6 +74,7 @@ #include "opto/output.hpp" #include "opto/parse.hpp" #include "opto/phaseX.hpp" +#include "opto/reachability.hpp" #include "opto/rootnode.hpp" #include "opto/runtime.hpp" #include "opto/stringopts.hpp" @@ -396,6 +397,9 @@ void Compile::remove_useless_node(Node* dead) { if (dead->is_expensive()) { remove_expensive_node(dead); } + if (dead->is_ReachabilityFence()) { + remove_reachability_fence(dead->as_ReachabilityFence()); + } if (dead->is_OpaqueTemplateAssertionPredicate()) { remove_template_assertion_predicate_opaque(dead->as_OpaqueTemplateAssertionPredicate()); } @@ -459,6 +463,7 @@ void Compile::disconnect_useless_nodes(Unique_Node_List& useful, Unique_Node_Lis // Remove useless Template Assertion Predicate opaque nodes remove_useless_nodes(_template_assertion_predicate_opaques, useful); remove_useless_nodes(_expensive_nodes, useful); // remove useless expensive nodes + remove_useless_nodes(_reachability_fences, useful); // remove useless node recorded for post loop opts IGVN pass remove_useless_nodes(_for_post_loop_igvn, useful); // remove useless node recorded for post loop opts IGVN pass remove_useless_nodes(_for_merge_stores_igvn, useful); // remove useless node recorded for merge stores IGVN pass remove_useless_unstable_if_traps(useful); // remove useless unstable_if traps @@ -665,6 +670,7 @@ Compile::Compile(ciEnv* ci_env, ciMethod* target, int osr_bci, _parse_predicates(comp_arena(), 8, 0, nullptr), _template_assertion_predicate_opaques(comp_arena(), 8, 0, nullptr), _expensive_nodes(comp_arena(), 8, 0, nullptr), + _reachability_fences(comp_arena(), 8, 0, nullptr), _for_post_loop_igvn(comp_arena(), 8, 0, nullptr), _for_merge_stores_igvn(comp_arena(), 8, 0, nullptr), _unstable_if_traps(comp_arena(), 8, 0, nullptr), @@ -934,6 +940,7 @@ Compile::Compile(ciEnv* ci_env, _directive(directive), _log(ci_env->log()), _first_failure_details(nullptr), + _reachability_fences(comp_arena(), 8, 0, nullptr), _for_post_loop_igvn(comp_arena(), 8, 0, nullptr), _for_merge_stores_igvn(comp_arena(), 8, 0, nullptr), _congraph(nullptr), @@ -2510,12 +2517,23 @@ void Compile::Optimize() { return; } - if (failing()) return; - C->clear_major_progress(); // ensure that major progress is now clear process_for_post_loop_opts_igvn(igvn); + if (failing()) return; + + // Once loop optimizations are over, it is safe to get rid of all reachability fence nodes and + // migrate reachability edges to safepoints. + if (OptimizeReachabilityFences && _reachability_fences.length() > 0) { + TracePhase tp1(_t_idealLoop); + TracePhase tp2(_t_reachability); + PhaseIdealLoop::optimize(igvn, PostLoopOptsExpandReachabilityFences); + print_method(PHASE_EXPAND_REACHABILITY_FENCES, 2); + if (failing()) return; + assert(_reachability_fences.length() == 0 || PreserveReachabilityFencesOnConstants, "no RF nodes allowed"); + } + process_for_merge_stores_igvn(igvn); if (failing()) return; @@ -3973,10 +3991,28 @@ void Compile::final_graph_reshaping_walk(Node_Stack& nstack, Node* root, Final_R } } + expand_reachability_edges(sfpt); + // Skip next transformation if compressed oops are not used. if (UseCompressedOops && !Matcher::gen_narrow_oop_implicit_null_checks()) return; + // Go over ReachabilityFence nodes to skip DecodeN nodes for referents. + // The sole purpose of RF node is to keep the referent oop alive and + // decoding the oop for that is not needed. + for (int i = 0; i < C->reachability_fences_count(); i++) { + ReachabilityFenceNode* rf = C->reachability_fence(i); + DecodeNNode* dn = rf->in(1)->isa_DecodeN(); + if (dn != nullptr) { + if (!dn->has_non_debug_uses() || Matcher::narrow_oop_use_complex_address()) { + rf->set_req(1, dn->in(1)); + if (dn->outcnt() == 0) { + dn->disconnect_inputs(this); + } + } + } + } + // Go over safepoints nodes to skip DecodeN/DecodeNKlass nodes for debug edges. // It could be done for an uncommon traps or any safepoints/calls // if the DecodeN/DecodeNKlass node is referenced only in a debug info. @@ -3990,21 +4026,8 @@ void Compile::final_graph_reshaping_walk(Node_Stack& nstack, Node* root, Final_R n->as_CallStaticJava()->uncommon_trap_request() != 0); for (int j = start; j < end; j++) { Node* in = n->in(j); - if (in->is_DecodeNarrowPtr()) { - bool safe_to_skip = true; - if (!is_uncommon ) { - // Is it safe to skip? - for (uint i = 0; i < in->outcnt(); i++) { - Node* u = in->raw_out(i); - if (!u->is_SafePoint() || - (u->is_Call() && u->as_Call()->has_non_debug_use(n))) { - safe_to_skip = false; - } - } - } - if (safe_to_skip) { - n->set_req(j, in->in(1)); - } + if (in->is_DecodeNarrowPtr() && (is_uncommon || !in->has_non_debug_uses())) { + n->set_req(j, in->in(1)); if (in->outcnt() == 0) { in->disconnect_inputs(this); } diff --git a/src/hotspot/share/opto/compile.hpp b/src/hotspot/share/opto/compile.hpp index eb6be669f24..ff0085d79de 100644 --- a/src/hotspot/share/opto/compile.hpp +++ b/src/hotspot/share/opto/compile.hpp @@ -80,6 +80,7 @@ class PhaseIterGVN; class PhaseRegAlloc; class PhaseCCP; class PhaseOutput; +class ReachabilityFenceNode; class RootNode; class relocInfo; class StartNode; @@ -107,7 +108,8 @@ enum LoopOptsMode { LoopOptsMaxUnroll, LoopOptsShenandoahExpand, LoopOptsSkipSplitIf, - LoopOptsVerify + LoopOptsVerify, + PostLoopOptsExpandReachabilityFences }; // The type of all node counts and indexes. @@ -385,6 +387,7 @@ class Compile : public Phase { // of Template Assertion Predicates themselves. GrowableArray _template_assertion_predicate_opaques; GrowableArray _expensive_nodes; // List of nodes that are expensive to compute and that we'd better not let the GVN freely common + GrowableArray _reachability_fences; // List of reachability fences GrowableArray _for_post_loop_igvn; // List of nodes for IGVN after loop opts are over GrowableArray _for_merge_stores_igvn; // List of nodes for IGVN merge stores GrowableArray _unstable_if_traps; // List of ifnodes after IGVN @@ -714,11 +717,13 @@ public: int template_assertion_predicate_count() const { return _template_assertion_predicate_opaques.length(); } int expensive_count() const { return _expensive_nodes.length(); } int coarsened_count() const { return _coarsened_locks.length(); } - Node* macro_node(int idx) const { return _macro_nodes.at(idx); } Node* expensive_node(int idx) const { return _expensive_nodes.at(idx); } + ReachabilityFenceNode* reachability_fence(int idx) const { return _reachability_fences.at(idx); } + int reachability_fences_count() const { return _reachability_fences.length(); } + ConnectionGraph* congraph() { return _congraph;} void set_congraph(ConnectionGraph* congraph) { _congraph = congraph;} void add_macro_node(Node * n) { @@ -740,6 +745,14 @@ public: _expensive_nodes.remove_if_existing(n); } + void add_reachability_fence(ReachabilityFenceNode* rf) { + _reachability_fences.append(rf); + } + + void remove_reachability_fence(ReachabilityFenceNode* n) { + _reachability_fences.remove_if_existing(n); + } + void add_parse_predicate(ParsePredicateNode* n) { assert(!_parse_predicates.contains(n), "duplicate entry in Parse Predicate list"); _parse_predicates.append(n); @@ -1300,6 +1313,9 @@ public: // Definitions of pd methods static void pd_compiler2_init(); + // Materialize reachability fences from reachability edges on safepoints. + void expand_reachability_edges(Unique_Node_List& safepoints); + // Static parse-time type checking logic for gen_subtype_check: enum SubTypeCheckResult { SSC_always_false, SSC_always_true, SSC_easy_test, SSC_full_test }; SubTypeCheckResult static_subtype_check(const TypeKlassPtr* superk, const TypeKlassPtr* subk, bool skip = StressReflectiveCode); diff --git a/src/hotspot/share/opto/escape.cpp b/src/hotspot/share/opto/escape.cpp index 64ee60037d6..a05ad0ef99a 100644 --- a/src/hotspot/share/opto/escape.cpp +++ b/src/hotspot/share/opto/escape.cpp @@ -1273,21 +1273,33 @@ bool ConnectionGraph::reduce_phi_on_safepoints_helper(Node* ophi, Node* cast, No for (uint spi = 0; spi < safepoints.size(); spi++) { SafePointNode* sfpt = safepoints.at(spi)->as_SafePoint(); - JVMState *jvms = sfpt->jvms(); - uint merge_idx = (sfpt->req() - jvms->scloff()); - int debug_start = jvms->debug_start(); + + SafePointNode::NodeEdgeTempStorage non_debug_edges_worklist(*_igvn); + + // All sfpt inputs are implicitly included into debug info during the scalarization process below. + // Keep non-debug inputs separately, so they stay non-debug. + sfpt->remove_non_debug_edges(non_debug_edges_worklist); + + JVMState* jvms = sfpt->jvms(); + uint merge_idx = (sfpt->req() - jvms->scloff()); + int debug_start = jvms->debug_start(); SafePointScalarMergeNode* smerge = new SafePointScalarMergeNode(merge_t, merge_idx); smerge->init_req(0, _compile->root()); _igvn->register_new_node_with_optimizer(smerge); + assert(sfpt->jvms()->endoff() == sfpt->req(), "no extra edges past debug info allowed"); + // The next two inputs are: // (1) A copy of the original pointer to NSR objects. // (2) A selector, used to decide if we need to rematerialize an object // or use the pointer to a NSR object. - // See more details of these fields in the declaration of SafePointScalarMergeNode + // See more details of these fields in the declaration of SafePointScalarMergeNode. + // It is safe to include them into debug info straight away since create_scalarized_object_description() + // will include all newly added inputs into debug info anyway. sfpt->add_req(nsr_merge_pointer); sfpt->add_req(selector); + sfpt->jvms()->set_endoff(sfpt->req()); for (uint i = 1; i < ophi->req(); i++) { Node* base = ophi->in(i); @@ -1302,13 +1314,15 @@ bool ConnectionGraph::reduce_phi_on_safepoints_helper(Node* ophi, Node* cast, No AllocateNode* alloc = ptn->ideal_node()->as_Allocate(); SafePointScalarObjectNode* sobj = mexp.create_scalarized_object_description(alloc, sfpt); if (sobj == nullptr) { - return false; + sfpt->restore_non_debug_edges(non_debug_edges_worklist); + return false; // non-recoverable failure; recompile } // Now make a pass over the debug information replacing any references // to the allocated object with "sobj" Node* ccpp = alloc->result_cast(); sfpt->replace_edges_in_range(ccpp, sobj, debug_start, jvms->debug_end(), _igvn); + non_debug_edges_worklist.remove_edge_if_present(ccpp); // drop scalarized input from non-debug info // Register the scalarized object as a candidate for reallocation smerge->add_req(sobj); @@ -1316,11 +1330,15 @@ bool ConnectionGraph::reduce_phi_on_safepoints_helper(Node* ophi, Node* cast, No // Replaces debug information references to "original_sfpt_parent" in "sfpt" with references to "smerge" sfpt->replace_edges_in_range(original_sfpt_parent, smerge, debug_start, jvms->debug_end(), _igvn); + non_debug_edges_worklist.remove_edge_if_present(original_sfpt_parent); // drop scalarized input from non-debug info // The call to 'replace_edges_in_range' above might have removed the // reference to ophi that we need at _merge_pointer_idx. The line below make // sure the reference is maintained. sfpt->set_req(smerge->merge_pointer_idx(jvms), nsr_merge_pointer); + + sfpt->restore_non_debug_edges(non_debug_edges_worklist); + _igvn->_worklist.push(sfpt); } @@ -4712,6 +4730,7 @@ void ConnectionGraph::split_unique_types(GrowableArray &alloc_worklist, op == Op_StrIndexOf || op == Op_StrIndexOfChar || op == Op_SubTypeCheck || op == Op_ReinterpretS2HF || + op == Op_ReachabilityFence || BarrierSet::barrier_set()->barrier_set_c2()->is_gc_barrier_node(use))) { n->dump(); use->dump(); diff --git a/src/hotspot/share/opto/gcm.cpp b/src/hotspot/share/opto/gcm.cpp index 4a1553b1e00..e3d3108a22e 100644 --- a/src/hotspot/share/opto/gcm.cpp +++ b/src/hotspot/share/opto/gcm.cpp @@ -152,9 +152,12 @@ bool PhaseCFG::is_CFG(Node* n) { } bool PhaseCFG::is_control_proj_or_safepoint(Node* n) const { - bool result = (n->is_Mach() && n->as_Mach()->ideal_Opcode() == Op_SafePoint) || (n->is_Proj() && n->as_Proj()->bottom_type() == Type::CONTROL); - assert(!result || (n->is_Mach() && n->as_Mach()->ideal_Opcode() == Op_SafePoint) - || (n->is_Proj() && n->as_Proj()->_con == 0), "If control projection, it must be projection 0"); + bool result = n->is_ReachabilityFence() || + (n->is_Mach() && n->as_Mach()->ideal_Opcode() == Op_SafePoint) || + (n->is_Proj() && n->as_Proj()->bottom_type() == Type::CONTROL); + assert(!n->is_Proj() || + n->as_Proj()->bottom_type() != Type::CONTROL || + n->as_Proj()->_con == 0, "If control projection, it must be projection 0"); return result; } diff --git a/src/hotspot/share/opto/graphKit.cpp b/src/hotspot/share/opto/graphKit.cpp index 8d32694e9a5..bbd00d111f7 100644 --- a/src/hotspot/share/opto/graphKit.cpp +++ b/src/hotspot/share/opto/graphKit.cpp @@ -41,6 +41,7 @@ #include "opto/machnode.hpp" #include "opto/opaquenode.hpp" #include "opto/parse.hpp" +#include "opto/reachability.hpp" #include "opto/rootnode.hpp" #include "opto/runtime.hpp" #include "opto/subtypenode.hpp" @@ -3541,6 +3542,15 @@ Node* GraphKit::insert_mem_bar_volatile(int opcode, int alias_idx, Node* precede return membar; } +//------------------------------insert_reachability_fence---------------------- +Node* GraphKit::insert_reachability_fence(Node* referent) { + assert(!referent->is_top(), ""); + Node* rf = _gvn.transform(new ReachabilityFenceNode(C, control(), referent)); + set_control(rf); + C->record_for_igvn(rf); + return rf; +} + //------------------------------shared_lock------------------------------------ // Emit locking code. FastLockNode* GraphKit::shared_lock(Node* obj) { diff --git a/src/hotspot/share/opto/graphKit.hpp b/src/hotspot/share/opto/graphKit.hpp index 273009ca7ce..f53f73d0978 100644 --- a/src/hotspot/share/opto/graphKit.hpp +++ b/src/hotspot/share/opto/graphKit.hpp @@ -805,6 +805,7 @@ class GraphKit : public Phase { int next_monitor(); Node* insert_mem_bar(int opcode, Node* precedent = nullptr); Node* insert_mem_bar_volatile(int opcode, int alias_idx, Node* precedent = nullptr); + Node* insert_reachability_fence(Node* referent); // Optional 'precedent' is appended as an extra edge, to force ordering. FastLockNode* shared_lock(Node* obj); void shared_unlock(Node* box, Node* obj); diff --git a/src/hotspot/share/opto/library_call.cpp b/src/hotspot/share/opto/library_call.cpp index e0f95377cde..26417436ed9 100644 --- a/src/hotspot/share/opto/library_call.cpp +++ b/src/hotspot/share/opto/library_call.cpp @@ -568,6 +568,7 @@ bool LibraryCallKit::try_to_inline(int predicate) { case vmIntrinsics::_Reference_get0: return inline_reference_get0(); case vmIntrinsics::_Reference_refersTo0: return inline_reference_refersTo0(false); + case vmIntrinsics::_Reference_reachabilityFence: return inline_reference_reachabilityFence(); case vmIntrinsics::_PhantomReference_refersTo0: return inline_reference_refersTo0(true); case vmIntrinsics::_Reference_clear0: return inline_reference_clear0(false); case vmIntrinsics::_PhantomReference_clear0: return inline_reference_clear0(true); @@ -7025,6 +7026,14 @@ bool LibraryCallKit::inline_reference_clear0(bool is_phantom) { return true; } +//-----------------------inline_reference_reachabilityFence----------------- +// bool java.lang.ref.Reference.reachabilityFence(); +bool LibraryCallKit::inline_reference_reachabilityFence() { + Node* referent = argument(0); + insert_reachability_fence(referent); + return true; +} + Node* LibraryCallKit::load_field_from_object(Node* fromObj, const char* fieldName, const char* fieldTypeString, DecoratorSet decorators, bool is_static, ciInstanceKlass* fromKls) { diff --git a/src/hotspot/share/opto/library_call.hpp b/src/hotspot/share/opto/library_call.hpp index 9b87df645e1..9aae48302cf 100644 --- a/src/hotspot/share/opto/library_call.hpp +++ b/src/hotspot/share/opto/library_call.hpp @@ -312,6 +312,7 @@ class LibraryCallKit : public GraphKit { bool inline_divmod_methods(vmIntrinsics::ID id); bool inline_reference_get0(); bool inline_reference_refersTo0(bool is_phantom); + bool inline_reference_reachabilityFence(); bool inline_reference_clear0(bool is_phantom); bool inline_Class_cast(); bool inline_aescrypt_Block(vmIntrinsics::ID id); diff --git a/src/hotspot/share/opto/loopTransform.cpp b/src/hotspot/share/opto/loopTransform.cpp index 60a61b6bb4e..b65f90093ab 100644 --- a/src/hotspot/share/opto/loopTransform.cpp +++ b/src/hotspot/share/opto/loopTransform.cpp @@ -52,17 +52,70 @@ // Given an IfNode, return the loop-exiting projection or null if both // arms remain in the loop. Node *IdealLoopTree::is_loop_exit(Node *iff) const { - if (iff->outcnt() != 2) return nullptr; // Ignore partially dead tests - PhaseIdealLoop *phase = _phase; + assert(iff->is_If(), "not an If: %s", iff->Name()); + assert(is_member(_phase->get_loop(iff)), "not related"); + + if (iff->outcnt() != 2) { + return nullptr; // Ignore partially dead tests + } // Test is an IfNode, has 2 projections. If BOTH are in the loop // we need loop unswitching instead of peeling. - if (!is_member(phase->get_loop(iff->raw_out(0)))) + if (!is_member(_phase->get_loop(iff->raw_out(0)))) { return iff->raw_out(0); - if (!is_member(phase->get_loop(iff->raw_out(1)))) + } + if (!is_member(_phase->get_loop(iff->raw_out(1)))) { return iff->raw_out(1); + } return nullptr; } +//------------------------------unique_loop_exit_or_null---------------------- +// Return the loop-exit projection if loop exit is unique. +IfFalseNode* IdealLoopTree::unique_loop_exit_proj_or_null() { + if (is_loop() && head()->is_BaseCountedLoop()) { + IfNode* loop_end = head()->as_BaseCountedLoop()->loopexit_or_null(); + if (loop_end == nullptr) { + return nullptr; // malformed loop shape + } + // Look for other loop exits. + assert(_phase->is_dominator(head(), tail()), "sanity"); + for (Node* ctrl = tail(); ctrl != head(); ctrl = ctrl->in(0)) { + assert(is_member(_phase->get_loop(ctrl)), "sanity"); + if (ctrl->is_If()) { + if (!is_loop_exit(ctrl->as_If())) { + continue; // local branch + } else if (ctrl != loop_end) { + return nullptr; // multiple loop exits + } + } else if (ctrl->is_Region()) { + return nullptr; // give up on control flow merges + } else if (ctrl->is_ReachabilityFence() || + ctrl->is_SafePoint() || + ctrl->is_MemBar() || + ctrl->Opcode() == Op_Blackhole) { + continue; // skip + } else if (ctrl->is_Proj()) { + if (ctrl->is_IfProj() || + ctrl->Opcode() == Op_SCMemProj || + ctrl->Opcode() == Op_Proj) { + continue; // skip simple control projections + } else if (ctrl->is_CatchProj() || + ctrl->is_JumpProj()) { + return nullptr; // give up on control flow splits + } else { + assert(false, "unknown control projection: %s", ctrl->Name()); + return nullptr; // stop on unknown control node + } + } else { + assert(false, "unknown CFG node: %s", ctrl->Name()); + return nullptr; // stop on unknown control node + } + } + assert(is_loop_exit(loop_end), "not a loop exit?"); + return loop_end->false_proj_or_null(); + } + return nullptr; // not found or multiple loop exits +} //============================================================================= @@ -3135,9 +3188,13 @@ static CountedLoopNode* locate_pre_from_main(CountedLoopNode* main_loop) { Node* ctrl = main_loop->skip_assertion_predicates_with_halt(); assert(ctrl->Opcode() == Op_IfTrue || ctrl->Opcode() == Op_IfFalse, ""); Node* iffm = ctrl->in(0); - assert(iffm->Opcode() == Op_If, ""); + assert(iffm->Opcode() == Op_If, "%s", iffm->Name()); Node* p_f = iffm->in(0); - assert(p_f->Opcode() == Op_IfFalse, ""); + // Skip ReachabilityFences hoisted out of pre-loop. + while (p_f->is_ReachabilityFence()) { + p_f = p_f->in(0); + } + assert(p_f->Opcode() == Op_IfFalse, "%s", p_f->Name()); CountedLoopNode* pre_loop = p_f->in(0)->as_CountedLoopEnd()->loopnode(); assert(pre_loop->is_pre_loop(), "No pre loop found"); return pre_loop; diff --git a/src/hotspot/share/opto/loopnode.cpp b/src/hotspot/share/opto/loopnode.cpp index 6963a67118f..35a9108892c 100644 --- a/src/hotspot/share/opto/loopnode.cpp +++ b/src/hotspot/share/opto/loopnode.cpp @@ -44,6 +44,7 @@ #include "opto/opaquenode.hpp" #include "opto/opcodes.hpp" #include "opto/predicates.hpp" +#include "opto/reachability.hpp" #include "opto/rootnode.hpp" #include "opto/runtime.hpp" #include "opto/vectorization.hpp" @@ -3934,6 +3935,7 @@ IdealLoopTree::IdealLoopTree(PhaseIdealLoop* phase, Node* head, Node* tail): _pa _has_range_checks(0), _has_range_checks_computed(0), _safepts(nullptr), _required_safept(nullptr), + _reachability_fences(nullptr), _allow_optimizations(true) { precond(_head != nullptr); precond(_tail != nullptr); @@ -4855,6 +4857,15 @@ uint IdealLoopTree::est_loop_flow_merge_sz() const { return 0; } +void IdealLoopTree::register_reachability_fence(ReachabilityFenceNode* rf) { + if (_reachability_fences == nullptr) { + _reachability_fences = new Node_List(); + } + if (!_reachability_fences->contains(rf)) { + _reachability_fences->push(rf); + } +} + #ifndef PRODUCT //------------------------------dump_head-------------------------------------- // Dump 1 liner for loop header info @@ -4914,6 +4925,9 @@ void IdealLoopTree::dump_head() { if (_has_call) tty->print(" has_call"); if (_has_sfpt) tty->print(" has_sfpt"); if (_rce_candidate) tty->print(" rce"); + if (_reachability_fences != nullptr && _reachability_fences->size() > 0) { + tty->print(" has_rf"); + } if (_safepts != nullptr && _safepts->size() > 0) { tty->print(" sfpts={"); _safepts->dump_simple(); tty->print(" }"); } @@ -4921,6 +4935,9 @@ void IdealLoopTree::dump_head() { tty->print(" req={"); _required_safept->dump_simple(); tty->print(" }"); } if (Verbose) { + if (_reachability_fences != nullptr && _reachability_fences->size() > 0) { + tty->print(" rfs={"); _reachability_fences->dump_simple(); tty->print(" }"); + } tty->print(" body={"); _body.dump_simple(); tty->print(" }"); } if (_head->is_Loop() && _head->as_Loop()->is_strip_mined()) { @@ -5179,12 +5196,14 @@ bool PhaseIdealLoop::process_expensive_nodes() { // Create a PhaseLoop. Build the ideal Loop tree. Map each Ideal Node to // its corresponding LoopNode. If 'optimize' is true, do some loop cleanups. void PhaseIdealLoop::build_and_optimize() { - assert(!C->post_loop_opts_phase(), "no loop opts allowed"); - bool do_split_ifs = (_mode == LoopOptsDefault); bool skip_loop_opts = (_mode == LoopOptsNone); bool do_max_unroll = (_mode == LoopOptsMaxUnroll); + bool do_verify = (_mode == LoopOptsVerify); + bool do_expand_reachability_fences = (_mode == PostLoopOptsExpandReachabilityFences); + assert(!C->post_loop_opts_phase() || do_expand_reachability_fences || do_verify, + "no loop opts allowed"); bool old_progress = C->major_progress(); uint orig_worklist_size = _igvn._worklist.size(); @@ -5251,11 +5270,13 @@ void PhaseIdealLoop::build_and_optimize() { BarrierSetC2* bs = BarrierSet::barrier_set()->barrier_set_c2(); // Nothing to do, so get out - bool stop_early = !C->has_loops() && !skip_loop_opts && !do_split_ifs && !do_max_unroll && !_verify_me && - !_verify_only && !bs->is_gc_specific_loop_opts_pass(_mode); + bool stop_early = !C->has_loops() && !skip_loop_opts && !do_split_ifs && !do_max_unroll && + !do_expand_reachability_fences && !_verify_me && !_verify_only && + !bs->is_gc_specific_loop_opts_pass(_mode) ; bool do_expensive_nodes = C->should_optimize_expensive_nodes(_igvn); + bool do_optimize_reachability_fences = OptimizeReachabilityFences && (C->reachability_fences_count() > 0); bool strip_mined_loops_expanded = bs->strip_mined_loops_expanded(_mode); - if (stop_early && !do_expensive_nodes) { + if (stop_early && !do_expensive_nodes && !do_optimize_reachability_fences) { return; } @@ -5331,7 +5352,7 @@ void PhaseIdealLoop::build_and_optimize() { // Given early legal placement, try finding counted loops. This placement // is good enough to discover most loop invariants. - if (!_verify_me && !_verify_only && !strip_mined_loops_expanded) { + if (!_verify_me && !_verify_only && !strip_mined_loops_expanded && !do_expand_reachability_fences) { _ltree_root->counted_loop( this ); } @@ -5361,8 +5382,14 @@ void PhaseIdealLoop::build_and_optimize() { eliminate_useless_multiversion_if(); if (stop_early) { - assert(do_expensive_nodes, "why are we here?"); - if (process_expensive_nodes()) { + assert(do_expensive_nodes || do_optimize_reachability_fences, "why are we here?"); + // Use the opportunity to optimize reachability fence nodes irrespective of + // whether loop optimizations are performed or not. + if (do_optimize_reachability_fences && optimize_reachability_fences()) { + recompute_dom_depth(); + DEBUG_ONLY( if (VerifyLoopOptimizations) { verify(); } ); + } + if (do_expensive_nodes && process_expensive_nodes()) { // If we made some progress when processing expensive nodes then // the IGVN may modify the graph in a way that will allow us to // make some more progress: we need to try processing expensive @@ -5390,6 +5417,22 @@ void PhaseIdealLoop::build_and_optimize() { } #endif + if (do_optimize_reachability_fences && optimize_reachability_fences()) { + recompute_dom_depth(); + DEBUG_ONLY( if (VerifyLoopOptimizations) { verify(); } ); + } + + if (do_expand_reachability_fences) { + assert(C->post_loop_opts_phase(), "required"); + if (expand_reachability_fences()) { + recompute_dom_depth(); + DEBUG_ONLY( if (VerifyLoopOptimizations) { verify(); } ); + } + return; + } + + assert(!C->post_loop_opts_phase(), "required"); + if (skip_loop_opts) { C->restore_major_progress(old_progress); return; @@ -6286,6 +6329,8 @@ int PhaseIdealLoop::build_loop_tree_impl(Node* n, int pre_order) { // Record all safepoints in this loop. if (innermost->_safepts == nullptr) innermost->_safepts = new Node_List(); innermost->_safepts->push(n); + } else if (n->is_ReachabilityFence()) { + innermost->register_reachability_fence(n->as_ReachabilityFence()); } } } diff --git a/src/hotspot/share/opto/loopnode.hpp b/src/hotspot/share/opto/loopnode.hpp index 6667c71511c..26b82259327 100644 --- a/src/hotspot/share/opto/loopnode.hpp +++ b/src/hotspot/share/opto/loopnode.hpp @@ -44,6 +44,7 @@ class PredicateBlock; class PathFrequency; class PhaseIdealLoop; class LoopSelector; +class ReachabilityFenceNode; class UnswitchedLoopSelector; class VectorSet; class VSharedData; @@ -662,6 +663,7 @@ public: Node_List* _safepts; // List of safepoints in this loop Node_List* _required_safept; // A inner loop cannot delete these safepts; + Node_List* _reachability_fences; // List of reachability fences in this loop bool _allow_optimizations; // Allow loop optimizations IdealLoopTree(PhaseIdealLoop* phase, Node* head, Node* tail); @@ -720,6 +722,9 @@ public: // Check for Node being a loop-breaking test Node *is_loop_exit(Node *iff) const; + // Return unique loop-exit projection or null if the loop has multiple exits. + IfFalseNode* unique_loop_exit_proj_or_null(); + // Remove simplistic dead code from loop body void DCE_loop_body(); @@ -825,6 +830,9 @@ public: return _head->as_Loop()->is_strip_mined() ? _parent : this; } + // Registers a reachability fence node in the loop. + void register_reachability_fence(ReachabilityFenceNode* rf); + #ifndef PRODUCT void dump_head(); // Dump loop head only void dump(); // Dump this loop recursively @@ -1167,6 +1175,16 @@ public: forward_ctrl(old_node, new_node); } + void remove_dead_data_node(Node* dead) { + assert(dead->outcnt() == 0 && !dead->is_top(), "must be dead"); + assert(!dead->is_CFG(), "not a data node"); + Node* c = get_ctrl(dead); + IdealLoopTree* lpt = get_loop(c); + _loop_or_ctrl.map(dead->_idx, nullptr); // This node is useless + lpt->_body.yank(dead); + igvn().remove_dead_node(dead, PhaseIterGVN::NodeOrigin::Graph); + } + private: // Place 'n' in some loop nest, where 'n' is a CFG node @@ -1599,6 +1617,15 @@ public: // Implementation of the loop predication to promote checks outside the loop bool loop_predication_impl(IdealLoopTree *loop); + // Reachability Fence (RF) support. + private: + void insert_rf(Node* ctrl, Node* referent); + void replace_rf(Node* old_node, Node* new_node); + void remove_rf(ReachabilityFenceNode* rf); + public: + bool optimize_reachability_fences(); + bool expand_reachability_fences(); + private: bool loop_predication_impl_helper(IdealLoopTree* loop, IfProjNode* if_success_proj, ParsePredicateSuccessProj* parse_predicate_proj, CountedLoopNode* cl, ConNode* zero, diff --git a/src/hotspot/share/opto/macro.cpp b/src/hotspot/share/opto/macro.cpp index 01c67187883..8c5dbe7fb48 100644 --- a/src/hotspot/share/opto/macro.cpp +++ b/src/hotspot/share/opto/macro.cpp @@ -44,6 +44,7 @@ #include "opto/node.hpp" #include "opto/opaquenode.hpp" #include "opto/phaseX.hpp" +#include "opto/reachability.hpp" #include "opto/rootnode.hpp" #include "opto/runtime.hpp" #include "opto/subnode.hpp" @@ -683,6 +684,8 @@ bool PhaseMacroExpand::can_eliminate_allocation(PhaseIterGVN* igvn, AllocateNode use->as_ArrayCopy()->is_copyofrange_validated()) && use->in(ArrayCopyNode::Dest) == res) { // ok to eliminate + } else if (use->is_ReachabilityFence() && OptimizeReachabilityFences) { + // ok to eliminate } else if (use->is_SafePoint()) { SafePointNode* sfpt = use->as_SafePoint(); if (sfpt->is_Call() && sfpt->as_Call()->has_non_debug_use(res)) { @@ -778,7 +781,13 @@ void PhaseMacroExpand::undo_previous_scalarizations(GrowableArray 0) { SafePointNode* sfpt_done = safepoints_done.pop(); + + SafePointNode::NodeEdgeTempStorage non_debug_edges_worklist(igvn()); + + sfpt_done->remove_non_debug_edges(non_debug_edges_worklist); + // remove any extra entries we added to the safepoint + assert(sfpt_done->jvms()->endoff() == sfpt_done->req(), "no extra edges past debug info allowed"); uint last = sfpt_done->req() - 1; for (int k = 0; k < nfields; k++) { sfpt_done->del_req(last--); @@ -799,6 +808,9 @@ void PhaseMacroExpand::undo_previous_scalarizations(GrowableArray restore_non_debug_edges(non_debug_edges_worklist); + _igvn._worklist.push(sfpt_done); } } @@ -839,6 +851,8 @@ void PhaseMacroExpand::undo_previous_scalarizations(GrowableArray jvms()->endoff() == sfpt->req(), "no extra edges past debug info allowed"); + // Fields of scalar objs are referenced only at the end // of regular debuginfo at the last (youngest) JVMS. // Record relative start index. @@ -964,17 +978,25 @@ SafePointScalarObjectNode* PhaseMacroExpand::create_scalarized_object_descriptio } // Do scalar replacement. -bool PhaseMacroExpand::scalar_replacement(AllocateNode *alloc, GrowableArray & safepoints) { - GrowableArray safepoints_done; +bool PhaseMacroExpand::scalar_replacement(AllocateNode* alloc, GrowableArray& safepoints) { + GrowableArray safepoints_done; Node* res = alloc->result_cast(); assert(res == nullptr || res->is_CheckCastPP(), "unexpected AllocateNode result"); // Process the safepoint uses while (safepoints.length() > 0) { SafePointNode* sfpt = safepoints.pop(); + + SafePointNode::NodeEdgeTempStorage non_debug_edges_worklist(igvn()); + + // All sfpt inputs are implicitly included into debug info during the scalarization process below. + // Keep non-debug inputs separately, so they stay non-debug. + sfpt->remove_non_debug_edges(non_debug_edges_worklist); + SafePointScalarObjectNode* sobj = create_scalarized_object_description(alloc, sfpt); if (sobj == nullptr) { + sfpt->restore_non_debug_edges(non_debug_edges_worklist); undo_previous_scalarizations(safepoints_done, alloc); return false; } @@ -983,6 +1005,8 @@ bool PhaseMacroExpand::scalar_replacement(AllocateNode *alloc, GrowableArray jvms(); sfpt->replace_edges_in_range(res, sobj, jvms->debug_start(), jvms->debug_end(), &_igvn); + non_debug_edges_worklist.remove_edge_if_present(res); // drop scalarized input from non-debug info + sfpt->restore_non_debug_edges(non_debug_edges_worklist); _igvn._worklist.push(sfpt); // keep it for rollback @@ -1073,6 +1097,8 @@ void PhaseMacroExpand::process_users_of_allocation(CallNode *alloc) { } } _igvn._worklist.push(ac); + } else if (use->is_ReachabilityFence() && OptimizeReachabilityFences) { + use->as_ReachabilityFence()->clear_referent(_igvn); // redundant fence; will be removed during IGVN } else { eliminate_gc_barrier(use); } diff --git a/src/hotspot/share/opto/memnode.cpp b/src/hotspot/share/opto/memnode.cpp index 9ec3402c701..c7fe4508473 100644 --- a/src/hotspot/share/opto/memnode.cpp +++ b/src/hotspot/share/opto/memnode.cpp @@ -4081,7 +4081,7 @@ uint LoadStoreNode::ideal_reg() const { bool LoadStoreNode::result_not_used() const { for (DUIterator_Fast imax, i = fast_outs(imax); i < imax; i++) { Node *x = fast_out(i); - if (x->Opcode() == Op_SCMemProj) { + if (x->Opcode() == Op_SCMemProj || x->is_ReachabilityFence()) { continue; } if (x->bottom_type() == TypeTuple::MEMBAR && diff --git a/src/hotspot/share/opto/node.cpp b/src/hotspot/share/opto/node.cpp index cb5795a1250..3eafd97d7c1 100644 --- a/src/hotspot/share/opto/node.cpp +++ b/src/hotspot/share/opto/node.cpp @@ -38,6 +38,7 @@ #include "opto/matcher.hpp" #include "opto/node.hpp" #include "opto/opcodes.hpp" +#include "opto/reachability.hpp" #include "opto/regmask.hpp" #include "opto/rootnode.hpp" #include "opto/type.hpp" @@ -503,6 +504,9 @@ Node *Node::clone() const { if (is_expensive()) { C->add_expensive_node(n); } + if (is_ReachabilityFence()) { + C->add_reachability_fence(n->as_ReachabilityFence()); + } if (for_post_loop_opts_igvn()) { // Don't add cloned node to Compile::_for_post_loop_opts_igvn list automatically. // If it is applicable, it will happen anyway when the cloned node is registered with IGVN. @@ -622,6 +626,9 @@ void Node::destruct(PhaseValues* phase) { if (is_expensive()) { compile->remove_expensive_node(this); } + if (is_ReachabilityFence()) { + compile->remove_reachability_fence(as_ReachabilityFence()); + } if (is_OpaqueTemplateAssertionPredicate()) { compile->remove_template_assertion_predicate_opaque(as_OpaqueTemplateAssertionPredicate()); } @@ -2994,6 +3001,25 @@ bool Node::is_data_proj_of_pure_function(const Node* maybe_pure_function) const return Opcode() == Op_Proj && as_Proj()->_con == TypeFunc::Parms && maybe_pure_function->is_CallLeafPure(); } +//--------------------------has_non_debug_uses------------------------------ +// Checks whether the node has any non-debug uses or not. +bool Node::has_non_debug_uses() const { + for (DUIterator_Fast imax, i = fast_outs(imax); i < imax; i++) { + Node* u = fast_out(i); + if (u->is_SafePoint()) { + if (u->is_Call() && u->as_Call()->has_non_debug_use(this)) { + return true; + } + // Non-call safepoints have only debug uses. + } else if (u->is_ReachabilityFence()) { + // Reachability fence is treated as debug use. + } else { + return true; // everything else is conservatively treated as non-debug use + } + } + return false; // no non-debug uses found +} + //============================================================================= //------------------------------yank------------------------------------------- // Find and remove diff --git a/src/hotspot/share/opto/node.hpp b/src/hotspot/share/opto/node.hpp index 46b89aa2c5f..36855d659ba 100644 --- a/src/hotspot/share/opto/node.hpp +++ b/src/hotspot/share/opto/node.hpp @@ -168,6 +168,7 @@ class Pipeline; class PopulateIndexNode; class ProjNode; class RangeCheckNode; +class ReachabilityFenceNode; class ReductionNode; class RegMask; class RegionNode; @@ -452,6 +453,9 @@ public: // Check whether node has become unreachable bool is_unreachable(PhaseIterGVN &igvn) const; + // Does the node have any immediate non-debug uses? + bool has_non_debug_uses() const; + // Set a required input edge, also updates corresponding output edge void add_req( Node *n ); // Append a NEW required input void add_req( Node *n0, Node *n1 ) { @@ -824,6 +828,7 @@ public: DEFINE_CLASS_ID(Move, Node, 20) DEFINE_CLASS_ID(LShift, Node, 21) DEFINE_CLASS_ID(Neg, Node, 22) + DEFINE_CLASS_ID(ReachabilityFence, Node, 23) _max_classes = ClassMask_Neg }; @@ -1013,6 +1018,7 @@ public: DEFINE_CLASS_QUERY(PCTable) DEFINE_CLASS_QUERY(Phi) DEFINE_CLASS_QUERY(Proj) + DEFINE_CLASS_QUERY(ReachabilityFence) DEFINE_CLASS_QUERY(Reduction) DEFINE_CLASS_QUERY(Region) DEFINE_CLASS_QUERY(Root) diff --git a/src/hotspot/share/opto/parse.hpp b/src/hotspot/share/opto/parse.hpp index 42f44f0f7ea..5118019fc31 100644 --- a/src/hotspot/share/opto/parse.hpp +++ b/src/hotspot/share/opto/parse.hpp @@ -356,6 +356,7 @@ class Parse : public GraphKit { bool _wrote_stable; // Did we write a @Stable field? bool _wrote_fields; // Did we write any field? Node* _alloc_with_final_or_stable; // An allocation node with final or @Stable field + Node* _stress_rf_hook; // StressReachabilityFences support // Variables which track Java semantics during bytecode parsing: diff --git a/src/hotspot/share/opto/parse1.cpp b/src/hotspot/share/opto/parse1.cpp index 683633f6355..6a400631bff 100644 --- a/src/hotspot/share/opto/parse1.cpp +++ b/src/hotspot/share/opto/parse1.cpp @@ -369,6 +369,15 @@ void Parse::load_interpreter_state(Node* osr_buf) { continue; } set_local(index, check_interpreter_type(l, type, bad_type_exit)); + if (StressReachabilityFences && type->isa_oopptr() != nullptr) { + // Keep all oop locals alive until the method returns as if there are + // reachability fences for them at the end of the method. + Node* loc = local(index); + if (loc->bottom_type() != TypePtr::NULL_PTR) { + assert(loc->bottom_type()->isa_oopptr() != nullptr, "%s", Type::str(loc->bottom_type())); + _stress_rf_hook->add_req(loc); + } + } } for (index = 0; index < sp(); index++) { @@ -377,6 +386,15 @@ void Parse::load_interpreter_state(Node* osr_buf) { if (l->is_top()) continue; // nothing here const Type *type = osr_block->stack_type_at(index); set_stack(index, check_interpreter_type(l, type, bad_type_exit)); + if (StressReachabilityFences && type->isa_oopptr() != nullptr) { + // Keep all oops on stack alive until the method returns as if there are + // reachability fences for them at the end of the method. + Node* stk = stack(index); + if (stk->bottom_type() != TypePtr::NULL_PTR) { + assert(stk->bottom_type()->isa_oopptr() != nullptr, "%s", Type::str(stk->bottom_type())); + _stress_rf_hook->add_req(stk); + } + } } if (bad_type_exit->control()->req() > 1) { @@ -411,6 +429,7 @@ Parse::Parse(JVMState* caller, ciMethod* parse_method, float expected_uses) _wrote_stable = false; _wrote_fields = false; _alloc_with_final_or_stable = nullptr; + _stress_rf_hook = (StressReachabilityFences ? new Node(1) : nullptr); _block = nullptr; _first_return = true; _replaced_nodes_for_exceptions = false; @@ -642,6 +661,11 @@ Parse::Parse(JVMState* caller, ciMethod* parse_method, float expected_uses) if (log) log->done("parse nodes='%d' live='%d' memory='%zu'", C->unique(), C->live_nodes(), C->node_arena()->used()); + + if (StressReachabilityFences) { + _stress_rf_hook->destruct(&_gvn); + _stress_rf_hook = nullptr; + } } //---------------------------do_all_blocks------------------------------------- @@ -1194,6 +1218,14 @@ SafePointNode* Parse::create_entry_map() { return entry_map; } +//-----------------------is_auto_boxed_primitive------------------------------ +// Helper method to detect auto-boxed primitives (result of valueOf() call). +static bool is_auto_boxed_primitive(Node* n) { + return (n->is_Proj() && n->as_Proj()->_con == TypeFunc::Parms && + n->in(0)->is_CallJava() && + n->in(0)->as_CallJava()->method()->is_boxing_method()); +} + //-----------------------------do_method_entry-------------------------------- // Emit any code needed in the pseudo-block before BCI zero. // The main thing to do is lock the receiver of a synchronized method. @@ -1207,6 +1239,19 @@ void Parse::do_method_entry() { make_dtrace_method_entry(method()); } + if (StressReachabilityFences) { + // Keep all oop arguments alive until the method returns as if there are + // reachability fences for them at the end of the method. + int max_locals = jvms()->loc_size(); + for (int idx = 0; idx < max_locals; idx++) { + Node* loc = local(idx); + if (loc->bottom_type()->isa_oopptr() != nullptr && + !is_auto_boxed_primitive(loc)) { // ignore auto-boxed primitives + _stress_rf_hook->add_req(loc); + } + } + } + #ifdef ASSERT // Narrow receiver type when it is too broad for the method being parsed. if (!method()->is_static()) { @@ -2197,6 +2242,15 @@ void Parse::return_current(Node* value) { call_register_finalizer(); } + if (StressReachabilityFences) { + // Insert reachability fences for all oop arguments at the end of the method. + for (uint i = 1; i < _stress_rf_hook->req(); i++) { + Node* referent = _stress_rf_hook->in(i); + assert(referent->bottom_type()->isa_oopptr(), "%s", Type::str(referent->bottom_type())); + insert_reachability_fence(referent); + } + } + // Do not set_parse_bci, so that return goo is credited to the return insn. set_bci(InvocationEntryBci); if (method()->is_synchronized()) { diff --git a/src/hotspot/share/opto/phase.cpp b/src/hotspot/share/opto/phase.cpp index 5603033ce69..3f1866990e2 100644 --- a/src/hotspot/share/opto/phase.cpp +++ b/src/hotspot/share/opto/phase.cpp @@ -90,6 +90,9 @@ void Phase::print_timers() { tty->print_cr (" Prune Useless: %7.3f s", timers[_t_vector_pru].seconds()); tty->print_cr (" Renumber Live: %7.3f s", timers[_t_renumberLive].seconds()); tty->print_cr (" IdealLoop: %7.3f s", timers[_t_idealLoop].seconds()); + tty->print_cr (" ReachabilityFence: %7.3f s", timers[_t_reachability].seconds()); + tty->print_cr (" Optimize: %7.3f s", timers[_t_reachability_optimize].seconds()); + tty->print_cr (" Expand: %7.3f s", timers[_t_reachability_expand].seconds()); tty->print_cr (" AutoVectorize: %7.3f s", timers[_t_autoVectorize].seconds()); tty->print_cr (" IdealLoop Verify: %7.3f s", timers[_t_idealLoopVerify].seconds()); tty->print_cr (" Cond Const Prop: %7.3f s", timers[_t_ccp].seconds()); diff --git a/src/hotspot/share/opto/phase.hpp b/src/hotspot/share/opto/phase.hpp index 6700df6ec17..5bd3c34f15f 100644 --- a/src/hotspot/share/opto/phase.hpp +++ b/src/hotspot/share/opto/phase.hpp @@ -85,6 +85,9 @@ public: f( _t_vector_pru, "vector_pru") \ f( _t_renumberLive, "") \ f( _t_idealLoop, "idealLoop") \ + f( _t_reachability, "reachabilityFence") \ + f( _t_reachability_optimize, "reachabilityFence_optimize") \ + f( _t_reachability_expand, "reachabilityFence_expand") \ f( _t_autoVectorize, "autoVectorize") \ f( _t_idealLoopVerify, "idealLoopVerify") \ f( _t_ccp, "ccp") \ diff --git a/src/hotspot/share/opto/phasetype.hpp b/src/hotspot/share/opto/phasetype.hpp index ce432fbfc01..2b599ace03a 100644 --- a/src/hotspot/share/opto/phasetype.hpp +++ b/src/hotspot/share/opto/phasetype.hpp @@ -106,6 +106,7 @@ flags(PHASEIDEALLOOP1, "PhaseIdealLoop 1") \ flags(PHASEIDEALLOOP2, "PhaseIdealLoop 2") \ flags(PHASEIDEALLOOP3, "PhaseIdealLoop 3") \ + flags(EXPAND_REACHABILITY_FENCES, "Expand Reachability Fences") \ flags(AUTO_VECTORIZATION1_BEFORE_APPLY, "AutoVectorization 1, before Apply") \ flags(AUTO_VECTORIZATION3_AFTER_ADJUST_LIMIT, "AutoVectorization 2, after Adjusting Pre-loop Limit") \ flags(AUTO_VECTORIZATION4_AFTER_SPECULATIVE_RUNTIME_CHECKS, "AutoVectorization 3, after Adding Speculative Runtime Checks") \ diff --git a/src/hotspot/share/opto/reachability.cpp b/src/hotspot/share/opto/reachability.cpp new file mode 100644 index 00000000000..b45b340ab85 --- /dev/null +++ b/src/hotspot/share/opto/reachability.cpp @@ -0,0 +1,512 @@ +/* + * 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 "opto/c2_MacroAssembler.hpp" +#include "opto/callnode.hpp" +#include "opto/compile.hpp" +#include "opto/loopnode.hpp" +#include "opto/phaseX.hpp" +#include "opto/reachability.hpp" +#include "opto/regalloc.hpp" +#include "opto/runtime.hpp" +#include "utilities/pair.hpp" + +/* + * java.lang.ref.Reference::reachabilityFence support. + * + * Reachability Fence (RF) ensures that the given object (referent) remains strongly reachable + * regardless of any optimizing transformations the virtual machine may perform that might otherwise + * allow the object to become unreachable. + * + * RFs are intended to be used in performance-critical code, so the primary goal for C2 support is + * to reduce their runtime overhead as much as possible. + * + * Reference::reachabilityFence() calls are intrinsified into ReachabilityFence CFG nodes. RF node keeps + * its referent alive, so the referent's location is recorded at every safepoint (in its oop map) which + * interferes with referent's live range. + * + * It is tempting to directly attach referents to interfering safepoints right from the beginning, but it + * doesn't play well with some optimizations C2 does (e.g., during loop-invariant code motion a safepoint + * can become interfering once a load is hoisted). + * + * Instead, reachability representation transitions through multiple phases: + * (0) initial set of RFs is materialized during parsing (as a result of + * Reference.reachabilityFence intrinsification); + * (1) optimization pass during loop opts eliminates redundant RF nodes and + * moves the ones with loop-invariant referents outside (after) loops; + * (2) after loop opts are over, RF nodes are eliminated and their referents are transferred to + * safepoint nodes (appended as edges after debug info); + * (3) during final graph reshaping, referent edges are removed from safepoints and materialized as RF nodes + * attached to their safepoint node (closely following it in CFG graph). + * + * Some implementation considerations. + * + * (a) It looks attractive to get rid of RF nodes early and transfer to safepoint-attached representation, + * but it is not correct until loop opts are done. + * + * Live ranges of values are routinely extended during loop opts. And it can break the invariant that + * all interfering safepoints contain the referent in their oop map. (If an interfering safepoint doesn't + * keep the referent alive, then it becomes possible for the referent to be prematurely GCed.) + * + * compiler/c2/TestReachabilityFence.java demonstrates a situation where a load is hoisted out of a loop thus + * extending the live range of the value it produces beyond the safepoint on loop-back edge. + * + * After loop opts are over, it becomes possible to reliably enumerate all interfering safepoints and + * to ensure that the referent is present in their oop maps. Current assumption is that after loop opts the IR graph + * is stable enough, so relative order of memory operations and safepoints is preserved and only safepoints between + * a referent and it's uses are taken into account. A more conservative analysis can be employed -- any safepoint dominated + * by a referent is treated as interfering with it -- if it turns out that the assumption doesn't hold. + * + * (b) RF nodes may interfere with Register Allocator (RA). If a safepoint is pruned during macro expansion, + * it can make some RF nodes redundant, but we don't have information about their relations anymore to detect that. + * Redundant RF node unnecessarily extends referent's live range and increases register pressure. + * + * Hence, we eliminate RF nodes and transfer their referents to corresponding safepoints (phase #2). + * When safepoints are pruned, corresponding reachability edges also go away. + * + * (c) Unfortunately, it's not straightforward to stay with safepoint-attached representation till the very end, + * because information about derived oops is attached to safepoints in a similar way. So, for now RFs are + * rematerialized at safepoints before RA (phase #3). + */ + +bool ReachabilityFenceNode::is_redundant(PhaseGVN& gvn) { + const Type* referent_t = gvn.type(referent()); + if (referent_t == TypePtr::NULL_PTR) { + return true; // no-op fence: null referent + } + if (!OptimizeReachabilityFences) { + return false; // keep reachability fence nodes intact + } + if (!PreserveReachabilityFencesOnConstants && referent_t->singleton()) { + return true; // no-op fence: constants are strongly reachable + } + return false; +} + +Node* ReachabilityFenceNode::Ideal(PhaseGVN* phase, bool can_reshape) { + return (remove_dead_region(phase, can_reshape) ? this : nullptr); +} + +Node* ReachabilityFenceNode::Identity(PhaseGVN* phase) { + if (is_redundant(*phase)) { + return in(0); + } + return this; +} + +// Turn the RF node into a no-op by setting its referent to null. +// Subsequent IGVN pass removes cleared nodes. +bool ReachabilityFenceNode::clear_referent(PhaseIterGVN& phase) { + if (phase.type(referent()) == TypePtr::NULL_PTR) { + return false; + } else { + phase.replace_input_of(this, 1, phase.makecon(TypePtr::NULL_PTR)); + return true; + } +} + +#ifndef PRODUCT +static void rf_desc(outputStream* st, const ReachabilityFenceNode* rf, PhaseRegAlloc* ra) { + char buf[50]; + ra->dump_register(rf->referent(), buf, sizeof(buf)); + st->print("reachability fence [%s]", buf); +} + +void ReachabilityFenceNode::format(PhaseRegAlloc* ra, outputStream* st) const { + rf_desc(st, this, ra); +} + +void ReachabilityFenceNode::emit(C2_MacroAssembler* masm, PhaseRegAlloc* ra) const { + ResourceMark rm; + stringStream ss; + rf_desc(&ss, this, ra); + const char* desc = masm->code_string(ss.freeze()); + masm->block_comment(desc); +} +#endif + +// Detect safepoint nodes which are important for reachability tracking purposes. +// Some SafePoint nodes can't affect referent's reachability in any noticeable way and +// can be safely ignored during the analysis. +static bool is_interfering_sfpt_candidate(SafePointNode* sfpt) { + if (sfpt->jvms() == nullptr) { + return false; // not a real safepoint + } else if (sfpt->is_CallStaticJava() && sfpt->as_CallStaticJava()->is_uncommon_trap()) { + return false; // uncommon traps are exit points + } + return true; // a full-blown safepoint can interfere with a reachability fence and its referent +} + +void PhaseIdealLoop::insert_rf(Node* ctrl, Node* referent) { + IdealLoopTree* lpt = get_loop(ctrl); + Node* ctrl_end = ctrl->unique_ctrl_out(); + + auto new_rf = new ReachabilityFenceNode(C, ctrl, referent); + + register_control(new_rf, lpt, ctrl); + set_idom(new_rf, ctrl, dom_depth(ctrl) + 1); + lpt->register_reachability_fence(new_rf); + + igvn().rehash_node_delayed(ctrl_end); + ctrl_end->replace_edge(ctrl, new_rf); + + if (idom(ctrl_end) == ctrl) { + set_idom(ctrl_end, new_rf, dom_depth(new_rf) + 1); + } else { + assert(ctrl_end->is_Region(), ""); + } +} + +void PhaseIdealLoop::replace_rf(Node* old_node, Node* new_node) { + assert(old_node->is_ReachabilityFence() || + (old_node->is_Proj() && old_node->in(0)->is_ReachabilityFence()), + "%s", NodeClassNames[old_node->Opcode()]); + + IdealLoopTree* lpt = get_loop(old_node); + lpt->_body.yank(old_node); + assert(lpt->_reachability_fences != nullptr, "missing"); + assert(lpt->_reachability_fences->contains(old_node), "missing"); + lpt->_reachability_fences->yank(old_node); + replace_node_and_forward_ctrl(old_node, new_node); +} + +void PhaseIdealLoop::remove_rf(ReachabilityFenceNode* rf) { + Node* rf_ctrl_in = rf->in(0); + Node* referent = rf->referent(); + if (rf->clear_referent(igvn()) && referent->outcnt() == 0) { + remove_dead_data_node(referent); + } + replace_rf(rf, rf_ctrl_in); +} + +//====================================================================== +//---------------------------- Phase 1 --------------------------------- +// Optimization pass over reachability fences during loop opts. +// Moves RFs with loop-invariant referents out of the loop. +bool PhaseIdealLoop::optimize_reachability_fences() { + Compile::TracePhase tp(_t_reachability_optimize); + + assert(OptimizeReachabilityFences, "required"); + + // ResourceMark rm; // NB! not safe because insert_rf may trigger _idom reallocation + Unique_Node_List redundant_rfs; + typedef Pair LoopExit; + GrowableArray worklist; + + for (int i = 0; i < C->reachability_fences_count(); i++) { + ReachabilityFenceNode* rf = C->reachability_fence(i); + assert(!rf->is_redundant(igvn()), "required"); + // Move RFs out of counted loops when possible. + IdealLoopTree* lpt = get_loop(rf); + Node* referent = rf->referent(); + if (lpt->is_invariant(referent)) { + IfFalseNode* unique_loop_exit = lpt->unique_loop_exit_proj_or_null(); + if (unique_loop_exit != nullptr) { + // Skip over an outer strip-mined loop. + if (!lpt->is_root()) { + IdealLoopTree* outer_lpt = lpt->_parent; + if (outer_lpt->head()->is_OuterStripMinedLoop()) { + if (outer_lpt->is_invariant(referent)) { + IfNode* outer_loop_end = outer_lpt->head()->as_OuterStripMinedLoop()->outer_loop_end(); + if (outer_loop_end != nullptr && outer_loop_end->false_proj_or_null() != nullptr) { + unique_loop_exit = outer_loop_end->false_proj_or_null(); + } + } else { + // An attempt to insert an RF node inside outer strip-mined loop breaks + // its IR invariants and manifests as assertion failures. + assert(false, "not loop invariant in outer strip-mined loop"); + continue; // skip + } + } + } + + LoopExit p(referent, unique_loop_exit); + worklist.push(p); + redundant_rfs.push(rf); + +#ifndef PRODUCT + if (TraceLoopOpts) { + IdealLoopTree* loop = get_loop(unique_loop_exit->in(0)); + tty->print_cr("ReachabilityFence: N%d: %s N%d/N%d -> loop exit N%d (%s N%d/N%d)", + rf->_idx, lpt->head()->Name(), lpt->head()->_idx, lpt->tail()->_idx, + unique_loop_exit->_idx, + loop->head()->Name(), loop->head()->_idx, loop->tail()->_idx); + } +#endif // !PRODUCT + } + } + } + + // Populate RFs outside loops. + while (worklist.is_nonempty()) { + LoopExit p = worklist.pop(); + Node* referent = p.first; + Node* ctrl_out = p.second; + insert_rf(ctrl_out, referent); + } + + // Eliminate redundant RFs. + bool progress = (redundant_rfs.size() > 0); + while (redundant_rfs.size() > 0) { + remove_rf(redundant_rfs.pop()->as_ReachabilityFence()); + } + + return progress; +} + +//====================================================================== +//---------------------------- Phase 2 --------------------------------- + +// Linearly traverse CFG upwards starting at ctrl_start until first merge point. +// All encountered safepoints are recorded in safepoints list, except +// the ones filtered out by is_interfering_sfpt_candidate(). +static void enumerate_interfering_sfpts_linear_traversal(Node* ctrl_start, Node_Stack& stack, VectorSet& visited, + Node_List& interfering_sfpts) { + for (Node* ctrl = ctrl_start; ctrl != nullptr; ctrl = ctrl->in(0)) { + assert(ctrl->is_CFG(), ""); + if (visited.test_set(ctrl->_idx)) { + return; + } else { + if (ctrl->is_Region()) { + stack.push(ctrl, 1); + return; // stop at merge points + } else if (ctrl->is_SafePoint() && is_interfering_sfpt_candidate(ctrl->as_SafePoint())) { + assert(!ctrl->is_CallStaticJava() || !ctrl->as_CallStaticJava()->is_uncommon_trap(), + "uncommon traps should not be enumerated"); + interfering_sfpts.push(ctrl); + } + } + } +} + +// Enumerate all safepoints which are reachable from the RF to its referent through CFG. +// Start at RF node and traverse CFG upwards until referent's control node is reached. +static void enumerate_interfering_sfpts(ReachabilityFenceNode* rf, PhaseIdealLoop* phase, + Node_Stack& stack, VectorSet& visited, + Node_List& interfering_sfpts) { + assert(stack.is_empty(), "required"); + assert(visited.is_empty(), "required"); + + Node* referent = rf->referent(); + Node* referent_ctrl = phase->get_ctrl(referent); + assert(phase->is_dominator(referent_ctrl, rf), "sanity"); + + visited.set(referent_ctrl->_idx); // end point + enumerate_interfering_sfpts_linear_traversal(rf, stack, visited, interfering_sfpts); // starting point in CFG + while (stack.is_nonempty()) { + Node* cur = stack.node(); + uint idx = stack.index(); + + assert(cur != nullptr, ""); + assert(cur->is_Region(), "%s", NodeClassNames[cur->Opcode()]); + assert(phase->is_dominator(referent_ctrl, cur), ""); + assert(idx > 0 && idx <= cur->req(), "%d %d", idx, cur->req()); + + if (idx < cur->req()) { + stack.set_index(idx + 1); + enumerate_interfering_sfpts_linear_traversal(cur->in(idx), stack, visited, interfering_sfpts); + } else { + stack.pop(); + } + } + // Reset temporary structures to their initial state. + assert(stack.is_empty(), "required"); + visited.clear(); +} + +// Start offset for reachability info on a safepoint node. +static uint rf_base_offset(SafePointNode* sfpt) { + return sfpt->jvms()->debug_end(); +} + +static bool dominates_another_rf(ReachabilityFenceNode* rf, PhaseIdealLoop* phase) { + assert(!rf->is_redundant(phase->igvn()), ""); + + for (int i = 0; i < phase->C->reachability_fences_count(); i++) { + ReachabilityFenceNode* other_rf = phase->C->reachability_fence(i); + assert(other_rf->outcnt() > 0, "dead node"); + if (rf != other_rf && rf->referent()->eqv_uncast(other_rf->referent()) && + phase->is_dominator(rf, other_rf)) { + return true; // dominates another reachability fence with the same referent + } + } + return false; +} + +// Phase 2: migrate reachability info to safepoints. +// All RFs are replaced with edges from corresponding referents to interfering safepoints. +// Interfering safepoints are safepoint nodes which are reachable from the RF to its referent through CFG. +bool PhaseIdealLoop::expand_reachability_fences() { + Compile::TracePhase tp(_t_reachability_expand); + + assert(OptimizeReachabilityFences, "required"); + assert(C->post_loop_opts_phase(), "required"); + DEBUG_ONLY( int no_of_constant_rfs = 0; ) + + ResourceMark rm; + Unique_Node_List for_removal; + typedef Pair ReachabilityEdge; + GrowableArray reachability_edges; + { + // Reuse temporary structures to avoid allocating them for every single RF node. + Node_List sfpt_worklist; + Node_Stack stack(0); + VectorSet visited; + + for (int i = 0; i < C->reachability_fences_count(); i++) { + ReachabilityFenceNode* rf = C->reachability_fence(i); + assert(!rf->is_redundant(igvn()), "missed"); + if (PreserveReachabilityFencesOnConstants) { + const Type* referent_t = igvn().type(rf->referent()); + assert(referent_t != TypePtr::NULL_PTR, "redundant rf"); + bool is_constant_rf = referent_t->singleton(); + if (is_constant_rf) { + DEBUG_ONLY( no_of_constant_rfs += 1; ) + continue; // leave RFs on constants intact + } + } + if (dominates_another_rf(rf, this)) { + // Redundant fence: dominates another RF with the same referent. + // RF is redundant for some referent oop when the referent has another RF which + // keeps it alive across the RF. In terms of dominance relation it can be formulated + // as "a referent has another RF which is dominated by the redundant RF". + // + // NB! It is safe to assume that dominating RF is redundant only during expansion. + // Otherwise, there's a chance for the superseding RF node to go away before expansion. + // Non-RF users are ignored for a similar reason: they can go away before or after expansion + // takes place, so no guarantees reachability information is preserved. + } else { + assert(sfpt_worklist.size() == 0, "not empty"); + enumerate_interfering_sfpts(rf, this, stack, visited, sfpt_worklist); + + Node* referent = rf->referent(); + while (sfpt_worklist.size() > 0) { + SafePointNode* sfpt = sfpt_worklist.pop()->as_SafePoint(); + assert(is_dominator(get_ctrl(referent), sfpt), ""); + assert(sfpt->req() == rf_base_offset(sfpt), "no extra edges allowed"); + if (sfpt->find_edge(referent) == -1) { + ReachabilityEdge p(sfpt, referent); + reachability_edges.push(p); + } + } + } + for_removal.push(rf); + } + } + // Materialize reachability edges. + while (reachability_edges.length() > 0) { + ReachabilityEdge p = reachability_edges.pop(); + SafePointNode* sfpt = p.first; + Node* referent = p.second; + if (sfpt->find_edge(referent) == -1) { + sfpt->add_req(referent); + igvn()._worklist.push(sfpt); + } + } + // Eliminate processed RFs. They become redundant once reachability edges are added. + bool progress = (for_removal.size() > 0); + while (for_removal.size() > 0) { + remove_rf(for_removal.pop()->as_ReachabilityFence()); + } + + assert(C->reachability_fences_count() == no_of_constant_rfs, ""); + return progress; +} + +//====================================================================== +//---------------------------- Phase 3 --------------------------------- + +// Find a point in CFG right after safepoint node to insert reachability fence. +static Node* sfpt_ctrl_out(SafePointNode* sfpt) { + if (sfpt->is_Call()) { + CallProjections callprojs; + sfpt->as_Call()->extract_projections(&callprojs, + false /*separate_io_proj*/, + false /*do_asserts*/, + true /*allow_handlers*/); + // Calls can have multiple control projections. However, reachability edge expansion + // happens during final graph reshaping which is performed very late in compilation pipeline. + // The assumption here is that the control path chosen for insertion can't go away. + // So, materializing a reachability fence on a single control path produced by a call + // is enough to keep the referent oop alive across the call. + if (callprojs.fallthrough_catchproj != nullptr) { + return callprojs.fallthrough_catchproj; + } else if (callprojs.catchall_catchproj != nullptr) { + return callprojs.catchall_catchproj; // rethrow stub + } else if (callprojs.fallthrough_proj != nullptr) { + return callprojs.fallthrough_proj; // no exceptions thrown + } else { + ShouldNotReachHere(); + } + } else { + return sfpt; + } +} + +// Phase 3: materialize reachability fences from reachability edges on safepoints. +// Turn extra safepoint edges into reachability fences immediately following the safepoint. +// +// NB! As of now, a special care is needed to properly enumerate reachability edges because +// there are other use cases for non-debug safepoint edges. expand_reachability_edges() runs +// after macro expansion where runtime calls during array allocation are annotated with +// valid_length_test_input as an extra edges. Until there's a mechanism to distinguish between +// different types of non-debug edges, unrelated cases are filtered out explicitly and in ad-hoc manner. +void Compile::expand_reachability_edges(Unique_Node_List& safepoints) { + for (uint i = 0; i < safepoints.size(); i++) { + SafePointNode* sfpt = safepoints.at(i)->as_SafePoint(); + + uint rf_offset = rf_base_offset(sfpt); + if (sfpt->jvms() != nullptr && sfpt->req() > rf_offset) { + assert(is_interfering_sfpt_candidate(sfpt), ""); + Node* ctrl_out = sfpt_ctrl_out(sfpt); + Node* ctrl_end = ctrl_out->unique_ctrl_out(); + + Node* extra_edge = nullptr; + if (sfpt->is_Call()) { + address entry = sfpt->as_Call()->entry_point(); + if (entry == OptoRuntime::new_array_Java() || + entry == OptoRuntime::new_array_nozero_Java()) { + // valid_length_test_input is appended during macro expansion at the very end + int last_idx = sfpt->req() - 1; + extra_edge = sfpt->in(last_idx); + sfpt->del_req(last_idx); + } + } + + while (sfpt->req() > rf_offset) { + int idx = sfpt->req() - 1; + Node* referent = sfpt->in(idx); + sfpt->del_req(idx); + + Node* new_rf = new ReachabilityFenceNode(C, ctrl_out, referent); + ctrl_end->replace_edge(ctrl_out, new_rf); + ctrl_end = new_rf; + } + + if (extra_edge != nullptr) { + sfpt->add_req(extra_edge); // Add valid_length_test_input edge back + } + } + } +} diff --git a/src/hotspot/share/opto/reachability.hpp b/src/hotspot/share/opto/reachability.hpp new file mode 100644 index 00000000000..ba435c8484f --- /dev/null +++ b/src/hotspot/share/opto/reachability.hpp @@ -0,0 +1,83 @@ +/* + * 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_OPTO_REACHABILITY_HPP +#define SHARE_OPTO_REACHABILITY_HPP + +#include "opto/multnode.hpp" +#include "opto/node.hpp" +#include "opto/opcodes.hpp" +#include "opto/type.hpp" + +//------------------------ReachabilityFenceNode-------------------------- +// Represents a Reachability Fence (RF) in the code. +// +// RF ensures that the given object (referent) remains strongly reachable regardless of +// any optimizing transformations the virtual machine may perform that might otherwise +// allow the object to become unreachable. + +// java.lang.ref.Reference::reachabilityFence calls are intrinsified into ReachabilityFence nodes. +// +// More details in reachability.cpp. +class ReachabilityFenceNode : public Node { +public: + ReachabilityFenceNode(Compile* C, Node* ctrl, Node* referent) + : Node(1) { + assert(referent->bottom_type()->isa_oopptr() || + referent->bottom_type()->isa_narrowoop() != nullptr || + referent->bottom_type() == TypePtr::NULL_PTR, + "%s", Type::str(referent->bottom_type())); + init_class_id(Class_ReachabilityFence); + init_req(TypeFunc::Control, ctrl); + add_req(referent); + C->add_reachability_fence(this); + } + virtual int Opcode() const; + virtual bool is_CFG() const { return true; } + virtual uint hash() const { return NO_HASH; } // CFG nodes do not hash + virtual bool depends_only_on_test() const { return false; }; + virtual uint ideal_reg() const { return 0; } // not matched in the AD file + virtual const Type* bottom_type() const { return Type::CONTROL; } + virtual const RegMask& in_RegMask(uint idx) const { + // Fake input register mask for the referent: accepts all registers and all stack slots. + // This avoids redundant register moves around reachability fences. + return RegMask::ALL; + } + virtual const RegMask& out_RegMask() const { + return RegMask::EMPTY; + } + + virtual Node* Ideal(PhaseGVN* phase, bool can_reshape); + virtual Node* Identity(PhaseGVN* phase); + + Node* referent() const { return in(1); } + bool is_redundant(PhaseGVN& gvn); + bool clear_referent(PhaseIterGVN& phase); + +#ifndef PRODUCT + virtual void format(PhaseRegAlloc* ra, outputStream* st) const; + virtual void emit(C2_MacroAssembler* masm, PhaseRegAlloc* ra) const; +#endif +}; + +#endif // SHARE_OPTO_REACHABILITY_HPP diff --git a/src/java.base/share/classes/java/lang/ref/Reference.java b/src/java.base/share/classes/java/lang/ref/Reference.java index 88bdb99dfd6..df46ffe6ca6 100644 --- a/src/java.base/share/classes/java/lang/ref/Reference.java +++ b/src/java.base/share/classes/java/lang/ref/Reference.java @@ -644,12 +644,9 @@ public abstract sealed class Reference<@jdk.internal.RequiresIdentity T> * {@code null}, this method has no effect. * @since 9 */ - @ForceInline + @IntrinsicCandidate public static void reachabilityFence(Object ref) { - // Does nothing. This method is annotated with @ForceInline to eliminate - // most of the overhead that using @DontInline would cause with the - // HotSpot JVM, when this fence is used in a wide variety of situations. - // HotSpot JVM retains the ref and does not GC it before a call to - // this method, because the JIT-compilers do not have GC-only safepoints. + // Does nothing. HotSpot JVM retains the ref and does not GC it before a call to this method. + // Using an intrinsic allows JIT-compilers to further optimize it while retaining the correct semantics. } } diff --git a/test/hotspot/jtreg/compiler/c2/ReachabilityFenceFlagsTest.java b/test/hotspot/jtreg/compiler/c2/ReachabilityFenceFlagsTest.java new file mode 100644 index 00000000000..4289794b278 --- /dev/null +++ b/test/hotspot/jtreg/compiler/c2/ReachabilityFenceFlagsTest.java @@ -0,0 +1,52 @@ +/* + * 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. + */ + +package compiler.c2; + +/* + * @test + * @bug 8290892 + * @summary Test diagnostic modes for Reference.reachabilityFence support + * + * @requires vm.compiler2.enabled + * + * @run main/othervm -Xcomp -XX:-TieredCompilation -XX:+UnlockDiagnosticVMOptions + * -XX:+StressReachabilityFences -XX:-OptimizeReachabilityFences -XX:-PreserveReachabilityFencesOnConstants + * compiler.c2.ReachabilityFenceFlagsTest + * + * @run main/othervm -Xcomp -XX:-TieredCompilation -XX:+UnlockDiagnosticVMOptions + * -XX:+StressReachabilityFences -XX:-OptimizeReachabilityFences -XX:+PreserveReachabilityFencesOnConstants + * compiler.c2.ReachabilityFenceFlagsTest + * + * @run main/othervm -Xcomp -XX:-TieredCompilation -XX:+UnlockDiagnosticVMOptions + * -XX:+StressReachabilityFences -XX:+OptimizeReachabilityFences -XX:-PreserveReachabilityFencesOnConstants + * compiler.c2.ReachabilityFenceFlagsTest + * + * @run main/othervm -Xcomp -XX:-TieredCompilation -XX:+UnlockDiagnosticVMOptions + * -XX:+StressReachabilityFences -XX:+OptimizeReachabilityFences -XX:+PreserveReachabilityFencesOnConstants + * compiler.c2.ReachabilityFenceFlagsTest + */ +public class ReachabilityFenceFlagsTest { + public static void main(String[] args) throws Throwable { + } +} diff --git a/test/hotspot/jtreg/compiler/c2/ReachabilityFenceOnConstantTest.java b/test/hotspot/jtreg/compiler/c2/ReachabilityFenceOnConstantTest.java new file mode 100644 index 00000000000..db8590865fb --- /dev/null +++ b/test/hotspot/jtreg/compiler/c2/ReachabilityFenceOnConstantTest.java @@ -0,0 +1,117 @@ +/* + * 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. + */ + +package compiler.c2; + +import jdk.internal.misc.Unsafe; +import jdk.internal.vm.annotation.Stable; + +import java.lang.ref.Cleaner; +import java.lang.ref.Reference; + +/* + * @test + * @bug 8290892 + * @summary Tests to ensure that reachabilityFence() correctly keeps objects from being collected prematurely. + * @modules java.base/jdk.internal.misc + * java.base/jdk.internal.vm.annotation + * @run main/bootclasspath/othervm -Xbatch -XX:-TieredCompilation -XX:CompileCommand=quiet + * -XX:CompileCommand=compileonly,*::test + * -XX:+UnlockDiagnosticVMOptions -XX:+PreserveReachabilityFencesOnConstants + * compiler.c2.ReachabilityFenceOnConstantTest + */ +public class ReachabilityFenceOnConstantTest { + static final Unsafe U = Unsafe.getUnsafe(); + + static final long BUFFER_SIZE = 1024; + static @Stable MyBuffer BUFFER = new MyBuffer(); + + static volatile boolean isCleaned = false; + + static class MyBuffer { + final @Stable long address; + final @Stable long limit; + + MyBuffer() { + final long adr = U.allocateMemory(BUFFER_SIZE); + U.setMemory(adr, BUFFER_SIZE, (byte)0); + address = adr; + limit = BUFFER_SIZE; + System.out.printf("Allocated memory (%d bytes): 0x%016x\n", BUFFER_SIZE, adr); + Cleaner.create().register(this, () -> { + System.out.printf("Freed memory (%d bytes): 0x%016x\n", BUFFER_SIZE, adr); + U.setMemory(adr, BUFFER_SIZE, (byte)-1); // clear + U.freeMemory(adr); + isCleaned = true; + }); + } + + byte getByte(long offset) { + return U.getByte(null, address + offset); + } + } + + static int test() { + int acc = 0; + MyBuffer buf = BUFFER; + try { + for (long i = 0; i < buf.limit; i++) { + acc += buf.getByte(i); + } + } finally { + Reference.reachabilityFence(buf); + } + return acc; + } + + static void runTest() { + for (int i = 0; i < 20_000; i++) { + if (test() != 0) { + throw new AssertionError("observed stale buffer: TestConstantOop::isCleaned=" + isCleaned); + } + } + } + + public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException { + runTest(); // run test() and constant fold accesses to BUFFER (and it's state) during JIT-compilation + + BUFFER = null; // remove last strong root + + // Ensure the instance is GCed. + while (!isCleaned) { + try { + System.gc(); + Thread.sleep(100); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + try { + runTest(); // repeat to ensure stale BUFFER contents is not accessed + } catch (NullPointerException e) { + // expected; ignore + } + System.out.println("TEST PASSED"); + } +} diff --git a/test/hotspot/jtreg/compiler/c2/ReachabilityFenceTest.java b/test/hotspot/jtreg/compiler/c2/ReachabilityFenceTest.java new file mode 100644 index 00000000000..d0bce024696 --- /dev/null +++ b/test/hotspot/jtreg/compiler/c2/ReachabilityFenceTest.java @@ -0,0 +1,353 @@ +/* + * 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. + */ + +package compiler.c2; + +import java.lang.ref.Cleaner; +import java.lang.ref.Reference; +import java.util.Arrays; +import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; +import jdk.internal.misc.Unsafe; + +import compiler.lib.ir_framework.*; + +/* + * @test + * @bug 8290892 + * @summary Tests to ensure that reachabilityFence() correctly keeps objects from being collected prematurely. + * @modules java.base/jdk.internal.misc + * @library /test/lib / + * @run main/othervm -Xbatch compiler.c2.ReachabilityFenceTest + */ +public class ReachabilityFenceTest { + private static final int SIZE = 100; + + static final boolean[] STATUS = new boolean[2]; + + interface MyBuffer { + byte get(int offset); + } + static class MyBufferOnHeap implements MyBuffer { + + private static int current = 0; + private final static byte[][] payload = new byte[10][]; + + private final int id; + + public MyBufferOnHeap() { + // Get a unique id, allocate memory, and save the address in the payload array. + id = current++; + payload[id] = new byte[SIZE]; + + // Initialize buffer + for (int i = 0; i < SIZE; ++i) { + put(i, (byte) 42); + } + + // Register a cleaner to free the memory when the buffer is garbage collected. + int lid = id; // Capture current value + Cleaner.create().register(this, () -> { free(lid); }); + + System.out.println("Created new buffer of size = " + SIZE + " with id = " + id); + } + + private static void free(int id) { + System.out.println("Freeing buffer with id = " + id); + for (int i = 0; i < SIZE; ++i) { + payload[id][i] = (byte)0; + } + payload[id] = null; + + synchronized (STATUS) { + STATUS[0] = true; + STATUS.notifyAll(); + } + } + + public void put(int offset, byte b) { + payload[id][offset] = b; + } + + public byte get(int offset) { + try { + return payload[id][offset]; + } finally { + Reference.reachabilityFence(this); + } + } + } + + static class MyBufferOffHeap implements MyBuffer { + private static Unsafe UNSAFE = Unsafe.getUnsafe(); + + private static int current = 0; + private static long payload[] = new long[10]; + + private final int id; + + public MyBufferOffHeap() { + // Get a unique id, allocate memory, and save the address in the payload array. + id = current++; + payload[id] = UNSAFE.allocateMemory(SIZE); + + // Initialize buffer + for (int i = 0; i < SIZE; ++i) { + put(i, (byte) 42); + } + + // Register a cleaner to free the memory when the buffer is garbage collected + int lid = id; // Capture current value + Cleaner.create().register(this, () -> { free(lid); }); + + System.out.println("Created new buffer of size = " + SIZE + " with id = " + id); + } + + private static void free(int id) { + System.out.println("Freeing buffer with id = " + id); + for (int i = 0; i < SIZE; ++i) { + UNSAFE.putByte(payload[id] + i, (byte)0); + } + // UNSAFE.freeMemory(payload[id]); // don't deallocate backing memory to avoid crashes + payload[id] = 0; + + synchronized (STATUS) { + STATUS[1] = true; + STATUS.notifyAll(); + } + } + + public void put(int offset, byte b) { + UNSAFE.putByte(payload[id] + offset, b); + } + + public byte get(int offset) { + try { + return UNSAFE.getByte(payload[id] + offset); + } finally { + Reference.reachabilityFence(this); + } + } + } + + static MyBufferOffHeap bufferOff = new MyBufferOffHeap(); + static MyBufferOnHeap bufferOn = new MyBufferOnHeap(); + + static long[] counters = new long[4]; + + @ForceInline + static boolean test(MyBuffer buf) { + if (buf == null) { + return false; + } + for (int i = 0; i < SIZE; i++) { + // The access is split into base address load (payload[id]), offset computation, and data load. + // While offset is loop-variant, payload[id] is not and can be hoisted. + // If bufferOff and payload[id] loads are hoisted outside outermost loop, it eliminates all usages of + // myBuffer oop inside the loop and bufferOff can be GCed at the safepoint on outermost loop back branch. + byte b = buf.get(i); // inlined + if (b != 42) { + String msg = "Unexpected value = " + b + ". Buffer was garbage collected before reachabilityFence was reached!"; + throw new AssertionError(msg); + } + } + return true; + } + + /* ===================================== Off-heap versions ===================================== */ + + @Test + @Arguments(values = {Argument.NUMBER_42}) + @IR(counts = {IRNode.REACHABILITY_FENCE, "1"}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.REACHABILITY_FENCE, ">=1"}, phase = CompilePhase.AFTER_LOOP_OPTS) + @IR(counts = {IRNode.REACHABILITY_FENCE, "0"}, phase = CompilePhase.EXPAND_REACHABILITY_FENCES) + @IR(counts = {IRNode.REACHABILITY_FENCE, "1"}, phase = CompilePhase.FINAL_CODE) + static long testOffHeap1(int limit) { + for (long j = 0; j < limit; j++) { + MyBufferOffHeap myBuffer = bufferOff; // local + if (!test(myBuffer)) { + return j; + } + counters[0] = j; + } // safepoint on loop backedge does NOT contain myBuffer local as part of its JVM state + return limit; + } + + @Test + @Arguments(values = {Argument.NUMBER_42}) + @IR(counts = {IRNode.REACHABILITY_FENCE, "2"}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.REACHABILITY_FENCE, ">=2"}, phase = CompilePhase.AFTER_LOOP_OPTS) + @IR(counts = {IRNode.REACHABILITY_FENCE, "0"}, phase = CompilePhase.EXPAND_REACHABILITY_FENCES) + @IR(counts = {IRNode.REACHABILITY_FENCE, "1"}, phase = CompilePhase.FINAL_CODE) + // Both RF nodes share the same referent and there's a single safepoint on loop-back edge. + // That's the reason why there are 2 RF nodes after parsing, but 1 RF node at the end. + static long testOffHeap2(int limit) { + for (long j = 0; j < limit; j++) { + MyBufferOffHeap myBuffer = bufferOff; // local + try { + if (!test(myBuffer)) { + return j; + } + counters[1] = j; + } finally { + Reference.reachabilityFence(myBuffer); + } + } // safepoint on loop-back edge does NOT contain myBuffer local as part of its JVM state + return limit; + } + + /* ===================================== On-heap versions ===================================== */ + + @Test + @Arguments(values = {Argument.NUMBER_42}) + @IR(counts = {IRNode.REACHABILITY_FENCE, "1"}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.REACHABILITY_FENCE, ">=1"}, phase = CompilePhase.AFTER_LOOP_OPTS) + @IR(counts = {IRNode.REACHABILITY_FENCE, "0"}, phase = CompilePhase.EXPAND_REACHABILITY_FENCES) + @IR(counts = {IRNode.REACHABILITY_FENCE, "1"}, phase = CompilePhase.FINAL_CODE) + static long testOnHeap1(int limit) { + for (long j = 0; j < limit; j++) { + MyBufferOnHeap myBuffer = bufferOn; // local + if (!test(myBuffer)) { + return j; + } + counters[2] = j; + } // safepoint on loop backedge does NOT contain myBuffer local as part of its JVM state + return limit; + } + + @Test + @Arguments(values = {Argument.NUMBER_42}) + @IR(counts = {IRNode.REACHABILITY_FENCE, "2"}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.REACHABILITY_FENCE, ">=2"}, phase = CompilePhase.AFTER_LOOP_OPTS) + @IR(counts = {IRNode.REACHABILITY_FENCE, "0"}, phase = CompilePhase.EXPAND_REACHABILITY_FENCES) + @IR(counts = {IRNode.REACHABILITY_FENCE, "1"}, phase = CompilePhase.FINAL_CODE) + static long testOnHeap2(int limit) { + for (long j = 0; j < limit; j++) { + MyBufferOnHeap myBuffer = bufferOn; // local + try { + if (!test(myBuffer)) { + return j; + } + counters[3] = j; + } finally { + Reference.reachabilityFence(myBuffer); + } + } // safepoint on loop backedge does NOT contain myBuffer local as part of its JVM state + return limit; + } + + /* ===================================== Helper methods ===================================== */ + + static void runJavaTestCases() throws Throwable { + // Warmup to trigger compilations. + for (int i = 0; i < 10_000; i++) { + testOffHeap1(10); + testOffHeap2(10); + testOnHeap1(10); + testOnHeap2(10); + } + + @SuppressWarnings("unchecked") + Callable[] tasks = new Callable[] { + () -> testOffHeap1(100_000_000), + () -> testOffHeap2(100_000_000), + () -> testOnHeap1(100_000_000), + () -> testOnHeap2(100_000_000), + }; + int taskCount = tasks.length; + CountDownLatch latch = new CountDownLatch(taskCount + 1); + final Thread[] workers = new Thread[taskCount]; + final Throwable[] result = new Throwable[taskCount]; + + for (int i = 0; i < taskCount; i++) { + final int id = i; + workers[id] = new Thread(() -> { + latch.countDown(); // synchronize with main thread + try { + System.out.printf("Computation thread #%d has started\n", id); + long cnt = tasks[id].call(); + System.out.printf("#%d Finished after %d iterations\n", id, cnt); + } catch (Throwable e) { + System.out.printf("#%d Finished with an exception %s\n", id, e); + result[id] = e; + } + }); + } + + for (Thread worker : workers) { + worker.start(); + } + + latch.countDown(); // synchronize with worker threads + + Thread.sleep(100); // let workers proceed + + // Clear references to buffers and make sure it's garbage collected. + System.out.printf("Buffers set to null. Waiting for garbage collection. (counters = %s)\n", Arrays.toString(counters)); + bufferOn = null; + bufferOff = null; + + System.gc(); + + synchronized (STATUS) { + do { + if (STATUS[0] && STATUS[1]) { + break; + } else { + System.out.printf("Waiting for cleanup... (counters = %s)\n", Arrays.toString(counters)); + System.gc(); + STATUS.wait(100); + } + } while (true); + } + + for (Thread worker : workers) { + worker.join(); + } + + System.out.printf("Results: %s\n", Arrays.deepToString(result)); + + for (Throwable e : result) { + if (e != null) { + throw e; + } + } + } + + static void runIRTestCases() { + TestFramework framework = new TestFramework(); + framework.addFlags("--add-exports", "java.base/jdk.internal.misc=ALL-UNNAMED"); + framework.start(); + } + + public static void main(String[] args) throws Throwable { + try { + runIRTestCases(); + runJavaTestCases(); + System.out.println("TEST PASSED"); + } catch (Throwable e) { + System.out.println("TEST FAILED"); + throw e; + } + } +} diff --git a/test/hotspot/jtreg/compiler/c2/irTests/ReachabilityFenceTest.java b/test/hotspot/jtreg/compiler/c2/irTests/ReachabilityFenceTest.java new file mode 100644 index 00000000000..7f4142c18d3 --- /dev/null +++ b/test/hotspot/jtreg/compiler/c2/irTests/ReachabilityFenceTest.java @@ -0,0 +1,125 @@ +/* + * 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. + */ + +package compiler.c2.irTests; + +import java.lang.ref.Cleaner; +import java.lang.ref.Reference; + +import compiler.lib.ir_framework.*; +import jdk.internal.misc.Unsafe; + +/* + * @test + * @bug 8290892 + * @summary IR tests on reachability fence-related loop transformations. + * @modules java.base/jdk.internal.misc + * @library /test/lib / + * @run driver compiler.c2.irTests.ReachabilityFenceTest + */ +public class ReachabilityFenceTest { + private static int SIZE = 100; + + /* ===================================== On-heap version ===================================== */ + + private static int[] a = new int[SIZE]; + private static int[] b = new int[SIZE]; + private static int[] c = new int[SIZE]; + + @Test + @IR(counts = {IRNode.REACHABILITY_FENCE, "3"}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.REACHABILITY_FENCE, "3"}, phase = CompilePhase.AFTER_LOOP_OPTS) + @IR(counts = {IRNode.REACHABILITY_FENCE, "0"}, phase = CompilePhase.EXPAND_REACHABILITY_FENCES) + @IR(counts = {IRNode.REACHABILITY_FENCE, "3"}, phase = CompilePhase.FINAL_CODE) + static void testCountedLoopInt() { + for (int i = 0; i < a.length; i++) { + try { + c[i] = a[i] + b[i]; + } finally { + Reference.reachabilityFence(a); + Reference.reachabilityFence(b); + Reference.reachabilityFence(c); + } + } + } + + /* ===================================== Off-heap version ===================================== */ + + static class OffHeapBuffer { + private static Unsafe UNSAFE = Unsafe.getUnsafe(); + + private final long payload; + + public final long size; + + OffHeapBuffer() { + size = SIZE; + payload = UNSAFE.allocateMemory(SIZE); + + Cleaner.create().register(this, () -> { + UNSAFE.freeMemory(payload); + }); + } + + public void put(long offset, byte b) { + try { + UNSAFE.putByte(payload + offset, b); + } finally { + Reference.reachabilityFence(this); + } + } + + public byte get(long offset) { + try { + return UNSAFE.getByte(payload + offset); + } finally { + Reference.reachabilityFence(this); + } + } + } + + private static OffHeapBuffer bufA = new OffHeapBuffer(); + private static OffHeapBuffer bufB = new OffHeapBuffer(); + private static OffHeapBuffer bufC = new OffHeapBuffer(); + + @Test + @IR(counts = {IRNode.REACHABILITY_FENCE, "3"}, phase = CompilePhase.AFTER_PARSING) + @IR(counts = {IRNode.REACHABILITY_FENCE, "3"}, phase = CompilePhase.AFTER_LOOP_OPTS) + @IR(counts = {IRNode.REACHABILITY_FENCE, "0"}, phase = CompilePhase.EXPAND_REACHABILITY_FENCES) + @IR(counts = {IRNode.REACHABILITY_FENCE, "3"}, phase = CompilePhase.FINAL_CODE) + static void testCountedLoopLong() { + for (long i = 0; i < bufA.size; i++) { + byte a = bufA.get(i); // +1 RF + byte b = bufB.get(i); // +1 RF + bufC.put(i, (byte)(a+b)); // +1 RF + } + } + + /* ============================================================================================ */ + + public static void main(String[] args) throws Throwable { + TestFramework framework = new TestFramework(); + framework.addFlags("--add-exports", "java.base/jdk.internal.misc=ALL-UNNAMED"); + framework.start(); + } +} diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/CompilePhase.java b/test/hotspot/jtreg/compiler/lib/ir_framework/CompilePhase.java index 1e3ba7c314e..43178f41185 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/CompilePhase.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/CompilePhase.java @@ -114,6 +114,7 @@ public enum CompilePhase { PHASEIDEALLOOP1( "PhaseIdealLoop 1"), PHASEIDEALLOOP2( "PhaseIdealLoop 2"), PHASEIDEALLOOP3( "PhaseIdealLoop 3"), + EXPAND_REACHABILITY_FENCES( "Expand Reachability Fences"), AUTO_VECTORIZATION1_BEFORE_APPLY( "AutoVectorization 1, before Apply"), AUTO_VECTORIZATION3_AFTER_ADJUST_LIMIT( "AutoVectorization 2, after Adjusting Pre-loop Limit"), AUTO_VECTORIZATION4_AFTER_SPECULATIVE_RUNTIME_CHECKS("AutoVectorization 3, after Adding Speculative Runtime Checks"), diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java index 0e1accf8a50..3f742a0ce62 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java @@ -3130,6 +3130,11 @@ public class IRNode { fromBeforeRemoveUselessToFinalCode(BLACKHOLE, "Blackhole"); } + public static final String REACHABILITY_FENCE = PREFIX + "REACHABILITY_FENCE" + POSTFIX; + static { + fromBeforeRemoveUselessToFinalCode(REACHABILITY_FENCE, "ReachabilityFence"); + } + public static final String SELECT_FROM_TWO_VECTOR_VB = VECTOR_PREFIX + "SELECT_FROM_TWO_VECTOR_VB" + POSTFIX; static { vectorNode(SELECT_FROM_TWO_VECTOR_VB, "SelectFromTwoVector", TYPE_BYTE); From 531dad0cd7df3fa4e6a06d5727677f881a8d3721 Mon Sep 17 00:00:00 2001 From: Mark Powers Date: Mon, 13 Apr 2026 20:37:18 +0000 Subject: [PATCH 033/108] 8369917: LMS/HSS RFC 9858 Support Reviewed-by: weijun --- .../sun/security/provider/DigestBase.java | 19 +- .../classes/sun/security/provider/HSS.java | 362 +++++++++++++----- .../classes/sun/security/provider/SHA2.java | 3 +- .../classes/sun/security/provider/SHA3.java | 24 +- .../classes/sun/security/util/KeyUtil.java | 7 +- .../sun/security/provider/hss/TestHSS.java | 135 +++++-- 6 files changed, 408 insertions(+), 142 deletions(-) diff --git a/src/java.base/share/classes/sun/security/provider/DigestBase.java b/src/java.base/share/classes/sun/security/provider/DigestBase.java index 2aaf0a2fac6..0bb15ef3efe 100644 --- a/src/java.base/share/classes/sun/security/provider/DigestBase.java +++ b/src/java.base/share/classes/sun/security/provider/DigestBase.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2024, 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 @@ -242,4 +242,21 @@ abstract class DigestBase extends MessageDigestSpi implements Cloneable { padding = new byte[136]; padding[0] = (byte)0x80; } + + /** + * Digest block-length bytes in a single operation. + * Subclasses are expected to override this method. It is intended + * for fixed-length short input where input includes padding bytes. + * @param input byte array to be digested + * @param inLen the length of the input + * @param output the output buffer + * @param outOffset the offset into output buffer where digest should be written + * @param outLen the length of the output buffer + * @throws UnsupportedOperationException if a subclass does not override this method + */ + void implDigestFixedLengthPreprocessed ( + byte[] input, int inLen, byte[] output, int outOffset, int outLen) + throws UnsupportedOperationException { + throw new UnsupportedOperationException("should not be here"); + } } diff --git a/src/java.base/share/classes/sun/security/provider/HSS.java b/src/java.base/share/classes/sun/security/provider/HSS.java index c1cb5ed6a30..50afba7cab8 100644 --- a/src/java.base/share/classes/sun/security/provider/HSS.java +++ b/src/java.base/share/classes/sun/security/provider/HSS.java @@ -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 @@ -24,16 +24,22 @@ */ package sun.security.provider; +import java.io.ByteArrayOutputStream; +import java.io.InvalidObjectException; +import java.io.Serial; +import java.io.Serializable; +import java.security.SecureRandom; +import java.security.*; +import java.security.spec.AlgorithmParameterSpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.KeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.util.Arrays; + import sun.security.util.*; import sun.security.x509.AlgorithmId; import sun.security.x509.X509Key; -import java.io.*; -import java.security.*; -import java.security.SecureRandom; -import java.security.spec.*; -import java.util.Arrays; - /** * Implementation of the Hierarchical Signature System using the * Leighton-Micali Signatures (HSS/LMS) as described in RFC 8554 and @@ -196,42 +202,94 @@ public final class HSS extends SignatureSpi { static class LMSUtils { static final int LMS_RESERVED = 0; - static final int LMS_SHA256_M32_H5 = 5; - static final int LMS_SHA256_M32_H10 = 6; - static final int LMS_SHA256_M32_H15 = 7; - static final int LMS_SHA256_M32_H20 = 8; - static final int LMS_SHA256_M32_H25 = 9; + static final int LMS_SHA256_M32_H5 = 0x05; + static final int LMS_SHA256_M32_H10 = 0x06; + static final int LMS_SHA256_M32_H15 = 0x07; + static final int LMS_SHA256_M32_H20 = 0x08; + static final int LMS_SHA256_M32_H25 = 0x09; + static final int LMS_SHA256_M24_H5 = 0x0a; + static final int LMS_SHA256_M24_H10 = 0x0b; + static final int LMS_SHA256_M24_H15 = 0x0c; + static final int LMS_SHA256_M24_H20 = 0x0d; + static final int LMS_SHA256_M24_H25 = 0x0e; + static final int LMS_SHAKE_M32_H5 = 0x0f; + static final int LMS_SHAKE_M32_H10 = 0x10; + static final int LMS_SHAKE_M32_H15 = 0x11; + static final int LMS_SHAKE_M32_H20 = 0x12; + static final int LMS_SHAKE_M32_H25 = 0x13; + static final int LMS_SHAKE_M24_H5 = 0x14; + static final int LMS_SHAKE_M24_H10 = 0x15; + static final int LMS_SHAKE_M24_H15 = 0x16; + static final int LMS_SHAKE_M24_H20 = 0x17; + static final int LMS_SHAKE_M24_H25 = 0x18; static String lmsType(int type) { - String typeStr; - switch (type) { - case LMS_RESERVED: typeStr = "LMS_RESERVED"; break; - case LMS_SHA256_M32_H5: typeStr = "LMS_SHA256_M32_H5"; break; - case LMS_SHA256_M32_H10: typeStr = "LMS_SHA256_M32_H10"; break; - case LMS_SHA256_M32_H15: typeStr = "LMS_SHA256_M32_H15"; break; - case LMS_SHA256_M32_H20: typeStr = "LMS_SHA256_M32_H20"; break; - case LMS_SHA256_M32_H25: typeStr = "LMS_SHA256_M32_H25"; break; - default: typeStr = "unrecognized"; - } + String typeStr = switch (type) { + case LMS_RESERVED -> "LMS_RESERVED"; + case LMS_SHA256_M32_H5 -> "LMS_SHA256_M32_H5"; + case LMS_SHA256_M32_H10 -> "LMS_SHA256_M32_H10"; + case LMS_SHA256_M32_H15 -> "LMS_SHA256_M32_H15"; + case LMS_SHA256_M32_H20 -> "LMS_SHA256_M32_H20"; + case LMS_SHA256_M32_H25 -> "LMS_SHA256_M32_H25"; + case LMS_SHA256_M24_H5 -> "LMS_SHA256_M24_H5"; + case LMS_SHA256_M24_H10 -> "LMS_SHA256_M24_H10"; + case LMS_SHA256_M24_H15 -> "LMS_SHA256_M24_H15"; + case LMS_SHA256_M24_H20 -> "LMS_SHA256_M24_H20"; + case LMS_SHA256_M24_H25 -> "LMS_SHA256_M24_H25"; + case LMS_SHAKE_M32_H5 -> "LMS_SHAKE_M32_H5"; + case LMS_SHAKE_M32_H10 -> "LMS_SHAKE_M32_H10"; + case LMS_SHAKE_M32_H15 -> "LMS_SHAKE_M32_H15"; + case LMS_SHAKE_M32_H20 -> "LMS_SHAKE_M32_H20"; + case LMS_SHAKE_M32_H25 -> "LMS_SHAKE_M32_H25"; + case LMS_SHAKE_M24_H5 -> "LMS_SHAKE_M24_H5"; + case LMS_SHAKE_M24_H10 -> "LMS_SHAKE_M24_H10"; + case LMS_SHAKE_M24_H15 -> "LMS_SHAKE_M24_H15"; + case LMS_SHAKE_M24_H20 -> "LMS_SHAKE_M24_H20"; + case LMS_SHAKE_M24_H25 -> "LMS_SHAKE_M24_H25"; + default -> "unrecognized"; + }; return typeStr; } static final int LMOTS_RESERVED = 0; - static final int LMOTS_SHA256_N32_W1 = 1; - static final int LMOTS_SHA256_N32_W2 = 2; - static final int LMOTS_SHA256_N32_W4 = 3; - static final int LMOTS_SHA256_N32_W8 = 4; + static final int LMOTS_SHA256_N32_W1 = 0x01; + static final int LMOTS_SHA256_N32_W2 = 0x02; + static final int LMOTS_SHA256_N32_W4 = 0x03; + static final int LMOTS_SHA256_N32_W8 = 0x04; + static final int LMOTS_SHA256_N24_W1 = 0x05; + static final int LMOTS_SHA256_N24_W2 = 0x06; + static final int LMOTS_SHA256_N24_W4 = 0x07; + static final int LMOTS_SHA256_N24_W8 = 0x08; + static final int LMOTS_SHAKE_N32_W1 = 0x09; + static final int LMOTS_SHAKE_N32_W2 = 0x0a; + static final int LMOTS_SHAKE_N32_W4 = 0x0b; + static final int LMOTS_SHAKE_N32_W8 = 0x0c; + static final int LMOTS_SHAKE_N24_W1 = 0x0d; + static final int LMOTS_SHAKE_N24_W2 = 0x0e; + static final int LMOTS_SHAKE_N24_W4 = 0x0f; + static final int LMOTS_SHAKE_N24_W8 = 0x10; static String lmotsType(int type) { - String typeStr; - switch (type) { - case LMOTS_RESERVED: typeStr = "LMOTS_RESERVED"; break; - case LMOTS_SHA256_N32_W1: typeStr = "LMOTS_SHA256_N32_W1"; break; - case LMOTS_SHA256_N32_W2: typeStr = "LMOTS_SHA256_N32_W2"; break; - case LMOTS_SHA256_N32_W4: typeStr = "LMOTS_SHA256_N32_W4"; break; - case LMOTS_SHA256_N32_W8: typeStr = "LMOTS_SHA256_N32_W8"; break; - default: typeStr = "unrecognized"; - } + String typeStr = switch (type) { + case LMOTS_RESERVED -> "LMOTS_RESERVED"; + case LMOTS_SHA256_N32_W1 -> "LMOTS_SHA256_N32_W1"; + case LMOTS_SHA256_N32_W2 -> "LMOTS_SHA256_N32_W2"; + case LMOTS_SHA256_N32_W4 -> "LMOTS_SHA256_N32_W4"; + case LMOTS_SHA256_N32_W8 -> "LMOTS_SHA256_N32_W8"; + case LMOTS_SHA256_N24_W1 -> "LMOTS_SHA256_N24_W1"; + case LMOTS_SHA256_N24_W2 -> "LMOTS_SHA256_N24_W2"; + case LMOTS_SHA256_N24_W4 -> "LMOTS_SHA256_N24_W4"; + case LMOTS_SHA256_N24_W8 -> "LMOTS_SHA256_N24_W8"; + case LMOTS_SHAKE_N32_W1 -> "LMOTS_SHAKE_N32_W1"; + case LMOTS_SHAKE_N32_W2 -> "LMOTS_SHAKE_N32_W2"; + case LMOTS_SHAKE_N32_W4 -> "LMOTS_SHAKE_N32_W4"; + case LMOTS_SHAKE_N32_W8 -> "LMOTS_SHAKE_N32_W8"; + case LMOTS_SHAKE_N24_W1 -> "LMOTS_SHAKE_N24_W1"; + case LMOTS_SHAKE_N24_W2 -> "LMOTS_SHAKE_N24_W2"; + case LMOTS_SHAKE_N24_W4 -> "LMOTS_SHAKE_N24_W4"; + case LMOTS_SHAKE_N24_W8 -> "LMOTS_SHAKE_N24_W8"; + default -> "unrecognized"; + }; return typeStr; } @@ -352,53 +410,65 @@ public final class HSS extends SignatureSpi { static class LMSParams { final int m; // the number of bytes used from the hash output - final int hashAlg_m = 32; // output length of the LMS tree hash function + final int hashAlg_m; // output length of the LMS tree hash function final int h; // height of the LMS tree final int twoPowh; final String hashAlgStr; - LMSParams(int m, int h, String hashAlgStr) { + private LMSParams(int m, int h, String hashAlgStr, int hashAlg_m) { this.m = m; this.h = h; this.hashAlgStr = hashAlgStr; + this.hashAlg_m = hashAlg_m; twoPowh = 1 << h; } static LMSParams of(int type) { - int m; - int h; - String hashAlgStr; - switch (type) { - case LMSUtils.LMS_SHA256_M32_H5: - m = 32; - h = 5; - hashAlgStr = "SHA-256"; - break; - case LMSUtils.LMS_SHA256_M32_H10: - m = 32; - h = 10; - hashAlgStr = "SHA-256"; - break; - case LMSUtils.LMS_SHA256_M32_H15: - m = 32; - h = 15; - hashAlgStr = "SHA-256"; - break; - case LMSUtils.LMS_SHA256_M32_H20: - m = 32; - h = 20; - hashAlgStr = "SHA-256"; - break; - case LMSUtils.LMS_SHA256_M32_H25: - m = 32; - h = 25; - hashAlgStr = "SHA-256"; - break; - default: + LMSParams params = switch (type) { + case LMSUtils.LMS_SHA256_M32_H5 -> + new LMSParams(32, 5, "SHA-256", 32); + case LMSUtils.LMS_SHA256_M32_H10 -> + new LMSParams(32, 10, "SHA-256", 32); + case LMSUtils.LMS_SHA256_M32_H15 -> + new LMSParams(32, 15, "SHA-256", 32); + case LMSUtils.LMS_SHA256_M32_H20 -> + new LMSParams(32, 20, "SHA-256", 32); + case LMSUtils.LMS_SHA256_M32_H25 -> + new LMSParams(32, 25, "SHA-256", 32); + case LMSUtils.LMS_SHA256_M24_H5 -> + new LMSParams(24, 5, "SHA-256", 32); + case LMSUtils.LMS_SHA256_M24_H10 -> + new LMSParams(24, 10, "SHA-256", 32); + case LMSUtils.LMS_SHA256_M24_H15 -> + new LMSParams(24, 15, "SHA-256", 32); + case LMSUtils.LMS_SHA256_M24_H20 -> + new LMSParams(24, 20, "SHA-256", 32); + case LMSUtils.LMS_SHA256_M24_H25 -> + new LMSParams(24, 25, "SHA-256", 32); + case LMSUtils.LMS_SHAKE_M32_H5 -> + new LMSParams(32, 5, "SHAKE256-512", 64); + case LMSUtils.LMS_SHAKE_M32_H10 -> + new LMSParams(32, 10, "SHAKE256-512", 64); + case LMSUtils.LMS_SHAKE_M32_H15 -> + new LMSParams(32, 15, "SHAKE256-512", 64); + case LMSUtils.LMS_SHAKE_M32_H20 -> + new LMSParams(32, 20, "SHAKE256-512", 64); + case LMSUtils.LMS_SHAKE_M32_H25 -> + new LMSParams(32, 25, "SHAKE256-512", 64); + case LMSUtils.LMS_SHAKE_M24_H5 -> + new LMSParams(24, 5, "SHAKE256-512", 64); + case LMSUtils.LMS_SHAKE_M24_H10 -> + new LMSParams(24, 10, "SHAKE256-512", 64); + case LMSUtils.LMS_SHAKE_M24_H15 -> + new LMSParams(24, 15, "SHAKE256-512", 64); + case LMSUtils.LMS_SHAKE_M24_H20 -> + new LMSParams(24, 20, "SHAKE256-512", 64); + case LMSUtils.LMS_SHAKE_M24_H25 -> + new LMSParams(24, 25, "SHAKE256-512", 64); + default -> throw new IllegalArgumentException("Unsupported or bad LMS type"); - } - - return new LMSParams(m, h, hashAlgStr); + }; + return params; } boolean hasSameHash(LMSParams other) { @@ -495,7 +565,7 @@ public final class HSS extends SignatureSpi { static class LMOTSParams { final int lmotSigType; final int n; // the number of bytes used from the hash output - final int hashAlg_n = 32; // the output length of the hash function + int hashAlg_n; // the output length of the hash function final int w; final int twoPowWMinus1; final int ls; @@ -511,6 +581,7 @@ public final class HSS extends SignatureSpi { // back into the buffer. This way, we avoid memory allocations and some // computations that would have to be done otherwise. final byte[] hashBuf; + // Precomputed block for SHA256 when the message size is 55 bytes // (i.e. when SHA256 is used) private static final byte[] hashbufSha256_32 = { @@ -523,10 +594,64 @@ public final class HSS extends SignatureSpi { 0, 0, 0, 0, 0, 0, 0, (byte) 0x80, 0, 0, 0, 0, 0, 0, 1, (byte) 0xb8 }; + // Precomputed block for SHA256 when the message size is 47 bytes + // (i.e. when SHA256-192 is used) + private static final byte[] hashbufSha256_24 = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, (byte) 0x80, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 0x78 + }; + // Precomputed block for SHAKE256 when the message size is 55 bytes + // (i.e. when SHAKE256 is used) + private static final byte[] hashbufShake256_32 = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, (byte) 0x1F, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, (byte) 0x80 + }; + // Precomputed block for SHAKE256 when the message size is 47 bytes + // (i.e. when SHAKE256-192 is used) + private static final byte[] hashbufShake256_24 = { + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, (byte) 0x1F, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, (byte) 0x80 + }; private LMOTSParams( int lmotSigType, int hLen, int w, - int ls, int p, String hashAlgName) { + int ls, int p, String hashAlgName, int hashAlg_n) { this.lmotSigType = lmotSigType; this.n = hLen; this.w = w; @@ -534,32 +659,60 @@ public final class HSS extends SignatureSpi { this.p = p; twoPowWMinus1 = (1 << w) - 1; this.hashAlgName = hashAlgName; - hashBuf = hashbufSha256_32; + this.hashAlg_n = hashAlg_n; + hashBuf = switch (hashAlgName) { + case "SHAKE256-512" -> { + yield this.n == 24 ? + hashbufShake256_24 : hashbufShake256_32; + } + case "SHA-256" -> { + yield this.n == 24 ? + hashbufSha256_24 : hashbufSha256_32; + } + default -> + throw new IllegalArgumentException( + "Unknown hash algorithm "+hashAlgName); + }; } static LMOTSParams of(int lmotsType) { - LMOTSParams params; - switch (lmotsType) { - case LMSUtils.LMOTS_SHA256_N32_W1: - params = new LMOTSParams( - lmotsType, 32, 1, 7, 265, "SHA-256"); - break; - case LMSUtils.LMOTS_SHA256_N32_W2: - params = new LMOTSParams( - lmotsType, 32, 2, 6, 133, "SHA-256"); - break; - case LMSUtils.LMOTS_SHA256_N32_W4: - params = new LMOTSParams( - lmotsType, 32, 4, 4, 67, "SHA-256"); - break; - case LMSUtils.LMOTS_SHA256_N32_W8: - params = new LMOTSParams( - lmotsType, 32, 8, 0, 34, "SHA-256"); - break; - default: + LMOTSParams params = switch (lmotsType) { + case LMSUtils.LMOTS_SHA256_N32_W1 -> + new LMOTSParams(lmotsType, 32, 1, 7, 265, "SHA-256", 32); + case LMSUtils.LMOTS_SHA256_N32_W2 -> + new LMOTSParams(lmotsType, 32, 2, 6, 133, "SHA-256", 32); + case LMSUtils.LMOTS_SHA256_N32_W4 -> + new LMOTSParams(lmotsType, 32, 4, 4, 67, "SHA-256", 32); + case LMSUtils.LMOTS_SHA256_N32_W8 -> + new LMOTSParams(lmotsType, 32, 8, 0, 34, "SHA-256", 32); + case LMSUtils.LMOTS_SHA256_N24_W1 -> + new LMOTSParams(lmotsType, 24, 1, 8, 200, "SHA-256", 32); + case LMSUtils.LMOTS_SHA256_N24_W2 -> + new LMOTSParams(lmotsType, 24, 2, 6, 101, "SHA-256", 32); + case LMSUtils.LMOTS_SHA256_N24_W4 -> + new LMOTSParams(lmotsType, 24, 4, 4, 51, "SHA-256", 32); + case LMSUtils.LMOTS_SHA256_N24_W8 -> + new LMOTSParams(lmotsType, 24, 8, 0, 26, "SHA-256", 32); + case LMSUtils.LMOTS_SHAKE_N32_W1 -> + new LMOTSParams(lmotsType, 32, 1, 7, 265, "SHAKE256-512", 64); + case LMSUtils.LMOTS_SHAKE_N32_W2 -> + new LMOTSParams(lmotsType, 32, 2, 6, 133, "SHAKE256-512", 64); + case LMSUtils.LMOTS_SHAKE_N32_W4 -> + new LMOTSParams(lmotsType, 32, 4, 4, 67, "SHAKE256-512", 64); + case LMSUtils.LMOTS_SHAKE_N32_W8 -> + new LMOTSParams(lmotsType, 32, 8, 0, 34, "SHAKE256-512", 64); + case LMSUtils.LMOTS_SHAKE_N24_W1 -> + new LMOTSParams(lmotsType, 24, 1, 8, 200, "SHAKE256-512", 64); + case LMSUtils.LMOTS_SHAKE_N24_W2 -> + new LMOTSParams(lmotsType, 24, 2, 6, 101, "SHAKE256-512", 64); + case LMSUtils.LMOTS_SHAKE_N24_W4 -> + new LMOTSParams(lmotsType, 24, 4, 4, 51, "SHAKE256-512", 64); + case LMSUtils.LMOTS_SHAKE_N24_W8 -> + new LMOTSParams(lmotsType, 24, 8, 0, 26, "SHAKE256-512", 64); + default -> throw new IllegalArgumentException( "Unsupported or bad OTS Algorithm Identifier."); - } + }; return params; } @@ -580,13 +733,6 @@ public final class HSS extends SignatureSpi { S[len + 1] = (byte) (sum & 0xff); } - void digestFixedLengthPreprocessed( - SHA2.SHA256 sha256, byte[] input, int inLen, - byte[] output, int outOffset, int outLen) { - sha256.implDigestFixedLengthPreprocessed( - input, inLen, output, outOffset, outLen); - } - byte[] lmotsPubKeyCandidate( LMSignature lmSig, byte[] message, LMSPublicKey pKey) throws SignatureException { @@ -625,7 +771,13 @@ public final class HSS extends SignatureSpi { byte[] preZi = hashBuf.clone(); int hashLen = hashBuf.length; - SHA2.SHA256 sha256 = new SHA2.SHA256(); + + DigestBase db; + if (hashAlgName.startsWith("SHAKE")) { + db = new SHA3.SHAKE256Hash(); + } else { + db = new SHA2.SHA256(); + } pKey.getI(preZi, 0); lmSig.getQArr(preZi, 16); @@ -643,11 +795,11 @@ public final class HSS extends SignatureSpi { for (int j = a; j < twoPowWMinus1; j++) { preZi[22] = (byte) j; if (j < twoPowWMinus2) { - digestFixedLengthPreprocessed( - sha256, preZi, hashLen, preZi, 23, n); + db.implDigestFixedLengthPreprocessed(preZi, + hashLen, preZi, 23, n); } else { - digestFixedLengthPreprocessed( - sha256, preZi, hashLen, preCandidate, 22 + i * n, n); + db.implDigestFixedLengthPreprocessed(preZi, + hashLen, preCandidate, 22 + i * n, n); } } } diff --git a/src/java.base/share/classes/sun/security/provider/SHA2.java b/src/java.base/share/classes/sun/security/provider/SHA2.java index e966e6b77f8..7d8c2840de9 100644 --- a/src/java.base/share/classes/sun/security/provider/SHA2.java +++ b/src/java.base/share/classes/sun/security/provider/SHA2.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2023, 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 @@ -117,6 +117,7 @@ abstract class SHA2 extends DigestBase { } + @Override protected void implDigestFixedLengthPreprocessed( byte[] input, int inLen, byte[] output, int outOffset, int outLen) { implReset(); diff --git a/src/java.base/share/classes/sun/security/provider/SHA3.java b/src/java.base/share/classes/sun/security/provider/SHA3.java index a096cac5f50..0578645c1cd 100644 --- a/src/java.base/share/classes/sun/security/provider/SHA3.java +++ b/src/java.base/share/classes/sun/security/provider/SHA3.java @@ -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 @@ -98,6 +98,15 @@ public abstract class SHA3 extends DigestBase { this.suffix = suffix; } + @Override + protected void implDigestFixedLengthPreprocessed( + byte[] input, int inLen, byte[] output, int outOffset, int outLen) { + implReset(); + + implCompress(input, 0); + implDigest0(output, outOffset, outLen); + } + private void implCompressCheck(byte[] b, int ofs) { Objects.requireNonNull(b); Preconditions.checkIndex(ofs + blockSize - 1, b.length, Preconditions.AIOOBE_FORMATTER); @@ -136,9 +145,6 @@ public abstract class SHA3 extends DigestBase { * DigestBase calls implReset() when necessary. */ void implDigest(byte[] out, int ofs) { - // Moving this allocation to the block where it is used causes a little - // performance drop, that is why it is here. - byte[] byteState = new byte[8]; if (engineGetDigestLength() == 0) { // This is an XOF, so the digest() call is illegal. throw new ProviderException("Calling digest() is not allowed in an XOF"); @@ -146,8 +152,12 @@ public abstract class SHA3 extends DigestBase { finishAbsorb(); + implDigest0(out, ofs, engineGetDigestLength()); + } + + void implDigest0(byte[] out, int ofs, int outLen) { int availableBytes = blockSize; - int numBytes = engineGetDigestLength(); + int numBytes = outLen; while (numBytes > availableBytes) { for (int i = 0; i < availableBytes / 8; i++) { @@ -163,6 +173,10 @@ public abstract class SHA3 extends DigestBase { asLittleEndian.set(out, ofs, state[i]); ofs += 8; } + + // Moving this allocation to the block where it is used causes a little + // performance drop, that is why it is here. + byte[] byteState = new byte[8]; if (numBytes % 8 != 0) { asLittleEndian.set(byteState, 0, state[numLongs]); System.arraycopy(byteState, 0, out, ofs, numBytes % 8); diff --git a/src/java.base/share/classes/sun/security/util/KeyUtil.java b/src/java.base/share/classes/sun/security/util/KeyUtil.java index 942a91d61b8..e9dabdc5b06 100644 --- a/src/java.base/share/classes/sun/security/util/KeyUtil.java +++ b/src/java.base/share/classes/sun/security/util/KeyUtil.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 @@ -444,15 +444,16 @@ public final class KeyUtil { // is the LMS public key for the top-level tree. // Section 5.3: LMS public key is u32str(type) || u32str(otstype) || I || T[1] // Section 8: type is the numeric identifier for an LMS specification. - // This RFC defines 5 SHA-256 based types, value from 5 to 9. if (rawKey.length < 8) { throw new NoSuchAlgorithmException("Cannot decode public key"); } int num = ((rawKey[4] & 0xff) << 24) + ((rawKey[5] & 0xff) << 16) + ((rawKey[6] & 0xff) << 8) + (rawKey[7] & 0xff); return switch (num) { - // RFC 8554 only supports SHA_256 hash algorithm + // RFC 8554 only supports SHA_256 hash algorithms case 5, 6, 7, 8, 9 -> AlgorithmId.SHA256_oid; + // RFC 9858 supports SHAKE_256 hash algorithms + case 15, 16, 17, 18, 19 -> AlgorithmId.SHAKE256_512_oid; default -> throw new NoSuchAlgorithmException("Unknown LMS type: " + num); }; } catch (IOException e) { diff --git a/test/jdk/sun/security/provider/hss/TestHSS.java b/test/jdk/sun/security/provider/hss/TestHSS.java index 8056855cc1a..48019d4e465 100644 --- a/test/jdk/sun/security/provider/hss/TestHSS.java +++ b/test/jdk/sun/security/provider/hss/TestHSS.java @@ -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 @@ -23,7 +23,7 @@ /* * @test - * @bug 8298127 8347596 + * @bug 8298127 8347596 8369917 * @library /test/lib * @summary tests for HSS/LMS provider * @modules java.base/sun.security.util @@ -194,6 +194,8 @@ public class TestHSS { static TestCase[] TestCases = new TestCase[] { // Test Case #1 // RFC 8554 Test Case 1 + // LM_SHA256_M32_H5, LMOTS_SHA256_N32_W8 + // LM_SHA256_M32_H5, LMOTS_SHA256_N32_W8 new TestCase( null, // exception true, // expected result @@ -307,6 +309,8 @@ public class TestHSS { // Test Case #2 // RFC 8554 Test Case 2 + // LM_SHA256_M32_H10, LMOTS_SHA256_N32_W4 + // LM_SHA256_M32_H5, LMOTS_SHA256_N32_W8 new TestCase( null, // exception true, // expected result @@ -456,11 +460,11 @@ public class TestHSS { ), // Test Case #3 - // Additional Parameter sets for LMS Hash-Based Signatures (fluhrer) - // This test should fail because SHA256_M24 is supported. + // RFC 9858 A.1 + // LMS_SHA256_M24_H5, LMOTS_SHA256_N24_W8 new TestCase( - new InvalidKeySpecException(), - false, // expected result + null, // exception + true, // expected result decode(""" 00000001 0000000a @@ -502,11 +506,11 @@ public class TestHSS { ), // Test Case #4 - // Additional Parameter sets for LMS Hash-Based Signatures (fluhrer) - // This test should fail because SHAKE is not supported. + // RFC 9858 A.2 + // LMS_SHAKE_M24_H5, LMOTS_SHAKE_N24_W8 new TestCase( - new InvalidKeySpecException(), - false, // expected result + null, // exception + true, // expected result decode(""" 00000001 00000014 @@ -549,11 +553,11 @@ public class TestHSS { ), // Test Case #5 - // Additional Parameter sets for LMS Hash-Based Signatures (fluhrer) - // This test should fail because SHAKE is not supported. + // RFC 9858 A.3 + // LMS_SHAKE_M32_H5, LMOTS_SHAKE_N32_W8 new TestCase( - new InvalidKeySpecException(), - false, // expected result + null, // exception + true, // expected result decode(""" 00000001 0000000f @@ -611,6 +615,83 @@ public class TestHSS { ), // Test Case #6 + // RFC 9858 A.4 + // LMS_SHA256_M24_H20, LMOTS_SHA256_N24_W4 + new TestCase( + null, // exception + true, // expected result + decode(""" + 00000001 + 0000000d + 00000007 + 404142434445464748494a4b4c4d4e4f9c08a50d170406869892802ee4142fcd + eac990f110c2460c"""), + decode(""" + 54657374206d65737361676520666f72205348413235362f31393220773d34 + """), + decode(""" + 00000000 + 00000064 + 00000007 + 853fa6e1a65fef076acd2485505b93be9aeb2641e3d3805c1887f26f4bcdb6ac + 0337b76fa5d6603834287e010b20516f7c336df2134c0a981f1ec2bb7baee516 + e91e67d3bd16c8d945a7f2be4fd84a604ae3743efc609ee0e69572e9c6d4a682 + 50e877b75d3cae63e9d5c15a32bb3cd17045f6b3e195284fdd1ee3cfbe18f1cb + d06ef3e7af34b1844d42dac453115a4507ed525cec120d054b403c61a7e5034f + ac4be6ef5412d194d4b6bbc0ae6cd3fe9993d583ee06f4030bc832efec24d1f7 + 13f5088731b91a98491fa3adf1b322bce26df24c8415e3a46bdfe07a6fd48e6d + 951515758cd6434991098bf6949249fca338ec235871dd564998d07d9b1b1b8d + 644e657fee8039da8fe195d129faddb12d543b86b0ab8cf6f26c121783f3b828 + d03f793b42909272f688e4ef6d46e82bdd1a02b1ff86c3b79920b2e6f19faf75 + c623242f1f2c549f84fb2f4c3ffead3120d97baea507467bb2da79f132bbe15b + 596fdfcb70983107ebca2597de9d55bd83bcae5c28a85259dadb354859986e60 + c8afa0b10bd08a8f9ed9b1ede3377075fe0ae36349f7d2ed7bfc9ece0d4cd697 + 2059329419feaf3b9a1045b6cfa4ae89b1cea8950aea4af870d1a3a3909ebc5a + 3013d6deb927abc0f95093e83cb36a9c1d6f13add19268ac7a0371f8335b0952 + a57fdb0141d55d937dd6ebb08fee8a5cf426ac97d54ee7aa17e6c57be5e62a52 + a6b1b986730d3a3aad8a7d327ddf883e6bc7b636eb2a5c4f2a635ae5bada5418 + d43dfedb69c0a0209334fac89d420d6ad5a2e1df95d26a1bfeb99a5e8455061b + fdf2d6e8394caf8a4be699b8afa38e524d4053330af478f85bf33d3ca3a35bc9 + 6987282bd513a8f6a52db9ba36aa90882b3bf573fa275449d8d49eb30bed2bb1 + 7a0ecc7d8a20807f2ea3dd37acd46c713cc2ac9d01a20a30d6832eef86a1e26d + 1cad7761bf4130a6565572766026509deeddaf46b605452b218a4e137a7ce063 + b546a35c52510f0ea2cac879192ec443e43b37c5ffa23da7a7fc254324a3de70 + 5c771794f10ea356e5a747e5146fd804a47719803c185b380e34b8dcc8269c2b + 073d86b2307cf90c6c3ef9271f2d53df2579f0c4cfb632db37a9025965f70b46 + 16673228e98644be6576417b7a97f104350259e7f697408cdf8cf81a3e774162 + 6ccdb87ad8531264cb5ceb7c8c097cec505091a3ee3a826c54f78169abc2e7d0 + a318dac10250ba940e51e79a3f572fb32bf442be6fd81267946e6387f9a8c705 + d945c653f2684655e3fa6b9ee311d8a091bef9898292fa272fb8761f066c23d8 + 7aa10d67871cc5419c843b796855c51ad1272e9264acd2035a82b12c2ddbc85a + dfcd7c22366a36495349391dbf0001064b8f6b28365445d733e48f1b058a6cb3 + e71bbb8df3e90406299894f4ca682943ceeba410b33b07716ffc18d6eab75f2d + 6372f1133605fa3c3ed66f2d8f7c5abe59e87d4500965e347523d73cb356c144 + 827aaa22b1c72a15293c7400e02aaefcf36f68a8246900e6e6228e7ad19d1450 + c23434f1e45043dc2b6db57f20d8f5b344d4162aa651333287cd8bf8fac41c78 + d61fe2929209bfe2dc5a2f80205c043b22e540a29f0ea0a5ff529e55bf1dfe42 + 96fc4bb4ac2e875322ab115db479fe979d64f78409af4ec3ad3b758fff83af1b + 9c48e90ca39366f426c2fb921df55c72786a9217723945a1ac1a66af7def4f8b + 367001732cce0e5bac91ac9d603807f8bab105b46d315d4cb88feb1c8686884b + 0000000d + 13d1a8ef00c5811c15c4d774fdcf75155315aff53ebdff8fb6a54f12c165963d + d5690cc9842b0e2190afc5443497584c832155599d00aced84bb3b59170396f7 + db4fa84aa8577f76cf9367d6e99d3d5be3555d7156b004f2002f505681b1ad22 + 9b9b46a666672aa8ee662c3a0456a9adda7a44fbaca46789577dcd36dc5cdff3 + 4b864d0a32492a0acbcaa6c011748f205b91ab2ab84f2333fb3e3b9acaecdac3 + 8b58aa5f32e718e225631ed6674cccb8c119acbd4992ab3130a6e912deec5983 + 5ab52fbc549430f8b403e4a2a51cc7f46fc143d365763aa1708fd25bcd657a79 + 0e54718d970906242a3b8a97dff18e91a44c4ba818a8dd2d242251265b023b82 + 6077eb740f6682e6c4ada2b85a67988d406132c2ad899099e44cfe610c3a5af7 + 0b406224411a59597e5dda0f31cd16c914b67e96141661f0074f43eb02273481 + bc324ded26c64f2388559d8c8bd0ef8b34ca4afebfac2a689b4246c264241488 + dcf922350dc44f7bc09d57dc1126291b2318810e0f44801c071e572fd032c780 + f44c9503a4c03c37417dc96422ba0849c37956f9fd5d33ea4fcab84276effec6 + 52ca77d7d47ac93c633d99e0a236f03d5587d1990ffaef737fced1f5cdd8f373 + 844e9f316aad41a0b12302639f83a2d74c9fe30d305a942bc0c30352a5e44dfb + """) + ), + + // Test Case #7 // LMSigParameters.lms_sha256_m32_h15, LMOtsParameters.sha256_n32_w8 new TestCase( null, // exception @@ -713,7 +794,7 @@ public class TestHSS { """) ), - // Test Case #7 + // Test Case #8 // LMSigParameters.lms_sha256_m32_h20, LMOtsParameters.sha256_n32_w8 new TestCase( null, // exception @@ -821,7 +902,7 @@ public class TestHSS { """) ), - // Test Case #8 + // Test Case #9 // LMSigParameters.lms_sha256_m32_h15, LMOtsParameters.sha256_n32_w4 new TestCase( null, // exception @@ -957,7 +1038,7 @@ public class TestHSS { """) ), - // Test Case #9 + // Test Case #10 // LMSigParameters.lms_sha256_m32_h20, LMOtsParameters.sha256_n32_w4 new TestCase( null, // exception @@ -1098,7 +1179,7 @@ public class TestHSS { """) ), - // Test Case #10 + // Test Case #11 // LMSigParameters.lms_sha256_m32_h15, LMOtsParameters.sha256_n32_w4 // LMSigParameters.lms_sha256_m32_h10, LMOtsParameters.sha256_n32_w4 new TestCase( @@ -1320,7 +1401,7 @@ public class TestHSS { """) ), - // Test Case #11 + // Test Case #12 // LMSigParameters.lms_sha256_m32_h15, LMOtsParameters.sha256_n32_w4 // LMSigParameters.lms_sha256_m32_h15, LMOtsParameters.sha256_n32_w4 new TestCase( @@ -1547,7 +1628,7 @@ public class TestHSS { """) ), - // Test Case #12 + // Test Case #13 // LMSigParameters.lms_sha256_m32_h20, LMOtsParameters.sha256_n32_w4 // LMSigParameters.lms_sha256_m32_h10, LMOtsParameters.sha256_n32_w4 new TestCase( @@ -1774,7 +1855,7 @@ public class TestHSS { """) ), - // Test Case #13 + // Test Case #14 // LMSigParameters.lms_sha256_m32_h20, LMOtsParameters.sha256_n32_w4 // LMSigParameters.lms_sha256_m32_h15, LMOtsParameters.sha256_n32_w4 new TestCase( @@ -2006,7 +2087,7 @@ public class TestHSS { """) ), - // Test Case #14 + // Test Case #15 // LMS signature length is incorrect new TestCase( new SignatureException(), @@ -2119,7 +2200,7 @@ public class TestHSS { """) ), - // Test Case #15 + // Test Case #16 // HSS signature and public key have different tree heights new TestCase( new SignatureException(), @@ -2232,7 +2313,7 @@ public class TestHSS { """) ), - // Test Case #16 + // Test Case #17 // bad signature new TestCase( null, // exception @@ -2345,7 +2426,7 @@ public class TestHSS { """) ), - // Test Case #17 + // Test Case #18 // Invalid key in HSS signature new TestCase( new SignatureException(), @@ -2458,7 +2539,7 @@ public class TestHSS { """) ), - // Test Case #18 + // Test Case #19 // LMS signature is too short new TestCase( new SignatureException(), @@ -2485,7 +2566,7 @@ public class TestHSS { 965a25bfd37f196b9073f3d4a232feb6""") ), - // Test Case #19 + // Test Case #20 // bad signature new TestCase( null, // exception From 9d6a94ef6789f62ae47f0112e75dc446000e63f4 Mon Sep 17 00:00:00 2001 From: Mahendra Chhipa Date: Mon, 13 Apr 2026 22:43:57 +0000 Subject: [PATCH 034/108] 8368091: Use JUnit Jupiter API in sun/net/ext, sun/net/www and sun/net/spi tests Reviewed-by: dfuchs --- .../net/ext/ExtendedSocketOptionsTest.java | 25 +-- .../sun/net/spi/DefaultProxySelectorTest.java | 34 ++-- test/jdk/sun/net/www/MessageHeaderTest.java | 17 +- .../KeepAliveStreamCleanerTestDriver.java | 4 +- .../www/http/KeepAliveStreamCleanerTest.java | 6 +- .../RequestMethodEquality.java | 29 +-- .../protocol/file/DirPermissionDenied.java | 19 +- .../protocol/http/HttpHeaderParserTest.java | 166 +++++++++--------- .../protocol/http/TestTransparentNTLM.java | 40 ++--- .../jar/MultiReleaseJarURLConnection.java | 96 +++++----- test/jdk/sun/net/www/protocol/jrt/Basic.java | 34 ++-- 11 files changed, 234 insertions(+), 236 deletions(-) diff --git a/test/jdk/sun/net/ext/ExtendedSocketOptionsTest.java b/test/jdk/sun/net/ext/ExtendedSocketOptionsTest.java index d42d780db9d..af9345953b8 100644 --- a/test/jdk/sun/net/ext/ExtendedSocketOptionsTest.java +++ b/test/jdk/sun/net/ext/ExtendedSocketOptionsTest.java @@ -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 @@ -21,8 +21,7 @@ * questions. */ -import org.testng.Assert; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import java.util.ArrayList; import java.util.Collections; @@ -33,6 +32,10 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertSame; + /** * @test * @bug 8260366 @@ -40,11 +43,11 @@ import java.util.concurrent.Future; * jdk.net.ExtendedSocketOptions doesn't lead to a deadlock * @modules java.base/sun.net.ext:open * jdk.net - * @run testng/othervm ExtendedSocketOptionsTest - * @run testng/othervm ExtendedSocketOptionsTest - * @run testng/othervm ExtendedSocketOptionsTest - * @run testng/othervm ExtendedSocketOptionsTest - * @run testng/othervm ExtendedSocketOptionsTest + * @run junit/othervm ${test.main.class} + * @run junit/othervm ${test.main.class} + * @run junit/othervm ${test.main.class} + * @run junit/othervm ${test.main.class} + * @run junit/othervm ${test.main.class} */ public class ExtendedSocketOptionsTest { @@ -83,13 +86,13 @@ public class ExtendedSocketOptionsTest { // check that the sun.net.ext.ExtendedSocketOptions#getInstance() does indeed return // the registered instance final Object extSocketOptions = callSunNetExtSocketOptionsGetInstance(); - Assert.assertNotNull(extSocketOptions, "sun.net.ext.ExtendedSocketOptions#getInstance()" + + assertNotNull(extSocketOptions, "sun.net.ext.ExtendedSocketOptions#getInstance()" + " unexpectedly returned null"); // now verify that each call to getInstance(), either in the tasks or here, returned the exact // same instance of ExtendedSocketOptions - Assert.assertEquals(2, GetInstanceTask.extendedSocketOptionsInstances.size()); + assertEquals(2, GetInstanceTask.extendedSocketOptionsInstances.size()); for (final Object inst : GetInstanceTask.extendedSocketOptionsInstances) { - Assert.assertSame(inst, extSocketOptions, "sun.net.ext.ExtendedSocketOptions#getInstance()" + + assertSame(extSocketOptions, inst, "sun.net.ext.ExtendedSocketOptions#getInstance()" + " returned different instances"); } } diff --git a/test/jdk/sun/net/spi/DefaultProxySelectorTest.java b/test/jdk/sun/net/spi/DefaultProxySelectorTest.java index 5fdff96f5f2..d070d843fee 100644 --- a/test/jdk/sun/net/spi/DefaultProxySelectorTest.java +++ b/test/jdk/sun/net/spi/DefaultProxySelectorTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 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 @@ -21,18 +21,19 @@ * questions. */ -import org.testng.Assert; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import sun.net.spi.DefaultProxySelector; import java.net.ProxySelector; import java.net.URI; +import static org.junit.jupiter.api.Assertions.assertThrows; + /** * @test * @bug 6563286 6797318 8177648 * @summary Tests sun.net.spi.DefaultProxySelector#select(URI) - * @run testng DefaultProxySelectorTest + * @run junit ${test.main.class} * @modules java.base/sun.net.spi:+open */ public class DefaultProxySelectorTest { @@ -44,12 +45,7 @@ public class DefaultProxySelectorTest { @Test public void testIllegalArgForNull() { final ProxySelector selector = new DefaultProxySelector(); - try { - selector.select(null); - Assert.fail("select() was expected to fail for null URI"); - } catch (IllegalArgumentException iae) { - // expected - } + assertThrows(IllegalArgumentException.class, () -> selector.select(null)); } /** @@ -61,12 +57,11 @@ public class DefaultProxySelectorTest { @Test public void testIllegalArgForNoHost() throws Exception { final ProxySelector selector = new DefaultProxySelector(); - assertFailsWithIAE(selector, new URI("http", "/test", null)); - assertFailsWithIAE(selector, new URI("https", "/test2", null)); - assertFailsWithIAE(selector, new URI("ftp", "/test3", null)); + assertThrows(IllegalArgumentException.class, () -> selector.select(new URI("http", "/test", null))); + assertThrows(IllegalArgumentException.class, () -> selector.select(new URI("https", "/test2", null))); + assertThrows(IllegalArgumentException.class, () -> selector.select(new URI("ftp", "/test3", null))); } - /** * Tests that {@link DefaultProxySelector} throws a {@link IllegalArgumentException} * for URIs that don't have protocol/scheme information @@ -76,15 +71,6 @@ public class DefaultProxySelectorTest { @Test public void testIllegalArgForNoScheme() throws Exception { final ProxySelector selector = new DefaultProxySelector(); - assertFailsWithIAE(selector, new URI(null, "/test", null)); - } - - private static void assertFailsWithIAE(final ProxySelector selector, final URI uri) { - try { - selector.select(uri); - Assert.fail("select() was expected to fail for URI " + uri); - } catch (IllegalArgumentException iae) { - // expected - } + assertThrows(IllegalArgumentException.class, () -> selector.select(new URI(null, "/test", null))); } } diff --git a/test/jdk/sun/net/www/MessageHeaderTest.java b/test/jdk/sun/net/www/MessageHeaderTest.java index 903e805f5f4..6928173df67 100644 --- a/test/jdk/sun/net/www/MessageHeaderTest.java +++ b/test/jdk/sun/net/www/MessageHeaderTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2021, 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 @@ -25,16 +25,17 @@ * @test * @bug 8003948 8133686 * @modules java.base/sun.net.www - * @run testng MessageHeaderTest + * @run junit ${test.main.class} */ import java.io.*; import java.util.*; -import org.testng.Assert; -import org.testng.annotations.Test; +import org.junit.jupiter.api.Test; import sun.net.www.MessageHeader; +import static org.junit.jupiter.api.Assertions.assertEquals; + public class MessageHeaderTest { /* This test checks to see if the MessageHeader.getHeaders method returns headers with the same value field in the order they were added @@ -53,12 +54,12 @@ public class MessageHeaderTest { var actualHeaders = testHeader.getHeaders().get("test"); - Assert.assertEquals(expectedHeaders, actualHeaders, String.format(errorMessageTemplate, expectedHeaders.toString(), actualHeaders.toString())); + assertEquals(expectedHeaders, actualHeaders, String.format(errorMessageTemplate, expectedHeaders.toString(), actualHeaders.toString())); } @Test public void ntlmNegotiateTest () throws Exception { - String expected[] = { + String[] expected = { "{null: HTTP/1.1 200 Ok}{Foo: bar}{Bar: foo}{WWW-Authenticate: NTLM sdsds}", "{null: HTTP/1.1 200 Ok}{Foo: bar}{Bar: foo}{WWW-Authenticate: }", "{null: HTTP/1.1 200 Ok}{Foo: bar}{Bar: foo}{WWW-Authenticate: NTLM sdsds}", @@ -90,8 +91,8 @@ public class MessageHeaderTest { boolean result = h.filterNTLMResponses("WWW-Authenticate"); String after = h.toString(); after = after.substring(after.indexOf('{')); - Assert.assertEquals(expected[i], after, i + " expected != after"); - Assert.assertEquals(result, expectedResult[i], i + " result != expectedResult"); + assertEquals(expected[i], after, i + " expected != after"); + assertEquals(expectedResult[i], result, i + " result != expectedResult"); } } } diff --git a/test/jdk/sun/net/www/http/KeepAliveStreamCleaner/KeepAliveStreamCleanerTestDriver.java b/test/jdk/sun/net/www/http/KeepAliveStreamCleaner/KeepAliveStreamCleanerTestDriver.java index 5f66aff365e..bf7553acf62 100644 --- a/test/jdk/sun/net/www/http/KeepAliveStreamCleaner/KeepAliveStreamCleanerTestDriver.java +++ b/test/jdk/sun/net/www/http/KeepAliveStreamCleaner/KeepAliveStreamCleanerTestDriver.java @@ -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 @@ -26,5 +26,5 @@ * @bug 8255124 * @summary Tests that KeepAliveStreamCleaner run does not throw an IllegalMonitorState Exception. * @modules java.base/sun.net.www.http - * @run testng java.base/sun.net.www.http.KeepAliveStreamCleanerTest + * @run junit java.base/sun.net.www.http.KeepAliveStreamCleanerTest */ diff --git a/test/jdk/sun/net/www/http/KeepAliveStreamCleaner/java.base/sun/net/www/http/KeepAliveStreamCleanerTest.java b/test/jdk/sun/net/www/http/KeepAliveStreamCleaner/java.base/sun/net/www/http/KeepAliveStreamCleanerTest.java index b4bc638099d..d927e0a8029 100644 --- a/test/jdk/sun/net/www/http/KeepAliveStreamCleaner/java.base/sun/net/www/http/KeepAliveStreamCleanerTest.java +++ b/test/jdk/sun/net/www/http/KeepAliveStreamCleaner/java.base/sun/net/www/http/KeepAliveStreamCleanerTest.java @@ -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 @@ -23,9 +23,9 @@ package sun.net.www.http; -import org.testng.annotations.Test; -@Test +import org.junit.jupiter.api.Test; + public class KeepAliveStreamCleanerTest { /* diff --git a/test/jdk/sun/net/www/http/RequestMethodCheck/RequestMethodEquality.java b/test/jdk/sun/net/www/http/RequestMethodCheck/RequestMethodEquality.java index 290248989d8..364bafd0749 100644 --- a/test/jdk/sun/net/www/http/RequestMethodCheck/RequestMethodEquality.java +++ b/test/jdk/sun/net/www/http/RequestMethodCheck/RequestMethodEquality.java @@ -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 @@ -30,17 +30,16 @@ * @modules java.base/sun.net.www.http * java.base/sun.net.www.protocol.http * @build java.base/sun.net.www.http.HttpClientAccess - * @run testng/othervm RequestMethodEquality + * @run junit/othervm ${test.main.class} */ import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; import com.sun.net.httpserver.HttpServer; import jdk.test.lib.net.URIBuilder; -import org.testng.Assert; -import org.testng.annotations.AfterTest; -import org.testng.annotations.BeforeTest; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; import sun.net.www.http.HttpClient; import sun.net.www.http.HttpClientAccess; import sun.net.www.http.KeepAliveCache; @@ -52,21 +51,23 @@ import java.net.InetSocketAddress; import java.net.Proxy; import java.net.URL; +import static org.junit.jupiter.api.Assertions.assertNotEquals; + public class RequestMethodEquality { private static final String TEST_CONTEXT = "/reqmethodtest"; - private HttpServer server; - private CustomHandler handler; - private HttpClientAccess httpClientAccess; + private static HttpServer server; + private static CustomHandler handler; + private static HttpClientAccess httpClientAccess; - @BeforeTest - public void setup() throws Exception { + @BeforeAll + public static void setup() throws Exception { handler = new CustomHandler(); server = createServer(handler); httpClientAccess = new HttpClientAccess(); } - @AfterTest - public void tearDown() throws Exception { + @AfterAll + public static void tearDown() throws Exception { if (server != null) { server.stop(0); } @@ -111,7 +112,7 @@ public class RequestMethodEquality { // If both connectTimeout values are equal, it means the test retrieved the same broken // HttpClient from the cache and is trying to re-use it. - Assert.assertNotEquals(originalConnectTimeout, cachedConnectTimeout, "Both connectTimeout values are equal.\nThis means the test is reusing a broken HttpClient rather than creating a new one."); + assertNotEquals(originalConnectTimeout, cachedConnectTimeout, "Both connectTimeout values are equal.\nThis means the test is reusing a broken HttpClient rather than creating a new one."); } finally { if (conn != null) { conn.disconnect(); diff --git a/test/jdk/sun/net/www/protocol/file/DirPermissionDenied.java b/test/jdk/sun/net/www/protocol/file/DirPermissionDenied.java index 0e3f9e86c4e..b6d8790bb5b 100644 --- a/test/jdk/sun/net/www/protocol/file/DirPermissionDenied.java +++ b/test/jdk/sun/net/www/protocol/file/DirPermissionDenied.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2026, Oracle and/or its affiliates. All rights reserved. * 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 @@ * @library /test/lib * @build DirPermissionDenied jdk.test.lib.process.* * jdk.test.lib.util.FileUtils - * @run testng DirPermissionDenied + * @run junit ${test.main.class} */ import java.io.IOException; @@ -41,9 +41,10 @@ import java.nio.file.Paths; import jdk.test.lib.process.ProcessTools; import jdk.test.lib.util.FileUtils; -import org.testng.annotations.AfterTest; -import org.testng.annotations.Test; -import org.testng.annotations.BeforeTest; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + public class DirPermissionDenied { private static final Path TEST_DIR = Paths.get( "DirPermissionDeniedDirectory"); @@ -79,8 +80,8 @@ public class DirPermissionDenied { } } - @BeforeTest - public void setup() throws Throwable { + @BeforeAll + public static void setup() throws Throwable { // mkdir and chmod "333" Files.createDirectories(TEST_DIR); ProcessTools.executeCommand("chmod", "333", TEST_DIR.toString()) @@ -89,8 +90,8 @@ public class DirPermissionDenied { .shouldHaveExitValue(0); } - @AfterTest - public void tearDown() throws Throwable { + @AfterAll + public static void tearDown() throws Throwable { // add read permission to ensure the dir removable ProcessTools.executeCommand("chmod", "733", TEST_DIR.toString()) .outputTo(System.out) diff --git a/test/jdk/sun/net/www/protocol/http/HttpHeaderParserTest.java b/test/jdk/sun/net/www/protocol/http/HttpHeaderParserTest.java index 245cd49d518..ad3b3fa3722 100644 --- a/test/jdk/sun/net/www/protocol/http/HttpHeaderParserTest.java +++ b/test/jdk/sun/net/www/protocol/http/HttpHeaderParserTest.java @@ -1,7 +1,7 @@ /* - * Copyright (c) 2022, 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,13 +28,12 @@ * @library /test/lib * @summary Sanity check that HttpHeaderParser works same as MessageHeader * @modules java.base/sun.net.www java.base/sun.net.www.protocol.http:open - * @run testng/othervm HttpHeaderParserTest + * @run junit/othervm ${test.main.class} */ import java.io.ByteArrayInputStream; import java.io.IOException; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -45,20 +44,21 @@ import java.util.concurrent.atomic.AtomicInteger; import static java.lang.String.format; import static java.nio.charset.StandardCharsets.ISO_8859_1; import static java.nio.charset.StandardCharsets.US_ASCII; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + import jdk.test.lib.net.HttpHeaderParser; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import sun.net.www.MessageHeader; public class HttpHeaderParserTest { - @DataProvider(name = "responses") - public Object[][] responses() { + public static List responses() { List responses = new ArrayList<>(); - - String[] basic = - { "HTTP/1.1 200 OK\r\n\r\n", + List basic = List.of( + "HTTP/1.1 200 OK\r\n\r\n", "HTTP/1.1 200 OK\r\n" + "Date: Mon, 15 Jan 2001 12:18:21 GMT\r\n" + @@ -142,11 +142,11 @@ public class HttpHeaderParserTest { "Vary: Host,Accept-Encoding,User-Agent\r\n" + "X-Mod-Pagespeed: 1.12.34.2-0\r\n" + "Connection: keep-alive\r\n\r\n" - }; - Arrays.stream(basic).forEach(responses::add); + ); + responses.addAll(basic); // add some tests where some of the CRLF are replaced // by a single LF - Arrays.stream(basic) + basic.stream() .map(HttpHeaderParserTest::mixedCRLF) .forEach(responses::add); @@ -211,8 +211,8 @@ public class HttpHeaderParserTest { responses.add(mixedCRLF(template).replace("$NEWLINE", newLineChar)); } - String[] bad = // much of this is to retain parity with legacy MessageHeaders - { "HTTP/1.1 200 OK\r\n" + + List bad = List.of( // much of this is to retain parity with legacy MessageHeaders + "HTTP/1.1 200 OK\r\n" + "Connection:\r\n\r\n", // empty value, no body "HTTP/1.1 200 OK\r\n" + @@ -258,12 +258,11 @@ public class HttpHeaderParserTest { "HTTP/1.0 404 Not Found\r\n" + "header-without-colon\r\n\r\n" + - "SOMEBODY", + "SOMEBODY" - }; - Arrays.stream(bad).forEach(responses::add); - - return responses.stream().map(p -> new Object[] { p }).toArray(Object[][]::new); + ); + responses.addAll(bad); + return responses; } static final AtomicInteger index = new AtomicInteger(); @@ -318,8 +317,8 @@ public class HttpHeaderParserTest { return res.toString(); } - - @Test(dataProvider = "responses") + @ParameterizedTest + @MethodSource("responses") public void verifyHeaders(String respString) throws Exception { System.out.println("\ntesting:\n\t" + respString .replace("\r\n", "") @@ -344,7 +343,7 @@ public class HttpHeaderParserTest { String statusLine1 = messageHeaderMap.get(null).get(0); String statusLine2 = decoder.getRequestDetails(); if (statusLine1.startsWith("HTTP")) {// skip the case where MH's messes up the status-line - assertEquals(statusLine2, statusLine1, "Status-line not equal"); + assertEquals(statusLine1, statusLine2, "Status-line not equal"); } else { assertTrue(statusLine2.startsWith("HTTP/1."), "Status-line not HTTP/1."); } @@ -366,91 +365,86 @@ public class HttpHeaderParserTest { availableBytes, headerStream.available())); } - @DataProvider(name = "errors") - public Object[][] errors() { - List responses = new ArrayList<>(); - + public static List errors() { // These responses are parsed, somewhat, by MessageHeaders but give // nonsensible results. They, correctly, fail with the Http1HeaderParser. - String[] bad = - {// "HTTP/1.1 402 Payment Required\r\n" + - // "Content-Length: 65\r\n\r", // missing trailing LF //TODO: incomplete + List responses = List.of( + // "HTTP/1.1 402 Payment Required\r\n" + + // "Content-Length: 65\r\n\r", // missing trailing LF //TODO: incomplete - "HTTP/1.1 402 Payment Required\r\n" + - "Content-Length: 65\r\n\rT\r\n\r\nGGGGGG", + "HTTP/1.1 402 Payment Required\r\n" + + "Content-Length: 65\r\n\rT\r\n\r\nGGGGGG", - "HTTP/1.1 200OK\r\n\rT", + "HTTP/1.1 200OK\r\n\rT", - "HTTP/1.1 200OK\rT", + "HTTP/1.1 200OK\rT", - "HTTP/1.0 FOO\r\n", + "HTTP/1.0 FOO\r\n", - "HTTP/1.1 BAR\r\n", + "HTTP/1.1 BAR\r\n", - "HTTP/1.1 +99\r\n", + "HTTP/1.1 +99\r\n", - "HTTP/1.1 -22\r\n", + "HTTP/1.1 -22\r\n", - "HTTP/1.1 -20 \r\n", + "HTTP/1.1 -20 \r\n", + "HTTP/1.1 200 OK\r\n" + + "X-fo\u00ffo: foo\r\n" + // invalid char in name + "Content-Length: 5\r\n" + + "Content-Type: text/html; charset=UTF-8\r\n\r\n" + + "XXXXX", + + "HTTP/1.1 200 OK\r\n" + "HTTP/1.1 200 OK\r\n" + - "X-fo\u00ffo: foo\r\n" + // invalid char in name - "Content-Length: 5\r\n" + - "Content-Type: text/html; charset=UTF-8\r\n\r\n" + - "XXXXX", + "X-foo : bar\r\n" + // trim space after name + "Content-Length: 5\r\n" + + "Content-Type: text/html; charset=UTF-8\r\n\r\n" + + "XXXXX", - "HTTP/1.1 200 OK\r\n" + - "HTTP/1.1 200 OK\r\n" + - "X-foo : bar\r\n" + // trim space after name - "Content-Length: 5\r\n" + - "Content-Type: text/html; charset=UTF-8\r\n\r\n" + - "XXXXX", + "HTTP/1.1 200 OK\r\n" + + " X-foo: bar\r\n" + // trim space before name + "Content-Length: 5\r\n" + + "Content-Type: text/html; charset=UTF-8\r\n\r\n" + + "XXXXX", - "HTTP/1.1 200 OK\r\n" + - " X-foo: bar\r\n" + // trim space before name - "Content-Length: 5\r\n" + - "Content-Type: text/html; charset=UTF-8\r\n\r\n" + - "XXXXX", + "HTTP/1.1 200 OK\r\n" + + "X foo: bar\r\n" + // invalid space in name + "Content-Length: 5\r\n" + + "Content-Type: text/html; charset=UTF-8\r\n\r\n" + + "XXXXX", - "HTTP/1.1 200 OK\r\n" + - "X foo: bar\r\n" + // invalid space in name - "Content-Length: 5\r\n" + - "Content-Type: text/html; charset=UTF-8\r\n\r\n" + - "XXXXX", + "HTTP/1.1 200 OK\r\n" + + "Content-Length: 5\r\n" + + "Content Type: text/html; charset=UTF-8\r\n\r\n" + // invalid space in name + "XXXXX", - "HTTP/1.1 200 OK\r\n" + - "Content-Length: 5\r\n" + - "Content Type: text/html; charset=UTF-8\r\n\r\n" + // invalid space in name - "XXXXX", + "HTTP/1.1 200 OK\r\n" + + "Conte\r" + + " nt-Length: 9\r\n" + // fold results in space in header name + "Content-Type: text/html; charset=UTF-8\r\n\r\n" + + "XXXXX", - "HTTP/1.1 200 OK\r\n" + - "Conte\r" + - " nt-Length: 9\r\n" + // fold results in space in header name - "Content-Type: text/html; charset=UTF-8\r\n\r\n" + - "XXXXX", + "HTTP/1.1 200 OK\r\n" + + " : no header\r\n" + // all blank header-name (not fold) + "Content-Length: 65\r\n\r\n" + + "XXXXX", - "HTTP/1.1 200 OK\r\n" + - " : no header\r\n" + // all blank header-name (not fold) - "Content-Length: 65\r\n\r\n" + - "XXXXX", + "HTTP/1.1 200 OK\r\n" + + " \t : no header\r\n" + // all blank header-name (not fold) + "Content-Length: 65\r\n\r\n" + + "XXXXX"); - "HTTP/1.1 200 OK\r\n" + - " \t : no header\r\n" + // all blank header-name (not fold) - "Content-Length: 65\r\n\r\n" + - "XXXXX", - - }; - Arrays.stream(bad).forEach(responses::add); - - return responses.stream().map(p -> new Object[] { p }).toArray(Object[][]::new); + return responses; } - @Test(dataProvider = "errors", expectedExceptions = IOException.class) + @ParameterizedTest + @MethodSource("errors") public void errors(String respString) throws IOException { byte[] bytes = respString.getBytes(US_ASCII); HttpHeaderParser decoder = new HttpHeaderParser(); ByteArrayInputStream bais = new ByteArrayInputStream(bytes); - decoder.parse(bais); + assertThrows(IOException.class, () -> decoder.parse(bais)); } void assertHeadersEqual(Map> expected, @@ -477,7 +471,7 @@ public class HttpHeaderParserTest { format("%s. Expected list size %d, actual size %s", msg, values.size(), otherValues.size())); if (!(values.containsAll(otherValues) && otherValues.containsAll(values))) - assertTrue(false, format("Lists are unequal [%s] [%s]", values, otherValues)); + fail(format("Lists are unequal [%s] [%s]", values, otherValues)); break; } } diff --git a/test/jdk/sun/net/www/protocol/http/TestTransparentNTLM.java b/test/jdk/sun/net/www/protocol/http/TestTransparentNTLM.java index 0df834765c8..1c2262ba8e2 100644 --- a/test/jdk/sun/net/www/protocol/http/TestTransparentNTLM.java +++ b/test/jdk/sun/net/www/protocol/http/TestTransparentNTLM.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2019, 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 @@ -28,18 +28,18 @@ * and is used only when the relevant property is set. * @requires os.family == "windows" * @library /test/lib - * @run testng/othervm + * @run junit/othervm * -Dtest.auth.succeed=false * TestTransparentNTLM - * @run testng/othervm + * @run junit/othervm * -Djdk.http.ntlm.transparentAuth=allHosts * -Dtest.auth.succeed=true * TestTransparentNTLM - * @run testng/othervm + * @run junit/othervm * -Djdk.http.ntlm.transparentAuth=blahblah * -Dtest.auth.succeed=false * TestTransparentNTLM - * @run testng/othervm + * @run junit/othervm * -Djdk.http.ntlm.transparentAuth=trustedHosts * -Dtest.auth.succeed=false * TestTransparentNTLM @@ -57,21 +57,21 @@ import java.net.ServerSocket; import java.net.Socket; import java.net.URL; import jdk.test.lib.net.URIBuilder; -import org.testng.annotations.AfterTest; -import org.testng.annotations.BeforeTest; -import org.testng.annotations.Test; -import org.testng.SkipException; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + import static java.lang.System.out; import static java.net.Proxy.NO_PROXY; import static java.nio.charset.StandardCharsets.UTF_8; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.fail; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.fail; public class TestTransparentNTLM { - boolean succeed; // true if authentication is expected to succeed - Server server; - URL url; + static boolean succeed; // true if authentication is expected to succeed + static Server server; + static URL url; @Test public void testNTLM() throws IOException { @@ -81,11 +81,11 @@ public class TestTransparentNTLM { out.println("received: " + respCode); if (succeed) { - assertEquals(respCode, HttpURLConnection.HTTP_OK); + assertEquals(HttpURLConnection.HTTP_OK, respCode); String body = new String(uc.getInputStream().readAllBytes(), UTF_8); out.println("received body: " + body); } else { - assertEquals(respCode, HttpURLConnection.HTTP_UNAUTHORIZED); + assertEquals(HttpURLConnection.HTTP_UNAUTHORIZED, respCode); } } @@ -169,8 +169,8 @@ public class TestTransparentNTLM { } } - @BeforeTest - public void setup() throws Exception { + @BeforeAll + public static void setup() throws Exception { succeed = System.getProperty("test.auth.succeed").equals("true"); if (succeed) out.println("Expect client to succeed, with 200 Ok"); @@ -187,8 +187,8 @@ public class TestTransparentNTLM { .toURL(); } - @AfterTest - public void teardown() throws Exception { + @AfterAll + public static void teardown() throws Exception { server.close(); server.join(); } diff --git a/test/jdk/sun/net/www/protocol/jar/MultiReleaseJarURLConnection.java b/test/jdk/sun/net/www/protocol/jar/MultiReleaseJarURLConnection.java index f113e7d3fcd..3be7dd5bd98 100644 --- a/test/jdk/sun/net/www/protocol/jar/MultiReleaseJarURLConnection.java +++ b/test/jdk/sun/net/www/protocol/jar/MultiReleaseJarURLConnection.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 @@ -31,7 +31,7 @@ * @build CreateMultiReleaseTestJars * jdk.test.lib.util.JarBuilder * jdk.test.lib.compiler.Compiler - * @run testng MultiReleaseJarURLConnection + * @run junit ${test.main.class} */ import java.io.IOException; @@ -59,22 +59,27 @@ import java.util.jar.JarFile; import com.sun.net.httpserver.HttpServer; import com.sun.net.httpserver.SimpleFileServer; import jdk.test.lib.net.URIBuilder; -import org.testng.Assert; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; public class MultiReleaseJarURLConnection { - String userdir = System.getProperty("user.dir", "."); - String unversioned = userdir + "/unversioned.jar"; - String unsigned = userdir + "/multi-release.jar"; - String signed = userdir + "/signed-multi-release.jar"; - HttpServer server; - ExecutorService executor; + static String userdir = System.getProperty("user.dir", "."); + static String unversioned = userdir + "/unversioned.jar"; + static String unsigned = userdir + "/multi-release.jar"; + static String signed = userdir + "/signed-multi-release.jar"; + static HttpServer server; + static ExecutorService executor; - @BeforeClass - public void initialize() throws Exception { + @BeforeAll + public static void initialize() throws Exception { CreateMultiReleaseTestJars creator = new CreateMultiReleaseTestJars(); creator.compileEntries(); creator.buildUnversionedJar(); @@ -87,8 +92,8 @@ public class MultiReleaseJarURLConnection { server.start(); } - @AfterClass - public void close() throws IOException { + @AfterAll + public static void close() throws IOException { // Windows requires server to stop before file is deleted if (server != null) server.stop(0); @@ -99,8 +104,7 @@ public class MultiReleaseJarURLConnection { Files.delete(Paths.get(signed)); } - @DataProvider(name = "data") - public Object[][] createData() { + public static Object[][] createData() { return new Object[][]{ {"unversioned", unversioned}, {"unsigned", unsigned}, @@ -108,20 +112,21 @@ public class MultiReleaseJarURLConnection { }; } - @Test(dataProvider = "data") + @ParameterizedTest + @MethodSource("createData") public void testRuntimeVersioning(String style, String file) throws Exception { String urlFile = "jar:file:" + file + "!/"; String baseUrlEntry = urlFile + "version/Version.java"; String rtreturn = "return " + Runtime.version().major(); - Assert.assertTrue(readAndCompare(new URL(baseUrlEntry), "return 8")); + assertTrue(readAndCompare(new URL(baseUrlEntry), "return 8")); // #runtime is "magic" for a multi-release jar, but not for unversioned jar - Assert.assertTrue(readAndCompare(new URL(baseUrlEntry + "#runtime"), + assertTrue(readAndCompare(new URL(baseUrlEntry + "#runtime"), style.equals("unversioned") ? "return 8" : rtreturn)); // #fragment or any other fragment is not magic - Assert.assertTrue(readAndCompare(new URL(baseUrlEntry + "#fragment"), "return 8")); + assertTrue(readAndCompare(new URL(baseUrlEntry + "#fragment"), "return 8")); // cached entities not affected - Assert.assertTrue(readAndCompare(new URL(baseUrlEntry), "return 8")); + assertTrue(readAndCompare(new URL(baseUrlEntry), "return 8")); // the following tests will not work with unversioned jars if (style.equals("unversioned")) @@ -130,13 +135,14 @@ public class MultiReleaseJarURLConnection { // direct access to versioned entry String versUrlEntry = urlFile + "META-INF/versions/" + Runtime.version().major() + "/version/Version.java"; - Assert.assertTrue(readAndCompare(new URL(versUrlEntry), rtreturn)); + assertTrue(readAndCompare(new URL(versUrlEntry), rtreturn)); // adding any fragment does not change things - Assert.assertTrue(readAndCompare(new URL(versUrlEntry + "#runtime"), rtreturn)); - Assert.assertTrue(readAndCompare(new URL(versUrlEntry + "#fragment"), rtreturn)); + assertTrue(readAndCompare(new URL(versUrlEntry + "#runtime"), rtreturn)); + assertTrue(readAndCompare(new URL(versUrlEntry + "#fragment"), rtreturn)); } - @Test(dataProvider = "data") + @ParameterizedTest + @MethodSource("createData") public void testCachedJars(String style, String file) throws Exception { String urlFile = "jar:file:" + file + "!/"; @@ -150,28 +156,27 @@ public class MultiReleaseJarURLConnection { JarFile runtimeJar = juc.getJarFile(); Runtime.Version runtime = runtimeJar.getVersion(); if (style.equals("unversioned")) { - Assert.assertEquals(root, runtime); + assertEquals(root, runtime); } else { - Assert.assertNotEquals(root, runtime); + assertNotEquals(root, runtime); } juc = (JarURLConnection) rootUrl.openConnection(); JarFile jar = juc.getJarFile(); - Assert.assertEquals(jar.getVersion(), root); - Assert.assertEquals(jar, rootJar); + assertEquals(root, jar.getVersion()); + assertEquals(rootJar, jar); juc = (JarURLConnection) runtimeUrl.openConnection(); jar = juc.getJarFile(); - Assert.assertEquals(jar.getVersion(), runtime); - Assert.assertEquals(jar, runtimeJar); + assertEquals(runtime, jar.getVersion()); + assertEquals(runtimeJar, jar); rootJar.close(); runtimeJar.close(); jar.close(); // probably not needed } - @DataProvider(name = "resourcedata") - public Object[][] createResourceData() throws Exception { + public static Object[][] createResourceData() throws Exception { return new Object[][]{ {"unversioned", Paths.get(unversioned).toUri().toURL()}, {"unsigned", Paths.get(unsigned).toUri().toURL()}, @@ -189,7 +194,8 @@ public class MultiReleaseJarURLConnection { }; } - @Test(dataProvider = "resourcedata") + @ParameterizedTest + @MethodSource("createResourceData") public void testResources(String style, URL url) throws Throwable { // System.out.println(" testing " + style + " url: " + url); URL[] urls = {url}; @@ -199,30 +205,28 @@ public class MultiReleaseJarURLConnection { // verify we are loading a runtime versioned class MethodType mt = MethodType.methodType(int.class); MethodHandle mh = MethodHandles.lookup().findVirtual(vcls, "getVersion", mt); - Assert.assertEquals((int)mh.invoke(vcls.newInstance()), - style.equals("unversioned") ? 8 : Runtime.version().major()); - + assertEquals(style.equals("unversioned") ? 8 : Runtime.version().major(), (int)mh.invoke(vcls.newInstance())); // now get a resource and verify that we don't have a fragment attached Enumeration vclsUrlEnum = cldr.getResources("version/Version.class"); - Assert.assertTrue(vclsUrlEnum.hasMoreElements()); + assertTrue(vclsUrlEnum.hasMoreElements()); URL vclsUrls[] = new URL[] { vcls.getResource("/version/Version.class"), vcls.getResource("Version.class"), cldr.getResource("version/Version.class"), vclsUrlEnum.nextElement() }; - Assert.assertFalse(vclsUrlEnum.hasMoreElements()); + assertFalse(vclsUrlEnum.hasMoreElements()); for (URL vclsUrl : vclsUrls) { String fragment = vclsUrl.getRef(); - Assert.assertNull(fragment); + assertNull(fragment); // and verify that the url is a reified pointer to the runtime entry String rep = vclsUrl.toString(); //System.out.println(" getResource(\"/version/Version.class\") returned: " + rep); if (style.equals("http")) { - Assert.assertTrue(rep.startsWith("jar:http:")); + assertTrue(rep.startsWith("jar:http:")); } else { - Assert.assertTrue(rep.startsWith("jar:file:")); + assertTrue(rep.startsWith("jar:file:")); } String suffix; if (style.equals("unversioned")) { @@ -231,7 +235,7 @@ public class MultiReleaseJarURLConnection { suffix = ".jar!/META-INF/versions/" + Runtime.version().major() + "/version/Version.class"; } - Assert.assertTrue(rep.endsWith(suffix)); + assertTrue(rep.endsWith(suffix)); } cldr.close(); } diff --git a/test/jdk/sun/net/www/protocol/jrt/Basic.java b/test/jdk/sun/net/www/protocol/jrt/Basic.java index 785920b4a4d..272d82bd4d6 100644 --- a/test/jdk/sun/net/www/protocol/jrt/Basic.java +++ b/test/jdk/sun/net/www/protocol/jrt/Basic.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 @@ -24,21 +24,25 @@ /** * @test * @summary Basic test of jimage protocol handler - * @run testng Basic + * @run junit ${test.main.class} */ +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + import java.io.IOException; import java.net.URL; import java.net.URLConnection; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; -import static org.testng.Assert.*; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; + public class Basic { - @DataProvider(name = "urls") - public Object[][] urls() { + public static Object[][] urls() { Object[][] data = { {"jrt:/java.base/java/lang/Object.class", true}, // Valid resource with and without percent-encoding. @@ -67,7 +71,8 @@ public class Basic { return data; } - @Test(dataProvider = "urls") + @ParameterizedTest + @MethodSource("urls") public void testConnect(String urlString, boolean exists) throws Exception { URL url = new URL(urlString); URLConnection uc = url.openConnection(); @@ -79,32 +84,35 @@ public class Basic { } } - @Test(dataProvider = "urls") + @ParameterizedTest + @MethodSource("urls") public void testInputStream(String urlString, boolean exists) throws Exception { URL url = new URL(urlString); URLConnection uc = url.openConnection(); try { int b = uc.getInputStream().read(); - assertTrue(b != -1); + assertNotEquals(-1, b); if (!exists) fail("IOException expected"); } catch (IOException ioe) { if (exists) fail("IOException not expected"); } } - @Test(dataProvider = "urls") + @ParameterizedTest + @MethodSource("urls") public void testContentLength(String urlString, boolean exists) throws Exception { URL url = new URL(urlString); int len = url.openConnection().getContentLength(); assertTrue((exists && len > 0) || (!exists && len == -1)); } - @Test(dataProvider = "urls") + @ParameterizedTest + @MethodSource("urls") public void testGetContent(String urlString, boolean exists) throws Exception { URL url = new URL(urlString); try { Object obj = url.getContent(); - assertTrue(obj != null); + assertNotNull(obj); if (!exists) fail("IOException expected"); } catch (IOException ioe) { if (exists) fail("IOException not expected"); From 51cd741573330352fc7b7395b1c8aeca8a4bdcf5 Mon Sep 17 00:00:00 2001 From: Jaikiran Pai Date: Tue, 14 Apr 2026 04:45:15 +0000 Subject: [PATCH 035/108] 8381670: Revert the changes to GZIPInputStream related to InputStream.available() usage Reviewed-by: lancea, iris --- .../java/util/zip/GZIPInputStream.java | 76 +++++---------- .../zip/GZIP/GZIPInputStreamAvailable.java | 93 ------------------- 2 files changed, 21 insertions(+), 148 deletions(-) delete mode 100644 test/jdk/java/util/zip/GZIP/GZIPInputStreamAvailable.java 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 ebcb9e3204c..72fb8036f08 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, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2024, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -79,11 +79,7 @@ public class GZIPInputStream extends InflaterInputStream { super(in, createInflater(in, size), size); usesDefaultInflater = true; try { - // 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); + readHeader(in); } catch (IOException ioe) { this.inf.end(); throw ioe; @@ -194,40 +190,12 @@ 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, boolean failOnEOF) throws IOException { + private int readHeader(InputStream this_in) 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 (magic != GZIP_MAGIC) { + if (readUShort(in) != GZIP_MAGIC) { throw new ZipException("Not in GZIP format"); } // Check compression method @@ -290,21 +258,23 @@ public class GZIPInputStream extends InflaterInputStream { (readUInt(in) != (inf.getBytesWritten() & 0xffffffffL))) throw new ZipException("Corrupt GZIP trailer"); + // If there are more bytes available in "in" or + // the leftover in the "inf" is > 26 bytes: + // this.trailer(8) + next.header.min(10) + next.trailer(8) // try concatenated case - int m = 8; // this.trailer - try { - int numNextHeaderBytes = readHeader(in, false); // next.header (if available) - if (numNextHeaderBytes == -1) { - return true; // end of stream reached + if (this.in.available() > 0 || n > 26) { + int m = 8; // this.trailer + try { + m += readHeader(in); // next.header + } catch (IOException ze) { + return true; // ignore any malformed, do nothing } - m += numNextHeaderBytes; - } catch (IOException ze) { - return true; // ignore any malformed, do nothing + inf.reset(); + if (n > m) + inf.setInput(buf, len - n + m, n - m); + return false; } - inf.reset(); - if (n > m) - inf.setInput(buf, len - n + m, n - m); - return false; + return true; } /* @@ -331,16 +301,12 @@ public class GZIPInputStream extends InflaterInputStream { if (b == -1) { throw new EOFException(); } - 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 + // 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); + + ".read() returned value out of range -1..255: " + b); } + return b; } private byte[] tmpbuf = new byte[128]; diff --git a/test/jdk/java/util/zip/GZIP/GZIPInputStreamAvailable.java b/test/jdk/java/util/zip/GZIP/GZIPInputStreamAvailable.java deleted file mode 100644 index f63015e0930..00000000000 --- a/test/jdk/java/util/zip/GZIP/GZIPInputStreamAvailable.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -/* @test - * @bug 7036144 - * @summary Test concatenated gz streams when available() returns zero - * @run junit GZIPInputStreamAvailable - */ - -import org.junit.jupiter.api.Test; - -import java.io.*; -import java.util.*; -import java.util.zip.*; - -import static org.junit.jupiter.api.Assertions.assertArrayEquals; - -public class GZIPInputStreamAvailable { - - public static final int NUM_COPIES = 100; - - @Test - public void testZeroAvailable() throws IOException { - - // Create some uncompressed data and then repeat it NUM_COPIES times - byte[] uncompressed1 = "this is a test".getBytes("ASCII"); - byte[] uncompressedN = repeat(uncompressed1, NUM_COPIES); - - // Compress the original data and then repeat that NUM_COPIES times - byte[] compressed1 = deflate(uncompressed1); - byte[] compressedN = repeat(compressed1, NUM_COPIES); - - // (a) Read back inflated data from a stream where available() is accurate and verify - byte[] readback1 = inflate(new ByteArrayInputStream(compressedN)); - assertArrayEquals(uncompressedN, readback1); - - // (b) Read back inflated data from a stream where available() always returns zero and verify - byte[] readback2 = inflate(new ZeroAvailableStream(new ByteArrayInputStream(compressedN))); - assertArrayEquals(uncompressedN, readback2); - } - - public static byte[] repeat(byte[] data, int count) { - byte[] repeat = new byte[data.length * count]; - int off = 0; - for (int i = 0; i < count; i++) { - System.arraycopy(data, 0, repeat, off, data.length); - off += data.length; - } - return repeat; - } - - public static byte[] deflate(byte[] data) throws IOException { - ByteArrayOutputStream buf = new ByteArrayOutputStream(); - try (GZIPOutputStream out = new GZIPOutputStream(buf)) { - out.write(data); - } - return buf.toByteArray(); - } - - public static byte[] inflate(InputStream in) throws IOException { - return new GZIPInputStream(in).readAllBytes(); - } - - public static class ZeroAvailableStream extends FilterInputStream { - public ZeroAvailableStream(InputStream in) { - super(in); - } - @Override - public int available() { - return 0; - } - } -} From da0ba897775531aed9a228b15e47628f9bd622fa Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Tue, 14 Apr 2026 07:04:35 +0000 Subject: [PATCH 036/108] 8381768: C2: GC barrier stubs miscalculate skipped instructions size Reviewed-by: kvn, phh --- src/hotspot/share/asm/codeBuffer.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/hotspot/share/asm/codeBuffer.cpp b/src/hotspot/share/asm/codeBuffer.cpp index 6a288e0dad0..854cf73049b 100644 --- a/src/hotspot/share/asm/codeBuffer.cpp +++ b/src/hotspot/share/asm/codeBuffer.cpp @@ -858,6 +858,13 @@ csize_t CodeBuffer::figure_expanded_capacities(CodeSection* which_cs, } void CodeBuffer::expand(CodeSection* which_cs, csize_t amount) { +#ifdef ASSERT + // The code below copies contents across temp buffers. The following + // sizes relate to buffer contents, and should not be changed by buffer + // expansion. + int old_total_skipped = total_skipped_instructions_size(); +#endif + #ifndef PRODUCT if (PrintNMethods && (WizardMode || Verbose)) { tty->print("expanding CodeBuffer:"); @@ -916,6 +923,7 @@ void CodeBuffer::expand(CodeSection* which_cs, csize_t amount) { assert(cb_sect->capacity() >= new_capacity[n], "big enough"); address cb_start = cb_sect->start(); cb_sect->set_end(cb_start + this_sect->size()); + cb_sect->register_skipped(this_sect->_skipped_instructions_size); if (this_sect->mark() == nullptr) { cb_sect->clear_mark(); } else { @@ -952,6 +960,9 @@ void CodeBuffer::expand(CodeSection* which_cs, csize_t amount) { this->print_on(tty); } #endif //PRODUCT + + assert(old_total_skipped == total_skipped_instructions_size(), + "Should match: %d == %d", old_total_skipped, total_skipped_instructions_size()); } void CodeBuffer::adjust_internal_address(address from, address to) { From f2f8828188f45d16344c82adfbf951f7409b8825 Mon Sep 17 00:00:00 2001 From: Kieran Farrell Date: Tue, 14 Apr 2026 07:36:55 +0000 Subject: [PATCH 037/108] 8364182: Add jcmd VM.security_properties command Reviewed-by: coffeys, kevinw, alanb --- src/hotspot/share/classfile/vmSymbols.hpp | 1 + .../share/services/diagnosticCommand.cpp | 44 +++++++------- .../share/services/diagnosticCommand.hpp | 9 +++ .../share/classes/java/security/Security.java | 4 ++ .../access/JavaSecurityPropertiesAccess.java | 1 + .../classes/jdk/internal/vm/VMSupport.java | 5 ++ .../dcmd/vm/SecurityPropertiesTest.java | 58 +++++++++++++++++++ 7 files changed, 99 insertions(+), 23 deletions(-) create mode 100644 test/hotspot/jtreg/serviceability/dcmd/vm/SecurityPropertiesTest.java diff --git a/src/hotspot/share/classfile/vmSymbols.hpp b/src/hotspot/share/classfile/vmSymbols.hpp index 2ae42bebcfd..33d00b93365 100644 --- a/src/hotspot/share/classfile/vmSymbols.hpp +++ b/src/hotspot/share/classfile/vmSymbols.hpp @@ -702,6 +702,7 @@ class SerializeClosure; template(appendToClassPathForInstrumentation_name, "appendToClassPathForInstrumentation") \ do_alias(appendToClassPathForInstrumentation_signature, string_void_signature) \ template(serializePropertiesToByteArray_name, "serializePropertiesToByteArray") \ + template(serializeSecurityPropertiesToByteArray_name, "serializeSecurityPropertiesToByteArray") \ template(serializeAgentPropertiesToByteArray_name, "serializeAgentPropertiesToByteArray") \ template(encodeThrowable_name, "encodeThrowable") \ template(encodeThrowable_signature, "(Ljava/lang/Throwable;JI)I") \ diff --git a/src/hotspot/share/services/diagnosticCommand.cpp b/src/hotspot/share/services/diagnosticCommand.cpp index 0846f339227..f2fa114133e 100644 --- a/src/hotspot/share/services/diagnosticCommand.cpp +++ b/src/hotspot/share/services/diagnosticCommand.cpp @@ -99,6 +99,7 @@ void DCmd::register_dcmds() { DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export)); DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export)); DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export)); + DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export)); DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export)); DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export)); DCmdFactory::register_DCmdFactory(new DCmdFactoryImpl(full_export)); @@ -334,8 +335,8 @@ void JVMTIAgentLoadDCmd::execute(DCmdSource source, TRAPS) { #endif // INCLUDE_JVMTI #endif // INCLUDE_SERVICES -void PrintSystemPropertiesDCmd::execute(DCmdSource source, TRAPS) { - // load VMSupport +// helper method for printing system and security properties +static void print_properties(Symbol* method_name, outputStream* out, TRAPS) { Symbol* klass = vmSymbols::jdk_internal_vm_VMSupport(); Klass* k = SystemDictionary::resolve_or_fail(klass, true, CHECK); InstanceKlass* ik = InstanceKlass::cast(k); @@ -343,39 +344,36 @@ void PrintSystemPropertiesDCmd::execute(DCmdSource source, TRAPS) { ik->initialize(THREAD); } if (HAS_PENDING_EXCEPTION) { - java_lang_Throwable::print(PENDING_EXCEPTION, output()); - output()->cr(); + java_lang_Throwable::print(PENDING_EXCEPTION, out); + out->cr(); CLEAR_PENDING_EXCEPTION; return; } - - // invoke the serializePropertiesToByteArray method JavaValue result(T_OBJECT); JavaCallArguments args; - Symbol* signature = vmSymbols::void_byte_array_signature(); - JavaCalls::call_static(&result, - ik, - vmSymbols::serializePropertiesToByteArray_name(), - signature, - &args, - THREAD); + JavaCalls::call_static(&result, ik, method_name, signature, &args, THREAD); + if (HAS_PENDING_EXCEPTION) { - java_lang_Throwable::print(PENDING_EXCEPTION, output()); - output()->cr(); + java_lang_Throwable::print(PENDING_EXCEPTION, out); + out->cr(); CLEAR_PENDING_EXCEPTION; return; } - - // The result should be a [B oop res = result.get_oop(); - assert(res->is_typeArray(), "just checking"); - assert(TypeArrayKlass::cast(res->klass())->element_type() == T_BYTE, "just checking"); - - // copy the bytes to the output stream + assert(res->is_typeArray(), "should be a byte array"); + assert(TypeArrayKlass::cast(res->klass())->element_type() == T_BYTE, "should be a byte array"); typeArrayOop ba = typeArrayOop(res); - jbyte* addr = typeArrayOop(res)->byte_at_addr(0); - output()->print_raw((const char*)addr, ba->length()); + jbyte* addr = ba->byte_at_addr(0); + out->print_raw((const char*)addr, ba->length()); +} + +void PrintSystemPropertiesDCmd::execute(DCmdSource source, TRAPS) { + print_properties(vmSymbols::serializePropertiesToByteArray_name(), output(), THREAD); +} + +void PrintSecurityPropertiesDCmd::execute(DCmdSource source, TRAPS) { + print_properties(vmSymbols::serializeSecurityPropertiesToByteArray_name(), output(), THREAD); } VMUptimeDCmd::VMUptimeDCmd(outputStream* output, bool heap) : diff --git a/src/hotspot/share/services/diagnosticCommand.hpp b/src/hotspot/share/services/diagnosticCommand.hpp index c41e7bf2e2e..97ceb19d0ad 100644 --- a/src/hotspot/share/services/diagnosticCommand.hpp +++ b/src/hotspot/share/services/diagnosticCommand.hpp @@ -94,6 +94,15 @@ public: virtual void execute(DCmdSource source, TRAPS); }; +class PrintSecurityPropertiesDCmd : public DCmd { +public: + PrintSecurityPropertiesDCmd(outputStream* output, bool heap) : DCmd(output, heap) { } + static const char* name() { return "VM.security_properties"; } + static const char* description() { return "Print java.security.Security properties."; } + static const char* impact() { return "Low"; } + virtual void execute(DCmdSource source, TRAPS); +}; + // See also: print_flag in attachListener.cpp class PrintVMFlagsDCmd : public DCmdWithParser { protected: diff --git a/src/java.base/share/classes/java/security/Security.java b/src/java.base/share/classes/java/security/Security.java index 30a22b05742..9faa172c8e7 100644 --- a/src/java.base/share/classes/java/security/Security.java +++ b/src/java.base/share/classes/java/security/Security.java @@ -330,6 +330,10 @@ public final class Security { public Properties getInitialProperties() { return initialSecurityProperties; } + @Override + public Properties getCurrentProperties() { + return props; + } }); } diff --git a/src/java.base/share/classes/jdk/internal/access/JavaSecurityPropertiesAccess.java b/src/java.base/share/classes/jdk/internal/access/JavaSecurityPropertiesAccess.java index a4875f357e3..2d9dbea052a 100644 --- a/src/java.base/share/classes/jdk/internal/access/JavaSecurityPropertiesAccess.java +++ b/src/java.base/share/classes/jdk/internal/access/JavaSecurityPropertiesAccess.java @@ -29,4 +29,5 @@ import java.util.Properties; public interface JavaSecurityPropertiesAccess { Properties getInitialProperties(); + Properties getCurrentProperties(); } diff --git a/src/java.base/share/classes/jdk/internal/vm/VMSupport.java b/src/java.base/share/classes/jdk/internal/vm/VMSupport.java index 197da0d456c..32c358340af 100644 --- a/src/java.base/share/classes/jdk/internal/vm/VMSupport.java +++ b/src/java.base/share/classes/jdk/internal/vm/VMSupport.java @@ -98,6 +98,11 @@ public class VMSupport { return serializePropertiesToByteArray(onlyStrings(System.getProperties())); } + public static byte[] serializeSecurityPropertiesToByteArray() throws IOException { + Properties p = SharedSecrets.getJavaSecurityPropertiesAccess().getCurrentProperties(); + return serializePropertiesToByteArray(onlyStrings(p)); + } + public static byte[] serializeAgentPropertiesToByteArray() throws IOException { return serializePropertiesToByteArray(onlyStrings(getAgentProperties())); } diff --git a/test/hotspot/jtreg/serviceability/dcmd/vm/SecurityPropertiesTest.java b/test/hotspot/jtreg/serviceability/dcmd/vm/SecurityPropertiesTest.java new file mode 100644 index 00000000000..b5a6ead34c1 --- /dev/null +++ b/test/hotspot/jtreg/serviceability/dcmd/vm/SecurityPropertiesTest.java @@ -0,0 +1,58 @@ +/* + * 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. + */ + +import java.security.Security; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.dcmd.CommandExecutor; +import jdk.test.lib.dcmd.JMXExecutor; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +/* + * @test + * @bug 8364182 + * @summary Test of diagnostic command VM.security_properties + * @library /test/lib + * @modules java.base/jdk.internal.misc + * java.compiler + * java.management + * jdk.internal.jvmstat/sun.jvmstat.monitor + * @run junit/othervm SecurityPropertiesTest + */ +public class SecurityPropertiesTest { + private static final String PROPERTY_NAME = "SecurityPropertiesTestPropertyName"; + private static final String PROPERTY_VALUE = "SecurityPropertiesTestPropertyValue"; + + private void run(CommandExecutor executor) { + Security.setProperty(PROPERTY_NAME, PROPERTY_VALUE); + OutputAnalyzer output = executor.execute("VM.security_properties"); + assertTrue(output.getOutput().contains(PROPERTY_NAME + "=" + PROPERTY_VALUE), + "Should contain the test security property"); + } + + @Test + public void jmx() { + run(new JMXExecutor()); + } +} \ No newline at end of file From e71fa9e806525a09e5bbacf37445b8e746333451 Mon Sep 17 00:00:00 2001 From: Christian Hagedorn Date: Tue, 14 Apr 2026 07:50:32 +0000 Subject: [PATCH 038/108] 8382057: C2: "assert((ptr->bottom_type() == Type::TOP) || ((base == Compile::current()->top()) == (ptr->bottom_type()->make_ptr()->isa_oopptr() == nullptr))) failed: base input only needed for heap addresses" with -XX:+CountCompiledCalls Reviewed-by: rcastanedalo, bmaillard, kvn --- src/hotspot/share/opto/doCall.cpp | 6 +-- .../debug/TestCountCompiledCalls.java | 38 +++++++++++++++++++ 2 files changed, 41 insertions(+), 3 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/debug/TestCountCompiledCalls.java diff --git a/src/hotspot/share/opto/doCall.cpp b/src/hotspot/share/opto/doCall.cpp index 9a1da726f00..d6e75f17f50 100644 --- a/src/hotspot/share/opto/doCall.cpp +++ b/src/hotspot/share/opto/doCall.cpp @@ -1081,13 +1081,13 @@ void Parse::catch_inline_exceptions(SafePointNode* ex_map) { #ifndef PRODUCT void Parse::count_compiled_calls(bool at_method_entry, bool is_inline) { - if( CountCompiledCalls ) { - if( at_method_entry ) { + if (CountCompiledCalls) { + if (at_method_entry) { // bump invocation counter if top method (for statistics) if (CountCompiledCalls && depth() == 1) { const TypePtr* addr_type = TypeMetadataPtr::make(method()); Node* adr1 = makecon(addr_type); - Node* adr2 = basic_plus_adr(adr1, adr1, in_bytes(Method::compiled_invocation_counter_offset())); + Node* adr2 = off_heap_plus_addr(adr1, in_bytes(Method::compiled_invocation_counter_offset())); increment_counter(adr2); } } else if (is_inline) { diff --git a/test/hotspot/jtreg/compiler/debug/TestCountCompiledCalls.java b/test/hotspot/jtreg/compiler/debug/TestCountCompiledCalls.java new file mode 100644 index 00000000000..1a3fdf6e9d6 --- /dev/null +++ b/test/hotspot/jtreg/compiler/debug/TestCountCompiledCalls.java @@ -0,0 +1,38 @@ +/* + * 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. + */ + +/* + * @test + * @bug 8382057 + * @requires vm.debug == true + * + * @run main/othervm -Xbatch -XX:+CountCompiledCalls ${test.main.class} + */ + +package compiler.debug; + +public class TestCountCompiledCalls { + public static void main(String[] args) { + System.out.println("Hello World!"); + } +} From 6548fb80ac57098b2fe6fca6b403d3303ece91a0 Mon Sep 17 00:00:00 2001 From: Christian Hagedorn Date: Tue, 14 Apr 2026 07:50:54 +0000 Subject: [PATCH 039/108] 8382050: compiler/intrinsics/klass/CastNullCheckDroppingsTest.java fails with -XX:+StressUnstableIfTraps Reviewed-by: rcastanedalo, qamai --- .../intrinsics/klass/CastNullCheckDroppingsTest.java | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/test/hotspot/jtreg/compiler/intrinsics/klass/CastNullCheckDroppingsTest.java b/test/hotspot/jtreg/compiler/intrinsics/klass/CastNullCheckDroppingsTest.java index 31449eefb33..ff8358c3cd8 100644 --- a/test/hotspot/jtreg/compiler/intrinsics/klass/CastNullCheckDroppingsTest.java +++ b/test/hotspot/jtreg/compiler/intrinsics/klass/CastNullCheckDroppingsTest.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 @@ -25,8 +25,8 @@ * @test NullCheckDroppingsTest * @bug 8054492 * @summary Casting can result in redundant null checks in generated code - * @requires vm.hasJFR - * @requires vm.flavor == "server" & !vm.graal.enabled + * @requires vm.hasJFR & vm.flavor == "server" & !vm.graal.enabled & vm.flagless + * * @library /test/lib * @modules java.base/jdk.internal.misc * java.management @@ -35,9 +35,6 @@ * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox * @run main/othervm -Xbootclasspath/a:. -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI * -Xmixed -XX:-BackgroundCompilation -XX:-TieredCompilation -XX:CompileThreshold=1000 - * -XX:+UnlockExperimentalVMOptions -XX:PerMethodTrapLimit=100 -XX:-StressReflectiveCode - * -XX:+UncommonNullCast -XX:-StressMethodHandleLinkerInlining -XX:TypeProfileLevel=0 - * -XX:-AlwaysIncrementalInline -XX:-StressIncrementalInlining * -XX:CompileCommand=exclude,compiler.intrinsics.klass.CastNullCheckDroppingsTest::runTest * compiler.intrinsics.klass.CastNullCheckDroppingsTest */ From c4199831b9df7341b020dba04f482a0910b76818 Mon Sep 17 00:00:00 2001 From: Thomas Schatzl Date: Tue, 14 Apr 2026 08:08:18 +0000 Subject: [PATCH 040/108] 8381934: Wrong type passed to FREE_C_HEAP_ARRAY deallocating G1CardSetMemoryManager Reviewed-by: iwalulya --- src/hotspot/share/gc/g1/g1CardSetMemory.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hotspot/share/gc/g1/g1CardSetMemory.cpp b/src/hotspot/share/gc/g1/g1CardSetMemory.cpp index 60602ef942b..0da2f90da3f 100644 --- a/src/hotspot/share/gc/g1/g1CardSetMemory.cpp +++ b/src/hotspot/share/gc/g1/g1CardSetMemory.cpp @@ -90,7 +90,7 @@ G1CardSetMemoryManager::~G1CardSetMemoryManager() { for (uint i = 0; i < num_mem_object_types(); i++) { _allocators[i].~G1CardSetAllocator(); } - FREE_C_HEAP_ARRAY(G1CardSetAllocator, _allocators); + FREE_C_HEAP_ARRAY(G1CardSetAllocator, _allocators); } void G1CardSetMemoryManager::free(uint type, void* value) { From 64bbbe751b4d05ee79cb3c384cc3d399a093983f Mon Sep 17 00:00:00 2001 From: Severin Gehwolf Date: Tue, 14 Apr 2026 08:33:28 +0000 Subject: [PATCH 041/108] 8380636: Add static asserts for mirrored library constants Reviewed-by: cnorrbin, dholmes --- src/hotspot/os/linux/cgroupSubsystem_linux.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/hotspot/os/linux/cgroupSubsystem_linux.cpp b/src/hotspot/os/linux/cgroupSubsystem_linux.cpp index 13a005591fb..4a2d75ecdf3 100644 --- a/src/hotspot/os/linux/cgroupSubsystem_linux.cpp +++ b/src/hotspot/os/linux/cgroupSubsystem_linux.cpp @@ -40,6 +40,8 @@ // Inlined from for portability. #ifndef CGROUP2_SUPER_MAGIC # define CGROUP2_SUPER_MAGIC 0x63677270 +#else + STATIC_ASSERT(CGROUP2_SUPER_MAGIC == 0x63677270); #endif // controller names have to match the *_IDX indices From d5d8532ca2ca5cbdcb80af43fb1a192c20af9914 Mon Sep 17 00:00:00 2001 From: Jatin Bhateja Date: Tue, 14 Apr 2026 09:07:14 +0000 Subject: [PATCH 042/108] 8381579: C2: "fatal error: LROTATE: double" when using VectorAPI Reviewed-by: mhaessig, qamai --- src/hotspot/share/prims/vectorSupport.cpp | 74 +++++------ .../jdk/incubator/vector/VectorOperators.java | 10 +- .../TestShiftOpOnFloatingVector.java | 121 ++++++++++++++++++ 3 files changed, 163 insertions(+), 42 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/vectorapi/TestShiftOpOnFloatingVector.java diff --git a/src/hotspot/share/prims/vectorSupport.cpp b/src/hotspot/share/prims/vectorSupport.cpp index 7d80ed327fd..5c6010acdf1 100644 --- a/src/hotspot/share/prims/vectorSupport.cpp +++ b/src/hotspot/share/prims/vectorSupport.cpp @@ -226,7 +226,7 @@ int VectorSupport::vop2ideal(jint id, LaneType lt) { case LT_LONG: return Op_AddL; case LT_FLOAT: return Op_AddF; case LT_DOUBLE: return Op_AddD; - default: fatal("ADD: %s", lanetype2name(lt)); + default: return 0; } break; } @@ -238,7 +238,7 @@ int VectorSupport::vop2ideal(jint id, LaneType lt) { case LT_LONG: return Op_SubL; case LT_FLOAT: return Op_SubF; case LT_DOUBLE: return Op_SubD; - default: fatal("SUB: %s", lanetype2name(lt)); + default: return 0; } break; } @@ -250,7 +250,7 @@ int VectorSupport::vop2ideal(jint id, LaneType lt) { case LT_LONG: return Op_MulL; case LT_FLOAT: return Op_MulF; case LT_DOUBLE: return Op_MulD; - default: fatal("MUL: %s", lanetype2name(lt)); + default: return 0; } break; } @@ -262,7 +262,7 @@ int VectorSupport::vop2ideal(jint id, LaneType lt) { case LT_LONG: return Op_DivL; case LT_FLOAT: return Op_DivF; case LT_DOUBLE: return Op_DivD; - default: fatal("DIV: %s", lanetype2name(lt)); + default: return 0; } break; } @@ -274,7 +274,7 @@ int VectorSupport::vop2ideal(jint id, LaneType lt) { case LT_LONG: return Op_MinL; case LT_FLOAT: return Op_MinF; case LT_DOUBLE: return Op_MinD; - default: fatal("MIN: %s", lanetype2name(lt)); + default: return 0; } break; } @@ -286,7 +286,7 @@ int VectorSupport::vop2ideal(jint id, LaneType lt) { case LT_LONG: return Op_MaxL; case LT_FLOAT: return Op_MaxF; case LT_DOUBLE: return Op_MaxD; - default: fatal("MAX: %s", lanetype2name(lt)); + default: return 0; } break; } @@ -296,7 +296,7 @@ int VectorSupport::vop2ideal(jint id, LaneType lt) { case LT_SHORT: case LT_INT: case LT_LONG: return Op_UMinV; - default: fatal("MIN: %s", lanetype2name(lt)); + default: return 0; } break; } @@ -306,7 +306,7 @@ int VectorSupport::vop2ideal(jint id, LaneType lt) { case LT_SHORT: case LT_INT: case LT_LONG: return Op_UMaxV; - default: fatal("MAX: %s", lanetype2name(lt)); + default: return 0; } break; } @@ -318,7 +318,7 @@ int VectorSupport::vop2ideal(jint id, LaneType lt) { case LT_LONG: return Op_AbsL; case LT_FLOAT: return Op_AbsF; case LT_DOUBLE: return Op_AbsD; - default: fatal("ABS: %s", lanetype2name(lt)); + default: return 0; } break; } @@ -330,7 +330,7 @@ int VectorSupport::vop2ideal(jint id, LaneType lt) { case LT_LONG: return Op_NegL; case LT_FLOAT: return Op_NegF; case LT_DOUBLE: return Op_NegD; - default: fatal("NEG: %s", lanetype2name(lt)); + default: return 0; } break; } @@ -340,7 +340,7 @@ int VectorSupport::vop2ideal(jint id, LaneType lt) { case LT_SHORT: // fall-through case LT_INT: return Op_AndI; case LT_LONG: return Op_AndL; - default: fatal("AND: %s", lanetype2name(lt)); + default: return 0; } break; } @@ -350,7 +350,7 @@ int VectorSupport::vop2ideal(jint id, LaneType lt) { case LT_SHORT: // fall-through case LT_INT: return Op_OrI; case LT_LONG: return Op_OrL; - default: fatal("OR: %s", lanetype2name(lt)); + default: return 0; } break; } @@ -360,7 +360,7 @@ int VectorSupport::vop2ideal(jint id, LaneType lt) { case LT_SHORT: // fall-through case LT_INT: return Op_XorI; case LT_LONG: return Op_XorL; - default: fatal("XOR: %s", lanetype2name(lt)); + default: return 0; } break; } @@ -368,7 +368,7 @@ int VectorSupport::vop2ideal(jint id, LaneType lt) { switch (lt) { case LT_FLOAT: return Op_SqrtF; case LT_DOUBLE: return Op_SqrtD; - default: fatal("SQRT: %s", lanetype2name(lt)); + default: return 0; } break; } @@ -376,7 +376,7 @@ int VectorSupport::vop2ideal(jint id, LaneType lt) { switch (lt) { case LT_FLOAT: return Op_FmaF; case LT_DOUBLE: return Op_FmaD; - default: fatal("FMA: %s", lanetype2name(lt)); + default: return 0; } break; } @@ -386,7 +386,7 @@ int VectorSupport::vop2ideal(jint id, LaneType lt) { case LT_SHORT: // fall-through case LT_INT: return Op_LShiftI; case LT_LONG: return Op_LShiftL; - default: fatal("LSHIFT: %s", lanetype2name(lt)); + default: return 0; } break; } @@ -396,7 +396,7 @@ int VectorSupport::vop2ideal(jint id, LaneType lt) { case LT_SHORT: // fall-through case LT_INT: return Op_RShiftI; case LT_LONG: return Op_RShiftL; - default: fatal("RSHIFT: %s", lanetype2name(lt)); + default: return 0; } break; } @@ -406,7 +406,7 @@ int VectorSupport::vop2ideal(jint id, LaneType lt) { case LT_SHORT: return Op_URShiftS; case LT_INT: return Op_URShiftI; case LT_LONG: return Op_URShiftL; - default: fatal("URSHIFT: %s", lanetype2name(lt)); + default: return 0; } break; } @@ -416,7 +416,7 @@ int VectorSupport::vop2ideal(jint id, LaneType lt) { case LT_SHORT: // fall-through case LT_INT: // fall-through case LT_LONG: return Op_RotateLeft; - default: fatal("LROTATE: %s", lanetype2name(lt)); + default: return 0; } break; } @@ -426,7 +426,7 @@ int VectorSupport::vop2ideal(jint id, LaneType lt) { case LT_SHORT: // fall-through case LT_INT: // fall-through case LT_LONG: return Op_RotateRight; - default: fatal("RROTATE: %s", lanetype2name(lt)); + default: return 0; } break; } @@ -438,7 +438,7 @@ int VectorSupport::vop2ideal(jint id, LaneType lt) { case LT_LONG: // fall-through case LT_FLOAT: // fall-through case LT_DOUBLE: return Op_VectorMaskLastTrue; - default: fatal("MASK_LASTTRUE: %s", lanetype2name(lt)); + default: return 0; } break; } @@ -450,7 +450,7 @@ int VectorSupport::vop2ideal(jint id, LaneType lt) { case LT_LONG: // fall-through case LT_FLOAT: // fall-through case LT_DOUBLE: return Op_VectorMaskFirstTrue; - default: fatal("MASK_FIRSTTRUE: %s", lanetype2name(lt)); + default: return 0; } break; } @@ -462,7 +462,7 @@ int VectorSupport::vop2ideal(jint id, LaneType lt) { case LT_LONG: // fall-through case LT_FLOAT: // fall-through case LT_DOUBLE: return Op_VectorMaskTrueCount; - default: fatal("MASK_TRUECOUNT: %s", lanetype2name(lt)); + default: return 0; } break; } @@ -474,7 +474,7 @@ int VectorSupport::vop2ideal(jint id, LaneType lt) { case LT_LONG: // fall-through case LT_FLOAT: // fall-through case LT_DOUBLE: return Op_VectorMaskToLong; - default: fatal("MASK_TOLONG: %s", lanetype2name(lt)); + default: return 0; } break; } @@ -486,7 +486,7 @@ int VectorSupport::vop2ideal(jint id, LaneType lt) { case LT_LONG: // fall-through case LT_FLOAT: // fall-through case LT_DOUBLE: return Op_ExpandV; - default: fatal("EXPAND: %s", lanetype2name(lt)); + default: return 0; } break; } @@ -498,7 +498,7 @@ int VectorSupport::vop2ideal(jint id, LaneType lt) { case LT_LONG: // fall-through case LT_FLOAT: // fall-through case LT_DOUBLE: return Op_CompressV; - default: fatal("COMPRESS: %s", lanetype2name(lt)); + default: return 0; } break; } @@ -510,7 +510,7 @@ int VectorSupport::vop2ideal(jint id, LaneType lt) { case LT_LONG: // fall-through case LT_FLOAT: // fall-through case LT_DOUBLE: return Op_CompressM; - default: fatal("MASK_COMPRESS: %s", lanetype2name(lt)); + default: return 0; } break; } @@ -520,7 +520,7 @@ int VectorSupport::vop2ideal(jint id, LaneType lt) { case LT_SHORT: // for byte and short types temporarily case LT_INT: return Op_PopCountI; case LT_LONG: return Op_PopCountL; - default: fatal("BILT_COUNT: %s", lanetype2name(lt)); + default: return 0; } break; } @@ -530,7 +530,7 @@ int VectorSupport::vop2ideal(jint id, LaneType lt) { case LT_SHORT: case LT_INT: return Op_CountTrailingZerosI; case LT_LONG: return Op_CountTrailingZerosL; - default: fatal("TZ_COUNT: %s", lanetype2name(lt)); + default: return 0; } break; } @@ -540,7 +540,7 @@ int VectorSupport::vop2ideal(jint id, LaneType lt) { case LT_SHORT: case LT_INT: return Op_CountLeadingZerosI; case LT_LONG: return Op_CountLeadingZerosL; - default: fatal("LZ_COUNT: %s", lanetype2name(lt)); + default: return 0; } break; } @@ -550,7 +550,7 @@ int VectorSupport::vop2ideal(jint id, LaneType lt) { case LT_SHORT: // Op_ReverseI for byte and short case LT_INT: return Op_ReverseI; case LT_LONG: return Op_ReverseL; - default: fatal("REVERSE: %s", lanetype2name(lt)); + default: return 0; } break; } @@ -565,7 +565,7 @@ int VectorSupport::vop2ideal(jint id, LaneType lt) { case LT_BYTE: // Intentionally fall-through case LT_INT: return Op_ReverseBytesI; case LT_LONG: return Op_ReverseBytesL; - default: fatal("REVERSE_BYTES: %s", lanetype2name(lt)); + default: return 0; } break; } @@ -576,7 +576,7 @@ int VectorSupport::vop2ideal(jint id, LaneType lt) { case LT_SHORT: // fall-through case LT_INT: // fall-through case LT_LONG: return Op_SaturatingAddV; - default: fatal("S[U]ADD: %s", lanetype2name(lt)); + default: return 0; } break; } @@ -587,7 +587,7 @@ int VectorSupport::vop2ideal(jint id, LaneType lt) { case LT_SHORT: // fall-through case LT_INT: // fall-through case LT_LONG: return Op_SaturatingSubV; - default: fatal("S[U}SUB: %s", lanetype2name(lt)); + default: return 0; } break; } @@ -595,7 +595,7 @@ int VectorSupport::vop2ideal(jint id, LaneType lt) { switch (lt) { case LT_INT: case LT_LONG: return Op_CompressBits; - default: fatal("COMPRESS_BITS: %s", lanetype2name(lt)); + default: return 0; } break; } @@ -603,7 +603,7 @@ int VectorSupport::vop2ideal(jint id, LaneType lt) { switch (lt) { case LT_INT: case LT_LONG: return Op_ExpandBits; - default: fatal("EXPAND_BITS: %s", lanetype2name(lt)); + default: return 0; } break; } @@ -627,7 +627,7 @@ int VectorSupport::vop2ideal(jint id, LaneType lt) { case VECTOR_OP_EXPM1: // fall-through case VECTOR_OP_HYPOT: return 0; // not supported; should be handled in Java code - default: fatal("unknown op: %d", vop); + default: return 0; } return 0; // Unimplemented } diff --git a/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/VectorOperators.java b/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/VectorOperators.java index 2f2d33ab130..cc5a7ccbdef 100644 --- a/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/VectorOperators.java +++ b/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/VectorOperators.java @@ -560,15 +560,15 @@ public final class VectorOperators { /** Produce {@code a<<(n&(ESIZE*8-1))}. Integral only. */ - public static final /*bitwise*/ Binary LSHL = binary("LSHL", "<<", VectorSupport.VECTOR_OP_LSHIFT, VO_SHIFT); + public static final /*bitwise*/ Binary LSHL = binary("LSHL", "<<", VectorSupport.VECTOR_OP_LSHIFT, VO_SHIFT+VO_NOFP); /** Produce {@code a>>(n&(ESIZE*8-1))}. Integral only. */ - public static final /*bitwise*/ Binary ASHR = binary("ASHR", ">>", VectorSupport.VECTOR_OP_RSHIFT, VO_SHIFT); + public static final /*bitwise*/ Binary ASHR = binary("ASHR", ">>", VectorSupport.VECTOR_OP_RSHIFT, VO_SHIFT+VO_NOFP); /** Produce {@code (a&EMASK)>>>(n&(ESIZE*8-1))}. Integral only. */ - public static final /*bitwise*/ Binary LSHR = binary("LSHR", ">>>", VectorSupport.VECTOR_OP_URSHIFT, VO_SHIFT); + public static final /*bitwise*/ Binary LSHR = binary("LSHR", ">>>", VectorSupport.VECTOR_OP_URSHIFT, VO_SHIFT+VO_NOFP); /** Produce {@code rotateLeft(a,n)}. Integral only. */ - public static final /*bitwise*/ Binary ROL = binary("ROL", "rotateLeft", VectorSupport.VECTOR_OP_LROTATE, VO_SHIFT); + public static final /*bitwise*/ Binary ROL = binary("ROL", "rotateLeft", VectorSupport.VECTOR_OP_LROTATE, VO_SHIFT+VO_NOFP); /** Produce {@code rotateRight(a,n)}. Integral only. */ - public static final /*bitwise*/ Binary ROR = binary("ROR", "rotateRight", VectorSupport.VECTOR_OP_RROTATE, VO_SHIFT); + public static final /*bitwise*/ Binary ROR = binary("ROR", "rotateRight", VectorSupport.VECTOR_OP_RROTATE, VO_SHIFT+VO_NOFP); /** Produce {@code compress(a,n)}. Integral, {@code int} and {@code long}, only. * @since 19 */ diff --git a/test/hotspot/jtreg/compiler/vectorapi/TestShiftOpOnFloatingVector.java b/test/hotspot/jtreg/compiler/vectorapi/TestShiftOpOnFloatingVector.java new file mode 100644 index 00000000000..ea1878bcb53 --- /dev/null +++ b/test/hotspot/jtreg/compiler/vectorapi/TestShiftOpOnFloatingVector.java @@ -0,0 +1,121 @@ +/* + * 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. + */ + +package compiler.vectorapi; + +import jdk.incubator.vector.*; + +/* + * @test + * @bug 8381579 + * @summary Verify shift operations on floating-point vectors throw UnsupportedOperationException. + * @modules jdk.incubator.vector + * @library /test/lib / + * @run main/othervm -Xbatch -XX:-TieredCompilation + * compiler.vectorapi.TestShiftOpOnFloatingVector + */ + +public class TestShiftOpOnFloatingVector { + + static final int ITERATIONS = 4_000; + static final int ARRAY_LEN = 100; + + static void testROL() { + double[] arr = new double[ARRAY_LEN]; + for (int i = 0; i < arr.length; i++) { + arr[i] = i * 1.5 + 7.89; + } + for (int i = 0; i < arr.length; i += DoubleVector.SPECIES_PREFERRED.length()) { + DoubleVector v = DoubleVector.fromArray(DoubleVector.SPECIES_PREFERRED, arr, i); + DoubleVector r = v.lanewise(VectorOperators.ROL, 7); + r.intoArray(arr, i); + } + } + + static void testROR() { + double[] arr = new double[ARRAY_LEN]; + for (int i = 0; i < arr.length; i++) { + arr[i] = i * 1.5 + 7.89; + } + for (int i = 0; i < arr.length; i += DoubleVector.SPECIES_PREFERRED.length()) { + DoubleVector v = DoubleVector.fromArray(DoubleVector.SPECIES_PREFERRED, arr, i); + DoubleVector r = v.lanewise(VectorOperators.ROR, 3); + r.intoArray(arr, i); + } + } + + static void testLSHL() { + float[] arr = new float[ARRAY_LEN]; + for (int i = 0; i < arr.length; i++) { + arr[i] = i * 0.5f + 1.0f; + } + for (int i = 0; i < arr.length; i += FloatVector.SPECIES_PREFERRED.length()) { + FloatVector v = FloatVector.fromArray(FloatVector.SPECIES_PREFERRED, arr, i); + FloatVector r = v.lanewise(VectorOperators.LSHL, 2); + r.intoArray(arr, i); + } + } + + static void testLSHR() { + float[] arr = new float[ARRAY_LEN]; + for (int i = 0; i < arr.length; i++) { + arr[i] = i * 0.3f + 2.0f; + } + for (int i = 0; i < arr.length; i += FloatVector.SPECIES_PREFERRED.length()) { + FloatVector v = FloatVector.fromArray(FloatVector.SPECIES_PREFERRED, arr, i); + FloatVector r = v.lanewise(VectorOperators.LSHR, 5); + r.intoArray(arr, i); + } + } + + static void testASHR() { + double[] arr = new double[ARRAY_LEN]; + for (int i = 0; i < arr.length; i++) { + arr[i] = i * 2.0 + 3.0; + } + for (int i = 0; i < arr.length; i += DoubleVector.SPECIES_PREFERRED.length()) { + DoubleVector v = DoubleVector.fromArray(DoubleVector.SPECIES_PREFERRED, arr, i); + DoubleVector r = v.lanewise(VectorOperators.ASHR, 4); + r.intoArray(arr, i); + } + } + + static void runTest(Runnable test, String name) { + for (int i = 0; i < ITERATIONS; i++) { + try { + test.run(); + throw new AssertionError(name + ": Expected UnsupportedOperationException was not thrown"); + } catch (UnsupportedOperationException e) { + // expected + } + } + } + + public static void main(String[] args) { + runTest(TestShiftOpOnFloatingVector::testROL, "ROL"); + runTest(TestShiftOpOnFloatingVector::testROR, "ROR"); + runTest(TestShiftOpOnFloatingVector::testLSHL, "LSHL"); + runTest(TestShiftOpOnFloatingVector::testLSHR, "LSHR"); + runTest(TestShiftOpOnFloatingVector::testASHR, "ASHR"); + } +} From 540c714eea7bd5f1f15b59eaa05819b12c948aac Mon Sep 17 00:00:00 2001 From: Ivan Walulya Date: Tue, 14 Apr 2026 10:51:58 +0000 Subject: [PATCH 043/108] 8382084: Use dynamic chunk sizes in PartialArraySplitter Co-authored-by: Thomas Schatzl Reviewed-by: ayang, tschatzl --- src/hotspot/share/gc/g1/g1ConcurrentMark.cpp | 7 +++--- src/hotspot/share/gc/g1/g1FullGCMarker.cpp | 4 ++-- .../share/gc/g1/g1ParScanThreadState.cpp | 4 ++-- .../share/gc/parallel/psCompactionManager.cpp | 6 ++--- .../share/gc/parallel/psPromotionManager.cpp | 6 ++--- .../share/gc/shared/partialArraySplitter.cpp | 7 +++--- .../share/gc/shared/partialArraySplitter.hpp | 10 ++++---- .../gc/shared/partialArraySplitter.inline.hpp | 13 ++++++---- .../share/gc/shared/partialArrayState.cpp | 7 ++++-- .../share/gc/shared/partialArrayState.hpp | 8 +++++-- .../gc/shared/partialArrayTaskStepper.cpp | 5 ++-- .../gc/shared/partialArrayTaskStepper.hpp | 19 ++++++--------- .../shared/partialArrayTaskStepper.inline.hpp | 24 ++++++++----------- .../shared/test_partialArrayTaskStepper.cpp | 14 ++++++----- 14 files changed, 68 insertions(+), 66 deletions(-) diff --git a/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp b/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp index a72d5fc5cf9..dbb5ba509a2 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp @@ -2173,8 +2173,7 @@ void G1CMTask::reset_for_restart() { void G1CMTask::register_partial_array_splitter() { ::new (&_partial_array_splitter) PartialArraySplitter(_cm->partial_array_state_manager(), - _cm->max_num_tasks(), - ObjArrayMarkingStride); + _cm->max_num_tasks()); } void G1CMTask::unregister_partial_array_splitter() { @@ -2359,7 +2358,7 @@ size_t G1CMTask::start_partial_array_processing(objArrayOop obj) { process_klass(obj->klass()); size_t array_length = obj->length(); - size_t initial_chunk_size = _partial_array_splitter.start(_task_queue, obj, nullptr, array_length); + size_t initial_chunk_size = _partial_array_splitter.start(_task_queue, obj, nullptr, array_length, ObjArrayMarkingStride); process_array_chunk(obj, 0, initial_chunk_size); @@ -2917,7 +2916,7 @@ G1CMTask::G1CMTask(uint worker_id, _cm(cm), _mark_bitmap(nullptr), _task_queue(task_queue), - _partial_array_splitter(_cm->partial_array_state_manager(), _cm->max_num_tasks(), ObjArrayMarkingStride), + _partial_array_splitter(_cm->partial_array_state_manager(), _cm->max_num_tasks()), _mark_stats_cache(mark_stats, G1RegionMarkStatsCache::RegionMarkStatsCacheSize), _calls(0), _time_target_ms(0.0), diff --git a/src/hotspot/share/gc/g1/g1FullGCMarker.cpp b/src/hotspot/share/gc/g1/g1FullGCMarker.cpp index 2b0b78ac1ce..5af1eae92c5 100644 --- a/src/hotspot/share/gc/g1/g1FullGCMarker.cpp +++ b/src/hotspot/share/gc/g1/g1FullGCMarker.cpp @@ -39,7 +39,7 @@ G1FullGCMarker::G1FullGCMarker(G1FullCollector* collector, _worker_id(worker_id), _bitmap(collector->mark_bitmap()), _task_queue(), - _partial_array_splitter(collector->partial_array_state_manager(), collector->workers(), ObjArrayMarkingStride), + _partial_array_splitter(collector->partial_array_state_manager(), collector->workers()), _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), @@ -65,7 +65,7 @@ void G1FullGCMarker::start_partial_array_processing(objArrayOop obj) { // 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); + size_t initial_chunk_size = _partial_array_splitter.start(task_queue(), obj, nullptr, array_length, ObjArrayMarkingStride); process_array_chunk(obj, 0, initial_chunk_size); } } diff --git a/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp b/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp index 2b0e6ce9cf4..52c8d4d4389 100644 --- a/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp +++ b/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp @@ -78,7 +78,7 @@ G1ParScanThreadState::G1ParScanThreadState(G1CollectedHeap* g1h, _surviving_young_words(nullptr), _surviving_words_length(collection_set->young_region_length() + 1), _old_gen_is_full(false), - _partial_array_splitter(g1h->partial_array_state_manager(), num_workers, ParGCArrayScanChunk), + _partial_array_splitter(g1h->partial_array_state_manager(), num_workers), _string_dedup_requests(), _max_num_optional_regions(collection_set->num_optional_regions()), _numa(g1h->numa()), @@ -253,7 +253,7 @@ void G1ParScanThreadState::start_partial_objarray(oop from_obj, size_t array_length = to_array->length(); size_t initial_chunk_size = // The source array is unused when processing states. - _partial_array_splitter.start(_task_queue, nullptr, to_array, array_length); + _partial_array_splitter.start(_task_queue, nullptr, to_array, array_length, ParGCArrayScanChunk); assert(_scanner.skip_card_mark_set(), "must be"); // Process the initial chunk. No need to process the type in the diff --git a/src/hotspot/share/gc/parallel/psCompactionManager.cpp b/src/hotspot/share/gc/parallel/psCompactionManager.cpp index 0108f1a9762..048355bfad3 100644 --- a/src/hotspot/share/gc/parallel/psCompactionManager.cpp +++ b/src/hotspot/share/gc/parallel/psCompactionManager.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 @@ -58,7 +58,7 @@ PreservedMarksSet* ParCompactionManager::_preserved_marks_set = nullptr; ParCompactionManager::ParCompactionManager(PreservedMarks* preserved_marks, ReferenceProcessor* ref_processor, uint parallel_gc_threads) - :_partial_array_splitter(_partial_array_state_manager, parallel_gc_threads, ObjArrayMarkingStride), + :_partial_array_splitter(_partial_array_state_manager, parallel_gc_threads), _mark_and_push_closure(this, ref_processor) { ParallelScavengeHeap* heap = ParallelScavengeHeap::heap(); @@ -126,7 +126,7 @@ void ParCompactionManager::push_objArray(oop obj) { objArrayOop obj_array = objArrayOop(obj); size_t array_length = obj_array->length(); size_t initial_chunk_size = - _partial_array_splitter.start(&_marking_stack, obj_array, nullptr, array_length); + _partial_array_splitter.start(&_marking_stack, obj_array, nullptr, array_length, ObjArrayMarkingStride); follow_array(obj_array, 0, initial_chunk_size); } diff --git a/src/hotspot/share/gc/parallel/psPromotionManager.cpp b/src/hotspot/share/gc/parallel/psPromotionManager.cpp index 39fcc5556c6..ac22430aa4c 100644 --- a/src/hotspot/share/gc/parallel/psPromotionManager.cpp +++ b/src/hotspot/share/gc/parallel/psPromotionManager.cpp @@ -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 @@ -158,7 +158,7 @@ PartialArrayTaskStats* PSPromotionManager::partial_array_task_stats() { // Most members are initialized either by initialize() or reset(). PSPromotionManager::PSPromotionManager() - : _partial_array_splitter(_partial_array_state_manager, ParallelGCThreads, ParGCArrayScanChunk) + : _partial_array_splitter(_partial_array_state_manager, ParallelGCThreads) { // We set the old lab's start array. _old_lab.set_start_array(old_gen()->start_array()); @@ -273,7 +273,7 @@ void PSPromotionManager::push_objArray(oop old_obj, oop new_obj) { size_t array_length = to_array->length(); size_t initial_chunk_size = // The source array is unused when processing states. - _partial_array_splitter.start(&_claimed_stack_depth, nullptr, to_array, array_length); + _partial_array_splitter.start(&_claimed_stack_depth, nullptr, to_array, array_length, ParGCArrayScanChunk); process_array_chunk(to_array, 0, initial_chunk_size); } diff --git a/src/hotspot/share/gc/shared/partialArraySplitter.cpp b/src/hotspot/share/gc/shared/partialArraySplitter.cpp index d1833872683..04884d5e666 100644 --- a/src/hotspot/share/gc/shared/partialArraySplitter.cpp +++ b/src/hotspot/share/gc/shared/partialArraySplitter.cpp @@ -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 @@ -28,10 +28,9 @@ #include "utilities/macros.hpp" PartialArraySplitter::PartialArraySplitter(PartialArrayStateManager* manager, - uint num_workers, - size_t chunk_size) + uint num_workers) : _allocator(manager), - _stepper(num_workers, chunk_size) + _stepper(num_workers) TASKQUEUE_STATS_ONLY(COMMA _stats()) {} diff --git a/src/hotspot/share/gc/shared/partialArraySplitter.hpp b/src/hotspot/share/gc/shared/partialArraySplitter.hpp index 87cc137e797..340f370d1d5 100644 --- a/src/hotspot/share/gc/shared/partialArraySplitter.hpp +++ b/src/hotspot/share/gc/shared/partialArraySplitter.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 @@ -44,8 +44,7 @@ class PartialArraySplitter { public: PartialArraySplitter(PartialArrayStateManager* manager, - uint num_workers, - size_t chunk_size); + uint num_workers); ~PartialArraySplitter() = default; NONCOPYABLE(PartialArraySplitter); @@ -60,6 +59,8 @@ public: // // length is their length in elements. // + // chunk_size the size of a single chunk. + // // If t is a ScannerTask, queue->push(t) must be a valid expression. The // result of that expression is ignored. // @@ -76,7 +77,8 @@ public: size_t start(Queue* queue, objArrayOop from_array, objArrayOop to_array, - size_t length); + size_t length, + size_t chunk_size); // Result type for claim(), carrying multiple values. Provides the claimed // chunk's start and end array indices. diff --git a/src/hotspot/share/gc/shared/partialArraySplitter.inline.hpp b/src/hotspot/share/gc/shared/partialArraySplitter.inline.hpp index abb0cf13101..7679358e218 100644 --- a/src/hotspot/share/gc/shared/partialArraySplitter.inline.hpp +++ b/src/hotspot/share/gc/shared/partialArraySplitter.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, 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 @@ -39,14 +39,16 @@ template size_t PartialArraySplitter::start(Queue* queue, objArrayOop source, objArrayOop destination, - size_t length) { - PartialArrayTaskStepper::Step step = _stepper.start(length); + size_t length, + size_t chunk_size) { + precond(chunk_size > 0); + PartialArrayTaskStepper::Step step = _stepper.start(length, chunk_size); // Push initial partial scan tasks. if (step._ncreate > 0) { TASKQUEUE_STATS_ONLY(_stats.inc_split();); TASKQUEUE_STATS_ONLY(_stats.inc_pushed(step._ncreate);) PartialArrayState* state = - _allocator.allocate(source, destination, step._index, length, step._ncreate); + _allocator.allocate(source, destination, step._index, length, chunk_size, step._ncreate); for (uint i = 0; i < step._ncreate; ++i) { queue->push(ScannerTask(state)); } @@ -75,9 +77,10 @@ PartialArraySplitter::claim(PartialArrayState* state, Queue* queue, bool stolen) queue->push(ScannerTask(state)); } } + size_t chunk_size = state->chunk_size(); // Release state, decrementing refcount, now that we're done with it. _allocator.release(state); - return Claim{step._index, step._index + _stepper.chunk_size()}; + return Claim{step._index, step._index + chunk_size}; } #endif // SHARE_GC_SHARED_PARTIALARRAYSPLITTER_INLINE_HPP diff --git a/src/hotspot/share/gc/shared/partialArrayState.cpp b/src/hotspot/share/gc/shared/partialArrayState.cpp index aadbc46b7c1..d3b21c2fdaa 100644 --- a/src/hotspot/share/gc/shared/partialArrayState.cpp +++ b/src/hotspot/share/gc/shared/partialArrayState.cpp @@ -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 @@ -35,10 +35,12 @@ PartialArrayState::PartialArrayState(oop src, oop dst, size_t index, size_t length, + size_t chunk_size, size_t initial_refcount) : _source(src), _destination(dst), _length(length), + _chunk_size(chunk_size), _index(index), _refcount(initial_refcount) { @@ -77,6 +79,7 @@ PartialArrayStateAllocator::~PartialArrayStateAllocator() { PartialArrayState* PartialArrayStateAllocator::allocate(oop src, oop dst, size_t index, size_t length, + size_t chunk_size, size_t initial_refcount) { void* p; FreeListEntry* head = _free_list; @@ -87,7 +90,7 @@ PartialArrayState* PartialArrayStateAllocator::allocate(oop src, oop dst, head->~FreeListEntry(); p = head; } - return ::new (p) PartialArrayState(src, dst, index, length, initial_refcount); + return ::new (p) PartialArrayState(src, dst, index, length, chunk_size, initial_refcount); } void PartialArrayStateAllocator::release(PartialArrayState* state) { diff --git a/src/hotspot/share/gc/shared/partialArrayState.hpp b/src/hotspot/share/gc/shared/partialArrayState.hpp index 3dafeb0f14c..75e297526ae 100644 --- a/src/hotspot/share/gc/shared/partialArrayState.hpp +++ b/src/hotspot/share/gc/shared/partialArrayState.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 @@ -61,6 +61,7 @@ class PartialArrayState { oop _source; oop _destination; size_t _length; + size_t _chunk_size; Atomic _index; Atomic _refcount; @@ -68,7 +69,7 @@ class PartialArrayState { PartialArrayState(oop src, oop dst, size_t index, size_t length, - size_t initial_refcount); + size_t chunk_size, size_t initial_refcount); public: // Deleted to require management by allocator object. @@ -89,6 +90,8 @@ public: // The length of the array oop. size_t length() const { return _length; } + size_t chunk_size() const { return _chunk_size; } + // A pointer to the start index for the next segment to process, for atomic // update. Atomic* index_addr() { return &_index; } @@ -130,6 +133,7 @@ public: // from the associated manager. PartialArrayState* allocate(oop src, oop dst, size_t index, size_t length, + size_t chunk_size, size_t initial_refcount); // Decrement the state's refcount. If the new refcount is zero, add the diff --git a/src/hotspot/share/gc/shared/partialArrayTaskStepper.cpp b/src/hotspot/share/gc/shared/partialArrayTaskStepper.cpp index d91ba347d6c..f7d53c9348a 100644 --- a/src/hotspot/share/gc/shared/partialArrayTaskStepper.cpp +++ b/src/hotspot/share/gc/shared/partialArrayTaskStepper.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 @@ -48,8 +48,7 @@ static uint compute_task_fanout(uint task_limit) { return result; } -PartialArrayTaskStepper::PartialArrayTaskStepper(uint n_workers, size_t chunk_size) : - _chunk_size(chunk_size), +PartialArrayTaskStepper::PartialArrayTaskStepper(uint n_workers) : _task_limit(compute_task_limit(n_workers)), _task_fanout(compute_task_fanout(_task_limit)) {} diff --git a/src/hotspot/share/gc/shared/partialArrayTaskStepper.hpp b/src/hotspot/share/gc/shared/partialArrayTaskStepper.hpp index 11499ca2ffe..594cc7b245a 100644 --- a/src/hotspot/share/gc/shared/partialArrayTaskStepper.hpp +++ b/src/hotspot/share/gc/shared/partialArrayTaskStepper.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,19 +40,19 @@ class PartialArrayState; // substantially expand the task queues. class PartialArrayTaskStepper { public: - PartialArrayTaskStepper(uint n_workers, size_t chunk_size); + PartialArrayTaskStepper(uint n_workers); struct Step { size_t _index; // Array index for the step. uint _ncreate; // Number of new tasks to create. }; - // Called with the length of the array to be processed. Returns a Step with - // _index being the end of the initial chunk, which the caller should - // process. This is also the starting index for the next chunk to process. + // Called with the length of the array to be processed and chunk size. + // Returns a Step with _index being the end of the initial chunk, which the + // caller should process. This is also the starting index for the next chunk to process. // The _ncreate is the number of tasks to enqueue to continue processing the // array. If _ncreate is zero then _index will be length. - inline Step start(size_t length) const; + inline Step start(size_t length, size_t chunk_size) const; // Atomically increment state's index by chunk_size() to claim the next // chunk. Returns a Step with _index being the starting index of the @@ -60,21 +60,16 @@ public: // to enqueue. inline Step next(PartialArrayState* state) const; - // The size of chunks to claim for each task. - inline size_t chunk_size() const; - class TestSupport; // For unit tests private: - // Size (number of elements) of a chunk to process. - size_t _chunk_size; // Limit on the number of partial array tasks to create for a given array. uint _task_limit; // Maximum number of new tasks to create when processing an existing task. uint _task_fanout; // For unit tests. - inline Step next_impl(size_t length, Atomic* index_addr) const; + inline Step next_impl(size_t length, size_t chunk_size, Atomic* index_addr) const; }; #endif // SHARE_GC_SHARED_PARTIALARRAYTASKSTEPPER_HPP diff --git a/src/hotspot/share/gc/shared/partialArrayTaskStepper.inline.hpp b/src/hotspot/share/gc/shared/partialArrayTaskStepper.inline.hpp index 6946f7c69ff..538815698f2 100644 --- a/src/hotspot/share/gc/shared/partialArrayTaskStepper.inline.hpp +++ b/src/hotspot/share/gc/shared/partialArrayTaskStepper.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 @@ -31,13 +31,9 @@ #include "utilities/checkedCast.hpp" #include "utilities/debug.hpp" -size_t PartialArrayTaskStepper::chunk_size() const { - return _chunk_size; -} - PartialArrayTaskStepper::Step -PartialArrayTaskStepper::start(size_t length) const { - size_t end = length % _chunk_size; // End of initial chunk. +PartialArrayTaskStepper::start(size_t length, size_t chunk_size) const { + size_t end = length % chunk_size; // End of initial chunk. // If the initial chunk is the complete array, then don't need any partial // tasks. Otherwise, start with just one partial task; see new task // calculation in next(). @@ -45,24 +41,24 @@ PartialArrayTaskStepper::start(size_t length) const { } PartialArrayTaskStepper::Step -PartialArrayTaskStepper::next_impl(size_t length, Atomic* index_addr) const { +PartialArrayTaskStepper::next_impl(size_t length, size_t chunk_size, Atomic* index_addr) const { // The start of the next task is in the state's index. // Atomically increment by the chunk size to claim the associated chunk. // Because we limit the number of enqueued tasks to being no more than the // number of remaining chunks to process, we can use an atomic add for the // claim, rather than a CAS loop. - size_t start = index_addr->fetch_then_add(_chunk_size, memory_order_relaxed); + size_t start = index_addr->fetch_then_add(chunk_size, memory_order_relaxed); assert(start < length, "invariant: start %zu, length %zu", start, length); - assert(((length - start) % _chunk_size) == 0, + assert(((length - start) % chunk_size) == 0, "invariant: start %zu, length %zu, chunk size %zu", - start, length, _chunk_size); + start, length, chunk_size); // Determine the number of new tasks to create. // Zero-based index for this partial task. The initial task isn't counted. - uint task_num = checked_cast(start / _chunk_size); + uint task_num = checked_cast(start / chunk_size); // Number of tasks left to process, including this one. - uint remaining_tasks = checked_cast((length - start) / _chunk_size); + uint remaining_tasks = checked_cast((length - start) / chunk_size); assert(remaining_tasks > 0, "invariant"); // Compute number of pending tasks, including this one. The maximum number // of tasks is a function of task_num (N) and _task_fanout (F). @@ -89,7 +85,7 @@ PartialArrayTaskStepper::next_impl(size_t length, Atomic* index_addr) co PartialArrayTaskStepper::Step PartialArrayTaskStepper::next(PartialArrayState* state) const { - return next_impl(state->length(), state->index_addr()); + return next_impl(state->length(), state->chunk_size(), state->index_addr()); } #endif // SHARE_GC_SHARED_PARTIALARRAYTASKSTEPPER_INLINE_HPP diff --git a/test/hotspot/gtest/gc/shared/test_partialArrayTaskStepper.cpp b/test/hotspot/gtest/gc/shared/test_partialArrayTaskStepper.cpp index 977e7a9d949..3f5b6c0706c 100644 --- a/test/hotspot/gtest/gc/shared/test_partialArrayTaskStepper.cpp +++ b/test/hotspot/gtest/gc/shared/test_partialArrayTaskStepper.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,8 +34,9 @@ class PartialArrayTaskStepper::TestSupport : AllStatic { public: static Step next(const Stepper* stepper, size_t length, + size_t chunk_size, Atomic* to_length_addr) { - return stepper->next_impl(length, to_length_addr); + return stepper->next_impl(length, chunk_size, to_length_addr); } }; @@ -43,23 +44,24 @@ using StepperSupport = PartialArrayTaskStepper::TestSupport; static uint simulate(const Stepper* stepper, size_t length, + size_t chunk_size, Atomic* to_length_addr) { - Step init = stepper->start(length); + Step init = stepper->start(length, chunk_size); to_length_addr->store_relaxed(init._index); uint queue_count = init._ncreate; uint task = 0; for ( ; queue_count > 0; ++task) { --queue_count; - Step step = StepperSupport::next(stepper, length, to_length_addr); + Step step = StepperSupport::next(stepper, length, chunk_size, to_length_addr); queue_count += step._ncreate; } return task; } static void run_test(size_t length, size_t chunk_size, uint n_workers) { - const PartialArrayTaskStepper stepper(n_workers, chunk_size); + const PartialArrayTaskStepper stepper(n_workers); Atomic to_length; - uint tasks = simulate(&stepper, length, &to_length); + uint tasks = simulate(&stepper, length, chunk_size, &to_length); ASSERT_EQ(length, to_length.load_relaxed()); ASSERT_EQ(tasks, length / chunk_size); } From d4bb97c7d63bb9256027446ac5712c175052ab96 Mon Sep 17 00:00:00 2001 From: Ivan Walulya Date: Tue, 14 Apr 2026 14:17:21 +0000 Subject: [PATCH 044/108] 8379677: G1: Micros-System.gc benchmarks regress after moving to PartialArrayStates Co-authored-by: Thomas Schatzl Reviewed-by: tschatzl, ayang --- src/hotspot/share/gc/g1/g1FullGCMarker.cpp | 20 +++++++++++++++---- src/hotspot/share/gc/shared/gc_globals.hpp | 8 +++++++- .../share/gc/shared/jvmFlagConstraintsGC.cpp | 14 ++++++++++++- .../share/gc/shared/jvmFlagConstraintsGC.hpp | 5 +++-- 4 files changed, 39 insertions(+), 8 deletions(-) diff --git a/src/hotspot/share/gc/g1/g1FullGCMarker.cpp b/src/hotspot/share/gc/g1/g1FullGCMarker.cpp index 5af1eae92c5..3be4ab8d839 100644 --- a/src/hotspot/share/gc/g1/g1FullGCMarker.cpp +++ b/src/hotspot/share/gc/g1/g1FullGCMarker.cpp @@ -60,14 +60,26 @@ void G1FullGCMarker::process_partial_array(PartialArrayState* state, bool stolen process_array_chunk(obj_array, claim._start, claim._end); } +static uintx calc_array_stride(uint array_len, uint num_threads) { + precond(num_threads > 0); + + const size_t stride = (array_len + num_threads - 1) / num_threads; + return clamp(stride, ArrayMarkingMinStride, ObjArrayMarkingStride); +} + 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, ObjArrayMarkingStride); - process_array_chunk(obj, 0, initial_chunk_size); + const int array_length = obj->length(); + + if (array_length == 0) { + return; } + + const uintx stride = calc_array_stride(array_length, _collector->workers()); + const size_t initial_chunk_size = _partial_array_splitter.start(task_queue(), obj, nullptr, array_length, stride); + + process_array_chunk(obj, 0, initial_chunk_size); } void G1FullGCMarker::complete_marking(G1ScannerTasksQueueSet* task_queues, diff --git a/src/hotspot/share/gc/shared/gc_globals.hpp b/src/hotspot/share/gc/shared/gc_globals.hpp index 66ca10f1fb6..c9102944197 100644 --- a/src/hotspot/share/gc/shared/gc_globals.hpp +++ b/src/hotspot/share/gc/shared/gc_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 @@ -256,6 +256,12 @@ "before pushing a continuation entry") \ range(1, INT_MAX/2) \ \ + product(uintx, ArrayMarkingMinStride, 64, DIAGNOSTIC, \ + "Minimum chunk size for split array processing during marking; " \ + "the effective stride is clamped between this value " \ + "and ObjArrayMarkingStride.") \ + constraint(ArrayMarkingMinStrideConstraintFunc,AfterErgo) \ + \ product(bool, AggressiveHeap, false, \ "(Deprecated) Optimize heap options for long-running memory " \ "intensive apps") \ diff --git a/src/hotspot/share/gc/shared/jvmFlagConstraintsGC.cpp b/src/hotspot/share/gc/shared/jvmFlagConstraintsGC.cpp index ea3d644d105..4d7ffce3a5d 100644 --- a/src/hotspot/share/gc/shared/jvmFlagConstraintsGC.cpp +++ b/src/hotspot/share/gc/shared/jvmFlagConstraintsGC.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 @@ -414,3 +414,15 @@ JVMFlag::Error GCCardSizeInBytesConstraintFunc(uint value, bool verbose) { return JVMFlag::SUCCESS; } } + +JVMFlag::Error ArrayMarkingMinStrideConstraintFunc(uintx value, bool verbose) { + if (value > ObjArrayMarkingStride) { + JVMFlag::printError(verbose, + "ArrayMarkingMinStride (%zu) must be " + "less than or equal to ObjArrayMarkingStride (%zu)\n", + value, ObjArrayMarkingStride); + return JVMFlag::VIOLATES_CONSTRAINT; + } else { + return JVMFlag::SUCCESS; + } +} diff --git a/src/hotspot/share/gc/shared/jvmFlagConstraintsGC.hpp b/src/hotspot/share/gc/shared/jvmFlagConstraintsGC.hpp index a89f42959e1..1d2f45397aa 100644 --- a/src/hotspot/share/gc/shared/jvmFlagConstraintsGC.hpp +++ b/src/hotspot/share/gc/shared/jvmFlagConstraintsGC.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2024, 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 @@ -66,7 +66,8 @@ f(uintx, SurvivorRatioConstraintFunc) \ f(size_t, MetaspaceSizeConstraintFunc) \ f(size_t, MaxMetaspaceSizeConstraintFunc) \ - f(uint, GCCardSizeInBytesConstraintFunc) + f(uint, GCCardSizeInBytesConstraintFunc) \ + f(uintx, ArrayMarkingMinStrideConstraintFunc) SHARED_GC_CONSTRAINTS(DECLARE_CONSTRAINT) From 49a15be918673ee49c3a02287aa268a87419147a Mon Sep 17 00:00:00 2001 From: Naoto Sato Date: Tue, 14 Apr 2026 16:35:31 +0000 Subject: [PATCH 045/108] 8382020: Time Zone Abbreviation Not Localized for Non-English Locales Reviewed-by: iris --- .../tools/cldrconverter/CLDRConverter.java | 38 +- .../resources/cldr/TimeZoneNamesTest.java | 368 ++++++++++-------- 2 files changed, 237 insertions(+), 169 deletions(-) diff --git a/make/jdk/src/classes/build/tools/cldrconverter/CLDRConverter.java b/make/jdk/src/classes/build/tools/cldrconverter/CLDRConverter.java index de496b3f606..9f42326ef09 100644 --- a/make/jdk/src/classes/build/tools/cldrconverter/CLDRConverter.java +++ b/make/jdk/src/classes/build/tools/cldrconverter/CLDRConverter.java @@ -801,10 +801,7 @@ public class CLDRConverter { String tzKey = Optional.ofNullable((String)handlerSupplMeta.get(tzid)) .orElse(tzid); // Follow link, if needed - String tzLink = null; - for (var k = tzKey; tzdbLinks.containsKey(k);) { - k = tzLink = tzdbLinks.get(k); - } + String tzLink = getTZDBLink(tzKey); if (tzLink == null && tzdbLinks.containsValue(tzKey)) { // reverse link search // this is needed as in tzdb, "America/Buenos_Aires" links to @@ -833,7 +830,7 @@ public class CLDRConverter { } else { // TZDB short names tznames = Arrays.copyOf(tznames, tznames.length); - fillTZDBShortNames(tzid, tznames); + fillTZDBShortNames(tzKey, tznames); names.put(tzid, tznames); } } else { @@ -846,11 +843,13 @@ public class CLDRConverter { String metaKey = METAZONE_ID_PREFIX + meta; data = map.get(metaKey); if (data instanceof String[] tznames) { - // TZDB short names - tznames = Arrays.copyOf((String[])names.getOrDefault(metaKey, tznames), 6); - fillTZDBShortNames(tzid, tznames); - // Keep the metazone prefix here. - names.putIfAbsent(metaKey, tznames); + if (isDefaultZone(meta, tzKey)) { + // Record the metazone names only from the default + // (001) zone, with short names filled from TZDB + tznames = Arrays.copyOf(tznames, tznames.length); + fillTZDBShortNames(tzKey, tznames); + names.put(metaKey, tznames); + } names.put(tzid, meta); if (tzLink != null && availableIds.contains(tzLink)) { names.put(tzLink, meta); @@ -1504,12 +1503,12 @@ public class CLDRConverter { * Fill the TZDB short names if there is no name provided by the CLDR */ private static void fillTZDBShortNames(String tzid, String[] names) { - var val = tzdbShortNamesMap.get(tzdbLinks.getOrDefault(tzid, tzid)); + var val = tzdbShortNamesMap.getOrDefault(tzid, tzdbShortNamesMap.get(getTZDBLink(tzid))); if (val != null) { var format = val.split(NBSP)[0]; var rule = val.split(NBSP)[1]; IntStream.of(1, 3, 5).forEach(i -> { - if (names[i] == null) { + if (names[i] == null || names[i].isEmpty()) { if (format.contains("%s")) { names[i] = switch (i) { case 1 -> format.formatted(tzdbSubstLetters.get(rule + NBSP + STD)); @@ -1531,6 +1530,21 @@ public class CLDRConverter { } } + private static boolean isDefaultZone(String meta, String tzid) { + String zone001 = handlerMetaZones.zidMap().get(meta); + var tzLink = getTZDBLink(tzid); + return canonicalTZMap.getOrDefault(tzid, tzid).equals(zone001) || + tzLink != null && canonicalTZMap.getOrDefault(tzLink, tzLink).equals(zone001); + } + + private static String getTZDBLink(String tzid) { + String tzLink = null; + for (var k = tzid; tzdbLinks.containsKey(k);) { + k = tzLink = tzdbLinks.get(k); + } + return tzLink; + } + /* * Convert TZDB offsets to JDK's offsets, eg, "-08" to "GMT-08:00". * If it cannot recognize the pattern, return the argument as is. diff --git a/test/jdk/sun/util/resources/cldr/TimeZoneNamesTest.java b/test/jdk/sun/util/resources/cldr/TimeZoneNamesTest.java index 4d9b7500d87..b02f988e7c0 100644 --- a/test/jdk/sun/util/resources/cldr/TimeZoneNamesTest.java +++ b/test/jdk/sun/util/resources/cldr/TimeZoneNamesTest.java @@ -24,7 +24,7 @@ /* * @test * @bug 8181157 8202537 8234347 8236548 8261279 8322647 8174269 8346948 - * 8354548 8381379 + * 8354548 8381379 8382020 * @modules jdk.localedata * @summary Checks CLDR time zone names are generated correctly at * either build or runtime @@ -62,164 +62,218 @@ public class TimeZoneNamesTest { // no "metazone" zones (some of them were assigned metazones // over time, thus they are not "generated" per se - {"Asia/Srednekolymsk", Locale.US, "Magadan Standard Time", - "GMT+11:00", - "Magadan Summer Time", - "GMT+12:00", - "Magadan Time", - "GMT+11:00"}, - {"Asia/Srednekolymsk", Locale.FRANCE, "heure normale de Magadan", - "UTC+11:00", - "heure d’été de Magadan", - "UTC+12:00", - "heure de Magadan", - "UTC+11:00"}, - {"America/Punta_Arenas", Locale.US, "Punta Arenas Standard Time", - "GMT-03:00", - "Punta Arenas Daylight Time", - "GMT-02:00", - "Punta Arenas Time", - "GMT-03:00"}, - {"America/Punta_Arenas", Locale.FRANCE, "Punta Arenas (heure standard)", - "UTC−03:00", - "Punta Arenas (heure d’été)", - "UTC−02:00", - "heure : Punta Arenas", - "UTC−03:00"}, - {"Asia/Famagusta", Locale.US, "Eastern European Standard Time", - "EET", - "Eastern European Summer Time", - "EEST", - "Eastern European Time", - "EET"}, - {"Asia/Famagusta", Locale.FRANCE, "heure normale d’Europe de l’Est", - "EET", - "heure d’été d’Europe de l’Est", - "EEST", - "heure d’Europe de l’Est", - "EET"}, - {"Europe/Astrakhan", Locale.US, "Samara Standard Time", - "GMT+04:00", - "Samara Summer Time", - "GMT+05:00", - "Samara Time", - "GMT+04:00"}, - {"Europe/Astrakhan", Locale.FRANCE, "heure normale de Samara", - "UTC+04:00", - "heure d’été de Samara", - "UTC+05:00", - "heure de Samara", - "UTC+04:00"}, - {"Europe/Saratov", Locale.US, "Samara Standard Time", - "GMT+04:00", - "Samara Summer Time", - "GMT+05:00", - "Samara Time", - "GMT+04:00"}, - {"Europe/Saratov", Locale.FRANCE, "heure normale de Samara", - "UTC+04:00", - "heure d’été de Samara", - "UTC+05:00", - "heure de Samara", - "UTC+04:00"}, - {"Europe/Ulyanovsk", Locale.US, "Samara Standard Time", - "GMT+04:00", - "Samara Summer Time", - "GMT+05:00", - "Samara Time", - "GMT+04:00"}, - {"Europe/Ulyanovsk", Locale.FRANCE, "heure normale de Samara", - "UTC+04:00", - "heure d’été de Samara", - "UTC+05:00", - "heure de Samara", - "UTC+04:00"}, - {"Pacific/Bougainville", Locale.US, "Bougainville Standard Time", - "GMT+11:00", - "Bougainville Daylight Time", - "GMT+11:00", - "Bougainville Time", - "GMT+11:00"}, - {"Pacific/Bougainville", Locale.FRANCE, "Bougainville (heure standard)", - "UTC+11:00", - "Bougainville (heure d’été)", - "UTC+11:00", - "heure : Bougainville", - "UTC+11:00"}, - {"Europe/Istanbul", Locale.US, "Türkiye Standard Time", - "GMT+03:00", - "Türkiye Summer Time", - "GMT+04:00", - "Türkiye Time", - "GMT+03:00"}, - {"Europe/Istanbul", Locale.FRANCE, "heure normale de Turquie", - "UTC+03:00", - "heure avancée de Turquie", - "UTC+04:00", - "heure de Turquie", - "UTC+03:00"}, - {"Asia/Istanbul", Locale.US, "Türkiye Standard Time", - "GMT+03:00", - "Türkiye Summer Time", - "GMT+04:00", - "Türkiye Time", - "GMT+03:00"}, - {"Asia/Istanbul", Locale.FRANCE, "heure normale de Turquie", - "UTC+03:00", - "heure avancée de Turquie", - "UTC+04:00", - "heure de Turquie", - "UTC+03:00"}, - {"Turkey", Locale.US, "Türkiye Standard Time", - "GMT+03:00", - "Türkiye Summer Time", - "GMT+04:00", - "Türkiye Time", - "GMT+03:00"}, - {"Turkey", Locale.FRANCE, "heure normale de Turquie", - "UTC+03:00", - "heure avancée de Turquie", - "UTC+04:00", - "heure de Turquie", - "UTC+03:00"}, + {"Asia/Srednekolymsk", Locale.US, + "Magadan Standard Time", + "GMT+11:00", + "Magadan Summer Time", + "GMT+12:00", + "Magadan Time", + "GMT+11:00"}, + {"Asia/Srednekolymsk", Locale.FRANCE, + "heure normale de Magadan", + "UTC+11:00", + "heure d’été de Magadan", + "UTC+12:00", + "heure de Magadan", + "UTC+11:00"}, + {"America/Punta_Arenas", Locale.US, + "Punta Arenas Standard Time", + "GMT-03:00", + "Punta Arenas Daylight Time", + "GMT-02:00", + "Punta Arenas Time", + "GMT-03:00"}, + {"America/Punta_Arenas", Locale.FRANCE, + "Punta Arenas (heure standard)", + "UTC−03:00", + "Punta Arenas (heure d’été)", + "UTC−02:00", + "heure : Punta Arenas", + "UTC−03:00"}, + {"Asia/Famagusta", Locale.US, + "Eastern European Standard Time", + "EET", + "Eastern European Summer Time", + "EEST", + "Eastern European Time", + "EET"}, + {"Asia/Famagusta", Locale.FRANCE, + "heure normale d’Europe de l’Est", + "EET", + "heure d’été d’Europe de l’Est", + "EEST", + "heure d’Europe de l’Est", + "EET"}, + {"Europe/Astrakhan", Locale.US, + "Samara Standard Time", + "GMT+04:00", + "Samara Summer Time", + "GMT+05:00", + "Samara Time", + "GMT+04:00"}, + {"Europe/Astrakhan", Locale.FRANCE, + "heure normale de Samara", + "UTC+04:00", + "heure d’été de Samara", + "UTC+05:00", + "heure de Samara", + "UTC+04:00"}, + {"Europe/Saratov", Locale.US, + "Samara Standard Time", + "GMT+04:00", + "Samara Summer Time", + "GMT+05:00", + "Samara Time", + "GMT+04:00"}, + {"Europe/Saratov", Locale.FRANCE, + "heure normale de Samara", + "UTC+04:00", + "heure d’été de Samara", + "UTC+05:00", + "heure de Samara", + "UTC+04:00"}, + {"Europe/Ulyanovsk", Locale.US, + "Samara Standard Time", + "GMT+04:00", + "Samara Summer Time", + "GMT+05:00", + "Samara Time", + "GMT+04:00"}, + {"Europe/Ulyanovsk", Locale.FRANCE, + "heure normale de Samara", + "UTC+04:00", + "heure d’été de Samara", + "UTC+05:00", + "heure de Samara", + "UTC+04:00"}, + {"Pacific/Bougainville", Locale.US, + "Bougainville Standard Time", + "GMT+11:00", + "Bougainville Daylight Time", + "GMT+11:00", + "Bougainville Time", + "GMT+11:00"}, + {"Pacific/Bougainville", Locale.FRANCE, + "Bougainville (heure standard)", + "UTC+11:00", + "Bougainville (heure d’été)", + "UTC+11:00", + "heure : Bougainville", + "UTC+11:00"}, + {"Europe/Istanbul", Locale.US, + "Türkiye Standard Time", + "GMT+03:00", + "Türkiye Summer Time", + "GMT+04:00", + "Türkiye Time", + "GMT+03:00"}, + {"Europe/Istanbul", Locale.FRANCE, + "heure normale de Turquie", + "UTC+03:00", + "heure avancée de Turquie", + "UTC+04:00", + "heure de Turquie", + "UTC+03:00"}, + {"Asia/Istanbul", Locale.US, + "Türkiye Standard Time", + "GMT+03:00", + "Türkiye Summer Time", + "GMT+04:00", + "Türkiye Time", + "GMT+03:00"}, + {"Asia/Istanbul", Locale.FRANCE, + "heure normale de Turquie", + "UTC+03:00", + "heure avancée de Turquie", + "UTC+04:00", + "heure de Turquie", + "UTC+03:00"}, + {"Turkey", Locale.US, + "Türkiye Standard Time", + "GMT+03:00", + "Türkiye Summer Time", + "GMT+04:00", + "Türkiye Time", + "GMT+03:00"}, + {"Turkey", Locale.FRANCE, + "heure normale de Turquie", + "UTC+03:00", + "heure avancée de Turquie", + "UTC+04:00", + "heure de Turquie", + "UTC+03:00"}, // Short names derived from TZDB at build time - {"Europe/Lisbon", Locale.US, "Western European Standard Time", - "WET", - "Western European Summer Time", - "WEST", - "Western European Time", - "WET"}, - {"Atlantic/Azores", Locale.US, "Azores Standard Time", - "GMT-01:00", - "Azores Summer Time", - "GMT", - "Azores Time", - "GMT-01:00"}, - {"Australia/Perth", Locale.US, "Australian Western Standard Time", - "AWST", - "Australian Western Daylight Time", - "AWDT", - "Australian Western Time", - "AWT"}, - {"Africa/Harare", Locale.US, "Central Africa Time", - "CAT", - "Harare Daylight Time", - "CAT", - "Harare Time", - "CAT"}, - {"Europe/Dublin", Locale.US, "Greenwich Mean Time", - "GMT", - "Irish Standard Time", - "IST", - "Dublin Time", - "GMT"}, - {"Pacific/Gambier", Locale.US, "Gambier Time", - "GMT-09:00", - "Gambier Daylight Time", - "GMT-09:00", - "Gambier Time", - "GMT-09:00"}, + {"Europe/Lisbon", Locale.US, + "Western European Standard Time", + "WET", + "Western European Summer Time", + "WEST", + "Western European Time", + "WET"}, + {"Atlantic/Azores", Locale.US, + "Azores Standard Time", + "GMT-01:00", + "Azores Summer Time", + "GMT", + "Azores Time", + "GMT-01:00"}, + {"Australia/Perth", Locale.US, + "Australian Western Standard Time", + "AWST", + "Australian Western Daylight Time", + "AWDT", + "Australian Western Time", + "AWT"}, + {"Africa/Harare", Locale.US, + "Central Africa Time", + "CAT", + "Harare Daylight Time", + "CAT", + "Harare Time", + "CAT"}, + {"Europe/Dublin", Locale.US, + "Greenwich Mean Time", + "GMT", + "Irish Standard Time", + "IST", + "Dublin Time", + "GMT"}, + {"Pacific/Gambier", Locale.US, + "Gambier Time", + "GMT-09:00", + "Gambier Daylight Time", + "GMT-09:00", + "Gambier Time", + "GMT-09:00"}, + {"America/New_York", Locale.US, + "Eastern Standard Time", + "EST", + "Eastern Daylight Time", + "EDT", + "Eastern Time", + "ET"}, + {"America/New_York", Locale.GERMAN, + "Nordamerikanische Ostküsten-Normalzeit", + "EST", + "Nordamerikanische Ostküsten-Sommerzeit", + "EDT", + "Nordamerikanische Ostküstenzeit", + "ET"}, + {"America/New_York", Locale.JAPANESE, + "米国東部標準時", + "EST", + "米国東部夏時間", + "EDT", + "米国東部時間", + "ET"}, + {"America/New_York", Locale.of("ru"), + "Восточная Америка, стандартное время", + "EST", + "Восточная Америка, летнее время", + "EDT", + "Восточная Америка", + "ET"}, }; } From 44c484c150e66d3be3e865d52601388133380063 Mon Sep 17 00:00:00 2001 From: Ashutosh Mehra Date: Tue, 14 Apr 2026 18:08:23 +0000 Subject: [PATCH 046/108] 8381677: Remove dependence of UseSHA on UseSHAIntrinsics Reviewed-by: kvn, adinn, mdoerr, fyang, amitkumar --- .../cpu/aarch64/vm_version_aarch64.cpp | 4 --- src/hotspot/cpu/ppc/vm_version_ppc.cpp | 5 ---- src/hotspot/cpu/riscv/vm_version_riscv.cpp | 5 ---- src/hotspot/cpu/s390/vm_version_s390.cpp | 4 --- src/hotspot/cpu/x86/vm_version_x86.cpp | 4 --- ...UseSHASpecificTestCaseForSupportedCPU.java | 27 +++---------------- 6 files changed, 3 insertions(+), 46 deletions(-) diff --git a/src/hotspot/cpu/aarch64/vm_version_aarch64.cpp b/src/hotspot/cpu/aarch64/vm_version_aarch64.cpp index 15af2d5c4e2..441bd4859fe 100644 --- a/src/hotspot/cpu/aarch64/vm_version_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/vm_version_aarch64.cpp @@ -437,10 +437,6 @@ void VM_Version::initialize() { FLAG_SET_DEFAULT(UseSHA512Intrinsics, false); } - if (!(UseSHA1Intrinsics || UseSHA256Intrinsics || UseSHA3Intrinsics || UseSHA512Intrinsics)) { - FLAG_SET_DEFAULT(UseSHA, false); - } - if (supports_pmull()) { if (FLAG_IS_DEFAULT(UseGHASHIntrinsics)) { FLAG_SET_DEFAULT(UseGHASHIntrinsics, true); diff --git a/src/hotspot/cpu/ppc/vm_version_ppc.cpp b/src/hotspot/cpu/ppc/vm_version_ppc.cpp index 0b69ef7d25a..3e3b1103c86 100644 --- a/src/hotspot/cpu/ppc/vm_version_ppc.cpp +++ b/src/hotspot/cpu/ppc/vm_version_ppc.cpp @@ -311,11 +311,6 @@ void VM_Version::initialize() { FLAG_SET_DEFAULT(UseSHA3Intrinsics, false); } - if (!(UseSHA1Intrinsics || UseSHA256Intrinsics || UseSHA512Intrinsics)) { - FLAG_SET_DEFAULT(UseSHA, false); - } - - #ifdef COMPILER2 if (FLAG_IS_DEFAULT(UseSquareToLenIntrinsic)) { UseSquareToLenIntrinsic = true; diff --git a/src/hotspot/cpu/riscv/vm_version_riscv.cpp b/src/hotspot/cpu/riscv/vm_version_riscv.cpp index 36f0864da0b..3a6415d52bd 100644 --- a/src/hotspot/cpu/riscv/vm_version_riscv.cpp +++ b/src/hotspot/cpu/riscv/vm_version_riscv.cpp @@ -420,11 +420,6 @@ void VM_Version::c2_initialize() { FLAG_SET_DEFAULT(UseSHA3Intrinsics, false); } - // UseSHA - if (!(UseSHA1Intrinsics || UseSHA256Intrinsics || UseSHA3Intrinsics || UseSHA512Intrinsics)) { - FLAG_SET_DEFAULT(UseSHA, false); - } - // AES if (UseZvkn) { UseAES = UseAES || FLAG_IS_DEFAULT(UseAES); diff --git a/src/hotspot/cpu/s390/vm_version_s390.cpp b/src/hotspot/cpu/s390/vm_version_s390.cpp index 7f5b4870aab..7e9000991ca 100644 --- a/src/hotspot/cpu/s390/vm_version_s390.cpp +++ b/src/hotspot/cpu/s390/vm_version_s390.cpp @@ -289,10 +289,6 @@ void VM_Version::initialize() { FLAG_SET_DEFAULT(UseSHA3Intrinsics, false); } - if (!(UseSHA1Intrinsics || UseSHA256Intrinsics || UseSHA512Intrinsics)) { - FLAG_SET_DEFAULT(UseSHA, false); - } - if (UseSecondarySupersTable && VM_Version::get_model_index() < 5 /* z196/z11 */) { if (!FLAG_IS_DEFAULT(UseSecondarySupersTable)) { warning("UseSecondarySupersTable requires z196 or later."); diff --git a/src/hotspot/cpu/x86/vm_version_x86.cpp b/src/hotspot/cpu/x86/vm_version_x86.cpp index 39a9c618350..a688d834e9b 100644 --- a/src/hotspot/cpu/x86/vm_version_x86.cpp +++ b/src/hotspot/cpu/x86/vm_version_x86.cpp @@ -1373,10 +1373,6 @@ void VM_Version::get_processor_features() { FLAG_SET_DEFAULT(UseSHA3Intrinsics, false); } - if (!(UseSHA1Intrinsics || UseSHA256Intrinsics || UseSHA512Intrinsics || UseSHA3Intrinsics)) { - FLAG_SET_DEFAULT(UseSHA, false); - } - #if COMPILER2_OR_JVMCI int max_vector_size = 0; if (UseAVX == 0 || !os_supports_avx_vectors()) { diff --git a/test/hotspot/jtreg/compiler/intrinsics/sha/cli/testcases/UseSHASpecificTestCaseForSupportedCPU.java b/test/hotspot/jtreg/compiler/intrinsics/sha/cli/testcases/UseSHASpecificTestCaseForSupportedCPU.java index ddcfc7c5f7b..65f3e9b9141 100644 --- a/test/hotspot/jtreg/compiler/intrinsics/sha/cli/testcases/UseSHASpecificTestCaseForSupportedCPU.java +++ b/test/hotspot/jtreg/compiler/intrinsics/sha/cli/testcases/UseSHASpecificTestCaseForSupportedCPU.java @@ -73,10 +73,10 @@ public class UseSHASpecificTestCaseForSupportedCPU @Override protected void verifyOptionValues() throws Throwable { - // Verify that UseSHA is disabled when all UseSHA*Intrinsics are - // disabled. + // Verify that UseSHA is not affected and remains enabled when all UseSHA*Intrinsics + // are disabled. CommandLineOptionTest.verifyOptionValueForSameVM( - DigestOptionsBase.USE_SHA_OPTION, "false", String.format( + DigestOptionsBase.USE_SHA_OPTION, "true", String.format( "'%s' option should be disabled when all UseSHA*Intrinsics are" + " disabled", DigestOptionsBase.USE_SHA_OPTION), DigestOptionsBase.UNLOCK_DIAGNOSTIC_VM_OPTIONS, @@ -89,27 +89,6 @@ public class UseSHASpecificTestCaseForSupportedCPU CommandLineOptionTest.prepareBooleanFlag( DigestOptionsBase.USE_SHA3_INTRINSICS_OPTION, false)); - CommandLineOptionTest.verifyOptionValueForSameVM( - // Verify that UseSHA is disabled when all UseSHA*Intrinsics are - // disabled even if it was explicitly enabled. - DigestOptionsBase.USE_SHA_OPTION, "false", - String.format("'%s' option should be disabled when all " - + "UseSHA*Intrinsics are disabled even if %s flag set " - + "to JVM", DigestOptionsBase.USE_SHA_OPTION, - CommandLineOptionTest.prepareBooleanFlag( - DigestOptionsBase.USE_SHA_OPTION, true)), - DigestOptionsBase.UNLOCK_DIAGNOSTIC_VM_OPTIONS, - CommandLineOptionTest.prepareBooleanFlag( - DigestOptionsBase.USE_SHA_OPTION, true), - CommandLineOptionTest.prepareBooleanFlag( - DigestOptionsBase.USE_SHA1_INTRINSICS_OPTION, false), - CommandLineOptionTest.prepareBooleanFlag( - DigestOptionsBase.USE_SHA256_INTRINSICS_OPTION, false), - CommandLineOptionTest.prepareBooleanFlag( - DigestOptionsBase.USE_SHA512_INTRINSICS_OPTION, false), - CommandLineOptionTest.prepareBooleanFlag( - DigestOptionsBase.USE_SHA3_INTRINSICS_OPTION, false)); - // Verify that explicitly disabled UseSHA option remains disabled even // if all UseSHA*Intrinsics options were enabled. CommandLineOptionTest.verifyOptionValueForSameVM( From bf68776ebcdcbb97faf21517efb3a5e025b29269 Mon Sep 17 00:00:00 2001 From: Ashutosh Mehra Date: Tue, 14 Apr 2026 19:39:32 +0000 Subject: [PATCH 047/108] 8382167: Warning message should be displayed only when UseAESCTRIntrinsics is explicitly enabled by the user Reviewed-by: sviswanathan, kvn --- src/hotspot/cpu/x86/vm_version_x86.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hotspot/cpu/x86/vm_version_x86.cpp b/src/hotspot/cpu/x86/vm_version_x86.cpp index a688d834e9b..cf9de40a237 100644 --- a/src/hotspot/cpu/x86/vm_version_x86.cpp +++ b/src/hotspot/cpu/x86/vm_version_x86.cpp @@ -1187,7 +1187,7 @@ void VM_Version::get_processor_features() { } if (!UseAESIntrinsics) { if (UseAESCTRIntrinsics) { - if (FLAG_IS_DEFAULT(UseAESCTRIntrinsics)) { + if (!FLAG_IS_DEFAULT(UseAESCTRIntrinsics)) { warning("AES-CTR intrinsics require UseAESIntrinsics flag to be enabled. Intrinsics will be disabled."); } FLAG_SET_DEFAULT(UseAESCTRIntrinsics, false); From 764fc09bed335173c2cb4180c946e8593f023ae8 Mon Sep 17 00:00:00 2001 From: William Kemper Date: Tue, 14 Apr 2026 20:23:46 +0000 Subject: [PATCH 048/108] 8381643: Shenandoah: Remove duplicate region filtering logic Reviewed-by: kdnilsen, ruili, xpeng --- .../shenandoahGenerationalHeuristics.cpp | 130 ++++-------------- .../shenandoahGenerationalHeuristics.hpp | 15 +- .../heuristics/shenandoahGlobalHeuristics.cpp | 6 +- .../heuristics/shenandoahGlobalHeuristics.hpp | 6 +- .../heuristics/shenandoahHeuristics.cpp | 12 +- .../heuristics/shenandoahSpaceInfo.hpp | 5 + .../heuristics/shenandoahYoungHeuristics.cpp | 6 +- .../heuristics/shenandoahYoungHeuristics.hpp | 6 +- .../gc/shenandoah/mode/shenandoahMode.cpp | 1 - .../gc/shenandoah/shenandoahGeneration.cpp | 1 + .../gc/shenandoah/shenandoahGeneration.hpp | 2 +- .../gc/shenandoah/shenandoahOldGeneration.hpp | 5 + .../share/gc/shenandoah/shenandoahTrace.cpp | 32 +++-- .../share/gc/shenandoah/shenandoahTrace.hpp | 11 +- src/hotspot/share/jfr/metadata/metadata.xml | 12 +- src/jdk.jfr/share/conf/jfr/default.jfc | 4 + src/jdk.jfr/share/conf/jfr/profile.jfc | 4 + ...tShenandoahEvacuationInformationEvent.java | 4 +- ...stShenandoahPromotionInformationEvent.java | 100 ++++++++++++++ test/lib/jdk/test/lib/jfr/EventNames.java | 1 + 20 files changed, 216 insertions(+), 147 deletions(-) create mode 100644 test/jdk/jdk/jfr/event/gc/detailed/TestShenandoahPromotionInformationEvent.java diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.cpp index a12f48c0877..594367e2972 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.cpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.cpp @@ -69,15 +69,27 @@ ShenandoahGenerationalHeuristics::ShenandoahGenerationalHeuristics(ShenandoahGen : ShenandoahAdaptiveHeuristics(generation), _generation(generation), _add_regions_to_old(0) { } -void ShenandoahGenerationalHeuristics::choose_collection_set(ShenandoahCollectionSet* collection_set) { +void ShenandoahGenerationalHeuristics::choose_collection_set_from_regiondata(ShenandoahCollectionSet* collection_set, + RegionData* data, size_t data_size, + size_t free) { ShenandoahGenerationalHeap* heap = ShenandoahGenerationalHeap::heap(); - assert(collection_set->is_empty(), "Collection set must be empty here"); - _add_regions_to_old = 0; - // Choose the collection set - filter_regions(collection_set); + // Find the amount that will be promoted, regions that will be promoted in + // place, and preselected older regions that will be promoted by evacuation. + ShenandoahInPlacePromotionPlanner in_place_promotions(heap); + compute_evacuation_budgets(in_place_promotions, heap); + + // Call the subclasses to add regions into the collection set. + select_collection_set_regions(collection_set, data, data_size, free); + + // Even if collection_set->is_empty(), we want to adjust budgets, making reserves available to mutator. + adjust_evacuation_budgets(heap, collection_set); + + if (collection_set->has_old_regions()) { + heap->shenandoah_policy()->record_mixed_cycle(); + } if (_generation->is_global()) { // We have just chosen a collection set for a global cycle. The mark bitmap covering old regions is complete, so @@ -91,6 +103,14 @@ void ShenandoahGenerationalHeuristics::choose_collection_set(ShenandoahCollectio // after a global cycle for old regions that were not included in this collection set. heap->old_generation()->transition_old_generation_after_global_gc(); } + + ShenandoahTracer::report_promotion_info(collection_set, + in_place_promotions.humongous_region_stats().count, + in_place_promotions.humongous_region_stats().garbage, + in_place_promotions.humongous_region_stats().free, + in_place_promotions.regular_region_stats().count, + in_place_promotions.regular_region_stats().garbage, + in_place_promotions.regular_region_stats().free); } void ShenandoahGenerationalHeuristics::compute_evacuation_budgets(ShenandoahInPlacePromotionPlanner& in_place_promotions, @@ -216,106 +236,6 @@ void ShenandoahGenerationalHeuristics::compute_evacuation_budgets(ShenandoahInPl // case of a GLOBAL gc. During choose_collection_set() of GLOBAL, old will be expanded on demand. } -void ShenandoahGenerationalHeuristics::filter_regions(ShenandoahCollectionSet* collection_set) { - auto heap = ShenandoahGenerationalHeap::heap(); - const size_t region_size_bytes = ShenandoahHeapRegion::region_size_bytes(); - - // Check all pinned regions have updated status before choosing the collection set. - heap->assert_pinned_region_status(_generation); - - // Step 1. Build up the region candidates we care about, rejecting losers and accepting winners right away. - - const size_t num_regions = heap->num_regions(); - - RegionData* candidates = _region_data; - - size_t cand_idx = 0; - - size_t total_garbage = 0; - - size_t immediate_garbage = 0; - size_t immediate_regions = 0; - - size_t free = 0; - size_t free_regions = 0; - - for (size_t i = 0; i < num_regions; i++) { - ShenandoahHeapRegion* region = heap->get_region(i); - if (!_generation->contains(region)) { - continue; - } - const size_t garbage = region->garbage(); - total_garbage += garbage; - if (region->is_empty()) { - free_regions++; - free += region_size_bytes; - } else if (region->is_regular()) { - if (!region->has_live()) { - // We can recycle it right away and put it in the free set. - immediate_regions++; - immediate_garbage += garbage; - region->make_trash_immediate(); - } else { - // This is our candidate for later consideration. Note that this region - // could still be promoted in place and may not necessarily end up in the - // collection set. - assert(region->get_top_before_promote() == nullptr, "Cannot add region %zu scheduled for in-place-promotion to the collection set", i); - candidates[cand_idx].set_region_and_garbage(region, garbage); - cand_idx++; - } - } else if (region->is_humongous_start()) { - // Reclaim humongous regions here, and count them as the immediate garbage - DEBUG_ONLY(assert_humongous_mark_consistency(region)); - if (!region->has_live()) { - heap->trash_humongous_region_at(region); - - // Count only the start. Continuations would be counted on "trash" path - immediate_regions++; - immediate_garbage += garbage; - } - } else if (region->is_trash()) { - // Count in just trashed humongous continuation regions - immediate_regions++; - immediate_garbage += garbage; - } - } - - // Step 2. Look back at garbage statistics, and decide if we want to collect anything, - // given the amount of immediately reclaimable garbage. If we do, figure out the collection set. - assert(immediate_garbage <= total_garbage, - "Cannot have more immediate garbage than total garbage: " PROPERFMT " vs " PROPERFMT, - PROPERFMTARGS(immediate_garbage), PROPERFMTARGS(total_garbage)); - - const size_t immediate_percent = (total_garbage == 0) ? 0 : (immediate_garbage * 100 / total_garbage); - ShenandoahInPlacePromotionPlanner in_place_promotions(heap); - if (immediate_percent <= ShenandoahImmediateThreshold) { - - // Find the amount that will be promoted, regions that will be promoted in - // place, and preselected older regions that will be promoted by evacuation. - compute_evacuation_budgets(in_place_promotions, heap); - - // 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); - - // Even if collection_set->is_empty(), we want to adjust budgets, making reserves available to mutator. - adjust_evacuation_budgets(heap, collection_set); - - if (collection_set->has_old_regions()) { - heap->shenandoah_policy()->record_mixed_cycle(); - } - } - - collection_set->summarize(total_garbage, immediate_garbage, immediate_regions); - ShenandoahTracer::report_evacuation_info(collection_set, - free_regions, - in_place_promotions.humongous_region_stats().count, - in_place_promotions.regular_region_stats().count, - in_place_promotions.regular_region_stats().garbage, - in_place_promotions.regular_region_stats().free, - immediate_regions, - immediate_garbage); -} - void ShenandoahGenerationalHeuristics::add_tenured_regions_to_collection_set(const size_t old_promotion_reserve, ShenandoahGenerationalHeap *const heap, size_t candidates, AgedRegionData* sorted_regions) { diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.hpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.hpp index 1a47cb756f4..8ea5cdb36c8 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.hpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGenerationalHeuristics.hpp @@ -53,10 +53,13 @@ class ShenandoahGenerationalHeuristics : public ShenandoahAdaptiveHeuristics { public: explicit ShenandoahGenerationalHeuristics(ShenandoahGeneration* generation); - void choose_collection_set(ShenandoahCollectionSet* collection_set) override; - void post_initialize() override; + // Wraps budget computation, subclass region selection, budget adjustment, and tracing. + void choose_collection_set_from_regiondata(ShenandoahCollectionSet* set, + RegionData* data, size_t data_size, + size_t free) override; + private: // Compute evacuation budgets prior to choosing collection set. void compute_evacuation_budgets(ShenandoahInPlacePromotionPlanner& in_place_promotions, ShenandoahHeap* const heap); @@ -84,15 +87,17 @@ private: ShenandoahGenerationalHeap *const heap, size_t candidates, AgedRegionData* sorted_regions); - // Filter and sort remaining regions before adding to collection set. - void filter_regions(ShenandoahCollectionSet* collection_set); - // Adjust evacuation budgets after choosing collection set. On entry, the instance variable _regions_to_xfer // represents regions to be transferred to old based on decisions made in top_off_collection_set() void adjust_evacuation_budgets(ShenandoahHeap* const heap, ShenandoahCollectionSet* const collection_set); protected: + // Subclasses override this to perform generation-specific region selection. + virtual void select_collection_set_regions(ShenandoahCollectionSet* set, + RegionData* data, size_t data_size, + size_t free) = 0; + ShenandoahGeneration* _generation; size_t _add_regions_to_old; diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.cpp index ed25cd2e1a9..9452e8b28cb 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.cpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.cpp @@ -112,9 +112,9 @@ ShenandoahGlobalHeuristics::ShenandoahGlobalHeuristics(ShenandoahGlobalGeneratio : ShenandoahGenerationalHeuristics(generation) { } -void ShenandoahGlobalHeuristics::choose_collection_set_from_regiondata(ShenandoahCollectionSet* cset, - RegionData* data, size_t size, - size_t actual_free) { +void ShenandoahGlobalHeuristics::select_collection_set_regions(ShenandoahCollectionSet* cset, + RegionData* data, size_t size, + size_t actual_free) { QuickSort::sort(data, size, compare_by_garbage); choose_global_collection_set(cset, data, size, actual_free, 0); } diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.hpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.hpp index 8102fa24d14..1e96a665704 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.hpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahGlobalHeuristics.hpp @@ -161,9 +161,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; + void select_collection_set_regions(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 895088381ee..3091b19b600 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.cpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahHeuristics.cpp @@ -29,6 +29,7 @@ #include "gc/shenandoah/shenandoahCollectorPolicy.hpp" #include "gc/shenandoah/shenandoahHeapRegion.inline.hpp" #include "gc/shenandoah/shenandoahMarkingContext.inline.hpp" +#include "gc/shenandoah/shenandoahTrace.hpp" #include "logging/log.hpp" #include "logging/logTag.hpp" #include "runtime/globals_extension.hpp" @@ -79,10 +80,6 @@ void ShenandoahHeuristics::choose_collection_set(ShenandoahCollectionSet* collec ShenandoahHeap* heap = ShenandoahHeap::heap(); assert(collection_set->is_empty(), "Must be empty"); - assert(!heap->mode()->is_generational(), "Wrong heuristic for heap mode"); - - // Check all pinned regions have updated status before choosing the collection set. - heap->assert_pinned_region_status(); // Step 1. Build up the region candidates we care about, rejecting losers and accepting winners right away. @@ -103,6 +100,10 @@ void ShenandoahHeuristics::choose_collection_set(ShenandoahCollectionSet* collec for (size_t i = 0; i < num_regions; i++) { ShenandoahHeapRegion* region = heap->get_region(i); + if (!_space_info->contains(region)) { + continue; + } + size_t garbage = region->garbage(); total_garbage += garbage; @@ -117,6 +118,8 @@ void ShenandoahHeuristics::choose_collection_set(ShenandoahCollectionSet* collec region->make_trash_immediate(); } else { // This is our candidate for later consideration. + assert(region->get_top_before_promote() == nullptr, + "Cannot add region %zu scheduled for in-place-promotion to the collection set", i); candidates[cand_idx].set_region_and_garbage(region, garbage); cand_idx++; } @@ -149,6 +152,7 @@ void ShenandoahHeuristics::choose_collection_set(ShenandoahCollectionSet* collec choose_collection_set_from_regiondata(collection_set, candidates, cand_idx, immediate_garbage + free); } collection_set->summarize(total_garbage, immediate_garbage, immediate_regions); + ShenandoahTracer::report_evacuation_info(collection_set, free_regions, immediate_regions, immediate_garbage); } void ShenandoahHeuristics::start_idle_span() { diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahSpaceInfo.hpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahSpaceInfo.hpp index 6ed05abf0b1..765061a43ed 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahSpaceInfo.hpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahSpaceInfo.hpp @@ -27,6 +27,8 @@ #include "utilities/globalDefinitions.hpp" +class ShenandoahHeapRegion; + /* * The purpose of this interface is to decouple the heuristics from a * direct dependency on the ShenandoahHeap singleton instance. This is @@ -46,6 +48,9 @@ public: // in time within each GC cycle. For certain GC cycles, the value returned may include some bytes allocated before // the start of the current GC cycle. virtual size_t bytes_allocated_since_gc_start() const = 0; + + // Return true if this region belongs to this space. + virtual bool contains(ShenandoahHeapRegion* region) const = 0; }; #endif //SHARE_GC_SHENANDOAH_HEURISTICS_SHENANDOAHSPACEINFO_HPP diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahYoungHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahYoungHeuristics.cpp index 68ffb6592db..27aa9a47510 100644 --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahYoungHeuristics.cpp +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahYoungHeuristics.cpp @@ -37,9 +37,9 @@ ShenandoahYoungHeuristics::ShenandoahYoungHeuristics(ShenandoahYoungGeneration* } -void ShenandoahYoungHeuristics::choose_collection_set_from_regiondata(ShenandoahCollectionSet* cset, - RegionData* data, size_t size, - size_t actual_free) { +void ShenandoahYoungHeuristics::select_collection_set_regions(ShenandoahCollectionSet* cset, + RegionData* data, size_t size, + size_t actual_free) { // See comments in ShenandoahAdaptiveHeuristics::choose_collection_set_from_regiondata(): // we do the same here, but with the following adjustments for generational mode: // diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahYoungHeuristics.hpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahYoungHeuristics.hpp index 806cef673d5..8fabc40693c 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; + void select_collection_set_regions(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/mode/shenandoahMode.cpp b/src/hotspot/share/gc/shenandoah/mode/shenandoahMode.cpp index 5ef21719ed4..1c2c15c40dc 100644 --- a/src/hotspot/share/gc/shenandoah/mode/shenandoahMode.cpp +++ b/src/hotspot/share/gc/shenandoah/mode/shenandoahMode.cpp @@ -26,7 +26,6 @@ #include "gc/shenandoah/heuristics/shenandoahAdaptiveHeuristics.hpp" #include "gc/shenandoah/heuristics/shenandoahAggressiveHeuristics.hpp" #include "gc/shenandoah/heuristics/shenandoahCompactHeuristics.hpp" -#include "gc/shenandoah/heuristics/shenandoahSpaceInfo.hpp" #include "gc/shenandoah/heuristics/shenandoahStaticHeuristics.hpp" #include "gc/shenandoah/mode/shenandoahMode.hpp" diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp index 3d592e9f9be..7d082e4a8b0 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp @@ -301,6 +301,7 @@ void ShenandoahGeneration::prepare_regions_and_collection_set(bool concurrent) { collection_set->clear(); ShenandoahHeapLocker locker(heap->lock()); + heap->assert_pinned_region_status(this); _heuristics->choose_collection_set(collection_set); } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.hpp b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.hpp index 6f32d101152..9f8944127c0 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.hpp @@ -143,7 +143,7 @@ public: virtual bool contains(ShenandoahAffiliation affiliation) const = 0; // Return true if this region is affiliated with this generation. - virtual bool contains(ShenandoahHeapRegion* region) const = 0; + virtual bool contains(ShenandoahHeapRegion* region) const override = 0; // Return true if this object is affiliated with this generation. virtual bool contains(oop obj) const = 0; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.hpp b/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.hpp index 630736190f0..0069d38a84e 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.hpp @@ -213,7 +213,12 @@ public: bool is_concurrent_mark_in_progress() override; bool entry_coalesce_and_fill(); + + // Global collections touch old regions, so the old generation needs to be informed of this. + // The old generation may decide to schedule additional mixed collections, or may decide to + // immediately coalesce-and-fill old objects in regions that were not collected. void transition_old_generation_after_global_gc(); + void prepare_gc() override; void prepare_regions_and_collection_set(bool concurrent) override; void record_success_concurrent(bool abbreviated) override; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahTrace.cpp b/src/hotspot/share/gc/shenandoah/shenandoahTrace.cpp index bbb44348355..c28e572dd6b 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahTrace.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahTrace.cpp @@ -27,9 +27,7 @@ #include "jfr/jfrEvents.hpp" void ShenandoahTracer::report_evacuation_info(const ShenandoahCollectionSet* cset, - size_t free_regions, size_t regions_promoted_humongous, size_t regions_promoted_regular, - size_t regular_promoted_garbage, size_t regular_promoted_free, size_t regions_immediate, - size_t immediate_size) { + size_t free_regions, size_t regions_immediate, size_t immediate_size) { EventShenandoahEvacuationInformation e; if (e.should_commit()) { @@ -37,13 +35,6 @@ void ShenandoahTracer::report_evacuation_info(const ShenandoahCollectionSet* cse e.set_cSetRegions(cset->count()); e.set_cSetUsedBefore(cset->used()); e.set_cSetUsedAfter(cset->live()); - e.set_collectedOld(cset->get_live_bytes_in_old_regions()); - e.set_collectedPromoted(cset->get_live_bytes_in_tenurable_regions()); - e.set_collectedYoung(cset->get_live_bytes_in_untenurable_regions()); - e.set_regionsPromotedHumongous(regions_promoted_humongous); - e.set_regionsPromotedRegular(regions_promoted_regular); - e.set_regularPromotedGarbage(regular_promoted_garbage); - e.set_regularPromotedFree(regular_promoted_free); e.set_freeRegions(free_regions); e.set_regionsImmediate(regions_immediate); e.set_immediateBytes(immediate_size); @@ -51,3 +42,24 @@ void ShenandoahTracer::report_evacuation_info(const ShenandoahCollectionSet* cse e.commit(); } } + +void ShenandoahTracer::report_promotion_info(const ShenandoahCollectionSet* cset, + size_t regions_promoted_humongous, size_t humongous_promoted_garbage, size_t humongous_promoted_free, + size_t regions_promoted_regular, size_t regular_promoted_garbage, size_t regular_promoted_free) { + + EventShenandoahPromotionInformation e; + if (e.should_commit()) { + e.set_gcId(GCId::current()); + e.set_collectedOld(cset->get_live_bytes_in_old_regions()); + e.set_collectedPromoted(cset->get_live_bytes_in_tenurable_regions()); + e.set_collectedYoung(cset->get_live_bytes_in_untenurable_regions()); + e.set_regionsPromotedHumongous(regions_promoted_humongous); + e.set_humongousPromotedGarbage(humongous_promoted_garbage); + e.set_humongousPromotedFree(humongous_promoted_free); + e.set_regionsPromotedRegular(regions_promoted_regular); + e.set_regularPromotedGarbage(regular_promoted_garbage); + e.set_regularPromotedFree(regular_promoted_free); + + e.commit(); + } +} diff --git a/src/hotspot/share/gc/shenandoah/shenandoahTrace.hpp b/src/hotspot/share/gc/shenandoah/shenandoahTrace.hpp index 116968103de..e5c80e0705f 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahTrace.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahTrace.hpp @@ -34,11 +34,14 @@ class ShenandoahTracer : public GCTracer, public CHeapObj { public: ShenandoahTracer() : GCTracer(Shenandoah) {} - // Sends a JFR event (if enabled) summarizing the composition of the collection set + // Sends a JFR event summarizing the composition of the collection set static void report_evacuation_info(const ShenandoahCollectionSet* cset, - size_t free_regions, size_t regions_promoted_humongous, size_t regions_promoted_regular, - size_t regular_promoted_garbage, size_t regular_promoted_free, size_t regions_immediate, - size_t immediate_size); + size_t free_regions, size_t regions_immediate, size_t immediate_size); + + // Sends a JFR event summarizing in-place promotion activity (generational mode only) + static void report_promotion_info(const ShenandoahCollectionSet* cset, + size_t regions_promoted_humongous, size_t humongous_promoted_garbage, size_t humongous_promoted_free, + size_t regions_promoted_regular, size_t regular_promoted_garbage, size_t regular_promoted_free); }; #endif diff --git a/src/hotspot/share/jfr/metadata/metadata.xml b/src/hotspot/share/jfr/metadata/metadata.xml index 2b082165005..09d9e0ccabf 100644 --- a/src/hotspot/share/jfr/metadata/metadata.xml +++ b/src/hotspot/share/jfr/metadata/metadata.xml @@ -1273,16 +1273,22 @@ + + + + + + + + + - - - diff --git a/src/jdk.jfr/share/conf/jfr/default.jfc b/src/jdk.jfr/share/conf/jfr/default.jfc index 554e09261d6..d077157d2a1 100644 --- a/src/jdk.jfr/share/conf/jfr/default.jfc +++ b/src/jdk.jfr/share/conf/jfr/default.jfc @@ -576,6 +576,10 @@ false + + false + + true false diff --git a/src/jdk.jfr/share/conf/jfr/profile.jfc b/src/jdk.jfr/share/conf/jfr/profile.jfc index 108886fb18d..126615af14b 100644 --- a/src/jdk.jfr/share/conf/jfr/profile.jfc +++ b/src/jdk.jfr/share/conf/jfr/profile.jfc @@ -576,6 +576,10 @@ false + + false + + true true diff --git a/test/jdk/jdk/jfr/event/gc/detailed/TestShenandoahEvacuationInformationEvent.java b/test/jdk/jdk/jfr/event/gc/detailed/TestShenandoahEvacuationInformationEvent.java index 5730f4d9ff4..888ff9eb17a 100644 --- a/test/jdk/jdk/jfr/event/gc/detailed/TestShenandoahEvacuationInformationEvent.java +++ b/test/jdk/jdk/jfr/event/gc/detailed/TestShenandoahEvacuationInformationEvent.java @@ -68,8 +68,8 @@ public class TestShenandoahEvacuationInformationEvent { long setUsedAfter = Events.assertField(event, "cSetUsedAfter").atLeast(0L).getValue(); long setUsedBefore = Events.assertField(event, "cSetUsedBefore").atLeast(setUsedAfter).getValue(); long freeRegions = Events.assertField(event, "freeRegions").atLeast(0L).getValue(); - Events.assertField(event, "collectedOld").atLeast(0L).getValue(); - Events.assertField(event, "collectedYoung").atLeast(0L).getValue(); + Events.assertField(event, "regionsImmediate").atLeast(0L).getValue(); + Events.assertField(event, "immediateBytes").atLeast(0L).getValue(); Asserts.assertGreaterThanOrEqual(shenandoahMaxHeapRegionCount, freeRegions + cSetRegions, "numRegions >= freeRegions + cSetRegions"); Asserts.assertGreaterThanOrEqual(shenandoahHeapRegionSize * cSetRegions, setUsedAfter, "ShenandoahHeapRegionSize * cSetRegions >= setUsedAfter"); diff --git a/test/jdk/jdk/jfr/event/gc/detailed/TestShenandoahPromotionInformationEvent.java b/test/jdk/jdk/jfr/event/gc/detailed/TestShenandoahPromotionInformationEvent.java new file mode 100644 index 00000000000..08a360e14fd --- /dev/null +++ b/test/jdk/jdk/jfr/event/gc/detailed/TestShenandoahPromotionInformationEvent.java @@ -0,0 +1,100 @@ +/* + * Copyright Amazon.com Inc. All rights reserved. + * 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. + * + */ + +package jdk.jfr.event.gc.detailed; + +import java.time.Duration; +import java.util.List; +import java.util.Random; + +import jdk.jfr.Recording; +import jdk.jfr.consumer.RecordedEvent; +import jdk.test.lib.Asserts; +import jdk.test.lib.jfr.EventNames; +import jdk.test.lib.jfr.Events; + +/** + * @test + * @requires vm.hasJFR & vm.gc.Shenandoah + * @requires vm.flagless + * @library /test/lib /test/jdk + * @run main/othervm -Xmx64m -XX:+UnlockExperimentalVMOptions -XX:ShenandoahRegionSize=1m -XX:+UseShenandoahGC -XX:ShenandoahGCMode=generational jdk.jfr.event.gc.detailed.TestShenandoahPromotionInformationEvent + */ + +public class TestShenandoahPromotionInformationEvent { + private final static String EVENT_NAME = EventNames.ShenandoahPromotionInformation; + + public static void main(String[] args) throws Exception { + Recording recording = new Recording(); + recording.enable(EVENT_NAME).withThreshold(Duration.ofMillis(0)); + recording.start(); + allocate(); + recording.stop(); + + List events = Events.fromRecording(recording); + Asserts.assertFalse(events.isEmpty(), "No events found"); + for (RecordedEvent event : events) { + if (!Events.isEventType(event, EVENT_NAME)) { + continue; + } + System.out.println("Event: " + event); + + Events.assertField(event, "gcId").getValue(); + Events.assertField(event, "collectedOld").atLeast(0L).getValue(); + Events.assertField(event, "collectedPromoted").atLeast(0L).getValue(); + Events.assertField(event, "collectedYoung").atLeast(0L).getValue(); + Events.assertField(event, "regionsPromotedHumongous").atLeast(0L).getValue(); + Events.assertField(event, "humongousPromotedGarbage").atLeast(0L).getValue(); + Events.assertField(event, "humongousPromotedFree").atLeast(0L).getValue(); + Events.assertField(event, "regionsPromotedRegular").atLeast(0L).getValue(); + Events.assertField(event, "regularPromotedGarbage").atLeast(0L).getValue(); + Events.assertField(event, "regularPromotedFree").atLeast(0L).getValue(); + } + } + + private static void allocate() { + DummyObject[] dummys = new DummyObject[6000]; + + Random r = new Random(0); + long bytesToAllocate = 256 * 1024 * 1024; + int currPos = 0; + while (bytesToAllocate > 0) { + int allocSize = 1000 + (r.nextInt(4000)); + bytesToAllocate -= allocSize; + dummys[currPos] = new DummyObject(allocSize); + currPos = (currPos + r.nextInt(20)) % dummys.length; + } + for (int c = 0; c < dummys.length; c++) { + dummys[c] = null; + } + System.gc(); + } + + public static class DummyObject { + public byte[] payload; + DummyObject(int size) { + payload = new byte[size]; + } + } +} diff --git a/test/lib/jdk/test/lib/jfr/EventNames.java b/test/lib/jdk/test/lib/jfr/EventNames.java index 8b0113f75f4..06ee62a2f7c 100644 --- a/test/lib/jdk/test/lib/jfr/EventNames.java +++ b/test/lib/jdk/test/lib/jfr/EventNames.java @@ -115,6 +115,7 @@ public class EventNames { public static final String ShenandoahHeapRegionInformation = PREFIX + "ShenandoahHeapRegionInformation"; public static final String ShenandoahHeapRegionStateChange = PREFIX + "ShenandoahHeapRegionStateChange"; public static final String ShenandoahEvacuationInformation = PREFIX + "ShenandoahEvacuationInformation"; + public static final String ShenandoahPromotionInformation = PREFIX + "ShenandoahPromotionInformation"; public static final String TenuringDistribution = PREFIX + "TenuringDistribution"; public static final String GarbageCollection = PREFIX + "GarbageCollection"; public static final String ParallelOldGarbageCollection = PREFIX + "ParallelOldGarbageCollection"; From d57bbc026fe140578ee7c317af9738678f4534c6 Mon Sep 17 00:00:00 2001 From: Fei Yang Date: Wed, 15 Apr 2026 00:57:20 +0000 Subject: [PATCH 049/108] 8382087: aarch64: remove unused function declarations in macroAssembler_aarch64.hpp Reviewed-by: mhaessig, ayang, aph --- src/hotspot/cpu/aarch64/assembler_aarch64.hpp | 24 ------------------- .../cpu/aarch64/macroAssembler_aarch64.hpp | 4 ---- 2 files changed, 28 deletions(-) diff --git a/src/hotspot/cpu/aarch64/assembler_aarch64.hpp b/src/hotspot/cpu/aarch64/assembler_aarch64.hpp index ebd8f3a9e03..4c1c8d9bbc8 100644 --- a/src/hotspot/cpu/aarch64/assembler_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/assembler_aarch64.hpp @@ -1000,30 +1000,6 @@ public: f(0b0101010, 31, 25), f(0, 24), sf(offset, 23, 5), f(0, 4), f(cond, 3, 0); } -#define INSN(NAME, cond) \ - void NAME(address dest) { \ - br(cond, dest); \ - } - - INSN(beq, EQ); - INSN(bne, NE); - INSN(bhs, HS); - INSN(bcs, CS); - INSN(blo, LO); - INSN(bcc, CC); - INSN(bmi, MI); - INSN(bpl, PL); - INSN(bvs, VS); - INSN(bvc, VC); - INSN(bhi, HI); - INSN(bls, LS); - INSN(bge, GE); - INSN(blt, LT); - INSN(bgt, GT); - INSN(ble, LE); - INSN(bal, AL); - INSN(bnv, NV); - void br(Condition cc, Label &L); #undef INSN diff --git a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp index dfba2dcff46..a6cc862d05c 100644 --- a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp @@ -890,10 +890,6 @@ public: // thread in the default location (rthread) void reset_last_Java_frame(bool clear_fp); - // Stores - void store_check(Register obj); // store check for obj - register is destroyed afterwards - void store_check(Register obj, Address dst); // same as above, dst is exact store location (reg. is destroyed) - void resolve_jobject(Register value, Register tmp1, Register tmp2); void resolve_global_jobject(Register value, Register tmp1, Register tmp2); From 9ebee751e8ffa319c9ac6b8aababd3daa994b805 Mon Sep 17 00:00:00 2001 From: "lingjun.cg" Date: Wed, 15 Apr 2026 02:13:26 +0000 Subject: [PATCH 050/108] 8379981: Virtual thread: crash in stackChunkOopDesc::print_on when thawing frame Reviewed-by: pchilanomate --- src/hotspot/share/runtime/frame.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hotspot/share/runtime/frame.cpp b/src/hotspot/share/runtime/frame.cpp index 8f969600ba8..d691a3c8028 100644 --- a/src/hotspot/share/runtime/frame.cpp +++ b/src/hotspot/share/runtime/frame.cpp @@ -1286,7 +1286,7 @@ public: } bool is_good(oop* p) { - return *p == nullptr || (dbg_is_safe(*p, -1) && dbg_is_safe((*p)->klass(), -1) && oopDesc::is_oop_or_null(*p)); + return *p == nullptr || (dbg_is_safe(*p, -1) && dbg_is_safe((*p)->klass_without_asserts(), -1) && oopDesc::is_oop_or_null(*p)); } void describe(FrameValues& values, int frame_no) { for (int i = 0; i < _oops->length(); i++) { From 20e8ea0e0640bf8b0727cf30ced041a1def9c350 Mon Sep 17 00:00:00 2001 From: Jayathirth D V Date: Wed, 15 Apr 2026 04:22:28 +0000 Subject: [PATCH 051/108] 8382047: Update Libpng to 1.6.57 Reviewed-by: avu, azvegint, prr --- src/java.desktop/share/legal/libpng.md | 3 +- .../native/libsplashscreen/libpng/CHANGES | 11 ++++ .../native/libsplashscreen/libpng/README | 2 +- .../share/native/libsplashscreen/libpng/png.c | 4 +- .../share/native/libsplashscreen/libpng/png.h | 14 ++--- .../native/libsplashscreen/libpng/pngconf.h | 2 +- .../libsplashscreen/libpng/pnglibconf.h | 2 +- .../native/libsplashscreen/libpng/pngrtran.c | 28 +++++----- .../native/libsplashscreen/libpng/pngset.c | 54 +++++++++++++++++-- 9 files changed, 89 insertions(+), 31 deletions(-) diff --git a/src/java.desktop/share/legal/libpng.md b/src/java.desktop/share/legal/libpng.md index 034de22bf25..7783fc7ff03 100644 --- a/src/java.desktop/share/legal/libpng.md +++ b/src/java.desktop/share/legal/libpng.md @@ -1,4 +1,4 @@ -## libpng v1.6.56 +## libpng v1.6.57 ### libpng License
@@ -180,6 +180,7 @@ Authors, for copyright and licensing purposes.
  * Mans Rullgard
  * Matt Sarett
  * Mike Klein
+ * Mohammad Seet
  * Pascal Massimino
  * Paul Schmidt
  * Petr Simecek
diff --git a/src/java.desktop/share/native/libsplashscreen/libpng/CHANGES b/src/java.desktop/share/native/libsplashscreen/libpng/CHANGES
index 673d4d50420..ba81df0c0e6 100644
--- a/src/java.desktop/share/native/libsplashscreen/libpng/CHANGES
+++ b/src/java.desktop/share/native/libsplashscreen/libpng/CHANGES
@@ -6368,6 +6368,17 @@ Version 1.6.56 [March 25, 2026]
     (Contributed by Bob Friesenhahn and Philippe Antoine.)
   Performed various refactorings and cleanups.
 
+Version 1.6.57 [April 8, 2026]
+  Fixed CVE-2026-34757 (medium severity):
+    Use-after-free in `png_set_PLTE`, `png_set_tRNS` and `png_set_hIST`
+    leading to corrupted chunk data and potential heap information disclosure.
+    Also hardened the append-style setters (`png_set_text`, `png_set_sPLT`,
+    `png_set_unknown_chunks`) against a theoretical variant of the same
+    aliasing pattern.
+    (Reported by Iv4n .)
+  Fixed integer overflow in rowbytes computation in read transforms.
+    (Contributed by Mohammad Seet.)
+
 Send comments/corrections/commendations to png-mng-implement at lists.sf.net.
 Subscription is required; visit
 
diff --git a/src/java.desktop/share/native/libsplashscreen/libpng/README b/src/java.desktop/share/native/libsplashscreen/libpng/README
index d0b085f7933..179b8dc8cb4 100644
--- a/src/java.desktop/share/native/libsplashscreen/libpng/README
+++ b/src/java.desktop/share/native/libsplashscreen/libpng/README
@@ -1,4 +1,4 @@
-README for libpng version 1.6.56
+README for libpng version 1.6.57
 ================================
 
 See the note about version numbers near the top of `png.h`.
diff --git a/src/java.desktop/share/native/libsplashscreen/libpng/png.c b/src/java.desktop/share/native/libsplashscreen/libpng/png.c
index fd095b515b9..e4e13b0a684 100644
--- a/src/java.desktop/share/native/libsplashscreen/libpng/png.c
+++ b/src/java.desktop/share/native/libsplashscreen/libpng/png.c
@@ -42,7 +42,7 @@
 #include "pngpriv.h"
 
 /* Generate a compiler error if there is an old png.h in the search path. */
-typedef png_libpng_version_1_6_56 Your_png_h_is_not_version_1_6_56;
+typedef png_libpng_version_1_6_57 Your_png_h_is_not_version_1_6_57;
 
 /* Sanity check the chunks definitions - PNG_KNOWN_CHUNKS from pngpriv.h and the
  * corresponding macro definitions.  This causes a compile time failure if
@@ -849,7 +849,7 @@ png_get_copyright(png_const_structrp png_ptr)
    return PNG_STRING_COPYRIGHT
 #else
    return PNG_STRING_NEWLINE \
-      "libpng version 1.6.56" PNG_STRING_NEWLINE \
+      "libpng version 1.6.57" PNG_STRING_NEWLINE \
       "Copyright (c) 2018-2026 Cosmin Truta" PNG_STRING_NEWLINE \
       "Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson" \
       PNG_STRING_NEWLINE \
diff --git a/src/java.desktop/share/native/libsplashscreen/libpng/png.h b/src/java.desktop/share/native/libsplashscreen/libpng/png.h
index 56ec204cd1a..349e7d07383 100644
--- a/src/java.desktop/share/native/libsplashscreen/libpng/png.h
+++ b/src/java.desktop/share/native/libsplashscreen/libpng/png.h
@@ -29,7 +29,7 @@
  * However, the following notice accompanied the original version of this
  * file and, per its terms, should not be removed:
  *
- * libpng version 1.6.56
+ * libpng version 1.6.57
  *
  * Copyright (c) 2018-2026 Cosmin Truta
  * Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson
@@ -43,7 +43,7 @@
  *   libpng versions 0.89, June 1996, through 0.96, May 1997: Andreas Dilger
  *   libpng versions 0.97, January 1998, through 1.6.35, July 2018:
  *     Glenn Randers-Pehrson
- *   libpng versions 1.6.36, December 2018, through 1.6.56, March 2026:
+ *   libpng versions 1.6.36, December 2018, through 1.6.57, April 2026:
  *     Cosmin Truta
  *   See also "Contributing Authors", below.
  */
@@ -267,7 +267,7 @@
  *    ...
  *    1.5.30                  15    10530  15.so.15.30[.0]
  *    ...
- *    1.6.56                  16    10656  16.so.16.56[.0]
+ *    1.6.57                  16    10657  16.so.16.57[.0]
  *
  *    Henceforth the source version will match the shared-library major and
  *    minor numbers; the shared-library major version number will be used for
@@ -303,7 +303,7 @@
  */
 
 /* Version information for png.h - this should match the version in png.c */
-#define PNG_LIBPNG_VER_STRING "1.6.56"
+#define PNG_LIBPNG_VER_STRING "1.6.57"
 #define PNG_HEADER_VERSION_STRING " libpng version " PNG_LIBPNG_VER_STRING "\n"
 
 /* The versions of shared library builds should stay in sync, going forward */
@@ -314,7 +314,7 @@
 /* These should match the first 3 components of PNG_LIBPNG_VER_STRING: */
 #define PNG_LIBPNG_VER_MAJOR   1
 #define PNG_LIBPNG_VER_MINOR   6
-#define PNG_LIBPNG_VER_RELEASE 56
+#define PNG_LIBPNG_VER_RELEASE 57
 
 /* This should be zero for a public release, or non-zero for a
  * development version.
@@ -345,7 +345,7 @@
  * From version 1.0.1 it is:
  * XXYYZZ, where XX=major, YY=minor, ZZ=release
  */
-#define PNG_LIBPNG_VER 10656 /* 1.6.56 */
+#define PNG_LIBPNG_VER 10657 /* 1.6.57 */
 
 /* Library configuration: these options cannot be changed after
  * the library has been built.
@@ -455,7 +455,7 @@ extern "C" {
 /* This triggers a compiler error in png.c, if png.c and png.h
  * do not agree upon the version number.
  */
-typedef char *png_libpng_version_1_6_56;
+typedef char *png_libpng_version_1_6_57;
 
 /* Basic control structions.  Read libpng-manual.txt or libpng.3 for more info.
  *
diff --git a/src/java.desktop/share/native/libsplashscreen/libpng/pngconf.h b/src/java.desktop/share/native/libsplashscreen/libpng/pngconf.h
index 5772e6ebb1c..1a5bb7b60f8 100644
--- a/src/java.desktop/share/native/libsplashscreen/libpng/pngconf.h
+++ b/src/java.desktop/share/native/libsplashscreen/libpng/pngconf.h
@@ -29,7 +29,7 @@
  * However, the following notice accompanied the original version of this
  * file and, per its terms, should not be removed:
  *
- * libpng version 1.6.56
+ * libpng version 1.6.57
  *
  * Copyright (c) 2018-2026 Cosmin Truta
  * Copyright (c) 1998-2002,2004,2006-2016,2018 Glenn Randers-Pehrson
diff --git a/src/java.desktop/share/native/libsplashscreen/libpng/pnglibconf.h b/src/java.desktop/share/native/libsplashscreen/libpng/pnglibconf.h
index 4a7e51d112d..de63c998927 100644
--- a/src/java.desktop/share/native/libsplashscreen/libpng/pnglibconf.h
+++ b/src/java.desktop/share/native/libsplashscreen/libpng/pnglibconf.h
@@ -31,7 +31,7 @@
  * However, the following notice accompanied the original version of this
  * file and, per its terms, should not be removed:
  */
-/* libpng version 1.6.56 */
+/* libpng version 1.6.57 */
 
 /* Copyright (c) 2018-2026 Cosmin Truta */
 /* Copyright (c) 1998-2002,2004,2006-2018 Glenn Randers-Pehrson */
diff --git a/src/java.desktop/share/native/libsplashscreen/libpng/pngrtran.c b/src/java.desktop/share/native/libsplashscreen/libpng/pngrtran.c
index f0972ba9bef..838c8460f91 100644
--- a/src/java.desktop/share/native/libsplashscreen/libpng/pngrtran.c
+++ b/src/java.desktop/share/native/libsplashscreen/libpng/pngrtran.c
@@ -2408,7 +2408,7 @@ png_do_unpack(png_row_infop row_info, png_bytep row)
       }
       row_info->bit_depth = 8;
       row_info->pixel_depth = (png_byte)(8 * row_info->channels);
-      row_info->rowbytes = row_width * row_info->channels;
+      row_info->rowbytes = (size_t)row_width * row_info->channels;
    }
 }
 #endif
@@ -2610,7 +2610,7 @@ png_do_scale_16_to_8(png_row_infop row_info, png_bytep row)
 
       row_info->bit_depth = 8;
       row_info->pixel_depth = (png_byte)(8 * row_info->channels);
-      row_info->rowbytes = row_info->width * row_info->channels;
+      row_info->rowbytes = (size_t)row_info->width * row_info->channels;
    }
 }
 #endif
@@ -2638,7 +2638,7 @@ png_do_chop(png_row_infop row_info, png_bytep row)
 
       row_info->bit_depth = 8;
       row_info->pixel_depth = (png_byte)(8 * row_info->channels);
-      row_info->rowbytes = row_info->width * row_info->channels;
+      row_info->rowbytes = (size_t)row_info->width * row_info->channels;
    }
 }
 #endif
@@ -2874,7 +2874,7 @@ png_do_read_filler(png_row_infop row_info, png_bytep row,
             *(--dp) = lo_filler;
             row_info->channels = 2;
             row_info->pixel_depth = 16;
-            row_info->rowbytes = row_width * 2;
+            row_info->rowbytes = (size_t)row_width * 2;
          }
 
          else
@@ -2889,7 +2889,7 @@ png_do_read_filler(png_row_infop row_info, png_bytep row,
             }
             row_info->channels = 2;
             row_info->pixel_depth = 16;
-            row_info->rowbytes = row_width * 2;
+            row_info->rowbytes = (size_t)row_width * 2;
          }
       }
 
@@ -2912,7 +2912,7 @@ png_do_read_filler(png_row_infop row_info, png_bytep row,
             *(--dp) = hi_filler;
             row_info->channels = 2;
             row_info->pixel_depth = 32;
-            row_info->rowbytes = row_width * 4;
+            row_info->rowbytes = (size_t)row_width * 4;
          }
 
          else
@@ -2929,7 +2929,7 @@ png_do_read_filler(png_row_infop row_info, png_bytep row,
             }
             row_info->channels = 2;
             row_info->pixel_depth = 32;
-            row_info->rowbytes = row_width * 4;
+            row_info->rowbytes = (size_t)row_width * 4;
          }
       }
 #endif
@@ -2953,7 +2953,7 @@ png_do_read_filler(png_row_infop row_info, png_bytep row,
             *(--dp) = lo_filler;
             row_info->channels = 4;
             row_info->pixel_depth = 32;
-            row_info->rowbytes = row_width * 4;
+            row_info->rowbytes = (size_t)row_width * 4;
          }
 
          else
@@ -2970,7 +2970,7 @@ png_do_read_filler(png_row_infop row_info, png_bytep row,
             }
             row_info->channels = 4;
             row_info->pixel_depth = 32;
-            row_info->rowbytes = row_width * 4;
+            row_info->rowbytes = (size_t)row_width * 4;
          }
       }
 
@@ -2997,7 +2997,7 @@ png_do_read_filler(png_row_infop row_info, png_bytep row,
             *(--dp) = hi_filler;
             row_info->channels = 4;
             row_info->pixel_depth = 64;
-            row_info->rowbytes = row_width * 8;
+            row_info->rowbytes = (size_t)row_width * 8;
          }
 
          else
@@ -3019,7 +3019,7 @@ png_do_read_filler(png_row_infop row_info, png_bytep row,
 
             row_info->channels = 4;
             row_info->pixel_depth = 64;
-            row_info->rowbytes = row_width * 8;
+            row_info->rowbytes = (size_t)row_width * 8;
          }
       }
 #endif
@@ -4513,7 +4513,7 @@ png_do_expand_palette(png_structrp png_ptr, png_row_infop row_info,
                }
                row_info->bit_depth = 8;
                row_info->pixel_depth = 32;
-               row_info->rowbytes = row_width * 4;
+               row_info->rowbytes = (size_t)row_width * 4;
                row_info->color_type = 6;
                row_info->channels = 4;
             }
@@ -4521,7 +4521,7 @@ png_do_expand_palette(png_structrp png_ptr, png_row_infop row_info,
             else
             {
                sp = row + (size_t)row_width - 1;
-               dp = row + (size_t)(row_width * 3) - 1;
+               dp = row + (size_t)row_width * 3 - 1;
                i = 0;
 #ifdef PNG_ARM_NEON_INTRINSICS_AVAILABLE
                i = png_do_expand_palette_rgb8_neon(png_ptr, row_info, row,
@@ -4540,7 +4540,7 @@ png_do_expand_palette(png_structrp png_ptr, png_row_infop row_info,
 
                row_info->bit_depth = 8;
                row_info->pixel_depth = 24;
-               row_info->rowbytes = row_width * 3;
+               row_info->rowbytes = (size_t)row_width * 3;
                row_info->color_type = 2;
                row_info->channels = 3;
             }
diff --git a/src/java.desktop/share/native/libsplashscreen/libpng/pngset.c b/src/java.desktop/share/native/libsplashscreen/libpng/pngset.c
index 05d18cd06b7..29082a6be08 100644
--- a/src/java.desktop/share/native/libsplashscreen/libpng/pngset.c
+++ b/src/java.desktop/share/native/libsplashscreen/libpng/pngset.c
@@ -414,6 +414,7 @@ void PNGAPI
 png_set_hIST(png_const_structrp png_ptr, png_inforp info_ptr,
     png_const_uint_16p hist)
 {
+   png_uint_16 safe_hist[PNG_MAX_PALETTE_LENGTH];
    int i;
 
    png_debug1(1, "in %s storage function", "hIST");
@@ -430,6 +431,13 @@ png_set_hIST(png_const_structrp png_ptr, png_inforp info_ptr,
       return;
    }
 
+   /* Snapshot the caller's hist before freeing, in case it points to
+    * info_ptr->hist (getter-to-setter aliasing).
+    */
+   memcpy(safe_hist, hist, (unsigned int)info_ptr->num_palette *
+       (sizeof (png_uint_16)));
+   hist = safe_hist;
+
    png_free_data(png_ptr, info_ptr, PNG_FREE_HIST, 0);
 
    /* Changed from info->num_palette to PNG_MAX_PALETTE_LENGTH in
@@ -771,7 +779,7 @@ void PNGAPI
 png_set_PLTE(png_structrp png_ptr, png_inforp info_ptr,
     png_const_colorp palette, int num_palette)
 {
-
+   png_color safe_palette[PNG_MAX_PALETTE_LENGTH];
    png_uint_32 max_palette_length;
 
    png_debug1(1, "in %s storage function", "PLTE");
@@ -805,6 +813,15 @@ png_set_PLTE(png_structrp png_ptr, png_inforp info_ptr,
       png_error(png_ptr, "Invalid palette");
    }
 
+   /* Snapshot the caller's palette before freeing, in case it points to
+    * info_ptr->palette (getter-to-setter aliasing).
+    */
+   if (num_palette > 0)
+      memcpy(safe_palette, palette, (unsigned int)num_palette *
+          (sizeof (png_color)));
+
+   palette = safe_palette;
+
    png_free_data(png_ptr, info_ptr, PNG_FREE_PLTE, 0);
 
    /* Changed in libpng-1.2.1 to allocate PNG_MAX_PALETTE_LENGTH instead
@@ -966,6 +983,7 @@ png_set_text_2(png_const_structrp png_ptr, png_inforp info_ptr,
     png_const_textp text_ptr, int num_text)
 {
    int i;
+   png_textp old_text = NULL;
 
    png_debug1(1, "in text storage function, chunk typeid = 0x%lx",
       png_ptr == NULL ? 0xabadca11UL : (unsigned long)png_ptr->chunk_name);
@@ -1013,7 +1031,10 @@ png_set_text_2(png_const_structrp png_ptr, png_inforp info_ptr,
          return 1;
       }
 
-      png_free(png_ptr, info_ptr->text);
+      /* Defer freeing the old array until after the copy loop below,
+       * in case text_ptr aliases info_ptr->text (getter-to-setter).
+       */
+      old_text = info_ptr->text;
 
       info_ptr->text = new_text;
       info_ptr->free_me |= PNG_FREE_TEXT;
@@ -1098,6 +1119,7 @@ png_set_text_2(png_const_structrp png_ptr, png_inforp info_ptr,
       {
          png_chunk_report(png_ptr, "text chunk: out of memory",
              PNG_CHUNK_WRITE_ERROR);
+         png_free(png_ptr, old_text);
 
          return 1;
       }
@@ -1151,6 +1173,8 @@ png_set_text_2(png_const_structrp png_ptr, png_inforp info_ptr,
       png_debug1(3, "transferred text chunk %d", info_ptr->num_text);
    }
 
+   png_free(png_ptr, old_text);
+
    return 0;
 }
 #endif
@@ -1194,6 +1218,16 @@ png_set_tRNS(png_structrp png_ptr, png_inforp info_ptr,
 
    if (trans_alpha != NULL)
    {
+       /* Snapshot the caller's trans_alpha before freeing, in case it
+        * points to info_ptr->trans_alpha (getter-to-setter aliasing).
+        */
+       png_byte safe_trans[PNG_MAX_PALETTE_LENGTH];
+
+       if (num_trans > 0 && num_trans <= PNG_MAX_PALETTE_LENGTH)
+          memcpy(safe_trans, trans_alpha, (size_t)num_trans);
+
+       trans_alpha = safe_trans;
+
        png_free_data(png_ptr, info_ptr, PNG_FREE_TRNS, 0);
 
        if (num_trans > 0 && num_trans <= PNG_MAX_PALETTE_LENGTH)
@@ -1278,6 +1312,7 @@ png_set_sPLT(png_const_structrp png_ptr,
  */
 {
    png_sPLT_tp np;
+   png_sPLT_tp old_spalettes;
 
    png_debug1(1, "in %s storage function", "sPLT");
 
@@ -1298,7 +1333,10 @@ png_set_sPLT(png_const_structrp png_ptr,
       return;
    }
 
-   png_free(png_ptr, info_ptr->splt_palettes);
+   /* Defer freeing the old array until after the copy loop below,
+    * in case entries aliases info_ptr->splt_palettes (getter-to-setter).
+    */
+   old_spalettes = info_ptr->splt_palettes;
 
    info_ptr->splt_palettes = np;
    info_ptr->free_me |= PNG_FREE_SPLT;
@@ -1362,6 +1400,8 @@ png_set_sPLT(png_const_structrp png_ptr,
    }
    while (--nentries);
 
+   png_free(png_ptr, old_spalettes);
+
    if (nentries > 0)
       png_chunk_report(png_ptr, "sPLT out of memory", PNG_CHUNK_WRITE_ERROR);
 }
@@ -1410,6 +1450,7 @@ png_set_unknown_chunks(png_const_structrp png_ptr,
     png_inforp info_ptr, png_const_unknown_chunkp unknowns, int num_unknowns)
 {
    png_unknown_chunkp np;
+   png_unknown_chunkp old_unknowns;
 
    if (png_ptr == NULL || info_ptr == NULL || num_unknowns <= 0 ||
        unknowns == NULL)
@@ -1456,7 +1497,10 @@ png_set_unknown_chunks(png_const_structrp png_ptr,
       return;
    }
 
-   png_free(png_ptr, info_ptr->unknown_chunks);
+   /* Defer freeing the old array until after the copy loop below,
+    * in case unknowns aliases info_ptr->unknown_chunks (getter-to-setter).
+    */
+   old_unknowns = info_ptr->unknown_chunks;
 
    info_ptr->unknown_chunks = np; /* safe because it is initialized */
    info_ptr->free_me |= PNG_FREE_UNKN;
@@ -1502,6 +1546,8 @@ png_set_unknown_chunks(png_const_structrp png_ptr,
       ++np;
       ++(info_ptr->unknown_chunks_num);
    }
+
+   png_free(png_ptr, old_unknowns);
 }
 
 void PNGAPI

From 2930e2b5308a5d6dd68253727db51d0ce167ed83 Mon Sep 17 00:00:00 2001
From: Jatin Bhateja 
Date: Wed, 15 Apr 2026 04:31:53 +0000
Subject: [PATCH 052/108] 8372797: [VectorAPI] Missing Min/Max identity
 transforms

Reviewed-by: xgong, vlivanov
---
 src/hotspot/share/opto/vectornode.cpp         |   82 +-
 src/hotspot/share/opto/vectornode.hpp         |   40 +-
 .../compiler/lib/ir_framework/IRNode.java     |   20 +
 .../VectorCommutativeOperSharingTest.java     |    4 +-
 .../vectorapi/VectorMinMaxTransforms.java     | 2036 +++++++++++++++++
 .../VectorUnsignedMinMaxOperationsTest.java   | 1353 ++++++++++-
 6 files changed, 3477 insertions(+), 58 deletions(-)
 create mode 100644 test/hotspot/jtreg/compiler/vectorapi/VectorMinMaxTransforms.java

diff --git a/src/hotspot/share/opto/vectornode.cpp b/src/hotspot/share/opto/vectornode.cpp
index 6012bdef86e..b07f0234492 100644
--- a/src/hotspot/share/opto/vectornode.cpp
+++ b/src/hotspot/share/opto/vectornode.cpp
@@ -2432,67 +2432,68 @@ bool MulVLNode::has_uint_inputs() const {
          has_vector_elements_fit_uint(in(2));
 }
 
-static Node* UMinMaxV_Ideal(Node* n, PhaseGVN* phase, bool can_reshape) {
+static Node* MinMaxV_Common_Ideal(MinMaxVNode* n, PhaseGVN* phase, bool can_reshape) {
   int vopc = n->Opcode();
-  assert(vopc == Op_UMinV || vopc == Op_UMaxV, "Unexpected opcode");
+  int min_opcode = n->min_opcode();
+  int max_opcode = n->max_opcode();
 
-  Node* umin = nullptr;
-  Node* umax = nullptr;
+  Node* min_op = nullptr;
+  Node* max_op = nullptr;
   int lopc = n->in(1)->Opcode();
   int ropc = n->in(2)->Opcode();
 
-  if (lopc == Op_UMinV && ropc == Op_UMaxV) {
-    umin = n->in(1);
-    umax = n->in(2);
-  } else if (lopc == Op_UMaxV && ropc == Op_UMinV) {
-    umin = n->in(2);
-    umax = n->in(1);
+  if (lopc == min_opcode && ropc == max_opcode) {
+    min_op = n->in(1);
+    max_op = n->in(2);
+  } else if (lopc == max_opcode && ropc == min_opcode) {
+    min_op = n->in(2);
+    max_op = n->in(1);
   } else {
     return nullptr;
   }
 
-  // UMin (UMin(a, b), UMax(a, b))  => UMin(a, b)
-  // UMin (UMax(a, b), UMin(b, a))  => UMin(a, b)
-  // UMax (UMin(a, b), UMax(a, b))  => UMax(a, b)
-  // UMax (UMax(a, b), UMin(b, a))  => UMax(a, b)
-  if (umin != nullptr && umax != nullptr) {
-    if ((umin->in(1) == umax->in(1) && umin->in(2) == umax->in(2)) ||
-        (umin->in(2) == umax->in(1) && umin->in(1) == umax->in(2))) {
-      if (vopc == Op_UMinV) {
-        return new UMinVNode(umax->in(1), umax->in(2), n->bottom_type()->is_vect());
-      } else {
-        return new UMaxVNode(umax->in(1), umax->in(2), n->bottom_type()->is_vect());
+  // Min (Min(a, b), Max(a, b))  => Min(a, b)
+  // Min (Max(a, b), Min(b, a))  => Min(a, b)
+  // Max (Min(a, b), Max(a, b))  => Max(a, b)
+  // Max (Max(a, b), Min(b, a))  => Max(a, b)
+
+  if (min_op != nullptr && max_op != nullptr) {
+    // Skip if predication status is inconsistent across n, min_op, and max_op,
+    // or if predicated operands carry different masks.
+    if (n->is_predicated_vector() != min_op->is_predicated_vector() ||
+        min_op->is_predicated_vector() != max_op->is_predicated_vector()) {
+      return nullptr;
+    }
+    if (min_op->is_predicated_vector() &&
+        !(n->in(3) == min_op->in(3) && min_op->in(3) == max_op->in(3))) {
+      return nullptr;
+    }
+
+    if ((min_op->in(1) == max_op->in(1) && min_op->in(2) == max_op->in(2)) ||
+        (min_op->in(2) == max_op->in(1) && min_op->in(1) == max_op->in(2))) {
+      // Use n->in(1) inputs for the result to preserve correct merge-masking
+      // passthrough: inactive lanes use in(1), so result->in(1) must equal
+      // n->in(1)->in(1) to maintain the original passthrough semantics.
+      VectorNode* result = VectorNode::make(vopc, n->in(1)->in(1), n->in(1)->in(2), n->bottom_type()->is_vect());
+      if (n->is_predicated_vector()) {
+        result->add_req(n->in(3));
+        result->add_flag(Node::Flag_is_predicated_vector);
       }
+      return result;
     }
   }
 
   return nullptr;
 }
 
-Node* UMinVNode::Ideal(PhaseGVN* phase, bool can_reshape) {
-  Node* progress = UMinMaxV_Ideal(this, phase, can_reshape);
+Node* MinMaxVNode::Ideal(PhaseGVN* phase, bool can_reshape) {
+  Node* progress = MinMaxV_Common_Ideal(this, phase, can_reshape);
   if (progress != nullptr) return progress;
 
   return VectorNode::Ideal(phase, can_reshape);
 }
 
-Node* UMinVNode::Identity(PhaseGVN* phase) {
-  // UMin (a, a) => a
-  if (in(1) == in(2)) {
-    return in(1);
-  }
-  return this;
-}
-
-Node* UMaxVNode::Ideal(PhaseGVN* phase, bool can_reshape) {
-  Node* progress = UMinMaxV_Ideal(this, phase, can_reshape);
-  if (progress != nullptr) return progress;
-
-  return VectorNode::Ideal(phase, can_reshape);
-}
-
-Node* UMaxVNode::Identity(PhaseGVN* phase) {
-  // UMax (a, a) => a
+Node* MinMaxVNode::Identity(PhaseGVN* phase) {
   if (in(1) == in(2)) {
     return in(1);
   }
@@ -2502,4 +2503,5 @@ Node* UMaxVNode::Identity(PhaseGVN* phase) {
 void VectorBoxAllocateNode::dump_spec(outputStream *st) const {
   CallStaticJavaNode::dump_spec(st);
 }
+
 #endif // !PRODUCT
diff --git a/src/hotspot/share/opto/vectornode.hpp b/src/hotspot/share/opto/vectornode.hpp
index dce43f92905..c4fff06e771 100644
--- a/src/hotspot/share/opto/vectornode.hpp
+++ b/src/hotspot/share/opto/vectornode.hpp
@@ -662,10 +662,22 @@ public:
   virtual int Opcode() const;
 };
 
-// Vector Min
-class MinVNode : public VectorNode {
+// Common superclass for Min/Max vector nodes
+class MinMaxVNode : public VectorNode {
 public:
-  MinVNode(Node* in1, Node* in2, const TypeVect* vt) : VectorNode(in1, in2, vt) {}
+  MinMaxVNode(Node* in1, Node* in2, const TypeVect* vt) : VectorNode(in1, in2, vt) {}
+  virtual int min_opcode() const = 0;
+  virtual int max_opcode() const = 0;
+  virtual Node* Ideal(PhaseGVN* phase, bool can_reshape);
+  virtual Node* Identity(PhaseGVN* phase);
+};
+
+// Vector Min
+class MinVNode : public MinMaxVNode {
+public:
+  MinVNode(Node* in1, Node* in2, const TypeVect* vt) : MinMaxVNode(in1, in2, vt) {}
+  virtual int min_opcode() const { return Op_MinV; }
+  virtual int max_opcode() const { return Op_MaxV; }
   virtual int Opcode() const;
 };
 
@@ -684,31 +696,33 @@ public:
 };
 
 // Vector Unsigned Min
-class UMinVNode : public VectorNode {
+class UMinVNode : public MinMaxVNode {
  public:
-  UMinVNode(Node* in1, Node* in2, const TypeVect* vt) : VectorNode(in1, in2 ,vt) {
+  UMinVNode(Node* in1, Node* in2, const TypeVect* vt) : MinMaxVNode(in1, in2, vt) {
     assert(is_integral_type(vt->element_basic_type()), "");
   }
-  virtual Node* Ideal(PhaseGVN* phase, bool can_reshape);
-  virtual Node* Identity(PhaseGVN* phase);
+  virtual int min_opcode() const { return Op_UMinV; }
+  virtual int max_opcode() const { return Op_UMaxV; }
   virtual int Opcode() const;
 };
 
 // Vector Max
-class MaxVNode : public VectorNode {
+class MaxVNode : public MinMaxVNode {
  public:
-  MaxVNode(Node* in1, Node* in2, const TypeVect* vt) : VectorNode(in1, in2, vt) {}
+  MaxVNode(Node* in1, Node* in2, const TypeVect* vt) : MinMaxVNode(in1, in2, vt) {}
+  virtual int min_opcode() const { return Op_MinV; }
+  virtual int max_opcode() const { return Op_MaxV; }
   virtual int Opcode() const;
 };
 
 // Vector Unsigned Max
-class UMaxVNode : public VectorNode {
+class UMaxVNode : public MinMaxVNode {
  public:
-  UMaxVNode(Node* in1, Node* in2, const TypeVect* vt) : VectorNode(in1, in2, vt) {
+  UMaxVNode(Node* in1, Node* in2, const TypeVect* vt) : MinMaxVNode(in1, in2, vt) {
     assert(is_integral_type(vt->element_basic_type()), "");
   }
-  virtual Node* Ideal(PhaseGVN* phase, bool can_reshape);
-  virtual Node* Identity(PhaseGVN* phase);
+  virtual int min_opcode() const { return Op_UMinV; }
+  virtual int max_opcode() const { return Op_UMaxV; }
   virtual int Opcode() const;
 };
 
diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java
index 3f742a0ce62..f3fc4afb170 100644
--- a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java
+++ b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java
@@ -1223,6 +1223,16 @@ public class IRNode {
         vectorNode(MAX_VI, "MaxV", TYPE_INT);
     }
 
+    public static final String MAX_VB = VECTOR_PREFIX + "MAX_VB" + POSTFIX;
+    static {
+        vectorNode(MAX_VB, "MaxV", TYPE_BYTE);
+    }
+
+    public static final String MAX_VS = VECTOR_PREFIX + "MAX_VS" + POSTFIX;
+    static {
+        vectorNode(MAX_VS, "MaxV", TYPE_SHORT);
+    }
+
     public static final String MAX_VHF = VECTOR_PREFIX + "MAX_VHF" + POSTFIX;
     static {
         vectorNode(MAX_VHF, "MaxVHF", TYPE_SHORT);
@@ -1339,6 +1349,16 @@ public class IRNode {
         vectorNode(MIN_VI, "MinV", TYPE_INT);
     }
 
+    public static final String MIN_VB = VECTOR_PREFIX + "MIN_VB" + POSTFIX;
+    static {
+        vectorNode(MIN_VB, "MinV", TYPE_BYTE);
+    }
+
+    public static final String MIN_VS = VECTOR_PREFIX + "MIN_VS" + POSTFIX;
+    static {
+        vectorNode(MIN_VS, "MinV", TYPE_SHORT);
+    }
+
     public static final String MIN_VHF = VECTOR_PREFIX + "MIN_VHF" + POSTFIX;
     static {
         vectorNode(MIN_VHF, "MinVHF", TYPE_SHORT);
diff --git a/test/hotspot/jtreg/compiler/vectorapi/VectorCommutativeOperSharingTest.java b/test/hotspot/jtreg/compiler/vectorapi/VectorCommutativeOperSharingTest.java
index a51946dd698..2b43cf26421 100644
--- a/test/hotspot/jtreg/compiler/vectorapi/VectorCommutativeOperSharingTest.java
+++ b/test/hotspot/jtreg/compiler/vectorapi/VectorCommutativeOperSharingTest.java
@@ -74,8 +74,8 @@ public class VectorCommutativeOperSharingTest {
     @Test
     @IR(counts = {IRNode.ADD_VI, IRNode.VECTOR_SIZE_ANY, " 2 ",
                   IRNode.MUL_VI, IRNode.VECTOR_SIZE_ANY, " 2 ",
-                  IRNode.MAX_VI, IRNode.VECTOR_SIZE_ANY, " 2 ",
-                  IRNode.MIN_VI, IRNode.VECTOR_SIZE_ANY, " 2 "},
+                  IRNode.MAX_VI, IRNode.VECTOR_SIZE_ANY, " 1 ",
+                  IRNode.MIN_VI, IRNode.VECTOR_SIZE_ANY, " 1 "},
         applyIfCPUFeatureOr = {"avx", "true", "rvv", "true"})
     public void testVectorIRSharing1(int index) {
         IntVector vec1 = IntVector.fromArray(I_SPECIES, ia, index);
diff --git a/test/hotspot/jtreg/compiler/vectorapi/VectorMinMaxTransforms.java b/test/hotspot/jtreg/compiler/vectorapi/VectorMinMaxTransforms.java
new file mode 100644
index 00000000000..acffe4d3257
--- /dev/null
+++ b/test/hotspot/jtreg/compiler/vectorapi/VectorMinMaxTransforms.java
@@ -0,0 +1,2036 @@
+/*
+ * 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.
+ */
+
+package compiler.vectorapi;
+
+import compiler.lib.generators.Generator;
+import compiler.lib.generators.Generators;
+import compiler.lib.ir_framework.*;
+import compiler.lib.verify.*;
+import jdk.incubator.vector.*;
+
+/**
+ * @test
+ * @bug 8372797
+ * @key randomness
+ * @library /test/lib /
+ * @summary IR verification for MinV/MaxV Identity and Ideal transforms
+ * @modules jdk.incubator.vector
+ *
+ * @run driver ${test.main.class}
+ */
+public class VectorMinMaxTransforms {
+    private static final int LENGTH = 256;
+    private static final Generators RD = Generators.G;
+
+    private static final VectorSpecies I_SPECIES = IntVector.SPECIES_PREFERRED;
+    private static int[] ia, ib, ir;
+
+    private static final VectorSpecies L_SPECIES = LongVector.SPECIES_PREFERRED;
+    private static long[] la, lb, lr;
+
+    private static final VectorSpecies F_SPECIES = FloatVector.SPECIES_PREFERRED;
+    private static float[] fa, fb, fr;
+
+    private static final VectorSpecies D_SPECIES = DoubleVector.SPECIES_PREFERRED;
+    private static double[] da, db, dr;
+
+    private static final VectorSpecies B_SPECIES = ByteVector.SPECIES_PREFERRED;
+    private static byte[] ba, bb, br;
+
+    private static final VectorSpecies S_SPECIES = ShortVector.SPECIES_PREFERRED;
+    private static short[] sa, sb, sr;
+
+    private static boolean[] m1arr, m2arr, m3arr;
+
+    static {
+        ia = new int[LENGTH];
+        ib = new int[LENGTH];
+        ir = new int[LENGTH];
+        la = new long[LENGTH];
+        lb = new long[LENGTH];
+        lr = new long[LENGTH];
+        fa = new float[LENGTH];
+        fb = new float[LENGTH];
+        fr = new float[LENGTH];
+        da = new double[LENGTH];
+        db = new double[LENGTH];
+        dr = new double[LENGTH];
+        ba = new byte[LENGTH];
+        bb = new byte[LENGTH];
+        br = new byte[LENGTH];
+        sa = new short[LENGTH];
+        sb = new short[LENGTH];
+        sr = new short[LENGTH];
+        m1arr = new boolean[LENGTH];
+        m2arr = new boolean[LENGTH];
+        m3arr = new boolean[LENGTH];
+
+        Generator iGen = RD.ints();
+        Generator lGen = RD.longs();
+        Generator fGen = RD.floats();
+        Generator dGen = RD.doubles();
+
+        RD.fill(iGen, ia);
+        RD.fill(iGen, ib);
+        RD.fill(lGen, la);
+        RD.fill(lGen, lb);
+        RD.fill(fGen, fa);
+        RD.fill(fGen, fb);
+        RD.fill(dGen, da);
+        RD.fill(dGen, db);
+
+        for (int i = 0; i < LENGTH; i++) {
+            ba[i] = iGen.next().byteValue();
+            bb[i] = iGen.next().byteValue();
+            sa[i] = iGen.next().shortValue();
+            sb[i] = iGen.next().shortValue();
+            m1arr[i] = (i % 2) == 0;
+            m2arr[i] = (i % 2) != 0;
+            m3arr[i] = (i % 3) == 0;
+        }
+    }
+
+    public static void main(String[] args) {
+        TestFramework testFramework = new TestFramework();
+        testFramework.setDefaultWarmup(10000)
+                     .addFlags("--add-modules=jdk.incubator.vector")
+                     .start();
+    }
+
+    // ---------- Int: Identity min(a,a)=>a ----------
+    @Test
+    @IR(counts = { IRNode.MIN_VI, IRNode.VECTOR_SIZE_ANY, " 0 " },
+        applyIfCPUFeatureOr = {"avx", "true", "asimd", "true", "rvv", "true"})
+    public void testIntMinIdentity(int index) {
+        IntVector v = IntVector.fromArray(I_SPECIES, ia, index);
+        v.lanewise(VectorOperators.MIN, v).intoArray(ir, index);
+    }
+
+    @Run(test = "testIntMinIdentity")
+    public void runIntMinIdentity() {
+        for (int i = 0; i < I_SPECIES.loopBound(LENGTH); i += I_SPECIES.length()) {
+            testIntMinIdentity(i);
+        }
+        for (int i = 0; i < I_SPECIES.loopBound(LENGTH); i++) {
+            int expected = Math.min(ia[i], ia[i]);
+            Verify.checkEQ(ir[i], expected);
+        }
+    }
+
+    // ---------- Int: Identity max(a,a)=>a ----------
+    @Test
+    @IR(counts = { IRNode.MAX_VI, IRNode.VECTOR_SIZE_ANY, " 0 " },
+        applyIfCPUFeatureOr = {"avx", "true", "asimd", "true", "rvv", "true"})
+    public void testIntMaxIdentity(int index) {
+        IntVector v = IntVector.fromArray(I_SPECIES, ia, index);
+        v.lanewise(VectorOperators.MAX, v).intoArray(ir, index);
+    }
+
+    @Run(test = "testIntMaxIdentity")
+    public void runIntMaxIdentity() {
+        for (int i = 0; i < I_SPECIES.loopBound(LENGTH); i += I_SPECIES.length()) {
+            testIntMaxIdentity(i);
+        }
+        for (int i = 0; i < I_SPECIES.loopBound(LENGTH); i++) {
+            int expected = Math.max(ia[i], ia[i]);
+            Verify.checkEQ(ir[i], expected);
+        }
+    }
+
+    // ---------- Int: Ideal min(min(a,b), max(a,b)) => min(a,b) ----------
+    @Test
+    @IR(counts = { IRNode.MIN_VI, IRNode.VECTOR_SIZE_ANY, " 1 ",
+                   IRNode.MAX_VI, IRNode.VECTOR_SIZE_ANY, " 0 " },
+        applyIfCPUFeatureOr = {"avx", "true", "asimd", "true", "rvv", "true"})
+    public void testIntMinIdeal(int index) {
+        IntVector v1 = IntVector.fromArray(I_SPECIES, ia, index);
+        IntVector v2 = IntVector.fromArray(I_SPECIES, ib, index);
+        v1.lanewise(VectorOperators.MIN, v2)
+          .lanewise(VectorOperators.MIN, v1.lanewise(VectorOperators.MAX, v2))
+          .intoArray(ir, index);
+    }
+
+    @Run(test = "testIntMinIdeal")
+    public void runIntMinIdeal() {
+        for (int i = 0; i < I_SPECIES.loopBound(LENGTH); i += I_SPECIES.length()) {
+            testIntMinIdeal(i);
+        }
+        for (int i = 0; i < I_SPECIES.loopBound(LENGTH); i++) {
+            int expected = Math.min(Math.min(ia[i], ib[i]), Math.max(ia[i], ib[i]));
+            Verify.checkEQ(ir[i], expected);
+        }
+    }
+
+    // ---------- Int: Ideal max(min(a,b), max(a,b)) => max(a,b) ----------
+    @Test
+    @IR(counts = { IRNode.MIN_VI, IRNode.VECTOR_SIZE_ANY, " 0 ",
+                   IRNode.MAX_VI, IRNode.VECTOR_SIZE_ANY, " 1 " },
+        applyIfCPUFeatureOr = {"avx", "true", "asimd", "true", "rvv", "true"})
+    public void testIntMaxIdeal(int index) {
+        IntVector v1 = IntVector.fromArray(I_SPECIES, ia, index);
+        IntVector v2 = IntVector.fromArray(I_SPECIES, ib, index);
+        v1.lanewise(VectorOperators.MIN, v2)
+          .lanewise(VectorOperators.MAX, v1.lanewise(VectorOperators.MAX, v2))
+          .intoArray(ir, index);
+    }
+
+    @Run(test = "testIntMaxIdeal")
+    public void runIntMaxIdeal() {
+        for (int i = 0; i < I_SPECIES.loopBound(LENGTH); i += I_SPECIES.length()) {
+            testIntMaxIdeal(i);
+        }
+        for (int i = 0; i < I_SPECIES.loopBound(LENGTH); i++) {
+            int expected = Math.max(Math.min(ia[i], ib[i]), Math.max(ia[i], ib[i]));
+            Verify.checkEQ(ir[i], expected);
+        }
+    }
+
+    // ---------- Long: Identity and Ideal ----------
+    @Test
+    @IR(counts = { IRNode.MIN_VL, IRNode.VECTOR_SIZE_ANY, " 0 " },
+        applyIfCPUFeatureOr = {"avx2", "true", "asimd", "true", "rvv", "true"})
+    public void testLongMinIdentity(int index) {
+        LongVector v = LongVector.fromArray(L_SPECIES, la, index);
+        v.lanewise(VectorOperators.MIN, v).intoArray(lr, index);
+    }
+
+    @Run(test = "testLongMinIdentity")
+    public void runLongMinIdentity() {
+        for (int i = 0; i < L_SPECIES.loopBound(LENGTH); i += L_SPECIES.length()) {
+            testLongMinIdentity(i);
+        }
+        for (int i = 0; i < L_SPECIES.loopBound(LENGTH); i++) {
+            Verify.checkEQ(lr[i], la[i]);
+        }
+    }
+
+    // ---------- Long: Identity max(a,a)=>a ----------
+    @Test
+    @IR(counts = { IRNode.MAX_VL, IRNode.VECTOR_SIZE_ANY, " 0 " },
+        applyIfCPUFeatureOr = {"avx2", "true", "asimd", "true", "rvv", "true"})
+    public void testLongMaxIdentity(int index) {
+        LongVector v = LongVector.fromArray(L_SPECIES, la, index);
+        v.lanewise(VectorOperators.MAX, v).intoArray(lr, index);
+    }
+
+    @Run(test = "testLongMaxIdentity")
+    public void runLongMaxIdentity() {
+        for (int i = 0; i < L_SPECIES.loopBound(LENGTH); i += L_SPECIES.length()) {
+            testLongMaxIdentity(i);
+        }
+        for (int i = 0; i < L_SPECIES.loopBound(LENGTH); i++) {
+            Verify.checkEQ(lr[i], la[i]);
+        }
+    }
+
+    // ---------- Long: Ideal min(min(a,b), max(a,b)) => min(a,b) ----------
+    @Test
+    @IR(counts = { IRNode.MIN_VL, IRNode.VECTOR_SIZE_ANY, " 1 ",
+                   IRNode.MAX_VL, IRNode.VECTOR_SIZE_ANY, " 0 " },
+        applyIfCPUFeatureOr = {"avx2", "true", "asimd", "true", "rvv", "true"})
+    public void testLongMinIdeal(int index) {
+        LongVector v1 = LongVector.fromArray(L_SPECIES, la, index);
+        LongVector v2 = LongVector.fromArray(L_SPECIES, lb, index);
+        v1.lanewise(VectorOperators.MIN, v2)
+          .lanewise(VectorOperators.MIN, v1.lanewise(VectorOperators.MAX, v2))
+          .intoArray(lr, index);
+    }
+
+    @Run(test = "testLongMinIdeal")
+    public void runLongMinIdeal() {
+        for (int i = 0; i < L_SPECIES.loopBound(LENGTH); i += L_SPECIES.length()) {
+            testLongMinIdeal(i);
+        }
+        for (int i = 0; i < L_SPECIES.loopBound(LENGTH); i++) {
+            long expected = Math.min(Math.min(la[i], lb[i]), Math.max(la[i], lb[i]));
+            Verify.checkEQ(lr[i], expected);
+        }
+    }
+
+    // ---------- Long: Ideal max(min(a,b), max(a,b)) => max(a,b) ----------
+    @Test
+    @IR(counts = { IRNode.MIN_VL, IRNode.VECTOR_SIZE_ANY, " 0 ",
+                   IRNode.MAX_VL, IRNode.VECTOR_SIZE_ANY, " 1 " },
+        applyIfCPUFeatureOr = {"avx2", "true", "asimd", "true", "rvv", "true"})
+    public void testLongMaxIdeal(int index) {
+        LongVector v1 = LongVector.fromArray(L_SPECIES, la, index);
+        LongVector v2 = LongVector.fromArray(L_SPECIES, lb, index);
+        v1.lanewise(VectorOperators.MIN, v2)
+          .lanewise(VectorOperators.MAX, v1.lanewise(VectorOperators.MAX, v2))
+          .intoArray(lr, index);
+    }
+
+    @Run(test = "testLongMaxIdeal")
+    public void runLongMaxIdeal() {
+        for (int i = 0; i < L_SPECIES.loopBound(LENGTH); i += L_SPECIES.length()) {
+            testLongMaxIdeal(i);
+        }
+        for (int i = 0; i < L_SPECIES.loopBound(LENGTH); i++) {
+            long expected = Math.max(Math.min(la[i], lb[i]), Math.max(la[i], lb[i]));
+            Verify.checkEQ(lr[i], expected);
+        }
+    }
+
+    // ---------- Float: Identity min(a,a)=>a ----------
+    @Test
+    @IR(counts = { IRNode.MIN_VF, IRNode.VECTOR_SIZE_ANY, " 0 " },
+        applyIfCPUFeatureOr = {"avx", "true", "asimd", "true", "rvv", "true"})
+    public void testFloatMinIdentity(int index) {
+        FloatVector v = FloatVector.fromArray(F_SPECIES, fa, index);
+        v.lanewise(VectorOperators.MIN, v).intoArray(fr, index);
+    }
+
+    @Run(test = "testFloatMinIdentity")
+    public void runFloatMinIdentity() {
+        for (int i = 0; i < F_SPECIES.loopBound(LENGTH); i += F_SPECIES.length()) {
+            testFloatMinIdentity(i);
+        }
+        for (int i = 0; i < F_SPECIES.loopBound(LENGTH); i++) {
+            Verify.checkEQ(fr[i], fa[i]);
+        }
+    }
+
+    // ---------- Float: Identity max(a,a)=>a ----------
+    @Test
+    @IR(counts = { IRNode.MAX_VF, IRNode.VECTOR_SIZE_ANY, " 0 " },
+        applyIfCPUFeatureOr = {"avx", "true", "asimd", "true", "rvv", "true"})
+    public void testFloatMaxIdentity(int index) {
+        FloatVector v = FloatVector.fromArray(F_SPECIES, fa, index);
+        v.lanewise(VectorOperators.MAX, v).intoArray(fr, index);
+    }
+
+    @Run(test = "testFloatMaxIdentity")
+    public void runFloatMaxIdentity() {
+        for (int i = 0; i < F_SPECIES.loopBound(LENGTH); i += F_SPECIES.length()) {
+            testFloatMaxIdentity(i);
+        }
+        for (int i = 0; i < F_SPECIES.loopBound(LENGTH); i++) {
+            Verify.checkEQ(fr[i], fa[i]);
+        }
+    }
+
+    // ---------- Float: Ideal min(min(a,b), max(a,b)) => min(a,b) ----------
+    @Test
+    @IR(counts = { IRNode.MIN_VF, IRNode.VECTOR_SIZE_ANY, " 1 ",
+                   IRNode.MAX_VF, IRNode.VECTOR_SIZE_ANY, " 0 " },
+        applyIfCPUFeatureOr = {"avx", "true", "asimd", "true", "rvv", "true"})
+    public void testFloatMinIdeal(int index) {
+        FloatVector v1 = FloatVector.fromArray(F_SPECIES, fa, index);
+        FloatVector v2 = FloatVector.fromArray(F_SPECIES, fb, index);
+        v1.lanewise(VectorOperators.MIN, v2)
+          .lanewise(VectorOperators.MIN, v1.lanewise(VectorOperators.MAX, v2))
+          .intoArray(fr, index);
+    }
+
+    @Run(test = "testFloatMinIdeal")
+    public void runFloatMinIdeal() {
+        for (int i = 0; i < F_SPECIES.loopBound(LENGTH); i += F_SPECIES.length()) {
+            testFloatMinIdeal(i);
+        }
+        for (int i = 0; i < F_SPECIES.loopBound(LENGTH); i++) {
+            float expected = Math.min(Math.min(fa[i], fb[i]), Math.max(fa[i], fb[i]));
+            Verify.checkEQ(fr[i], expected);
+        }
+    }
+
+    // ---------- Float: Ideal max(min(a,b), max(a,b)) => max(a,b) ----------
+    @Test
+    @IR(counts = { IRNode.MIN_VF, IRNode.VECTOR_SIZE_ANY, " 0 ",
+                   IRNode.MAX_VF, IRNode.VECTOR_SIZE_ANY, " 1 " },
+        applyIfCPUFeatureOr = {"avx", "true", "asimd", "true", "rvv", "true"})
+    public void testFloatMaxIdeal(int index) {
+        FloatVector v1 = FloatVector.fromArray(F_SPECIES, fa, index);
+        FloatVector v2 = FloatVector.fromArray(F_SPECIES, fb, index);
+        v1.lanewise(VectorOperators.MIN, v2)
+          .lanewise(VectorOperators.MAX, v1.lanewise(VectorOperators.MAX, v2))
+          .intoArray(fr, index);
+    }
+
+    @Run(test = "testFloatMaxIdeal")
+    public void runFloatMaxIdeal() {
+        for (int i = 0; i < F_SPECIES.loopBound(LENGTH); i += F_SPECIES.length()) {
+            testFloatMaxIdeal(i);
+        }
+        for (int i = 0; i < F_SPECIES.loopBound(LENGTH); i++) {
+            float expected = Math.max(Math.min(fa[i], fb[i]), Math.max(fa[i], fb[i]));
+            Verify.checkEQ(fr[i], expected);
+        }
+    }
+
+    // ---------- Double: Identity min(a,a)=>a ----------
+    @Test
+    @IR(counts = { IRNode.MIN_VD, IRNode.VECTOR_SIZE_ANY, " 0 " },
+        applyIfCPUFeatureOr = {"avx", "true", "asimd", "true", "rvv", "true"})
+    public void testDoubleMinIdentity(int index) {
+        DoubleVector v = DoubleVector.fromArray(D_SPECIES, da, index);
+        v.lanewise(VectorOperators.MIN, v).intoArray(dr, index);
+    }
+
+    @Run(test = "testDoubleMinIdentity")
+    public void runDoubleMinIdentity() {
+        for (int i = 0; i < D_SPECIES.loopBound(LENGTH); i += D_SPECIES.length()) {
+            testDoubleMinIdentity(i);
+        }
+        for (int i = 0; i < D_SPECIES.loopBound(LENGTH); i++) {
+            Verify.checkEQ(dr[i], da[i]);
+        }
+    }
+
+    // ---------- Double: Identity max(a,a)=>a ----------
+    @Test
+    @IR(counts = { IRNode.MAX_VD, IRNode.VECTOR_SIZE_ANY, " 0 " },
+        applyIfCPUFeatureOr = {"avx", "true", "asimd", "true", "rvv", "true"})
+    public void testDoubleMaxIdentity(int index) {
+        DoubleVector v = DoubleVector.fromArray(D_SPECIES, da, index);
+        v.lanewise(VectorOperators.MAX, v).intoArray(dr, index);
+    }
+
+    @Run(test = "testDoubleMaxIdentity")
+    public void runDoubleMaxIdentity() {
+        for (int i = 0; i < D_SPECIES.loopBound(LENGTH); i += D_SPECIES.length()) {
+            testDoubleMaxIdentity(i);
+        }
+        for (int i = 0; i < D_SPECIES.loopBound(LENGTH); i++) {
+            Verify.checkEQ(dr[i], da[i]);
+        }
+    }
+
+    // ---------- Double: Ideal min(min(a,b), max(a,b)) => min(a,b) ----------
+    @Test
+    @IR(counts = { IRNode.MIN_VD, IRNode.VECTOR_SIZE_ANY, " 1 ",
+                   IRNode.MAX_VD, IRNode.VECTOR_SIZE_ANY, " 0 " },
+        applyIfCPUFeatureOr = {"avx", "true", "asimd", "true", "rvv", "true"})
+    public void testDoubleMinIdeal(int index) {
+        DoubleVector v1 = DoubleVector.fromArray(D_SPECIES, da, index);
+        DoubleVector v2 = DoubleVector.fromArray(D_SPECIES, db, index);
+        v1.lanewise(VectorOperators.MIN, v2)
+          .lanewise(VectorOperators.MIN, v1.lanewise(VectorOperators.MAX, v2))
+          .intoArray(dr, index);
+    }
+
+    @Run(test = "testDoubleMinIdeal")
+    public void runDoubleMinIdeal() {
+        for (int i = 0; i < D_SPECIES.loopBound(LENGTH); i += D_SPECIES.length()) {
+            testDoubleMinIdeal(i);
+        }
+        for (int i = 0; i < D_SPECIES.loopBound(LENGTH); i++) {
+            double expected = Math.min(Math.min(da[i], db[i]), Math.max(da[i], db[i]));
+            Verify.checkEQ(dr[i], expected);
+        }
+    }
+
+    // ---------- Double: Ideal max(min(a,b), max(a,b)) => max(a,b) ----------
+    @Test
+    @IR(counts = { IRNode.MIN_VD, IRNode.VECTOR_SIZE_ANY, " 0 ",
+                   IRNode.MAX_VD, IRNode.VECTOR_SIZE_ANY, " 1 " },
+        applyIfCPUFeatureOr = {"avx", "true", "asimd", "true", "rvv", "true"})
+    public void testDoubleMaxIdeal(int index) {
+        DoubleVector v1 = DoubleVector.fromArray(D_SPECIES, da, index);
+        DoubleVector v2 = DoubleVector.fromArray(D_SPECIES, db, index);
+        v1.lanewise(VectorOperators.MIN, v2)
+          .lanewise(VectorOperators.MAX, v1.lanewise(VectorOperators.MAX, v2))
+          .intoArray(dr, index);
+    }
+
+    @Run(test = "testDoubleMaxIdeal")
+    public void runDoubleMaxIdeal() {
+        for (int i = 0; i < D_SPECIES.loopBound(LENGTH); i += D_SPECIES.length()) {
+            testDoubleMaxIdeal(i);
+        }
+        for (int i = 0; i < D_SPECIES.loopBound(LENGTH); i++) {
+            double expected = Math.max(Math.min(da[i], db[i]), Math.max(da[i], db[i]));
+            Verify.checkEQ(dr[i], expected);
+        }
+    }
+
+    // ---------- Byte: Identity min(a,a)=>a ----------
+    @Test
+    @IR(counts = { IRNode.MIN_VB, IRNode.VECTOR_SIZE_ANY, " 0 " },
+        applyIfCPUFeatureOr = {"avx", "true", "asimd", "true", "rvv", "true"})
+    public void testByteMinIdentity(int index) {
+        ByteVector v = ByteVector.fromArray(B_SPECIES, ba, index);
+        v.lanewise(VectorOperators.MIN, v).intoArray(br, index);
+    }
+
+    @Run(test = "testByteMinIdentity")
+    public void runByteMinIdentity() {
+        for (int i = 0; i < B_SPECIES.loopBound(LENGTH); i += B_SPECIES.length()) {
+            testByteMinIdentity(i);
+        }
+        for (int i = 0; i < B_SPECIES.loopBound(LENGTH); i++) {
+            Verify.checkEQ(br[i], ba[i]);
+        }
+    }
+
+    // ---------- Byte: Identity max(a,a)=>a ----------
+    @Test
+    @IR(counts = { IRNode.MAX_VB, IRNode.VECTOR_SIZE_ANY, " 0 " },
+        applyIfCPUFeatureOr = {"avx", "true", "asimd", "true", "rvv", "true"})
+    public void testByteMaxIdentity(int index) {
+        ByteVector v = ByteVector.fromArray(B_SPECIES, ba, index);
+        v.lanewise(VectorOperators.MAX, v).intoArray(br, index);
+    }
+
+    @Run(test = "testByteMaxIdentity")
+    public void runByteMaxIdentity() {
+        for (int i = 0; i < B_SPECIES.loopBound(LENGTH); i += B_SPECIES.length()) {
+            testByteMaxIdentity(i);
+        }
+        for (int i = 0; i < B_SPECIES.loopBound(LENGTH); i++) {
+            Verify.checkEQ(br[i], ba[i]);
+        }
+    }
+
+    // ---------- Byte: Ideal min(min(a,b), max(a,b)) => min(a,b) ----------
+    @Test
+    @IR(counts = { IRNode.MIN_VB, IRNode.VECTOR_SIZE_ANY, " 1 ",
+                   IRNode.MAX_VB, IRNode.VECTOR_SIZE_ANY, " 0 " },
+        applyIfCPUFeatureOr = {"avx", "true", "asimd", "true", "rvv", "true"})
+    public void testByteMinIdeal(int index) {
+        ByteVector v1 = ByteVector.fromArray(B_SPECIES, ba, index);
+        ByteVector v2 = ByteVector.fromArray(B_SPECIES, bb, index);
+        v1.lanewise(VectorOperators.MIN, v2)
+          .lanewise(VectorOperators.MIN, v1.lanewise(VectorOperators.MAX, v2))
+          .intoArray(br, index);
+    }
+
+    @Run(test = "testByteMinIdeal")
+    public void runByteMinIdeal() {
+        for (int i = 0; i < B_SPECIES.loopBound(LENGTH); i += B_SPECIES.length()) {
+            testByteMinIdeal(i);
+        }
+        for (int i = 0; i < B_SPECIES.loopBound(LENGTH); i++) {
+            byte expected = (byte) Math.min(Math.min(ba[i], bb[i]), Math.max(ba[i], bb[i]));
+            Verify.checkEQ(br[i], expected);
+        }
+    }
+
+    // ---------- Byte: Ideal max(min(a,b), max(a,b)) => max(a,b) ----------
+    @Test
+    @IR(counts = { IRNode.MIN_VB, IRNode.VECTOR_SIZE_ANY, " 0 ",
+                   IRNode.MAX_VB, IRNode.VECTOR_SIZE_ANY, " 1 " },
+        applyIfCPUFeatureOr = {"avx", "true", "asimd", "true", "rvv", "true"})
+    public void testByteMaxIdeal(int index) {
+        ByteVector v1 = ByteVector.fromArray(B_SPECIES, ba, index);
+        ByteVector v2 = ByteVector.fromArray(B_SPECIES, bb, index);
+        v1.lanewise(VectorOperators.MIN, v2)
+          .lanewise(VectorOperators.MAX, v1.lanewise(VectorOperators.MAX, v2))
+          .intoArray(br, index);
+    }
+
+    @Run(test = "testByteMaxIdeal")
+    public void runByteMaxIdeal() {
+        for (int i = 0; i < B_SPECIES.loopBound(LENGTH); i += B_SPECIES.length()) {
+            testByteMaxIdeal(i);
+        }
+        for (int i = 0; i < B_SPECIES.loopBound(LENGTH); i++) {
+            byte expected = (byte) Math.max(Math.min(ba[i], bb[i]), Math.max(ba[i], bb[i]));
+            Verify.checkEQ(br[i], expected);
+        }
+    }
+
+    // ---------- Short: Identity min(a,a)=>a ----------
+    @Test
+    @IR(counts = { IRNode.MIN_VS, IRNode.VECTOR_SIZE_ANY, " 0 " },
+        applyIfCPUFeatureOr = {"avx", "true", "asimd", "true", "rvv", "true"})
+    public void testShortMinIdentity(int index) {
+        ShortVector v = ShortVector.fromArray(S_SPECIES, sa, index);
+        v.lanewise(VectorOperators.MIN, v).intoArray(sr, index);
+    }
+
+    @Run(test = "testShortMinIdentity")
+    public void runShortMinIdentity() {
+        for (int i = 0; i < S_SPECIES.loopBound(LENGTH); i += S_SPECIES.length()) {
+            testShortMinIdentity(i);
+        }
+        for (int i = 0; i < S_SPECIES.loopBound(LENGTH); i++) {
+            Verify.checkEQ(sr[i], sa[i]);
+        }
+    }
+
+    // ---------- Short: Identity max(a,a)=>a ----------
+    @Test
+    @IR(counts = { IRNode.MAX_VS, IRNode.VECTOR_SIZE_ANY, " 0 " },
+        applyIfCPUFeatureOr = {"avx", "true", "asimd", "true", "rvv", "true"})
+    public void testShortMaxIdentity(int index) {
+        ShortVector v = ShortVector.fromArray(S_SPECIES, sa, index);
+        v.lanewise(VectorOperators.MAX, v).intoArray(sr, index);
+    }
+
+    @Run(test = "testShortMaxIdentity")
+    public void runShortMaxIdentity() {
+        for (int i = 0; i < S_SPECIES.loopBound(LENGTH); i += S_SPECIES.length()) {
+            testShortMaxIdentity(i);
+        }
+        for (int i = 0; i < S_SPECIES.loopBound(LENGTH); i++) {
+            Verify.checkEQ(sr[i], sa[i]);
+        }
+    }
+
+    // ---------- Short: Ideal min(min(a,b), max(a,b)) => min(a,b) ----------
+    @Test
+    @IR(counts = { IRNode.MIN_VS, IRNode.VECTOR_SIZE_ANY, " 1 ",
+                   IRNode.MAX_VS, IRNode.VECTOR_SIZE_ANY, " 0 " },
+        applyIfCPUFeatureOr = {"avx", "true", "asimd", "true", "rvv", "true"})
+    public void testShortMinIdeal(int index) {
+        ShortVector v1 = ShortVector.fromArray(S_SPECIES, sa, index);
+        ShortVector v2 = ShortVector.fromArray(S_SPECIES, sb, index);
+        v1.lanewise(VectorOperators.MIN, v2)
+          .lanewise(VectorOperators.MIN, v1.lanewise(VectorOperators.MAX, v2))
+          .intoArray(sr, index);
+    }
+
+    @Run(test = "testShortMinIdeal")
+    public void runShortMinIdeal() {
+        for (int i = 0; i < S_SPECIES.loopBound(LENGTH); i += S_SPECIES.length()) {
+            testShortMinIdeal(i);
+        }
+        for (int i = 0; i < S_SPECIES.loopBound(LENGTH); i++) {
+            short expected = (short) Math.min(Math.min(sa[i], sb[i]), Math.max(sa[i], sb[i]));
+            Verify.checkEQ(sr[i], expected);
+        }
+    }
+
+    // ---------- Short: Ideal max(min(a,b), max(a,b)) => max(a,b) ----------
+    @Test
+    @IR(counts = { IRNode.MIN_VS, IRNode.VECTOR_SIZE_ANY, " 0 ",
+                   IRNode.MAX_VS, IRNode.VECTOR_SIZE_ANY, " 1 " },
+        applyIfCPUFeatureOr = {"avx", "true", "asimd", "true", "rvv", "true"})
+    public void testShortMaxIdeal(int index) {
+        ShortVector v1 = ShortVector.fromArray(S_SPECIES, sa, index);
+        ShortVector v2 = ShortVector.fromArray(S_SPECIES, sb, index);
+        v1.lanewise(VectorOperators.MIN, v2)
+          .lanewise(VectorOperators.MAX, v1.lanewise(VectorOperators.MAX, v2))
+          .intoArray(sr, index);
+    }
+
+    @Run(test = "testShortMaxIdeal")
+    public void runShortMaxIdeal() {
+        for (int i = 0; i < S_SPECIES.loopBound(LENGTH); i += S_SPECIES.length()) {
+            testShortMaxIdeal(i);
+        }
+        for (int i = 0; i < S_SPECIES.loopBound(LENGTH); i++) {
+            short expected = (short) Math.max(Math.min(sa[i], sb[i]), Math.max(sa[i], sb[i]));
+            Verify.checkEQ(sr[i], expected);
+        }
+    }
+
+    // Predicated Int: min(min(a,b,m), max(a,b,m), m) => min(a,b,m)
+    @Test
+    @IR(counts = { IRNode.MIN_VI, IRNode.VECTOR_SIZE_ANY, " 1 ",
+                   IRNode.MAX_VI, IRNode.VECTOR_SIZE_ANY, " 0 " },
+        applyIfCPUFeatureOr = {"avx512f", "true", "sve", "true"})
+    public void testIntMaskedMinIdealSameMask(int index) {
+        IntVector v1 = IntVector.fromArray(I_SPECIES, ia, index);
+        IntVector v2 = IntVector.fromArray(I_SPECIES, ib, index);
+        VectorMask m = VectorMask.fromArray(I_SPECIES, m1arr, index);
+        v1.lanewise(VectorOperators.MIN, v2, m)
+          .lanewise(VectorOperators.MIN, v1.lanewise(VectorOperators.MAX, v2, m), m)
+          .intoArray(ir, index);
+    }
+
+    @Run(test = "testIntMaskedMinIdealSameMask")
+    public void runIntMaskedMinIdealSameMask() {
+        for (int i = 0; i < I_SPECIES.loopBound(LENGTH); i += I_SPECIES.length()) {
+            testIntMaskedMinIdealSameMask(i);
+        }
+        for (int i = 0; i < I_SPECIES.loopBound(LENGTH); i++) {
+            int a = ia[i], b = ib[i];
+            boolean mask = m1arr[i];
+            int minAB = mask ? Math.min(a, b) : a;
+            int maxAB = mask ? Math.max(a, b) : a;
+            int expected = mask ? Math.min(minAB, maxAB) : minAB;
+            Verify.checkEQ(ir[i], expected);
+        }
+    }
+
+    // Predicated Int: max(min(a,b,m), max(a,b,m), m) => max(a,b,m)
+    @Test
+    @IR(counts = { IRNode.MIN_VI, IRNode.VECTOR_SIZE_ANY, " 0 ",
+                   IRNode.MAX_VI, IRNode.VECTOR_SIZE_ANY, " 1 " },
+        applyIfCPUFeatureOr = {"avx512f", "true", "sve", "true"})
+    public void testIntMaskedMaxIdealSameMask(int index) {
+        IntVector v1 = IntVector.fromArray(I_SPECIES, ia, index);
+        IntVector v2 = IntVector.fromArray(I_SPECIES, ib, index);
+        VectorMask m = VectorMask.fromArray(I_SPECIES, m1arr, index);
+        v1.lanewise(VectorOperators.MIN, v2, m)
+          .lanewise(VectorOperators.MAX, v1.lanewise(VectorOperators.MAX, v2, m), m)
+          .intoArray(ir, index);
+    }
+
+    @Run(test = "testIntMaskedMaxIdealSameMask")
+    public void runIntMaskedMaxIdealSameMask() {
+        for (int i = 0; i < I_SPECIES.loopBound(LENGTH); i += I_SPECIES.length()) {
+            testIntMaskedMaxIdealSameMask(i);
+        }
+        for (int i = 0; i < I_SPECIES.loopBound(LENGTH); i++) {
+            int a = ia[i], b = ib[i];
+            boolean mask = m1arr[i];
+            int minAB = mask ? Math.min(a, b) : a;
+            int maxAB = mask ? Math.max(a, b) : a;
+            int expected = mask ? Math.max(minAB, maxAB) : minAB;
+            Verify.checkEQ(ir[i], expected);
+        }
+    }
+
+    // Predicated Int: max(min(a,b,m), max(b,a,m), m) => max(a,b,m)
+    @Test
+    @IR(counts = { IRNode.MIN_VI, IRNode.VECTOR_SIZE_ANY, " 0 ",
+                   IRNode.MAX_VI, IRNode.VECTOR_SIZE_ANY, " 1 " },
+        applyIfCPUFeatureOr = {"avx512f", "true", "sve", "true"})
+    public void testIntMaskedMaxIdealFlippedInputs(int index) {
+        IntVector v1 = IntVector.fromArray(I_SPECIES, ia, index);
+        IntVector v2 = IntVector.fromArray(I_SPECIES, ib, index);
+        VectorMask m = VectorMask.fromArray(I_SPECIES, m1arr, index);
+        v1.lanewise(VectorOperators.MIN, v2, m)
+          .lanewise(VectorOperators.MAX, v2.lanewise(VectorOperators.MAX, v1, m), m)
+          .intoArray(ir, index);
+    }
+
+    @Run(test = "testIntMaskedMaxIdealFlippedInputs")
+    public void runIntMaskedMaxIdealFlippedInputs() {
+        for (int i = 0; i < I_SPECIES.loopBound(LENGTH); i += I_SPECIES.length()) {
+            testIntMaskedMaxIdealFlippedInputs(i);
+        }
+        for (int i = 0; i < I_SPECIES.loopBound(LENGTH); i++) {
+            int a = ia[i], b = ib[i];
+            boolean mask = m1arr[i];
+            int minAB = mask ? Math.min(a, b) : a;
+            int maxBA = mask ? Math.max(b, a) : b;
+            int expected = mask ? Math.max(minAB, maxBA) : minAB;
+            Verify.checkEQ(ir[i], expected);
+        }
+    }
+
+    // Predicated Int: min(min(a,b,m), max(b,a,m), m) => min(a,b,m)
+    @Test
+    @IR(counts = { IRNode.MIN_VI, IRNode.VECTOR_SIZE_ANY, " 1 ",
+                   IRNode.MAX_VI, IRNode.VECTOR_SIZE_ANY, " 0 " },
+        applyIfCPUFeatureOr = {"avx512f", "true", "sve", "true"})
+    public void testIntMaskedMinIdealFlippedInputs(int index) {
+        IntVector v1 = IntVector.fromArray(I_SPECIES, ia, index);
+        IntVector v2 = IntVector.fromArray(I_SPECIES, ib, index);
+        VectorMask m = VectorMask.fromArray(I_SPECIES, m1arr, index);
+        v1.lanewise(VectorOperators.MIN, v2, m)
+          .lanewise(VectorOperators.MIN, v2.lanewise(VectorOperators.MAX, v1, m), m)
+          .intoArray(ir, index);
+    }
+
+    @Run(test = "testIntMaskedMinIdealFlippedInputs")
+    public void runIntMaskedMinIdealFlippedInputs() {
+        for (int i = 0; i < I_SPECIES.loopBound(LENGTH); i += I_SPECIES.length()) {
+            testIntMaskedMinIdealFlippedInputs(i);
+        }
+        for (int i = 0; i < I_SPECIES.loopBound(LENGTH); i++) {
+            int a = ia[i], b = ib[i];
+            boolean mask = m1arr[i];
+            int minAB = mask ? Math.min(a, b) : a;
+            int maxBA = mask ? Math.max(b, a) : b;
+            int expected = mask ? Math.min(minAB, maxBA) : minAB;
+            Verify.checkEQ(ir[i], expected);
+        }
+    }
+
+    // Predicated Int: min(min(a,b,m1), max(a,b,m2), m1) => NO transform
+    @Test
+    @IR(counts = { IRNode.MIN_VI, IRNode.VECTOR_SIZE_ANY, " 2 ",
+                   IRNode.MAX_VI, IRNode.VECTOR_SIZE_ANY, " 1 " },
+        applyIfCPUFeatureOr = {"avx512f", "true", "sve", "true"})
+    public void testIntMaskedMinIdealDiffMaskMinMax(int index) {
+        IntVector v1 = IntVector.fromArray(I_SPECIES, ia, index);
+        IntVector v2 = IntVector.fromArray(I_SPECIES, ib, index);
+        VectorMask mask1 = VectorMask.fromArray(I_SPECIES, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(I_SPECIES, m2arr, index);
+        v1.lanewise(VectorOperators.MIN, v2, mask1)
+          .lanewise(VectorOperators.MIN, v1.lanewise(VectorOperators.MAX, v2, mask2), mask1)
+          .intoArray(ir, index);
+    }
+
+    @Run(test = "testIntMaskedMinIdealDiffMaskMinMax")
+    public void runIntMaskedMinIdealDiffMaskMinMax() {
+        for (int i = 0; i < I_SPECIES.loopBound(LENGTH); i += I_SPECIES.length()) {
+            testIntMaskedMinIdealDiffMaskMinMax(i);
+        }
+        for (int i = 0; i < I_SPECIES.loopBound(LENGTH); i++) {
+            int a = ia[i], b = ib[i];
+            int minAB = m1arr[i] ? Math.min(a, b) : a;
+            int maxAB = m2arr[i] ? Math.max(a, b) : a;
+            int expected = m1arr[i] ? Math.min(minAB, maxAB) : minAB;
+            Verify.checkEQ(ir[i], expected);
+        }
+    }
+
+
+    // Predicated Int: min(min(a,b,m2), max(a,b,m1), m1) => NO transform
+    @Test
+    @IR(counts = { IRNode.MIN_VI, IRNode.VECTOR_SIZE_ANY, " 2 ",
+                   IRNode.MAX_VI, IRNode.VECTOR_SIZE_ANY, " 1 " },
+        applyIfCPUFeatureOr = {"avx512f", "true", "sve", "true"})
+    public void testIntMaskedMinIdealDiffMaskMinMaxSwapped(int index) {
+        IntVector v1 = IntVector.fromArray(I_SPECIES, ia, index);
+        IntVector v2 = IntVector.fromArray(I_SPECIES, ib, index);
+        VectorMask mask1 = VectorMask.fromArray(I_SPECIES, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(I_SPECIES, m2arr, index);
+        v1.lanewise(VectorOperators.MIN, v2, mask2)
+          .lanewise(VectorOperators.MIN, v1.lanewise(VectorOperators.MAX, v2, mask1), mask1)
+          .intoArray(ir, index);
+    }
+
+    @Run(test = "testIntMaskedMinIdealDiffMaskMinMaxSwapped")
+    public void runIntMaskedMinIdealDiffMaskMinMaxSwapped() {
+        for (int i = 0; i < I_SPECIES.loopBound(LENGTH); i += I_SPECIES.length()) {
+            testIntMaskedMinIdealDiffMaskMinMaxSwapped(i);
+        }
+        for (int i = 0; i < I_SPECIES.loopBound(LENGTH); i++) {
+            int a = ia[i], b = ib[i];
+            int minAB = m2arr[i] ? Math.min(a, b) : a;
+            int maxAB = m1arr[i] ? Math.max(a, b) : a;
+            int expected = m1arr[i] ? Math.min(minAB, maxAB) : minAB;
+            Verify.checkEQ(ir[i], expected);
+        }
+    }
+    // Predicated Int: min(min(a,b,m1), max(a,b,m1), m2) => NO transform
+    @Test
+    @IR(counts = { IRNode.MIN_VI, IRNode.VECTOR_SIZE_ANY, " 2 ",
+                   IRNode.MAX_VI, IRNode.VECTOR_SIZE_ANY, " 1 " },
+        applyIfCPUFeatureOr = {"avx512f", "true", "sve", "true"})
+    public void testIntMaskedMinIdealDiffMaskOuter(int index) {
+        IntVector v1 = IntVector.fromArray(I_SPECIES, ia, index);
+        IntVector v2 = IntVector.fromArray(I_SPECIES, ib, index);
+        VectorMask mask1 = VectorMask.fromArray(I_SPECIES, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(I_SPECIES, m2arr, index);
+        v1.lanewise(VectorOperators.MIN, v2, mask1)
+          .lanewise(VectorOperators.MIN, v1.lanewise(VectorOperators.MAX, v2, mask1), mask2)
+          .intoArray(ir, index);
+    }
+
+    @Run(test = "testIntMaskedMinIdealDiffMaskOuter")
+    public void runIntMaskedMinIdealDiffMaskOuter() {
+        for (int i = 0; i < I_SPECIES.loopBound(LENGTH); i += I_SPECIES.length()) {
+            testIntMaskedMinIdealDiffMaskOuter(i);
+        }
+        for (int i = 0; i < I_SPECIES.loopBound(LENGTH); i++) {
+            int a = ia[i], b = ib[i];
+            int minAB = m1arr[i] ? Math.min(a, b) : a;
+            int maxAB = m1arr[i] ? Math.max(a, b) : a;
+            int expected = m2arr[i] ? Math.min(minAB, maxAB) : minAB;
+            Verify.checkEQ(ir[i], expected);
+        }
+    }
+
+    // Predicated Int: min(min(a,b,m1), max(a,b,m2), m3) => NO transform
+    @Test
+    @IR(counts = { IRNode.MIN_VI, IRNode.VECTOR_SIZE_ANY, " 2 ",
+                   IRNode.MAX_VI, IRNode.VECTOR_SIZE_ANY, " 1 " },
+        applyIfCPUFeatureOr = {"avx512f", "true", "sve", "true"})
+    public void testIntMaskedMinIdealAllDiffMask(int index) {
+        IntVector v1 = IntVector.fromArray(I_SPECIES, ia, index);
+        IntVector v2 = IntVector.fromArray(I_SPECIES, ib, index);
+        VectorMask mask1 = VectorMask.fromArray(I_SPECIES, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(I_SPECIES, m2arr, index);
+        VectorMask mask3 = VectorMask.fromArray(I_SPECIES, m3arr, index);
+        v1.lanewise(VectorOperators.MIN, v2, mask1)
+          .lanewise(VectorOperators.MIN, v1.lanewise(VectorOperators.MAX, v2, mask2), mask3)
+          .intoArray(ir, index);
+    }
+
+    @Run(test = "testIntMaskedMinIdealAllDiffMask")
+    public void runIntMaskedMinIdealAllDiffMask() {
+        for (int i = 0; i < I_SPECIES.loopBound(LENGTH); i += I_SPECIES.length()) {
+            testIntMaskedMinIdealAllDiffMask(i);
+        }
+        for (int i = 0; i < I_SPECIES.loopBound(LENGTH); i++) {
+            int a = ia[i], b = ib[i];
+            int minAB = m1arr[i] ? Math.min(a, b) : a;
+            int maxAB = m2arr[i] ? Math.max(a, b) : a;
+            int expected = m3arr[i] ? Math.min(minAB, maxAB) : minAB;
+            Verify.checkEQ(ir[i], expected);
+        }
+    }
+
+    // Predicated Byte: min(min(a,b,m), max(a,b,m), m) => min(a,b,m)
+    @Test
+    @IR(counts = { IRNode.MIN_VB, IRNode.VECTOR_SIZE_ANY, " 1 ",
+                   IRNode.MAX_VB, IRNode.VECTOR_SIZE_ANY, " 0 " },
+        applyIfCPUFeatureOr = {"avx512bw", "true", "sve", "true"})
+    public void testByteMaskedMinIdealSameMask(int index) {
+        ByteVector v1 = ByteVector.fromArray(B_SPECIES, ba, index);
+        ByteVector v2 = ByteVector.fromArray(B_SPECIES, bb, index);
+        VectorMask m = VectorMask.fromArray(B_SPECIES, m1arr, index);
+        v1.lanewise(VectorOperators.MIN, v2, m)
+          .lanewise(VectorOperators.MIN, v1.lanewise(VectorOperators.MAX, v2, m), m)
+          .intoArray(br, index);
+    }
+
+    @Run(test = "testByteMaskedMinIdealSameMask")
+    public void runByteMaskedMinIdealSameMask() {
+        for (int i = 0; i < B_SPECIES.loopBound(LENGTH); i += B_SPECIES.length()) {
+            testByteMaskedMinIdealSameMask(i);
+        }
+        for (int i = 0; i < B_SPECIES.loopBound(LENGTH); i++) {
+            byte a = ba[i], b = bb[i];
+            boolean mask = m1arr[i];
+            byte minAB = (byte)(mask ? Math.min(a, b) : a);
+            byte maxAB = (byte)(mask ? Math.max(a, b) : a);
+            byte expected = (byte)(mask ? Math.min(minAB, maxAB) : minAB);
+            Verify.checkEQ(br[i], expected);
+        }
+    }
+
+    // Predicated Byte: max(min(a,b,m), max(a,b,m), m) => max(a,b,m)
+    @Test
+    @IR(counts = { IRNode.MIN_VB, IRNode.VECTOR_SIZE_ANY, " 0 ",
+                   IRNode.MAX_VB, IRNode.VECTOR_SIZE_ANY, " 1 " },
+        applyIfCPUFeatureOr = {"avx512bw", "true", "sve", "true"})
+    public void testByteMaskedMaxIdealSameMask(int index) {
+        ByteVector v1 = ByteVector.fromArray(B_SPECIES, ba, index);
+        ByteVector v2 = ByteVector.fromArray(B_SPECIES, bb, index);
+        VectorMask m = VectorMask.fromArray(B_SPECIES, m1arr, index);
+        v1.lanewise(VectorOperators.MIN, v2, m)
+          .lanewise(VectorOperators.MAX, v1.lanewise(VectorOperators.MAX, v2, m), m)
+          .intoArray(br, index);
+    }
+
+    @Run(test = "testByteMaskedMaxIdealSameMask")
+    public void runByteMaskedMaxIdealSameMask() {
+        for (int i = 0; i < B_SPECIES.loopBound(LENGTH); i += B_SPECIES.length()) {
+            testByteMaskedMaxIdealSameMask(i);
+        }
+        for (int i = 0; i < B_SPECIES.loopBound(LENGTH); i++) {
+            byte a = ba[i], b = bb[i];
+            boolean mask = m1arr[i];
+            byte minAB = (byte)(mask ? Math.min(a, b) : a);
+            byte maxAB = (byte)(mask ? Math.max(a, b) : a);
+            byte expected = (byte)(mask ? Math.max(minAB, maxAB) : minAB);
+            Verify.checkEQ(br[i], expected);
+        }
+    }
+
+    // Predicated Byte: max(min(a,b,m), max(b,a,m), m) => max(a,b,m)
+    @Test
+    @IR(counts = { IRNode.MIN_VB, IRNode.VECTOR_SIZE_ANY, " 0 ",
+                   IRNode.MAX_VB, IRNode.VECTOR_SIZE_ANY, " 1 " },
+        applyIfCPUFeatureOr = {"avx512bw", "true", "sve", "true"})
+    public void testByteMaskedMaxIdealFlippedInputs(int index) {
+        ByteVector v1 = ByteVector.fromArray(B_SPECIES, ba, index);
+        ByteVector v2 = ByteVector.fromArray(B_SPECIES, bb, index);
+        VectorMask m = VectorMask.fromArray(B_SPECIES, m1arr, index);
+        v1.lanewise(VectorOperators.MIN, v2, m)
+          .lanewise(VectorOperators.MAX, v2.lanewise(VectorOperators.MAX, v1, m), m)
+          .intoArray(br, index);
+    }
+
+    @Run(test = "testByteMaskedMaxIdealFlippedInputs")
+    public void runByteMaskedMaxIdealFlippedInputs() {
+        for (int i = 0; i < B_SPECIES.loopBound(LENGTH); i += B_SPECIES.length()) {
+            testByteMaskedMaxIdealFlippedInputs(i);
+        }
+        for (int i = 0; i < B_SPECIES.loopBound(LENGTH); i++) {
+            byte a = ba[i], b = bb[i];
+            boolean mask = m1arr[i];
+            byte minAB = (byte)(mask ? Math.min(a, b) : a);
+            byte maxBA = (byte)(mask ? Math.max(b, a) : b);
+            byte expected = (byte)(mask ? Math.max(minAB, maxBA) : minAB);
+            Verify.checkEQ(br[i], expected);
+        }
+    }
+
+    // Predicated Byte: min(min(a,b,m), max(b,a,m), m) => min(a,b,m)
+    @Test
+    @IR(counts = { IRNode.MIN_VB, IRNode.VECTOR_SIZE_ANY, " 1 ",
+                   IRNode.MAX_VB, IRNode.VECTOR_SIZE_ANY, " 0 " },
+        applyIfCPUFeatureOr = {"avx512bw", "true", "sve", "true"})
+    public void testByteMaskedMinIdealFlippedInputs(int index) {
+        ByteVector v1 = ByteVector.fromArray(B_SPECIES, ba, index);
+        ByteVector v2 = ByteVector.fromArray(B_SPECIES, bb, index);
+        VectorMask m = VectorMask.fromArray(B_SPECIES, m1arr, index);
+        v1.lanewise(VectorOperators.MIN, v2, m)
+          .lanewise(VectorOperators.MIN, v2.lanewise(VectorOperators.MAX, v1, m), m)
+          .intoArray(br, index);
+    }
+
+    @Run(test = "testByteMaskedMinIdealFlippedInputs")
+    public void runByteMaskedMinIdealFlippedInputs() {
+        for (int i = 0; i < B_SPECIES.loopBound(LENGTH); i += B_SPECIES.length()) {
+            testByteMaskedMinIdealFlippedInputs(i);
+        }
+        for (int i = 0; i < B_SPECIES.loopBound(LENGTH); i++) {
+            byte a = ba[i], b = bb[i];
+            boolean mask = m1arr[i];
+            byte minAB = (byte)(mask ? Math.min(a, b) : a);
+            byte maxBA = (byte)(mask ? Math.max(b, a) : b);
+            byte expected = (byte)(mask ? Math.min(minAB, maxBA) : minAB);
+            Verify.checkEQ(br[i], expected);
+        }
+    }
+
+    // Predicated Byte: min(min(a,b,m1), max(a,b,m2), m1) => NO transform
+    @Test
+    @IR(counts = { IRNode.MIN_VB, IRNode.VECTOR_SIZE_ANY, " 2 ",
+                   IRNode.MAX_VB, IRNode.VECTOR_SIZE_ANY, " 1 " },
+        applyIfCPUFeatureOr = {"avx512bw", "true", "sve", "true"})
+    public void testByteMaskedMinIdealDiffMaskMinMax(int index) {
+        ByteVector v1 = ByteVector.fromArray(B_SPECIES, ba, index);
+        ByteVector v2 = ByteVector.fromArray(B_SPECIES, bb, index);
+        VectorMask mask1 = VectorMask.fromArray(B_SPECIES, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(B_SPECIES, m2arr, index);
+        v1.lanewise(VectorOperators.MIN, v2, mask1)
+          .lanewise(VectorOperators.MIN, v1.lanewise(VectorOperators.MAX, v2, mask2), mask1)
+          .intoArray(br, index);
+    }
+
+    @Run(test = "testByteMaskedMinIdealDiffMaskMinMax")
+    public void runByteMaskedMinIdealDiffMaskMinMax() {
+        for (int i = 0; i < B_SPECIES.loopBound(LENGTH); i += B_SPECIES.length()) {
+            testByteMaskedMinIdealDiffMaskMinMax(i);
+        }
+        for (int i = 0; i < B_SPECIES.loopBound(LENGTH); i++) {
+            byte a = ba[i], b = bb[i];
+            byte minAB = (byte)(m1arr[i] ? Math.min(a, b) : a);
+            byte maxAB = (byte)(m2arr[i] ? Math.max(a, b) : a);
+            byte expected = (byte)(m1arr[i] ? Math.min(minAB, maxAB) : minAB);
+            Verify.checkEQ(br[i], expected);
+        }
+    }
+
+    // Predicated Byte: min(min(a,b,m2), max(a,b,m1), m1) => NO transform
+    @Test
+    @IR(counts = { IRNode.MIN_VB, IRNode.VECTOR_SIZE_ANY, " 2 ",
+                   IRNode.MAX_VB, IRNode.VECTOR_SIZE_ANY, " 1 " },
+        applyIfCPUFeatureOr = {"avx512bw", "true", "sve", "true"})
+    public void testByteMaskedMinIdealDiffMaskMinMaxSwapped(int index) {
+        ByteVector v1 = ByteVector.fromArray(B_SPECIES, ba, index);
+        ByteVector v2 = ByteVector.fromArray(B_SPECIES, bb, index);
+        VectorMask mask1 = VectorMask.fromArray(B_SPECIES, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(B_SPECIES, m2arr, index);
+        v1.lanewise(VectorOperators.MIN, v2, mask2)
+          .lanewise(VectorOperators.MIN, v1.lanewise(VectorOperators.MAX, v2, mask1), mask1)
+          .intoArray(br, index);
+    }
+
+    @Run(test = "testByteMaskedMinIdealDiffMaskMinMaxSwapped")
+    public void runByteMaskedMinIdealDiffMaskMinMaxSwapped() {
+        for (int i = 0; i < B_SPECIES.loopBound(LENGTH); i += B_SPECIES.length()) {
+            testByteMaskedMinIdealDiffMaskMinMaxSwapped(i);
+        }
+        for (int i = 0; i < B_SPECIES.loopBound(LENGTH); i++) {
+            byte a = ba[i], b = bb[i];
+            byte minAB = (byte)(m2arr[i] ? Math.min(a, b) : a);
+            byte maxAB = (byte)(m1arr[i] ? Math.max(a, b) : a);
+            byte expected = (byte)(m1arr[i] ? Math.min(minAB, maxAB) : minAB);
+            Verify.checkEQ(br[i], expected);
+        }
+    }
+
+    // Predicated Byte: min(min(a,b,m1), max(a,b,m1), m2) => NO transform
+    @Test
+    @IR(counts = { IRNode.MIN_VB, IRNode.VECTOR_SIZE_ANY, " 2 ",
+                   IRNode.MAX_VB, IRNode.VECTOR_SIZE_ANY, " 1 " },
+        applyIfCPUFeatureOr = {"avx512bw", "true", "sve", "true"})
+    public void testByteMaskedMinIdealDiffMaskOuter(int index) {
+        ByteVector v1 = ByteVector.fromArray(B_SPECIES, ba, index);
+        ByteVector v2 = ByteVector.fromArray(B_SPECIES, bb, index);
+        VectorMask mask1 = VectorMask.fromArray(B_SPECIES, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(B_SPECIES, m2arr, index);
+        v1.lanewise(VectorOperators.MIN, v2, mask1)
+          .lanewise(VectorOperators.MIN, v1.lanewise(VectorOperators.MAX, v2, mask1), mask2)
+          .intoArray(br, index);
+    }
+
+    @Run(test = "testByteMaskedMinIdealDiffMaskOuter")
+    public void runByteMaskedMinIdealDiffMaskOuter() {
+        for (int i = 0; i < B_SPECIES.loopBound(LENGTH); i += B_SPECIES.length()) {
+            testByteMaskedMinIdealDiffMaskOuter(i);
+        }
+        for (int i = 0; i < B_SPECIES.loopBound(LENGTH); i++) {
+            byte a = ba[i], b = bb[i];
+            byte minAB = (byte)(m1arr[i] ? Math.min(a, b) : a);
+            byte maxAB = (byte)(m1arr[i] ? Math.max(a, b) : a);
+            byte expected = (byte)(m2arr[i] ? Math.min(minAB, maxAB) : minAB);
+            Verify.checkEQ(br[i], expected);
+        }
+    }
+
+    // Predicated Byte: min(min(a,b,m1), max(a,b,m2), m3) => NO transform
+    @Test
+    @IR(counts = { IRNode.MIN_VB, IRNode.VECTOR_SIZE_ANY, " 2 ",
+                   IRNode.MAX_VB, IRNode.VECTOR_SIZE_ANY, " 1 " },
+        applyIfCPUFeatureOr = {"avx512bw", "true", "sve", "true"})
+    public void testByteMaskedMinIdealAllDiffMask(int index) {
+        ByteVector v1 = ByteVector.fromArray(B_SPECIES, ba, index);
+        ByteVector v2 = ByteVector.fromArray(B_SPECIES, bb, index);
+        VectorMask mask1 = VectorMask.fromArray(B_SPECIES, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(B_SPECIES, m2arr, index);
+        VectorMask mask3 = VectorMask.fromArray(B_SPECIES, m3arr, index);
+        v1.lanewise(VectorOperators.MIN, v2, mask1)
+          .lanewise(VectorOperators.MIN, v1.lanewise(VectorOperators.MAX, v2, mask2), mask3)
+          .intoArray(br, index);
+    }
+
+    @Run(test = "testByteMaskedMinIdealAllDiffMask")
+    public void runByteMaskedMinIdealAllDiffMask() {
+        for (int i = 0; i < B_SPECIES.loopBound(LENGTH); i += B_SPECIES.length()) {
+            testByteMaskedMinIdealAllDiffMask(i);
+        }
+        for (int i = 0; i < B_SPECIES.loopBound(LENGTH); i++) {
+            byte a = ba[i], b = bb[i];
+            byte minAB = (byte)(m1arr[i] ? Math.min(a, b) : a);
+            byte maxAB = (byte)(m2arr[i] ? Math.max(a, b) : a);
+            byte expected = (byte)(m3arr[i] ? Math.min(minAB, maxAB) : minAB);
+            Verify.checkEQ(br[i], expected);
+        }
+    }
+
+    // Predicated Short: min(min(a,b,m), max(a,b,m), m) => min(a,b,m)
+    @Test
+    @IR(counts = { IRNode.MIN_VS, IRNode.VECTOR_SIZE_ANY, " 1 ",
+                   IRNode.MAX_VS, IRNode.VECTOR_SIZE_ANY, " 0 " },
+        applyIfCPUFeatureOr = {"avx512bw", "true", "sve", "true"})
+    public void testShortMaskedMinIdealSameMask(int index) {
+        ShortVector v1 = ShortVector.fromArray(S_SPECIES, sa, index);
+        ShortVector v2 = ShortVector.fromArray(S_SPECIES, sb, index);
+        VectorMask m = VectorMask.fromArray(S_SPECIES, m1arr, index);
+        v1.lanewise(VectorOperators.MIN, v2, m)
+          .lanewise(VectorOperators.MIN, v1.lanewise(VectorOperators.MAX, v2, m), m)
+          .intoArray(sr, index);
+    }
+
+    @Run(test = "testShortMaskedMinIdealSameMask")
+    public void runShortMaskedMinIdealSameMask() {
+        for (int i = 0; i < S_SPECIES.loopBound(LENGTH); i += S_SPECIES.length()) {
+            testShortMaskedMinIdealSameMask(i);
+        }
+        for (int i = 0; i < S_SPECIES.loopBound(LENGTH); i++) {
+            short a = sa[i], b = sb[i];
+            boolean mask = m1arr[i];
+            short minAB = (short)(mask ? Math.min(a, b) : a);
+            short maxAB = (short)(mask ? Math.max(a, b) : a);
+            short expected = (short)(mask ? Math.min(minAB, maxAB) : minAB);
+            Verify.checkEQ(sr[i], expected);
+        }
+    }
+
+    // Predicated Short: max(min(a,b,m), max(a,b,m), m) => max(a,b,m)
+    @Test
+    @IR(counts = { IRNode.MIN_VS, IRNode.VECTOR_SIZE_ANY, " 0 ",
+                   IRNode.MAX_VS, IRNode.VECTOR_SIZE_ANY, " 1 " },
+        applyIfCPUFeatureOr = {"avx512bw", "true", "sve", "true"})
+    public void testShortMaskedMaxIdealSameMask(int index) {
+        ShortVector v1 = ShortVector.fromArray(S_SPECIES, sa, index);
+        ShortVector v2 = ShortVector.fromArray(S_SPECIES, sb, index);
+        VectorMask m = VectorMask.fromArray(S_SPECIES, m1arr, index);
+        v1.lanewise(VectorOperators.MIN, v2, m)
+          .lanewise(VectorOperators.MAX, v1.lanewise(VectorOperators.MAX, v2, m), m)
+          .intoArray(sr, index);
+    }
+
+    @Run(test = "testShortMaskedMaxIdealSameMask")
+    public void runShortMaskedMaxIdealSameMask() {
+        for (int i = 0; i < S_SPECIES.loopBound(LENGTH); i += S_SPECIES.length()) {
+            testShortMaskedMaxIdealSameMask(i);
+        }
+        for (int i = 0; i < S_SPECIES.loopBound(LENGTH); i++) {
+            short a = sa[i], b = sb[i];
+            boolean mask = m1arr[i];
+            short minAB = (short)(mask ? Math.min(a, b) : a);
+            short maxAB = (short)(mask ? Math.max(a, b) : a);
+            short expected = (short)(mask ? Math.max(minAB, maxAB) : minAB);
+            Verify.checkEQ(sr[i], expected);
+        }
+    }
+
+    // Predicated Short: max(min(a,b,m), max(b,a,m), m) => max(a,b,m)
+    @Test
+    @IR(counts = { IRNode.MIN_VS, IRNode.VECTOR_SIZE_ANY, " 0 ",
+                   IRNode.MAX_VS, IRNode.VECTOR_SIZE_ANY, " 1 " },
+        applyIfCPUFeatureOr = {"avx512bw", "true", "sve", "true"})
+    public void testShortMaskedMaxIdealFlippedInputs(int index) {
+        ShortVector v1 = ShortVector.fromArray(S_SPECIES, sa, index);
+        ShortVector v2 = ShortVector.fromArray(S_SPECIES, sb, index);
+        VectorMask m = VectorMask.fromArray(S_SPECIES, m1arr, index);
+        v1.lanewise(VectorOperators.MIN, v2, m)
+          .lanewise(VectorOperators.MAX, v2.lanewise(VectorOperators.MAX, v1, m), m)
+          .intoArray(sr, index);
+    }
+
+    @Run(test = "testShortMaskedMaxIdealFlippedInputs")
+    public void runShortMaskedMaxIdealFlippedInputs() {
+        for (int i = 0; i < S_SPECIES.loopBound(LENGTH); i += S_SPECIES.length()) {
+            testShortMaskedMaxIdealFlippedInputs(i);
+        }
+        for (int i = 0; i < S_SPECIES.loopBound(LENGTH); i++) {
+            short a = sa[i], b = sb[i];
+            boolean mask = m1arr[i];
+            short minAB = (short)(mask ? Math.min(a, b) : a);
+            short maxBA = (short)(mask ? Math.max(b, a) : b);
+            short expected = (short)(mask ? Math.max(minAB, maxBA) : minAB);
+            Verify.checkEQ(sr[i], expected);
+        }
+    }
+
+    // Predicated Short: min(min(a,b,m), max(b,a,m), m) => min(a,b,m)
+    @Test
+    @IR(counts = { IRNode.MIN_VS, IRNode.VECTOR_SIZE_ANY, " 1 ",
+                   IRNode.MAX_VS, IRNode.VECTOR_SIZE_ANY, " 0 " },
+        applyIfCPUFeatureOr = {"avx512bw", "true", "sve", "true"})
+    public void testShortMaskedMinIdealFlippedInputs(int index) {
+        ShortVector v1 = ShortVector.fromArray(S_SPECIES, sa, index);
+        ShortVector v2 = ShortVector.fromArray(S_SPECIES, sb, index);
+        VectorMask m = VectorMask.fromArray(S_SPECIES, m1arr, index);
+        v1.lanewise(VectorOperators.MIN, v2, m)
+          .lanewise(VectorOperators.MIN, v2.lanewise(VectorOperators.MAX, v1, m), m)
+          .intoArray(sr, index);
+    }
+
+    @Run(test = "testShortMaskedMinIdealFlippedInputs")
+    public void runShortMaskedMinIdealFlippedInputs() {
+        for (int i = 0; i < S_SPECIES.loopBound(LENGTH); i += S_SPECIES.length()) {
+            testShortMaskedMinIdealFlippedInputs(i);
+        }
+        for (int i = 0; i < S_SPECIES.loopBound(LENGTH); i++) {
+            short a = sa[i], b = sb[i];
+            boolean mask = m1arr[i];
+            short minAB = (short)(mask ? Math.min(a, b) : a);
+            short maxBA = (short)(mask ? Math.max(b, a) : b);
+            short expected = (short)(mask ? Math.min(minAB, maxBA) : minAB);
+            Verify.checkEQ(sr[i], expected);
+        }
+    }
+
+    // Predicated Short: min(min(a,b,m1), max(a,b,m2), m1) => NO transform
+    @Test
+    @IR(counts = { IRNode.MIN_VS, IRNode.VECTOR_SIZE_ANY, " 2 ",
+                   IRNode.MAX_VS, IRNode.VECTOR_SIZE_ANY, " 1 " },
+        applyIfCPUFeatureOr = {"avx512bw", "true", "sve", "true"})
+    public void testShortMaskedMinIdealDiffMaskMinMax(int index) {
+        ShortVector v1 = ShortVector.fromArray(S_SPECIES, sa, index);
+        ShortVector v2 = ShortVector.fromArray(S_SPECIES, sb, index);
+        VectorMask mask1 = VectorMask.fromArray(S_SPECIES, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(S_SPECIES, m2arr, index);
+        v1.lanewise(VectorOperators.MIN, v2, mask1)
+          .lanewise(VectorOperators.MIN, v1.lanewise(VectorOperators.MAX, v2, mask2), mask1)
+          .intoArray(sr, index);
+    }
+
+    @Run(test = "testShortMaskedMinIdealDiffMaskMinMax")
+    public void runShortMaskedMinIdealDiffMaskMinMax() {
+        for (int i = 0; i < S_SPECIES.loopBound(LENGTH); i += S_SPECIES.length()) {
+            testShortMaskedMinIdealDiffMaskMinMax(i);
+        }
+        for (int i = 0; i < S_SPECIES.loopBound(LENGTH); i++) {
+            short a = sa[i], b = sb[i];
+            short minAB = (short)(m1arr[i] ? Math.min(a, b) : a);
+            short maxAB = (short)(m2arr[i] ? Math.max(a, b) : a);
+            short expected = (short)(m1arr[i] ? Math.min(minAB, maxAB) : minAB);
+            Verify.checkEQ(sr[i], expected);
+        }
+    }
+
+    // Predicated Short: min(min(a,b,m2), max(a,b,m1), m1) => NO transform
+    @Test
+    @IR(counts = { IRNode.MIN_VS, IRNode.VECTOR_SIZE_ANY, " 2 ",
+                   IRNode.MAX_VS, IRNode.VECTOR_SIZE_ANY, " 1 " },
+        applyIfCPUFeatureOr = {"avx512bw", "true", "sve", "true"})
+    public void testShortMaskedMinIdealDiffMaskMinMaxSwapped(int index) {
+        ShortVector v1 = ShortVector.fromArray(S_SPECIES, sa, index);
+        ShortVector v2 = ShortVector.fromArray(S_SPECIES, sb, index);
+        VectorMask mask1 = VectorMask.fromArray(S_SPECIES, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(S_SPECIES, m2arr, index);
+        v1.lanewise(VectorOperators.MIN, v2, mask2)
+          .lanewise(VectorOperators.MIN, v1.lanewise(VectorOperators.MAX, v2, mask1), mask1)
+          .intoArray(sr, index);
+    }
+
+    @Run(test = "testShortMaskedMinIdealDiffMaskMinMaxSwapped")
+    public void runShortMaskedMinIdealDiffMaskMinMaxSwapped() {
+        for (int i = 0; i < S_SPECIES.loopBound(LENGTH); i += S_SPECIES.length()) {
+            testShortMaskedMinIdealDiffMaskMinMaxSwapped(i);
+        }
+        for (int i = 0; i < S_SPECIES.loopBound(LENGTH); i++) {
+            short a = sa[i], b = sb[i];
+            short minAB = (short)(m2arr[i] ? Math.min(a, b) : a);
+            short maxAB = (short)(m1arr[i] ? Math.max(a, b) : a);
+            short expected = (short)(m1arr[i] ? Math.min(minAB, maxAB) : minAB);
+            Verify.checkEQ(sr[i], expected);
+        }
+    }
+
+    // Predicated Short: min(min(a,b,m1), max(a,b,m1), m2) => NO transform
+    @Test
+    @IR(counts = { IRNode.MIN_VS, IRNode.VECTOR_SIZE_ANY, " 2 ",
+                   IRNode.MAX_VS, IRNode.VECTOR_SIZE_ANY, " 1 " },
+        applyIfCPUFeatureOr = {"avx512bw", "true", "sve", "true"})
+    public void testShortMaskedMinIdealDiffMaskOuter(int index) {
+        ShortVector v1 = ShortVector.fromArray(S_SPECIES, sa, index);
+        ShortVector v2 = ShortVector.fromArray(S_SPECIES, sb, index);
+        VectorMask mask1 = VectorMask.fromArray(S_SPECIES, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(S_SPECIES, m2arr, index);
+        v1.lanewise(VectorOperators.MIN, v2, mask1)
+          .lanewise(VectorOperators.MIN, v1.lanewise(VectorOperators.MAX, v2, mask1), mask2)
+          .intoArray(sr, index);
+    }
+
+    @Run(test = "testShortMaskedMinIdealDiffMaskOuter")
+    public void runShortMaskedMinIdealDiffMaskOuter() {
+        for (int i = 0; i < S_SPECIES.loopBound(LENGTH); i += S_SPECIES.length()) {
+            testShortMaskedMinIdealDiffMaskOuter(i);
+        }
+        for (int i = 0; i < S_SPECIES.loopBound(LENGTH); i++) {
+            short a = sa[i], b = sb[i];
+            short minAB = (short)(m1arr[i] ? Math.min(a, b) : a);
+            short maxAB = (short)(m1arr[i] ? Math.max(a, b) : a);
+            short expected = (short)(m2arr[i] ? Math.min(minAB, maxAB) : minAB);
+            Verify.checkEQ(sr[i], expected);
+        }
+    }
+
+    // Predicated Short: min(min(a,b,m1), max(a,b,m2), m3) => NO transform
+    @Test
+    @IR(counts = { IRNode.MIN_VS, IRNode.VECTOR_SIZE_ANY, " 2 ",
+                   IRNode.MAX_VS, IRNode.VECTOR_SIZE_ANY, " 1 " },
+        applyIfCPUFeatureOr = {"avx512bw", "true", "sve", "true"})
+    public void testShortMaskedMinIdealAllDiffMask(int index) {
+        ShortVector v1 = ShortVector.fromArray(S_SPECIES, sa, index);
+        ShortVector v2 = ShortVector.fromArray(S_SPECIES, sb, index);
+        VectorMask mask1 = VectorMask.fromArray(S_SPECIES, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(S_SPECIES, m2arr, index);
+        VectorMask mask3 = VectorMask.fromArray(S_SPECIES, m3arr, index);
+        v1.lanewise(VectorOperators.MIN, v2, mask1)
+          .lanewise(VectorOperators.MIN, v1.lanewise(VectorOperators.MAX, v2, mask2), mask3)
+          .intoArray(sr, index);
+    }
+
+    @Run(test = "testShortMaskedMinIdealAllDiffMask")
+    public void runShortMaskedMinIdealAllDiffMask() {
+        for (int i = 0; i < S_SPECIES.loopBound(LENGTH); i += S_SPECIES.length()) {
+            testShortMaskedMinIdealAllDiffMask(i);
+        }
+        for (int i = 0; i < S_SPECIES.loopBound(LENGTH); i++) {
+            short a = sa[i], b = sb[i];
+            short minAB = (short)(m1arr[i] ? Math.min(a, b) : a);
+            short maxAB = (short)(m2arr[i] ? Math.max(a, b) : a);
+            short expected = (short)(m3arr[i] ? Math.min(minAB, maxAB) : minAB);
+            Verify.checkEQ(sr[i], expected);
+        }
+    }
+
+    // Predicated Long: min(min(a,b,m), max(a,b,m), m) => min(a,b,m)
+    @Test
+    @IR(counts = { IRNode.MIN_VL, IRNode.VECTOR_SIZE_ANY, " 1 ",
+                   IRNode.MAX_VL, IRNode.VECTOR_SIZE_ANY, " 0 " },
+        applyIfCPUFeatureOr = {"avx512f", "true", "sve", "true"})
+    public void testLongMaskedMinIdealSameMask(int index) {
+        LongVector v1 = LongVector.fromArray(L_SPECIES, la, index);
+        LongVector v2 = LongVector.fromArray(L_SPECIES, lb, index);
+        VectorMask m = VectorMask.fromArray(L_SPECIES, m1arr, index);
+        v1.lanewise(VectorOperators.MIN, v2, m)
+          .lanewise(VectorOperators.MIN, v1.lanewise(VectorOperators.MAX, v2, m), m)
+          .intoArray(lr, index);
+    }
+
+    @Run(test = "testLongMaskedMinIdealSameMask")
+    public void runLongMaskedMinIdealSameMask() {
+        for (int i = 0; i < L_SPECIES.loopBound(LENGTH); i += L_SPECIES.length()) {
+            testLongMaskedMinIdealSameMask(i);
+        }
+        for (int i = 0; i < L_SPECIES.loopBound(LENGTH); i++) {
+            long a = la[i], b = lb[i];
+            boolean mask = m1arr[i];
+            long minAB = mask ? Math.min(a, b) : a;
+            long maxAB = mask ? Math.max(a, b) : a;
+            long expected = mask ? Math.min(minAB, maxAB) : minAB;
+            Verify.checkEQ(lr[i], expected);
+        }
+    }
+
+    // Predicated Long: max(min(a,b,m), max(a,b,m), m) => max(a,b,m)
+    @Test
+    @IR(counts = { IRNode.MIN_VL, IRNode.VECTOR_SIZE_ANY, " 0 ",
+                   IRNode.MAX_VL, IRNode.VECTOR_SIZE_ANY, " 1 " },
+        applyIfCPUFeatureOr = {"avx512f", "true", "sve", "true"})
+    public void testLongMaskedMaxIdealSameMask(int index) {
+        LongVector v1 = LongVector.fromArray(L_SPECIES, la, index);
+        LongVector v2 = LongVector.fromArray(L_SPECIES, lb, index);
+        VectorMask m = VectorMask.fromArray(L_SPECIES, m1arr, index);
+        v1.lanewise(VectorOperators.MIN, v2, m)
+          .lanewise(VectorOperators.MAX, v1.lanewise(VectorOperators.MAX, v2, m), m)
+          .intoArray(lr, index);
+    }
+
+    @Run(test = "testLongMaskedMaxIdealSameMask")
+    public void runLongMaskedMaxIdealSameMask() {
+        for (int i = 0; i < L_SPECIES.loopBound(LENGTH); i += L_SPECIES.length()) {
+            testLongMaskedMaxIdealSameMask(i);
+        }
+        for (int i = 0; i < L_SPECIES.loopBound(LENGTH); i++) {
+            long a = la[i], b = lb[i];
+            boolean mask = m1arr[i];
+            long minAB = mask ? Math.min(a, b) : a;
+            long maxAB = mask ? Math.max(a, b) : a;
+            long expected = mask ? Math.max(minAB, maxAB) : minAB;
+            Verify.checkEQ(lr[i], expected);
+        }
+    }
+
+    // Predicated Long: max(min(a,b,m), max(b,a,m), m) => max(a,b,m)
+    @Test
+    @IR(counts = { IRNode.MIN_VL, IRNode.VECTOR_SIZE_ANY, " 0 ",
+                   IRNode.MAX_VL, IRNode.VECTOR_SIZE_ANY, " 1 " },
+        applyIfCPUFeatureOr = {"avx512f", "true", "sve", "true"})
+    public void testLongMaskedMaxIdealFlippedInputs(int index) {
+        LongVector v1 = LongVector.fromArray(L_SPECIES, la, index);
+        LongVector v2 = LongVector.fromArray(L_SPECIES, lb, index);
+        VectorMask m = VectorMask.fromArray(L_SPECIES, m1arr, index);
+        v1.lanewise(VectorOperators.MIN, v2, m)
+          .lanewise(VectorOperators.MAX, v2.lanewise(VectorOperators.MAX, v1, m), m)
+          .intoArray(lr, index);
+    }
+
+    @Run(test = "testLongMaskedMaxIdealFlippedInputs")
+    public void runLongMaskedMaxIdealFlippedInputs() {
+        for (int i = 0; i < L_SPECIES.loopBound(LENGTH); i += L_SPECIES.length()) {
+            testLongMaskedMaxIdealFlippedInputs(i);
+        }
+        for (int i = 0; i < L_SPECIES.loopBound(LENGTH); i++) {
+            long a = la[i], b = lb[i];
+            boolean mask = m1arr[i];
+            long minAB = mask ? Math.min(a, b) : a;
+            long maxBA = mask ? Math.max(b, a) : b;
+            long expected = mask ? Math.max(minAB, maxBA) : minAB;
+            Verify.checkEQ(lr[i], expected);
+        }
+    }
+
+    // Predicated Long: min(min(a,b,m), max(b,a,m), m) => min(a,b,m)
+    @Test
+    @IR(counts = { IRNode.MIN_VL, IRNode.VECTOR_SIZE_ANY, " 1 ",
+                   IRNode.MAX_VL, IRNode.VECTOR_SIZE_ANY, " 0 " },
+        applyIfCPUFeatureOr = {"avx512f", "true", "sve", "true"})
+    public void testLongMaskedMinIdealFlippedInputs(int index) {
+        LongVector v1 = LongVector.fromArray(L_SPECIES, la, index);
+        LongVector v2 = LongVector.fromArray(L_SPECIES, lb, index);
+        VectorMask m = VectorMask.fromArray(L_SPECIES, m1arr, index);
+        v1.lanewise(VectorOperators.MIN, v2, m)
+          .lanewise(VectorOperators.MIN, v2.lanewise(VectorOperators.MAX, v1, m), m)
+          .intoArray(lr, index);
+    }
+
+    @Run(test = "testLongMaskedMinIdealFlippedInputs")
+    public void runLongMaskedMinIdealFlippedInputs() {
+        for (int i = 0; i < L_SPECIES.loopBound(LENGTH); i += L_SPECIES.length()) {
+            testLongMaskedMinIdealFlippedInputs(i);
+        }
+        for (int i = 0; i < L_SPECIES.loopBound(LENGTH); i++) {
+            long a = la[i], b = lb[i];
+            boolean mask = m1arr[i];
+            long minAB = mask ? Math.min(a, b) : a;
+            long maxBA = mask ? Math.max(b, a) : b;
+            long expected = mask ? Math.min(minAB, maxBA) : minAB;
+            Verify.checkEQ(lr[i], expected);
+        }
+    }
+
+    // Predicated Long: min(min(a,b,m1), max(a,b,m2), m1) => NO transform
+    @Test
+    @IR(counts = { IRNode.MIN_VL, IRNode.VECTOR_SIZE_ANY, " 2 ",
+                   IRNode.MAX_VL, IRNode.VECTOR_SIZE_ANY, " 1 " },
+        applyIfCPUFeatureOr = {"avx512f", "true", "sve", "true"})
+    public void testLongMaskedMinIdealDiffMaskMinMax(int index) {
+        LongVector v1 = LongVector.fromArray(L_SPECIES, la, index);
+        LongVector v2 = LongVector.fromArray(L_SPECIES, lb, index);
+        VectorMask mask1 = VectorMask.fromArray(L_SPECIES, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(L_SPECIES, m2arr, index);
+        v1.lanewise(VectorOperators.MIN, v2, mask1)
+          .lanewise(VectorOperators.MIN, v1.lanewise(VectorOperators.MAX, v2, mask2), mask1)
+          .intoArray(lr, index);
+    }
+
+    @Run(test = "testLongMaskedMinIdealDiffMaskMinMax")
+    public void runLongMaskedMinIdealDiffMaskMinMax() {
+        for (int i = 0; i < L_SPECIES.loopBound(LENGTH); i += L_SPECIES.length()) {
+            testLongMaskedMinIdealDiffMaskMinMax(i);
+        }
+        for (int i = 0; i < L_SPECIES.loopBound(LENGTH); i++) {
+            long a = la[i], b = lb[i];
+            long minAB = m1arr[i] ? Math.min(a, b) : a;
+            long maxAB = m2arr[i] ? Math.max(a, b) : a;
+            long expected = m1arr[i] ? Math.min(minAB, maxAB) : minAB;
+            Verify.checkEQ(lr[i], expected);
+        }
+    }
+
+    // Predicated Long: min(min(a,b,m2), max(a,b,m1), m1) => NO transform
+    @Test
+    @IR(counts = { IRNode.MIN_VL, IRNode.VECTOR_SIZE_ANY, " 2 ",
+                   IRNode.MAX_VL, IRNode.VECTOR_SIZE_ANY, " 1 " },
+        applyIfCPUFeatureOr = {"avx512f", "true", "sve", "true"})
+    public void testLongMaskedMinIdealDiffMaskMinMaxSwapped(int index) {
+        LongVector v1 = LongVector.fromArray(L_SPECIES, la, index);
+        LongVector v2 = LongVector.fromArray(L_SPECIES, lb, index);
+        VectorMask mask1 = VectorMask.fromArray(L_SPECIES, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(L_SPECIES, m2arr, index);
+        v1.lanewise(VectorOperators.MIN, v2, mask2)
+          .lanewise(VectorOperators.MIN, v1.lanewise(VectorOperators.MAX, v2, mask1), mask1)
+          .intoArray(lr, index);
+    }
+
+    @Run(test = "testLongMaskedMinIdealDiffMaskMinMaxSwapped")
+    public void runLongMaskedMinIdealDiffMaskMinMaxSwapped() {
+        for (int i = 0; i < L_SPECIES.loopBound(LENGTH); i += L_SPECIES.length()) {
+            testLongMaskedMinIdealDiffMaskMinMaxSwapped(i);
+        }
+        for (int i = 0; i < L_SPECIES.loopBound(LENGTH); i++) {
+            long a = la[i], b = lb[i];
+            long minAB = m2arr[i] ? Math.min(a, b) : a;
+            long maxAB = m1arr[i] ? Math.max(a, b) : a;
+            long expected = m1arr[i] ? Math.min(minAB, maxAB) : minAB;
+            Verify.checkEQ(lr[i], expected);
+        }
+    }
+
+    // Predicated Long: min(min(a,b,m1), max(a,b,m1), m2) => NO transform
+    @Test
+    @IR(counts = { IRNode.MIN_VL, IRNode.VECTOR_SIZE_ANY, " 2 ",
+                   IRNode.MAX_VL, IRNode.VECTOR_SIZE_ANY, " 1 " },
+        applyIfCPUFeatureOr = {"avx512f", "true", "sve", "true"})
+    public void testLongMaskedMinIdealDiffMaskOuter(int index) {
+        LongVector v1 = LongVector.fromArray(L_SPECIES, la, index);
+        LongVector v2 = LongVector.fromArray(L_SPECIES, lb, index);
+        VectorMask mask1 = VectorMask.fromArray(L_SPECIES, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(L_SPECIES, m2arr, index);
+        v1.lanewise(VectorOperators.MIN, v2, mask1)
+          .lanewise(VectorOperators.MIN, v1.lanewise(VectorOperators.MAX, v2, mask1), mask2)
+          .intoArray(lr, index);
+    }
+
+    @Run(test = "testLongMaskedMinIdealDiffMaskOuter")
+    public void runLongMaskedMinIdealDiffMaskOuter() {
+        for (int i = 0; i < L_SPECIES.loopBound(LENGTH); i += L_SPECIES.length()) {
+            testLongMaskedMinIdealDiffMaskOuter(i);
+        }
+        for (int i = 0; i < L_SPECIES.loopBound(LENGTH); i++) {
+            long a = la[i], b = lb[i];
+            long minAB = m1arr[i] ? Math.min(a, b) : a;
+            long maxAB = m1arr[i] ? Math.max(a, b) : a;
+            long expected = m2arr[i] ? Math.min(minAB, maxAB) : minAB;
+            Verify.checkEQ(lr[i], expected);
+        }
+    }
+
+    // Predicated Long: min(min(a,b,m1), max(a,b,m2), m3) => NO transform
+    @Test
+    @IR(counts = { IRNode.MIN_VL, IRNode.VECTOR_SIZE_ANY, " 2 ",
+                   IRNode.MAX_VL, IRNode.VECTOR_SIZE_ANY, " 1 " },
+        applyIfCPUFeatureOr = {"avx512f", "true", "sve", "true"})
+    public void testLongMaskedMinIdealAllDiffMask(int index) {
+        LongVector v1 = LongVector.fromArray(L_SPECIES, la, index);
+        LongVector v2 = LongVector.fromArray(L_SPECIES, lb, index);
+        VectorMask mask1 = VectorMask.fromArray(L_SPECIES, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(L_SPECIES, m2arr, index);
+        VectorMask mask3 = VectorMask.fromArray(L_SPECIES, m3arr, index);
+        v1.lanewise(VectorOperators.MIN, v2, mask1)
+          .lanewise(VectorOperators.MIN, v1.lanewise(VectorOperators.MAX, v2, mask2), mask3)
+          .intoArray(lr, index);
+    }
+
+    @Run(test = "testLongMaskedMinIdealAllDiffMask")
+    public void runLongMaskedMinIdealAllDiffMask() {
+        for (int i = 0; i < L_SPECIES.loopBound(LENGTH); i += L_SPECIES.length()) {
+            testLongMaskedMinIdealAllDiffMask(i);
+        }
+        for (int i = 0; i < L_SPECIES.loopBound(LENGTH); i++) {
+            long a = la[i], b = lb[i];
+            long minAB = m1arr[i] ? Math.min(a, b) : a;
+            long maxAB = m2arr[i] ? Math.max(a, b) : a;
+            long expected = m3arr[i] ? Math.min(minAB, maxAB) : minAB;
+            Verify.checkEQ(lr[i], expected);
+        }
+    }
+
+    // Predicated Float: min(min(a,b,m), max(a,b,m), m) => min(a,b,m)
+    @Test
+    @IR(counts = { IRNode.MIN_VF, IRNode.VECTOR_SIZE_ANY, " 1 ",
+                   IRNode.MAX_VF, IRNode.VECTOR_SIZE_ANY, " 0 " },
+        applyIfCPUFeatureOr = {"avx10_2", "true", "sve", "true"})
+    public void testFloatMaskedMinIdealSameMask(int index) {
+        FloatVector v1 = FloatVector.fromArray(F_SPECIES, fa, index);
+        FloatVector v2 = FloatVector.fromArray(F_SPECIES, fb, index);
+        VectorMask m = VectorMask.fromArray(F_SPECIES, m1arr, index);
+        v1.lanewise(VectorOperators.MIN, v2, m)
+          .lanewise(VectorOperators.MIN, v1.lanewise(VectorOperators.MAX, v2, m), m)
+          .intoArray(fr, index);
+    }
+
+    @Run(test = "testFloatMaskedMinIdealSameMask")
+    public void runFloatMaskedMinIdealSameMask() {
+        for (int i = 0; i < F_SPECIES.loopBound(LENGTH); i += F_SPECIES.length()) {
+            testFloatMaskedMinIdealSameMask(i);
+        }
+        for (int i = 0; i < F_SPECIES.loopBound(LENGTH); i++) {
+            float a = fa[i], b = fb[i];
+            boolean mask = m1arr[i];
+            float minAB = mask ? Math.min(a, b) : a;
+            float maxAB = mask ? Math.max(a, b) : a;
+            float expected = mask ? Math.min(minAB, maxAB) : minAB;
+            Verify.checkEQ(fr[i], expected);
+        }
+    }
+
+    // Predicated Float: max(min(a,b,m), max(a,b,m), m) => max(a,b,m)
+    @Test
+    @IR(counts = { IRNode.MIN_VF, IRNode.VECTOR_SIZE_ANY, " 0 ",
+                   IRNode.MAX_VF, IRNode.VECTOR_SIZE_ANY, " 1 " },
+        applyIfCPUFeatureOr = {"avx10_2", "true", "sve", "true"})
+    public void testFloatMaskedMaxIdealSameMask(int index) {
+        FloatVector v1 = FloatVector.fromArray(F_SPECIES, fa, index);
+        FloatVector v2 = FloatVector.fromArray(F_SPECIES, fb, index);
+        VectorMask m = VectorMask.fromArray(F_SPECIES, m1arr, index);
+        v1.lanewise(VectorOperators.MIN, v2, m)
+          .lanewise(VectorOperators.MAX, v1.lanewise(VectorOperators.MAX, v2, m), m)
+          .intoArray(fr, index);
+    }
+
+    @Run(test = "testFloatMaskedMaxIdealSameMask")
+    public void runFloatMaskedMaxIdealSameMask() {
+        for (int i = 0; i < F_SPECIES.loopBound(LENGTH); i += F_SPECIES.length()) {
+            testFloatMaskedMaxIdealSameMask(i);
+        }
+        for (int i = 0; i < F_SPECIES.loopBound(LENGTH); i++) {
+            float a = fa[i], b = fb[i];
+            boolean mask = m1arr[i];
+            float minAB = mask ? Math.min(a, b) : a;
+            float maxAB = mask ? Math.max(a, b) : a;
+            float expected = mask ? Math.max(minAB, maxAB) : minAB;
+            Verify.checkEQ(fr[i], expected);
+        }
+    }
+
+    // Predicated Float: max(min(a,b,m), max(b,a,m), m) => max(a,b,m)
+    @Test
+    @IR(counts = { IRNode.MIN_VF, IRNode.VECTOR_SIZE_ANY, " 0 ",
+                   IRNode.MAX_VF, IRNode.VECTOR_SIZE_ANY, " 1 " },
+        applyIfCPUFeatureOr = {"avx10_2", "true", "sve", "true"})
+    public void testFloatMaskedMaxIdealFlippedInputs(int index) {
+        FloatVector v1 = FloatVector.fromArray(F_SPECIES, fa, index);
+        FloatVector v2 = FloatVector.fromArray(F_SPECIES, fb, index);
+        VectorMask m = VectorMask.fromArray(F_SPECIES, m1arr, index);
+        v1.lanewise(VectorOperators.MIN, v2, m)
+          .lanewise(VectorOperators.MAX, v2.lanewise(VectorOperators.MAX, v1, m), m)
+          .intoArray(fr, index);
+    }
+
+    @Run(test = "testFloatMaskedMaxIdealFlippedInputs")
+    public void runFloatMaskedMaxIdealFlippedInputs() {
+        for (int i = 0; i < F_SPECIES.loopBound(LENGTH); i += F_SPECIES.length()) {
+            testFloatMaskedMaxIdealFlippedInputs(i);
+        }
+        for (int i = 0; i < F_SPECIES.loopBound(LENGTH); i++) {
+            float a = fa[i], b = fb[i];
+            boolean mask = m1arr[i];
+            float minAB = mask ? Math.min(a, b) : a;
+            float maxBA = mask ? Math.max(b, a) : b;
+            float expected = mask ? Math.max(minAB, maxBA) : minAB;
+            Verify.checkEQ(fr[i], expected);
+        }
+    }
+
+    // Predicated Float: min(min(a,b,m), max(b,a,m), m) => min(a,b,m)
+    @Test
+    @IR(counts = { IRNode.MIN_VF, IRNode.VECTOR_SIZE_ANY, " 1 ",
+                   IRNode.MAX_VF, IRNode.VECTOR_SIZE_ANY, " 0 " },
+        applyIfCPUFeatureOr = {"avx10_2", "true", "sve", "true"})
+    public void testFloatMaskedMinIdealFlippedInputs(int index) {
+        FloatVector v1 = FloatVector.fromArray(F_SPECIES, fa, index);
+        FloatVector v2 = FloatVector.fromArray(F_SPECIES, fb, index);
+        VectorMask m = VectorMask.fromArray(F_SPECIES, m1arr, index);
+        v1.lanewise(VectorOperators.MIN, v2, m)
+          .lanewise(VectorOperators.MIN, v2.lanewise(VectorOperators.MAX, v1, m), m)
+          .intoArray(fr, index);
+    }
+
+    @Run(test = "testFloatMaskedMinIdealFlippedInputs")
+    public void runFloatMaskedMinIdealFlippedInputs() {
+        for (int i = 0; i < F_SPECIES.loopBound(LENGTH); i += F_SPECIES.length()) {
+            testFloatMaskedMinIdealFlippedInputs(i);
+        }
+        for (int i = 0; i < F_SPECIES.loopBound(LENGTH); i++) {
+            float a = fa[i], b = fb[i];
+            boolean mask = m1arr[i];
+            float minAB = mask ? Math.min(a, b) : a;
+            float maxBA = mask ? Math.max(b, a) : b;
+            float expected = mask ? Math.min(minAB, maxBA) : minAB;
+            Verify.checkEQ(fr[i], expected);
+        }
+    }
+
+    // Predicated Float: min(min(a,b,m1), max(a,b,m2), m1) => NO transform
+    @Test
+    @IR(counts = { IRNode.MIN_VF, IRNode.VECTOR_SIZE_ANY, " 2 ",
+                   IRNode.MAX_VF, IRNode.VECTOR_SIZE_ANY, " 1 " },
+        applyIfCPUFeatureOr = {"avx10_2", "true", "sve", "true"})
+    public void testFloatMaskedMinIdealDiffMaskMinMax(int index) {
+        FloatVector v1 = FloatVector.fromArray(F_SPECIES, fa, index);
+        FloatVector v2 = FloatVector.fromArray(F_SPECIES, fb, index);
+        VectorMask mask1 = VectorMask.fromArray(F_SPECIES, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(F_SPECIES, m2arr, index);
+        v1.lanewise(VectorOperators.MIN, v2, mask1)
+          .lanewise(VectorOperators.MIN, v1.lanewise(VectorOperators.MAX, v2, mask2), mask1)
+          .intoArray(fr, index);
+    }
+
+    @Run(test = "testFloatMaskedMinIdealDiffMaskMinMax")
+    public void runFloatMaskedMinIdealDiffMaskMinMax() {
+        for (int i = 0; i < F_SPECIES.loopBound(LENGTH); i += F_SPECIES.length()) {
+            testFloatMaskedMinIdealDiffMaskMinMax(i);
+        }
+        for (int i = 0; i < F_SPECIES.loopBound(LENGTH); i++) {
+            float a = fa[i], b = fb[i];
+            float minAB = m1arr[i] ? Math.min(a, b) : a;
+            float maxAB = m2arr[i] ? Math.max(a, b) : a;
+            float expected = m1arr[i] ? Math.min(minAB, maxAB) : minAB;
+            Verify.checkEQ(fr[i], expected);
+        }
+    }
+
+    // Predicated Float: min(min(a,b,m2), max(a,b,m1), m1) => NO transform
+    @Test
+    @IR(counts = { IRNode.MIN_VF, IRNode.VECTOR_SIZE_ANY, " 2 ",
+                   IRNode.MAX_VF, IRNode.VECTOR_SIZE_ANY, " 1 " },
+        applyIfCPUFeatureOr = {"avx10_2", "true", "sve", "true"})
+    public void testFloatMaskedMinIdealDiffMaskMinMaxSwapped(int index) {
+        FloatVector v1 = FloatVector.fromArray(F_SPECIES, fa, index);
+        FloatVector v2 = FloatVector.fromArray(F_SPECIES, fb, index);
+        VectorMask mask1 = VectorMask.fromArray(F_SPECIES, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(F_SPECIES, m2arr, index);
+        v1.lanewise(VectorOperators.MIN, v2, mask2)
+          .lanewise(VectorOperators.MIN, v1.lanewise(VectorOperators.MAX, v2, mask1), mask1)
+          .intoArray(fr, index);
+    }
+
+    @Run(test = "testFloatMaskedMinIdealDiffMaskMinMaxSwapped")
+    public void runFloatMaskedMinIdealDiffMaskMinMaxSwapped() {
+        for (int i = 0; i < F_SPECIES.loopBound(LENGTH); i += F_SPECIES.length()) {
+            testFloatMaskedMinIdealDiffMaskMinMaxSwapped(i);
+        }
+        for (int i = 0; i < F_SPECIES.loopBound(LENGTH); i++) {
+            float a = fa[i], b = fb[i];
+            float minAB = m2arr[i] ? Math.min(a, b) : a;
+            float maxAB = m1arr[i] ? Math.max(a, b) : a;
+            float expected = m1arr[i] ? Math.min(minAB, maxAB) : minAB;
+            Verify.checkEQ(fr[i], expected);
+        }
+    }
+
+    // Predicated Float: min(min(a,b,m1), max(a,b,m1), m2) => NO transform
+    @Test
+    @IR(counts = { IRNode.MIN_VF, IRNode.VECTOR_SIZE_ANY, " 2 ",
+                   IRNode.MAX_VF, IRNode.VECTOR_SIZE_ANY, " 1 " },
+        applyIfCPUFeatureOr = {"avx10_2", "true", "sve", "true"})
+    public void testFloatMaskedMinIdealDiffMaskOuter(int index) {
+        FloatVector v1 = FloatVector.fromArray(F_SPECIES, fa, index);
+        FloatVector v2 = FloatVector.fromArray(F_SPECIES, fb, index);
+        VectorMask mask1 = VectorMask.fromArray(F_SPECIES, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(F_SPECIES, m2arr, index);
+        v1.lanewise(VectorOperators.MIN, v2, mask1)
+          .lanewise(VectorOperators.MIN, v1.lanewise(VectorOperators.MAX, v2, mask1), mask2)
+          .intoArray(fr, index);
+    }
+
+    @Run(test = "testFloatMaskedMinIdealDiffMaskOuter")
+    public void runFloatMaskedMinIdealDiffMaskOuter() {
+        for (int i = 0; i < F_SPECIES.loopBound(LENGTH); i += F_SPECIES.length()) {
+            testFloatMaskedMinIdealDiffMaskOuter(i);
+        }
+        for (int i = 0; i < F_SPECIES.loopBound(LENGTH); i++) {
+            float a = fa[i], b = fb[i];
+            float minAB = m1arr[i] ? Math.min(a, b) : a;
+            float maxAB = m1arr[i] ? Math.max(a, b) : a;
+            float expected = m2arr[i] ? Math.min(minAB, maxAB) : minAB;
+            Verify.checkEQ(fr[i], expected);
+        }
+    }
+
+    // Predicated Float: min(min(a,b,m1), max(a,b,m2), m3) => NO transform
+    @Test
+    @IR(counts = { IRNode.MIN_VF, IRNode.VECTOR_SIZE_ANY, " 2 ",
+                   IRNode.MAX_VF, IRNode.VECTOR_SIZE_ANY, " 1 " },
+        applyIfCPUFeatureOr = {"avx10_2", "true", "sve", "true"})
+    public void testFloatMaskedMinIdealAllDiffMask(int index) {
+        FloatVector v1 = FloatVector.fromArray(F_SPECIES, fa, index);
+        FloatVector v2 = FloatVector.fromArray(F_SPECIES, fb, index);
+        VectorMask mask1 = VectorMask.fromArray(F_SPECIES, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(F_SPECIES, m2arr, index);
+        VectorMask mask3 = VectorMask.fromArray(F_SPECIES, m3arr, index);
+        v1.lanewise(VectorOperators.MIN, v2, mask1)
+          .lanewise(VectorOperators.MIN, v1.lanewise(VectorOperators.MAX, v2, mask2), mask3)
+          .intoArray(fr, index);
+    }
+
+    @Run(test = "testFloatMaskedMinIdealAllDiffMask")
+    public void runFloatMaskedMinIdealAllDiffMask() {
+        for (int i = 0; i < F_SPECIES.loopBound(LENGTH); i += F_SPECIES.length()) {
+            testFloatMaskedMinIdealAllDiffMask(i);
+        }
+        for (int i = 0; i < F_SPECIES.loopBound(LENGTH); i++) {
+            float a = fa[i], b = fb[i];
+            float minAB = m1arr[i] ? Math.min(a, b) : a;
+            float maxAB = m2arr[i] ? Math.max(a, b) : a;
+            float expected = m3arr[i] ? Math.min(minAB, maxAB) : minAB;
+            Verify.checkEQ(fr[i], expected);
+        }
+    }
+
+    // Predicated Double: min(min(a,b,m), max(a,b,m), m) => min(a,b,m)
+    @Test
+    @IR(counts = { IRNode.MIN_VD, IRNode.VECTOR_SIZE_ANY, " 1 ",
+                   IRNode.MAX_VD, IRNode.VECTOR_SIZE_ANY, " 0 " },
+        applyIfCPUFeatureOr = {"avx10_2", "true", "sve", "true"})
+    public void testDoubleMaskedMinIdealSameMask(int index) {
+        DoubleVector v1 = DoubleVector.fromArray(D_SPECIES, da, index);
+        DoubleVector v2 = DoubleVector.fromArray(D_SPECIES, db, index);
+        VectorMask m = VectorMask.fromArray(D_SPECIES, m1arr, index);
+        v1.lanewise(VectorOperators.MIN, v2, m)
+          .lanewise(VectorOperators.MIN, v1.lanewise(VectorOperators.MAX, v2, m), m)
+          .intoArray(dr, index);
+    }
+
+    @Run(test = "testDoubleMaskedMinIdealSameMask")
+    public void runDoubleMaskedMinIdealSameMask() {
+        for (int i = 0; i < D_SPECIES.loopBound(LENGTH); i += D_SPECIES.length()) {
+            testDoubleMaskedMinIdealSameMask(i);
+        }
+        for (int i = 0; i < D_SPECIES.loopBound(LENGTH); i++) {
+            double a = da[i], b = db[i];
+            boolean mask = m1arr[i];
+            double minAB = mask ? Math.min(a, b) : a;
+            double maxAB = mask ? Math.max(a, b) : a;
+            double expected = mask ? Math.min(minAB, maxAB) : minAB;
+            Verify.checkEQ(dr[i], expected);
+        }
+    }
+
+    // Predicated Double: max(min(a,b,m), max(a,b,m), m) => max(a,b,m)
+    @Test
+    @IR(counts = { IRNode.MIN_VD, IRNode.VECTOR_SIZE_ANY, " 0 ",
+                   IRNode.MAX_VD, IRNode.VECTOR_SIZE_ANY, " 1 " },
+        applyIfCPUFeatureOr = {"avx10_2", "true", "sve", "true"})
+    public void testDoubleMaskedMaxIdealSameMask(int index) {
+        DoubleVector v1 = DoubleVector.fromArray(D_SPECIES, da, index);
+        DoubleVector v2 = DoubleVector.fromArray(D_SPECIES, db, index);
+        VectorMask m = VectorMask.fromArray(D_SPECIES, m1arr, index);
+        v1.lanewise(VectorOperators.MIN, v2, m)
+          .lanewise(VectorOperators.MAX, v1.lanewise(VectorOperators.MAX, v2, m), m)
+          .intoArray(dr, index);
+    }
+
+    @Run(test = "testDoubleMaskedMaxIdealSameMask")
+    public void runDoubleMaskedMaxIdealSameMask() {
+        for (int i = 0; i < D_SPECIES.loopBound(LENGTH); i += D_SPECIES.length()) {
+            testDoubleMaskedMaxIdealSameMask(i);
+        }
+        for (int i = 0; i < D_SPECIES.loopBound(LENGTH); i++) {
+            double a = da[i], b = db[i];
+            boolean mask = m1arr[i];
+            double minAB = mask ? Math.min(a, b) : a;
+            double maxAB = mask ? Math.max(a, b) : a;
+            double expected = mask ? Math.max(minAB, maxAB) : minAB;
+            Verify.checkEQ(dr[i], expected);
+        }
+    }
+
+    // Predicated Double: max(min(a,b,m), max(b,a,m), m) => max(a,b,m)
+    @Test
+    @IR(counts = { IRNode.MIN_VD, IRNode.VECTOR_SIZE_ANY, " 0 ",
+                   IRNode.MAX_VD, IRNode.VECTOR_SIZE_ANY, " 1 " },
+        applyIfCPUFeatureOr = {"avx10_2", "true", "sve", "true"})
+    public void testDoubleMaskedMaxIdealFlippedInputs(int index) {
+        DoubleVector v1 = DoubleVector.fromArray(D_SPECIES, da, index);
+        DoubleVector v2 = DoubleVector.fromArray(D_SPECIES, db, index);
+        VectorMask m = VectorMask.fromArray(D_SPECIES, m1arr, index);
+        v1.lanewise(VectorOperators.MIN, v2, m)
+          .lanewise(VectorOperators.MAX, v2.lanewise(VectorOperators.MAX, v1, m), m)
+          .intoArray(dr, index);
+    }
+
+    @Run(test = "testDoubleMaskedMaxIdealFlippedInputs")
+    public void runDoubleMaskedMaxIdealFlippedInputs() {
+        for (int i = 0; i < D_SPECIES.loopBound(LENGTH); i += D_SPECIES.length()) {
+            testDoubleMaskedMaxIdealFlippedInputs(i);
+        }
+        for (int i = 0; i < D_SPECIES.loopBound(LENGTH); i++) {
+            double a = da[i], b = db[i];
+            boolean mask = m1arr[i];
+            double minAB = mask ? Math.min(a, b) : a;
+            double maxBA = mask ? Math.max(b, a) : b;
+            double expected = mask ? Math.max(minAB, maxBA) : minAB;
+            Verify.checkEQ(dr[i], expected);
+        }
+    }
+
+    // Predicated Double: min(min(a,b,m), max(b,a,m), m) => min(a,b,m)
+    @Test
+    @IR(counts = { IRNode.MIN_VD, IRNode.VECTOR_SIZE_ANY, " 1 ",
+                   IRNode.MAX_VD, IRNode.VECTOR_SIZE_ANY, " 0 " },
+        applyIfCPUFeatureOr = {"avx10_2", "true", "sve", "true"})
+    public void testDoubleMaskedMinIdealFlippedInputs(int index) {
+        DoubleVector v1 = DoubleVector.fromArray(D_SPECIES, da, index);
+        DoubleVector v2 = DoubleVector.fromArray(D_SPECIES, db, index);
+        VectorMask m = VectorMask.fromArray(D_SPECIES, m1arr, index);
+        v1.lanewise(VectorOperators.MIN, v2, m)
+          .lanewise(VectorOperators.MIN, v2.lanewise(VectorOperators.MAX, v1, m), m)
+          .intoArray(dr, index);
+    }
+
+    @Run(test = "testDoubleMaskedMinIdealFlippedInputs")
+    public void runDoubleMaskedMinIdealFlippedInputs() {
+        for (int i = 0; i < D_SPECIES.loopBound(LENGTH); i += D_SPECIES.length()) {
+            testDoubleMaskedMinIdealFlippedInputs(i);
+        }
+        for (int i = 0; i < D_SPECIES.loopBound(LENGTH); i++) {
+            double a = da[i], b = db[i];
+            boolean mask = m1arr[i];
+            double minAB = mask ? Math.min(a, b) : a;
+            double maxBA = mask ? Math.max(b, a) : b;
+            double expected = mask ? Math.min(minAB, maxBA) : minAB;
+            Verify.checkEQ(dr[i], expected);
+        }
+    }
+
+    // Predicated Double: min(min(a,b,m1), max(a,b,m2), m1) => NO transform
+    @Test
+    @IR(counts = { IRNode.MIN_VD, IRNode.VECTOR_SIZE_ANY, " 2 ",
+                   IRNode.MAX_VD, IRNode.VECTOR_SIZE_ANY, " 1 " },
+        applyIfCPUFeatureOr = {"avx10_2", "true", "sve", "true"})
+    public void testDoubleMaskedMinIdealDiffMaskMinMax(int index) {
+        DoubleVector v1 = DoubleVector.fromArray(D_SPECIES, da, index);
+        DoubleVector v2 = DoubleVector.fromArray(D_SPECIES, db, index);
+        VectorMask mask1 = VectorMask.fromArray(D_SPECIES, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(D_SPECIES, m2arr, index);
+        v1.lanewise(VectorOperators.MIN, v2, mask1)
+          .lanewise(VectorOperators.MIN, v1.lanewise(VectorOperators.MAX, v2, mask2), mask1)
+          .intoArray(dr, index);
+    }
+
+    @Run(test = "testDoubleMaskedMinIdealDiffMaskMinMax")
+    public void runDoubleMaskedMinIdealDiffMaskMinMax() {
+        for (int i = 0; i < D_SPECIES.loopBound(LENGTH); i += D_SPECIES.length()) {
+            testDoubleMaskedMinIdealDiffMaskMinMax(i);
+        }
+        for (int i = 0; i < D_SPECIES.loopBound(LENGTH); i++) {
+            double a = da[i], b = db[i];
+            double minAB = m1arr[i] ? Math.min(a, b) : a;
+            double maxAB = m2arr[i] ? Math.max(a, b) : a;
+            double expected = m1arr[i] ? Math.min(minAB, maxAB) : minAB;
+            Verify.checkEQ(dr[i], expected);
+        }
+    }
+
+    // Predicated Double: min(min(a,b,m2), max(a,b,m1), m1) => NO transform
+    @Test
+    @IR(counts = { IRNode.MIN_VD, IRNode.VECTOR_SIZE_ANY, " 2 ",
+                   IRNode.MAX_VD, IRNode.VECTOR_SIZE_ANY, " 1 " },
+        applyIfCPUFeatureOr = {"avx10_2", "true", "sve", "true"})
+    public void testDoubleMaskedMinIdealDiffMaskMinMaxSwapped(int index) {
+        DoubleVector v1 = DoubleVector.fromArray(D_SPECIES, da, index);
+        DoubleVector v2 = DoubleVector.fromArray(D_SPECIES, db, index);
+        VectorMask mask1 = VectorMask.fromArray(D_SPECIES, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(D_SPECIES, m2arr, index);
+        v1.lanewise(VectorOperators.MIN, v2, mask2)
+          .lanewise(VectorOperators.MIN, v1.lanewise(VectorOperators.MAX, v2, mask1), mask1)
+          .intoArray(dr, index);
+    }
+
+    @Run(test = "testDoubleMaskedMinIdealDiffMaskMinMaxSwapped")
+    public void runDoubleMaskedMinIdealDiffMaskMinMaxSwapped() {
+        for (int i = 0; i < D_SPECIES.loopBound(LENGTH); i += D_SPECIES.length()) {
+            testDoubleMaskedMinIdealDiffMaskMinMaxSwapped(i);
+        }
+        for (int i = 0; i < D_SPECIES.loopBound(LENGTH); i++) {
+            double a = da[i], b = db[i];
+            double minAB = m2arr[i] ? Math.min(a, b) : a;
+            double maxAB = m1arr[i] ? Math.max(a, b) : a;
+            double expected = m1arr[i] ? Math.min(minAB, maxAB) : minAB;
+            Verify.checkEQ(dr[i], expected);
+        }
+    }
+
+    // Predicated Double: min(min(a,b,m1), max(a,b,m1), m2) => NO transform
+    @Test
+    @IR(counts = { IRNode.MIN_VD, IRNode.VECTOR_SIZE_ANY, " 2 ",
+                   IRNode.MAX_VD, IRNode.VECTOR_SIZE_ANY, " 1 " },
+        applyIfCPUFeatureOr = {"avx10_2", "true", "sve", "true"})
+    public void testDoubleMaskedMinIdealDiffMaskOuter(int index) {
+        DoubleVector v1 = DoubleVector.fromArray(D_SPECIES, da, index);
+        DoubleVector v2 = DoubleVector.fromArray(D_SPECIES, db, index);
+        VectorMask mask1 = VectorMask.fromArray(D_SPECIES, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(D_SPECIES, m2arr, index);
+        v1.lanewise(VectorOperators.MIN, v2, mask1)
+          .lanewise(VectorOperators.MIN, v1.lanewise(VectorOperators.MAX, v2, mask1), mask2)
+          .intoArray(dr, index);
+    }
+
+    @Run(test = "testDoubleMaskedMinIdealDiffMaskOuter")
+    public void runDoubleMaskedMinIdealDiffMaskOuter() {
+        for (int i = 0; i < D_SPECIES.loopBound(LENGTH); i += D_SPECIES.length()) {
+            testDoubleMaskedMinIdealDiffMaskOuter(i);
+        }
+        for (int i = 0; i < D_SPECIES.loopBound(LENGTH); i++) {
+            double a = da[i], b = db[i];
+            double minAB = m1arr[i] ? Math.min(a, b) : a;
+            double maxAB = m1arr[i] ? Math.max(a, b) : a;
+            double expected = m2arr[i] ? Math.min(minAB, maxAB) : minAB;
+            Verify.checkEQ(dr[i], expected);
+        }
+    }
+
+    // Predicated Double: min(min(a,b,m1), max(a,b,m2), m3) => NO transform
+    @Test
+    @IR(counts = { IRNode.MIN_VD, IRNode.VECTOR_SIZE_ANY, " 2 ",
+                   IRNode.MAX_VD, IRNode.VECTOR_SIZE_ANY, " 1 " },
+        applyIfCPUFeatureOr = {"avx10_2", "true", "sve", "true"})
+    public void testDoubleMaskedMinIdealAllDiffMask(int index) {
+        DoubleVector v1 = DoubleVector.fromArray(D_SPECIES, da, index);
+        DoubleVector v2 = DoubleVector.fromArray(D_SPECIES, db, index);
+        VectorMask mask1 = VectorMask.fromArray(D_SPECIES, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(D_SPECIES, m2arr, index);
+        VectorMask mask3 = VectorMask.fromArray(D_SPECIES, m3arr, index);
+        v1.lanewise(VectorOperators.MIN, v2, mask1)
+          .lanewise(VectorOperators.MIN, v1.lanewise(VectorOperators.MAX, v2, mask2), mask3)
+          .intoArray(dr, index);
+    }
+
+    @Run(test = "testDoubleMaskedMinIdealAllDiffMask")
+    public void runDoubleMaskedMinIdealAllDiffMask() {
+        for (int i = 0; i < D_SPECIES.loopBound(LENGTH); i += D_SPECIES.length()) {
+            testDoubleMaskedMinIdealAllDiffMask(i);
+        }
+        for (int i = 0; i < D_SPECIES.loopBound(LENGTH); i++) {
+            double a = da[i], b = db[i];
+            double minAB = m1arr[i] ? Math.min(a, b) : a;
+            double maxAB = m2arr[i] ? Math.max(a, b) : a;
+            double expected = m3arr[i] ? Math.min(minAB, maxAB) : minAB;
+            Verify.checkEQ(dr[i], expected);
+        }
+    }
+}
diff --git a/test/hotspot/jtreg/compiler/vectorapi/VectorUnsignedMinMaxOperationsTest.java b/test/hotspot/jtreg/compiler/vectorapi/VectorUnsignedMinMaxOperationsTest.java
index 464e887cbac..b1b98306312 100644
--- a/test/hotspot/jtreg/compiler/vectorapi/VectorUnsignedMinMaxOperationsTest.java
+++ b/test/hotspot/jtreg/compiler/vectorapi/VectorUnsignedMinMaxOperationsTest.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
@@ -34,10 +34,11 @@ package compiler.vectorapi;
 
 import jdk.incubator.vector.*;
 import compiler.lib.ir_framework.*;
+import compiler.lib.verify.*;
 import java.util.stream.IntStream;
 
 public class VectorUnsignedMinMaxOperationsTest {
-    private static final int COUNT = 2048;
+    private static final int COUNT = 1024;
     private static final VectorSpecies lspec    = LongVector.SPECIES_PREFERRED;
     private static final VectorSpecies ispec = IntVector.SPECIES_PREFERRED;
     private static final VectorSpecies sspec   = ShortVector.SPECIES_PREFERRED;
@@ -58,6 +59,8 @@ public class VectorUnsignedMinMaxOperationsTest {
     private short[] short_out;
     private byte[]  byte_out;
 
+    private boolean[] m1arr, m2arr, m3arr;
+
     public static void main(String[] args) {
         TestFramework testFramework = new TestFramework();
         testFramework.setDefaultWarmup(5000)
@@ -102,6 +105,14 @@ public class VectorUnsignedMinMaxOperationsTest {
         int_out   = new int[COUNT];
         short_out = new short[COUNT];
         byte_out  = new byte[COUNT];
+        m1arr = new boolean[COUNT];
+        m2arr = new boolean[COUNT];
+        m3arr = new boolean[COUNT];
+        for (int j = 0; j < COUNT; j++) {
+            m1arr[j] = (j % 2) == 0;
+            m2arr[j] = (j % 2) != 0;
+            m3arr[j] = (j % 3) == 0;
+        }
     }
 
     @Test
@@ -468,5 +479,1341 @@ public class VectorUnsignedMinMaxOperationsTest {
             }
         }
     }
-}
 
+
+    // Predicated: umin(umin(a,b,m), umax(a,b,m), m) => umin(a,b,m)
+    @Test
+    @IR(counts = {IRNode.UMIN_VI, " 1 ", IRNode.UMAX_VI, " 0 "}, applyIfCPUFeatureOr = {"avx512f", "true", "sve", "true"})
+    public void umin_masked_same_mask(int index) {
+        IntVector vec1 = IntVector.fromArray(ispec, int_in1, index);
+        IntVector vec2 = IntVector.fromArray(ispec, int_in2, index);
+        VectorMask m = VectorMask.fromArray(ispec, m1arr, index);
+        vec1.lanewise(VectorOperators.UMIN, vec2, m)
+            .lanewise(VectorOperators.UMIN,
+                      vec1.lanewise(VectorOperators.UMAX, vec2, m), m)
+            .intoArray(int_out, index);
+    }
+
+    @Run(test = "umin_masked_same_mask")
+    public void umin_masked_same_mask_runner() {
+        for (int i = 0; i < ispec.loopBound(COUNT); i += ispec.length()) {
+            umin_masked_same_mask(i);
+        }
+        for (int i = 0; i < ispec.loopBound(COUNT); i++) {
+            int a = int_in1[i], b = int_in2[i];
+            int minAB = m1arr[i] ? VectorMath.minUnsigned(a, b) : a;
+            int maxAB = m1arr[i] ? VectorMath.maxUnsigned(a, b) : a;
+            int expected = m1arr[i] ? VectorMath.minUnsigned(minAB, maxAB) : minAB;
+            Verify.checkEQ(int_out[i], expected);
+        }
+    }
+
+    // Predicated: umin(umin(a,b,m), umax(b,a,m), m) => umin(a,b,m)
+    @Test
+    @IR(counts = {IRNode.UMIN_VI, " 1 ", IRNode.UMAX_VI, " 0 "}, applyIfCPUFeatureOr = {"avx512f", "true", "sve", "true"})
+    public void umin_masked_flipped_inputs(int index) {
+        IntVector vec1 = IntVector.fromArray(ispec, int_in1, index);
+        IntVector vec2 = IntVector.fromArray(ispec, int_in2, index);
+        VectorMask m = VectorMask.fromArray(ispec, m1arr, index);
+        vec1.lanewise(VectorOperators.UMIN, vec2, m)
+            .lanewise(VectorOperators.UMIN,
+                      vec2.lanewise(VectorOperators.UMAX, vec1, m), m)
+            .intoArray(int_out, index);
+    }
+
+    @Run(test = "umin_masked_flipped_inputs")
+    public void umin_masked_flipped_inputs_runner() {
+        for (int i = 0; i < ispec.loopBound(COUNT); i += ispec.length()) {
+            umin_masked_flipped_inputs(i);
+        }
+        for (int i = 0; i < ispec.loopBound(COUNT); i++) {
+            int a = int_in1[i], b = int_in2[i];
+            int minAB = m1arr[i] ? VectorMath.minUnsigned(a, b) : a;
+            int maxBA = m1arr[i] ? VectorMath.maxUnsigned(b, a) : b;
+            int expected = m1arr[i] ? VectorMath.minUnsigned(minAB, maxBA) : minAB;
+            Verify.checkEQ(int_out[i], expected);
+        }
+    }
+
+    // Predicated: umin(umin(a,b,m1), umax(a,b,m2), m1) => NO transform
+    @Test
+    @IR(counts = {IRNode.UMIN_VI, " 2 ", IRNode.UMAX_VI, " 1 "}, applyIfCPUFeatureOr = {"avx512f", "true", "sve", "true"})
+    public void umin_masked_diff_mask_minmax(int index) {
+        IntVector vec1 = IntVector.fromArray(ispec, int_in1, index);
+        IntVector vec2 = IntVector.fromArray(ispec, int_in2, index);
+        VectorMask mask1 = VectorMask.fromArray(ispec, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(ispec, m2arr, index);
+        vec1.lanewise(VectorOperators.UMIN, vec2, mask1)
+            .lanewise(VectorOperators.UMIN,
+                      vec1.lanewise(VectorOperators.UMAX, vec2, mask2), mask1)
+            .intoArray(int_out, index);
+    }
+
+    @Run(test = "umin_masked_diff_mask_minmax")
+    public void umin_masked_diff_mask_minmax_runner() {
+        for (int i = 0; i < ispec.loopBound(COUNT); i += ispec.length()) {
+            umin_masked_diff_mask_minmax(i);
+        }
+        for (int i = 0; i < ispec.loopBound(COUNT); i++) {
+            int a = int_in1[i], b = int_in2[i];
+            int minAB = m1arr[i] ? VectorMath.minUnsigned(a, b) : a;
+            int maxAB = m2arr[i] ? VectorMath.maxUnsigned(a, b) : a;
+            int expected = m1arr[i] ? VectorMath.minUnsigned(minAB, maxAB) : minAB;
+            Verify.checkEQ(int_out[i], expected);
+        }
+    }
+
+    // Predicated: umin(umin(a,b,m2), umax(a,b,m1), m1) => NO transform
+    @Test
+    @IR(counts = {IRNode.UMIN_VI, " 2 ", IRNode.UMAX_VI, " 1 "}, applyIfCPUFeatureOr = {"avx512f", "true", "sve", "true"})
+    public void umin_masked_diff_mask_minmax_swap(int index) {
+        IntVector vec1 = IntVector.fromArray(ispec, int_in1, index);
+        IntVector vec2 = IntVector.fromArray(ispec, int_in2, index);
+        VectorMask mask1 = VectorMask.fromArray(ispec, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(ispec, m2arr, index);
+        vec1.lanewise(VectorOperators.UMIN, vec2, mask2)
+            .lanewise(VectorOperators.UMIN,
+                      vec1.lanewise(VectorOperators.UMAX, vec2, mask1), mask1)
+            .intoArray(int_out, index);
+    }
+
+    @Run(test = "umin_masked_diff_mask_minmax_swap")
+    public void umin_masked_diff_mask_minmax_swap_runner() {
+        for (int i = 0; i < ispec.loopBound(COUNT); i += ispec.length()) {
+            umin_masked_diff_mask_minmax_swap(i);
+        }
+        for (int i = 0; i < ispec.loopBound(COUNT); i++) {
+            int a = int_in1[i], b = int_in2[i];
+            int minAB = m2arr[i] ? VectorMath.minUnsigned(a, b) : a;
+            int maxAB = m1arr[i] ? VectorMath.maxUnsigned(a, b) : a;
+            int expected = m1arr[i] ? VectorMath.minUnsigned(minAB, maxAB) : minAB;
+            Verify.checkEQ(int_out[i], expected);
+        }
+    }
+
+    // Predicated: umin(umin(a,b,m1), umax(a,b,m1), m2) => NO transform
+    @Test
+    @IR(counts = {IRNode.UMIN_VI, " 2 ", IRNode.UMAX_VI, " 1 "}, applyIfCPUFeatureOr = {"avx512f", "true", "sve", "true"})
+    public void umin_masked_diff_mask_outer(int index) {
+        IntVector vec1 = IntVector.fromArray(ispec, int_in1, index);
+        IntVector vec2 = IntVector.fromArray(ispec, int_in2, index);
+        VectorMask mask1 = VectorMask.fromArray(ispec, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(ispec, m2arr, index);
+        vec1.lanewise(VectorOperators.UMIN, vec2, mask1)
+            .lanewise(VectorOperators.UMIN,
+                      vec1.lanewise(VectorOperators.UMAX, vec2, mask1), mask2)
+            .intoArray(int_out, index);
+    }
+
+    @Run(test = "umin_masked_diff_mask_outer")
+    public void umin_masked_diff_mask_outer_runner() {
+        for (int i = 0; i < ispec.loopBound(COUNT); i += ispec.length()) {
+            umin_masked_diff_mask_outer(i);
+        }
+        for (int i = 0; i < ispec.loopBound(COUNT); i++) {
+            int a = int_in1[i], b = int_in2[i];
+            int minAB = m1arr[i] ? VectorMath.minUnsigned(a, b) : a;
+            int maxAB = m1arr[i] ? VectorMath.maxUnsigned(a, b) : a;
+            int expected = m2arr[i] ? VectorMath.minUnsigned(minAB, maxAB) : minAB;
+            Verify.checkEQ(int_out[i], expected);
+        }
+    }
+
+    // Predicated: umin(umin(a,b,m1), umax(a,b,m2), m3) => NO transform
+    @Test
+    @IR(counts = {IRNode.UMIN_VI, " 2 ", IRNode.UMAX_VI, " 1 "}, applyIfCPUFeatureOr = {"avx512f", "true", "sve", "true"})
+    public void umin_masked_all_diff_mask(int index) {
+        IntVector vec1 = IntVector.fromArray(ispec, int_in1, index);
+        IntVector vec2 = IntVector.fromArray(ispec, int_in2, index);
+        VectorMask mask1 = VectorMask.fromArray(ispec, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(ispec, m2arr, index);
+        VectorMask mask3 = VectorMask.fromArray(ispec, m3arr, index);
+        vec1.lanewise(VectorOperators.UMIN, vec2, mask1)
+            .lanewise(VectorOperators.UMIN,
+                      vec1.lanewise(VectorOperators.UMAX, vec2, mask2), mask3)
+            .intoArray(int_out, index);
+    }
+
+    @Run(test = "umin_masked_all_diff_mask")
+    public void umin_masked_all_diff_mask_runner() {
+        for (int i = 0; i < ispec.loopBound(COUNT); i += ispec.length()) {
+            umin_masked_all_diff_mask(i);
+        }
+        for (int i = 0; i < ispec.loopBound(COUNT); i++) {
+            int a = int_in1[i], b = int_in2[i];
+            int minAB = m1arr[i] ? VectorMath.minUnsigned(a, b) : a;
+            int maxAB = m2arr[i] ? VectorMath.maxUnsigned(a, b) : a;
+            int expected = m3arr[i] ? VectorMath.minUnsigned(minAB, maxAB) : minAB;
+            Verify.checkEQ(int_out[i], expected);
+        }
+    }
+
+    // Predicated: umax(umin(a,b,m), umax(a,b,m), m) => umax(a,b,m)
+    @Test
+    @IR(counts = {IRNode.UMIN_VI, " 0 ", IRNode.UMAX_VI, " 1 "}, applyIfCPUFeatureOr = {"avx512f", "true", "sve", "true"})
+    public void umax_masked_same_mask(int index) {
+        IntVector vec1 = IntVector.fromArray(ispec, int_in1, index);
+        IntVector vec2 = IntVector.fromArray(ispec, int_in2, index);
+        VectorMask m = VectorMask.fromArray(ispec, m1arr, index);
+        vec1.lanewise(VectorOperators.UMIN, vec2, m)
+            .lanewise(VectorOperators.UMAX,
+                      vec1.lanewise(VectorOperators.UMAX, vec2, m), m)
+            .intoArray(int_out, index);
+    }
+
+    @Run(test = "umax_masked_same_mask")
+    public void umax_masked_same_mask_runner() {
+        for (int i = 0; i < ispec.loopBound(COUNT); i += ispec.length()) {
+            umax_masked_same_mask(i);
+        }
+        for (int i = 0; i < ispec.loopBound(COUNT); i++) {
+            int a = int_in1[i], b = int_in2[i];
+            int minAB = m1arr[i] ? VectorMath.minUnsigned(a, b) : a;
+            int maxAB = m1arr[i] ? VectorMath.maxUnsigned(a, b) : a;
+            int expected = m1arr[i] ? VectorMath.maxUnsigned(minAB, maxAB) : minAB;
+            Verify.checkEQ(int_out[i], expected);
+        }
+    }
+
+    // Predicated: umax(umin(a,b,m), umax(b,a,m), m) => umax(a,b,m)
+    @Test
+    @IR(counts = {IRNode.UMIN_VI, " 0 ", IRNode.UMAX_VI, " 1 "}, applyIfCPUFeatureOr = {"avx512f", "true", "sve", "true"})
+    public void umax_masked_flipped_inputs(int index) {
+        IntVector vec1 = IntVector.fromArray(ispec, int_in1, index);
+        IntVector vec2 = IntVector.fromArray(ispec, int_in2, index);
+        VectorMask m = VectorMask.fromArray(ispec, m1arr, index);
+        vec1.lanewise(VectorOperators.UMIN, vec2, m)
+            .lanewise(VectorOperators.UMAX,
+                      vec2.lanewise(VectorOperators.UMAX, vec1, m), m)
+            .intoArray(int_out, index);
+    }
+
+    @Run(test = "umax_masked_flipped_inputs")
+    public void umax_masked_flipped_inputs_runner() {
+        for (int i = 0; i < ispec.loopBound(COUNT); i += ispec.length()) {
+            umax_masked_flipped_inputs(i);
+        }
+        for (int i = 0; i < ispec.loopBound(COUNT); i++) {
+            int a = int_in1[i], b = int_in2[i];
+            int minAB = m1arr[i] ? VectorMath.minUnsigned(a, b) : a;
+            int maxBA = m1arr[i] ? VectorMath.maxUnsigned(b, a) : b;
+            int expected = m1arr[i] ? VectorMath.maxUnsigned(minAB, maxBA) : minAB;
+            Verify.checkEQ(int_out[i], expected);
+        }
+    }
+
+    // Predicated: umax(umin(a,b,m1), umax(a,b,m2), m1) => NO transform
+    @Test
+    @IR(counts = {IRNode.UMIN_VI, " 1 ", IRNode.UMAX_VI, " 2 "}, applyIfCPUFeatureOr = {"avx512f", "true", "sve", "true"})
+    public void umax_masked_diff_mask_minmax(int index) {
+        IntVector vec1 = IntVector.fromArray(ispec, int_in1, index);
+        IntVector vec2 = IntVector.fromArray(ispec, int_in2, index);
+        VectorMask mask1 = VectorMask.fromArray(ispec, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(ispec, m2arr, index);
+        vec1.lanewise(VectorOperators.UMIN, vec2, mask1)
+            .lanewise(VectorOperators.UMAX,
+                      vec1.lanewise(VectorOperators.UMAX, vec2, mask2), mask1)
+            .intoArray(int_out, index);
+    }
+
+    @Run(test = "umax_masked_diff_mask_minmax")
+    public void umax_masked_diff_mask_minmax_runner() {
+        for (int i = 0; i < ispec.loopBound(COUNT); i += ispec.length()) {
+            umax_masked_diff_mask_minmax(i);
+        }
+        for (int i = 0; i < ispec.loopBound(COUNT); i++) {
+            int a = int_in1[i], b = int_in2[i];
+            int minAB = m1arr[i] ? VectorMath.minUnsigned(a, b) : a;
+            int maxAB = m2arr[i] ? VectorMath.maxUnsigned(a, b) : a;
+            int expected = m1arr[i] ? VectorMath.maxUnsigned(minAB, maxAB) : minAB;
+            Verify.checkEQ(int_out[i], expected);
+        }
+    }
+
+    // Predicated: umax(umin(a,b,m2), umax(a,b,m1), m1) => NO transform
+    @Test
+    @IR(counts = {IRNode.UMIN_VI, " 1 ", IRNode.UMAX_VI, " 2 "}, applyIfCPUFeatureOr = {"avx512f", "true", "sve", "true"})
+    public void umax_masked_diff_mask_minmax_swap(int index) {
+        IntVector vec1 = IntVector.fromArray(ispec, int_in1, index);
+        IntVector vec2 = IntVector.fromArray(ispec, int_in2, index);
+        VectorMask mask1 = VectorMask.fromArray(ispec, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(ispec, m2arr, index);
+        vec1.lanewise(VectorOperators.UMIN, vec2, mask2)
+            .lanewise(VectorOperators.UMAX,
+                      vec1.lanewise(VectorOperators.UMAX, vec2, mask1), mask1)
+            .intoArray(int_out, index);
+    }
+
+    @Run(test = "umax_masked_diff_mask_minmax_swap")
+    public void umax_masked_diff_mask_minmax_swap_runner() {
+        for (int i = 0; i < ispec.loopBound(COUNT); i += ispec.length()) {
+            umax_masked_diff_mask_minmax_swap(i);
+        }
+        for (int i = 0; i < ispec.loopBound(COUNT); i++) {
+            int a = int_in1[i], b = int_in2[i];
+            int minAB = m2arr[i] ? VectorMath.minUnsigned(a, b) : a;
+            int maxAB = m1arr[i] ? VectorMath.maxUnsigned(a, b) : a;
+            int expected = m1arr[i] ? VectorMath.maxUnsigned(minAB, maxAB) : minAB;
+            Verify.checkEQ(int_out[i], expected);
+        }
+    }
+
+    // Predicated: umax(umin(a,b,m1), umax(a,b,m1), m2) => NO transform
+    @Test
+    @IR(counts = {IRNode.UMIN_VI, " 1 ", IRNode.UMAX_VI, " 2 "}, applyIfCPUFeatureOr = {"avx512f", "true", "sve", "true"})
+    public void umax_masked_diff_mask_outer(int index) {
+        IntVector vec1 = IntVector.fromArray(ispec, int_in1, index);
+        IntVector vec2 = IntVector.fromArray(ispec, int_in2, index);
+        VectorMask mask1 = VectorMask.fromArray(ispec, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(ispec, m2arr, index);
+        vec1.lanewise(VectorOperators.UMIN, vec2, mask1)
+            .lanewise(VectorOperators.UMAX,
+                      vec1.lanewise(VectorOperators.UMAX, vec2, mask1), mask2)
+            .intoArray(int_out, index);
+    }
+
+    @Run(test = "umax_masked_diff_mask_outer")
+    public void umax_masked_diff_mask_outer_runner() {
+        for (int i = 0; i < ispec.loopBound(COUNT); i += ispec.length()) {
+            umax_masked_diff_mask_outer(i);
+        }
+        for (int i = 0; i < ispec.loopBound(COUNT); i++) {
+            int a = int_in1[i], b = int_in2[i];
+            int minAB = m1arr[i] ? VectorMath.minUnsigned(a, b) : a;
+            int maxAB = m1arr[i] ? VectorMath.maxUnsigned(a, b) : a;
+            int expected = m2arr[i] ? VectorMath.maxUnsigned(minAB, maxAB) : minAB;
+            Verify.checkEQ(int_out[i], expected);
+        }
+    }
+
+    // Predicated: umax(umin(a,b,m1), umax(a,b,m2), m3) => NO transform
+    @Test
+    @IR(counts = {IRNode.UMIN_VI, " 1 ", IRNode.UMAX_VI, " 2 "}, applyIfCPUFeatureOr = {"avx512f", "true", "sve", "true"})
+    public void umax_masked_all_diff_mask(int index) {
+        IntVector vec1 = IntVector.fromArray(ispec, int_in1, index);
+        IntVector vec2 = IntVector.fromArray(ispec, int_in2, index);
+        VectorMask mask1 = VectorMask.fromArray(ispec, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(ispec, m2arr, index);
+        VectorMask mask3 = VectorMask.fromArray(ispec, m3arr, index);
+        vec1.lanewise(VectorOperators.UMIN, vec2, mask1)
+            .lanewise(VectorOperators.UMAX,
+                      vec1.lanewise(VectorOperators.UMAX, vec2, mask2), mask3)
+            .intoArray(int_out, index);
+    }
+
+    @Run(test = "umax_masked_all_diff_mask")
+    public void umax_masked_all_diff_mask_runner() {
+        for (int i = 0; i < ispec.loopBound(COUNT); i += ispec.length()) {
+            umax_masked_all_diff_mask(i);
+        }
+        for (int i = 0; i < ispec.loopBound(COUNT); i++) {
+            int a = int_in1[i], b = int_in2[i];
+            int minAB = m1arr[i] ? VectorMath.minUnsigned(a, b) : a;
+            int maxAB = m2arr[i] ? VectorMath.maxUnsigned(a, b) : a;
+            int expected = m3arr[i] ? VectorMath.maxUnsigned(minAB, maxAB) : minAB;
+            Verify.checkEQ(int_out[i], expected);
+        }
+    }
+
+    // Predicated Byte: umin(umin(a,b,m), umax(a,b,m), m) => umin(a,b,m)
+    @Test
+    @IR(counts = {IRNode.UMIN_VB, " 1 ", IRNode.UMAX_VB, " 0 "}, applyIfCPUFeatureOr = {"avx512bw", "true", "sve", "true"})
+    public void umin_byte_masked_same_mask(int index) {
+        ByteVector vec1 = ByteVector.fromArray(bspec, byte_in1, index);
+        ByteVector vec2 = ByteVector.fromArray(bspec, byte_in2, index);
+        VectorMask m = VectorMask.fromArray(bspec, m1arr, index);
+        vec1.lanewise(VectorOperators.UMIN, vec2, m)
+            .lanewise(VectorOperators.UMIN,
+                      vec1.lanewise(VectorOperators.UMAX, vec2, m), m)
+            .intoArray(byte_out, index);
+    }
+
+    @Run(test = "umin_byte_masked_same_mask")
+    public void umin_byte_masked_same_mask_runner() {
+        for (int i = 0; i < bspec.loopBound(COUNT); i += bspec.length()) {
+            umin_byte_masked_same_mask(i);
+        }
+        for (int i = 0; i < bspec.loopBound(COUNT); i++) {
+            byte a = byte_in1[i], b = byte_in2[i];
+            byte minAB = (byte)(m1arr[i] ? VectorMath.minUnsigned(a, b) : a);
+            byte maxAB = (byte)(m1arr[i] ? VectorMath.maxUnsigned(a, b) : a);
+            byte expected = (byte)(m1arr[i] ? VectorMath.minUnsigned(minAB, maxAB) : minAB);
+            Verify.checkEQ(byte_out[i], expected);
+        }
+    }
+
+    // Predicated Byte: umin(umin(a,b,m), umax(b,a,m), m) => umin(a,b,m)
+    @Test
+    @IR(counts = {IRNode.UMIN_VB, " 1 ", IRNode.UMAX_VB, " 0 "}, applyIfCPUFeatureOr = {"avx512bw", "true", "sve", "true"})
+    public void umin_byte_masked_flipped_inputs(int index) {
+        ByteVector vec1 = ByteVector.fromArray(bspec, byte_in1, index);
+        ByteVector vec2 = ByteVector.fromArray(bspec, byte_in2, index);
+        VectorMask m = VectorMask.fromArray(bspec, m1arr, index);
+        vec1.lanewise(VectorOperators.UMIN, vec2, m)
+            .lanewise(VectorOperators.UMIN,
+                      vec2.lanewise(VectorOperators.UMAX, vec1, m), m)
+            .intoArray(byte_out, index);
+    }
+
+    @Run(test = "umin_byte_masked_flipped_inputs")
+    public void umin_byte_masked_flipped_inputs_runner() {
+        for (int i = 0; i < bspec.loopBound(COUNT); i += bspec.length()) {
+            umin_byte_masked_flipped_inputs(i);
+        }
+        for (int i = 0; i < bspec.loopBound(COUNT); i++) {
+            byte a = byte_in1[i], b = byte_in2[i];
+            byte minAB = (byte)(m1arr[i] ? VectorMath.minUnsigned(a, b) : a);
+            byte maxBA = (byte)(m1arr[i] ? VectorMath.maxUnsigned(b, a) : b);
+            byte expected = (byte)(m1arr[i] ? VectorMath.minUnsigned(minAB, maxBA) : minAB);
+            Verify.checkEQ(byte_out[i], expected);
+        }
+    }
+
+    // Predicated Byte: umin(umin(a,b,m1), umax(a,b,m2), m1) => NO transform
+    @Test
+    @IR(counts = {IRNode.UMIN_VB, " 2 ", IRNode.UMAX_VB, " 1 "}, applyIfCPUFeatureOr = {"avx512bw", "true", "sve", "true"})
+    public void umin_byte_masked_diff_mask_minmax(int index) {
+        ByteVector vec1 = ByteVector.fromArray(bspec, byte_in1, index);
+        ByteVector vec2 = ByteVector.fromArray(bspec, byte_in2, index);
+        VectorMask mask1 = VectorMask.fromArray(bspec, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(bspec, m2arr, index);
+        vec1.lanewise(VectorOperators.UMIN, vec2, mask1)
+            .lanewise(VectorOperators.UMIN,
+                      vec1.lanewise(VectorOperators.UMAX, vec2, mask2), mask1)
+            .intoArray(byte_out, index);
+    }
+
+    @Run(test = "umin_byte_masked_diff_mask_minmax")
+    public void umin_byte_masked_diff_mask_minmax_runner() {
+        for (int i = 0; i < bspec.loopBound(COUNT); i += bspec.length()) {
+            umin_byte_masked_diff_mask_minmax(i);
+        }
+        for (int i = 0; i < bspec.loopBound(COUNT); i++) {
+            byte a = byte_in1[i], b = byte_in2[i];
+            byte minAB = (byte)(m1arr[i] ? VectorMath.minUnsigned(a, b) : a);
+            byte maxAB = (byte)(m2arr[i] ? VectorMath.maxUnsigned(a, b) : a);
+            byte expected = (byte)(m1arr[i] ? VectorMath.minUnsigned(minAB, maxAB) : minAB);
+            Verify.checkEQ(byte_out[i], expected);
+        }
+    }
+
+    // Predicated Byte: umin(umin(a,b,m2), umax(a,b,m1), m1) => NO transform
+    @Test
+    @IR(counts = {IRNode.UMIN_VB, " 2 ", IRNode.UMAX_VB, " 1 "}, applyIfCPUFeatureOr = {"avx512bw", "true", "sve", "true"})
+    public void umin_byte_masked_diff_mask_minmax_swap(int index) {
+        ByteVector vec1 = ByteVector.fromArray(bspec, byte_in1, index);
+        ByteVector vec2 = ByteVector.fromArray(bspec, byte_in2, index);
+        VectorMask mask1 = VectorMask.fromArray(bspec, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(bspec, m2arr, index);
+        vec1.lanewise(VectorOperators.UMIN, vec2, mask2)
+            .lanewise(VectorOperators.UMIN,
+                      vec1.lanewise(VectorOperators.UMAX, vec2, mask1), mask1)
+            .intoArray(byte_out, index);
+    }
+
+    @Run(test = "umin_byte_masked_diff_mask_minmax_swap")
+    public void umin_byte_masked_diff_mask_minmax_swap_runner() {
+        for (int i = 0; i < bspec.loopBound(COUNT); i += bspec.length()) {
+            umin_byte_masked_diff_mask_minmax_swap(i);
+        }
+        for (int i = 0; i < bspec.loopBound(COUNT); i++) {
+            byte a = byte_in1[i], b = byte_in2[i];
+            byte minAB = (byte)(m2arr[i] ? VectorMath.minUnsigned(a, b) : a);
+            byte maxAB = (byte)(m1arr[i] ? VectorMath.maxUnsigned(a, b) : a);
+            byte expected = (byte)(m1arr[i] ? VectorMath.minUnsigned(minAB, maxAB) : minAB);
+            Verify.checkEQ(byte_out[i], expected);
+        }
+    }
+
+    // Predicated Byte: umin(umin(a,b,m1), umax(a,b,m1), m2) => NO transform
+    @Test
+    @IR(counts = {IRNode.UMIN_VB, " 2 ", IRNode.UMAX_VB, " 1 "}, applyIfCPUFeatureOr = {"avx512bw", "true", "sve", "true"})
+    public void umin_byte_masked_diff_mask_outer(int index) {
+        ByteVector vec1 = ByteVector.fromArray(bspec, byte_in1, index);
+        ByteVector vec2 = ByteVector.fromArray(bspec, byte_in2, index);
+        VectorMask mask1 = VectorMask.fromArray(bspec, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(bspec, m2arr, index);
+        vec1.lanewise(VectorOperators.UMIN, vec2, mask1)
+            .lanewise(VectorOperators.UMIN,
+                      vec1.lanewise(VectorOperators.UMAX, vec2, mask1), mask2)
+            .intoArray(byte_out, index);
+    }
+
+    @Run(test = "umin_byte_masked_diff_mask_outer")
+    public void umin_byte_masked_diff_mask_outer_runner() {
+        for (int i = 0; i < bspec.loopBound(COUNT); i += bspec.length()) {
+            umin_byte_masked_diff_mask_outer(i);
+        }
+        for (int i = 0; i < bspec.loopBound(COUNT); i++) {
+            byte a = byte_in1[i], b = byte_in2[i];
+            byte minAB = (byte)(m1arr[i] ? VectorMath.minUnsigned(a, b) : a);
+            byte maxAB = (byte)(m1arr[i] ? VectorMath.maxUnsigned(a, b) : a);
+            byte expected = (byte)(m2arr[i] ? VectorMath.minUnsigned(minAB, maxAB) : minAB);
+            Verify.checkEQ(byte_out[i], expected);
+        }
+    }
+
+    // Predicated Byte: umin(umin(a,b,m1), umax(a,b,m2), m3) => NO transform
+    @Test
+    @IR(counts = {IRNode.UMIN_VB, " 2 ", IRNode.UMAX_VB, " 1 "}, applyIfCPUFeatureOr = {"avx512bw", "true", "sve", "true"})
+    public void umin_byte_masked_all_diff_mask(int index) {
+        ByteVector vec1 = ByteVector.fromArray(bspec, byte_in1, index);
+        ByteVector vec2 = ByteVector.fromArray(bspec, byte_in2, index);
+        VectorMask mask1 = VectorMask.fromArray(bspec, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(bspec, m2arr, index);
+        VectorMask mask3 = VectorMask.fromArray(bspec, m3arr, index);
+        vec1.lanewise(VectorOperators.UMIN, vec2, mask1)
+            .lanewise(VectorOperators.UMIN,
+                      vec1.lanewise(VectorOperators.UMAX, vec2, mask2), mask3)
+            .intoArray(byte_out, index);
+    }
+
+    @Run(test = "umin_byte_masked_all_diff_mask")
+    public void umin_byte_masked_all_diff_mask_runner() {
+        for (int i = 0; i < bspec.loopBound(COUNT); i += bspec.length()) {
+            umin_byte_masked_all_diff_mask(i);
+        }
+        for (int i = 0; i < bspec.loopBound(COUNT); i++) {
+            byte a = byte_in1[i], b = byte_in2[i];
+            byte minAB = (byte)(m1arr[i] ? VectorMath.minUnsigned(a, b) : a);
+            byte maxAB = (byte)(m2arr[i] ? VectorMath.maxUnsigned(a, b) : a);
+            byte expected = (byte)(m3arr[i] ? VectorMath.minUnsigned(minAB, maxAB) : minAB);
+            Verify.checkEQ(byte_out[i], expected);
+        }
+    }
+
+    // Predicated Byte: umax(umin(a,b,m), umax(a,b,m), m) => umax(a,b,m)
+    @Test
+    @IR(counts = {IRNode.UMIN_VB, " 0 ", IRNode.UMAX_VB, " 1 "}, applyIfCPUFeatureOr = {"avx512bw", "true", "sve", "true"})
+    public void umax_byte_masked_same_mask(int index) {
+        ByteVector vec1 = ByteVector.fromArray(bspec, byte_in1, index);
+        ByteVector vec2 = ByteVector.fromArray(bspec, byte_in2, index);
+        VectorMask m = VectorMask.fromArray(bspec, m1arr, index);
+        vec1.lanewise(VectorOperators.UMIN, vec2, m)
+            .lanewise(VectorOperators.UMAX,
+                      vec1.lanewise(VectorOperators.UMAX, vec2, m), m)
+            .intoArray(byte_out, index);
+    }
+
+    @Run(test = "umax_byte_masked_same_mask")
+    public void umax_byte_masked_same_mask_runner() {
+        for (int i = 0; i < bspec.loopBound(COUNT); i += bspec.length()) {
+            umax_byte_masked_same_mask(i);
+        }
+        for (int i = 0; i < bspec.loopBound(COUNT); i++) {
+            byte a = byte_in1[i], b = byte_in2[i];
+            byte minAB = (byte)(m1arr[i] ? VectorMath.minUnsigned(a, b) : a);
+            byte maxAB = (byte)(m1arr[i] ? VectorMath.maxUnsigned(a, b) : a);
+            byte expected = (byte)(m1arr[i] ? VectorMath.maxUnsigned(minAB, maxAB) : minAB);
+            Verify.checkEQ(byte_out[i], expected);
+        }
+    }
+
+    // Predicated Byte: umax(umin(a,b,m), umax(b,a,m), m) => umax(a,b,m)
+    @Test
+    @IR(counts = {IRNode.UMIN_VB, " 0 ", IRNode.UMAX_VB, " 1 "}, applyIfCPUFeatureOr = {"avx512bw", "true", "sve", "true"})
+    public void umax_byte_masked_flipped_inputs(int index) {
+        ByteVector vec1 = ByteVector.fromArray(bspec, byte_in1, index);
+        ByteVector vec2 = ByteVector.fromArray(bspec, byte_in2, index);
+        VectorMask m = VectorMask.fromArray(bspec, m1arr, index);
+        vec1.lanewise(VectorOperators.UMIN, vec2, m)
+            .lanewise(VectorOperators.UMAX,
+                      vec2.lanewise(VectorOperators.UMAX, vec1, m), m)
+            .intoArray(byte_out, index);
+    }
+
+    @Run(test = "umax_byte_masked_flipped_inputs")
+    public void umax_byte_masked_flipped_inputs_runner() {
+        for (int i = 0; i < bspec.loopBound(COUNT); i += bspec.length()) {
+            umax_byte_masked_flipped_inputs(i);
+        }
+        for (int i = 0; i < bspec.loopBound(COUNT); i++) {
+            byte a = byte_in1[i], b = byte_in2[i];
+            byte minAB = (byte)(m1arr[i] ? VectorMath.minUnsigned(a, b) : a);
+            byte maxBA = (byte)(m1arr[i] ? VectorMath.maxUnsigned(b, a) : b);
+            byte expected = (byte)(m1arr[i] ? VectorMath.maxUnsigned(minAB, maxBA) : minAB);
+            Verify.checkEQ(byte_out[i], expected);
+        }
+    }
+
+    // Predicated Byte: umax(umin(a,b,m1), umax(a,b,m2), m1) => NO transform
+    @Test
+    @IR(counts = {IRNode.UMIN_VB, " 1 ", IRNode.UMAX_VB, " 2 "}, applyIfCPUFeatureOr = {"avx512bw", "true", "sve", "true"})
+    public void umax_byte_masked_diff_mask_minmax(int index) {
+        ByteVector vec1 = ByteVector.fromArray(bspec, byte_in1, index);
+        ByteVector vec2 = ByteVector.fromArray(bspec, byte_in2, index);
+        VectorMask mask1 = VectorMask.fromArray(bspec, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(bspec, m2arr, index);
+        vec1.lanewise(VectorOperators.UMIN, vec2, mask1)
+            .lanewise(VectorOperators.UMAX,
+                      vec1.lanewise(VectorOperators.UMAX, vec2, mask2), mask1)
+            .intoArray(byte_out, index);
+    }
+
+    @Run(test = "umax_byte_masked_diff_mask_minmax")
+    public void umax_byte_masked_diff_mask_minmax_runner() {
+        for (int i = 0; i < bspec.loopBound(COUNT); i += bspec.length()) {
+            umax_byte_masked_diff_mask_minmax(i);
+        }
+        for (int i = 0; i < bspec.loopBound(COUNT); i++) {
+            byte a = byte_in1[i], b = byte_in2[i];
+            byte minAB = (byte)(m1arr[i] ? VectorMath.minUnsigned(a, b) : a);
+            byte maxAB = (byte)(m2arr[i] ? VectorMath.maxUnsigned(a, b) : a);
+            byte expected = (byte)(m1arr[i] ? VectorMath.maxUnsigned(minAB, maxAB) : minAB);
+            Verify.checkEQ(byte_out[i], expected);
+        }
+    }
+
+    // Predicated Byte: umax(umin(a,b,m2), umax(a,b,m1), m1) => NO transform
+    @Test
+    @IR(counts = {IRNode.UMIN_VB, " 1 ", IRNode.UMAX_VB, " 2 "}, applyIfCPUFeatureOr = {"avx512bw", "true", "sve", "true"})
+    public void umax_byte_masked_diff_mask_minmax_swap(int index) {
+        ByteVector vec1 = ByteVector.fromArray(bspec, byte_in1, index);
+        ByteVector vec2 = ByteVector.fromArray(bspec, byte_in2, index);
+        VectorMask mask1 = VectorMask.fromArray(bspec, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(bspec, m2arr, index);
+        vec1.lanewise(VectorOperators.UMIN, vec2, mask2)
+            .lanewise(VectorOperators.UMAX,
+                      vec1.lanewise(VectorOperators.UMAX, vec2, mask1), mask1)
+            .intoArray(byte_out, index);
+    }
+
+    @Run(test = "umax_byte_masked_diff_mask_minmax_swap")
+    public void umax_byte_masked_diff_mask_minmax_swap_runner() {
+        for (int i = 0; i < bspec.loopBound(COUNT); i += bspec.length()) {
+            umax_byte_masked_diff_mask_minmax_swap(i);
+        }
+        for (int i = 0; i < bspec.loopBound(COUNT); i++) {
+            byte a = byte_in1[i], b = byte_in2[i];
+            byte minAB = (byte)(m2arr[i] ? VectorMath.minUnsigned(a, b) : a);
+            byte maxAB = (byte)(m1arr[i] ? VectorMath.maxUnsigned(a, b) : a);
+            byte expected = (byte)(m1arr[i] ? VectorMath.maxUnsigned(minAB, maxAB) : minAB);
+            Verify.checkEQ(byte_out[i], expected);
+        }
+    }
+
+    // Predicated Byte: umax(umin(a,b,m1), umax(a,b,m1), m2) => NO transform
+    @Test
+    @IR(counts = {IRNode.UMIN_VB, " 1 ", IRNode.UMAX_VB, " 2 "}, applyIfCPUFeatureOr = {"avx512bw", "true", "sve", "true"})
+    public void umax_byte_masked_diff_mask_outer(int index) {
+        ByteVector vec1 = ByteVector.fromArray(bspec, byte_in1, index);
+        ByteVector vec2 = ByteVector.fromArray(bspec, byte_in2, index);
+        VectorMask mask1 = VectorMask.fromArray(bspec, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(bspec, m2arr, index);
+        vec1.lanewise(VectorOperators.UMIN, vec2, mask1)
+            .lanewise(VectorOperators.UMAX,
+                      vec1.lanewise(VectorOperators.UMAX, vec2, mask1), mask2)
+            .intoArray(byte_out, index);
+    }
+
+    @Run(test = "umax_byte_masked_diff_mask_outer")
+    public void umax_byte_masked_diff_mask_outer_runner() {
+        for (int i = 0; i < bspec.loopBound(COUNT); i += bspec.length()) {
+            umax_byte_masked_diff_mask_outer(i);
+        }
+        for (int i = 0; i < bspec.loopBound(COUNT); i++) {
+            byte a = byte_in1[i], b = byte_in2[i];
+            byte minAB = (byte)(m1arr[i] ? VectorMath.minUnsigned(a, b) : a);
+            byte maxAB = (byte)(m1arr[i] ? VectorMath.maxUnsigned(a, b) : a);
+            byte expected = (byte)(m2arr[i] ? VectorMath.maxUnsigned(minAB, maxAB) : minAB);
+            Verify.checkEQ(byte_out[i], expected);
+        }
+    }
+
+    // Predicated Byte: umax(umin(a,b,m1), umax(a,b,m2), m3) => NO transform
+    @Test
+    @IR(counts = {IRNode.UMIN_VB, " 1 ", IRNode.UMAX_VB, " 2 "}, applyIfCPUFeatureOr = {"avx512bw", "true", "sve", "true"})
+    public void umax_byte_masked_all_diff_mask(int index) {
+        ByteVector vec1 = ByteVector.fromArray(bspec, byte_in1, index);
+        ByteVector vec2 = ByteVector.fromArray(bspec, byte_in2, index);
+        VectorMask mask1 = VectorMask.fromArray(bspec, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(bspec, m2arr, index);
+        VectorMask mask3 = VectorMask.fromArray(bspec, m3arr, index);
+        vec1.lanewise(VectorOperators.UMIN, vec2, mask1)
+            .lanewise(VectorOperators.UMAX,
+                      vec1.lanewise(VectorOperators.UMAX, vec2, mask2), mask3)
+            .intoArray(byte_out, index);
+    }
+
+    @Run(test = "umax_byte_masked_all_diff_mask")
+    public void umax_byte_masked_all_diff_mask_runner() {
+        for (int i = 0; i < bspec.loopBound(COUNT); i += bspec.length()) {
+            umax_byte_masked_all_diff_mask(i);
+        }
+        for (int i = 0; i < bspec.loopBound(COUNT); i++) {
+            byte a = byte_in1[i], b = byte_in2[i];
+            byte minAB = (byte)(m1arr[i] ? VectorMath.minUnsigned(a, b) : a);
+            byte maxAB = (byte)(m2arr[i] ? VectorMath.maxUnsigned(a, b) : a);
+            byte expected = (byte)(m3arr[i] ? VectorMath.maxUnsigned(minAB, maxAB) : minAB);
+            Verify.checkEQ(byte_out[i], expected);
+        }
+    }
+
+    // Predicated Short: umin(umin(a,b,m), umax(a,b,m), m) => umin(a,b,m)
+    @Test
+    @IR(counts = {IRNode.UMIN_VS, " 1 ", IRNode.UMAX_VS, " 0 "}, applyIfCPUFeatureOr = {"avx512bw", "true", "sve", "true"})
+    public void umin_short_masked_same_mask(int index) {
+        ShortVector vec1 = ShortVector.fromArray(sspec, short_in1, index);
+        ShortVector vec2 = ShortVector.fromArray(sspec, short_in2, index);
+        VectorMask m = VectorMask.fromArray(sspec, m1arr, index);
+        vec1.lanewise(VectorOperators.UMIN, vec2, m)
+            .lanewise(VectorOperators.UMIN,
+                      vec1.lanewise(VectorOperators.UMAX, vec2, m), m)
+            .intoArray(short_out, index);
+    }
+
+    @Run(test = "umin_short_masked_same_mask")
+    public void umin_short_masked_same_mask_runner() {
+        for (int i = 0; i < sspec.loopBound(COUNT); i += sspec.length()) {
+            umin_short_masked_same_mask(i);
+        }
+        for (int i = 0; i < sspec.loopBound(COUNT); i++) {
+            short a = short_in1[i], b = short_in2[i];
+            short minAB = (short)(m1arr[i] ? VectorMath.minUnsigned(a, b) : a);
+            short maxAB = (short)(m1arr[i] ? VectorMath.maxUnsigned(a, b) : a);
+            short expected = (short)(m1arr[i] ? VectorMath.minUnsigned(minAB, maxAB) : minAB);
+            Verify.checkEQ(short_out[i], expected);
+        }
+    }
+
+    // Predicated Short: umin(umin(a,b,m), umax(b,a,m), m) => umin(a,b,m)
+    @Test
+    @IR(counts = {IRNode.UMIN_VS, " 1 ", IRNode.UMAX_VS, " 0 "}, applyIfCPUFeatureOr = {"avx512bw", "true", "sve", "true"})
+    public void umin_short_masked_flipped_inputs(int index) {
+        ShortVector vec1 = ShortVector.fromArray(sspec, short_in1, index);
+        ShortVector vec2 = ShortVector.fromArray(sspec, short_in2, index);
+        VectorMask m = VectorMask.fromArray(sspec, m1arr, index);
+        vec1.lanewise(VectorOperators.UMIN, vec2, m)
+            .lanewise(VectorOperators.UMIN,
+                      vec2.lanewise(VectorOperators.UMAX, vec1, m), m)
+            .intoArray(short_out, index);
+    }
+
+    @Run(test = "umin_short_masked_flipped_inputs")
+    public void umin_short_masked_flipped_inputs_runner() {
+        for (int i = 0; i < sspec.loopBound(COUNT); i += sspec.length()) {
+            umin_short_masked_flipped_inputs(i);
+        }
+        for (int i = 0; i < sspec.loopBound(COUNT); i++) {
+            short a = short_in1[i], b = short_in2[i];
+            short minAB = (short)(m1arr[i] ? VectorMath.minUnsigned(a, b) : a);
+            short maxBA = (short)(m1arr[i] ? VectorMath.maxUnsigned(b, a) : b);
+            short expected = (short)(m1arr[i] ? VectorMath.minUnsigned(minAB, maxBA) : minAB);
+            Verify.checkEQ(short_out[i], expected);
+        }
+    }
+
+    // Predicated Short: umin(umin(a,b,m1), umax(a,b,m2), m1) => NO transform
+    @Test
+    @IR(counts = {IRNode.UMIN_VS, " 2 ", IRNode.UMAX_VS, " 1 "}, applyIfCPUFeatureOr = {"avx512bw", "true", "sve", "true"})
+    public void umin_short_masked_diff_mask_minmax(int index) {
+        ShortVector vec1 = ShortVector.fromArray(sspec, short_in1, index);
+        ShortVector vec2 = ShortVector.fromArray(sspec, short_in2, index);
+        VectorMask mask1 = VectorMask.fromArray(sspec, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(sspec, m2arr, index);
+        vec1.lanewise(VectorOperators.UMIN, vec2, mask1)
+            .lanewise(VectorOperators.UMIN,
+                      vec1.lanewise(VectorOperators.UMAX, vec2, mask2), mask1)
+            .intoArray(short_out, index);
+    }
+
+    @Run(test = "umin_short_masked_diff_mask_minmax")
+    public void umin_short_masked_diff_mask_minmax_runner() {
+        for (int i = 0; i < sspec.loopBound(COUNT); i += sspec.length()) {
+            umin_short_masked_diff_mask_minmax(i);
+        }
+        for (int i = 0; i < sspec.loopBound(COUNT); i++) {
+            short a = short_in1[i], b = short_in2[i];
+            short minAB = (short)(m1arr[i] ? VectorMath.minUnsigned(a, b) : a);
+            short maxAB = (short)(m2arr[i] ? VectorMath.maxUnsigned(a, b) : a);
+            short expected = (short)(m1arr[i] ? VectorMath.minUnsigned(minAB, maxAB) : minAB);
+            Verify.checkEQ(short_out[i], expected);
+        }
+    }
+
+    // Predicated Short: umin(umin(a,b,m2), umax(a,b,m1), m1) => NO transform
+    @Test
+    @IR(counts = {IRNode.UMIN_VS, " 2 ", IRNode.UMAX_VS, " 1 "}, applyIfCPUFeatureOr = {"avx512bw", "true", "sve", "true"})
+    public void umin_short_masked_diff_mask_minmax_swap(int index) {
+        ShortVector vec1 = ShortVector.fromArray(sspec, short_in1, index);
+        ShortVector vec2 = ShortVector.fromArray(sspec, short_in2, index);
+        VectorMask mask1 = VectorMask.fromArray(sspec, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(sspec, m2arr, index);
+        vec1.lanewise(VectorOperators.UMIN, vec2, mask2)
+            .lanewise(VectorOperators.UMIN,
+                      vec1.lanewise(VectorOperators.UMAX, vec2, mask1), mask1)
+            .intoArray(short_out, index);
+    }
+
+    @Run(test = "umin_short_masked_diff_mask_minmax_swap")
+    public void umin_short_masked_diff_mask_minmax_swap_runner() {
+        for (int i = 0; i < sspec.loopBound(COUNT); i += sspec.length()) {
+            umin_short_masked_diff_mask_minmax_swap(i);
+        }
+        for (int i = 0; i < sspec.loopBound(COUNT); i++) {
+            short a = short_in1[i], b = short_in2[i];
+            short minAB = (short)(m2arr[i] ? VectorMath.minUnsigned(a, b) : a);
+            short maxAB = (short)(m1arr[i] ? VectorMath.maxUnsigned(a, b) : a);
+            short expected = (short)(m1arr[i] ? VectorMath.minUnsigned(minAB, maxAB) : minAB);
+            Verify.checkEQ(short_out[i], expected);
+        }
+    }
+
+    // Predicated Short: umin(umin(a,b,m1), umax(a,b,m1), m2) => NO transform
+    @Test
+    @IR(counts = {IRNode.UMIN_VS, " 2 ", IRNode.UMAX_VS, " 1 "}, applyIfCPUFeatureOr = {"avx512bw", "true", "sve", "true"})
+    public void umin_short_masked_diff_mask_outer(int index) {
+        ShortVector vec1 = ShortVector.fromArray(sspec, short_in1, index);
+        ShortVector vec2 = ShortVector.fromArray(sspec, short_in2, index);
+        VectorMask mask1 = VectorMask.fromArray(sspec, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(sspec, m2arr, index);
+        vec1.lanewise(VectorOperators.UMIN, vec2, mask1)
+            .lanewise(VectorOperators.UMIN,
+                      vec1.lanewise(VectorOperators.UMAX, vec2, mask1), mask2)
+            .intoArray(short_out, index);
+    }
+
+    @Run(test = "umin_short_masked_diff_mask_outer")
+    public void umin_short_masked_diff_mask_outer_runner() {
+        for (int i = 0; i < sspec.loopBound(COUNT); i += sspec.length()) {
+            umin_short_masked_diff_mask_outer(i);
+        }
+        for (int i = 0; i < sspec.loopBound(COUNT); i++) {
+            short a = short_in1[i], b = short_in2[i];
+            short minAB = (short)(m1arr[i] ? VectorMath.minUnsigned(a, b) : a);
+            short maxAB = (short)(m1arr[i] ? VectorMath.maxUnsigned(a, b) : a);
+            short expected = (short)(m2arr[i] ? VectorMath.minUnsigned(minAB, maxAB) : minAB);
+            Verify.checkEQ(short_out[i], expected);
+        }
+    }
+
+    // Predicated Short: umin(umin(a,b,m1), umax(a,b,m2), m3) => NO transform
+    @Test
+    @IR(counts = {IRNode.UMIN_VS, " 2 ", IRNode.UMAX_VS, " 1 "}, applyIfCPUFeatureOr = {"avx512bw", "true", "sve", "true"})
+    public void umin_short_masked_all_diff_mask(int index) {
+        ShortVector vec1 = ShortVector.fromArray(sspec, short_in1, index);
+        ShortVector vec2 = ShortVector.fromArray(sspec, short_in2, index);
+        VectorMask mask1 = VectorMask.fromArray(sspec, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(sspec, m2arr, index);
+        VectorMask mask3 = VectorMask.fromArray(sspec, m3arr, index);
+        vec1.lanewise(VectorOperators.UMIN, vec2, mask1)
+            .lanewise(VectorOperators.UMIN,
+                      vec1.lanewise(VectorOperators.UMAX, vec2, mask2), mask3)
+            .intoArray(short_out, index);
+    }
+
+    @Run(test = "umin_short_masked_all_diff_mask")
+    public void umin_short_masked_all_diff_mask_runner() {
+        for (int i = 0; i < sspec.loopBound(COUNT); i += sspec.length()) {
+            umin_short_masked_all_diff_mask(i);
+        }
+        for (int i = 0; i < sspec.loopBound(COUNT); i++) {
+            short a = short_in1[i], b = short_in2[i];
+            short minAB = (short)(m1arr[i] ? VectorMath.minUnsigned(a, b) : a);
+            short maxAB = (short)(m2arr[i] ? VectorMath.maxUnsigned(a, b) : a);
+            short expected = (short)(m3arr[i] ? VectorMath.minUnsigned(minAB, maxAB) : minAB);
+            Verify.checkEQ(short_out[i], expected);
+        }
+    }
+
+    // Predicated Short: umax(umin(a,b,m), umax(a,b,m), m) => umax(a,b,m)
+    @Test
+    @IR(counts = {IRNode.UMIN_VS, " 0 ", IRNode.UMAX_VS, " 1 "}, applyIfCPUFeatureOr = {"avx512bw", "true", "sve", "true"})
+    public void umax_short_masked_same_mask(int index) {
+        ShortVector vec1 = ShortVector.fromArray(sspec, short_in1, index);
+        ShortVector vec2 = ShortVector.fromArray(sspec, short_in2, index);
+        VectorMask m = VectorMask.fromArray(sspec, m1arr, index);
+        vec1.lanewise(VectorOperators.UMIN, vec2, m)
+            .lanewise(VectorOperators.UMAX,
+                      vec1.lanewise(VectorOperators.UMAX, vec2, m), m)
+            .intoArray(short_out, index);
+    }
+
+    @Run(test = "umax_short_masked_same_mask")
+    public void umax_short_masked_same_mask_runner() {
+        for (int i = 0; i < sspec.loopBound(COUNT); i += sspec.length()) {
+            umax_short_masked_same_mask(i);
+        }
+        for (int i = 0; i < sspec.loopBound(COUNT); i++) {
+            short a = short_in1[i], b = short_in2[i];
+            short minAB = (short)(m1arr[i] ? VectorMath.minUnsigned(a, b) : a);
+            short maxAB = (short)(m1arr[i] ? VectorMath.maxUnsigned(a, b) : a);
+            short expected = (short)(m1arr[i] ? VectorMath.maxUnsigned(minAB, maxAB) : minAB);
+            Verify.checkEQ(short_out[i], expected);
+        }
+    }
+
+    // Predicated Short: umax(umin(a,b,m), umax(b,a,m), m) => umax(a,b,m)
+    @Test
+    @IR(counts = {IRNode.UMIN_VS, " 0 ", IRNode.UMAX_VS, " 1 "}, applyIfCPUFeatureOr = {"avx512bw", "true", "sve", "true"})
+    public void umax_short_masked_flipped_inputs(int index) {
+        ShortVector vec1 = ShortVector.fromArray(sspec, short_in1, index);
+        ShortVector vec2 = ShortVector.fromArray(sspec, short_in2, index);
+        VectorMask m = VectorMask.fromArray(sspec, m1arr, index);
+        vec1.lanewise(VectorOperators.UMIN, vec2, m)
+            .lanewise(VectorOperators.UMAX,
+                      vec2.lanewise(VectorOperators.UMAX, vec1, m), m)
+            .intoArray(short_out, index);
+    }
+
+    @Run(test = "umax_short_masked_flipped_inputs")
+    public void umax_short_masked_flipped_inputs_runner() {
+        for (int i = 0; i < sspec.loopBound(COUNT); i += sspec.length()) {
+            umax_short_masked_flipped_inputs(i);
+        }
+        for (int i = 0; i < sspec.loopBound(COUNT); i++) {
+            short a = short_in1[i], b = short_in2[i];
+            short minAB = (short)(m1arr[i] ? VectorMath.minUnsigned(a, b) : a);
+            short maxBA = (short)(m1arr[i] ? VectorMath.maxUnsigned(b, a) : b);
+            short expected = (short)(m1arr[i] ? VectorMath.maxUnsigned(minAB, maxBA) : minAB);
+            Verify.checkEQ(short_out[i], expected);
+        }
+    }
+
+    // Predicated Short: umax(umin(a,b,m1), umax(a,b,m2), m1) => NO transform
+    @Test
+    @IR(counts = {IRNode.UMIN_VS, " 1 ", IRNode.UMAX_VS, " 2 "}, applyIfCPUFeatureOr = {"avx512bw", "true", "sve", "true"})
+    public void umax_short_masked_diff_mask_minmax(int index) {
+        ShortVector vec1 = ShortVector.fromArray(sspec, short_in1, index);
+        ShortVector vec2 = ShortVector.fromArray(sspec, short_in2, index);
+        VectorMask mask1 = VectorMask.fromArray(sspec, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(sspec, m2arr, index);
+        vec1.lanewise(VectorOperators.UMIN, vec2, mask1)
+            .lanewise(VectorOperators.UMAX,
+                      vec1.lanewise(VectorOperators.UMAX, vec2, mask2), mask1)
+            .intoArray(short_out, index);
+    }
+
+    @Run(test = "umax_short_masked_diff_mask_minmax")
+    public void umax_short_masked_diff_mask_minmax_runner() {
+        for (int i = 0; i < sspec.loopBound(COUNT); i += sspec.length()) {
+            umax_short_masked_diff_mask_minmax(i);
+        }
+        for (int i = 0; i < sspec.loopBound(COUNT); i++) {
+            short a = short_in1[i], b = short_in2[i];
+            short minAB = (short)(m1arr[i] ? VectorMath.minUnsigned(a, b) : a);
+            short maxAB = (short)(m2arr[i] ? VectorMath.maxUnsigned(a, b) : a);
+            short expected = (short)(m1arr[i] ? VectorMath.maxUnsigned(minAB, maxAB) : minAB);
+            Verify.checkEQ(short_out[i], expected);
+        }
+    }
+
+    // Predicated Short: umax(umin(a,b,m2), umax(a,b,m1), m1) => NO transform
+    @Test
+    @IR(counts = {IRNode.UMIN_VS, " 1 ", IRNode.UMAX_VS, " 2 "}, applyIfCPUFeatureOr = {"avx512bw", "true", "sve", "true"})
+    public void umax_short_masked_diff_mask_minmax_swap(int index) {
+        ShortVector vec1 = ShortVector.fromArray(sspec, short_in1, index);
+        ShortVector vec2 = ShortVector.fromArray(sspec, short_in2, index);
+        VectorMask mask1 = VectorMask.fromArray(sspec, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(sspec, m2arr, index);
+        vec1.lanewise(VectorOperators.UMIN, vec2, mask2)
+            .lanewise(VectorOperators.UMAX,
+                      vec1.lanewise(VectorOperators.UMAX, vec2, mask1), mask1)
+            .intoArray(short_out, index);
+    }
+
+    @Run(test = "umax_short_masked_diff_mask_minmax_swap")
+    public void umax_short_masked_diff_mask_minmax_swap_runner() {
+        for (int i = 0; i < sspec.loopBound(COUNT); i += sspec.length()) {
+            umax_short_masked_diff_mask_minmax_swap(i);
+        }
+        for (int i = 0; i < sspec.loopBound(COUNT); i++) {
+            short a = short_in1[i], b = short_in2[i];
+            short minAB = (short)(m2arr[i] ? VectorMath.minUnsigned(a, b) : a);
+            short maxAB = (short)(m1arr[i] ? VectorMath.maxUnsigned(a, b) : a);
+            short expected = (short)(m1arr[i] ? VectorMath.maxUnsigned(minAB, maxAB) : minAB);
+            Verify.checkEQ(short_out[i], expected);
+        }
+    }
+
+    // Predicated Short: umax(umin(a,b,m1), umax(a,b,m1), m2) => NO transform
+    @Test
+    @IR(counts = {IRNode.UMIN_VS, " 1 ", IRNode.UMAX_VS, " 2 "}, applyIfCPUFeatureOr = {"avx512bw", "true", "sve", "true"})
+    public void umax_short_masked_diff_mask_outer(int index) {
+        ShortVector vec1 = ShortVector.fromArray(sspec, short_in1, index);
+        ShortVector vec2 = ShortVector.fromArray(sspec, short_in2, index);
+        VectorMask mask1 = VectorMask.fromArray(sspec, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(sspec, m2arr, index);
+        vec1.lanewise(VectorOperators.UMIN, vec2, mask1)
+            .lanewise(VectorOperators.UMAX,
+                      vec1.lanewise(VectorOperators.UMAX, vec2, mask1), mask2)
+            .intoArray(short_out, index);
+    }
+
+    @Run(test = "umax_short_masked_diff_mask_outer")
+    public void umax_short_masked_diff_mask_outer_runner() {
+        for (int i = 0; i < sspec.loopBound(COUNT); i += sspec.length()) {
+            umax_short_masked_diff_mask_outer(i);
+        }
+        for (int i = 0; i < sspec.loopBound(COUNT); i++) {
+            short a = short_in1[i], b = short_in2[i];
+            short minAB = (short)(m1arr[i] ? VectorMath.minUnsigned(a, b) : a);
+            short maxAB = (short)(m1arr[i] ? VectorMath.maxUnsigned(a, b) : a);
+            short expected = (short)(m2arr[i] ? VectorMath.maxUnsigned(minAB, maxAB) : minAB);
+            Verify.checkEQ(short_out[i], expected);
+        }
+    }
+
+    // Predicated Short: umax(umin(a,b,m1), umax(a,b,m2), m3) => NO transform
+    @Test
+    @IR(counts = {IRNode.UMIN_VS, " 1 ", IRNode.UMAX_VS, " 2 "}, applyIfCPUFeatureOr = {"avx512bw", "true", "sve", "true"})
+    public void umax_short_masked_all_diff_mask(int index) {
+        ShortVector vec1 = ShortVector.fromArray(sspec, short_in1, index);
+        ShortVector vec2 = ShortVector.fromArray(sspec, short_in2, index);
+        VectorMask mask1 = VectorMask.fromArray(sspec, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(sspec, m2arr, index);
+        VectorMask mask3 = VectorMask.fromArray(sspec, m3arr, index);
+        vec1.lanewise(VectorOperators.UMIN, vec2, mask1)
+            .lanewise(VectorOperators.UMAX,
+                      vec1.lanewise(VectorOperators.UMAX, vec2, mask2), mask3)
+            .intoArray(short_out, index);
+    }
+
+    @Run(test = "umax_short_masked_all_diff_mask")
+    public void umax_short_masked_all_diff_mask_runner() {
+        for (int i = 0; i < sspec.loopBound(COUNT); i += sspec.length()) {
+            umax_short_masked_all_diff_mask(i);
+        }
+        for (int i = 0; i < sspec.loopBound(COUNT); i++) {
+            short a = short_in1[i], b = short_in2[i];
+            short minAB = (short)(m1arr[i] ? VectorMath.minUnsigned(a, b) : a);
+            short maxAB = (short)(m2arr[i] ? VectorMath.maxUnsigned(a, b) : a);
+            short expected = (short)(m3arr[i] ? VectorMath.maxUnsigned(minAB, maxAB) : minAB);
+            Verify.checkEQ(short_out[i], expected);
+        }
+    }
+
+    // Predicated Long: umin(umin(a,b,m), umax(a,b,m), m) => umin(a,b,m)
+    @Test
+    @IR(counts = {IRNode.UMIN_VL, " 1 ", IRNode.UMAX_VL, " 0 "}, applyIfCPUFeatureOr = {"avx512f", "true", "sve", "true"})
+    public void umin_long_masked_same_mask(int index) {
+        LongVector vec1 = LongVector.fromArray(lspec, long_in1, index);
+        LongVector vec2 = LongVector.fromArray(lspec, long_in2, index);
+        VectorMask m = VectorMask.fromArray(lspec, m1arr, index);
+        vec1.lanewise(VectorOperators.UMIN, vec2, m)
+            .lanewise(VectorOperators.UMIN,
+                      vec1.lanewise(VectorOperators.UMAX, vec2, m), m)
+            .intoArray(long_out, index);
+    }
+
+    @Run(test = "umin_long_masked_same_mask")
+    public void umin_long_masked_same_mask_runner() {
+        for (int i = 0; i < lspec.loopBound(COUNT); i += lspec.length()) {
+            umin_long_masked_same_mask(i);
+        }
+        for (int i = 0; i < lspec.loopBound(COUNT); i++) {
+            long a = long_in1[i], b = long_in2[i];
+            long minAB = m1arr[i] ? VectorMath.minUnsigned(a, b) : a;
+            long maxAB = m1arr[i] ? VectorMath.maxUnsigned(a, b) : a;
+            long expected = m1arr[i] ? VectorMath.minUnsigned(minAB, maxAB) : minAB;
+            Verify.checkEQ(long_out[i], expected);
+        }
+    }
+
+    // Predicated Long: umin(umin(a,b,m), umax(b,a,m), m) => umin(a,b,m)
+    @Test
+    @IR(counts = {IRNode.UMIN_VL, " 1 ", IRNode.UMAX_VL, " 0 "}, applyIfCPUFeatureOr = {"avx512f", "true", "sve", "true"})
+    public void umin_long_masked_flipped_inputs(int index) {
+        LongVector vec1 = LongVector.fromArray(lspec, long_in1, index);
+        LongVector vec2 = LongVector.fromArray(lspec, long_in2, index);
+        VectorMask m = VectorMask.fromArray(lspec, m1arr, index);
+        vec1.lanewise(VectorOperators.UMIN, vec2, m)
+            .lanewise(VectorOperators.UMIN,
+                      vec2.lanewise(VectorOperators.UMAX, vec1, m), m)
+            .intoArray(long_out, index);
+    }
+
+    @Run(test = "umin_long_masked_flipped_inputs")
+    public void umin_long_masked_flipped_inputs_runner() {
+        for (int i = 0; i < lspec.loopBound(COUNT); i += lspec.length()) {
+            umin_long_masked_flipped_inputs(i);
+        }
+        for (int i = 0; i < lspec.loopBound(COUNT); i++) {
+            long a = long_in1[i], b = long_in2[i];
+            long minAB = m1arr[i] ? VectorMath.minUnsigned(a, b) : a;
+            long maxBA = m1arr[i] ? VectorMath.maxUnsigned(b, a) : b;
+            long expected = m1arr[i] ? VectorMath.minUnsigned(minAB, maxBA) : minAB;
+            Verify.checkEQ(long_out[i], expected);
+        }
+    }
+
+    // Predicated Long: umin(umin(a,b,m1), umax(a,b,m2), m1) => NO transform
+    @Test
+    @IR(counts = {IRNode.UMIN_VL, " 2 ", IRNode.UMAX_VL, " 1 "}, applyIfCPUFeatureOr = {"avx512f", "true", "sve", "true"})
+    public void umin_long_masked_diff_mask_minmax(int index) {
+        LongVector vec1 = LongVector.fromArray(lspec, long_in1, index);
+        LongVector vec2 = LongVector.fromArray(lspec, long_in2, index);
+        VectorMask mask1 = VectorMask.fromArray(lspec, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(lspec, m2arr, index);
+        vec1.lanewise(VectorOperators.UMIN, vec2, mask1)
+            .lanewise(VectorOperators.UMIN,
+                      vec1.lanewise(VectorOperators.UMAX, vec2, mask2), mask1)
+            .intoArray(long_out, index);
+    }
+
+    @Run(test = "umin_long_masked_diff_mask_minmax")
+    public void umin_long_masked_diff_mask_minmax_runner() {
+        for (int i = 0; i < lspec.loopBound(COUNT); i += lspec.length()) {
+            umin_long_masked_diff_mask_minmax(i);
+        }
+        for (int i = 0; i < lspec.loopBound(COUNT); i++) {
+            long a = long_in1[i], b = long_in2[i];
+            long minAB = m1arr[i] ? VectorMath.minUnsigned(a, b) : a;
+            long maxAB = m2arr[i] ? VectorMath.maxUnsigned(a, b) : a;
+            long expected = m1arr[i] ? VectorMath.minUnsigned(minAB, maxAB) : minAB;
+            Verify.checkEQ(long_out[i], expected);
+        }
+    }
+
+    // Predicated Long: umin(umin(a,b,m2), umax(a,b,m1), m1) => NO transform
+    @Test
+    @IR(counts = {IRNode.UMIN_VL, " 2 ", IRNode.UMAX_VL, " 1 "}, applyIfCPUFeatureOr = {"avx512f", "true", "sve", "true"})
+    public void umin_long_masked_diff_mask_minmax_swap(int index) {
+        LongVector vec1 = LongVector.fromArray(lspec, long_in1, index);
+        LongVector vec2 = LongVector.fromArray(lspec, long_in2, index);
+        VectorMask mask1 = VectorMask.fromArray(lspec, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(lspec, m2arr, index);
+        vec1.lanewise(VectorOperators.UMIN, vec2, mask2)
+            .lanewise(VectorOperators.UMIN,
+                      vec1.lanewise(VectorOperators.UMAX, vec2, mask1), mask1)
+            .intoArray(long_out, index);
+    }
+
+    @Run(test = "umin_long_masked_diff_mask_minmax_swap")
+    public void umin_long_masked_diff_mask_minmax_swap_runner() {
+        for (int i = 0; i < lspec.loopBound(COUNT); i += lspec.length()) {
+            umin_long_masked_diff_mask_minmax_swap(i);
+        }
+        for (int i = 0; i < lspec.loopBound(COUNT); i++) {
+            long a = long_in1[i], b = long_in2[i];
+            long minAB = m2arr[i] ? VectorMath.minUnsigned(a, b) : a;
+            long maxAB = m1arr[i] ? VectorMath.maxUnsigned(a, b) : a;
+            long expected = m1arr[i] ? VectorMath.minUnsigned(minAB, maxAB) : minAB;
+            Verify.checkEQ(long_out[i], expected);
+        }
+    }
+
+    // Predicated Long: umin(umin(a,b,m1), umax(a,b,m1), m2) => NO transform
+    @Test
+    @IR(counts = {IRNode.UMIN_VL, " 2 ", IRNode.UMAX_VL, " 1 "}, applyIfCPUFeatureOr = {"avx512f", "true", "sve", "true"})
+    public void umin_long_masked_diff_mask_outer(int index) {
+        LongVector vec1 = LongVector.fromArray(lspec, long_in1, index);
+        LongVector vec2 = LongVector.fromArray(lspec, long_in2, index);
+        VectorMask mask1 = VectorMask.fromArray(lspec, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(lspec, m2arr, index);
+        vec1.lanewise(VectorOperators.UMIN, vec2, mask1)
+            .lanewise(VectorOperators.UMIN,
+                      vec1.lanewise(VectorOperators.UMAX, vec2, mask1), mask2)
+            .intoArray(long_out, index);
+    }
+
+    @Run(test = "umin_long_masked_diff_mask_outer")
+    public void umin_long_masked_diff_mask_outer_runner() {
+        for (int i = 0; i < lspec.loopBound(COUNT); i += lspec.length()) {
+            umin_long_masked_diff_mask_outer(i);
+        }
+        for (int i = 0; i < lspec.loopBound(COUNT); i++) {
+            long a = long_in1[i], b = long_in2[i];
+            long minAB = m1arr[i] ? VectorMath.minUnsigned(a, b) : a;
+            long maxAB = m1arr[i] ? VectorMath.maxUnsigned(a, b) : a;
+            long expected = m2arr[i] ? VectorMath.minUnsigned(minAB, maxAB) : minAB;
+            Verify.checkEQ(long_out[i], expected);
+        }
+    }
+
+    // Predicated Long: umin(umin(a,b,m1), umax(a,b,m2), m3) => NO transform
+    @Test
+    @IR(counts = {IRNode.UMIN_VL, " 2 ", IRNode.UMAX_VL, " 1 "}, applyIfCPUFeatureOr = {"avx512f", "true", "sve", "true"})
+    public void umin_long_masked_all_diff_mask(int index) {
+        LongVector vec1 = LongVector.fromArray(lspec, long_in1, index);
+        LongVector vec2 = LongVector.fromArray(lspec, long_in2, index);
+        VectorMask mask1 = VectorMask.fromArray(lspec, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(lspec, m2arr, index);
+        VectorMask mask3 = VectorMask.fromArray(lspec, m3arr, index);
+        vec1.lanewise(VectorOperators.UMIN, vec2, mask1)
+            .lanewise(VectorOperators.UMIN,
+                      vec1.lanewise(VectorOperators.UMAX, vec2, mask2), mask3)
+            .intoArray(long_out, index);
+    }
+
+    @Run(test = "umin_long_masked_all_diff_mask")
+    public void umin_long_masked_all_diff_mask_runner() {
+        for (int i = 0; i < lspec.loopBound(COUNT); i += lspec.length()) {
+            umin_long_masked_all_diff_mask(i);
+        }
+        for (int i = 0; i < lspec.loopBound(COUNT); i++) {
+            long a = long_in1[i], b = long_in2[i];
+            long minAB = m1arr[i] ? VectorMath.minUnsigned(a, b) : a;
+            long maxAB = m2arr[i] ? VectorMath.maxUnsigned(a, b) : a;
+            long expected = m3arr[i] ? VectorMath.minUnsigned(minAB, maxAB) : minAB;
+            Verify.checkEQ(long_out[i], expected);
+        }
+    }
+
+    // Predicated Long: umax(umin(a,b,m), umax(a,b,m), m) => umax(a,b,m)
+    @Test
+    @IR(counts = {IRNode.UMIN_VL, " 0 ", IRNode.UMAX_VL, " 1 "}, applyIfCPUFeatureOr = {"avx512f", "true", "sve", "true"})
+    public void umax_long_masked_same_mask(int index) {
+        LongVector vec1 = LongVector.fromArray(lspec, long_in1, index);
+        LongVector vec2 = LongVector.fromArray(lspec, long_in2, index);
+        VectorMask m = VectorMask.fromArray(lspec, m1arr, index);
+        vec1.lanewise(VectorOperators.UMIN, vec2, m)
+            .lanewise(VectorOperators.UMAX,
+                      vec1.lanewise(VectorOperators.UMAX, vec2, m), m)
+            .intoArray(long_out, index);
+    }
+
+    @Run(test = "umax_long_masked_same_mask")
+    public void umax_long_masked_same_mask_runner() {
+        for (int i = 0; i < lspec.loopBound(COUNT); i += lspec.length()) {
+            umax_long_masked_same_mask(i);
+        }
+        for (int i = 0; i < lspec.loopBound(COUNT); i++) {
+            long a = long_in1[i], b = long_in2[i];
+            long minAB = m1arr[i] ? VectorMath.minUnsigned(a, b) : a;
+            long maxAB = m1arr[i] ? VectorMath.maxUnsigned(a, b) : a;
+            long expected = m1arr[i] ? VectorMath.maxUnsigned(minAB, maxAB) : minAB;
+            Verify.checkEQ(long_out[i], expected);
+        }
+    }
+
+    // Predicated Long: umax(umin(a,b,m), umax(b,a,m), m) => umax(a,b,m)
+    @Test
+    @IR(counts = {IRNode.UMIN_VL, " 0 ", IRNode.UMAX_VL, " 1 "}, applyIfCPUFeatureOr = {"avx512f", "true", "sve", "true"})
+    public void umax_long_masked_flipped_inputs(int index) {
+        LongVector vec1 = LongVector.fromArray(lspec, long_in1, index);
+        LongVector vec2 = LongVector.fromArray(lspec, long_in2, index);
+        VectorMask m = VectorMask.fromArray(lspec, m1arr, index);
+        vec1.lanewise(VectorOperators.UMIN, vec2, m)
+            .lanewise(VectorOperators.UMAX,
+                      vec2.lanewise(VectorOperators.UMAX, vec1, m), m)
+            .intoArray(long_out, index);
+    }
+
+    @Run(test = "umax_long_masked_flipped_inputs")
+    public void umax_long_masked_flipped_inputs_runner() {
+        for (int i = 0; i < lspec.loopBound(COUNT); i += lspec.length()) {
+            umax_long_masked_flipped_inputs(i);
+        }
+        for (int i = 0; i < lspec.loopBound(COUNT); i++) {
+            long a = long_in1[i], b = long_in2[i];
+            long minAB = m1arr[i] ? VectorMath.minUnsigned(a, b) : a;
+            long maxBA = m1arr[i] ? VectorMath.maxUnsigned(b, a) : b;
+            long expected = m1arr[i] ? VectorMath.maxUnsigned(minAB, maxBA) : minAB;
+            Verify.checkEQ(long_out[i], expected);
+        }
+    }
+
+    // Predicated Long: umax(umin(a,b,m1), umax(a,b,m2), m1) => NO transform
+    @Test
+    @IR(counts = {IRNode.UMIN_VL, " 1 ", IRNode.UMAX_VL, " 2 "}, applyIfCPUFeatureOr = {"avx512f", "true", "sve", "true"})
+    public void umax_long_masked_diff_mask_minmax(int index) {
+        LongVector vec1 = LongVector.fromArray(lspec, long_in1, index);
+        LongVector vec2 = LongVector.fromArray(lspec, long_in2, index);
+        VectorMask mask1 = VectorMask.fromArray(lspec, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(lspec, m2arr, index);
+        vec1.lanewise(VectorOperators.UMIN, vec2, mask1)
+            .lanewise(VectorOperators.UMAX,
+                      vec1.lanewise(VectorOperators.UMAX, vec2, mask2), mask1)
+            .intoArray(long_out, index);
+    }
+
+    @Run(test = "umax_long_masked_diff_mask_minmax")
+    public void umax_long_masked_diff_mask_minmax_runner() {
+        for (int i = 0; i < lspec.loopBound(COUNT); i += lspec.length()) {
+            umax_long_masked_diff_mask_minmax(i);
+        }
+        for (int i = 0; i < lspec.loopBound(COUNT); i++) {
+            long a = long_in1[i], b = long_in2[i];
+            long minAB = m1arr[i] ? VectorMath.minUnsigned(a, b) : a;
+            long maxAB = m2arr[i] ? VectorMath.maxUnsigned(a, b) : a;
+            long expected = m1arr[i] ? VectorMath.maxUnsigned(minAB, maxAB) : minAB;
+            Verify.checkEQ(long_out[i], expected);
+        }
+    }
+
+    // Predicated Long: umax(umin(a,b,m2), umax(a,b,m1), m1) => NO transform
+    @Test
+    @IR(counts = {IRNode.UMIN_VL, " 1 ", IRNode.UMAX_VL, " 2 "}, applyIfCPUFeatureOr = {"avx512f", "true", "sve", "true"})
+    public void umax_long_masked_diff_mask_minmax_swap(int index) {
+        LongVector vec1 = LongVector.fromArray(lspec, long_in1, index);
+        LongVector vec2 = LongVector.fromArray(lspec, long_in2, index);
+        VectorMask mask1 = VectorMask.fromArray(lspec, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(lspec, m2arr, index);
+        vec1.lanewise(VectorOperators.UMIN, vec2, mask2)
+            .lanewise(VectorOperators.UMAX,
+                      vec1.lanewise(VectorOperators.UMAX, vec2, mask1), mask1)
+            .intoArray(long_out, index);
+    }
+
+    @Run(test = "umax_long_masked_diff_mask_minmax_swap")
+    public void umax_long_masked_diff_mask_minmax_swap_runner() {
+        for (int i = 0; i < lspec.loopBound(COUNT); i += lspec.length()) {
+            umax_long_masked_diff_mask_minmax_swap(i);
+        }
+        for (int i = 0; i < lspec.loopBound(COUNT); i++) {
+            long a = long_in1[i], b = long_in2[i];
+            long minAB = m2arr[i] ? VectorMath.minUnsigned(a, b) : a;
+            long maxAB = m1arr[i] ? VectorMath.maxUnsigned(a, b) : a;
+            long expected = m1arr[i] ? VectorMath.maxUnsigned(minAB, maxAB) : minAB;
+            Verify.checkEQ(long_out[i], expected);
+        }
+    }
+
+    // Predicated Long: umax(umin(a,b,m1), umax(a,b,m1), m2) => NO transform
+    @Test
+    @IR(counts = {IRNode.UMIN_VL, " 1 ", IRNode.UMAX_VL, " 2 "}, applyIfCPUFeatureOr = {"avx512f", "true", "sve", "true"})
+    public void umax_long_masked_diff_mask_outer(int index) {
+        LongVector vec1 = LongVector.fromArray(lspec, long_in1, index);
+        LongVector vec2 = LongVector.fromArray(lspec, long_in2, index);
+        VectorMask mask1 = VectorMask.fromArray(lspec, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(lspec, m2arr, index);
+        vec1.lanewise(VectorOperators.UMIN, vec2, mask1)
+            .lanewise(VectorOperators.UMAX,
+                      vec1.lanewise(VectorOperators.UMAX, vec2, mask1), mask2)
+            .intoArray(long_out, index);
+    }
+
+    @Run(test = "umax_long_masked_diff_mask_outer")
+    public void umax_long_masked_diff_mask_outer_runner() {
+        for (int i = 0; i < lspec.loopBound(COUNT); i += lspec.length()) {
+            umax_long_masked_diff_mask_outer(i);
+        }
+        for (int i = 0; i < lspec.loopBound(COUNT); i++) {
+            long a = long_in1[i], b = long_in2[i];
+            long minAB = m1arr[i] ? VectorMath.minUnsigned(a, b) : a;
+            long maxAB = m1arr[i] ? VectorMath.maxUnsigned(a, b) : a;
+            long expected = m2arr[i] ? VectorMath.maxUnsigned(minAB, maxAB) : minAB;
+            Verify.checkEQ(long_out[i], expected);
+        }
+    }
+
+    // Predicated Long: umax(umin(a,b,m1), umax(a,b,m2), m3) => NO transform
+    @Test
+    @IR(counts = {IRNode.UMIN_VL, " 1 ", IRNode.UMAX_VL, " 2 "}, applyIfCPUFeatureOr = {"avx512f", "true", "sve", "true"})
+    public void umax_long_masked_all_diff_mask(int index) {
+        LongVector vec1 = LongVector.fromArray(lspec, long_in1, index);
+        LongVector vec2 = LongVector.fromArray(lspec, long_in2, index);
+        VectorMask mask1 = VectorMask.fromArray(lspec, m1arr, index);
+        VectorMask mask2 = VectorMask.fromArray(lspec, m2arr, index);
+        VectorMask mask3 = VectorMask.fromArray(lspec, m3arr, index);
+        vec1.lanewise(VectorOperators.UMIN, vec2, mask1)
+            .lanewise(VectorOperators.UMAX,
+                      vec1.lanewise(VectorOperators.UMAX, vec2, mask2), mask3)
+            .intoArray(long_out, index);
+    }
+
+    @Run(test = "umax_long_masked_all_diff_mask")
+    public void umax_long_masked_all_diff_mask_runner() {
+        for (int i = 0; i < lspec.loopBound(COUNT); i += lspec.length()) {
+            umax_long_masked_all_diff_mask(i);
+        }
+        for (int i = 0; i < lspec.loopBound(COUNT); i++) {
+            long a = long_in1[i], b = long_in2[i];
+            long minAB = m1arr[i] ? VectorMath.minUnsigned(a, b) : a;
+            long maxAB = m2arr[i] ? VectorMath.maxUnsigned(a, b) : a;
+            long expected = m3arr[i] ? VectorMath.maxUnsigned(minAB, maxAB) : minAB;
+            Verify.checkEQ(long_out[i], expected);
+        }
+    }
+}

From 0a6775a4dcae5f92e116e22399d0ec31dd2b8e41 Mon Sep 17 00:00:00 2001
From: Amit Kumar 
Date: Wed, 15 Apr 2026 04:38:38 +0000
Subject: [PATCH 053/108] 8381787: Add testcase for Vector Lane Reversal issue

Reviewed-by: mdoerr, varadam
---
 .../vector/TestLongVectorReinterpret.java     | 84 +++++++++++++++++++
 1 file changed, 84 insertions(+)
 create mode 100644 test/jdk/jdk/incubator/vector/TestLongVectorReinterpret.java

diff --git a/test/jdk/jdk/incubator/vector/TestLongVectorReinterpret.java b/test/jdk/jdk/incubator/vector/TestLongVectorReinterpret.java
new file mode 100644
index 00000000000..3d51031840c
--- /dev/null
+++ b/test/jdk/jdk/incubator/vector/TestLongVectorReinterpret.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2026 IBM Corp. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+ * @test
+ * @bug 8371187
+ * @summary Randomized test for lane swapping issue in LongVector reinterpretation on BE platforms
+ * @modules jdk.incubator.vector
+ * @run main/othervm --add-modules=jdk.incubator.vector TestLongVectorReinterpret
+ */
+
+import jdk.incubator.vector.*;
+import java.util.SplittableRandom;
+
+public class TestLongVectorReinterpret {
+
+    static final VectorSpecies SPECIES = LongVector.SPECIES_128;
+    static final SplittableRandom RAND = new SplittableRandom(42);
+
+    public static void main(String[] args) {
+        for (int iter = 0; iter < 1000; iter++) {
+            verifyLaneOrdering();
+        }
+    }
+
+    static void verifyLaneOrdering() {
+        long[] a = new long[SPECIES.length()];
+        long[] b = new long[SPECIES.length()];
+
+        for (int i = 0; i < a.length; i++) {
+            a[i] = RAND.nextLong();
+            b[i] = RAND.nextLong();
+        }
+
+        LongVector v1 = LongVector.fromArray(SPECIES, a, 0);
+        LongVector v2 = LongVector.fromArray(SPECIES, b, 0);
+
+        IntVector result = v2.reinterpretAsInts().add(v1.reinterpretAsInts());
+
+        int[] expected = new int[SPECIES.length() * 2];
+
+        for (int i = 0; i < a.length; i++) {
+            int loA = (int) a[i];
+            int hiA = (int) (a[i] >>> 32);
+
+            int loB = (int) b[i];
+            int hiB = (int) (b[i] >>> 32);
+
+            expected[2 * i]     = loA + loB;
+            expected[2 * i + 1] = hiA + hiB;
+        }
+
+        for (int i = 0; i < expected.length; i++) {
+            int actual = result.lane(i);
+            if (actual != expected[i]) {
+                throw new AssertionError(
+                    "Mismatch at lane " + i +
+                    " expected=" + expected[i] +
+                    " actual=" + actual +
+                    " vector=" + result);
+            }
+        }
+    }
+}

From 29024c253e3970a2f32a2573936bb85b0eabdbcc Mon Sep 17 00:00:00 2001
From: David Holmes 
Date: Wed, 15 Apr 2026 05:27:03 +0000
Subject: [PATCH 054/108] 8382202: New file VectorMinMaxTransforms.java has a
 copyright format error

Reviewed-by: mikael
---
 .../jtreg/compiler/vectorapi/VectorMinMaxTransforms.java    | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/test/hotspot/jtreg/compiler/vectorapi/VectorMinMaxTransforms.java b/test/hotspot/jtreg/compiler/vectorapi/VectorMinMaxTransforms.java
index acffe4d3257..be3ec322892 100644
--- a/test/hotspot/jtreg/compiler/vectorapi/VectorMinMaxTransforms.java
+++ b/test/hotspot/jtreg/compiler/vectorapi/VectorMinMaxTransforms.java
@@ -12,9 +12,9 @@
  * 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.
+ * 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

From 436d291a1c679f892e5164ddf7d04ca59d6ca254 Mon Sep 17 00:00:00 2001
From: Eric Fang 
Date: Wed, 15 Apr 2026 08:24:51 +0000
Subject: [PATCH 055/108] 8370863: VectorAPI: Optimize the VectorMaskCast chain
 in specific patterns

Reviewed-by: xgong, vlivanov, galder
---
 src/hotspot/share/opto/phaseX.cpp             |   7 +-
 src/hotspot/share/opto/vectornode.cpp         |  33 +-
 src/hotspot/share/opto/vectornode.hpp         |   1 +
 .../TestVectorLongToMaskNodeIdealization.java |   8 +-
 .../vectorapi/VectorMaskCastIdentityTest.java | 104 +++---
 .../vectorapi/VectorMaskCastTest.java         | 188 +++++++----
 .../vectorapi/VectorMaskToLongTest.java       |  33 +-
 .../VectorStoreMaskIdentityTest.java          | 317 ++++++++++++++++++
 .../vector/VectorStoreMaskBenchmark.java      |  84 +++++
 9 files changed, 654 insertions(+), 121 deletions(-)
 create mode 100644 test/hotspot/jtreg/compiler/vectorapi/VectorStoreMaskIdentityTest.java
 create mode 100644 test/micro/org/openjdk/bench/jdk/incubator/vector/VectorStoreMaskBenchmark.java

diff --git a/src/hotspot/share/opto/phaseX.cpp b/src/hotspot/share/opto/phaseX.cpp
index 0fecc14f31a..a4d6a6c33d0 100644
--- a/src/hotspot/share/opto/phaseX.cpp
+++ b/src/hotspot/share/opto/phaseX.cpp
@@ -2123,7 +2123,12 @@ void PhaseIterGVN::verify_Identity_for(Node* n) {
 
   if (n->is_Vector()) {
     // Found with tier1-3. Not investigated yet.
-    // The observed issue was with AndVNode::Identity
+    // The observed issue was with AndVNode::Identity and
+    // VectorStoreMaskNode::Identity (see JDK-8370863).
+    //
+    // Found with:
+    //   compiler/vectorapi/VectorStoreMaskIdentityTest.java
+    //   -XX:CompileThreshold=100 -XX:-TieredCompilation -XX:VerifyIterativeGVN=1110
     return;
   }
 
diff --git a/src/hotspot/share/opto/vectornode.cpp b/src/hotspot/share/opto/vectornode.cpp
index b07f0234492..dbadc18da01 100644
--- a/src/hotspot/share/opto/vectornode.cpp
+++ b/src/hotspot/share/opto/vectornode.cpp
@@ -1047,6 +1047,20 @@ Node* VectorNode::Ideal(PhaseGVN* phase, bool can_reshape) {
   return nullptr;
 }
 
+// Traverses a chain of VectorMaskCast and returns the first non VectorMaskCast node.
+//
+// Due to the unique nature of vector masks, for specific IR patterns,
+// VectorMaskCast does not affect the output results. For example:
+//   (VectorStoreMask (VectorMaskCast* (VectorLoadMask x))) => (x)
+//   x remains to be a bool vector with no changes.
+// This function can be used to eliminate the VectorMaskCast in such patterns.
+Node* VectorNode::uncast_mask(Node* n) {
+  while (n->Opcode() == Op_VectorMaskCast) {
+    n = n->in(1);
+  }
+  return n;
+}
+
 // Return initial Pack node. Additional operands added with add_opd() calls.
 PackNode* PackNode::make(Node* s, uint vlen, BasicType bt) {
   const TypeVect* vt = TypeVect::make(bt, vlen);
@@ -1495,10 +1509,12 @@ Node* VectorLoadMaskNode::Identity(PhaseGVN* phase) {
 
 Node* VectorStoreMaskNode::Identity(PhaseGVN* phase) {
   // Identity transformation on boolean vectors.
-  //   VectorStoreMask (VectorLoadMask bv) elem_size ==> bv
+  //   VectorStoreMask (VectorMaskCast* VectorLoadMask bv) elem_size ==> bv
   //   vector[n]{bool} => vector[n]{t} => vector[n]{bool}
-  if (in(1)->Opcode() == Op_VectorLoadMask) {
-    return in(1)->in(1);
+  Node* in1 = VectorNode::uncast_mask(in(1));
+  if (in1->Opcode() == Op_VectorLoadMask) {
+    assert(length() == in1->as_Vector()->length(), "vector length must match");
+    return in1->in(1);
   }
   return this;
 }
@@ -1959,11 +1975,12 @@ Node* VectorMaskOpNode::Ideal(PhaseGVN* phase, bool can_reshape) {
 }
 
 Node* VectorMaskCastNode::Identity(PhaseGVN* phase) {
-  Node* in1 = in(1);
-  // VectorMaskCast (VectorMaskCast x) => x
-  if (in1->Opcode() == Op_VectorMaskCast &&
-      vect_type()->eq(in1->in(1)->bottom_type())) {
-      return in1->in(1);
+  // (VectorMaskCast+ x) => (x)
+  // If the types of the input and output nodes in a VectorMaskCast chain are
+  // exactly the same, the intermediate VectorMaskCast nodes can be eliminated.
+  Node* n = VectorNode::uncast_mask(this);
+  if (vect_type()->eq(n->bottom_type())) {
+      return n;
   }
   return this;
 }
diff --git a/src/hotspot/share/opto/vectornode.hpp b/src/hotspot/share/opto/vectornode.hpp
index c4fff06e771..de866898302 100644
--- a/src/hotspot/share/opto/vectornode.hpp
+++ b/src/hotspot/share/opto/vectornode.hpp
@@ -195,6 +195,7 @@ class VectorNode : public TypeNode {
   static bool is_scalar_op_that_returns_int_but_vector_op_returns_long(int opc);
   static bool is_reinterpret_opcode(int opc);
 
+  static Node* uncast_mask(Node* n);
 
   static void trace_new_vector(Node* n, const char* context) {
 #ifdef ASSERT
diff --git a/test/hotspot/jtreg/compiler/vectorapi/TestVectorLongToMaskNodeIdealization.java b/test/hotspot/jtreg/compiler/vectorapi/TestVectorLongToMaskNodeIdealization.java
index 706e21554bb..f9e50fbd23b 100644
--- a/test/hotspot/jtreg/compiler/vectorapi/TestVectorLongToMaskNodeIdealization.java
+++ b/test/hotspot/jtreg/compiler/vectorapi/TestVectorLongToMaskNodeIdealization.java
@@ -46,7 +46,7 @@ import static compiler.lib.template_framework.Template.let;
 
 /*
  * @test
- * @bug 8277997 8378968
+ * @bug 8277997 8378968 8380290
  * @key randomness
  * @summary Testing some optimizations in VectorLongToMaskNode::Ideal
  *          For now: VectorMask.fromLong(.., mask.toLong())
@@ -93,7 +93,7 @@ public class TestVectorLongToMaskNodeIdealization {
                   IRNode.VECTOR_STORE_MASK,                     "> 0", // Not yet optimized away
                   IRNode.VECTOR_LONG_TO_MASK,                   "= 0", // Optimized away
                   IRNode.VECTOR_MASK_TO_LONG,                   "= 0", // Optimized away
-                  IRNode.VECTOR_MASK_CAST,                      "> 0", // Not yet optimized away
+                  IRNode.VECTOR_MASK_CAST,                      "= 0", // Optimized away
                   IRNode.VECTOR_BLEND_I,  IRNode.VECTOR_SIZE_4, "> 0",
                   IRNode.XOR_VI,          IRNode.VECTOR_SIZE_4, "> 0",
                   IRNode.STORE_VECTOR,                          "> 0"},
@@ -168,7 +168,7 @@ public class TestVectorLongToMaskNodeIdealization {
                   IRNode.VECTOR_STORE_MASK,                     "> 0", // Not yet optimized away
                   IRNode.VECTOR_LONG_TO_MASK,                   "= 0", // Optimized away
                   IRNode.VECTOR_MASK_TO_LONG,                   "= 0", // Optimized away
-                  IRNode.VECTOR_MASK_CAST,                      "> 0", // Not yet optimized away: Cast Z->Z, see JDK-8379866
+                  IRNode.VECTOR_MASK_CAST,                      "= 0", // Optimized away
                   IRNode.VECTOR_BLEND_I,  IRNode.VECTOR_SIZE_4, "> 0",
                   IRNode.XOR_VI,          IRNode.VECTOR_SIZE_4, "> 0",
                   IRNode.STORE_VECTOR,                          "> 0"},
@@ -219,7 +219,7 @@ public class TestVectorLongToMaskNodeIdealization {
                   IRNode.VECTOR_STORE_MASK,                     "= 0",
                   IRNode.VECTOR_LONG_TO_MASK,                   "= 0", // Optimized away
                   IRNode.VECTOR_MASK_TO_LONG,                   "= 0", // Optimized away
-                  IRNode.VECTOR_MASK_CAST,                      "> 0", // Not yet optimized Z->Z, see JDK-8379866
+                  IRNode.VECTOR_MASK_CAST,                      "= 0", // Optimized away
                   IRNode.VECTOR_BLEND_I,  IRNode.VECTOR_SIZE_4, "> 0",
                   IRNode.XOR_VI,          IRNode.VECTOR_SIZE_4, "> 0",
                   IRNode.STORE_VECTOR,                          "> 0"},
diff --git a/test/hotspot/jtreg/compiler/vectorapi/VectorMaskCastIdentityTest.java b/test/hotspot/jtreg/compiler/vectorapi/VectorMaskCastIdentityTest.java
index e4f166f510a..e1c20da76ec 100644
--- a/test/hotspot/jtreg/compiler/vectorapi/VectorMaskCastIdentityTest.java
+++ b/test/hotspot/jtreg/compiler/vectorapi/VectorMaskCastIdentityTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ * Copyright (c) 2025, 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
  * 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,10 +23,10 @@
 
 /*
 * @test
-* @bug 8356760
+* @bug 8356760 8370863
 * @key randomness
 * @library /test/lib /
-* @summary Optimize VectorMask.fromLong for all-true/all-false cases
+* @summary test VectorMaskCast Identity() optimizations
 * @modules jdk.incubator.vector
 *
 * @run driver compiler.vectorapi.VectorMaskCastIdentityTest
@@ -50,70 +50,88 @@ public class VectorMaskCastIdentityTest {
     }
 
     @Test
-    @IR(counts = { IRNode.VECTOR_MASK_CAST, "= 2" }, applyIfCPUFeatureOr = {"asimd", "true", "rvv", "true"})
-    public static int testTwoCastToDifferentType() {
-        // The types before and after the two casts are not the same, so the cast cannot be eliminated.
-        VectorMask mFloat64 = VectorMask.fromArray(FloatVector.SPECIES_64, mr, 0);
-        VectorMask mDouble128 = mFloat64.cast(DoubleVector.SPECIES_128);
-        VectorMask mInt64 = mDouble128.cast(IntVector.SPECIES_64);
-        return mInt64.trueCount();
+    @IR(counts = { IRNode.LOAD_VECTOR_Z, IRNode.VECTOR_SIZE_4, "> 0",
+                   IRNode.VECTOR_MASK_CAST, "= 0" },
+        applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" },
+        applyIf = { "MaxVectorSize", ">= 16" })
+    public static int testOneCastToSameType() {
+        // The types before and after the cast sequence are the same,
+        // so the casts will be eliminated.
+        VectorMask mInt128 = VectorMask.fromArray(IntVector.SPECIES_128, mr, 0);
+        mInt128 = mInt128.cast(IntVector.SPECIES_128);
+        // Insert a not() to prevent the casts being optimized by the optimization:
+        // (VectorStoreMask (VectorMaskCast* (VectorLoadMask x))) => x
+        return mInt128.not().trueCount();
     }
 
-    @Run(test = "testTwoCastToDifferentType")
-    public static void testTwoCastToDifferentType_runner() {
-        int count = testTwoCastToDifferentType();
-        VectorMask mFloat64 = VectorMask.fromArray(FloatVector.SPECIES_64, mr, 0);
-        Asserts.assertEquals(count, mFloat64.trueCount());
+    @Run(test = "testOneCastToSameType")
+    public static void testOneCastToSameType_runner() {
+        int count = testOneCastToSameType();
+        VectorMask mInt128 = VectorMask.fromArray(IntVector.SPECIES_128, mr, 0);
+        Asserts.assertEquals(count, mInt128.not().trueCount());
     }
 
     @Test
-    @IR(counts = { IRNode.VECTOR_MASK_CAST, "= 2" }, applyIfCPUFeatureOr = {"avx2", "true"})
-    public static int testTwoCastToDifferentType2() {
-        // The types before and after the two casts are not the same, so the cast cannot be eliminated.
-        VectorMask mInt128 = VectorMask.fromArray(IntVector.SPECIES_128, mr, 0);
-        VectorMask mDouble256 = mInt128.cast(DoubleVector.SPECIES_256);
-        VectorMask  mShort64 = mDouble256.cast(ShortVector.SPECIES_64);
-        return mShort64.trueCount();
-    }
-
-    @Run(test = "testTwoCastToDifferentType2")
-    public static void testTwoCastToDifferentType2_runner() {
-        int count = testTwoCastToDifferentType2();
-        VectorMask mInt128 = VectorMask.fromArray(IntVector.SPECIES_128, mr, 0);
-        Asserts.assertEquals(count, mInt128.trueCount());
-    }
-
-    @Test
-    @IR(counts = { IRNode.VECTOR_MASK_CAST, "= 0" }, applyIfCPUFeatureOr = {"avx2", "true", "asimd", "true", "rvv", "true"})
+    @IR(counts = { IRNode.LOAD_VECTOR_Z, IRNode.VECTOR_SIZE_4, "> 0",
+                   IRNode.VECTOR_MASK_CAST, "= 0" },
+        applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" },
+        applyIf = { "MaxVectorSize", ">= 16" })
     public static int testTwoCastToSameType() {
-        // The types before and after the two casts are the same, so the cast will be eliminated.
+        // The types before and after the cast sequence are the same,
+        // so the casts will be eliminated.
         VectorMask mInt128 = VectorMask.fromArray(IntVector.SPECIES_128, mr, 0);
         VectorMask mFloat128 = mInt128.cast(FloatVector.SPECIES_128);
         VectorMask mInt128_2 = mFloat128.cast(IntVector.SPECIES_128);
-        return mInt128_2.trueCount();
+        return mInt128_2.not().trueCount();
     }
 
     @Run(test = "testTwoCastToSameType")
     public static void testTwoCastToSameType_runner() {
         int count = testTwoCastToSameType();
         VectorMask mInt128 = VectorMask.fromArray(IntVector.SPECIES_128, mr, 0);
-        Asserts.assertEquals(count, mInt128.trueCount());
+        Asserts.assertEquals(count, mInt128.not().trueCount());
     }
 
     @Test
-    @IR(counts = { IRNode.VECTOR_MASK_CAST, "= 1" }, applyIfCPUFeatureOr = {"avx2", "true", "asimd", "true", "rvv", "true"})
+    @IR(counts = { IRNode.LOAD_VECTOR_Z, IRNode.VECTOR_SIZE_4, "> 0",
+                   IRNode.VECTOR_MASK_CAST, "= 1" },
+        applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" },
+        applyIf = { "MaxVectorSize", ">= 16" })
     public static int testOneCastToDifferentType() {
-        // The types before and after the only cast are different, the cast will not be eliminated.
-        VectorMask mFloat128 = VectorMask.fromArray(FloatVector.SPECIES_128, mr, 0).not();
-        VectorMask mInt128 = mFloat128.cast(IntVector.SPECIES_128);
-        return mInt128.trueCount();
+        // The types before and after the cast sequence are different,
+        // so the casts will not be eliminated.
+        VectorMask mInt128 = VectorMask.fromArray(IntVector.SPECIES_128, mr, 0);
+        VectorMask mShort64 = mInt128.cast(ShortVector.SPECIES_64);
+        return mShort64.not().trueCount();
     }
 
     @Run(test = "testOneCastToDifferentType")
     public static void testOneCastToDifferentType_runner() {
         int count = testOneCastToDifferentType();
-        VectorMask mInt128 = VectorMask.fromArray(FloatVector.SPECIES_128, mr, 0).not();
-        Asserts.assertEquals(count, mInt128.trueCount());
+        VectorMask mInt128 = VectorMask.fromArray(IntVector.SPECIES_128, mr, 0);
+        Asserts.assertEquals(count, mInt128.not().trueCount());
+    }
+
+    @Test
+    @IR(counts = { IRNode.LOAD_VECTOR_Z, IRNode.VECTOR_SIZE_4, "> 0",
+                   IRNode.VECTOR_MASK_CAST, "= 2" },
+        applyIfCPUFeatureOr = { "asimd", "true", "avx2", "true", "rvv", "true" },
+        applyIf = { "MaxVectorSize", ">= 16" })
+    public static int testTwoCastToDifferentType() {
+        // The types before and after the cast sequence are different, so the
+        // casts are not eliminated. We should probably be able to eliminate
+        // the intermediate cast, so that we only need a cast from short to int.
+        VectorMask mShort64 = VectorMask.fromArray(ShortVector.SPECIES_64, mr, 0);
+        VectorMask mFloat128 = mShort64.cast(FloatVector.SPECIES_128);
+        VectorMask mInt128 = mFloat128.cast(IntVector.SPECIES_128);
+        return mInt128.not().trueCount();
+    }
+
+    @Run(test = "testTwoCastToDifferentType")
+    public static void testTwoCastToDifferentType_runner() {
+        int count = testTwoCastToDifferentType();
+        VectorMask mShort64 = VectorMask.fromArray(ShortVector.SPECIES_64, mr, 0);
+        Asserts.assertEquals(count, mShort64.not().trueCount());
     }
 
     public static void main(String[] args) {
diff --git a/test/hotspot/jtreg/compiler/vectorapi/VectorMaskCastTest.java b/test/hotspot/jtreg/compiler/vectorapi/VectorMaskCastTest.java
index 1f346cffd2f..c75ea6c7a66 100644
--- a/test/hotspot/jtreg/compiler/vectorapi/VectorMaskCastTest.java
+++ b/test/hotspot/jtreg/compiler/vectorapi/VectorMaskCastTest.java
@@ -1,5 +1,6 @@
 /*
  * Copyright (c) 2021, 2023, Arm Limited. All rights reserved.
+ * Copyright (c) 2025, 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -76,13 +77,15 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeatureOr = {"avx2", "true", "asimd", "true", "rvv", "true"})
     public static VectorMask testByte64ToShort128(VectorMask v) {
-        return v.cast(ShortVector.SPECIES_128);
+        // A not operation is introduced to prevent the cast from being optimized away.
+        return v.not().cast(ShortVector.SPECIES_128);
     }
 
     @Run(test = "testByte64ToShort128")
     public static void testByte64ToShort128_runner() {
         VectorMask mByte64 = VectorMask.fromArray(ByteVector.SPECIES_64, mask_arr, 0);
         VectorMask res = testByte64ToShort128(mByte64);
+        mByte64 = mByte64.not();
         Asserts.assertEquals(res.toString(), mByte64.toString());
         Asserts.assertEquals(res.trueCount(), mByte64.trueCount());
     }
@@ -90,13 +93,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeature = {"avx2", "true"})
     public static VectorMask testByte64ToInt256(VectorMask v) {
-        return v.cast(IntVector.SPECIES_256);
+        return v.not().cast(IntVector.SPECIES_256);
     }
 
     @Run(test = "testByte64ToInt256")
     public static void testByte64ToInt256_runner() {
         VectorMask mByte64 = VectorMask.fromArray(ByteVector.SPECIES_64, mask_arr, 0);
         VectorMask res = testByte64ToInt256(mByte64);
+        mByte64 = mByte64.not();
         Asserts.assertEquals(res.toString(), mByte64.toString());
         Asserts.assertEquals(res.trueCount(), mByte64.trueCount());
     }
@@ -104,13 +108,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeature = {"avx2", "true"})
     public static VectorMask testByte64ToFloat256(VectorMask v) {
-        return v.cast(FloatVector.SPECIES_256);
+        return v.not().cast(FloatVector.SPECIES_256);
     }
 
     @Run(test = "testByte64ToFloat256")
     public static void testByte64ToFloat256_runner() {
         VectorMask mByte64 = VectorMask.fromArray(ByteVector.SPECIES_64, mask_arr, 0);
         VectorMask res = testByte64ToFloat256(mByte64);
+        mByte64 = mByte64.not();
         Asserts.assertEquals(res.toString(), mByte64.toString());
         Asserts.assertEquals(res.trueCount(), mByte64.trueCount());
     }
@@ -118,13 +123,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeature = {"avx512vl", "true"})
     public static VectorMask testByte64ToLong512(VectorMask v) {
-        return v.cast(LongVector.SPECIES_512);
+        return v.not().cast(LongVector.SPECIES_512);
     }
 
     @Run(test = "testByte64ToLong512")
     public static void testByte64ToLong512_runner() {
         VectorMask mByte64 = VectorMask.fromArray(ByteVector.SPECIES_64, mask_arr, 0);
         VectorMask res = testByte64ToLong512(mByte64);
+        mByte64 = mByte64.not();
         Asserts.assertEquals(res.toString(), mByte64.toString());
         Asserts.assertEquals(res.trueCount(), mByte64.trueCount());
     }
@@ -132,13 +138,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeature = {"avx512vl", "true"})
     public static VectorMask testByte64ToDouble512(VectorMask v) {
-       return v.cast(DoubleVector.SPECIES_512);
+       return v.not().cast(DoubleVector.SPECIES_512);
     }
 
     @Run(test = "testByte64ToDouble512")
     public static void testByte64ToDouble512_runner() {
         VectorMask mByte64 = VectorMask.fromArray(ByteVector.SPECIES_64, mask_arr, 0);
         VectorMask res = testByte64ToDouble512(mByte64);
+        mByte64 = mByte64.not();
         Asserts.assertEquals(res.toString(), mByte64.toString());
         Asserts.assertEquals(res.trueCount(), mByte64.trueCount());
     }
@@ -146,13 +153,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeature = {"avx2", "true"})
     public static VectorMask testByte128ToShort256(VectorMask v) {
-        return v.cast(ShortVector.SPECIES_256);
+        return v.not().cast(ShortVector.SPECIES_256);
     }
 
     @Run(test = "testByte128ToShort256")
     public static void testByte128ToShort256_runner() {
         VectorMask mByte128 = VectorMask.fromArray(ByteVector.SPECIES_128, mask_arr, 0);
         VectorMask res = testByte128ToShort256(mByte128);
+        mByte128 = mByte128.not();
         Asserts.assertEquals(res.toString(), mByte128.toString());
         Asserts.assertEquals(res.trueCount(), mByte128.trueCount());
     }
@@ -160,13 +168,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeature = {"avx512vl", "true"})
     public static VectorMask testByte128ToInt512(VectorMask v) {
-        return v.cast(IntVector.SPECIES_512);
+        return v.not().cast(IntVector.SPECIES_512);
     }
 
     @Run(test = "testByte128ToInt512")
     public static void testByte128ToInt512_runner() {
         VectorMask mByte128 = VectorMask.fromArray(ByteVector.SPECIES_128, mask_arr, 0);
         VectorMask res = testByte128ToInt512(mByte128);
+        mByte128 = mByte128.not();
         Asserts.assertEquals(res.toString(), mByte128.toString());
         Asserts.assertEquals(res.trueCount(), mByte128.trueCount());
     }
@@ -174,13 +183,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeature = {"avx512vl", "true"})
     public static VectorMask testByte128ToFloat512(VectorMask v) {
-        return v.cast(FloatVector.SPECIES_512);
+        return v.not().cast(FloatVector.SPECIES_512);
     }
 
     @Run(test = "testByte128ToFloat512")
     public static void testByte128ToFloat512_runner() {
         VectorMask mByte128 = VectorMask.fromArray(ByteVector.SPECIES_128, mask_arr, 0);
         VectorMask res = testByte128ToFloat512(mByte128);
+        mByte128 = mByte128.not();
         Asserts.assertEquals(res.toString(), mByte128.toString());
         Asserts.assertEquals(res.trueCount(), mByte128.trueCount());
     }
@@ -188,13 +198,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeature = {"avx512vl", "true"})
     public static VectorMask testByte256ToShort512(VectorMask v) {
-        return v.cast(ShortVector.SPECIES_512);
+        return v.not().cast(ShortVector.SPECIES_512);
     }
 
     @Run(test = "testByte256ToShort512")
     public static void testByte256ToShort512_runner() {
         VectorMask mByte256 = VectorMask.fromArray(ByteVector.SPECIES_256, mask_arr, 0);
         VectorMask res = testByte256ToShort512(mByte256);
+        mByte256 = mByte256.not();
         Asserts.assertEquals(res.toString(), mByte256.toString());
         Asserts.assertEquals(res.trueCount(), mByte256.trueCount());
     }
@@ -203,13 +214,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeatureOr = {"avx2", "true", "asimd", "true", "rvv", "true"})
     public static VectorMask testShort64ToInt128(VectorMask v) {
-        return v.cast(IntVector.SPECIES_128);
+        return v.not().cast(IntVector.SPECIES_128);
     }
 
     @Run(test = "testShort64ToInt128")
     public static void testShort64ToInt128_runner() {
         VectorMask mShort64 = VectorMask.fromArray(ShortVector.SPECIES_64, mask_arr, 0);
         VectorMask res = testShort64ToInt128(mShort64);
+        mShort64 = mShort64.not();
         Asserts.assertEquals(res.toString(), mShort64.toString());
         Asserts.assertEquals(res.trueCount(), mShort64.trueCount());
     }
@@ -217,13 +229,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeatureOr = {"avx2", "true", "asimd", "true", "rvv", "true"})
     public static VectorMask testShort64ToFloat128(VectorMask v) {
-        return v.cast(FloatVector.SPECIES_128);
+        return v.not().cast(FloatVector.SPECIES_128);
     }
 
     @Run(test = "testShort64ToFloat128")
     public static void testShort64ToFloat128_runner() {
         VectorMask mShort64 = VectorMask.fromArray(ShortVector.SPECIES_64, mask_arr, 0);
         VectorMask res = testShort64ToFloat128(mShort64);
+        mShort64 = mShort64.not();
         Asserts.assertEquals(res.toString(), mShort64.toString());
         Asserts.assertEquals(res.trueCount(), mShort64.trueCount());
     }
@@ -231,13 +244,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeature = {"avx2", "true"})
     public static VectorMask testShort64ToLong256(VectorMask v) {
-        return v.cast(LongVector.SPECIES_256);
+        return v.not().cast(LongVector.SPECIES_256);
     }
 
     @Run(test = "testShort64ToLong256")
     public static void testShort64ToLong256_runner() {
         VectorMask mShort64 = VectorMask.fromArray(ShortVector.SPECIES_64, mask_arr, 0);
         VectorMask res = testShort64ToLong256(mShort64);
+        mShort64 = mShort64.not();
         Asserts.assertEquals(res.toString(), mShort64.toString());
         Asserts.assertEquals(res.trueCount(), mShort64.trueCount());
     }
@@ -245,13 +259,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeature = {"avx2", "true"})
     public static VectorMask testShort64ToDouble256(VectorMask v) {
-        return v.cast(DoubleVector.SPECIES_256);
+        return v.not().cast(DoubleVector.SPECIES_256);
     }
 
     @Run(test = "testShort64ToDouble256")
     public static void testShort64ToDouble256_runner() {
         VectorMask mShort64 = VectorMask.fromArray(ShortVector.SPECIES_64, mask_arr, 0);
         VectorMask res = testShort64ToDouble256(mShort64);
+        mShort64 = mShort64.not();
         Asserts.assertEquals(res.toString(), mShort64.toString());
         Asserts.assertEquals(res.trueCount(), mShort64.trueCount());
     }
@@ -259,13 +274,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeatureOr = {"avx2", "true", "asimd", "true", "rvv", "true"})
     public static VectorMask testShort128ToByte64(VectorMask v) {
-        return v.cast(ByteVector.SPECIES_64);
+        return v.not().cast(ByteVector.SPECIES_64);
     }
 
     @Run(test = "testShort128ToByte64")
     public static void testShort128ToByte64_runner() {
         VectorMask mShort128 = VectorMask.fromArray(ShortVector.SPECIES_128, mask_arr, 0);
         VectorMask res = testShort128ToByte64(mShort128);
+        mShort128 = mShort128.not();
         Asserts.assertEquals(res.toString(), mShort128.toString());
         Asserts.assertEquals(res.trueCount(), mShort128.trueCount());
     }
@@ -273,13 +289,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeature = {"avx2", "true"})
     public static VectorMask testShort128ToInt256(VectorMask v) {
-        return v.cast(IntVector.SPECIES_256);
+        return v.not().cast(IntVector.SPECIES_256);
     }
 
     @Run(test = "testShort128ToInt256")
     public static void testShort128ToInt256_runner() {
         VectorMask mShort128 = VectorMask.fromArray(ShortVector.SPECIES_128, mask_arr, 0);
         VectorMask res = testShort128ToInt256(mShort128);
+        mShort128 = mShort128.not();
         Asserts.assertEquals(res.toString(), mShort128.toString());
         Asserts.assertEquals(res.trueCount(), mShort128.trueCount());
     }
@@ -287,13 +304,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeature = {"avx2", "true"})
     public static VectorMask testShort128ToFloat256(VectorMask v) {
-        return v.cast(FloatVector.SPECIES_256);
+        return v.not().cast(FloatVector.SPECIES_256);
     }
 
     @Run(test = "testShort128ToFloat256")
     public static void testShort128ToFloat256_runner() {
         VectorMask mShort128 = VectorMask.fromArray(ShortVector.SPECIES_128, mask_arr, 0);
         VectorMask res = testShort128ToFloat256(mShort128);
+        mShort128 = mShort128.not();
         Asserts.assertEquals(res.toString(), mShort128.toString());
         Asserts.assertEquals(res.trueCount(), mShort128.trueCount());
     }
@@ -301,13 +319,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeature = {"avx512vl", "true"})
     public static VectorMask testShort128ToLong512(VectorMask v) {
-        return v.cast(LongVector.SPECIES_512);
+        return v.not().cast(LongVector.SPECIES_512);
     }
 
     @Run(test = "testShort128ToLong512")
     public static void testShort128ToLong512_runner() {
         VectorMask mShort128 = VectorMask.fromArray(ShortVector.SPECIES_128, mask_arr, 0);
         VectorMask res = testShort128ToLong512(mShort128);
+        mShort128 = mShort128.not();
         Asserts.assertEquals(res.toString(), mShort128.toString());
         Asserts.assertEquals(res.trueCount(), mShort128.trueCount());
     }
@@ -315,13 +334,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeature = {"avx512vl", "true"})
     public static VectorMask testShort128ToDouble512(VectorMask v) {
-        return v.cast(DoubleVector.SPECIES_512);
+        return v.not().cast(DoubleVector.SPECIES_512);
     }
 
     @Run(test = "testShort128ToDouble512")
     public static void testShort128ToDouble512_runner() {
         VectorMask mShort128 = VectorMask.fromArray(ShortVector.SPECIES_128, mask_arr, 0);
         VectorMask res = testShort128ToDouble512(mShort128);
+        mShort128 = mShort128.not();
         Asserts.assertEquals(res.toString(), mShort128.toString());
         Asserts.assertEquals(res.trueCount(), mShort128.trueCount());
     }
@@ -329,13 +349,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeature = {"avx2", "true"})
     public static VectorMask testShort256ToByte128(VectorMask v) {
-       return v.cast(ByteVector.SPECIES_128);
+       return v.not().cast(ByteVector.SPECIES_128);
     }
 
     @Run(test = "testShort256ToByte128")
     public static void testShort256ToByte128_runner() {
         VectorMask mShort256 = VectorMask.fromArray(ShortVector.SPECIES_256, mask_arr, 0);
         VectorMask res = testShort256ToByte128(mShort256);
+        mShort256 = mShort256.not();
         Asserts.assertEquals(res.toString(), mShort256.toString());
         Asserts.assertEquals(res.trueCount(), mShort256.trueCount());
     }
@@ -343,13 +364,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeature = {"avx512vl", "true"})
     public static VectorMask testShort256ToInt512(VectorMask v) {
-        return v.cast(IntVector.SPECIES_512);
+        return v.not().cast(IntVector.SPECIES_512);
     }
 
     @Run(test = "testShort256ToInt512")
     public static void testShort256ToInt512_runner() {
         VectorMask mShort256 = VectorMask.fromArray(ShortVector.SPECIES_256, mask_arr, 0);
         VectorMask res = testShort256ToInt512(mShort256);
+        mShort256 = mShort256.not();
         Asserts.assertEquals(res.toString(), mShort256.toString());
         Asserts.assertEquals(res.trueCount(), mShort256.trueCount());
     }
@@ -357,13 +379,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeature = {"avx512vl", "true"})
     public static VectorMask testShort256ToFloat512(VectorMask v) {
-        return v.cast(FloatVector.SPECIES_512);
+        return v.not().cast(FloatVector.SPECIES_512);
     }
 
     @Run(test = "testShort256ToFloat512")
     public static void testShort256ToFloat512_runner() {
         VectorMask mShort256 = VectorMask.fromArray(ShortVector.SPECIES_256, mask_arr, 0);
         VectorMask res = testShort256ToFloat512(mShort256);
+        mShort256 = mShort256.not();
         Asserts.assertEquals(res.toString(), mShort256.toString());
         Asserts.assertEquals(res.trueCount(), mShort256.trueCount());
     }
@@ -371,13 +394,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeature = {"avx512vl", "true"})
     public static VectorMask testShort512ToByte256(VectorMask v) {
-        return v.cast(ByteVector.SPECIES_256);
+        return v.not().cast(ByteVector.SPECIES_256);
     }
 
     @Run(test = "testShort512ToByte256")
     public static void testShort512ToByte256_runner() {
         VectorMask mShort512 = VectorMask.fromArray(ShortVector.SPECIES_512, mask_arr, 0);
         VectorMask res = testShort512ToByte256(mShort512);
+        mShort512 = mShort512.not();
         Asserts.assertEquals(res.toString(), mShort512.toString());
         Asserts.assertEquals(res.trueCount(), mShort512.trueCount());
     }
@@ -386,13 +410,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeatureOr = {"asimd", "true", "rvv", "true"})
     public static VectorMask testInt64ToLong128(VectorMask v) {
-        return v.cast(LongVector.SPECIES_128);
+        return v.not().cast(LongVector.SPECIES_128);
     }
 
     @Run(test = "testInt64ToLong128")
     public static void testInt64ToLong128_runner() {
         VectorMask mInt64 = VectorMask.fromArray(IntVector.SPECIES_64, mask_arr, 0);
         VectorMask res = testInt64ToLong128(mInt64);
+        mInt64 = mInt64.not();
         Asserts.assertEquals(res.toString(), mInt64.toString());
         Asserts.assertEquals(res.trueCount(), mInt64.trueCount());
     }
@@ -400,13 +425,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeatureOr = {"asimd", "true", "rvv", "true"})
     public static VectorMask testInt64ToDouble128(VectorMask v) {
-        return v.cast(DoubleVector.SPECIES_128);
+        return v.not().cast(DoubleVector.SPECIES_128);
     }
 
     @Run(test = "testInt64ToDouble128")
     public static void testInt64ToDouble128_runner() {
         VectorMask mInt64 = VectorMask.fromArray(IntVector.SPECIES_64, mask_arr, 0);
         VectorMask res = testInt64ToDouble128(mInt64);
+        mInt64 = mInt64.not();
         Asserts.assertEquals(res.toString(), mInt64.toString());
         Asserts.assertEquals(res.trueCount(), mInt64.trueCount());
     }
@@ -414,13 +440,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeatureOr = {"avx2", "true", "asimd", "true", "rvv", "true"})
     public static VectorMask testInt128ToShort64(VectorMask v) {
-        return v.cast(ShortVector.SPECIES_64);
+        return v.not().cast(ShortVector.SPECIES_64);
     }
 
     @Run(test = "testInt128ToShort64")
     public static void testInt128ToShort64_runner() {
         VectorMask mInt128 = VectorMask.fromArray(IntVector.SPECIES_128, mask_arr, 0);
         VectorMask res = testInt128ToShort64(mInt128);
+        mInt128 = mInt128.not();
         Asserts.assertEquals(res.toString(), mInt128.toString());
         Asserts.assertEquals(res.trueCount(), mInt128.trueCount());
     }
@@ -428,13 +455,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeature = {"avx2", "true"})
     public static VectorMask testInt128ToLong256(VectorMask v) {
-        return v.cast(LongVector.SPECIES_256);
+        return v.not().cast(LongVector.SPECIES_256);
     }
 
     @Run(test = "testInt128ToLong256")
     public static void testInt128ToLong256_runner() {
         VectorMask mInt128 = VectorMask.fromArray(IntVector.SPECIES_128, mask_arr, 0);
         VectorMask res = testInt128ToLong256(mInt128);
+        mInt128 = mInt128.not();
         Asserts.assertEquals(res.toString(), mInt128.toString());
         Asserts.assertEquals(res.trueCount(), mInt128.trueCount());
     }
@@ -442,13 +470,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeature = {"avx2", "true"})
     public static VectorMask testInt128ToDouble256(VectorMask v) {
-        return v.cast(DoubleVector.SPECIES_256);
+        return v.not().cast(DoubleVector.SPECIES_256);
     }
 
     @Run(test = "testInt128ToDouble256")
     public static void testInt128ToDouble256_runner() {
         VectorMask mInt128 = VectorMask.fromArray(IntVector.SPECIES_128, mask_arr, 0);
         VectorMask res = testInt128ToDouble256(mInt128);
+        mInt128 = mInt128.not();
         Asserts.assertEquals(res.toString(), mInt128.toString());
         Asserts.assertEquals(res.trueCount(), mInt128.trueCount());
     }
@@ -456,13 +485,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeature = {"avx2", "true"})
     public static VectorMask testInt256ToShort128(VectorMask v) {
-        return v.cast(ShortVector.SPECIES_128);
+        return v.not().cast(ShortVector.SPECIES_128);
     }
 
     @Run(test = "testInt256ToShort128")
     public static void testInt256ToShort128_runner() {
         VectorMask mInt256 = VectorMask.fromArray(IntVector.SPECIES_256, mask_arr, 0);
         VectorMask res = testInt256ToShort128(mInt256);
+        mInt256 = mInt256.not();
         Asserts.assertEquals(res.toString(), mInt256.toString());
         Asserts.assertEquals(res.trueCount(), mInt256.trueCount());
     }
@@ -470,13 +500,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeature = {"avx2", "true"})
     public static VectorMask testInt256ToByte64(VectorMask v) {
-    return v.cast(ByteVector.SPECIES_64);
+    return v.not().cast(ByteVector.SPECIES_64);
     }
 
     @Run(test = "testInt256ToByte64")
     public static void testInt256ToByte64_runner() {
         VectorMask mInt256 = VectorMask.fromArray(IntVector.SPECIES_256, mask_arr, 0);
         VectorMask res = testInt256ToByte64(mInt256);
+        mInt256 = mInt256.not();
         Asserts.assertEquals(res.toString(), mInt256.toString());
         Asserts.assertEquals(res.trueCount(), mInt256.trueCount());
     }
@@ -484,13 +515,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeature = {"avx512vl", "true"})
     public static VectorMask testInt256ToLong512(VectorMask v) {
-        return v.cast(LongVector.SPECIES_512);
+        return v.not().cast(LongVector.SPECIES_512);
     }
 
     @Run(test = "testInt256ToLong512")
     public static void testInt256ToLong512_runner() {
         VectorMask mInt256 = VectorMask.fromArray(IntVector.SPECIES_256, mask_arr, 0);
         VectorMask res = testInt256ToLong512(mInt256);
+        mInt256 = mInt256.not();
         Asserts.assertEquals(res.toString(), mInt256.toString());
         Asserts.assertEquals(res.trueCount(), mInt256.trueCount());
     }
@@ -498,13 +530,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeature = {"avx512vl", "true"})
     public static VectorMask testInt256ToDouble512(VectorMask v) {
-        return v.cast(DoubleVector.SPECIES_512);
+        return v.not().cast(DoubleVector.SPECIES_512);
     }
 
     @Run(test = "testInt256ToDouble512")
     public static void testInt256ToDouble512_runner() {
         VectorMask mInt256 = VectorMask.fromArray(IntVector.SPECIES_256, mask_arr, 0);
         VectorMask res = testInt256ToDouble512(mInt256);
+        mInt256 = mInt256.not();
         Asserts.assertEquals(res.toString(), mInt256.toString());
         Asserts.assertEquals(res.trueCount(), mInt256.trueCount());
     }
@@ -512,13 +545,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeature = {"avx512vl", "true"})
     public static VectorMask testInt512ToShort256(VectorMask v) {
-        return v.cast(ShortVector.SPECIES_256);
+        return v.not().cast(ShortVector.SPECIES_256);
     }
 
     @Run(test = "testInt512ToShort256")
     public static void testInt512ToShort256_runner() {
         VectorMask mInt512 = VectorMask.fromArray(IntVector.SPECIES_512, mask_arr, 0);
         VectorMask res = testInt512ToShort256(mInt512);
+        mInt512 = mInt512.not();
         Asserts.assertEquals(res.toString(), mInt512.toString());
         Asserts.assertEquals(res.trueCount(), mInt512.trueCount());
     }
@@ -526,13 +560,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeature = {"avx512vl", "true"})
     public static VectorMask testInt512ToByte128(VectorMask v) {
-        return v.cast(ByteVector.SPECIES_128);
+        return v.not().cast(ByteVector.SPECIES_128);
     }
 
     @Run(test = "testInt512ToByte128")
     public static void testInt512ToByte128_runner() {
         VectorMask mInt512 = VectorMask.fromArray(IntVector.SPECIES_512, mask_arr, 0);
         VectorMask res = testInt512ToByte128(mInt512);
+        mInt512 = mInt512.not();
         Asserts.assertEquals(res.toString(), mInt512.toString());
         Asserts.assertEquals(res.trueCount(), mInt512.trueCount());
     }
@@ -541,13 +576,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeatureOr = {"asimd", "true", "rvv", "true"})
     public static VectorMask testFloat64ToLong128(VectorMask v) {
-        return v.cast(LongVector.SPECIES_128);
+        return v.not().cast(LongVector.SPECIES_128);
     }
 
     @Run(test = "testFloat64ToLong128")
     public static void testFloat64ToLong128_runner() {
         VectorMask mFloat64 = VectorMask.fromArray(FloatVector.SPECIES_64, mask_arr, 0);
         VectorMask res = testFloat64ToLong128(mFloat64);
+        mFloat64 = mFloat64.not();
         Asserts.assertEquals(res.toString(), mFloat64.toString());
         Asserts.assertEquals(res.trueCount(), mFloat64.trueCount());
     }
@@ -555,13 +591,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeatureOr = {"asimd", "true", "rvv", "true"})
     public static VectorMask testFloat64ToDouble128(VectorMask v) {
-        return v.cast(DoubleVector.SPECIES_128);
+        return v.not().cast(DoubleVector.SPECIES_128);
     }
 
     @Run(test = "testFloat64ToDouble128")
     public static void testFloat64ToDouble128_runner() {
         VectorMask mFloat64 = VectorMask.fromArray(FloatVector.SPECIES_64, mask_arr, 0);
         VectorMask res = testFloat64ToDouble128(mFloat64);
+        mFloat64 = mFloat64.not();
         Asserts.assertEquals(res.toString(), mFloat64.toString());
         Asserts.assertEquals(res.trueCount(), mFloat64.trueCount());
     }
@@ -569,13 +606,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeatureOr = {"avx2", "true", "asimd", "true", "rvv", "true"})
     public static VectorMask testFloat128ToShort64(VectorMask v) {
-        return v.cast(ShortVector.SPECIES_64);
+        return v.not().cast(ShortVector.SPECIES_64);
     }
 
     @Run(test = "testFloat128ToShort64")
     public static void testFloat128ToShort64_runner() {
         VectorMask mFloat128 = VectorMask.fromArray(FloatVector.SPECIES_128, mask_arr, 0);
         VectorMask res = testFloat128ToShort64(mFloat128);
+        mFloat128 = mFloat128.not();
         Asserts.assertEquals(res.toString(), mFloat128.toString());
         Asserts.assertEquals(res.trueCount(), mFloat128.trueCount());
     }
@@ -583,13 +621,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeature = {"avx2", "true"})
     public static VectorMask testFloat128ToLong256(VectorMask v) {
-        return v.cast(LongVector.SPECIES_256);
+        return v.not().cast(LongVector.SPECIES_256);
     }
 
     @Run(test = "testFloat128ToLong256")
     public static void testFloat128ToLong256_runner() {
         VectorMask mFloat128 = VectorMask.fromArray(FloatVector.SPECIES_128, mask_arr, 0);
         VectorMask res = testFloat128ToLong256(mFloat128);
+        mFloat128 = mFloat128.not();
         Asserts.assertEquals(res.toString(), mFloat128.toString());
         Asserts.assertEquals(res.trueCount(), mFloat128.trueCount());
     }
@@ -597,13 +636,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeature = {"avx2", "true"})
     public static VectorMask testFloat128ToDouble256(VectorMask v) {
-        return v.cast(DoubleVector.SPECIES_256);
+        return v.not().cast(DoubleVector.SPECIES_256);
     }
 
     @Run(test = "testFloat128ToDouble256")
     public static void testFloat128ToDouble256_runner() {
         VectorMask mFloat128 = VectorMask.fromArray(FloatVector.SPECIES_128, mask_arr, 0);
         VectorMask res = testFloat128ToDouble256(mFloat128);
+        mFloat128 = mFloat128.not();
         Asserts.assertEquals(res.toString(), mFloat128.toString());
         Asserts.assertEquals(res.trueCount(), mFloat128.trueCount());
     }
@@ -611,13 +651,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeature = {"avx2", "true"})
     public static VectorMask testFloat256ToShort128(VectorMask v) {
-        return v.cast(ShortVector.SPECIES_128);
+        return v.not().cast(ShortVector.SPECIES_128);
     }
 
     @Run(test = "testFloat256ToShort128")
     public static void testFloat256ToShort128_runner() {
         VectorMask mFloat256 = VectorMask.fromArray(FloatVector.SPECIES_256, mask_arr, 0);
         VectorMask res = testFloat256ToShort128(mFloat256);
+        mFloat256 = mFloat256.not();
         Asserts.assertEquals(res.toString(), mFloat256.toString());
         Asserts.assertEquals(res.trueCount(), mFloat256.trueCount());
     }
@@ -625,13 +666,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeature = {"avx2", "true"})
     public static VectorMask testFloat256ToByte64(VectorMask v) {
-        return v.cast(ByteVector.SPECIES_64);
+        return v.not().cast(ByteVector.SPECIES_64);
     }
 
     @Run(test = "testFloat256ToByte64")
     public static void testFloat256ToByte64_runner() {
         VectorMask mFloat256 = VectorMask.fromArray(FloatVector.SPECIES_256, mask_arr, 0);
         VectorMask res = testFloat256ToByte64(mFloat256);
+        mFloat256 = mFloat256.not();
         Asserts.assertEquals(res.toString(), mFloat256.toString());
         Asserts.assertEquals(res.trueCount(), mFloat256.trueCount());
     }
@@ -639,13 +681,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeature = {"avx512vl", "true"})
     public static VectorMask testFloat256ToLong512(VectorMask v) {
-        return v.cast(LongVector.SPECIES_512);
+        return v.not().cast(LongVector.SPECIES_512);
     }
 
     @Run(test = "testFloat256ToLong512")
     public static void testFloat256ToLong512_runner() {
         VectorMask mFloat256 = VectorMask.fromArray(FloatVector.SPECIES_256, mask_arr, 0);
         VectorMask res = testFloat256ToLong512(mFloat256);
+        mFloat256 = mFloat256.not();
         Asserts.assertEquals(res.toString(), mFloat256.toString());
         Asserts.assertEquals(res.trueCount(), mFloat256.trueCount());
     }
@@ -653,13 +696,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeature = {"avx512vl", "true"})
     public static VectorMask testFloat256ToDouble512(VectorMask v) {
-        return v.cast(DoubleVector.SPECIES_512);
+        return v.not().cast(DoubleVector.SPECIES_512);
     }
 
     @Run(test = "testFloat256ToDouble512")
     public static void testFloat256ToDouble512_runner() {
         VectorMask mFloat256 = VectorMask.fromArray(FloatVector.SPECIES_256, mask_arr, 0);
         VectorMask res = testFloat256ToDouble512(mFloat256);
+        mFloat256 = mFloat256.not();
         Asserts.assertEquals(res.toString(), mFloat256.toString());
         Asserts.assertEquals(res.trueCount(), mFloat256.trueCount());
     }
@@ -667,13 +711,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeature = {"avx512vl", "true"})
     public static VectorMask testFloat512ToShort256(VectorMask v) {
-        return v.cast(ShortVector.SPECIES_256);
+        return v.not().cast(ShortVector.SPECIES_256);
     }
 
     @Run(test = "testFloat512ToShort256")
     public static void testFloat512ToShort256_runner() {
         VectorMask mFloat512 = VectorMask.fromArray(FloatVector.SPECIES_512, mask_arr, 0);
         VectorMask res = testFloat512ToShort256(mFloat512);
+        mFloat512 = mFloat512.not();
         Asserts.assertEquals(res.toString(), mFloat512.toString());
         Asserts.assertEquals(res.trueCount(), mFloat512.trueCount());
     }
@@ -681,13 +726,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeature = {"avx512vl", "true"})
     public static VectorMask testFloat512ToByte128(VectorMask v) {
-        return v.cast(ByteVector.SPECIES_128);
+        return v.not().cast(ByteVector.SPECIES_128);
     }
 
     @Run(test = "testFloat512ToByte128")
     public static void testFloat512ToByte128_runner() {
         VectorMask mFloat512 = VectorMask.fromArray(FloatVector.SPECIES_512, mask_arr, 0);
         VectorMask res = testFloat512ToByte128(mFloat512);
+        mFloat512 = mFloat512.not();
         Asserts.assertEquals(res.toString(), mFloat512.toString());
         Asserts.assertEquals(res.trueCount(), mFloat512.trueCount());
     }
@@ -696,13 +742,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeatureOr = {"asimd", "true", "rvv", "true"})
     public static VectorMask testLong128ToInt64(VectorMask v) {
-        return v.cast(IntVector.SPECIES_64);
+        return v.not().cast(IntVector.SPECIES_64);
     }
 
     @Run(test = "testLong128ToInt64")
     public static void testLong128ToInt64_runner() {
         VectorMask mLong128 = VectorMask.fromArray(LongVector.SPECIES_128, mask_arr, 0);
         VectorMask res = testLong128ToInt64(mLong128);
+        mLong128 = mLong128.not();
         Asserts.assertEquals(res.toString(), mLong128.toString());
         Asserts.assertEquals(res.trueCount(), mLong128.trueCount());
     }
@@ -710,13 +757,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeatureOr = {"asimd", "true", "rvv", "true"})
     public static VectorMask testLong128ToFloat64(VectorMask v) {
-        return v.cast(FloatVector.SPECIES_64);
+        return v.not().cast(FloatVector.SPECIES_64);
     }
 
     @Run(test = "testLong128ToFloat64")
     public static void testLong128ToFloat64_runner() {
         VectorMask mLong128 = VectorMask.fromArray(LongVector.SPECIES_128, mask_arr, 0);
         VectorMask res = testLong128ToFloat64(mLong128);
+        mLong128 = mLong128.not();
         Asserts.assertEquals(res.toString(), mLong128.toString());
         Asserts.assertEquals(res.trueCount(), mLong128.trueCount());
     }
@@ -724,13 +772,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeature = {"avx2", "true"})
     public static VectorMask testLong256ToInt128(VectorMask v) {
-        return v.cast(IntVector.SPECIES_128);
+        return v.not().cast(IntVector.SPECIES_128);
     }
 
     @Run(test = "testLong256ToInt128")
     public static void testLong256ToInt128_runner() {
         VectorMask mLong256 = VectorMask.fromArray(LongVector.SPECIES_256, mask_arr, 0);
         VectorMask res = testLong256ToInt128(mLong256);
+        mLong256 = mLong256.not();
         Asserts.assertEquals(res.toString(), mLong256.toString());
         Asserts.assertEquals(res.trueCount(), mLong256.trueCount());
     }
@@ -738,13 +787,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeature = {"avx2", "true"})
     public static VectorMask testLong256ToFloat128(VectorMask v) {
-        return v.cast(FloatVector.SPECIES_128);
+        return v.not().cast(FloatVector.SPECIES_128);
     }
 
     @Run(test = "testLong256ToFloat128")
     public static void testLong256ToFloat128_runner() {
         VectorMask mLong256 = VectorMask.fromArray(LongVector.SPECIES_256, mask_arr, 0);
         VectorMask res = testLong256ToFloat128(mLong256);
+        mLong256 = mLong256.not();
         Asserts.assertEquals(res.toString(), mLong256.toString());
         Asserts.assertEquals(res.trueCount(), mLong256.trueCount());
     }
@@ -752,13 +802,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeature = {"avx2", "true"})
     public static VectorMask testLong256ToShort64(VectorMask v) {
-       return v.cast(ShortVector.SPECIES_64);
+       return v.not().cast(ShortVector.SPECIES_64);
     }
 
     @Run(test = "testLong256ToShort64")
     public static void testLong256ToShort64_runner() {
         VectorMask mLong256 = VectorMask.fromArray(LongVector.SPECIES_256, mask_arr, 0);
         VectorMask res = testLong256ToShort64(mLong256);
+        mLong256 = mLong256.not();
         Asserts.assertEquals(res.toString(), mLong256.toString());
         Asserts.assertEquals(res.trueCount(), mLong256.trueCount());
     }
@@ -766,13 +817,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeature = {"avx512vl", "true"})
     public static VectorMask testLong512ToInt256(VectorMask v) {
-        return v.cast(IntVector.SPECIES_256);
+        return v.not().cast(IntVector.SPECIES_256);
     }
 
     @Run(test = "testLong512ToInt256")
     public static void testLong512ToInt256_runner() {
         VectorMask mLong512 = VectorMask.fromArray(LongVector.SPECIES_512, mask_arr, 0);
         VectorMask res = testLong512ToInt256(mLong512);
+        mLong512 = mLong512.not();
         Asserts.assertEquals(res.toString(), mLong512.toString());
         Asserts.assertEquals(res.trueCount(), mLong512.trueCount());
     }
@@ -780,13 +832,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeature = {"avx512vl", "true"})
     public static VectorMask testLong512ToFloat256(VectorMask v) {
-        return v.cast(FloatVector.SPECIES_256);
+        return v.not().cast(FloatVector.SPECIES_256);
     }
 
     @Run(test = "testLong512ToFloat256")
     public static void testLong512ToFloat256_runner() {
         VectorMask mLong512 = VectorMask.fromArray(LongVector.SPECIES_512, mask_arr, 0);
         VectorMask res = testLong512ToFloat256(mLong512);
+        mLong512 = mLong512.not();
         Asserts.assertEquals(res.toString(), mLong512.toString());
         Asserts.assertEquals(res.trueCount(), mLong512.trueCount());
     }
@@ -794,13 +847,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeature = {"avx512vl", "true"})
     public static VectorMask testLong512ToShort128(VectorMask v) {
-        return v.cast(ShortVector.SPECIES_128);
+        return v.not().cast(ShortVector.SPECIES_128);
     }
 
     @Run(test = "testLong512ToShort128")
     public static void testLong512ToShort128_runner() {
         VectorMask mLong512 = VectorMask.fromArray(LongVector.SPECIES_512, mask_arr, 0);
         VectorMask res = testLong512ToShort128(mLong512);
+        mLong512 = mLong512.not();
         Asserts.assertEquals(res.toString(), mLong512.toString());
         Asserts.assertEquals(res.trueCount(), mLong512.trueCount());
     }
@@ -808,13 +862,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeature = {"avx512vl", "true"})
     public static VectorMask testLong512ToByte64(VectorMask v) {
-        return v.cast(ByteVector.SPECIES_64);
+        return v.not().cast(ByteVector.SPECIES_64);
     }
 
     @Run(test = "testLong512ToByte64")
     public static void testLong512ToByte64_runner() {
         VectorMask mLong512 = VectorMask.fromArray(LongVector.SPECIES_512, mask_arr, 0);
         VectorMask res = testLong512ToByte64(mLong512);
+        mLong512 = mLong512.not();
         Asserts.assertEquals(res.toString(), mLong512.toString());
         Asserts.assertEquals(res.trueCount(), mLong512.trueCount());
     }
@@ -823,13 +878,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeatureOr = {"asimd", "true", "rvv", "true"})
     public static VectorMask testDouble128ToInt64(VectorMask v) {
-        return v.cast(IntVector.SPECIES_64);
+        return v.not().cast(IntVector.SPECIES_64);
     }
 
     @Run(test = "testDouble128ToInt64")
     public static void testDouble128ToInt64_runner() {
         VectorMask mDouble128 = VectorMask.fromArray(DoubleVector.SPECIES_128, mask_arr, 0);
         VectorMask res = testDouble128ToInt64(mDouble128);
+        mDouble128 = mDouble128.not();
         Asserts.assertEquals(res.toString(), mDouble128.toString());
         Asserts.assertEquals(res.trueCount(), mDouble128.trueCount());
     }
@@ -837,13 +893,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeatureOr = {"asimd", "true", "rvv", "true"})
     public static VectorMask testDouble128ToFloat64(VectorMask v) {
-        return v.cast(FloatVector.SPECIES_64);
+        return v.not().cast(FloatVector.SPECIES_64);
     }
 
     @Run(test = "testDouble128ToFloat64")
     public static void testDouble128ToFloat64_runner() {
         VectorMask mDouble128 = VectorMask.fromArray(DoubleVector.SPECIES_128, mask_arr, 0);
         VectorMask res = testDouble128ToFloat64(mDouble128);
+        mDouble128 = mDouble128.not();
         Asserts.assertEquals(res.toString(), mDouble128.toString());
         Asserts.assertEquals(res.trueCount(), mDouble128.trueCount());
     }
@@ -851,13 +908,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeature = {"avx2", "true"})
     public static VectorMask testDouble256ToInt128(VectorMask v) {
-        return v.cast(IntVector.SPECIES_128);
+        return v.not().cast(IntVector.SPECIES_128);
     }
 
     @Run(test = "testDouble256ToInt128")
     public static void testDouble256ToInt128_runner() {
         VectorMask mDouble256 = VectorMask.fromArray(DoubleVector.SPECIES_256, mask_arr, 0);
         VectorMask res = testDouble256ToInt128(mDouble256);
+        mDouble256 = mDouble256.not();
         Asserts.assertEquals(res.toString(), mDouble256.toString());
         Asserts.assertEquals(res.trueCount(), mDouble256.trueCount());
     }
@@ -865,13 +923,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeature = {"avx2", "true"})
     public static VectorMask testDouble256ToFloat128(VectorMask v) {
-        return v.cast(FloatVector.SPECIES_128);
+        return v.not().cast(FloatVector.SPECIES_128);
     }
 
     @Run(test = "testDouble256ToFloat128")
     public static void testDouble256ToFloat128_runner() {
         VectorMask mDouble256 = VectorMask.fromArray(DoubleVector.SPECIES_256, mask_arr, 0);
         VectorMask res = testDouble256ToFloat128(mDouble256);
+        mDouble256 = mDouble256.not();
         Asserts.assertEquals(res.toString(), mDouble256.toString());
         Asserts.assertEquals(res.trueCount(), mDouble256.trueCount());
     }
@@ -879,13 +938,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeature = {"avx2", "true"})
     public static VectorMask testDouble256ToShort64(VectorMask v) {
-        return v.cast(ShortVector.SPECIES_64);
+        return v.not().cast(ShortVector.SPECIES_64);
     }
 
     @Run(test = "testDouble256ToShort64")
     public static void testDouble256ToShort64_runner() {
         VectorMask mDouble256 = VectorMask.fromArray(DoubleVector.SPECIES_256, mask_arr, 0);
         VectorMask res = testDouble256ToShort64(mDouble256);
+        mDouble256 = mDouble256.not();
         Asserts.assertEquals(res.toString(), mDouble256.toString());
         Asserts.assertEquals(res.trueCount(), mDouble256.trueCount());
     }
@@ -893,13 +953,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeature = {"avx512vl", "true"})
     public static VectorMask testDouble512ToInt256(VectorMask v) {
-        return v.cast(IntVector.SPECIES_256);
+        return v.not().cast(IntVector.SPECIES_256);
     }
 
     @Run(test = "testDouble512ToInt256")
     public static void testDouble512ToInt256_runner() {
         VectorMask mDouble512 = VectorMask.fromArray(DoubleVector.SPECIES_512, mask_arr, 0);
         VectorMask res = testDouble512ToInt256(mDouble512);
+        mDouble512 = mDouble512.not();
         Asserts.assertEquals(res.toString(), mDouble512.toString());
         Asserts.assertEquals(res.trueCount(), mDouble512.trueCount());
     }
@@ -907,13 +968,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeature = {"avx512vl", "true"})
     public static VectorMask testDouble512ToFloat256(VectorMask v) {
-        return v.cast(FloatVector.SPECIES_256);
+        return v.not().cast(FloatVector.SPECIES_256);
     }
 
     @Run(test = "testDouble512ToFloat256")
     public static void testDouble512ToFloat256_runner() {
         VectorMask mDouble512 = VectorMask.fromArray(DoubleVector.SPECIES_512, mask_arr, 0);
         VectorMask res = testDouble512ToFloat256(mDouble512);
+        mDouble512 = mDouble512.not();
         Asserts.assertEquals(res.toString(), mDouble512.toString());
         Asserts.assertEquals(res.trueCount(), mDouble512.trueCount());
     }
@@ -921,13 +983,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeature = {"avx512vl", "true"})
     public static VectorMask testDouble512ToShort128(VectorMask v) {
-        return v.cast(ShortVector.SPECIES_128);
+        return v.not().cast(ShortVector.SPECIES_128);
     }
 
     @Run(test = "testDouble512ToShort128")
     public static void testDouble512ToShort128_runner() {
         VectorMask mDouble512 = VectorMask.fromArray(DoubleVector.SPECIES_512, mask_arr, 0);
         VectorMask res = testDouble512ToShort128(mDouble512);
+        mDouble512 = mDouble512.not();
         Asserts.assertEquals(res.toString(), mDouble512.toString());
         Asserts.assertEquals(res.trueCount(), mDouble512.trueCount());
     }
@@ -935,13 +998,14 @@ public class VectorMaskCastTest {
     @Test
     @IR(counts = { IRNode.VECTOR_MASK_CAST, "> 0" }, applyIfCPUFeature = {"avx512vl", "true"})
     public static VectorMask testDouble512ToByte64(VectorMask v) {
-        return v.cast(ByteVector.SPECIES_64);
+        return v.not().cast(ByteVector.SPECIES_64);
     }
 
     @Run(test = "testDouble512ToByte64")
     public static void testDouble512ToByte64_runner() {
         VectorMask mDouble512 = VectorMask.fromArray(DoubleVector.SPECIES_512, mask_arr, 0);
         VectorMask res = testDouble512ToByte64(mDouble512);
+        mDouble512 = mDouble512.not();
         Asserts.assertEquals(res.toString(), mDouble512.toString());
         Asserts.assertEquals(res.trueCount(), mDouble512.trueCount());
     }
diff --git a/test/hotspot/jtreg/compiler/vectorapi/VectorMaskToLongTest.java b/test/hotspot/jtreg/compiler/vectorapi/VectorMaskToLongTest.java
index 35a5aca966a..43415496072 100644
--- a/test/hotspot/jtreg/compiler/vectorapi/VectorMaskToLongTest.java
+++ b/test/hotspot/jtreg/compiler/vectorapi/VectorMaskToLongTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2025, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ * Copyright (c) 2025, 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -236,10 +236,29 @@ public class VectorMaskToLongTest {
         verifyMaskToLong(L_SPECIES, inputLong, got);
     }
 
+    // Test VectorMask.fromLong().toLong() with Float species.
+    // For floating-point types, VectorMaskCast is inserted between fromLong and toLong to convert
+    // between float and integer types. There are two relevant optimizations:
+    //   1. (VectorStoreMask (VectorMaskCast* (VectorLoadMask x))) => (x)
+    //   2. (VectorMaskToLong (VectorLongToMask x)) => (x)
+    // The optimization behavior varies by architecture:
+    // - SVE with bitperm: IR chain is (VectorMaskToLong (VectorStoreMask (VectorMaskCast
+    //   (VectorLoadMask (VectorLongToMask x))))), so both optimizations are triggered.
+    // - AVX-512/RVV: IR pattern is (VectorMaskToLong (VectorMaskCast (VectorLongToMask x))),
+    //   so neither optimization is triggered.
+    // - AVX2: Same as SVE with bitperm, both optimizations are triggered.
+    // - ASIMD (without SVE bitperm): VectorLongToMaskNode is not supported,
+    //   so neither optimization is triggered.
     @Test
+    @IR(counts = { IRNode.VECTOR_LONG_TO_MASK, "= 0",
+                   IRNode.VECTOR_MASK_TO_LONG, "= 0" },
+        applyIfCPUFeature = { "svebitperm", "true" })
     @IR(counts = { IRNode.VECTOR_LONG_TO_MASK, "= 1",
                    IRNode.VECTOR_MASK_TO_LONG, "= 1" },
-        applyIfCPUFeatureOr = { "svebitperm", "true", "avx2", "true", "rvv", "true" })
+        applyIfCPUFeatureOr = { "avx512", "true", "rvv", "true" })
+    @IR(counts = { IRNode.VECTOR_LONG_TO_MASK, "= 0",
+                   IRNode.VECTOR_MASK_TO_LONG, "= 0" },
+        applyIfCPUFeatureAnd = { "avx2", "true", "avx512", "false" })
     @IR(counts = { IRNode.VECTOR_LONG_TO_MASK, "= 0",
                    IRNode.VECTOR_MASK_TO_LONG, "= 1" },
         applyIfCPUFeatureAnd = { "asimd", "true", "svebitperm", "false" })
@@ -250,10 +269,18 @@ public class VectorMaskToLongTest {
         verifyMaskToLong(F_SPECIES, inputLong, got);
     }
 
+    // Test VectorMask.fromLong().toLong() with Double species.
+    // Same as testFromLongToLongFloat() - see comments there for detailed explanation.
     @Test
+    @IR(counts = { IRNode.VECTOR_LONG_TO_MASK, "= 0",
+                   IRNode.VECTOR_MASK_TO_LONG, "= 0" },
+        applyIfCPUFeature = { "svebitperm", "true" })
     @IR(counts = { IRNode.VECTOR_LONG_TO_MASK, "= 1",
                    IRNode.VECTOR_MASK_TO_LONG, "= 1" },
-        applyIfCPUFeatureOr = { "svebitperm", "true", "avx2", "true", "rvv", "true" })
+        applyIfCPUFeatureOr = { "avx512", "true", "rvv", "true" })
+    @IR(counts = { IRNode.VECTOR_LONG_TO_MASK, "= 0",
+                   IRNode.VECTOR_MASK_TO_LONG, "= 0" },
+        applyIfCPUFeatureAnd = { "avx2", "true", "avx512", "false" })
     @IR(counts = { IRNode.VECTOR_LONG_TO_MASK, "= 0",
                    IRNode.VECTOR_MASK_TO_LONG, "= 1" },
         applyIfCPUFeatureAnd = { "asimd", "true", "svebitperm", "false" })
diff --git a/test/hotspot/jtreg/compiler/vectorapi/VectorStoreMaskIdentityTest.java b/test/hotspot/jtreg/compiler/vectorapi/VectorStoreMaskIdentityTest.java
new file mode 100644
index 00000000000..c019f116373
--- /dev/null
+++ b/test/hotspot/jtreg/compiler/vectorapi/VectorStoreMaskIdentityTest.java
@@ -0,0 +1,317 @@
+/*
+ * Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+/*
+* @test
+* @bug 8370863
+* @library /test/lib /
+* @summary VectorStoreMaskNode Identity optimization tests
+* @modules jdk.incubator.vector
+*
+* @run driver ${test.main.class}
+*/
+
+package compiler.vectorapi;
+
+import compiler.lib.ir_framework.*;
+import jdk.incubator.vector.*;
+import jdk.test.lib.Asserts;
+
+public class VectorStoreMaskIdentityTest {
+    private static final VectorSpecies B64 = ByteVector.SPECIES_64;
+    private static final VectorSpecies S64 = ShortVector.SPECIES_64;
+    private static final VectorSpecies S128 = ShortVector.SPECIES_128;
+    private static final VectorSpecies I64 = IntVector.SPECIES_64;
+    private static final VectorSpecies I128 = IntVector.SPECIES_128;
+    private static final VectorSpecies I256 = IntVector.SPECIES_256;
+    private static final VectorSpecies L128 = LongVector.SPECIES_128;
+    private static final VectorSpecies L256 = LongVector.SPECIES_256;
+    private static final VectorSpecies F128 = FloatVector.SPECIES_128;
+    private static final VectorSpecies F256 = FloatVector.SPECIES_256;
+    private static final VectorSpecies D128 = DoubleVector.SPECIES_128;
+    private static final VectorSpecies D256 = DoubleVector.SPECIES_256;
+    private static final int LENGTH = 256; // large enough
+    private static boolean[] mask_in;
+    private static boolean[] mask_out;
+    static {
+        mask_in = new boolean[LENGTH];
+        mask_out = new boolean[LENGTH];
+        for (int i = 0; i < LENGTH; i++) {
+            mask_in[i] = (i & 3) == 0;
+        }
+    }
+
+    @DontInline
+    private static void verifyResult(int vlen) {
+        for (int i = 0; i < vlen; i++) {
+            Asserts.assertEquals(mask_in[i], mask_out[i], "index " + i);
+        }
+    }
+
+    @Test
+    @IR(counts = { IRNode.LOAD_VECTOR_Z, IRNode.VECTOR_SIZE_8, ">= 1",
+                   IRNode.VECTOR_LOAD_MASK, "= 0",
+                   IRNode.VECTOR_STORE_MASK, "= 0",
+                   IRNode.VECTOR_MASK_CAST, "= 0" },
+        applyIfCPUFeatureOr = { "asimd", "true", "avx", "true" },
+        applyIf = { "MaxVectorSize", ">= 16" })
+    public static void testVectorMaskStoreIdentityByte() {
+        VectorMask mask_byte_64 = VectorMask.fromArray(B64, mask_in, 0);
+
+        mask_byte_64.cast(S128).intoArray(mask_out, 0);
+        verifyResult(B64.length());
+
+        mask_byte_64.cast(S128).cast(B64).intoArray(mask_out, 0);
+        verifyResult(B64.length());
+
+        mask_byte_64.cast(S128).cast(B64).cast(S128).intoArray(mask_out, 0);
+        verifyResult(B64.length());
+    }
+
+    @Test
+    @IR(counts = { IRNode.LOAD_VECTOR_Z, IRNode.VECTOR_SIZE_8, ">= 1",
+                   IRNode.VECTOR_LOAD_MASK, "= 0",
+                   IRNode.VECTOR_STORE_MASK, "= 0",
+                   IRNode.VECTOR_MASK_CAST, "= 0" },
+        applyIfCPUFeatureOr = { "sve", "true", "avx2", "true" },
+        applyIf = { "MaxVectorSize", "> 16" })
+    public static void testVectorMaskStoreIdentityByte256() {
+        VectorMask mask_byte_64 = VectorMask.fromArray(B64, mask_in, 0);
+
+        mask_byte_64.cast(I256).intoArray(mask_out, 0);
+        verifyResult(B64.length());
+
+        mask_byte_64.cast(S128).cast(F256).intoArray(mask_out, 0);
+        verifyResult(B64.length());
+
+        mask_byte_64.cast(F256).cast(S128).cast(I256).intoArray(mask_out, 0);
+        verifyResult(B64.length());
+    }
+
+    @Test
+    @IR(counts = { IRNode.LOAD_VECTOR_Z, IRNode.VECTOR_SIZE_8, ">= 1",
+                   IRNode.VECTOR_LOAD_MASK, "= 0",
+                   IRNode.VECTOR_STORE_MASK, "= 0",
+                   IRNode.VECTOR_MASK_CAST, "= 0" },
+        applyIfCPUFeatureOr = { "asimd", "true", "avx", "true" },
+        applyIf = { "MaxVectorSize", ">= 16" })
+    public static void testVectorMaskStoreIdentityShort() {
+        VectorMask mask_short_128 = VectorMask.fromArray(S128, mask_in, 0);
+
+        mask_short_128.cast(B64).intoArray(mask_out, 0);
+        verifyResult(S128.length());
+
+        mask_short_128.cast(B64).cast(S128).intoArray(mask_out, 0);
+        verifyResult(S128.length());
+
+        mask_short_128.cast(B64).cast(S128).cast(B64).intoArray(mask_out, 0);
+        verifyResult(S128.length());
+    }
+
+    @Test
+    @IR(counts = { IRNode.LOAD_VECTOR_Z, IRNode.VECTOR_SIZE_8, ">= 1",
+                   IRNode.VECTOR_LOAD_MASK, "= 0",
+                   IRNode.VECTOR_STORE_MASK, "= 0",
+                   IRNode.VECTOR_MASK_CAST, "= 0" },
+        applyIfCPUFeatureOr = { "sve", "true", "avx2", "true" },
+        applyIf = { "MaxVectorSize", "> 16" })
+    public static void testVectorMaskStoreIdentityShort256() {
+        VectorMask mask_short_128 = VectorMask.fromArray(S128, mask_in, 0);
+
+        mask_short_128.cast(I256).intoArray(mask_out, 0);
+        verifyResult(S128.length());
+
+        mask_short_128.cast(B64).cast(I256).intoArray(mask_out, 0);
+        verifyResult(S128.length());
+
+        mask_short_128.cast(F256).cast(B64).cast(I256).intoArray(mask_out, 0);
+        verifyResult(S128.length());
+    }
+
+    @Test
+    @IR(counts = { IRNode.LOAD_VECTOR_Z, IRNode.VECTOR_SIZE_4, ">= 1",
+                   IRNode.VECTOR_LOAD_MASK, "= 0",
+                   IRNode.VECTOR_STORE_MASK, "= 0",
+                   IRNode.VECTOR_MASK_CAST, "= 0" },
+        applyIfCPUFeatureOr = { "asimd", "true", "avx", "true" },
+        applyIf = { "MaxVectorSize", ">= 16" })
+    public static void testVectorMaskStoreIdentityInt() {
+        VectorMask mask_int_128 = VectorMask.fromArray(I128, mask_in, 0);
+
+        mask_int_128.cast(F128).intoArray(mask_out, 0);
+        verifyResult(I128.length());
+
+        mask_int_128.cast(S64).cast(F128).intoArray(mask_out, 0);
+        verifyResult(I128.length());
+
+        mask_int_128.cast(F128).cast(I128).cast(S64).intoArray(mask_out, 0);
+        verifyResult(I128.length());
+    }
+
+    @Test
+    @IR(counts = { IRNode.LOAD_VECTOR_Z, IRNode.VECTOR_SIZE_4, ">= 1",
+                   IRNode.VECTOR_LOAD_MASK, "= 0",
+                   IRNode.VECTOR_STORE_MASK, "= 0",
+                   IRNode.VECTOR_MASK_CAST, "= 0" },
+        applyIfCPUFeatureOr = { "sve", "true", "avx2", "true" },
+        applyIf = { "MaxVectorSize", "> 16" })
+    public static void testVectorMaskStoreIdentityInt256() {
+        VectorMask mask_int_128 = VectorMask.fromArray(I128, mask_in, 0);
+
+        mask_int_128.cast(F128).intoArray(mask_out, 0);
+        verifyResult(I128.length());
+
+        mask_int_128.cast(S64).cast(L256).intoArray(mask_out, 0);
+        verifyResult(I128.length());
+
+        mask_int_128.cast(L256).cast(S64).cast(F128).intoArray(mask_out, 0);
+        verifyResult(I128.length());
+    }
+
+    @Test
+    @IR(counts = { IRNode.LOAD_VECTOR_Z, IRNode.VECTOR_SIZE_2, ">= 1",
+                   IRNode.VECTOR_LOAD_MASK, "= 0",
+                   IRNode.VECTOR_STORE_MASK, "= 0",
+                   IRNode.VECTOR_MASK_CAST, "= 0" },
+        applyIfCPUFeatureOr = { "asimd", "true" },
+        applyIf = { "MaxVectorSize", ">= 16" })
+    public static void testVectorMaskStoreIdentityLong() {
+        VectorMask mask_long_128 = VectorMask.fromArray(L128, mask_in, 0);
+
+        mask_long_128.cast(D128).intoArray(mask_out, 0);
+        verifyResult(L128.length());
+
+        mask_long_128.cast(I64).cast(D128).intoArray(mask_out, 0);
+        verifyResult(L128.length());
+
+        mask_long_128.cast(I64).cast(D128).cast(I64).intoArray(mask_out, 0);
+        verifyResult(L128.length());
+    }
+
+    @Test
+    @IR(counts = { IRNode.LOAD_VECTOR_Z, IRNode.VECTOR_SIZE_4, ">= 1",
+                   IRNode.VECTOR_LOAD_MASK, "= 0",
+                   IRNode.VECTOR_STORE_MASK, "= 0",
+                   IRNode.VECTOR_MASK_CAST, "= 0" },
+        applyIfCPUFeatureOr = { "sve", "true", "avx2", "true" },
+        applyIf = { "MaxVectorSize", "> 16" })
+    public static void testVectorMaskStoreIdentityLong256() {
+        VectorMask mask_long_256 = VectorMask.fromArray(L256, mask_in, 0);
+
+        mask_long_256.cast(I128).intoArray(mask_out, 0);
+        verifyResult(L256.length());
+
+        mask_long_256.cast(S64).cast(I128).intoArray(mask_out, 0);
+        verifyResult(L256.length());
+
+        mask_long_256.cast(F128).cast(I128).cast(S64).intoArray(mask_out, 0);
+        verifyResult(L256.length());
+    }
+
+    @Test
+    @IR(counts = { IRNode.LOAD_VECTOR_Z, IRNode.VECTOR_SIZE_4, ">= 1",
+                   IRNode.VECTOR_LOAD_MASK, "= 0",
+                   IRNode.VECTOR_STORE_MASK, "= 0",
+                   IRNode.VECTOR_MASK_CAST, "= 0" },
+        applyIfCPUFeatureOr = { "asimd", "true", "avx", "true" },
+        applyIf = { "MaxVectorSize", ">= 16" })
+    public static void testVectorMaskStoreIdentityFloat() {
+        VectorMask mask_float_128 = VectorMask.fromArray(F128, mask_in, 0);
+
+        mask_float_128.cast(I128).intoArray(mask_out, 0);
+        verifyResult(F128.length());
+
+        mask_float_128.cast(S64).cast(I128).intoArray(mask_out, 0);
+        verifyResult(F128.length());
+
+        mask_float_128.cast(S64).cast(I128).cast(S64).intoArray(mask_out, 0);
+        verifyResult(F128.length());
+    }
+
+    @Test
+    @IR(counts = { IRNode.LOAD_VECTOR_Z, IRNode.VECTOR_SIZE_4, ">= 1",
+                   IRNode.VECTOR_LOAD_MASK, "= 0",
+                   IRNode.VECTOR_STORE_MASK, "= 0",
+                   IRNode.VECTOR_MASK_CAST, "= 0" },
+        applyIfCPUFeatureOr = { "sve", "true", "avx2", "true" },
+        applyIf = { "MaxVectorSize", "> 16" })
+    public static void testVectorMaskStoreIdentityFloat256() {
+        VectorMask mask_float_128 = VectorMask.fromArray(F128, mask_in, 0);
+
+        mask_float_128.cast(I128).intoArray(mask_out, 0);
+        verifyResult(F128.length());
+
+        mask_float_128.cast(S64).cast(L256).intoArray(mask_out, 0);
+        verifyResult(F128.length());
+
+        mask_float_128.cast(L256).cast(S64).cast(I128).intoArray(mask_out, 0);
+        verifyResult(F128.length());
+    }
+
+    @Test
+    @IR(counts = { IRNode.LOAD_VECTOR_Z, IRNode.VECTOR_SIZE_2, ">= 1",
+                   IRNode.VECTOR_LOAD_MASK, "= 0",
+                   IRNode.VECTOR_STORE_MASK, "= 0",
+                   IRNode.VECTOR_MASK_CAST, "= 0" },
+        applyIfCPUFeatureOr = { "asimd", "true" },
+        applyIf = { "MaxVectorSize", ">= 16" })
+    public static void testVectorMaskStoreIdentityDouble() {
+        VectorMask mask_double_128 = VectorMask.fromArray(D128, mask_in, 0);
+
+        mask_double_128.cast(L128).intoArray(mask_out, 0);
+        verifyResult(D128.length());
+
+        mask_double_128.cast(I64).cast(L128).intoArray(mask_out, 0);
+        verifyResult(D128.length());
+
+        mask_double_128.cast(I64).cast(L128).cast(I64).intoArray(mask_out, 0);
+        verifyResult(D128.length());
+    }
+
+    @Test
+    @IR(counts = { IRNode.LOAD_VECTOR_Z, IRNode.VECTOR_SIZE_4, ">= 1",
+                   IRNode.VECTOR_LOAD_MASK, "= 0",
+                   IRNode.VECTOR_STORE_MASK, "= 0",
+                   IRNode.VECTOR_MASK_CAST, "= 0" },
+        applyIfCPUFeatureOr = { "sve", "true", "avx2", "true" },
+        applyIf = { "MaxVectorSize", "> 16" })
+    public static void testVectorMaskStoreIdentityDouble256() {
+        VectorMask mask_double_256 = VectorMask.fromArray(D256, mask_in, 0);
+
+        mask_double_256.cast(F128).intoArray(mask_out, 0);
+        verifyResult(D256.length());
+
+        mask_double_256.cast(S64).cast(I128).intoArray(mask_out, 0);
+        verifyResult(D256.length());
+
+        mask_double_256.cast(I128).cast(S64).cast(L256).intoArray(mask_out, 0);
+        verifyResult(D256.length());
+    }
+
+    public static void main(String[] args) {
+        TestFramework testFramework = new TestFramework();
+        testFramework.setDefaultWarmup(10000)
+                     .addFlags("--add-modules=jdk.incubator.vector")
+                     .start();
+    }
+}
\ No newline at end of file
diff --git a/test/micro/org/openjdk/bench/jdk/incubator/vector/VectorStoreMaskBenchmark.java b/test/micro/org/openjdk/bench/jdk/incubator/vector/VectorStoreMaskBenchmark.java
new file mode 100644
index 00000000000..d4dc321ccba
--- /dev/null
+++ b/test/micro/org/openjdk/bench/jdk/incubator/vector/VectorStoreMaskBenchmark.java
@@ -0,0 +1,84 @@
+/*
+ *  Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
+ *  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.
+ *
+ */
+
+package org.openjdk.bench.jdk.incubator.vector;
+
+import jdk.incubator.vector.*;
+import java.util.concurrent.TimeUnit;
+import org.openjdk.jmh.annotations.*;
+
+@OutputTimeUnit(TimeUnit.MICROSECONDS)
+@State(Scope.Thread)
+@Warmup(iterations = 10, time = 1)
+@Measurement(iterations = 10, time = 1)
+@Fork(value = 1, jvmArgs = {"--add-modules=jdk.incubator.vector"})
+public class VectorStoreMaskBenchmark {
+    static final int LENGTH = 256;
+    static final boolean[] mask_arr_input = new boolean[LENGTH];
+    static final boolean[] mask_arr_output = new boolean[LENGTH];
+    static {
+        for (int i = 0; i < LENGTH; i++) {
+            mask_arr_input[i] = (i & 1) == 0;
+        }
+    }
+
+    @CompilerControl(CompilerControl.Mode.INLINE)
+    public  void maskLoadCastStoreKernel(VectorSpecies species_from, VectorSpecies species_to) {
+        for (int i = 0; i < LENGTH; i += species_from.length()) {
+            VectorMask mask_from = VectorMask.fromArray(species_from, mask_arr_input, i);
+            VectorMask mask_to = mask_from.cast(species_to);
+            mask_to.intoArray(mask_arr_output, i);
+        }
+    }
+
+    @Benchmark
+    public void microMaskLoadCastStoreByte64() {
+        maskLoadCastStoreKernel(ByteVector.SPECIES_64, ShortVector.SPECIES_128);
+    }
+
+    @Benchmark
+    public void microMaskLoadCastStoreShort64() {
+        maskLoadCastStoreKernel(ShortVector.SPECIES_64, IntVector.SPECIES_128);
+    }
+
+    @Benchmark
+    public void microMaskLoadCastStoreInt128() {
+        maskLoadCastStoreKernel(IntVector.SPECIES_128, ShortVector.SPECIES_64);
+    }
+
+    @Benchmark
+    public void microMaskLoadCastStoreLong128() {
+        maskLoadCastStoreKernel(LongVector.SPECIES_128, IntVector.SPECIES_64);
+    }
+
+    @Benchmark
+    public void microMaskLoadCastStoreFloat128() {
+        maskLoadCastStoreKernel(FloatVector.SPECIES_128, ShortVector.SPECIES_64);
+    }
+
+    @Benchmark
+    public void microMaskLoadCastStoreDouble128() {
+        maskLoadCastStoreKernel(DoubleVector.SPECIES_128, IntVector.SPECIES_64);
+    }
+}
\ No newline at end of file

From 23a08ee81e2cf9427cc569089f6d20ac210397a1 Mon Sep 17 00:00:00 2001
From: Andrew Dinn 
Date: Wed, 15 Apr 2026 09:19:40 +0000
Subject: [PATCH 056/108] 8382039: Add support do_arch_array_entry() template

Reviewed-by: kvn, asmehra
---
 src/hotspot/cpu/aarch64/aarch64_vector.ad     |  8 +-
 .../cpu/aarch64/c2_MacroAssembler_aarch64.cpp | 43 +++++++---
 .../cpu/aarch64/c2_MacroAssembler_aarch64.hpp |  1 +
 .../cpu/aarch64/stubDeclarations_aarch64.hpp  | 22 +++--
 .../cpu/aarch64/stubGenerator_aarch64.cpp     | 32 +++++--
 .../cpu/aarch64/stubRoutines_aarch64.cpp      |  6 +-
 .../cpu/aarch64/stubRoutines_aarch64.hpp      | 17 +++-
 src/hotspot/cpu/arm/stubDeclarations_arm.hpp  | 15 ++--
 src/hotspot/cpu/arm/stubRoutines_arm.cpp      |  2 +-
 src/hotspot/cpu/arm/stubRoutines_arm.hpp      | 14 ++-
 src/hotspot/cpu/ppc/stubDeclarations_ppc.hpp  | 15 ++--
 .../cpu/riscv/stubDeclarations_riscv.hpp      | 15 ++--
 src/hotspot/cpu/riscv/stubRoutines_riscv.cpp  |  6 +-
 src/hotspot/cpu/riscv/stubRoutines_riscv.hpp  | 14 ++-
 .../cpu/s390/stubDeclarations_s390.hpp        | 15 ++--
 src/hotspot/cpu/s390/stubRoutines_s390.cpp    |  6 +-
 src/hotspot/cpu/s390/stubRoutines_s390.hpp    | 14 ++-
 src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp | 29 +++++--
 src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp |  1 +
 src/hotspot/cpu/x86/stubDeclarations_x86.hpp  | 22 +++--
 src/hotspot/cpu/x86/stubGenerator_x86_64.cpp  | 32 +++++--
 src/hotspot/cpu/x86/stubGenerator_x86_64.hpp  |  2 +-
 src/hotspot/cpu/x86/stubRoutines_x86.cpp      |  6 +-
 src/hotspot/cpu/x86/stubRoutines_x86.hpp      | 16 +++-
 .../cpu/zero/stubDeclarations_zero.hpp        | 15 ++--
 .../share/runtime/stubDeclarations.hpp        | 85 +++++++++++++------
 src/hotspot/share/runtime/stubInfo.cpp        | 16 +++-
 src/hotspot/share/runtime/stubInfo.hpp        | 15 +++-
 28 files changed, 355 insertions(+), 129 deletions(-)

diff --git a/src/hotspot/cpu/aarch64/aarch64_vector.ad b/src/hotspot/cpu/aarch64/aarch64_vector.ad
index 19f03d97a72..30b0c9c799b 100644
--- a/src/hotspot/cpu/aarch64/aarch64_vector.ad
+++ b/src/hotspot/cpu/aarch64/aarch64_vector.ad
@@ -597,13 +597,9 @@ instruct vloadcon(vReg dst, immI0 src) %{
     BasicType bt = Matcher::vector_element_basic_type(this);
     if (UseSVE == 0) {
       uint length_in_bytes = Matcher::vector_length_in_bytes(this);
+      int entry_idx = __ vector_iota_entry_index(bt);
       assert(length_in_bytes <= 16, "must be");
-      // The iota indices are ordered by type B/S/I/L/F/D, and the offset between two types is 16.
-      int offset = exact_log2(type2aelembytes(bt)) << 4;
-      if (is_floating_point_type(bt)) {
-        offset += 32;
-      }
-      __ lea(rscratch1, ExternalAddress(StubRoutines::aarch64::vector_iota_indices() + offset));
+      __ lea(rscratch1, ExternalAddress(StubRoutines::aarch64::vector_iota_indices(entry_idx)));
       if (length_in_bytes == 16) {
         __ ldrq($dst$$FloatRegister, rscratch1);
       } else {
diff --git a/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp
index 7aab7d389e1..bba37a7a390 100644
--- a/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp
+++ b/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp
@@ -2414,17 +2414,17 @@ void C2_MacroAssembler::neon_rearrange_hsd(FloatRegister dst, FloatRegister src,
       break;
     case T_LONG:
     case T_DOUBLE:
-      // Load the iota indices for Long type. The indices are ordered by
-      // type B/S/I/L/F/D, and the offset between two types is 16; Hence
-      // the offset for L is 48.
-      lea(rscratch1,
-          ExternalAddress(StubRoutines::aarch64::vector_iota_indices() + 48));
-      ldrq(tmp, rscratch1);
-      // Check whether the input "shuffle" is the same with iota indices.
-      // Return "src" if true, otherwise swap the two elements of "src".
-      cm(EQ, dst, size2, shuffle, tmp);
-      ext(tmp, size1, src, src, 8);
-      bsl(dst, size1, src, tmp);
+      {
+        int idx = vector_iota_entry_index(T_LONG);
+        lea(rscratch1,
+            ExternalAddress(StubRoutines::aarch64::vector_iota_indices(idx)));
+        ldrq(tmp, rscratch1);
+        // Check whether the input "shuffle" is the same with iota indices.
+        // Return "src" if true, otherwise swap the two elements of "src".
+        cm(EQ, dst, size2, shuffle, tmp);
+        ext(tmp, size1, src, src, 8);
+        bsl(dst, size1, src, tmp);
+      }
       break;
     default:
       assert(false, "unsupported element type");
@@ -2896,3 +2896,24 @@ void C2_MacroAssembler::sve_cpy(FloatRegister dst, SIMD_RegVariant T,
   }
   Assembler::sve_cpy(dst, T, pg, imm8, isMerge);
 }
+
+int C2_MacroAssembler::vector_iota_entry_index(BasicType bt) {
+  // The vector iota entries array is ordered by type B/S/I/L/F/D, and
+  // the offset between two types is 16.
+  switch(bt) {
+  case T_BYTE:
+    return 0;
+  case T_SHORT:
+    return 1;
+  case T_INT:
+    return 2;
+  case T_LONG:
+    return 3;
+  case T_FLOAT:
+    return 4;
+  case T_DOUBLE:
+    return 5;
+  default:
+    ShouldNotReachHere();
+  }
+}
diff --git a/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.hpp b/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.hpp
index 5c05832afbe..5964bb60d4f 100644
--- a/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.hpp
+++ b/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.hpp
@@ -249,4 +249,5 @@
 
   void sve_cpy(FloatRegister dst, SIMD_RegVariant T, PRegister pg, int imm8,
                bool isMerge);
+  int vector_iota_entry_index(BasicType bt);
 #endif // CPU_AARCH64_C2_MACROASSEMBLER_AARCH64_HPP
diff --git a/src/hotspot/cpu/aarch64/stubDeclarations_aarch64.hpp b/src/hotspot/cpu/aarch64/stubDeclarations_aarch64.hpp
index 9dac6a39b82..d1f59e479db 100644
--- a/src/hotspot/cpu/aarch64/stubDeclarations_aarch64.hpp
+++ b/src/hotspot/cpu/aarch64/stubDeclarations_aarch64.hpp
@@ -29,32 +29,39 @@
 #define STUBGEN_PREUNIVERSE_BLOBS_ARCH_DO(do_stub,                      \
                                           do_arch_blob,                 \
                                           do_arch_entry,                \
-                                          do_arch_entry_init)           \
+                                          do_arch_entry_init,           \
+                                          do_arch_entry_array)          \
   do_arch_blob(preuniverse, 0)                                          \
 
 
 #define STUBGEN_INITIAL_BLOBS_ARCH_DO(do_stub,                          \
                                       do_arch_blob,                     \
                                       do_arch_entry,                    \
-                                      do_arch_entry_init)               \
+                                      do_arch_entry_init,               \
+                                      do_arch_entry_array)              \
   do_arch_blob(initial, 10000)                                          \
 
 
 #define STUBGEN_CONTINUATION_BLOBS_ARCH_DO(do_stub,                     \
                                            do_arch_blob,                \
                                            do_arch_entry,               \
-                                           do_arch_entry_init)          \
+                                           do_arch_entry_init,          \
+                                           do_arch_entry_array)         \
   do_arch_blob(continuation, 2000)                                      \
 
+// count needed for declaration of vector_iota_indices stub
+#define VECTOR_IOTA_COUNT 6
 
 #define STUBGEN_COMPILER_BLOBS_ARCH_DO(do_stub,                         \
                                        do_arch_blob,                    \
                                        do_arch_entry,                   \
-                                       do_arch_entry_init)              \
+                                       do_arch_entry_init,              \
+                                       do_arch_entry_array)             \
   do_arch_blob(compiler, 70000)                                         \
   do_stub(compiler, vector_iota_indices)                                \
-  do_arch_entry(aarch64, compiler, vector_iota_indices,                 \
-                vector_iota_indices, vector_iota_indices)               \
+  do_arch_entry_array(aarch64, compiler, vector_iota_indices,           \
+                      vector_iota_indices, vector_iota_indices,         \
+                      VECTOR_IOTA_COUNT)                                \
   do_stub(compiler, large_array_equals)                                 \
   do_arch_entry(aarch64, compiler, large_array_equals,                  \
                 large_array_equals, large_array_equals)                 \
@@ -115,7 +122,8 @@
 #define STUBGEN_FINAL_BLOBS_ARCH_DO(do_stub,                            \
                                     do_arch_blob,                       \
                                     do_arch_entry,                      \
-                                    do_arch_entry_init)                 \
+                                    do_arch_entry_init,                 \
+                                    do_arch_entry_array)                \
   do_arch_blob(final, 20000 ZGC_ONLY(+85000))                           \
   do_stub(final, copy_byte_f)                                           \
   do_arch_entry(aarch64, final, copy_byte_f, copy_byte_f,               \
diff --git a/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp b/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp
index 32fd8afb268..5d9b2f1d826 100644
--- a/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp
+++ b/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp
@@ -819,12 +819,19 @@ class StubGenerator: public StubCodeGenerator {
   }
 
   // Generate indices for iota vector.
-  address generate_iota_indices(StubId stub_id) {
+  void generate_iota_indices(StubId stub_id) {
+    GrowableArray
entries; int entry_count = StubInfo::entry_count(stub_id); - assert(entry_count == 1, "sanity check"); - address start = load_archive_data(stub_id); + assert(entry_count == VECTOR_IOTA_COUNT, "sanity check"); + address start = load_archive_data(stub_id, &entries); if (start != nullptr) { - return start; + assert(entries.length() == entry_count - 1, + "unexpected entries count %d", entries.length()); + StubRoutines::aarch64::_vector_iota_indices[0] = start; + for (int i = 1; i < VECTOR_IOTA_COUNT; i++) { + StubRoutines::aarch64::_vector_iota_indices[i] = entries.at(i - 1); + } + return; } __ align(CodeEntryAlignment); StubCodeMark mark(this, stub_id); @@ -832,26 +839,37 @@ class StubGenerator: public StubCodeGenerator { // B __ emit_data64(0x0706050403020100, relocInfo::none); __ emit_data64(0x0F0E0D0C0B0A0908, relocInfo::none); + entries.append(__ pc()); // H __ emit_data64(0x0003000200010000, relocInfo::none); __ emit_data64(0x0007000600050004, relocInfo::none); + entries.append(__ pc()); // S __ emit_data64(0x0000000100000000, relocInfo::none); __ emit_data64(0x0000000300000002, relocInfo::none); + entries.append(__ pc()); // D __ emit_data64(0x0000000000000000, relocInfo::none); __ emit_data64(0x0000000000000001, relocInfo::none); + entries.append(__ pc()); // S - FP __ emit_data64(0x3F80000000000000, relocInfo::none); // 0.0f, 1.0f __ emit_data64(0x4040000040000000, relocInfo::none); // 2.0f, 3.0f + entries.append(__ pc()); // D - FP __ emit_data64(0x0000000000000000, relocInfo::none); // 0.0d __ emit_data64(0x3FF0000000000000, relocInfo::none); // 1.0d // record the stub entry and end - store_archive_data(stub_id, start, __ pc()); + store_archive_data(stub_id, start, __ pc(), &entries); - return start; + // install the entry addresses in the entry array + assert(entries.length() == entry_count - 1, + "unexpected entries count %d", entries.length()); + StubRoutines::aarch64::_vector_iota_indices[0] = start; + for (int i = 1; i < VECTOR_IOTA_COUNT; i++) { + StubRoutines::aarch64::_vector_iota_indices[i] = entries.at(i - 1); + } } // The inner part of zero_words(). This is the bulk operation, @@ -12621,7 +12639,7 @@ class StubGenerator: public StubCodeGenerator { #if COMPILER2_OR_JVMCI if (UseSVE == 0) { - StubRoutines::aarch64::_vector_iota_indices = generate_iota_indices(StubId::stubgen_vector_iota_indices_id); + generate_iota_indices(StubId::stubgen_vector_iota_indices_id); } // array equals stub for large arrays. diff --git a/src/hotspot/cpu/aarch64/stubRoutines_aarch64.cpp b/src/hotspot/cpu/aarch64/stubRoutines_aarch64.cpp index 35ec22b0897..aaf31d4f911 100644 --- a/src/hotspot/cpu/aarch64/stubRoutines_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/stubRoutines_aarch64.cpp @@ -41,8 +41,12 @@ static void empty_spin_wait() { } #define DEFINE_ARCH_ENTRY_INIT(arch, blob_name, stub_name, field_name, getter_name, init_function) \ address StubRoutines:: arch :: STUB_FIELD_NAME(field_name) = CAST_FROM_FN_PTR(address, init_function); -STUBGEN_ARCH_ENTRIES_DO(DEFINE_ARCH_ENTRY, DEFINE_ARCH_ENTRY_INIT) +#define DEFINE_ARCH_ENTRY_ARRAY(arch, blob_name, stub_name, field_name, getter_name, count) \ + address StubRoutines:: arch :: STUB_FIELD_NAME(field_name) [count]; +STUBGEN_ARCH_ENTRIES_DO(DEFINE_ARCH_ENTRY, DEFINE_ARCH_ENTRY_INIT, DEFINE_ARCH_ENTRY_ARRAY) + +#undef DEFINE_ARCH_ENTRY_ARARAY #undef DEFINE_ARCH_ENTRY_INIT #undef DEFINE_ARCH_ENTRY diff --git a/src/hotspot/cpu/aarch64/stubRoutines_aarch64.hpp b/src/hotspot/cpu/aarch64/stubRoutines_aarch64.hpp index f77192a3741..6067408ef13 100644 --- a/src/hotspot/cpu/aarch64/stubRoutines_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/stubRoutines_aarch64.hpp @@ -60,9 +60,13 @@ class aarch64 { #define DECLARE_ARCH_ENTRY_INIT(arch, blob_name, stub_name, field_name, getter_name, init_function) \ DECLARE_ARCH_ENTRY(arch, blob_name, stub_name, field_name, getter_name) -private: - STUBGEN_ARCH_ENTRIES_DO(DECLARE_ARCH_ENTRY, DECLARE_ARCH_ENTRY_INIT) +#define DECLARE_ARCH_ENTRY_ARRAY(arch, blob_name, stub_name, field_name, getter_name, count) \ + static address STUB_FIELD_NAME(field_name) [count]; +private: + STUBGEN_ARCH_ENTRIES_DO(DECLARE_ARCH_ENTRY, DECLARE_ARCH_ENTRY_INIT, DECLARE_ARCH_ENTRY_ARRAY) + +#undef DECLARE_ARCH_ENTRY_ARRAY #undef DECLARE_ARCH_ENTRY_INIT #undef DECLARE_ARCH_ENTRY @@ -78,8 +82,15 @@ private: #define DEFINE_ARCH_ENTRY_GETTER_INIT(arch, blob_name, stub_name, field_name, getter_name, init_function) \ DEFINE_ARCH_ENTRY_GETTER(arch, blob_name, stub_name, field_name, getter_name) - STUBGEN_ARCH_ENTRIES_DO(DEFINE_ARCH_ENTRY_GETTER, DEFINE_ARCH_ENTRY_GETTER_INIT) +#define DEFINE_ARCH_ENTRY_GETTER_ARRAY(arch, blob_name, stub_name, field_name, getter_name, count) \ + static address getter_name(int idx) { \ + assert(0 <= idx && idx < count, "entry array index out of range"); \ + return STUB_FIELD_NAME(field_name) [idx]; \ + } + STUBGEN_ARCH_ENTRIES_DO(DEFINE_ARCH_ENTRY_GETTER, DEFINE_ARCH_ENTRY_GETTER_INIT, DEFINE_ARCH_ENTRY_GETTER_ARRAY) + +#undef DEFINE_ARCH_ENTRY_GETTER_ARRAY #undef DEFINE_ARCH_ENTRY_GETTER_INIT #undef DEFINE_ARCH_ENTRY_GETTER diff --git a/src/hotspot/cpu/arm/stubDeclarations_arm.hpp b/src/hotspot/cpu/arm/stubDeclarations_arm.hpp index 5f768a205a5..5fb0d4e901f 100644 --- a/src/hotspot/cpu/arm/stubDeclarations_arm.hpp +++ b/src/hotspot/cpu/arm/stubDeclarations_arm.hpp @@ -29,7 +29,8 @@ #define STUBGEN_PREUNIVERSE_BLOBS_ARCH_DO(do_stub, \ do_arch_blob, \ do_arch_entry, \ - do_arch_entry_init) \ + do_arch_entry_init, \ + do_arch_entry_array) \ do_arch_blob(preuniverse, 500) \ do_stub(preuniverse, atomic_load_long) \ do_arch_entry(Arm, preuniverse, atomic_load_long, \ @@ -42,7 +43,8 @@ #define STUBGEN_INITIAL_BLOBS_ARCH_DO(do_stub, \ do_arch_blob, \ do_arch_entry, \ - do_arch_entry_init) \ + do_arch_entry_init, \ + do_arch_entry_array) \ do_arch_blob(initial, 9000) \ do_stub(initial, idiv_irem) \ do_arch_entry(Arm, initial, idiv_irem, \ @@ -51,14 +53,16 @@ #define STUBGEN_CONTINUATION_BLOBS_ARCH_DO(do_stub, \ do_arch_blob, \ do_arch_entry, \ - do_arch_entry_init) \ + do_arch_entry_init, \ + do_arch_entry_array) \ do_arch_blob(continuation, 2000) \ #define STUBGEN_COMPILER_BLOBS_ARCH_DO(do_stub, \ do_arch_blob, \ do_arch_entry, \ - do_arch_entry_init) \ + do_arch_entry_init, \ + do_arch_entry_array) \ do_arch_blob(compiler, 22000) \ do_stub(compiler, partial_subtype_check) \ do_arch_entry(Arm, compiler, partial_subtype_check, \ @@ -68,7 +72,8 @@ #define STUBGEN_FINAL_BLOBS_ARCH_DO(do_stub, \ do_arch_blob, \ do_arch_entry, \ - do_arch_entry_init) \ + do_arch_entry_init, \ + do_arch_entry_array) \ do_arch_blob(final, 22000) \ diff --git a/src/hotspot/cpu/arm/stubRoutines_arm.cpp b/src/hotspot/cpu/arm/stubRoutines_arm.cpp index 3ed747ea11a..38a9b298562 100644 --- a/src/hotspot/cpu/arm/stubRoutines_arm.cpp +++ b/src/hotspot/cpu/arm/stubRoutines_arm.cpp @@ -32,7 +32,7 @@ #define DEFINE_ARCH_ENTRY_INIT(arch, blob_name, stub_name, field_name, getter_name, init_function) \ address StubRoutines:: arch :: STUB_FIELD_NAME(field_name) = CAST_FROM_FN_PTR(address, init_function); -STUBGEN_ARCH_ENTRIES_DO(DEFINE_ARCH_ENTRY, DEFINE_ARCH_ENTRY_INIT) +STUBGEN_ARCH_ENTRIES_DO(DEFINE_ARCH_ENTRY, DEFINE_ARCH_ENTRY_INIT, DEFINE_ARCH_ENTRY_ARRAY) #undef DEFINE_ARCH_ENTRY_INIT #undef DEFINE_ARCH_ENTRY diff --git a/src/hotspot/cpu/arm/stubRoutines_arm.hpp b/src/hotspot/cpu/arm/stubRoutines_arm.hpp index 45ab10d14f9..29d96d0e653 100644 --- a/src/hotspot/cpu/arm/stubRoutines_arm.hpp +++ b/src/hotspot/cpu/arm/stubRoutines_arm.hpp @@ -55,9 +55,13 @@ class Arm { #define DECLARE_ARCH_ENTRY_INIT(arch, blob_name, stub_name, field_name, getter_name, init_function) \ DECLARE_ARCH_ENTRY(arch, blob_name, stub_name, field_name, getter_name) -private: - STUBGEN_ARCH_ENTRIES_DO(DECLARE_ARCH_ENTRY, DECLARE_ARCH_ENTRY_INIT) +#define DECLARE_ARCH_ENTRY_ARRAY(arch, blob_name, stub_name, field_name, getter_name, count) \ + static address STUB_FIELD_NAME(field_name) [count] ; +private: + STUBGEN_ARCH_ENTRIES_DO(DECLARE_ARCH_ENTRY, DECLARE_ARCH_ENTRY_INIT, DECLARE_ARCH_ENTRY_ARRAY) + +#undef DECLARE_ARCH_ENTRY_ARRAY #undef DECLARE_ARCH_ENTRY_INIT #undef DECLARE_ARCH_ENTRY @@ -71,8 +75,12 @@ public: #define DEFINE_ARCH_ENTRY_GETTER_INIT(arch, blob_name, stub_name, field_name, getter_name, init_function) \ DEFINE_ARCH_ENTRY_GETTER(arch, blob_name, stub_name, field_name, getter_name) - STUBGEN_ARCH_ENTRIES_DO(DEFINE_ARCH_ENTRY_GETTER, DEFINE_ARCH_ENTRY_GETTER_INIT) +#define DEFINE_ARCH_ENTRY_GETTER_ARRAY(arch, blob_name, stub_name, field_name, getter_name, count) \ + static address getter_name(int idx) { return STUB_FIELD_NAME(field_name) [idx] ; } + STUBGEN_ARCH_ENTRIES_DO(DEFINE_ARCH_ENTRY_GETTER, DEFINE_ARCH_ENTRY_GETTER_INIT, DEFINE_ARCH_ENTRY_GETTER_ARRAY) + +#undef DEFINE_ARCH_ENTRY_GETTER_ARRAY #undef DEFINE_ARCH_ENTRY_GETTER_INIT #undef DEFINE_ARCH_ENTRY_GETTER diff --git a/src/hotspot/cpu/ppc/stubDeclarations_ppc.hpp b/src/hotspot/cpu/ppc/stubDeclarations_ppc.hpp index be51afe42a4..41b8b71486d 100644 --- a/src/hotspot/cpu/ppc/stubDeclarations_ppc.hpp +++ b/src/hotspot/cpu/ppc/stubDeclarations_ppc.hpp @@ -29,35 +29,40 @@ #define STUBGEN_PREUNIVERSE_BLOBS_ARCH_DO(do_stub, \ do_arch_blob, \ do_arch_entry, \ - do_arch_entry_init) \ + do_arch_entry_init, \ + do_arch_entry_array) \ do_arch_blob(preuniverse, 0) \ #define STUBGEN_INITIAL_BLOBS_ARCH_DO(do_stub, \ do_arch_blob, \ do_arch_entry, \ - do_arch_entry_init) \ + do_arch_entry_init, \ + do_arch_entry_array) \ do_arch_blob(initial, 20000) \ #define STUBGEN_CONTINUATION_BLOBS_ARCH_DO(do_stub, \ do_arch_blob, \ do_arch_entry, \ - do_arch_entry_init) \ + do_arch_entry_init, \ + do_arch_entry_array) \ do_arch_blob(continuation, 2000) \ #define STUBGEN_COMPILER_BLOBS_ARCH_DO(do_stub, \ do_arch_blob, \ do_arch_entry, \ - do_arch_entry_init) \ + do_arch_entry_init, \ + do_arch_entry_array) \ do_arch_blob(compiler, 24000) \ #define STUBGEN_FINAL_BLOBS_ARCH_DO(do_stub, \ do_arch_blob, \ do_arch_entry, \ - do_arch_entry_init) \ + do_arch_entry_init, \ + do_arch_entry_array) \ do_arch_blob(final, 24000) \ diff --git a/src/hotspot/cpu/riscv/stubDeclarations_riscv.hpp b/src/hotspot/cpu/riscv/stubDeclarations_riscv.hpp index f977d759d20..890e354fd27 100644 --- a/src/hotspot/cpu/riscv/stubDeclarations_riscv.hpp +++ b/src/hotspot/cpu/riscv/stubDeclarations_riscv.hpp @@ -29,28 +29,32 @@ #define STUBGEN_PREUNIVERSE_BLOBS_ARCH_DO(do_stub, \ do_arch_blob, \ do_arch_entry, \ - do_arch_entry_init) \ + do_arch_entry_init, \ + do_arch_entry_array) \ do_arch_blob(preuniverse, 0) \ #define STUBGEN_INITIAL_BLOBS_ARCH_DO(do_stub, \ do_arch_blob, \ do_arch_entry, \ - do_arch_entry_init) \ + do_arch_entry_init, \ + do_arch_entry_array) \ do_arch_blob(initial, 10000) \ #define STUBGEN_CONTINUATION_BLOBS_ARCH_DO(do_stub, \ do_arch_blob, \ do_arch_entry, \ - do_arch_entry_init) \ + do_arch_entry_init, \ + do_arch_entry_array) \ do_arch_blob(continuation, 2000) \ #define STUBGEN_COMPILER_BLOBS_ARCH_DO(do_stub, \ do_arch_blob, \ do_arch_entry, \ - do_arch_entry_init) \ + do_arch_entry_init, \ + do_arch_entry_array) \ do_arch_blob(compiler, 45000) \ do_stub(compiler, compare_long_string_LL) \ do_arch_entry(riscv, compiler, compare_long_string_LL, \ @@ -81,7 +85,8 @@ #define STUBGEN_FINAL_BLOBS_ARCH_DO(do_stub, \ do_arch_blob, \ do_arch_entry, \ - do_arch_entry_init) \ + do_arch_entry_init, \ + do_arch_entry_array) \ do_arch_blob(final, 20000 ZGC_ONLY(+10000)) \ do_stub(final, copy_byte_f) \ do_arch_entry(riscv, final, copy_byte_f, copy_byte_f, \ diff --git a/src/hotspot/cpu/riscv/stubRoutines_riscv.cpp b/src/hotspot/cpu/riscv/stubRoutines_riscv.cpp index 51e31aa3672..b7f69eff9fa 100644 --- a/src/hotspot/cpu/riscv/stubRoutines_riscv.cpp +++ b/src/hotspot/cpu/riscv/stubRoutines_riscv.cpp @@ -42,8 +42,12 @@ #define DEFINE_ARCH_ENTRY_INIT(arch, blob_name, stub_name, field_name, getter_name, init_function) \ address StubRoutines:: arch :: STUB_FIELD_NAME(field_name) = CAST_FROM_FN_PTR(address, init_function); -STUBGEN_ARCH_ENTRIES_DO(DEFINE_ARCH_ENTRY, DEFINE_ARCH_ENTRY_INIT) +#define DEFINE_ARCH_ENTRY_ARRAY(arch, blob_name, stub_name, field_name, getter_name, count) \ + address StubRoutines:: arch :: STUB_FIELD_NAME(field_name) [count] ; +STUBGEN_ARCH_ENTRIES_DO(DEFINE_ARCH_ENTRY, DEFINE_ARCH_ENTRY_INIT, DEFINE_ARCH_ENTRY_ARRAY) + +#undef DEFINE_ARCH_ENTRY_ARRAY #undef DEFINE_ARCH_ENTRY_INIT #undef DEFINE_ARCH_ENTRY diff --git a/src/hotspot/cpu/riscv/stubRoutines_riscv.hpp b/src/hotspot/cpu/riscv/stubRoutines_riscv.hpp index 2c4e7210413..ec67a338052 100644 --- a/src/hotspot/cpu/riscv/stubRoutines_riscv.hpp +++ b/src/hotspot/cpu/riscv/stubRoutines_riscv.hpp @@ -61,9 +61,13 @@ class riscv { #define DECLARE_ARCH_ENTRY_INIT(arch, blob_name, stub_name, field_name, getter_name, init_function) \ DECLARE_ARCH_ENTRY(arch, blob_name, stub_name, field_name, getter_name) -private: - STUBGEN_ARCH_ENTRIES_DO(DECLARE_ARCH_ENTRY, DECLARE_ARCH_ENTRY_INIT) +#define DECLARE_ARCH_ENTRY_ARRAY(arch, blob_name, stub_name, field_name, getter_name, count) \ + static address STUB_FIELD_NAME(field_name) [count] ; +private: + STUBGEN_ARCH_ENTRIES_DO(DECLARE_ARCH_ENTRY, DECLARE_ARCH_ENTRY_INIT, DECLARE_ARCH_ENTRY_ARRAY) + +#undef DECLARE_ARCH_ENTRY_ARRAY #undef DECLARE_ARCH_ENTRY_INIT #undef DECLARE_ARCH_ENTRY @@ -79,8 +83,12 @@ private: #define DEFINE_ARCH_ENTRY_GETTER_INIT(arch, blob_name, stub_name, field_name, getter_name, init_function) \ DEFINE_ARCH_ENTRY_GETTER(arch, blob_name, stub_name, field_name, getter_name) - STUBGEN_ARCH_ENTRIES_DO(DEFINE_ARCH_ENTRY_GETTER, DEFINE_ARCH_ENTRY_GETTER_INIT) +#define DEFINE_ARCH_ENTRY_GETTER_ARRAY(arch, blob_name, stub_name, field_name, getter_name, count) \ + static address getter_name(int idx) { return STUB_FIELD_NAME(field_name) [idx] ; } + STUBGEN_ARCH_ENTRIES_DO(DEFINE_ARCH_ENTRY_GETTER, DEFINE_ARCH_ENTRY_GETTER_INIT, DEFINE_ARCH_ENTRY_GETTER_ARRAY) + +#undef DEFINE_ARCH_ENTRY_GETTER_ARRAY #undef DEFINE_ARCH_ENTRY_GETTER_INIT #undef DEFINE_ARCH_ENTRY_GETTER diff --git a/src/hotspot/cpu/s390/stubDeclarations_s390.hpp b/src/hotspot/cpu/s390/stubDeclarations_s390.hpp index c3ad3cefeb9..d0e26beedab 100644 --- a/src/hotspot/cpu/s390/stubDeclarations_s390.hpp +++ b/src/hotspot/cpu/s390/stubDeclarations_s390.hpp @@ -29,28 +29,32 @@ #define STUBGEN_PREUNIVERSE_BLOBS_ARCH_DO(do_stub, \ do_arch_blob, \ do_arch_entry, \ - do_arch_entry_init) \ + do_arch_entry_init, \ + do_arch_entry_array) \ do_arch_blob(preuniverse, 0) \ #define STUBGEN_INITIAL_BLOBS_ARCH_DO(do_stub, \ do_arch_blob, \ do_arch_entry, \ - do_arch_entry_init) \ + do_arch_entry_init, \ + do_arch_entry_array) \ do_arch_blob(initial, 20000) \ #define STUBGEN_CONTINUATION_BLOBS_ARCH_DO(do_stub, \ do_arch_blob, \ do_arch_entry, \ - do_arch_entry_init) \ + do_arch_entry_init, \ + do_arch_entry_array) \ do_arch_blob(continuation, 2000) \ #define STUBGEN_COMPILER_BLOBS_ARCH_DO(do_stub, \ do_arch_blob, \ do_arch_entry, \ - do_arch_entry_init) \ + do_arch_entry_init, \ + do_arch_entry_array) \ do_arch_blob(compiler, 20000 ) \ do_stub(compiler, partial_subtype_check) \ do_arch_entry(zarch, compiler, partial_subtype_check, \ @@ -60,7 +64,8 @@ #define STUBGEN_FINAL_BLOBS_ARCH_DO(do_stub, \ do_arch_blob, \ do_arch_entry, \ - do_arch_entry_init) \ + do_arch_entry_init, \ + do_arch_entry_array) \ do_arch_blob(final, 20000) \ diff --git a/src/hotspot/cpu/s390/stubRoutines_s390.cpp b/src/hotspot/cpu/s390/stubRoutines_s390.cpp index 3db4995338d..eda0ebfdecc 100644 --- a/src/hotspot/cpu/s390/stubRoutines_s390.cpp +++ b/src/hotspot/cpu/s390/stubRoutines_s390.cpp @@ -40,8 +40,12 @@ #define DEFINE_ARCH_ENTRY_INIT(arch, blob_name, stub_name, field_name, getter_name, init_function) \ address StubRoutines:: arch :: STUB_FIELD_NAME(field_name) = CAST_FROM_FN_PTR(address, init_function); -STUBGEN_ARCH_ENTRIES_DO(DEFINE_ARCH_ENTRY, DEFINE_ARCH_ENTRY_INIT) +#define DEFINE_ARCH_ENTRY_ARRAY(arch, blob_name, stub_name, field_name, getter_name, count) \ + address StubRoutines:: arch :: STUB_FIELD_NAME(field_name) [idx] ; +STUBGEN_ARCH_ENTRIES_DO(DEFINE_ARCH_ENTRY, DEFINE_ARCH_ENTRY_INIT, DEFINE_ARCH_ENTRY_ARRAY) + +#undef DEFINE_ARCH_ENTRY_ARRAY #undef DEFINE_ARCH_ENTRY_INIT #undef DEFINE_ARCH_ENTRY diff --git a/src/hotspot/cpu/s390/stubRoutines_s390.hpp b/src/hotspot/cpu/s390/stubRoutines_s390.hpp index 0a07efae46c..e575115b731 100644 --- a/src/hotspot/cpu/s390/stubRoutines_s390.hpp +++ b/src/hotspot/cpu/s390/stubRoutines_s390.hpp @@ -81,9 +81,13 @@ class zarch { #define DECLARE_ARCH_ENTRY_INIT(arch, blob_name, stub_name, field_name, getter_name, init_function) \ DECLARE_ARCH_ENTRY(arch, blob_name, stub_name, field_name, getter_name) -private: - STUBGEN_ARCH_ENTRIES_DO(DECLARE_ARCH_ENTRY, DECLARE_ARCH_ENTRY_INIT) +#define DECLARE_ARCH_ENTRY_ARRAY(arch, blob_name, stub_name, field_name, getter_name, count) \ + static address STUB_FIELD_NAME(field_name) [count] ; +private: + STUBGEN_ARCH_ENTRIES_DO(DECLARE_ARCH_ENTRY, DECLARE_ARCH_ENTRY_INIT, DECLARE_ARCH_ENTRY_ARRAY) + +#undef DECLARE_ARCH_ENTRY_ARRAY #undef DECLARE_ARCH_ENTRY_INIT #undef DECLARE_ARCH_ENTRY @@ -108,8 +112,12 @@ private: #define DEFINE_ARCH_ENTRY_GETTER_INIT(arch, blob_name, stub_name, field_name, getter_name, init_function) \ DEFINE_ARCH_ENTRY_GETTER(arch, blob_name, stub_name, field_name, getter_name) - STUBGEN_ARCH_ENTRIES_DO(DEFINE_ARCH_ENTRY_GETTER, DEFINE_ARCH_ENTRY_GETTER_INIT) +#define DEFINE_ARCH_ENTRY_GETTER_ARRAY(arch, blob_name, stub_name, field_name, getter_name, count) \ + static address getter_name(int idx) { return STUB_FIELD_NAME(field_name) [idx] ; } + STUBGEN_ARCH_ENTRIES_DO(DEFINE_ARCH_ENTRY_GETTER, DEFINE_ARCH_ENTRY_GETTER_INIT, DEFINE_ARCH_ENTRY_GETTER_ARRAY) + +#undef DEFINE_ARCH_ENTRY_GETTER_ARRAY #undef DEFINE_ARCH_ENTRY_GETTER_INIT #undef DEFINE_ARCH_ENTRY_GETTER diff --git a/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp b/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp index f36c816dd5e..b4d8aa10de2 100644 --- a/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp +++ b/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp @@ -1706,12 +1706,8 @@ void C2_MacroAssembler::load_constant_vector(BasicType bt, XMMRegister dst, Inte } void C2_MacroAssembler::load_iota_indices(XMMRegister dst, int vlen_in_bytes, BasicType bt) { - // The iota indices are ordered by type B/S/I/L/F/D, and the offset between two types is 64. - int offset = exact_log2(type2aelembytes(bt)) << 6; - if (is_floating_point_type(bt)) { - offset += 128; - } - ExternalAddress addr(StubRoutines::x86::vector_iota_indices() + offset); + int entry_idx = vector_iota_entry_index(bt); + ExternalAddress addr(StubRoutines::x86::vector_iota_indices(entry_idx)); load_vector(T_BYTE, dst, addr, vlen_in_bytes); } @@ -7164,3 +7160,24 @@ void C2_MacroAssembler::vminmax_fp16_avx10_2(int opcode, XMMRegister dst, XMMReg evminmaxph(dst, ktmp, src1, src2, true, AVX10_2_MINMAX_MIN_COMPARE_SIGN, vlen_enc); } } + +int C2_MacroAssembler::vector_iota_entry_index(BasicType bt) { + // The vector iota entries array is ordered by type B/S/I/L/F/D, and + // the offset between two types is 16. + switch(bt) { + case T_BYTE: + return 0; + case T_SHORT: + return 1; + case T_INT: + return 2; + case T_LONG: + return 3; + case T_FLOAT: + return 4; + case T_DOUBLE: + return 5; + default: + ShouldNotReachHere(); + } +} diff --git a/src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp b/src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp index 4e77f8a5f6f..9b229ad7221 100644 --- a/src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp +++ b/src/hotspot/cpu/x86/c2_MacroAssembler_x86.hpp @@ -596,4 +596,5 @@ public: void reconstruct_frame_pointer(Register rtmp); + int vector_iota_entry_index(BasicType bt); #endif // CPU_X86_C2_MACROASSEMBLER_X86_HPP diff --git a/src/hotspot/cpu/x86/stubDeclarations_x86.hpp b/src/hotspot/cpu/x86/stubDeclarations_x86.hpp index 07a1ab622ed..24886deb3c5 100644 --- a/src/hotspot/cpu/x86/stubDeclarations_x86.hpp +++ b/src/hotspot/cpu/x86/stubDeclarations_x86.hpp @@ -29,14 +29,16 @@ #define STUBGEN_PREUNIVERSE_BLOBS_ARCH_DO(do_stub, \ do_arch_blob, \ do_arch_entry, \ - do_arch_entry_init) \ + do_arch_entry_init, \ + do_arch_entry_array) \ do_arch_blob(preuniverse, 500) \ #define STUBGEN_INITIAL_BLOBS_ARCH_DO(do_stub, \ do_arch_blob, \ do_arch_entry, \ - do_arch_entry_init) \ + do_arch_entry_init, \ + do_arch_entry_array) \ do_arch_blob(initial, PRODUCT_ONLY(20000) NOT_PRODUCT(21000) WINDOWS_ONLY(+1000)) \ do_stub(initial, verify_mxcsr) \ do_arch_entry(x86, initial, verify_mxcsr, verify_mxcsr_entry, \ @@ -65,14 +67,18 @@ #define STUBGEN_CONTINUATION_BLOBS_ARCH_DO(do_stub, \ do_arch_blob, \ do_arch_entry, \ - do_arch_entry_init) \ + do_arch_entry_init, \ + do_arch_entry_array) \ do_arch_blob(continuation, 3000) \ +// count needed for declaration of vector_iota_indices stub +#define VECTOR_IOTA_COUNT 6 #define STUBGEN_COMPILER_BLOBS_ARCH_DO(do_stub, \ do_arch_blob, \ do_arch_entry, \ - do_arch_entry_init) \ + do_arch_entry_init, \ + do_arch_entry_array) \ do_arch_blob(compiler, 120000 WINDOWS_ONLY(+2000)) \ do_stub(compiler, vector_float_sign_mask) \ do_arch_entry(x86, compiler, vector_float_sign_mask, \ @@ -126,8 +132,9 @@ do_arch_entry(x86, compiler, vector_long_sign_mask, \ vector_long_sign_mask, vector_long_sign_mask) \ do_stub(compiler, vector_iota_indices) \ - do_arch_entry(x86, compiler, vector_iota_indices, \ - vector_iota_indices, vector_iota_indices) \ + do_arch_entry_array(x86, compiler, vector_iota_indices, \ + vector_iota_indices, vector_iota_indices, \ + VECTOR_IOTA_COUNT) \ do_stub(compiler, vector_count_leading_zeros_lut) \ do_arch_entry(x86, compiler, vector_count_leading_zeros_lut, \ vector_count_leading_zeros_lut, \ @@ -250,7 +257,8 @@ #define STUBGEN_FINAL_BLOBS_ARCH_DO(do_stub, \ do_arch_blob, \ do_arch_entry, \ - do_arch_entry_init) \ + do_arch_entry_init, \ + do_arch_entry_array) \ do_arch_blob(final, 33000 \ WINDOWS_ONLY(+22000) ZGC_ONLY(+20000)) \ diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64.cpp index 40be816fbf0..993d1964034 100644 --- a/src/hotspot/cpu/x86/stubGenerator_x86_64.cpp +++ b/src/hotspot/cpu/x86/stubGenerator_x86_64.cpp @@ -893,13 +893,20 @@ address StubGenerator::generate_popcount_avx_lut() { return start; } -address StubGenerator::generate_iota_indices() { +void StubGenerator::generate_iota_indices() { StubId stub_id = StubId::stubgen_vector_iota_indices_id; + GrowableArray
entries; int entry_count = StubInfo::entry_count(stub_id); - assert(entry_count == 1, "sanity check"); - address start = load_archive_data(stub_id); + assert(entry_count == VECTOR_IOTA_COUNT, "sanity check"); + address start = load_archive_data(stub_id, &entries); if (start != nullptr) { - return start; + assert(entries.length() == VECTOR_IOTA_COUNT - 1, + "unexpected extra entry count %d", entries.length()); + StubRoutines::x86::_vector_iota_indices[0] = start; + for (int i = 1; i < VECTOR_IOTA_COUNT; i++) { + StubRoutines::x86::_vector_iota_indices[i] = entries.at(i - 1); + } + return; } __ align(CodeEntryAlignment); StubCodeMark mark(this, stub_id); @@ -913,6 +920,7 @@ address StubGenerator::generate_iota_indices() { __ emit_data64(0x2F2E2D2C2B2A2928, relocInfo::none); __ emit_data64(0x3736353433323130, relocInfo::none); __ emit_data64(0x3F3E3D3C3B3A3938, relocInfo::none); + entries.append(__ pc()); // W __ emit_data64(0x0003000200010000, relocInfo::none); __ emit_data64(0x0007000600050004, relocInfo::none); @@ -922,6 +930,7 @@ address StubGenerator::generate_iota_indices() { __ emit_data64(0x0017001600150014, relocInfo::none); __ emit_data64(0x001B001A00190018, relocInfo::none); __ emit_data64(0x001F001E001D001C, relocInfo::none); + entries.append(__ pc()); // D __ emit_data64(0x0000000100000000, relocInfo::none); __ emit_data64(0x0000000300000002, relocInfo::none); @@ -931,6 +940,7 @@ address StubGenerator::generate_iota_indices() { __ emit_data64(0x0000000B0000000A, relocInfo::none); __ emit_data64(0x0000000D0000000C, relocInfo::none); __ emit_data64(0x0000000F0000000E, relocInfo::none); + entries.append(__ pc()); // Q __ emit_data64(0x0000000000000000, relocInfo::none); __ emit_data64(0x0000000000000001, relocInfo::none); @@ -940,6 +950,7 @@ address StubGenerator::generate_iota_indices() { __ emit_data64(0x0000000000000005, relocInfo::none); __ emit_data64(0x0000000000000006, relocInfo::none); __ emit_data64(0x0000000000000007, relocInfo::none); + entries.append(__ pc()); // D - FP __ emit_data64(0x3F80000000000000, relocInfo::none); // 0.0f, 1.0f __ emit_data64(0x4040000040000000, relocInfo::none); // 2.0f, 3.0f @@ -949,6 +960,7 @@ address StubGenerator::generate_iota_indices() { __ emit_data64(0x4130000041200000, relocInfo::none); // 10.0f, 11.0f __ emit_data64(0x4150000041400000, relocInfo::none); // 12.0f, 13.0f __ emit_data64(0x4170000041600000, relocInfo::none); // 14.0f, 15.0f + entries.append(__ pc()); // Q - FP __ emit_data64(0x0000000000000000, relocInfo::none); // 0.0d __ emit_data64(0x3FF0000000000000, relocInfo::none); // 1.0d @@ -960,9 +972,15 @@ address StubGenerator::generate_iota_indices() { __ emit_data64(0x401c000000000000, relocInfo::none); // 7.0d // record the stub entry and end - store_archive_data(stub_id, start, __ pc()); + store_archive_data(stub_id, start, __ pc(), &entries); - return start; + // install the entry addresses in the entry array + assert(entries.length() == entry_count - 1, + "unexpected entries count %d", entries.length()); + StubRoutines::x86::_vector_iota_indices[0] = start; + for (int i = 1; i < VECTOR_IOTA_COUNT; i++) { + StubRoutines::x86::_vector_iota_indices[i] = entries.at(i - 1); + } } address StubGenerator::generate_vector_reverse_bit_lut() { @@ -4837,7 +4855,7 @@ void StubGenerator::generate_compiler_stubs() { StubRoutines::x86::_vector_short_shuffle_mask = generate_vector_mask(StubId::stubgen_vector_short_shuffle_mask_id, 0x0100010001000100); StubRoutines::x86::_vector_long_shuffle_mask = generate_vector_mask(StubId::stubgen_vector_long_shuffle_mask_id, 0x0000000100000000); StubRoutines::x86::_vector_long_sign_mask = generate_vector_mask(StubId::stubgen_vector_long_sign_mask_id, 0x8000000000000000); - StubRoutines::x86::_vector_iota_indices = generate_iota_indices(); + generate_iota_indices(); StubRoutines::x86::_vector_count_leading_zeros_lut = generate_count_leading_zeros_lut(); StubRoutines::x86::_vector_reverse_bit_lut = generate_vector_reverse_bit_lut(); StubRoutines::x86::_vector_reverse_byte_perm_mask_long = generate_vector_reverse_byte_perm_mask_long(); diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64.hpp b/src/hotspot/cpu/x86/stubGenerator_x86_64.hpp index 05e8384d636..d3823cb559f 100644 --- a/src/hotspot/cpu/x86/stubGenerator_x86_64.hpp +++ b/src/hotspot/cpu/x86/stubGenerator_x86_64.hpp @@ -84,7 +84,7 @@ class StubGenerator: public StubCodeGenerator { address generate_count_leading_zeros_lut(); address generate_popcount_avx_lut(); - address generate_iota_indices(); + void generate_iota_indices(); address generate_vector_reverse_bit_lut(); address generate_vector_reverse_byte_perm_mask_long(); diff --git a/src/hotspot/cpu/x86/stubRoutines_x86.cpp b/src/hotspot/cpu/x86/stubRoutines_x86.cpp index 8696180c512..aaee01437af 100644 --- a/src/hotspot/cpu/x86/stubRoutines_x86.cpp +++ b/src/hotspot/cpu/x86/stubRoutines_x86.cpp @@ -44,8 +44,12 @@ #define DEFINE_ARCH_ENTRY_INIT(arch, blob_name, stub_name, field_name, getter_name, init_function) \ address StubRoutines:: arch :: STUB_FIELD_NAME(field_name) = CAST_FROM_FN_PTR(address, init_function); -STUBGEN_ARCH_ENTRIES_DO(DEFINE_ARCH_ENTRY, DEFINE_ARCH_ENTRY_INIT) +#define DEFINE_ARCH_ENTRY_ARRAY(arch, blob_name, stub_name, field_name, getter_name, count) \ + address StubRoutines:: arch :: STUB_FIELD_NAME(field_name) [count]; +STUBGEN_ARCH_ENTRIES_DO(DEFINE_ARCH_ENTRY, DEFINE_ARCH_ENTRY_INIT, DEFINE_ARCH_ENTRY_ARRAY) + +#undef DEFINE_ARCH_ENTRY_ARRAY #undef DEFINE_ARCH_ENTRY_INIT #undef DEFINE_ARCH_ENTRY diff --git a/src/hotspot/cpu/x86/stubRoutines_x86.hpp b/src/hotspot/cpu/x86/stubRoutines_x86.hpp index 3c6d75c1d4e..7283798888b 100644 --- a/src/hotspot/cpu/x86/stubRoutines_x86.hpp +++ b/src/hotspot/cpu/x86/stubRoutines_x86.hpp @@ -55,9 +55,13 @@ class x86 { #define DECLARE_ARCH_ENTRY_INIT(arch, blob_name, stub_name, field_name, getter_name, init_function) \ DECLARE_ARCH_ENTRY(arch, blob_name, stub_name, field_name, getter_name) -private: - STUBGEN_ARCH_ENTRIES_DO(DECLARE_ARCH_ENTRY, DECLARE_ARCH_ENTRY_INIT) +#define DECLARE_ARCH_ENTRY_ARRAY(arch, blob_name, stub_name, field_name, getter_name, count) \ + static address STUB_FIELD_NAME(field_name) [count] ; +private: + STUBGEN_ARCH_ENTRIES_DO(DECLARE_ARCH_ENTRY, DECLARE_ARCH_ENTRY_INIT, DECLARE_ARCH_ENTRY_ARRAY) + +#undef DECLARE_ARCH_ENTRY_ARRAY #undef DECLARE_ARCH_ENTRY_INIT #undef DECLARE_ARCH_ENTRY @@ -70,9 +74,13 @@ private: #define DEFINE_ARCH_ENTRY_GETTER_INIT(arch, blob_name, stub_name, field_name, getter_name, init_function) \ DEFINE_ARCH_ENTRY_GETTER(arch, blob_name, stub_name, field_name, getter_name) -public: - STUBGEN_ARCH_ENTRIES_DO(DEFINE_ARCH_ENTRY_GETTER, DEFINE_ARCH_ENTRY_GETTER_INIT) +#define DEFINE_ARCH_ENTRY_GETTER_ARRAY(arch, blob_name, stub_name, field_name, getter_name, count) \ + static address getter_name(int idx) { return STUB_FIELD_NAME(field_name) [idx]; } +public: + STUBGEN_ARCH_ENTRIES_DO(DEFINE_ARCH_ENTRY_GETTER, DEFINE_ARCH_ENTRY_GETTER_INIT, DEFINE_ARCH_ENTRY_GETTER_ARRAY) + +#undef DEFINE_ARCH_ENTRY_GETTER_ARRAY #undef DEFINE_ARCH_ENTRY_GETTER_INIT #undef DEFINE_ARCH_GETTER_ENTRY diff --git a/src/hotspot/cpu/zero/stubDeclarations_zero.hpp b/src/hotspot/cpu/zero/stubDeclarations_zero.hpp index 2357bbb5169..9abe313b3a7 100644 --- a/src/hotspot/cpu/zero/stubDeclarations_zero.hpp +++ b/src/hotspot/cpu/zero/stubDeclarations_zero.hpp @@ -29,35 +29,40 @@ #define STUBGEN_PREUNIVERSE_BLOBS_ARCH_DO(do_stub, \ do_arch_blob, \ do_arch_entry, \ - do_arch_entry_init) \ + do_arch_entry_init, \ + do_arch_entry_array) \ do_arch_blob(preuniverse, 0) \ #define STUBGEN_INITIAL_BLOBS_ARCH_DO(do_stub, \ do_arch_blob, \ do_arch_entry, \ - do_arch_entry_init) \ + do_arch_entry_init, \ + do_arch_entry_array) \ do_arch_blob(initial, 0) \ #define STUBGEN_CONTINUATION_BLOBS_ARCH_DO(do_stub, \ do_arch_blob, \ do_arch_entry, \ - do_arch_entry_init) \ + do_arch_entry_init, \ + do_arch_entry_array) \ do_arch_blob(continuation, 0) \ #define STUBGEN_COMPILER_BLOBS_ARCH_DO(do_stub, \ do_arch_blob, \ do_arch_entry, \ - do_arch_entry_init) \ + do_arch_entry_init, \ + do_arch_entry_array) \ do_arch_blob(compiler, 0) \ #define STUBGEN_FINAL_BLOBS_ARCH_DO(do_stub, \ do_arch_blob, \ do_arch_entry, \ - do_arch_entry_init) \ + do_arch_entry_init, \ + do_arch_entry_array) \ do_arch_blob(final, 0) \ diff --git a/src/hotspot/share/runtime/stubDeclarations.hpp b/src/hotspot/share/runtime/stubDeclarations.hpp index d1ce378ee20..ed1b3ea2e78 100644 --- a/src/hotspot/share/runtime/stubDeclarations.hpp +++ b/src/hotspot/share/runtime/stubDeclarations.hpp @@ -539,18 +539,19 @@ // generated. // // Architecture-specific entries need to be declared using the -// do_arch_entry template +// do_arch_entry templates // // do_arch_entry(arch, blob_name, stub_name, field_name, getter_name) // // do_arch_entry_init(arch, blob_name, stub_name, field_name, // getter_name, init_function) // +// do_arch_entry_array(arch, blob_name, stub_name, field_name, +// getter_name, count) +// // The only difference between these templates and the generic ones is // that they receive an extra argument which identifies the current // architecture e.g. x86, aarch64 etc. -// -// Currently there is no support for a do_arch_array_entry template. // Include arch-specific stub and entry declarations and make sure the // relevant template macros have been defined @@ -598,7 +599,8 @@ do_entry, do_entry_init, \ do_entry_array, \ do_arch_blob, \ - do_arch_entry, do_arch_entry_init) \ + do_arch_entry, do_arch_entry_init, \ + do_arch_entry_array) \ do_blob(preuniverse) \ do_stub(preuniverse, fence) \ do_entry(preuniverse, fence, fence_entry, fence_entry) \ @@ -615,7 +617,8 @@ atomic_cmpxchg_long_entry) \ /* merge in stubs and entries declared in arch header */ \ STUBGEN_PREUNIVERSE_BLOBS_ARCH_DO(do_stub, do_arch_blob, \ - do_arch_entry, do_arch_entry_init) \ + do_arch_entry, do_arch_entry_init, \ + do_arch_entry_array) \ end_blob(preuniverse) \ #define STUBGEN_INITIAL_BLOBS_DO(do_blob, end_blob, \ @@ -623,7 +626,8 @@ do_entry, do_entry_init, \ do_entry_array, \ do_arch_blob, \ - do_arch_entry, do_arch_entry_init) \ + do_arch_entry, do_arch_entry_init, \ + do_arch_entry_array) \ do_blob(initial) \ do_stub(initial, call_stub) \ do_entry(initial, call_stub, call_stub_entry, call_stub_entry) \ @@ -669,7 +673,8 @@ do_entry(initial, fmod, fmod, fmod) \ /* merge in stubs and entries declared in arch header */ \ STUBGEN_INITIAL_BLOBS_ARCH_DO(do_stub, do_arch_blob, \ - do_arch_entry, do_arch_entry_init) \ + do_arch_entry, do_arch_entry_init, \ + do_arch_entry_array) \ end_blob(initial) \ @@ -679,7 +684,8 @@ do_entry_array, \ do_arch_blob, \ do_arch_entry, \ - do_arch_entry_init) \ + do_arch_entry_init, \ + do_arch_entry_array) \ do_blob(continuation) \ do_stub(continuation, cont_thaw) \ do_entry(continuation, cont_thaw, cont_thaw, cont_thaw) \ @@ -694,7 +700,8 @@ cont_returnBarrierExc) \ /* merge in stubs and entries declared in arch header */ \ STUBGEN_CONTINUATION_BLOBS_ARCH_DO(do_stub, do_arch_blob, \ - do_arch_entry, do_arch_entry_init) \ + do_arch_entry, do_arch_entry_init, \ + do_arch_entry_array) \ end_blob(continuation) \ @@ -703,7 +710,8 @@ do_entry, do_entry_init, \ do_entry_array, \ do_arch_blob, \ - do_arch_entry, do_arch_entry_init) \ + do_arch_entry, do_arch_entry_init, \ + do_arch_entry_array) \ do_blob(compiler) \ do_stub(compiler, array_sort) \ do_entry(compiler, array_sort, array_sort, select_arraysort_function) \ @@ -848,7 +856,8 @@ bigIntegerLeftShiftWorker, bigIntegerLeftShift) \ /* merge in stubs and entries declared in arch header */ \ STUBGEN_COMPILER_BLOBS_ARCH_DO(do_stub, do_arch_blob, \ - do_arch_entry, do_arch_entry_init) \ + do_arch_entry, do_arch_entry_init, \ + do_arch_entry_array) \ end_blob(compiler) \ @@ -857,7 +866,8 @@ do_entry, do_entry_init, \ do_entry_array, \ do_arch_blob, \ - do_arch_entry, do_arch_entry_init) \ + do_arch_entry, do_arch_entry_init, \ + do_arch_entry_array) \ do_blob(final) \ do_stub(final, verify_oop) \ do_entry(final, verify_oop, verify_oop_subroutine_entry, \ @@ -1069,7 +1079,8 @@ lookup_secondary_supers_table_slow_path_stub) \ /* merge in stubs and entries declared in arch header */ \ STUBGEN_FINAL_BLOBS_ARCH_DO(do_stub, do_arch_blob, \ - do_arch_entry, do_arch_entry_init) \ + do_arch_entry, do_arch_entry_init, \ + do_arch_entry_array) \ end_blob(final) \ @@ -1082,37 +1093,43 @@ do_entry, do_entry_init, \ do_entry_array, \ do_arch_blob, \ - do_arch_entry, do_arch_entry_init) \ + do_arch_entry, do_arch_entry_init, \ + do_arch_entry_array) \ STUBGEN_PREUNIVERSE_BLOBS_DO(do_blob, end_blob, \ do_stub, \ do_entry, do_entry_init, \ do_entry_array, \ do_arch_blob, \ - do_arch_entry, do_arch_entry_init) \ + do_arch_entry, do_arch_entry_init, \ + do_arch_entry_array) \ STUBGEN_INITIAL_BLOBS_DO(do_blob, end_blob, \ do_stub, \ do_entry, do_entry_init, \ do_entry_array, \ do_arch_blob, \ - do_arch_entry, do_arch_entry_init) \ + do_arch_entry, do_arch_entry_init, \ + do_arch_entry_array) \ STUBGEN_CONTINUATION_BLOBS_DO(do_blob, end_blob, \ do_stub, \ do_entry, do_entry_init, \ do_entry_array, \ do_arch_blob, \ - do_arch_entry, do_arch_entry_init) \ + do_arch_entry, do_arch_entry_init, \ + do_arch_entry_array) \ STUBGEN_COMPILER_BLOBS_DO(do_blob, end_blob, \ do_stub, \ do_entry, do_entry_init, \ do_entry_array, \ do_arch_blob, \ - do_arch_entry, do_arch_entry_init) \ + do_arch_entry, do_arch_entry_init, \ + do_arch_entry_array) \ STUBGEN_FINAL_BLOBS_DO(do_blob, end_blob, \ do_stub, \ do_entry, do_entry_init, \ do_entry_array, \ do_arch_blob, \ - do_arch_entry, do_arch_entry_init) \ + do_arch_entry, do_arch_entry_init, \ + do_arch_entry_array) \ // Convenience macros for use by template implementations @@ -1162,6 +1179,9 @@ #define STUBGEN_COUNT5(_1, _2, _3, _4, count) \ + count +#define STUBGEN_COUNT6(_1, _2, _3, _4, _5, count) \ + + count + // Convenience templates that emit nothing // ignore do_blob(blob_name, type) declarations @@ -1200,7 +1220,8 @@ DO_ENTRY_EMPTY4, DO_ENTRY_EMPTY5, \ DO_ENTRY_EMPTY5, \ DO_ARCH_BLOB_EMPTY2, \ - DO_ARCH_ENTRY_EMPTY5, DO_ARCH_ENTRY_EMPTY6) \ + DO_ARCH_ENTRY_EMPTY5, DO_ARCH_ENTRY_EMPTY6, \ + DO_ARCH_ENTRY_EMPTY6) \ // client macro to operate only on StubGenerator stubs @@ -1210,7 +1231,8 @@ DO_ENTRY_EMPTY4, DO_ENTRY_EMPTY5, \ DO_ENTRY_EMPTY5, \ DO_ARCH_BLOB_EMPTY2, \ - DO_ARCH_ENTRY_EMPTY5, DO_ARCH_ENTRY_EMPTY6) \ + DO_ARCH_ENTRY_EMPTY5, DO_ARCH_ENTRY_EMPTY6, \ + DO_ARCH_ENTRY_EMPTY6) \ // client macros to operate only on StubGenerator blobs and stubs @@ -1220,18 +1242,21 @@ DO_ENTRY_EMPTY4, DO_ENTRY_EMPTY5, \ DO_ENTRY_EMPTY5, \ DO_ARCH_BLOB_EMPTY2, \ - DO_ARCH_ENTRY_EMPTY5,DO_ARCH_ENTRY_EMPTY6) \ + DO_ARCH_ENTRY_EMPTY5,DO_ARCH_ENTRY_EMPTY6, \ + DO_ARCH_ENTRY_EMPTY6) \ // client macro to operate only on StubGenerator generci and arch entries #define STUBGEN_ALL_ENTRIES_DO(do_entry, do_entry_init, do_entry_array, \ - do_arch_entry, do_arch_entry_init) \ + do_arch_entry, do_arch_entry_init, \ + do_arch_entry_array) \ STUBGEN_ALL_DO(DO_BLOB_EMPTY1, DO_BLOB_EMPTY1, \ DO_STUB_EMPTY2, \ do_entry, do_entry_init, \ do_entry_array, \ DO_ARCH_BLOB_EMPTY2, \ - do_arch_entry, do_arch_entry_init) \ + do_arch_entry, do_arch_entry_init, \ + do_arch_entry_array) \ // client macro to operate only on StubGenerator entries @@ -1241,7 +1266,8 @@ do_entry, do_entry_init, \ do_entry_array, \ DO_ARCH_BLOB_EMPTY2, \ - DO_ARCH_ENTRY_EMPTY5, DO_ARCH_ENTRY_EMPTY6) \ + DO_ARCH_ENTRY_EMPTY5, DO_ARCH_ENTRY_EMPTY6, \ + DO_ARCH_ENTRY_EMPTY6) \ // client macro to operate only on StubGenerator arch blobs @@ -1251,16 +1277,19 @@ DO_ENTRY_EMPTY4, DO_ENTRY_EMPTY5, \ DO_ENTRY_EMPTY5, \ do_arch_blob, \ - DO_ARCH_ENTRY_EMPTY5, DO_ARCH_ENTRY_EMPTY6) \ + DO_ARCH_ENTRY_EMPTY5, DO_ARCH_ENTRY_EMPTY6, \ + DO_ARCH_ENTRY_EMPTY6) \ // client macro to operate only on StubGenerator arch entries -#define STUBGEN_ARCH_ENTRIES_DO(do_arch_entry, do_arch_entry_init) \ +#define STUBGEN_ARCH_ENTRIES_DO(do_arch_entry, do_arch_entry_init, \ + do_arch_entry_array) \ STUBGEN_ALL_DO(DO_BLOB_EMPTY1, DO_BLOB_EMPTY1, \ DO_STUB_EMPTY2, \ DO_ENTRY_EMPTY4, DO_ENTRY_EMPTY5, \ DO_ENTRY_EMPTY5, \ DO_ARCH_BLOB_EMPTY2, \ - do_arch_entry, do_arch_entry_init) \ + do_arch_entry, do_arch_entry_init, \ + do_arch_entry_array) \ #endif // SHARE_RUNTIME_STUBDECLARATIONS_HPP diff --git a/src/hotspot/share/runtime/stubInfo.cpp b/src/hotspot/share/runtime/stubInfo.cpp index c07c0298f2b..4d4d865cf95 100644 --- a/src/hotspot/share/runtime/stubInfo.cpp +++ b/src/hotspot/share/runtime/stubInfo.cpp @@ -574,6 +574,18 @@ void StubInfo::process_stubgen_entry(StubGroup& group_cursor, field_name, id), \ 0); \ +#define PROCESS_STUBGEN_ENTRY_ARCH_ARRAY(arch_name, blob, stub, \ + field_name, getter_name, \ + count) \ + process_stubgen_entry(_group_cursor, _blob_cursor, \ + _stub_cursor, _entry_cursor, \ + #arch_name "_" # field_name "_entry (stub gen)", \ + BlobId:: JOIN3(stubgen, blob, id), \ + StubId:: JOIN3(stubgen, stub, id), \ + EntryId:: JOIN4(stubgen, arch_name, \ + field_name, id), \ + count); \ + void StubInfo::populate_stub_tables() { StubGroup _group_cursor; BlobId _blob_cursor = BlobId::NO_BLOBID; @@ -615,7 +627,8 @@ void StubInfo::populate_stub_tables() { PROCESS_STUBGEN_ENTRY, PROCESS_STUBGEN_ENTRY_INIT, PROCESS_STUBGEN_ENTRY_ARRAY, DO_ARCH_BLOB_EMPTY2, - PROCESS_STUBGEN_ENTRY_ARCH, PROCESS_STUBGEN_ENTRY_ARCH_INIT); + PROCESS_STUBGEN_ENTRY_ARCH, PROCESS_STUBGEN_ENTRY_ARCH_INIT, + PROCESS_STUBGEN_ENTRY_ARCH_ARRAY); assert(next(_blob_cursor) == BlobId::NUM_BLOBIDS, "should have exhausted all blob ids!"); assert(next(_stub_cursor) == StubId::NUM_STUBIDS, "should have exhausted all stub ids!"); assert(next(_entry_cursor) == EntryId::NUM_ENTRYIDS, "should have exhausted all entry ids!"); @@ -636,6 +649,7 @@ void StubInfo::populate_stub_tables() { #undef PROCESS_STUBGEN_ENTRY_ARRAY #undef PROCESS_STUBGEN_ENTRY_ARCH #undef PROCESS_STUBGEN_ENTRY_ARCH_INIT +#undef PROCESS_STUBGEN_ENTRY_ARCH_ARRAY #ifdef ASSERT diff --git a/src/hotspot/share/runtime/stubInfo.hpp b/src/hotspot/share/runtime/stubInfo.hpp index 447f7d3a582..2fe503a8d0e 100644 --- a/src/hotspot/share/runtime/stubInfo.hpp +++ b/src/hotspot/share/runtime/stubInfo.hpp @@ -349,6 +349,14 @@ enum class StubId : int { init_function) \ JOIN4(stubgen, arch_name, field_name, id), \ +#define STUBGEN_DECLARE_ARCH_ARRAY_TAG(arch_name, blob_name, stub_name, \ + field_name, getter_name, \ + count) \ + JOIN4(stubgen, arch_name, field_name, id), \ + JOIN4(stubgen, arch_name, field_name, max) = \ + JOIN4(stubgen, arch_name, field_name, id) + \ + count - 1, \ + // the above macros are enough to declare the enum enum class EntryId : int { @@ -366,7 +374,8 @@ enum class EntryId : int { STUBGEN_DECLARE_INIT_TAG, STUBGEN_DECLARE_ARRAY_TAG, STUBGEN_DECLARE_ARCH_TAG, - STUBGEN_DECLARE_ARCH_INIT_TAG) + STUBGEN_DECLARE_ARCH_INIT_TAG, + STUBGEN_DECLARE_ARCH_ARRAY_TAG) NUM_ENTRYIDS }; @@ -379,6 +388,7 @@ enum class EntryId : int { #undef STUBGEN_DECLARE_ARRAY_TAG #undef STUBGEN_DECLARE_ARCH_TAG #undef STUBGEN_DECLARE_ARCH_INIT_TAG +#undef STUBGEN_DECLARE_ARCH_ARRAY_TAG // we need static init expressions for blob, stub and entry counts in // each stubgroup @@ -404,7 +414,8 @@ enum class EntryId : int { #define STUBGEN_ENTRY_COUNT_INITIALIZER \ 0 STUBGEN_ALL_ENTRIES_DO(COUNT4, COUNT5, \ STUBGEN_COUNT5, \ - COUNT5, COUNT6) + COUNT5, COUNT6, \ + STUBGEN_COUNT6) // Declare management class StubInfo From d114e8d05827d3e4756aeb8bbe115ec02b066da7 Mon Sep 17 00:00:00 2001 From: David Beaumont Date: Wed, 15 Apr 2026 09:22:34 +0000 Subject: [PATCH 057/108] 8375649: idea.sh script adds source paths in a single, enormous, line to jdk.iml Reviewed-by: erikj, liach --- bin/idea.sh | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/bin/idea.sh b/bin/idea.sh index a184884b61a..d9a18956e3b 100644 --- a/bin/idea.sh +++ b/bin/idea.sh @@ -187,14 +187,18 @@ fi SOURCE_PREFIX="" +# SOURCES is a single string containing embeded newlines. for root in $MODULE_ROOTS; do if [ "x$CYGPATH" != "x" ]; then root=`$CYGPATH -am $root` elif [ "x$WSL_DISTRO_NAME" != "x" ]; then root=`wslpath -am $root` fi - - SOURCES=$SOURCES" $SOURCE_PREFIX""$root""$SOURCE_POSTFIX" + # Add line termination/indentation for everything after the first entry. + if [ "x$SOURCES" != "x" ]; then + SOURCES="${SOURCES}\n " + fi + SOURCES="${SOURCES}${SOURCE_PREFIX}${root}${SOURCE_POSTFIX}" done add_replacement "###SOURCE_ROOTS###" "$SOURCES" From aece6f483250d14af3c8a728de6817bb98f10436 Mon Sep 17 00:00:00 2001 From: Daniel Fuchs Date: Wed, 15 Apr 2026 11:21:07 +0000 Subject: [PATCH 058/108] 8381842: Refactor remaining TestNG tests under java/net/ to use JUnit Reviewed-by: vyazici --- .../java/net/DatagramPacket/Constructor.java | 65 +++--- test/jdk/java/net/DatagramPacket/Getters.java | 12 +- test/jdk/java/net/DatagramPacket/Setters.java | 52 +++-- .../InetAddress/HostsFileOrderingTest.java | 48 ++-- .../java/net/InetSocketAddress/ToString.java | 103 ++++----- .../net/NetworkInterface/NullMacAddress.java | 30 ++- .../jdk/java/net/SocketOption/AfterClose.java | 101 ++++---- .../net/SocketOption/CachedImplOptions.java | 53 +++-- .../net/SocketOption/ImmutableOptions.java | 67 +++--- .../net/SocketOption/NullsAndBadValues.java | 215 +++++++++--------- .../net/SocketOption/RequiredOptions.java | 13 +- test/jdk/java/net/SocketPermission/Ctor.java | 63 ++--- test/jdk/java/net/Socks/SocksIPv6Test.java | 55 ++--- .../java/net/Socks/SocksSocketImplTest.java | 39 ++-- .../UnixDomainSocketAddress/AddressTest.java | 12 +- .../UnixDomainSocketAddress/LengthTest.java | 69 +++--- ...xDomainSocketAddressSerializationTest.java | 38 ++-- 17 files changed, 527 insertions(+), 508 deletions(-) diff --git a/test/jdk/java/net/DatagramPacket/Constructor.java b/test/jdk/java/net/DatagramPacket/Constructor.java index ff77d51b798..9f108a263e9 100644 --- a/test/jdk/java/net/DatagramPacket/Constructor.java +++ b/test/jdk/java/net/DatagramPacket/Constructor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2020, 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 @@ -25,18 +25,19 @@ * @bug 4091803 7021373 * @summary this tests that the constructor of DatagramPacket rejects * bogus arguments properly. - * @run testng Constructor + * @run junit ${test.main.class} */ import java.net.DatagramPacket; import java.net.InetAddress; import java.net.InetSocketAddress; -import org.testng.annotations.Test; - -import static org.testng.Assert.assertFalse; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.expectThrows; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertThrows; public class Constructor { @@ -48,35 +49,33 @@ public class Constructor { @Test public void testNullPacket() { - expectThrows(NPE, + assertThrows(NPE, () -> new DatagramPacket(null, 100)); } @Test public void testNull() throws Exception { - expectThrows(NPE, () -> new DatagramPacket(null, 100)); - expectThrows(NPE, () -> new DatagramPacket(null, 0, 10)); - expectThrows(NPE, () -> new DatagramPacket(null, 0, 10, LOOPBACK, 80)); - expectThrows(NPE, () -> new DatagramPacket(null, 10, LOOPBACK, 80)); - expectThrows(NPE, () -> new DatagramPacket(null, 0, 10, new InetSocketAddress(80))); - expectThrows(NPE, () -> new DatagramPacket(null, 10, new InetSocketAddress(80))); + assertThrows(NPE, () -> new DatagramPacket(null, 100)); + assertThrows(NPE, () -> new DatagramPacket(null, 0, 10)); + assertThrows(NPE, () -> new DatagramPacket(null, 0, 10, LOOPBACK, 80)); + assertThrows(NPE, () -> new DatagramPacket(null, 10, LOOPBACK, 80)); + assertThrows(NPE, () -> new DatagramPacket(null, 0, 10, new InetSocketAddress(80))); + assertThrows(NPE, () -> new DatagramPacket(null, 10, new InetSocketAddress(80))); // no Exception expected for null addresses - new DatagramPacket(buf, 10, null, 0); - new DatagramPacket(buf, 10, 10, null, 0); + assertDoesNotThrow(() -> new DatagramPacket(buf, 10, null, 0)); + assertDoesNotThrow(() -> new DatagramPacket(buf, 10, 10, null, 0)); } @Test public void testNegativeBufferLength() { /* length lesser than buffer length */ - expectThrows(IAE, - () -> new DatagramPacket(buf, -128)); + assertThrows(IAE, () -> new DatagramPacket(buf, -128)); } @Test public void testPacketLengthTooLarge() { /* length greater than buffer length */ - expectThrows(IAE, - () -> new DatagramPacket(buf, 256)); + assertThrows(IAE, () -> new DatagramPacket(buf, 256)); } @Test @@ -84,14 +83,14 @@ public class Constructor { /* negative port */ InetAddress addr = InetAddress.getLocalHost(); - expectThrows(IAE, + assertThrows(IAE, () -> new DatagramPacket(buf, 100, addr, -1)); } @Test public void testPortValueTooLarge() { /* invalid port value */ - expectThrows(IAE, + assertThrows(IAE, () -> new DatagramPacket(buf, 128, LOOPBACK, Integer.MAX_VALUE)); } @@ -101,8 +100,9 @@ public class Constructor { int length = 50; DatagramPacket pkt = new DatagramPacket(buf, offset, length); - assertFalse((pkt.getData() != buf || pkt.getOffset() != offset || - pkt.getLength() != length), "simple constructor failed"); + assertSame(buf, pkt.getData()); + assertEquals(offset, pkt.getOffset()); + assertEquals(length, pkt.getLength()); } @Test @@ -112,20 +112,21 @@ public class Constructor { int port = 8080; DatagramPacket packet = new DatagramPacket(buf, offset, length, LOOPBACK, port); - assertFalse((packet.getData() != buf || packet.getOffset() != offset || - packet.getLength() != length || - packet.getAddress() != LOOPBACK || - packet.getPort() != port), "full constructor failed"); + assertSame(buf, packet.getData()); + assertEquals(offset, packet.getOffset()); + assertEquals(length, packet.getLength()); + assertSame(LOOPBACK, packet.getAddress()); + assertEquals(port, packet.getPort()); } @Test public void testDefaultValues() { DatagramPacket packet = new DatagramPacket(buf, 0); - assertTrue(packet.getAddress() == null); - assertTrue(packet.getPort() == 0); + assertNull(packet.getAddress()); + assertEquals(0, packet.getPort()); DatagramPacket packet1 = new DatagramPacket(buf, 0, 0); - assertTrue(packet1.getAddress() == null); - assertTrue(packet1.getPort() == 0); + assertNull(packet1.getAddress()); + assertEquals(0, packet1.getPort()); } } diff --git a/test/jdk/java/net/DatagramPacket/Getters.java b/test/jdk/java/net/DatagramPacket/Getters.java index f2e1c40603c..c66f7966267 100644 --- a/test/jdk/java/net/DatagramPacket/Getters.java +++ b/test/jdk/java/net/DatagramPacket/Getters.java @@ -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,15 +24,15 @@ /* @test * @bug 8237890 * @summary Check that DatagramPacket's get methods perform as expected - * @run testng Getters + * @run junit ${test.main.class} */ -import org.testng.annotations.Test; - import java.net.DatagramPacket; import java.net.InetSocketAddress; -import static org.testng.Assert.assertTrue; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; public class Getters { @@ -42,6 +42,6 @@ public class Getters { InetSocketAddress addr = (InetSocketAddress)packet.getSocketAddress(); assertTrue(addr.getAddress().isAnyLocalAddress()); - assertTrue(addr.getPort() == 0); + assertEquals(0, addr.getPort()); } } diff --git a/test/jdk/java/net/DatagramPacket/Setters.java b/test/jdk/java/net/DatagramPacket/Setters.java index 0e950c4f7f2..74424339a18 100644 --- a/test/jdk/java/net/DatagramPacket/Setters.java +++ b/test/jdk/java/net/DatagramPacket/Setters.java @@ -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 @@ -25,17 +25,19 @@ * @bug 7021373 * @summary check that the DatagramPacket setter methods * throw the correct exceptions - * @run testng Setters + * @run junit ${test.main.class} */ import java.net.DatagramPacket; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; - -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.expectThrows; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; public class Setters { @@ -50,11 +52,10 @@ public class Setters { // No Exception expected for null addresses pkt.setAddress(null); - assertTrue(pkt.getAddress() == null); + assertNull(pkt.getAddress()); } - @DataProvider - Object[][] data() { // add checks for setAddress with null - add getAddress to verify + static Object[][] data() { // add checks for setAddress with null - add getAddress to verify return new Object[][]{ { buf, 0, -1, IAE }, { buf, -1, 1, IAE }, @@ -66,24 +67,24 @@ public class Setters { }; } - @Test(dataProvider = "data") + @ParameterizedTest + @MethodSource("data") public void testSetData(byte[] buf, int offset, int length, Class exception) { DatagramPacket pkt = new DatagramPacket(new byte[8], 8); + if (exception != null) { - expectThrows(exception, () -> pkt.setData(buf, offset, length)); - } else if (buf == null) { - expectThrows(exception, () -> pkt.setData(buf)); + assertThrows(exception, () -> pkt.setData(buf, offset, length)); + if (buf == null) assertThrows(exception, () -> pkt.setData(buf)); } else { - pkt.setData(buf, offset, length); + assertDoesNotThrow(() -> pkt.setData(buf, offset, length)); } } - @DataProvider - Object[][] lengths() { + static Object[][] lengths() { return new Object[][]{ { 0, -1, IAE }, { 8, 1, IAE }, @@ -94,20 +95,20 @@ public class Setters { }; } - @Test(dataProvider = "lengths") + @ParameterizedTest + @MethodSource("lengths") public void testSetLength(int offset, int length, Class exception) { DatagramPacket pkt = new DatagramPacket(new byte[8], offset, 0); if (exception != null) { - expectThrows(exception, () -> pkt.setLength(length)); + assertThrows(exception, () -> pkt.setLength(length)); } else { - pkt.setLength(length); + assertDoesNotThrow(() -> pkt.setLength(length)); } } - @DataProvider - Object[][] ports() { + static Object[][] ports() { return new Object[][]{ { -1, IAE }, { -666, IAE }, @@ -122,14 +123,15 @@ public class Setters { }; } - @Test(dataProvider = "ports") + @ParameterizedTest + @MethodSource("ports") public void testSetPort(int port, Class exception) { DatagramPacket pkt = new DatagramPacket(new byte[8], 0); if (exception != null) { - expectThrows(exception, () -> pkt.setPort(port)); + assertThrows(exception, () -> pkt.setPort(port)); } else { - pkt.setPort(port); + assertDoesNotThrow(() -> pkt.setPort(port)); } } } diff --git a/test/jdk/java/net/InetAddress/HostsFileOrderingTest.java b/test/jdk/java/net/InetAddress/HostsFileOrderingTest.java index 304180b6c48..56b52b2a264 100644 --- a/test/jdk/java/net/InetAddress/HostsFileOrderingTest.java +++ b/test/jdk/java/net/InetAddress/HostsFileOrderingTest.java @@ -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 @@ -34,9 +34,9 @@ import java.util.stream.Stream; import jdk.test.lib.net.IPSupport; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; -import org.testng.Assert; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.fail; /* @test @@ -46,23 +46,23 @@ import org.testng.Assert; * @summary Test that "jdk.net.hosts.file" NameService implementation returns addresses * with respect to "java.net.preferIPv4Stack" and "java.net.preferIPv6Addresses" system * property values - * @run testng/othervm -Djdk.net.hosts.file=TestHostsFile.txt - * -Djava.net.preferIPv4Stack=true -Djava.net.preferIPv6Addresses=true HostsFileOrderingTest - * @run testng/othervm -Djdk.net.hosts.file=TestHostsFile.txt - * -Djava.net.preferIPv4Stack=true -Djava.net.preferIPv6Addresses=false HostsFileOrderingTest - * @run testng/othervm -Djdk.net.hosts.file=TestHostsFile.txt - * -Djava.net.preferIPv4Stack=true -Djava.net.preferIPv6Addresses=system HostsFileOrderingTest - * @run testng/othervm -Djdk.net.hosts.file=TestHostsFile.txt - * -Djava.net.preferIPv4Stack=true -Djava.net.preferIPv6Addresses=notVALID HostsFileOrderingTest - * @run testng/othervm -Djdk.net.hosts.file=TestHostsFile.txt - * -Djava.net.preferIPv4Stack=false -Djava.net.preferIPv6Addresses=true HostsFileOrderingTest - * @run testng/othervm -Djdk.net.hosts.file=TestHostsFile.txt - * -Djava.net.preferIPv4Stack=false -Djava.net.preferIPv6Addresses=false HostsFileOrderingTest - * @run testng/othervm -Djdk.net.hosts.file=TestHostsFile.txt - * -Djava.net.preferIPv4Stack=false -Djava.net.preferIPv6Addresses=system HostsFileOrderingTest - * @run testng/othervm -Djdk.net.hosts.file=TestHostsFile.txt - * -Djava.net.preferIPv4Stack=false -Djava.net.preferIPv6Addresses=notVALID HostsFileOrderingTest - * @run testng/othervm -Djdk.net.hosts.file=TestHostsFile.txt HostsFileOrderingTest + * @run junit/othervm -Djdk.net.hosts.file=TestHostsFile.txt + * -Djava.net.preferIPv4Stack=true -Djava.net.preferIPv6Addresses=true ${test.main.class} + * @run junit/othervm -Djdk.net.hosts.file=TestHostsFile.txt + * -Djava.net.preferIPv4Stack=true -Djava.net.preferIPv6Addresses=false ${test.main.class} + * @run junit/othervm -Djdk.net.hosts.file=TestHostsFile.txt + * -Djava.net.preferIPv4Stack=true -Djava.net.preferIPv6Addresses=system ${test.main.class} + * @run junit/othervm -Djdk.net.hosts.file=TestHostsFile.txt + * -Djava.net.preferIPv4Stack=true -Djava.net.preferIPv6Addresses=notVALID ${test.main.class} + * @run junit/othervm -Djdk.net.hosts.file=TestHostsFile.txt + * -Djava.net.preferIPv4Stack=false -Djava.net.preferIPv6Addresses=true ${test.main.class} + * @run junit/othervm -Djdk.net.hosts.file=TestHostsFile.txt + * -Djava.net.preferIPv4Stack=false -Djava.net.preferIPv6Addresses=false ${test.main.class} + * @run junit/othervm -Djdk.net.hosts.file=TestHostsFile.txt + * -Djava.net.preferIPv4Stack=false -Djava.net.preferIPv6Addresses=system ${test.main.class} + * @run junit/othervm -Djdk.net.hosts.file=TestHostsFile.txt + * -Djava.net.preferIPv4Stack=false -Djava.net.preferIPv6Addresses=notVALID ${test.main.class} + * @run junit/othervm -Djdk.net.hosts.file=TestHostsFile.txt ${test.main.class} */ public class HostsFileOrderingTest { @@ -70,8 +70,8 @@ public class HostsFileOrderingTest { /* * Generate hosts file with the predefined list of IP addresses */ - @BeforeClass - public void generateHostsFile() throws Exception { + @BeforeAll + public static void generateHostsFile() throws Exception { String content = ADDRESSES_LIST.stream() .map(addr -> addr + " " + TEST_HOST_NAME) .collect( @@ -97,7 +97,7 @@ public class HostsFileOrderingTest { } else { System.err.printf("Expected addresses:%n%s%n", Arrays.deepToString(expectedAddresses)); System.err.printf("Resolved addresses:%n%s%n", Arrays.deepToString(resolvedAddresses)); - Assert.fail("Wrong host resolution result is returned"); + fail("Wrong host resolution result is returned"); } } diff --git a/test/jdk/java/net/InetSocketAddress/ToString.java b/test/jdk/java/net/InetSocketAddress/ToString.java index 12eb599b872..a86e6c714f0 100644 --- a/test/jdk/java/net/InetSocketAddress/ToString.java +++ b/test/jdk/java/net/InetSocketAddress/ToString.java @@ -26,21 +26,27 @@ * @bug 8225499 4464064 * @library /test/lib * @summary InetSocketAddress::toString not friendly to IPv6 literal addresses - * @run testng/othervm ToString - * @run testng/othervm -Djava.net.preferIPv4Stack=true ToString - * @run testng/othervm -Djava.net.preferIPv6Addresses=true ToString + * @run junit/othervm ${test.main.class} + * @run junit/othervm -Djava.net.preferIPv4Stack=true ${test.main.class} + * @run junit/othervm -Djava.net.preferIPv6Addresses=true ${test.main.class} */ -import java.net.*; -import java.util.Optional; - -import org.testng.SkipException; -import org.testng.annotations.BeforeTest; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; import static jdk.test.lib.net.IPSupport.diagnoseConfigurationIssue; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; + public class ToString { private static final String loopbackAddr; @@ -75,22 +81,19 @@ public class ToString { } } - @BeforeTest - public void setup() { - Optional configurationIssue = diagnoseConfigurationIssue(); - configurationIssue.map(SkipException::new).ifPresent(x -> { - throw x; - }); + @BeforeAll + public static void setup() { + diagnoseConfigurationIssue().ifPresent(Assumptions::abort); } @Test - // InetSocketAddress.toString() throws NPE with unresolved address - public static void NPETest() { - System.out.println(new InetSocketAddress("unresolved", 12345)); + public void NPETest() { + // Test that InetSocketAddress.toString() does not throw NPE with unresolved address + assertDoesNotThrow(() -> System.out.println( + new InetSocketAddress("unresolved", 12345))); } - @DataProvider(name = "hostPortArgs") - public Object[][] createArgs1() { + public static Object[][] fromHostStringAndPort() { return new Object[][]{ // hostname, port number, expected string in format // /: or @@ -106,44 +109,34 @@ public class ToString { }; } - @Test(dataProvider = "hostPortArgs") - public static void testConstructor(String host, int port, String string) { + @ParameterizedTest + @MethodSource("fromHostStringAndPort") + public void testConstructor(String host, int port, String string) { String received = new InetSocketAddress(host, port).toString(); - - if (!string.equals(received)) { - throw new RuntimeException("Expected: " + string + " Received: " + received); - } + assertEquals(string, received); } - @DataProvider(name = "addrPortArgs") - public Object[][] createArgs2() { + public static Object[][] fromInetAddressAndPort() throws UnknownHostException { InetAddress nullAddr = null; - try { - return new Object[][]{ - // InetAddress, port number, expected string - {InetAddress.getLoopbackAddress(), 80, "localhost/" + loopbackAddr + ":80"}, - {InetAddress.getLocalHost(), 80, localAddr + ":80"}, - {InetAddress.getByAddress(new byte[]{1, 1, 1, 1}), 80, "/1.1.1.1:80"}, - {InetAddress.getByAddress(new byte[]{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}), 80, "/[101:101:101:101:101:101:101:101]:80"}, - {InetAddress.getByName("225.225.225.0"), 80, "/225.225.225.0:80"}, - {nullAddr, 80, wildcardAddr + ":80"} - }; - } catch (UnknownHostException uhe) { - throw new RuntimeException("Data provider creation failed: " + uhe, uhe); - } + return new Object[][]{ + // InetAddress, port number, expected string + {InetAddress.getLoopbackAddress(), 80, "localhost/" + loopbackAddr + ":80"}, + {InetAddress.getLocalHost(), 80, localAddr + ":80"}, + {InetAddress.getByAddress(new byte[]{1, 1, 1, 1}), 80, "/1.1.1.1:80"}, + {InetAddress.getByAddress(new byte[]{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}), 80, "/[101:101:101:101:101:101:101:101]:80"}, + {InetAddress.getByName("225.225.225.0"), 80, "/225.225.225.0:80"}, + {nullAddr, 80, wildcardAddr + ":80"} + }; } - @Test(dataProvider = "addrPortArgs") - public static void testConstructor(InetAddress addr, int port, String string) { + @ParameterizedTest + @MethodSource("fromInetAddressAndPort") + public void testConstructor(InetAddress addr, int port, String string) { String received = new InetSocketAddress(addr, port).toString(); - - if (!string.equals(received)) { - throw new RuntimeException("Expected: " + string + " Received: " + received); - } + assertEquals(string, received); } - @DataProvider(name = "unresolved") - public Object[][] createArgs3() { + public static Object[][] unresolvedFromHostStringAndPort() { return new Object[][]{ // hostname, port number, expected string {"::1", 80, "::1/:80"}, @@ -158,12 +151,10 @@ public class ToString { }; } - @Test(dataProvider = "unresolved") - public static void testCreateUnresolved(String host, int port, String string) { + @ParameterizedTest + @MethodSource("unresolvedFromHostStringAndPort") + public void testCreateUnresolved(String host, int port, String string) { String received = InetSocketAddress.createUnresolved(host, port).toString(); - - if (!string.equals(received)) { - throw new RuntimeException("Expected: " + string + " Received: " + received); - } + assertEquals(string, received); } } diff --git a/test/jdk/java/net/NetworkInterface/NullMacAddress.java b/test/jdk/java/net/NetworkInterface/NullMacAddress.java index 8161670860e..8edb447e44e 100644 --- a/test/jdk/java/net/NetworkInterface/NullMacAddress.java +++ b/test/jdk/java/net/NetworkInterface/NullMacAddress.java @@ -26,34 +26,30 @@ * @summary Test that querrying the mac address of the loopback interface * returns null and doesn't throw a SocketException. * @library /test/lib - * @run testng/othervm NullMacAddress - * @run testng/othervm -Djava.net.preferIPv6Addresses=true NullMacAddress - * @run testng/othervm -Djava.net.preferIPv4Stack=true NullMacAddress + * @run junit/othervm ${test.main.class} + * @run junit/othervm -Djava.net.preferIPv6Addresses=true ${test.main.class} + * @run junit/othervm -Djava.net.preferIPv4Stack=true ${test.main.class} */ -import org.testng.SkipException; -import org.testng.annotations.BeforeTest; -import org.testng.annotations.Test; - import static jdk.test.lib.net.IPSupport.diagnoseConfigurationIssue; -import static org.testng.Assert.*; import java.io.UncheckedIOException; import java.math.BigInteger; import java.net.NetworkInterface; import java.net.SocketException; import java.util.Locale; -import java.util.Optional; + +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertNull; public class NullMacAddress { - @BeforeTest - void setup() { - Optional configurationIssue = diagnoseConfigurationIssue(); - configurationIssue.map(SkipException::new).ifPresent(x -> { - throw x; - }); + @BeforeAll + public static void setup() { + diagnoseConfigurationIssue().ifPresent(Assumptions::abort); } @Test @@ -64,13 +60,13 @@ public class NullMacAddress { private void testMacAddress(NetworkInterface ni) { try { var name = ni.getDisplayName(); - System.out.println("Testing: " + name); + System.err.println("Testing: " + name); var loopback = ni.isLoopback(); var macAddress = ni.getHardwareAddress(); var hdr = macAddress == null ? "null" : "0x" + new BigInteger(1, macAddress) .toString(16).toUpperCase(Locale.ROOT); - System.out.println(" MAC address: " + hdr + (loopback ? " (loopback)" : "")); + System.err.println(" MAC address: " + hdr + (loopback ? " (loopback)" : "")); if (loopback) { assertNull(macAddress, "Loopback interface \"" + name + "\" doesn't have a null MAC Address"); diff --git a/test/jdk/java/net/SocketOption/AfterClose.java b/test/jdk/java/net/SocketOption/AfterClose.java index 825f344c662..e8cedab7dad 100644 --- a/test/jdk/java/net/SocketOption/AfterClose.java +++ b/test/jdk/java/net/SocketOption/AfterClose.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2021, 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,12 +25,11 @@ * @test * @bug 8224477 * @summary Ensures that IOException is thrown after the socket is closed - * @run testng AfterClose + * @run junit ${test.main.class} */ import java.io.IOException; import java.lang.reflect.Field; -import java.lang.reflect.Method; import java.net.DatagramSocket; import java.net.MulticastSocket; import java.net.NetworkInterface; @@ -48,11 +47,12 @@ import java.util.List; import java.util.Map; import java.util.stream.Collectors; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; import static java.lang.Boolean.*; import static java.net.StandardSocketOptions.*; -import static org.testng.Assert.expectThrows; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import static org.junit.jupiter.api.Assertions.assertThrows; public class AfterClose { @@ -117,8 +117,7 @@ public class AfterClose { // -- Socket - @DataProvider(name = "socketOptionValues") - public Object[][] socketOptionValues() throws Exception { + public static Object[][] socketOptionValues() throws Exception { try (Socket s = new Socket()) { return s.supportedOptions().stream() .map(so -> new Object[] {so, OPTION_VALUES_MAP.get(so)}) @@ -126,49 +125,51 @@ public class AfterClose { } } - @Test(dataProvider = "socketOptionValues") + @ParameterizedTest + @MethodSource("socketOptionValues") public void closedSocketImplUncreated(SocketOption option, List values) throws IOException { Socket socket = createClosedSocketImplUncreated(); for (int i=0; i<3; i++); { for (T value : values) { - expectThrows(IOE, () -> socket.setOption(option, value)); - expectThrows(IOE, () -> socket.getOption(option)); + assertThrows(IOE, () -> socket.setOption(option, value)); + assertThrows(IOE, () -> socket.getOption(option)); } } } - @Test(dataProvider = "socketOptionValues") + @ParameterizedTest + @MethodSource("socketOptionValues") public void closedSocketImplCreated(SocketOption option, List values) throws IOException { Socket socket = createClosedSocketImplCreated(); for (int i=0; i<3; i++); { for (T value : values) { - expectThrows(IOE, () -> socket.setOption(option, value)); - expectThrows(IOE, () -> socket.getOption(option)); + assertThrows(IOE, () -> socket.setOption(option, value)); + assertThrows(IOE, () -> socket.getOption(option)); } } } - @Test(dataProvider = "socketOptionValues") + @ParameterizedTest + @MethodSource("socketOptionValues") public void closedSocketAdapter(SocketOption option, List values) throws IOException { Socket socket = createClosedSocketFromAdapter(); for (int i=0; i<3; i++); { for (T value : values) { - if (!RO.equals(value)) expectThrows(IOE, () -> socket.setOption(option, value)); - expectThrows(IOE, () -> socket.getOption(option)); + if (!RO.equals(value)) assertThrows(IOE, () -> socket.setOption(option, value)); + assertThrows(IOE, () -> socket.getOption(option)); } } } // -- ServerSocket - @DataProvider(name = "serverSocketOptionValues") - public Object[][] serverSocketOptionValues() throws Exception { + public static Object[][] serverSocketOptionValues() throws Exception { try (ServerSocket ss = new ServerSocket()) { return ss.supportedOptions().stream() .map(so -> new Object[] {so, OPTION_VALUES_MAP.get(so)}) @@ -176,33 +177,36 @@ public class AfterClose { } } - @Test(dataProvider = "serverSocketOptionValues") + @ParameterizedTest + @MethodSource("serverSocketOptionValues") public void closedServerSocketImplUncreated(SocketOption option, List values) throws IOException { ServerSocket serverSocket = createClosedServerSocketImplUncreated(); for (int i=0; i<3; i++); { for (T value : values) { - expectThrows(IOE, () -> serverSocket.setOption(option, value)); - expectThrows(IOE, () -> serverSocket.getOption(option)); + assertThrows(IOE, () -> serverSocket.setOption(option, value)); + assertThrows(IOE, () -> serverSocket.getOption(option)); } } } - @Test(dataProvider = "serverSocketOptionValues") + @ParameterizedTest + @MethodSource("serverSocketOptionValues") public void closedServerSocketImplCreated(SocketOption option, List values) throws IOException { ServerSocket serverSocket = createClosedServerSocketImplCreated(); for (int i=0; i<3; i++); { for (T value : values) { - expectThrows(IOE, () -> serverSocket.setOption(option, value)); - expectThrows(IOE, () -> serverSocket.getOption(option)); + assertThrows(IOE, () -> serverSocket.setOption(option, value)); + assertThrows(IOE, () -> serverSocket.getOption(option)); } } } - @Test(dataProvider = "serverSocketOptionValues") + @ParameterizedTest + @MethodSource("serverSocketOptionValues") public void closedServerSocketAdapter(SocketOption option, List values) throws IOException { @@ -212,16 +216,15 @@ public class AfterClose { ServerSocket serverSocket = createClosedServerSocketFromAdapter(); for (int i=0; i<3; i++); { for (T value : values) { - if (!RO.equals(value)) expectThrows(IOE, () -> serverSocket.setOption(option, value)); - expectThrows(IOE, () -> serverSocket.getOption(option)); + if (!RO.equals(value)) assertThrows(IOE, () -> serverSocket.setOption(option, value)); + assertThrows(IOE, () -> serverSocket.getOption(option)); } } } // -- DatagramSocket - @DataProvider(name = "datagramSocketOptionValues") - public Object[][] datagramSocketOptionValues() throws Exception { + public static Object[][] datagramSocketOptionValues() throws Exception { try (DatagramSocket ds = new DatagramSocket()) { return ds.supportedOptions().stream() .map(so -> new Object[] {so, OPTION_VALUES_MAP.get(so)}) @@ -229,49 +232,51 @@ public class AfterClose { } } - @Test(dataProvider = "datagramSocketOptionValues") + @ParameterizedTest + @MethodSource("datagramSocketOptionValues") public void closedUnboundDatagramSocket(SocketOption option, List values) throws IOException { DatagramSocket datagramSocket = createClosedUnboundDatagramSocket(); for (int i=0; i<3; i++); { for (T value : values) { - if (!RO.equals(value)) expectThrows(IOE, () -> datagramSocket.setOption(option, value)); - expectThrows(IOE, () -> datagramSocket.getOption(option)); + if (!RO.equals(value)) assertThrows(IOE, () -> datagramSocket.setOption(option, value)); + assertThrows(IOE, () -> datagramSocket.getOption(option)); } } } - @Test(dataProvider = "datagramSocketOptionValues") + @ParameterizedTest + @MethodSource("datagramSocketOptionValues") public void closedBoundDatagramSocket(SocketOption option, List values) throws IOException { DatagramSocket datagramSocket = createClosedBoundDatagramSocket(); for (int i=0; i<3; i++); { for (T value : values) { - if (!RO.equals(value)) expectThrows(IOE, () -> datagramSocket.setOption(option, value)); - expectThrows(IOE, () -> datagramSocket.getOption(option)); + if (!RO.equals(value)) assertThrows(IOE, () -> datagramSocket.setOption(option, value)); + assertThrows(IOE, () -> datagramSocket.getOption(option)); } } } - @Test(dataProvider = "datagramSocketOptionValues") + @ParameterizedTest + @MethodSource("datagramSocketOptionValues") public void closedDatagramAdapter(SocketOption option, List values) throws IOException { DatagramSocket datagramSocket = createClosedBoundDatagramSocket(); for (int i=0; i<3; i++); { for (T value : values) { - if (!RO.equals(value)) expectThrows(IOE, () -> datagramSocket.setOption(option, value)); - expectThrows(IOE, () -> datagramSocket.getOption(option)); + if (!RO.equals(value)) assertThrows(IOE, () -> datagramSocket.setOption(option, value)); + assertThrows(IOE, () -> datagramSocket.getOption(option)); } } } // -- MulticastSocket - @DataProvider(name = "multicastSocketOptionValues") - public Object[][] multicastSocketOptionValues() throws Exception { + public static Object[][] multicastSocketOptionValues() throws Exception { try (MulticastSocket ms = new MulticastSocket()) { return ms.supportedOptions().stream() .map(so -> new Object[] {so, OPTION_VALUES_MAP.get(so)}) @@ -279,28 +284,30 @@ public class AfterClose { } } - @Test(dataProvider = "multicastSocketOptionValues") + @ParameterizedTest + @MethodSource("multicastSocketOptionValues") public void closedUnboundMulticastSocket(SocketOption option, List values) throws IOException { MulticastSocket multicastSocket = createClosedUnboundMulticastSocket(); for (int i=0; i<3; i++); { for (T value : values) { - if (!RO.equals(value)) expectThrows(IOE, () -> multicastSocket.setOption(option, value)); - expectThrows(IOE, () -> multicastSocket.getOption(option)); + if (!RO.equals(value)) assertThrows(IOE, () -> multicastSocket.setOption(option, value)); + assertThrows(IOE, () -> multicastSocket.getOption(option)); } } } - @Test(dataProvider = "multicastSocketOptionValues") + @ParameterizedTest + @MethodSource("multicastSocketOptionValues") public void closedBoundMulticastSocket(SocketOption option, List values) throws IOException { MulticastSocket multicastSocket = createClosedBoundMulticastSocket(); for (int i=0; i<3; i++); { for (T value : values) { - if (!RO.equals(value)) expectThrows(IOE, () -> multicastSocket.setOption(option, value)); - expectThrows(IOE, () -> multicastSocket.getOption(option)); + if (!RO.equals(value)) assertThrows(IOE, () -> multicastSocket.setOption(option, value)); + assertThrows(IOE, () -> multicastSocket.getOption(option)); } } } diff --git a/test/jdk/java/net/SocketOption/CachedImplOptions.java b/test/jdk/java/net/SocketOption/CachedImplOptions.java index 955414559c2..a5e3ec8f67c 100644 --- a/test/jdk/java/net/SocketOption/CachedImplOptions.java +++ b/test/jdk/java/net/SocketOption/CachedImplOptions.java @@ -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,8 +25,8 @@ * @test * @bug 8241988 * @summary Checks that the caching of options does not affect other impls - * @run testng/othervm CachedImplOptions - * @run testng/othervm -Djava.net.preferIPv4Stack=true CachedImplOptions + * @run junit/othervm ${test.main.class} + * @run junit/othervm -Djava.net.preferIPv4Stack=true ${test.main.class} */ import java.io.IOException; @@ -46,8 +46,11 @@ import java.net.SocketImpl; import java.net.SocketOption; import java.net.StandardSocketOptions; import java.util.Set; -import org.testng.annotations.Test; -import static org.testng.Assert.*; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; public class CachedImplOptions { @@ -58,16 +61,16 @@ public class CachedImplOptions { assertTrue(impl.supportedOptions().contains(StandardSocketOptions.SO_SNDBUF)); } try (var impl = new DatagramSocket(new FooDatagramSocketImpl()) {}) { - assertEquals(impl.supportedOptions(), Set.of(FooDatagramSocketImpl.FOO_OPTION)); - assertEquals(impl.supportedOptions(), Set.of(FooDatagramSocketImpl.FOO_OPTION)); + assertEquals(Set.of(FooDatagramSocketImpl.FOO_OPTION), impl.supportedOptions()); + assertEquals(Set.of(FooDatagramSocketImpl.FOO_OPTION), impl.supportedOptions()); } try (var impl = new DatagramSocket(new BarDatagramSocketImpl()) {}) { - assertEquals(impl.supportedOptions(), Set.of(BarDatagramSocketImpl.BAR_OPTION)); - assertEquals(impl.supportedOptions(), Set.of(BarDatagramSocketImpl.BAR_OPTION)); + assertEquals(Set.of(BarDatagramSocketImpl.BAR_OPTION), impl.supportedOptions()); + assertEquals(Set.of(BarDatagramSocketImpl.BAR_OPTION), impl.supportedOptions()); } try (var impl = new DatagramSocket(new BazDatagramSocketImpl()) {}) { - assertEquals(impl.supportedOptions(), Set.of(BazDatagramSocketImpl.BAZ_OPTION)); - assertEquals(impl.supportedOptions(), Set.of(BazDatagramSocketImpl.BAZ_OPTION)); + assertEquals(Set.of(BazDatagramSocketImpl.BAZ_OPTION), impl.supportedOptions()); + assertEquals(Set.of(BazDatagramSocketImpl.BAZ_OPTION), impl.supportedOptions()); } try (var impl = new DatagramSocket()) { assertTrue(impl.supportedOptions().contains(StandardSocketOptions.SO_SNDBUF)); @@ -86,8 +89,8 @@ public class CachedImplOptions { DatagramSocket.setDatagramSocketImplFactory(() -> new FooDatagramSocketImpl()); try (var impl = new MulticastSocket()) { - assertEquals(impl.supportedOptions(), Set.of(FooDatagramSocketImpl.FOO_OPTION)); - assertEquals(impl.supportedOptions(), Set.of(FooDatagramSocketImpl.FOO_OPTION)); + assertEquals(Set.of(FooDatagramSocketImpl.FOO_OPTION), impl.supportedOptions()); + assertEquals(Set.of(FooDatagramSocketImpl.FOO_OPTION), impl.supportedOptions()); } } @@ -144,16 +147,16 @@ public class CachedImplOptions { assertTrue(impl.supportedOptions().contains(StandardSocketOptions.SO_SNDBUF)); } try (var impl = new Socket(new LarrySocketImpl()) {}) { - assertEquals(impl.supportedOptions(), Set.of(LarrySocketImpl.LARRY_OPTION)); - assertEquals(impl.supportedOptions(), Set.of(LarrySocketImpl.LARRY_OPTION)); + assertEquals(Set.of(LarrySocketImpl.LARRY_OPTION), impl.supportedOptions()); + assertEquals(Set.of(LarrySocketImpl.LARRY_OPTION), impl.supportedOptions()); } try (var impl = new Socket(new CurlySocketImpl()) {}) { - assertEquals(impl.supportedOptions(), Set.of(CurlySocketImpl.CURLY_OPTION)); - assertEquals(impl.supportedOptions(), Set.of(CurlySocketImpl.CURLY_OPTION)); + assertEquals(Set.of(CurlySocketImpl.CURLY_OPTION), impl.supportedOptions()); + assertEquals(Set.of(CurlySocketImpl.CURLY_OPTION), impl.supportedOptions()); } try (var impl = new Socket(new MoeSocketImpl()) {}) { - assertEquals(impl.supportedOptions(), Set.of(MoeSocketImpl.MOE_OPTION)); - assertEquals(impl.supportedOptions(), Set.of(MoeSocketImpl.MOE_OPTION)); + assertEquals(Set.of(MoeSocketImpl.MOE_OPTION), impl.supportedOptions()); + assertEquals(Set.of(MoeSocketImpl.MOE_OPTION), impl.supportedOptions()); } try (var impl = new Socket()) { assertTrue(impl.supportedOptions().contains(StandardSocketOptions.SO_SNDBUF)); @@ -168,16 +171,16 @@ public class CachedImplOptions { assertTrue(impl.supportedOptions().contains(StandardSocketOptions.SO_RCVBUF)); } try (var impl = new ServerSocket(new LarrySocketImpl()) {}) { - assertEquals(impl.supportedOptions(), Set.of(LarrySocketImpl.LARRY_OPTION)); - assertEquals(impl.supportedOptions(), Set.of(LarrySocketImpl.LARRY_OPTION)); + assertEquals(Set.of(LarrySocketImpl.LARRY_OPTION), impl.supportedOptions()); + assertEquals(Set.of(LarrySocketImpl.LARRY_OPTION), impl.supportedOptions()); } try (var impl = new ServerSocket(new CurlySocketImpl()) {}) { - assertEquals(impl.supportedOptions(), Set.of(CurlySocketImpl.CURLY_OPTION)); - assertEquals(impl.supportedOptions(), Set.of(CurlySocketImpl.CURLY_OPTION)); + assertEquals(Set.of(CurlySocketImpl.CURLY_OPTION), impl.supportedOptions()); + assertEquals(Set.of(CurlySocketImpl.CURLY_OPTION), impl.supportedOptions()); } try (var impl = new ServerSocket(new MoeSocketImpl()) {}) { - assertEquals(impl.supportedOptions(), Set.of(MoeSocketImpl.MOE_OPTION)); - assertEquals(impl.supportedOptions(), Set.of(MoeSocketImpl.MOE_OPTION)); + assertEquals(Set.of(MoeSocketImpl.MOE_OPTION), impl.supportedOptions()); + assertEquals(Set.of(MoeSocketImpl.MOE_OPTION), impl.supportedOptions()); } try (var impl = new ServerSocket()) { assertTrue(impl.supportedOptions().contains(StandardSocketOptions.SO_RCVBUF)); diff --git a/test/jdk/java/net/SocketOption/ImmutableOptions.java b/test/jdk/java/net/SocketOption/ImmutableOptions.java index 372a0322a35..14b50afa8d3 100644 --- a/test/jdk/java/net/SocketOption/ImmutableOptions.java +++ b/test/jdk/java/net/SocketOption/ImmutableOptions.java @@ -26,69 +26,84 @@ * @bug 8148609 * @library /test/lib * @summary Assert that the set of socket options are immutable - * @run testng/othervm ImmutableOptions - * @run testng/othervm -Djava.net.preferIPv4Stack=true ImmutableOptions + * @run junit/othervm ${test.main.class} + * @run junit/othervm -Djava.net.preferIPv4Stack=true ${test.main.class} */ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.net.*; -import java.util.Optional; +import java.net.DatagramPacket; +import java.net.DatagramSocket; +import java.net.DatagramSocketImpl; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketAddress; +import java.net.SocketException; +import java.net.SocketImpl; +import java.net.SocketImplFactory; +import java.net.SocketOption; import java.util.Set; -import org.testng.SkipException; -import org.testng.annotations.BeforeTest; -import org.testng.annotations.Test; - import static jdk.test.lib.net.IPSupport.diagnoseConfigurationIssue; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertThrows; + public class ImmutableOptions { - @BeforeTest - void setupServerSocketFactory() throws IOException { - Optional configurationIssue = diagnoseConfigurationIssue(); - configurationIssue.map(SkipException::new).ifPresent(x -> { - throw x; - }); + @BeforeAll + public static void setupServerSocketFactory() throws IOException { + diagnoseConfigurationIssue().ifPresent(Assumptions::abort); ServerSocket.setSocketFactory(new ServerSocketImplFactory()); } - @Test(expectedExceptions = UnsupportedOperationException.class) + @Test public void socketThrows() throws IOException { CustomSocketImpl impl = new CustomSocketImpl(); Socket socket = new CustomSocket(impl); - socket.supportedOptions().clear(); + Set> options = socket.supportedOptions(); + assertThrows(UnsupportedOperationException.class, options::clear); } - @Test(expectedExceptions = UnsupportedOperationException.class) + @Test public void socketImplThrows() throws IOException { CustomSocketImpl impl = new CustomSocketImpl(); - impl.supportedOptions().clear(); + var options = impl.supportedOptions(); + assertThrows(UnsupportedOperationException.class, options::clear); } - @Test(expectedExceptions = UnsupportedOperationException.class) + @Test public void serverSocketThrows() throws IOException { ServerSocket ss = new ServerSocket(); - ss.supportedOptions().clear(); + Set> options = ss.supportedOptions(); + assertThrows(UnsupportedOperationException.class, options::clear); } - @Test(expectedExceptions = UnsupportedOperationException.class) + @Test public void serverSocketImplThrows() throws IOException { ServerSocket ss = new ServerSocket(); - ServerSocketImplFactory.mostRecentlyCreated.supportedOptions().clear(); + Set> options = + ServerSocketImplFactory.mostRecentlyCreated.supportedOptions(); + assertThrows(UnsupportedOperationException.class, options::clear); } - @Test(expectedExceptions = UnsupportedOperationException.class) + @Test public void datagramSocketThrows() throws IOException { CustomDatagramSocketImpl impl = new CustomDatagramSocketImpl(); DatagramSocket socket = new CustomDatagramSocket(impl); - socket.supportedOptions().clear(); + Set> options = socket.supportedOptions(); + assertThrows(UnsupportedOperationException.class, options::clear); } - @Test(expectedExceptions = UnsupportedOperationException.class) + @Test public void datagramSocketImplThrows() throws IOException { CustomDatagramSocketImpl impl = new CustomDatagramSocketImpl(); - impl.supportedOptions().clear(); + Set> options = impl.supportedOptions(); + assertThrows(UnsupportedOperationException.class, options::clear); } diff --git a/test/jdk/java/net/SocketOption/NullsAndBadValues.java b/test/jdk/java/net/SocketOption/NullsAndBadValues.java index 842fd4bc229..d6dab5fa327 100644 --- a/test/jdk/java/net/SocketOption/NullsAndBadValues.java +++ b/test/jdk/java/net/SocketOption/NullsAndBadValues.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2021, 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,8 +25,8 @@ * @test * @bug 8224477 * @summary Basic test for NPE, UOE, and IAE for get/setOption - * @run testng NullsAndBadValues - * @run testng/othervm -Dsun.net.useExclusiveBind=false NullsAndBadValues + * @run junit ${test.main.class} + * @run junit/othervm -Dsun.net.useExclusiveBind=false ${test.main.class} */ import java.net.DatagramSocket; @@ -43,11 +43,13 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Stream; -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; import static java.lang.Boolean.*; import static java.net.StandardSocketOptions.*; -import static org.testng.Assert.expectThrows; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import static org.junit.jupiter.api.Assertions.assertThrows; public class NullsAndBadValues { @@ -58,46 +60,46 @@ public class NullsAndBadValues { @Test public void nulls() throws Exception { try (Socket s = new Socket()) { - expectThrows(NPE, () -> s.setOption(null, null)); - expectThrows(NPE, () -> s.setOption(null, "")); - expectThrows(NPE, () -> s.setOption(null, 1)); - expectThrows(NPE, () -> s.getOption(null)); + assertThrows(NPE, () -> s.setOption(null, null)); + assertThrows(NPE, () -> s.setOption(null, "")); + assertThrows(NPE, () -> s.setOption(null, 1)); + assertThrows(NPE, () -> s.getOption(null)); } try (ServerSocket ss = new ServerSocket()) { - expectThrows(NPE, () -> ss.setOption(null, null)); - expectThrows(NPE, () -> ss.setOption(null, "")); - expectThrows(NPE, () -> ss.setOption(null, 1)); - expectThrows(NPE, () -> ss.getOption(null)); + assertThrows(NPE, () -> ss.setOption(null, null)); + assertThrows(NPE, () -> ss.setOption(null, "")); + assertThrows(NPE, () -> ss.setOption(null, 1)); + assertThrows(NPE, () -> ss.getOption(null)); } try (DatagramSocket ds = new DatagramSocket()) { - expectThrows(NPE, () -> ds.setOption(null, null)); - expectThrows(NPE, () -> ds.setOption(null, "")); - expectThrows(NPE, () -> ds.setOption(null, 1)); - expectThrows(NPE, () -> ds.getOption(null)); + assertThrows(NPE, () -> ds.setOption(null, null)); + assertThrows(NPE, () -> ds.setOption(null, "")); + assertThrows(NPE, () -> ds.setOption(null, 1)); + assertThrows(NPE, () -> ds.getOption(null)); } try (MulticastSocket ms = new MulticastSocket()) { - expectThrows(NPE, () -> ms.setOption(null, null)); - expectThrows(NPE, () -> ms.setOption(null, "")); - expectThrows(NPE, () -> ms.setOption(null, 1)); - expectThrows(NPE, () -> ms.getOption(null)); + assertThrows(NPE, () -> ms.setOption(null, null)); + assertThrows(NPE, () -> ms.setOption(null, "")); + assertThrows(NPE, () -> ms.setOption(null, 1)); + assertThrows(NPE, () -> ms.getOption(null)); } try (Socket sa = SocketChannel.open().socket()) { - expectThrows(NPE, () -> sa.setOption(null, null)); - expectThrows(NPE, () -> sa.setOption(null, "")); - expectThrows(NPE, () -> sa.setOption(null, 1)); - expectThrows(NPE, () -> sa.getOption(null)); + assertThrows(NPE, () -> sa.setOption(null, null)); + assertThrows(NPE, () -> sa.setOption(null, "")); + assertThrows(NPE, () -> sa.setOption(null, 1)); + assertThrows(NPE, () -> sa.getOption(null)); } try (ServerSocket ssa = ServerSocketChannel.open().socket()) { - expectThrows(NPE, () -> ssa.setOption(null, null)); - expectThrows(NPE, () -> ssa.setOption(null, "")); - expectThrows(NPE, () -> ssa.setOption(null, 1)); - expectThrows(NPE, () -> ssa.getOption(null)); + assertThrows(NPE, () -> ssa.setOption(null, null)); + assertThrows(NPE, () -> ssa.setOption(null, "")); + assertThrows(NPE, () -> ssa.setOption(null, 1)); + assertThrows(NPE, () -> ssa.getOption(null)); } try (DatagramSocket dsa = DatagramChannel.open().socket()) { - expectThrows(NPE, () -> dsa.setOption(null, null)); - expectThrows(NPE, () -> dsa.setOption(null, "")); - expectThrows(NPE, () -> dsa.setOption(null, 1)); - expectThrows(NPE, () -> dsa.getOption(null)); + assertThrows(NPE, () -> dsa.setOption(null, null)); + assertThrows(NPE, () -> dsa.setOption(null, "")); + assertThrows(NPE, () -> dsa.setOption(null, 1)); + assertThrows(NPE, () -> dsa.getOption(null)); } } @@ -114,67 +116,67 @@ public class NullsAndBadValues { @Test public void uoe() throws Exception { try (Socket s = new Socket()) { - expectThrows(UOE, () -> s.setOption(FAKE_SOCK_OPT, null)); - expectThrows(UOE, () -> s.setOption(FAKE_SOCK_OPT, TRUE)); - expectThrows(UOE, () -> s.setOption(FAKE_SOCK_OPT, FALSE)); - expectThrows(UOE, () -> s.setOption(RAW_SOCK_OPT, "")); - expectThrows(UOE, () -> s.setOption(RAW_SOCK_OPT, 1)); - expectThrows(UOE, () -> s.getOption(FAKE_SOCK_OPT)); - expectThrows(UOE, () -> s.getOption(RAW_SOCK_OPT)); + assertThrows(UOE, () -> s.setOption(FAKE_SOCK_OPT, null)); + assertThrows(UOE, () -> s.setOption(FAKE_SOCK_OPT, TRUE)); + assertThrows(UOE, () -> s.setOption(FAKE_SOCK_OPT, FALSE)); + assertThrows(UOE, () -> s.setOption(RAW_SOCK_OPT, "")); + assertThrows(UOE, () -> s.setOption(RAW_SOCK_OPT, 1)); + assertThrows(UOE, () -> s.getOption(FAKE_SOCK_OPT)); + assertThrows(UOE, () -> s.getOption(RAW_SOCK_OPT)); } try (ServerSocket ss = new ServerSocket()) { - expectThrows(UOE, () -> ss.setOption(FAKE_SOCK_OPT, null)); - expectThrows(UOE, () -> ss.setOption(FAKE_SOCK_OPT, TRUE)); - expectThrows(UOE, () -> ss.setOption(FAKE_SOCK_OPT, FALSE)); - expectThrows(UOE, () -> ss.setOption(RAW_SOCK_OPT, "")); - expectThrows(UOE, () -> ss.setOption(RAW_SOCK_OPT, 1)); - expectThrows(UOE, () -> ss.getOption(FAKE_SOCK_OPT)); - expectThrows(UOE, () -> ss.getOption(RAW_SOCK_OPT)); + assertThrows(UOE, () -> ss.setOption(FAKE_SOCK_OPT, null)); + assertThrows(UOE, () -> ss.setOption(FAKE_SOCK_OPT, TRUE)); + assertThrows(UOE, () -> ss.setOption(FAKE_SOCK_OPT, FALSE)); + assertThrows(UOE, () -> ss.setOption(RAW_SOCK_OPT, "")); + assertThrows(UOE, () -> ss.setOption(RAW_SOCK_OPT, 1)); + assertThrows(UOE, () -> ss.getOption(FAKE_SOCK_OPT)); + assertThrows(UOE, () -> ss.getOption(RAW_SOCK_OPT)); } try (DatagramSocket ds = new DatagramSocket()) { - expectThrows(UOE, () -> ds.setOption(FAKE_SOCK_OPT, null)); - expectThrows(UOE, () -> ds.setOption(FAKE_SOCK_OPT, TRUE)); - expectThrows(UOE, () -> ds.setOption(FAKE_SOCK_OPT, FALSE)); - expectThrows(UOE, () -> ds.setOption(RAW_SOCK_OPT, "")); - expectThrows(UOE, () -> ds.setOption(RAW_SOCK_OPT, 1)); - expectThrows(UOE, () -> ds.getOption(FAKE_SOCK_OPT)); - expectThrows(UOE, () -> ds.getOption(RAW_SOCK_OPT)); + assertThrows(UOE, () -> ds.setOption(FAKE_SOCK_OPT, null)); + assertThrows(UOE, () -> ds.setOption(FAKE_SOCK_OPT, TRUE)); + assertThrows(UOE, () -> ds.setOption(FAKE_SOCK_OPT, FALSE)); + assertThrows(UOE, () -> ds.setOption(RAW_SOCK_OPT, "")); + assertThrows(UOE, () -> ds.setOption(RAW_SOCK_OPT, 1)); + assertThrows(UOE, () -> ds.getOption(FAKE_SOCK_OPT)); + assertThrows(UOE, () -> ds.getOption(RAW_SOCK_OPT)); } try (MulticastSocket ms = new MulticastSocket()) { - expectThrows(UOE, () -> ms.setOption(FAKE_SOCK_OPT, null)); - expectThrows(UOE, () -> ms.setOption(FAKE_SOCK_OPT, TRUE)); - expectThrows(UOE, () -> ms.setOption(FAKE_SOCK_OPT, FALSE)); - expectThrows(UOE, () -> ms.setOption(RAW_SOCK_OPT, "")); - expectThrows(UOE, () -> ms.setOption(RAW_SOCK_OPT, 1)); - expectThrows(UOE, () -> ms.getOption(FAKE_SOCK_OPT)); - expectThrows(UOE, () -> ms.getOption(RAW_SOCK_OPT)); + assertThrows(UOE, () -> ms.setOption(FAKE_SOCK_OPT, null)); + assertThrows(UOE, () -> ms.setOption(FAKE_SOCK_OPT, TRUE)); + assertThrows(UOE, () -> ms.setOption(FAKE_SOCK_OPT, FALSE)); + assertThrows(UOE, () -> ms.setOption(RAW_SOCK_OPT, "")); + assertThrows(UOE, () -> ms.setOption(RAW_SOCK_OPT, 1)); + assertThrows(UOE, () -> ms.getOption(FAKE_SOCK_OPT)); + assertThrows(UOE, () -> ms.getOption(RAW_SOCK_OPT)); } try (Socket sa = SocketChannel.open().socket()) { - expectThrows(UOE, () -> sa.setOption(FAKE_SOCK_OPT, null)); - expectThrows(UOE, () -> sa.setOption(FAKE_SOCK_OPT, TRUE)); - expectThrows(UOE, () -> sa.setOption(FAKE_SOCK_OPT, FALSE)); - expectThrows(UOE, () -> sa.setOption(RAW_SOCK_OPT, "")); - expectThrows(UOE, () -> sa.setOption(RAW_SOCK_OPT, 1)); - expectThrows(UOE, () -> sa.getOption(FAKE_SOCK_OPT)); - expectThrows(UOE, () -> sa.getOption(RAW_SOCK_OPT)); + assertThrows(UOE, () -> sa.setOption(FAKE_SOCK_OPT, null)); + assertThrows(UOE, () -> sa.setOption(FAKE_SOCK_OPT, TRUE)); + assertThrows(UOE, () -> sa.setOption(FAKE_SOCK_OPT, FALSE)); + assertThrows(UOE, () -> sa.setOption(RAW_SOCK_OPT, "")); + assertThrows(UOE, () -> sa.setOption(RAW_SOCK_OPT, 1)); + assertThrows(UOE, () -> sa.getOption(FAKE_SOCK_OPT)); + assertThrows(UOE, () -> sa.getOption(RAW_SOCK_OPT)); } try (ServerSocket ssa = ServerSocketChannel.open().socket()) { - expectThrows(UOE, () -> ssa.setOption(FAKE_SOCK_OPT, null)); - expectThrows(UOE, () -> ssa.setOption(FAKE_SOCK_OPT, TRUE)); - expectThrows(UOE, () -> ssa.setOption(FAKE_SOCK_OPT, FALSE)); - expectThrows(UOE, () -> ssa.setOption(RAW_SOCK_OPT, "")); - expectThrows(UOE, () -> ssa.setOption(RAW_SOCK_OPT, 1)); - expectThrows(UOE, () -> ssa.getOption(FAKE_SOCK_OPT)); - expectThrows(UOE, () -> ssa.getOption(RAW_SOCK_OPT)); + assertThrows(UOE, () -> ssa.setOption(FAKE_SOCK_OPT, null)); + assertThrows(UOE, () -> ssa.setOption(FAKE_SOCK_OPT, TRUE)); + assertThrows(UOE, () -> ssa.setOption(FAKE_SOCK_OPT, FALSE)); + assertThrows(UOE, () -> ssa.setOption(RAW_SOCK_OPT, "")); + assertThrows(UOE, () -> ssa.setOption(RAW_SOCK_OPT, 1)); + assertThrows(UOE, () -> ssa.getOption(FAKE_SOCK_OPT)); + assertThrows(UOE, () -> ssa.getOption(RAW_SOCK_OPT)); } try (DatagramSocket dsa = DatagramChannel.open().socket()) { - expectThrows(UOE, () -> dsa.setOption(FAKE_SOCK_OPT, null)); - expectThrows(UOE, () -> dsa.setOption(FAKE_SOCK_OPT, TRUE)); - expectThrows(UOE, () -> dsa.setOption(FAKE_SOCK_OPT, FALSE)); - expectThrows(UOE, () -> dsa.setOption(RAW_SOCK_OPT, "")); - expectThrows(UOE, () -> dsa.setOption(RAW_SOCK_OPT, 1)); - expectThrows(UOE, () -> dsa.getOption(FAKE_SOCK_OPT)); - expectThrows(UOE, () -> dsa.getOption(RAW_SOCK_OPT)); + assertThrows(UOE, () -> dsa.setOption(FAKE_SOCK_OPT, null)); + assertThrows(UOE, () -> dsa.setOption(FAKE_SOCK_OPT, TRUE)); + assertThrows(UOE, () -> dsa.setOption(FAKE_SOCK_OPT, FALSE)); + assertThrows(UOE, () -> dsa.setOption(RAW_SOCK_OPT, "")); + assertThrows(UOE, () -> dsa.setOption(RAW_SOCK_OPT, 1)); + assertThrows(UOE, () -> dsa.getOption(FAKE_SOCK_OPT)); + assertThrows(UOE, () -> dsa.getOption(RAW_SOCK_OPT)); } } @@ -200,8 +202,7 @@ public class NullsAndBadValues { // -- Socket - @DataProvider(name = "socketBadOptionValues") - public Object[][] socketBadOptionValues() throws Exception { + public static Object[][] socketBadOptionValues() throws Exception { try (Socket s = new Socket()) { return s.supportedOptions().stream() .flatMap(NullsAndBadValues::socketOptionToBadValues) @@ -209,28 +210,29 @@ public class NullsAndBadValues { } } - @Test(dataProvider = "socketBadOptionValues") + @ParameterizedTest + @MethodSource("socketBadOptionValues") public void socket(SocketOption option, T value) throws Exception { try (Socket s = new Socket()) { - expectThrows(IAE, () -> s.setOption(option, value)); + assertThrows(IAE, () -> s.setOption(option, value)); } } - @Test(dataProvider = "socketBadOptionValues") + @ParameterizedTest + @MethodSource("socketBadOptionValues") public void socketAdapter(SocketOption option, T value) throws Exception { try (Socket s = SocketChannel.open().socket()) { - expectThrows(IAE, () -> s.setOption(option, value)); + assertThrows(IAE, () -> s.setOption(option, value)); } } // -- ServerSocket - @DataProvider(name = "serverSocketBadOptionValues") - public Object[][] serverSocketBadOptionValues() throws Exception { + public static Object[][] serverSocketBadOptionValues() throws Exception { try (ServerSocket ss = new ServerSocket()) { return ss.supportedOptions().stream() .flatMap(NullsAndBadValues::socketOptionToBadValues) @@ -238,16 +240,18 @@ public class NullsAndBadValues { } } - @Test(dataProvider = "serverSocketBadOptionValues") + @ParameterizedTest + @MethodSource("serverSocketBadOptionValues") public void serverSocket(SocketOption option, T value) throws Exception { try (ServerSocket ss = new ServerSocket()) { - expectThrows(IAE, () -> ss.setOption(option, value)); + assertThrows(IAE, () -> ss.setOption(option, value)); } } - @Test(dataProvider = "serverSocketBadOptionValues") + @ParameterizedTest + @MethodSource("serverSocketBadOptionValues") public void serverSocketAdapter(SocketOption option, T value) throws Exception { @@ -255,14 +259,13 @@ public class NullsAndBadValues { return; // SSC does not support IP_TOS try (ServerSocket ss = ServerSocketChannel.open().socket()) { - expectThrows(IAE, () -> ss.setOption(option, value)); + assertThrows(IAE, () -> ss.setOption(option, value)); } } // -- DatagramSocket - @DataProvider(name = "datagramSocketBadOptionValues") - public Object[][] datagramSocketBadOptionValues() throws Exception { + public static Object[][] datagramSocketBadOptionValues() throws Exception { try (DatagramSocket ds = new DatagramSocket()) { return ds.supportedOptions().stream() .flatMap(NullsAndBadValues::socketOptionToBadValues) @@ -270,28 +273,29 @@ public class NullsAndBadValues { } } - @Test(dataProvider = "datagramSocketBadOptionValues") + @ParameterizedTest + @MethodSource("datagramSocketBadOptionValues") public void datagramSocket(SocketOption option, T value) throws Exception { try (DatagramSocket ds = new DatagramSocket()) { - expectThrows(IAE, () -> ds.setOption(option, value)); + assertThrows(IAE, () -> ds.setOption(option, value)); } } - @Test(dataProvider = "datagramSocketBadOptionValues") + @ParameterizedTest + @MethodSource("datagramSocketBadOptionValues") public void datagramSocketAdapter(SocketOption option, T value) throws Exception { try (DatagramSocket ds = DatagramChannel.open().socket()) { - expectThrows(IAE, () -> ds.setOption(option, value)); + assertThrows(IAE, () -> ds.setOption(option, value)); } } // -- MulticastSocket - @DataProvider(name = "multicastSocketBadOptionValues") - public Object[][] multicastSocketBadOptionValues() throws Exception { + public static Object[][] multicastSocketBadOptionValues() throws Exception { try (MulticastSocket ms = new MulticastSocket()) { return ms.supportedOptions().stream() .flatMap(NullsAndBadValues::socketOptionToBadValues) @@ -299,12 +303,13 @@ public class NullsAndBadValues { } } - @Test(dataProvider = "multicastSocketBadOptionValues") + @ParameterizedTest + @MethodSource("multicastSocketBadOptionValues") public void multicastSocket(SocketOption option, T value) throws Exception { try (MulticastSocket ms = new MulticastSocket()) { - expectThrows(IAE, () -> ms.setOption(option, value)); + assertThrows(IAE, () -> ms.setOption(option, value)); } } diff --git a/test/jdk/java/net/SocketOption/RequiredOptions.java b/test/jdk/java/net/SocketOption/RequiredOptions.java index 3074d8fd274..2a1c689cae7 100644 --- a/test/jdk/java/net/SocketOption/RequiredOptions.java +++ b/test/jdk/java/net/SocketOption/RequiredOptions.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2021, 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 @@ -33,17 +33,18 @@ import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Set; import java.util.stream.Stream; -import org.testng.annotations.Test; -import org.testng.annotations.DataProvider; import static java.net.StandardSocketOptions.*; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + /* * @test * @bug 8235141 * @summary verifies that our implementation supports the set * of SocketOptions that are required by the API documentation. - * @run testng/othervm RequiredOptions + * @run junit/othervm ${test.main.class} */ public class RequiredOptions { @@ -60,7 +61,6 @@ public class RequiredOptions { return Set.of(Stream.of(options).flatMap(Set::stream).distinct().toArray(SocketOption[]::new)); } - @DataProvider(name = "sockets") static Object[][] provider() throws IOException { return new Object[][] { // UDP @@ -76,7 +76,8 @@ public class RequiredOptions { }; } - @Test(dataProvider = "sockets") + @ParameterizedTest + @MethodSource("provider") public void test(Configurable socket, Set> options) throws E { try (var s = socket) { diff --git a/test/jdk/java/net/SocketPermission/Ctor.java b/test/jdk/java/net/SocketPermission/Ctor.java index dde7596c052..20c194be847 100644 --- a/test/jdk/java/net/SocketPermission/Ctor.java +++ b/test/jdk/java/net/SocketPermission/Ctor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2019, 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,13 +26,14 @@ * @bug 4391898 8230407 * @summary SocketPermission(":",...) throws ArrayIndexOutOfBoundsException * SocketPermission constructor argument checks - * @run testng Ctor + * @run junit ${test.main.class} */ import java.net.SocketPermission; -import org.testng.annotations.Test; -import static java.lang.System.out; -import static org.testng.Assert.*; +import static java.lang.System.err; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertThrows; public class Ctor { @@ -48,39 +49,39 @@ public class Ctor { @Test public void npe() { NullPointerException e; - e = expectThrows(NPE, () -> new SocketPermission(null, null)); - out.println("caught expected NPE: " + e); - e = expectThrows(NPE, () -> new SocketPermission("foo", null)); - out.println("caught expected NPE: " + e); - e = expectThrows(NPE, () -> new SocketPermission(null, "connect")); - out.println("caught expected NPE: " + e); + e = assertThrows(NPE, () -> new SocketPermission(null, null)); + err.println("caught expected NPE: " + e); + e = assertThrows(NPE, () -> new SocketPermission("foo", null)); + err.println("caught expected NPE: " + e); + e = assertThrows(NPE, () -> new SocketPermission(null, "connect")); + err.println("caught expected NPE: " + e); } @Test public void iae() { IllegalArgumentException e; // host - e = expectThrows(IAE, () -> new SocketPermission("1:2:3:4", "connect")); - out.println("caught expected IAE: " + e); - e = expectThrows(IAE, () -> new SocketPermission("foo:5-4", "connect")); - out.println("caught expected IAE: " + e); + e = assertThrows(IAE, () -> new SocketPermission("1:2:3:4", "connect")); + err.println("caught expected IAE: " + e); + e = assertThrows(IAE, () -> new SocketPermission("foo:5-4", "connect")); + err.println("caught expected IAE: " + e); // actions - e = expectThrows(IAE, () -> new SocketPermission("foo", "")); - out.println("caught expected IAE: " + e); - e = expectThrows(IAE, () -> new SocketPermission("foo", "badAction")); - out.println("caught expected IAE: " + e); - e = expectThrows(IAE, () -> new SocketPermission("foo", "badAction,connect")); - out.println("caught expected IAE: " + e); - e = expectThrows(IAE, () -> new SocketPermission("foo", "badAction,,connect")); - out.println("caught expected IAE: " + e); - e = expectThrows(IAE, () -> new SocketPermission("foo", ",connect")); - out.println("caught expected IAE: " + e); - e = expectThrows(IAE, () -> new SocketPermission("foo", ",,connect")); - out.println("caught expected IAE: " + e); - e = expectThrows(IAE, () -> new SocketPermission("foo", "connect,")); - out.println("caught expected IAE: " + e); - e = expectThrows(IAE, () -> new SocketPermission("foo", "connect,,")); - out.println("caught expected IAE: " + e); + e = assertThrows(IAE, () -> new SocketPermission("foo", "")); + err.println("caught expected IAE: " + e); + e = assertThrows(IAE, () -> new SocketPermission("foo", "badAction")); + err.println("caught expected IAE: " + e); + e = assertThrows(IAE, () -> new SocketPermission("foo", "badAction,connect")); + err.println("caught expected IAE: " + e); + e = assertThrows(IAE, () -> new SocketPermission("foo", "badAction,,connect")); + err.println("caught expected IAE: " + e); + e = assertThrows(IAE, () -> new SocketPermission("foo", ",connect")); + err.println("caught expected IAE: " + e); + e = assertThrows(IAE, () -> new SocketPermission("foo", ",,connect")); + err.println("caught expected IAE: " + e); + e = assertThrows(IAE, () -> new SocketPermission("foo", "connect,")); + err.println("caught expected IAE: " + e); + e = assertThrows(IAE, () -> new SocketPermission("foo", "connect,,")); + err.println("caught expected IAE: " + e); } } diff --git a/test/jdk/java/net/Socks/SocksIPv6Test.java b/test/jdk/java/net/Socks/SocksIPv6Test.java index 1b277bb24fc..a8794724c54 100644 --- a/test/jdk/java/net/Socks/SocksIPv6Test.java +++ b/test/jdk/java/net/Socks/SocksIPv6Test.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2019, 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 @@ -25,8 +25,9 @@ * @bug 7100957 * @modules jdk.httpserver * @library /test/lib + * @build SocksServer * @summary Java doesn't correctly handle the SOCKS protocol when used over IPv6. - * @run testng SocksIPv6Test + * @run junit ${test.main.class} */ import java.io.BufferedReader; @@ -45,28 +46,28 @@ import java.net.SocketException; import java.net.NetworkInterface; import java.util.Collections; import java.util.List; -import com.sun.net.httpserver.*; import java.io.BufferedWriter; -import org.testng.SkipException; -import org.testng.annotations.AfterClass; -import org.testng.annotations.BeforeClass; -import org.testng.annotations.Test; +import com.sun.net.httpserver.HttpServer; import jdk.test.lib.NetworkConfiguration; -import static org.testng.Assert.*; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; public class SocksIPv6Test { - private HttpServer server; - private SocksServer socks; - private String response = "Hello."; + private static HttpServer server; + private static SocksServer socks; + private static final String response = "Hello."; - @BeforeClass - public void setUp() throws Exception { + @BeforeAll + public static void setUp() throws Exception { if (!ensureInet6AddressFamily() || !ensureIPv6OnLoopback()) { NetworkConfiguration.printSystemConfiguration(System.out); - throw new SkipException("Host does not support IPv6"); + Assumptions.abort("Host does not support IPv6"); } server = HttpServer.create(new InetSocketAddress("::1", 0), 0); @@ -93,7 +94,7 @@ public class SocksIPv6Test { }); } - private boolean ensureIPv6OnLoopback() throws Exception { + private static boolean ensureIPv6OnLoopback() throws Exception { boolean ipv6 = false; List nics = Collections.list(NetworkInterface.getNetworkInterfaces()); @@ -114,7 +115,7 @@ public class SocksIPv6Test { return ipv6; } - private boolean ensureInet6AddressFamily() throws IOException { + private static boolean ensureInet6AddressFamily() throws IOException { try (ServerSocket s = new ServerSocket()) { s.bind(new InetSocketAddress("::1", 0)); return true; @@ -124,7 +125,7 @@ public class SocksIPv6Test { return false; } - @Test(groups = "unit") + @Test public void testSocksOverIPv6() throws Exception { Proxy proxy = new Proxy(Proxy.Type.SOCKS, new InetSocketAddress("::1", socks.getPort())); @@ -135,10 +136,10 @@ public class SocksIPv6Test { new InputStreamReader(conn.getInputStream()))) { actual = reader.readLine(); } - assertEquals(actual, response); + assertEquals(response, actual); } - @Test(groups = "unit") + @Test public void testSocksOverIPv6Hostname() throws Exception { InetAddress ipv6Loopback = InetAddress.getByName("::1"); String ipv6Hostname = ipv6Loopback.getHostName(); @@ -155,24 +156,24 @@ public class SocksIPv6Test { ipv4HostAddress = null; } - System.out.println("ipv6Hostname: " + ipv6Hostname + " / " + ipv6HostAddress); - System.out.println("ipv4Hostname: " + ipv4Hostname + " / " + ipv4HostAddress); + System.err.println("ipv6Hostname: " + ipv6Hostname + " / " + ipv6HostAddress); + System.err.println("ipv4Hostname: " + ipv4Hostname + " / " + ipv4HostAddress); if (ipv6Hostname.equals(ipv6HostAddress)) { - System.out.println("Unable to get the hostname of the IPv6 loopback " + Assumptions.abort("Unable to get the hostname of the IPv6 loopback " + "address. Skipping test case."); return; } if (ipv4Hostname != null && ipv6Hostname.equals(ipv4Hostname)) { - System.out.println("IPv6 and IPv4 loopback addresses map to the" + Assumptions.abort("IPv6 and IPv4 loopback addresses map to the" + " same hostname. Skipping test case."); return; } if (!InetAddress.getByName(ipv6Hostname).getHostAddress() .equals(ipv6HostAddress)) { - System.out.println(ipv6Hostname + " resolves to \"" + Assumptions.abort(ipv6Hostname + " resolves to \"" + InetAddress.getByName(ipv6Hostname).getHostAddress() + "\", not \"" + ipv6HostAddress + "\". Skipping test case."); @@ -188,11 +189,11 @@ public class SocksIPv6Test { new InputStreamReader(conn.getInputStream()))) { actual = reader.readLine(); } - assertEquals(actual, response); + assertEquals(response, actual); } - @AfterClass - public void tearDown() { + @AfterAll + public static void tearDown() { if (server != null) { server.stop(1); } diff --git a/test/jdk/java/net/Socks/SocksSocketImplTest.java b/test/jdk/java/net/Socks/SocksSocketImplTest.java index 57d54defda3..05792aad784 100644 --- a/test/jdk/java/net/Socks/SocksSocketImplTest.java +++ b/test/jdk/java/net/Socks/SocksSocketImplTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2022, 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 @@ -21,10 +21,6 @@ * questions. */ -import org.testng.Assert; -import org.testng.annotations.AfterTest; -import org.testng.annotations.BeforeTest; -import org.testng.annotations.Test; import sun.net.spi.DefaultProxySelector; import java.io.IOException; @@ -37,25 +33,33 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.List; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.fail; + /** * @test * @bug 8230310 * @summary Tests java.net.SocksSocketImpl - * @run testng SocksSocketImplTest + * @run junit ${test.main.class} * @modules java.base/sun.net.spi:+open */ public class SocksSocketImplTest { - private ProxySelector previousDefault; + private static ProxySelector previousDefault; - @BeforeTest - public void beforeTest() { + @BeforeAll + public static void beforeTest() { previousDefault = ProxySelector.getDefault(); ProxySelector.setDefault(new SchemeStrippedProxySelector()); } - @AfterTest - public void afterTest() { + @AfterAll + public static void afterTest() { ProxySelector.setDefault(previousDefault); } @@ -65,17 +69,14 @@ public class SocksSocketImplTest { * which throws a {@link IllegalArgumentException}. This test then verifies that this IAE gets wrapped * by {@code java.net.SocksSocketImpl} into an {@link IOException} before being thrown * - * @throws Exception + * @throws Exception if the test fails */ @Test public void testIOEOnProxySelection() throws Exception { final int backlog = -1; final int port = 0; - try (ServerSocket ss = new ServerSocket(port, backlog, InetAddress.getLoopbackAddress()); - Socket s1 = new Socket(ss.getInetAddress(), ss.getLocalPort()); - Socket s2 = ss.accept()) { - Assert.fail("IOException was expected to be thrown, but wasn't"); - } catch (IOException ioe) { + try (ServerSocket ss = new ServerSocket(port, backlog, InetAddress.getLoopbackAddress())) { + IOException ioe = assertThrows(IOException.class, () -> new Socket(ss.getInetAddress(), ss.getLocalPort())); // expected // now verify the IOE was thrown for the correct expected reason if (!(ioe.getCause() instanceof IllegalArgumentException)) { @@ -96,7 +97,7 @@ public class SocksSocketImplTest { @Override public List select(final URI uri) { - System.out.println("Proxy selection for " + uri); + System.err.println("Proxy selection for " + uri); final URI schemeStrippedURI; try { // strip the scheme and pass the rest @@ -104,7 +105,7 @@ public class SocksSocketImplTest { } catch (URISyntaxException e) { throw new RuntimeException(e); } - System.out.println("Scheme stripped URI " + schemeStrippedURI + " is being used to select a proxy"); + System.err.println("Scheme stripped URI " + schemeStrippedURI + " is being used to select a proxy"); return super.select(schemeStrippedURI); } } diff --git a/test/jdk/java/net/UnixDomainSocketAddress/AddressTest.java b/test/jdk/java/net/UnixDomainSocketAddress/AddressTest.java index ba55eac46df..d5d1f295fd1 100644 --- a/test/jdk/java/net/UnixDomainSocketAddress/AddressTest.java +++ b/test/jdk/java/net/UnixDomainSocketAddress/AddressTest.java @@ -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 @@ -25,18 +25,16 @@ * @test * @bug 8231358 * @compile ../../nio/file/spi/testfsp/testfsp/TestProvider.java AddressTest.java - * @run testng/othervm AddressTest + * @run junit/othervm ${test.main.class} */ import java.net.UnixDomainSocketAddress; import java.net.URI; import java.nio.file.FileSystems; -import java.nio.file.spi.FileSystemProvider; import java.nio.file.Path; -import org.testng.annotations.Test; - -import static org.testng.Assert.assertThrows; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertThrows; /** * Verify that UnixDomainSocketAddress.of(path) throws IAE @@ -50,7 +48,7 @@ public class AddressTest { IllegalArgumentException.class; @Test - public static void runTest() throws Exception { + public void runTest() throws Exception { var fsp = new testfsp.TestProvider(FileSystems.getDefault().provider()); Path path = fsp.getPath(URI.create("file:/")); assertThrows(IAE, () -> UnixDomainSocketAddress.of(path)); diff --git a/test/jdk/java/net/UnixDomainSocketAddress/LengthTest.java b/test/jdk/java/net/UnixDomainSocketAddress/LengthTest.java index c34c8e001b7..d75658011c9 100644 --- a/test/jdk/java/net/UnixDomainSocketAddress/LengthTest.java +++ b/test/jdk/java/net/UnixDomainSocketAddress/LengthTest.java @@ -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 @@ -25,61 +25,54 @@ * @test * @summary Test UnixDomainSocketAddress constructor * @library /test/lib - * @run testng/othervm LengthTest + * @run junit/othervm ${test.main.class} */ -import org.testng.annotations.DataProvider; -import org.testng.annotations.Test; -import static java.lang.System.out; -import static java.net.StandardProtocolFamily.UNIX; -import static jdk.test.lib.Asserts.assertTrue; +import static java.lang.System.err; import java.net.UnixDomainSocketAddress; -import java.io.IOException; -import java.nio.channels.SocketChannel; import java.nio.file.Path; +import java.util.List; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; public class LengthTest { - final int namelen = 100; // length close to max + private static final int namelen = 100; // length close to max - @DataProvider(name = "strings") - public Object[][] strings() { - if (namelen == -1) - return new Object[][] {new String[]{""}}; - - return new Object[][]{ - {""}, - {new String(new char[100]).replaceAll("\0", "x")}, - {new String(new char[namelen]).replaceAll("\0", "x")}, - {new String(new char[namelen-1]).replaceAll("\0", "x")}, - }; + public static List strings() { + assert namelen > 0; + return List.of( + "", + "x".repeat(100), + "x".repeat(namelen), + "x".repeat(namelen - 1) + ); } - @Test(dataProvider = "strings") + @ParameterizedTest + @MethodSource("strings") public void expectPass(String s) { var addr = UnixDomainSocketAddress.of(s); - assertTrue(addr.getPath().toString().equals(s), "getPathName.equals(s)"); + assertEquals(s, addr.getPath().toString(), "getPathName.equals(s)"); var p = Path.of(s); addr = UnixDomainSocketAddress.of(p); - assertTrue(addr.getPath().equals(p), "getPath.equals(p)"); + assertEquals(p, addr.getPath(), "getPath.equals(p)"); } @Test public void expectNPE() { - try { - String s = null; - UnixDomainSocketAddress.of(s); - throw new RuntimeException("Expected NPE"); - } catch (NullPointerException npe) { - out.println("\tCaught expected exception: " + npe); - } - try { - Path p = null; - UnixDomainSocketAddress.of(p); - throw new RuntimeException("Expected NPE"); - } catch (NullPointerException npe) { - out.println("\tCaught expected exception: " + npe); - } + String s = null; + NullPointerException npe = + assertThrows(NullPointerException.class, () -> UnixDomainSocketAddress.of(s)); + err.println("\tCaugth expected NPE for UnixDomainSocketAddress.of(s): " + npe); + Path p = null; + npe = assertThrows(NullPointerException.class, () -> UnixDomainSocketAddress.of(p)); + err.println("\tCaugth expected NPE for UnixDomainSocketAddress.of(p): " + npe); } } diff --git a/test/jdk/java/net/UnixDomainSocketAddress/UnixDomainSocketAddressSerializationTest.java b/test/jdk/java/net/UnixDomainSocketAddress/UnixDomainSocketAddressSerializationTest.java index 305b4bee95e..b48805f61b3 100644 --- a/test/jdk/java/net/UnixDomainSocketAddress/UnixDomainSocketAddressSerializationTest.java +++ b/test/jdk/java/net/UnixDomainSocketAddress/UnixDomainSocketAddressSerializationTest.java @@ -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 @@ -21,7 +21,6 @@ * questions. */ -import org.testng.annotations.Test; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -35,51 +34,56 @@ import java.io.Serializable; import java.net.UnixDomainSocketAddress; import java.nio.file.Path; import static java.io.ObjectStreamConstants.*; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertTrue; -import static org.testng.Assert.expectThrows; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; /* * @test * @summary UnixDomainSocketAddress serialization test - * @run testng/othervm UnixDomainSocketAddressSerializationTest + * @run junit/othervm ${test.main.class} */ -@Test public class UnixDomainSocketAddressSerializationTest { private static final UnixDomainSocketAddress addr = UnixDomainSocketAddress.of(Path.of("test.sock")); - public static void test() throws Exception { - assertTrue(addr instanceof Serializable); + @Test + public void test() throws Exception { + assertInstanceOf(Serializable.class, addr); byte[] serialized = serialize(addr); assertTrue(serialized.length > 0); UnixDomainSocketAddress deserialized = deserialize(serialized, UnixDomainSocketAddress.class); - assertEquals(deserialized.getPath(), addr.getPath()); - assertEquals(deserialized.toString(), addr.toString()); - assertEquals(deserialized.hashCode(), addr.hashCode()); - assertEquals(deserialized, addr); + assertEquals(addr.getPath(), deserialized.getPath()); + assertEquals(addr.toString(), deserialized.toString()); + assertEquals(addr.hashCode(), deserialized.hashCode()); + assertEquals(addr, deserialized); } static final Class IOE = InvalidObjectException.class; static final Class NPE = NullPointerException.class; /** Tests that UnixDomainSocketAddress in the byte-stream is disallowed. */ - public static void testUnixDomainSocketAddressInStream() throws Exception { + @Test + public void testUnixDomainSocketAddressInStream() throws Exception { long suid = ObjectStreamClass.lookup(UnixDomainSocketAddress.class).getSerialVersionUID(); byte[] bytes = byteStreamFor(UnixDomainSocketAddress.class.getName(), suid); - expectThrows(IOE, () -> deserialize(bytes, UnixDomainSocketAddress.class)); + assertThrows(IOE, () -> deserialize(bytes, UnixDomainSocketAddress.class)); } /** Tests that SerialProxy with a null/absent path value in the byte-stream is disallowed. */ - public static void testSerialProxyNoStreamValues() throws Exception { + @Test + public void testSerialProxyNoStreamValues() throws Exception { Class c = Class.forName("java.net.UnixDomainSocketAddress$Ser"); long suid = ObjectStreamClass.lookup(c).getSerialVersionUID(); byte[] bytes = byteStreamFor(c.getName(), suid); - expectThrows(NPE, () -> deserialize(bytes, UnixDomainSocketAddress.class)); + assertThrows(NPE, () -> deserialize(bytes, UnixDomainSocketAddress.class)); } private static byte[] serialize(T t) From 3384c6736daf81aba08e15d6340065517747736e Mon Sep 17 00:00:00 2001 From: Bhavana Kilambi Date: Wed, 15 Apr 2026 12:27:56 +0000 Subject: [PATCH 059/108] 8366444: Add support for add/mul reduction operations for Float16 Reviewed-by: jbhateja, mchevalier, xgong, epeter --- src/hotspot/cpu/aarch64/aarch64_vector.ad | 90 ++++++- src/hotspot/cpu/aarch64/aarch64_vector_ad.m4 | 139 +++++++--- .../cpu/aarch64/c2_MacroAssembler_aarch64.cpp | 49 ++++ .../cpu/aarch64/c2_MacroAssembler_aarch64.hpp | 3 + src/hotspot/share/adlc/formssel.cpp | 8 +- src/hotspot/share/opto/classes.hpp | 2 + src/hotspot/share/opto/compile.cpp | 8 +- src/hotspot/share/opto/vectornode.cpp | 18 +- src/hotspot/share/opto/vectornode.hpp | 69 ++++- .../compiler/lib/ir_framework/IRNode.java | 10 + .../loopopts/superword/TestReductions.java | 141 +++++++++- .../TestFloat16VectorOperations.java | 240 +++++++++++++++++- .../vector/Float16OperationsBenchmark.java | 19 ++ .../bench/vm/compiler/VectorReduction2.java | 93 ++++++- 14 files changed, 820 insertions(+), 69 deletions(-) diff --git a/src/hotspot/cpu/aarch64/aarch64_vector.ad b/src/hotspot/cpu/aarch64/aarch64_vector.ad index 30b0c9c799b..4c854913e63 100644 --- a/src/hotspot/cpu/aarch64/aarch64_vector.ad +++ b/src/hotspot/cpu/aarch64/aarch64_vector.ad @@ -1,6 +1,6 @@ // // Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved. -// Copyright (c) 2020, 2025, Arm Limited. All rights reserved. +// Copyright (c) 2020, 2026, Arm Limited. All rights reserved. // DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. // // This code is free software; you can redistribute it and/or modify it @@ -247,10 +247,39 @@ source %{ case Op_MinVHF: case Op_MaxVHF: case Op_SqrtVHF: + if (UseSVE == 0 && !is_feat_fp16_supported()) { + return false; + } + break; + // At the time of writing this, the Vector API has no half-float (FP16) species. + // Consequently, AddReductionVHF and MulReductionVHF are only produced by the + // auto-vectorizer, which requires strictly ordered semantics for FP reductions. + // + // There is no direct Neon instruction that performs strictly ordered floating + // point add reduction. Hence, on Neon only machines, the add reduction operation + // is implemented as a scalarized sequence using half-precision scalar instruction + // FADD which requires FEAT_FP16 and ASIMDHP to be available on the target. + // On SVE machines (UseSVE > 0) however, there is a direct instruction (FADDA) which + // implements strictly ordered floating point add reduction which does not require + // the FEAT_FP16 and ASIMDHP checks as SVE supports half-precision floats by default. + case Op_AddReductionVHF: // FEAT_FP16 is enabled if both "fphp" and "asimdhp" features are supported. // Only the Neon instructions need this check. SVE supports half-precision floats // by default. - if (UseSVE == 0 && !is_feat_fp16_supported()) { + if (length_in_bytes < 8 || (UseSVE == 0 && !is_feat_fp16_supported())) { + return false; + } + break; + case Op_MulReductionVHF: + // There are no direct Neon/SVE instructions that perform strictly ordered + // floating point multiply reduction. + // For vector length ≤ 16 bytes, the reduction is implemented as a scalarized + // sequence using half-precision scalar instruction FMUL. This path requires + // FEAT_FP16 and ASIMDHP to be available on the target. + // For vector length > 16 bytes, this operation is disabled because there is no + // direct SVE instruction that performs a strictly ordered FP16 multiply + // reduction. + if (length_in_bytes < 8 || length_in_bytes > 16 || !is_feat_fp16_supported()) { return false; } break; @@ -300,6 +329,7 @@ source %{ case Op_VectorRearrange: case Op_MulReductionVD: case Op_MulReductionVF: + case Op_MulReductionVHF: case Op_MulReductionVI: case Op_MulReductionVL: case Op_CompressBitsV: @@ -364,6 +394,7 @@ source %{ case Op_VectorMaskCmp: case Op_LoadVectorGather: case Op_StoreVectorScatter: + case Op_AddReductionVHF: case Op_AddReductionVF: case Op_AddReductionVD: case Op_AndReductionV: @@ -3402,6 +3433,44 @@ instruct reduce_non_strict_order_add4F_neon(vRegF dst, vRegF fsrc, vReg vsrc, vR ins_pipe(pipe_slow); %} +// Add Reduction for Half floats (FP16). +// Neon does not provide direct instructions for strictly ordered floating-point add reductions. +// On Neon-only targets (UseSVE = 0), this operation is implemented as a sequence of scalar additions: +// values equal to the vector width are loaded into a vector register, each lane is extracted, +// and its value is accumulated into the running sum, producing a final scalar result. +instruct reduce_addHF_neon(vRegF dst, vRegF fsrc, vReg vsrc, vReg tmp) %{ + predicate(UseSVE == 0); + match(Set dst (AddReductionVHF fsrc vsrc)); + effect(TEMP_DEF dst, TEMP tmp); + format %{ "reduce_addHF $dst, $fsrc, $vsrc\t# 4HF/8HF. KILL $tmp" %} + ins_encode %{ + uint length_in_bytes = Matcher::vector_length_in_bytes(this, $vsrc); + __ neon_reduce_add_fp16($dst$$FloatRegister, $fsrc$$FloatRegister, + $vsrc$$FloatRegister, length_in_bytes, $tmp$$FloatRegister); + %} + ins_pipe(pipe_slow); +%} + +// This rule calculates the reduction result in strict order. Two cases will +// reach here: +// 1. Non strictly-ordered AddReductionVHF when vector size > 128-bits. For example - +// AddReductionVHF generated by Vector API. For vector size > 128-bits, it is more +// beneficial performance-wise to generate direct SVE instruction even if it is +// strictly ordered. +// 2. Strictly-ordered AddReductionVHF. For example - AddReductionVHF generated by +// auto-vectorization on SVE machine. +instruct reduce_addHF_sve(vRegF dst_src1, vReg src2) %{ + predicate(UseSVE > 0); + match(Set dst_src1 (AddReductionVHF dst_src1 src2)); + format %{ "reduce_addHF_sve $dst_src1, $dst_src1, $src2" %} + ins_encode %{ + uint length_in_bytes = Matcher::vector_length_in_bytes(this, $src2); + assert(length_in_bytes == MaxVectorSize, "invalid vector length"); + __ sve_fadda($dst_src1$$FloatRegister, __ H, ptrue, $src2$$FloatRegister); + %} + ins_pipe(pipe_slow); +%} + // This rule calculates the reduction result in strict order. Two cases will // reach here: // 1. Non strictly-ordered AddReductionVF when vector size > 128-bits. For example - @@ -3492,12 +3561,14 @@ instruct reduce_addL_masked(iRegLNoSp dst, iRegL isrc, vReg vsrc, pRegGov pg, vR ins_pipe(pipe_slow); %} -instruct reduce_addF_masked(vRegF dst_src1, vReg src2, pRegGov pg) %{ +instruct reduce_addFHF_masked(vRegF dst_src1, vReg src2, pRegGov pg) %{ predicate(UseSVE > 0); + match(Set dst_src1 (AddReductionVHF (Binary dst_src1 src2) pg)); match(Set dst_src1 (AddReductionVF (Binary dst_src1 src2) pg)); - format %{ "reduce_addF_masked $dst_src1, $pg, $dst_src1, $src2" %} + format %{ "reduce_addFHF_masked $dst_src1, $pg, $dst_src1, $src2" %} ins_encode %{ - __ sve_fadda($dst_src1$$FloatRegister, __ S, + BasicType bt = Matcher::vector_element_basic_type(this, $src2); + __ sve_fadda($dst_src1$$FloatRegister, __ elemType_to_regVariant(bt), $pg$$PRegister, $src2$$FloatRegister); %} ins_pipe(pipe_slow); @@ -3545,14 +3616,17 @@ instruct reduce_mulL(iRegLNoSp dst, iRegL isrc, vReg vsrc) %{ ins_pipe(pipe_slow); %} -instruct reduce_mulF(vRegF dst, vRegF fsrc, vReg vsrc, vReg tmp) %{ + +instruct reduce_mulFHF(vRegF dst, vRegF fsrc, vReg vsrc, vReg tmp) %{ predicate(Matcher::vector_length_in_bytes(n->in(2)) <= 16); + match(Set dst (MulReductionVHF fsrc vsrc)); match(Set dst (MulReductionVF fsrc vsrc)); effect(TEMP_DEF dst, TEMP tmp); - format %{ "reduce_mulF $dst, $fsrc, $vsrc\t# 2F/4F. KILL $tmp" %} + format %{ "reduce_mulFHF $dst, $fsrc, $vsrc\t# 2F/4F/4HF/8HF. KILL $tmp" %} ins_encode %{ uint length_in_bytes = Matcher::vector_length_in_bytes(this, $vsrc); - __ neon_reduce_mul_fp($dst$$FloatRegister, T_FLOAT, $fsrc$$FloatRegister, + BasicType bt = Matcher::vector_element_basic_type(this, $vsrc); + __ neon_reduce_mul_fp($dst$$FloatRegister, bt, $fsrc$$FloatRegister, $vsrc$$FloatRegister, length_in_bytes, $tmp$$FloatRegister); %} ins_pipe(pipe_slow); diff --git a/src/hotspot/cpu/aarch64/aarch64_vector_ad.m4 b/src/hotspot/cpu/aarch64/aarch64_vector_ad.m4 index 48bffb3cf35..58ed234194a 100644 --- a/src/hotspot/cpu/aarch64/aarch64_vector_ad.m4 +++ b/src/hotspot/cpu/aarch64/aarch64_vector_ad.m4 @@ -1,6 +1,6 @@ // // Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved. -// Copyright (c) 2020, 2025, Arm Limited. All rights reserved. +// Copyright (c) 2020, 2026, Arm Limited. All rights reserved. // DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. // // This code is free software; you can redistribute it and/or modify it @@ -237,10 +237,39 @@ source %{ case Op_MinVHF: case Op_MaxVHF: case Op_SqrtVHF: + if (UseSVE == 0 && !is_feat_fp16_supported()) { + return false; + } + break; + // At the time of writing this, the Vector API has no half-float (FP16) species. + // Consequently, AddReductionVHF and MulReductionVHF are only produced by the + // auto-vectorizer, which requires strictly ordered semantics for FP reductions. + // + // There is no direct Neon instruction that performs strictly ordered floating + // point add reduction. Hence, on Neon only machines, the add reduction operation + // is implemented as a scalarized sequence using half-precision scalar instruction + // FADD which requires FEAT_FP16 and ASIMDHP to be available on the target. + // On SVE machines (UseSVE > 0) however, there is a direct instruction (FADDA) which + // implements strictly ordered floating point add reduction which does not require + // the FEAT_FP16 and ASIMDHP checks as SVE supports half-precision floats by default. + case Op_AddReductionVHF: // FEAT_FP16 is enabled if both "fphp" and "asimdhp" features are supported. // Only the Neon instructions need this check. SVE supports half-precision floats // by default. - if (UseSVE == 0 && !is_feat_fp16_supported()) { + if (length_in_bytes < 8 || (UseSVE == 0 && !is_feat_fp16_supported())) { + return false; + } + break; + case Op_MulReductionVHF: + // There are no direct Neon/SVE instructions that perform strictly ordered + // floating point multiply reduction. + // For vector length ≤ 16 bytes, the reduction is implemented as a scalarized + // sequence using half-precision scalar instruction FMUL. This path requires + // FEAT_FP16 and ASIMDHP to be available on the target. + // For vector length > 16 bytes, this operation is disabled because there is no + // direct SVE instruction that performs a strictly ordered FP16 multiply + // reduction. + if (length_in_bytes < 8 || length_in_bytes > 16 || !is_feat_fp16_supported()) { return false; } break; @@ -290,6 +319,7 @@ source %{ case Op_VectorRearrange: case Op_MulReductionVD: case Op_MulReductionVF: + case Op_MulReductionVHF: case Op_MulReductionVI: case Op_MulReductionVL: case Op_CompressBitsV: @@ -354,6 +384,7 @@ source %{ case Op_VectorMaskCmp: case Op_LoadVectorGather: case Op_StoreVectorScatter: + case Op_AddReductionVHF: case Op_AddReductionVF: case Op_AddReductionVD: case Op_AndReductionV: @@ -2063,6 +2094,25 @@ instruct reduce_non_strict_order_add4F_neon(vRegF dst, vRegF fsrc, vReg vsrc, vR ins_pipe(pipe_slow); %} dnl + +// Add Reduction for Half floats (FP16). +// Neon does not provide direct instructions for strictly ordered floating-point add reductions. +// On Neon-only targets (UseSVE = 0), this operation is implemented as a sequence of scalar additions: +// values equal to the vector width are loaded into a vector register, each lane is extracted, +// and its value is accumulated into the running sum, producing a final scalar result. +instruct reduce_addHF_neon(vRegF dst, vRegF fsrc, vReg vsrc, vReg tmp) %{ + predicate(UseSVE == 0); + match(Set dst (AddReductionVHF fsrc vsrc)); + effect(TEMP_DEF dst, TEMP tmp); + format %{ "reduce_addHF $dst, $fsrc, $vsrc\t# 4HF/8HF. KILL $tmp" %} + ins_encode %{ + uint length_in_bytes = Matcher::vector_length_in_bytes(this, $vsrc); + __ neon_reduce_add_fp16($dst$$FloatRegister, $fsrc$$FloatRegister, + $vsrc$$FloatRegister, length_in_bytes, $tmp$$FloatRegister); + %} + ins_pipe(pipe_slow); +%} +dnl dnl REDUCE_ADD_FP_SVE($1, $2 ) dnl REDUCE_ADD_FP_SVE(type, size) define(`REDUCE_ADD_FP_SVE', ` @@ -2074,21 +2124,26 @@ define(`REDUCE_ADD_FP_SVE', ` // strictly ordered. // 2. Strictly-ordered AddReductionV$1. For example - AddReductionV$1 generated by // auto-vectorization on SVE machine. -instruct reduce_add$1_sve(vReg$1 dst_src1, vReg src2) %{ - predicate(!VM_Version::use_neon_for_vector(Matcher::vector_length_in_bytes(n->in(2))) || - n->as_Reduction()->requires_strict_order()); +instruct reduce_add$1_sve(vReg`'ifelse($1, HF, F, $1) dst_src1, vReg src2) %{ + ifelse($1, HF, + `predicate(UseSVE > 0);', + `predicate(!VM_Version::use_neon_for_vector(Matcher::vector_length_in_bytes(n->in(2))) || + n->as_Reduction()->requires_strict_order());') match(Set dst_src1 (AddReductionV$1 dst_src1 src2)); format %{ "reduce_add$1_sve $dst_src1, $dst_src1, $src2" %} ins_encode %{ - assert(UseSVE > 0, "must be sve"); - uint length_in_bytes = Matcher::vector_length_in_bytes(this, $src2); + ifelse($1, HF, `', + `assert(UseSVE > 0, "must be sve"); + ')dnl +uint length_in_bytes = Matcher::vector_length_in_bytes(this, $src2); assert(length_in_bytes == MaxVectorSize, "invalid vector length"); __ sve_fadda($dst_src1$$FloatRegister, __ $2, ptrue, $src2$$FloatRegister); %} ins_pipe(pipe_slow); %}')dnl dnl -REDUCE_ADD_FP_SVE(F, S) +REDUCE_ADD_FP_SVE(HF, H) +REDUCE_ADD_FP_SVE(F, S) // reduction addD @@ -2129,21 +2184,30 @@ dnl dnl REDUCE_ADD_FP_PREDICATE($1, $2 ) dnl REDUCE_ADD_FP_PREDICATE(insn_name, op_name) define(`REDUCE_ADD_FP_PREDICATE', ` -instruct reduce_add$1_masked(vReg$1 dst_src1, vReg src2, pRegGov pg) %{ +instruct reduce_add$1_masked(vReg$2 dst_src1, vReg src2, pRegGov pg) %{ predicate(UseSVE > 0); - match(Set dst_src1 (AddReductionV$1 (Binary dst_src1 src2) pg)); + ifelse($2, F, + `match(Set dst_src1 (AddReductionVHF (Binary dst_src1 src2) pg)); + match(Set dst_src1 (AddReductionV$2 (Binary dst_src1 src2) pg));', + `match(Set dst_src1 (AddReductionV$2 (Binary dst_src1 src2) pg));') format %{ "reduce_add$1_masked $dst_src1, $pg, $dst_src1, $src2" %} ins_encode %{ - __ sve_fadda($dst_src1$$FloatRegister, __ $2, - $pg$$PRegister, $src2$$FloatRegister); + ifelse($2, F, + `BasicType bt = Matcher::vector_element_basic_type(this, $src2); + ',)dnl +ifelse($2, F, + `__ sve_fadda($dst_src1$$FloatRegister, __ elemType_to_regVariant(bt), + $pg$$PRegister, $src2$$FloatRegister);', + `__ sve_fadda($dst_src1$$FloatRegister, __ $2, + $pg$$PRegister, $src2$$FloatRegister);') %} ins_pipe(pipe_slow); %}')dnl dnl REDUCE_ADD_INT_PREDICATE(I, iRegIorL2I) REDUCE_ADD_INT_PREDICATE(L, iRegL) -REDUCE_ADD_FP_PREDICATE(F, S) -REDUCE_ADD_FP_PREDICATE(D, D) +REDUCE_ADD_FP_PREDICATE(FHF, F) +REDUCE_ADD_FP_PREDICATE(D, D) // ------------------------------ Vector reduction mul ------------------------- @@ -2176,30 +2240,37 @@ instruct reduce_mulL(iRegLNoSp dst, iRegL isrc, vReg vsrc) %{ ins_pipe(pipe_slow); %} -instruct reduce_mulF(vRegF dst, vRegF fsrc, vReg vsrc, vReg tmp) %{ - predicate(Matcher::vector_length_in_bytes(n->in(2)) <= 16); - match(Set dst (MulReductionVF fsrc vsrc)); +dnl REDUCE_MUL_FP($1, $2 ) +dnl REDUCE_MUL_FP(insn_name, op_name) +define(`REDUCE_MUL_FP', ` +instruct reduce_mul$1(vReg$2 dst, vReg$2 ifelse($2, F, fsrc, dsrc), vReg vsrc, vReg tmp) %{ + predicate(Matcher::vector_length_in_bytes(n->in(2)) ifelse($2, F, <=, ==) 16); + ifelse($2, F, + `match(Set dst (MulReductionVHF fsrc vsrc)); + match(Set dst (MulReductionV$2 fsrc vsrc));', + `match(Set dst (MulReductionV$2 dsrc vsrc));') effect(TEMP_DEF dst, TEMP tmp); - format %{ "reduce_mulF $dst, $fsrc, $vsrc\t# 2F/4F. KILL $tmp" %} + ifelse($2, F, + `format %{ "reduce_mul$1 $dst, $fsrc, $vsrc\t# 2F/4F/4HF/8HF. KILL $tmp" %}', + `format %{ "reduce_mul$1 $dst, $dsrc, $vsrc\t# 2D. KILL $tmp" %}') ins_encode %{ - uint length_in_bytes = Matcher::vector_length_in_bytes(this, $vsrc); - __ neon_reduce_mul_fp($dst$$FloatRegister, T_FLOAT, $fsrc$$FloatRegister, - $vsrc$$FloatRegister, length_in_bytes, $tmp$$FloatRegister); + ifelse($2, F, + `uint length_in_bytes = Matcher::vector_length_in_bytes(this, $vsrc); + ',)dnl +ifelse($2, F, + `BasicType bt = Matcher::vector_element_basic_type(this, $vsrc); + ',)dnl +ifelse($2, F, + `__ neon_reduce_mul_fp($dst$$FloatRegister, bt, $fsrc$$FloatRegister, + $vsrc$$FloatRegister, length_in_bytes, $tmp$$FloatRegister);', + `__ neon_reduce_mul_fp($dst$$FloatRegister, T_DOUBLE, $dsrc$$FloatRegister, + $vsrc$$FloatRegister, 16, $tmp$$FloatRegister);') %} ins_pipe(pipe_slow); -%} - -instruct reduce_mulD(vRegD dst, vRegD dsrc, vReg vsrc, vReg tmp) %{ - predicate(Matcher::vector_length_in_bytes(n->in(2)) == 16); - match(Set dst (MulReductionVD dsrc vsrc)); - effect(TEMP_DEF dst, TEMP tmp); - format %{ "reduce_mulD $dst, $dsrc, $vsrc\t# 2D. KILL $tmp" %} - ins_encode %{ - __ neon_reduce_mul_fp($dst$$FloatRegister, T_DOUBLE, $dsrc$$FloatRegister, - $vsrc$$FloatRegister, 16, $tmp$$FloatRegister); - %} - ins_pipe(pipe_slow); -%} +%}')dnl +dnl +REDUCE_MUL_FP(FHF, F) +REDUCE_MUL_FP(D, D) dnl dnl REDUCE_BITWISE_OP_NEON($1, $2 $3 $4 ) diff --git a/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp index bba37a7a390..3c179f21c14 100644 --- a/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright 2026 Arm Limited and/or its affiliates. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -1883,6 +1884,27 @@ void C2_MacroAssembler::neon_reduce_mul_fp(FloatRegister dst, BasicType bt, BLOCK_COMMENT("neon_reduce_mul_fp {"); switch(bt) { + // The T_SHORT type below is for Float16 type which also uses floating-point + // instructions. + case T_SHORT: + fmulh(dst, fsrc, vsrc); + ext(vtmp, T8B, vsrc, vsrc, 2); + fmulh(dst, dst, vtmp); + ext(vtmp, T8B, vsrc, vsrc, 4); + fmulh(dst, dst, vtmp); + ext(vtmp, T8B, vsrc, vsrc, 6); + fmulh(dst, dst, vtmp); + if (isQ) { + ext(vtmp, T16B, vsrc, vsrc, 8); + fmulh(dst, dst, vtmp); + ext(vtmp, T16B, vsrc, vsrc, 10); + fmulh(dst, dst, vtmp); + ext(vtmp, T16B, vsrc, vsrc, 12); + fmulh(dst, dst, vtmp); + ext(vtmp, T16B, vsrc, vsrc, 14); + fmulh(dst, dst, vtmp); + } + break; case T_FLOAT: fmuls(dst, fsrc, vsrc); ins(vtmp, S, vsrc, 0, 1); @@ -1907,6 +1929,33 @@ void C2_MacroAssembler::neon_reduce_mul_fp(FloatRegister dst, BasicType bt, BLOCK_COMMENT("} neon_reduce_mul_fp"); } +// Vector reduction add for half float type with ASIMD instructions. +void C2_MacroAssembler::neon_reduce_add_fp16(FloatRegister dst, FloatRegister fsrc, FloatRegister vsrc, + unsigned vector_length_in_bytes, FloatRegister vtmp) { + assert(vector_length_in_bytes == 8 || vector_length_in_bytes == 16, "unsupported"); + bool isQ = vector_length_in_bytes == 16; + + BLOCK_COMMENT("neon_reduce_add_fp16 {"); + faddh(dst, fsrc, vsrc); + ext(vtmp, T8B, vsrc, vsrc, 2); + faddh(dst, dst, vtmp); + ext(vtmp, T8B, vsrc, vsrc, 4); + faddh(dst, dst, vtmp); + ext(vtmp, T8B, vsrc, vsrc, 6); + faddh(dst, dst, vtmp); + if (isQ) { + ext(vtmp, T16B, vsrc, vsrc, 8); + faddh(dst, dst, vtmp); + ext(vtmp, T16B, vsrc, vsrc, 10); + faddh(dst, dst, vtmp); + ext(vtmp, T16B, vsrc, vsrc, 12); + faddh(dst, dst, vtmp); + ext(vtmp, T16B, vsrc, vsrc, 14); + faddh(dst, dst, vtmp); + } + BLOCK_COMMENT("} neon_reduce_add_fp16"); +} + // Helper to select logical instruction void C2_MacroAssembler::neon_reduce_logical_helper(int opc, bool is64, Register Rd, Register Rn, Register Rm, diff --git a/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.hpp b/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.hpp index 5964bb60d4f..f96d3ffb863 100644 --- a/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.hpp @@ -177,6 +177,9 @@ FloatRegister fsrc, FloatRegister vsrc, unsigned vector_length_in_bytes, FloatRegister vtmp); + void neon_reduce_add_fp16(FloatRegister dst, FloatRegister fsrc, FloatRegister vsrc, + unsigned vector_length_in_bytes, FloatRegister vtmp); + void neon_reduce_logical(int opc, Register dst, BasicType bt, Register isrc, FloatRegister vsrc, unsigned vector_length_in_bytes); diff --git a/src/hotspot/share/adlc/formssel.cpp b/src/hotspot/share/adlc/formssel.cpp index 4dd2bff7c89..5802217c1c1 100644 --- a/src/hotspot/share/adlc/formssel.cpp +++ b/src/hotspot/share/adlc/formssel.cpp @@ -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 @@ -4233,11 +4233,13 @@ int MatchRule::is_expensive() const { strcmp(opType,"PopulateIndex")==0 || strcmp(opType,"AddReductionVI")==0 || strcmp(opType,"AddReductionVL")==0 || + strcmp(opType,"AddReductionVHF")==0 || strcmp(opType,"AddReductionVF")==0 || strcmp(opType,"AddReductionVD")==0 || strcmp(opType,"MulReductionVI")==0 || strcmp(opType,"MulReductionVL")==0 || strcmp(opType,"MulReductionVF")==0 || + strcmp(opType,"MulReductionVHF")==0 || strcmp(opType,"MulReductionVD")==0 || strcmp(opType,"MinReductionV")==0 || strcmp(opType,"MaxReductionV")==0 || @@ -4348,9 +4350,9 @@ bool MatchRule::is_vector() const { "MaxV", "MinV", "MinVHF", "MaxVHF", "UMinV", "UMaxV", "CompressV", "ExpandV", "CompressM", "CompressBitsV", "ExpandBitsV", "AddReductionVI", "AddReductionVL", - "AddReductionVF", "AddReductionVD", + "AddReductionVHF", "AddReductionVF", "AddReductionVD", "MulReductionVI", "MulReductionVL", - "MulReductionVF", "MulReductionVD", + "MulReductionVHF", "MulReductionVF", "MulReductionVD", "MaxReductionV", "MinReductionV", "AndReductionV", "OrReductionV", "XorReductionV", "MulAddVS2VI", "MacroLogicV", diff --git a/src/hotspot/share/opto/classes.hpp b/src/hotspot/share/opto/classes.hpp index 3c1a68d6224..0f67cf90183 100644 --- a/src/hotspot/share/opto/classes.hpp +++ b/src/hotspot/share/opto/classes.hpp @@ -396,6 +396,7 @@ macro(AddVL) macro(AddReductionVL) macro(AddVF) macro(AddVHF) +macro(AddReductionVHF) macro(AddReductionVF) macro(AddVD) macro(AddReductionVD) @@ -413,6 +414,7 @@ macro(MulReductionVI) macro(MulVL) macro(MulReductionVL) macro(MulVF) +macro(MulReductionVHF) macro(MulReductionVF) macro(MulVD) macro(MulReductionVD) diff --git a/src/hotspot/share/opto/compile.cpp b/src/hotspot/share/opto/compile.cpp index db3cbd4109c..e05df8ea716 100644 --- a/src/hotspot/share/opto/compile.cpp +++ b/src/hotspot/share/opto/compile.cpp @@ -3200,10 +3200,10 @@ void Compile::final_graph_reshaping_impl(Node *n, Final_Reshape_Counts& frc, Uni !n->in(2)->is_Con() ) { // right use is not a constant // Check for commutative opcode switch( nop ) { - case Op_AddI: case Op_AddF: case Op_AddD: case Op_AddL: + case Op_AddI: case Op_AddF: case Op_AddD: case Op_AddHF: case Op_AddL: case Op_MaxI: case Op_MaxL: case Op_MaxF: case Op_MaxD: case Op_MinI: case Op_MinL: case Op_MinF: case Op_MinD: - case Op_MulI: case Op_MulF: case Op_MulD: case Op_MulL: + case Op_MulI: case Op_MulF: case Op_MulD: case Op_MulHF: case Op_MulL: case Op_AndL: case Op_XorL: case Op_OrL: case Op_AndI: case Op_XorI: case Op_OrI: { // Move "last use" input to left by swapping inputs @@ -3282,6 +3282,8 @@ void Compile::handle_div_mod_op(Node* n, BasicType bt, bool is_unsigned) { void Compile::final_graph_reshaping_main_switch(Node* n, Final_Reshape_Counts& frc, uint nop, Unique_Node_List& dead_nodes) { switch( nop ) { // Count all float operations that may use FPU + case Op_AddHF: + case Op_MulHF: case Op_AddF: case Op_SubF: case Op_MulF: @@ -3788,10 +3790,12 @@ void Compile::final_graph_reshaping_main_switch(Node* n, Final_Reshape_Counts& f case Op_AddReductionVI: case Op_AddReductionVL: + case Op_AddReductionVHF: case Op_AddReductionVF: case Op_AddReductionVD: case Op_MulReductionVI: case Op_MulReductionVL: + case Op_MulReductionVHF: case Op_MulReductionVF: case Op_MulReductionVD: case Op_MinReductionV: diff --git a/src/hotspot/share/opto/vectornode.cpp b/src/hotspot/share/opto/vectornode.cpp index dbadc18da01..d19aa476196 100644 --- a/src/hotspot/share/opto/vectornode.cpp +++ b/src/hotspot/share/opto/vectornode.cpp @@ -1260,6 +1260,10 @@ int ReductionNode::opcode(int opc, BasicType bt) { assert(bt == T_LONG, "must be"); vopc = Op_AddReductionVL; break; + case Op_AddHF: + assert(bt == T_SHORT, "must be"); + vopc = Op_AddReductionVHF; + break; case Op_AddF: assert(bt == T_FLOAT, "must be"); vopc = Op_AddReductionVF; @@ -1284,6 +1288,10 @@ int ReductionNode::opcode(int opc, BasicType bt) { assert(bt == T_LONG, "must be"); vopc = Op_MulReductionVL; break; + case Op_MulHF: + assert(bt == T_SHORT, "must be"); + vopc = Op_MulReductionVHF; + break; case Op_MulF: assert(bt == T_FLOAT, "must be"); vopc = Op_MulReductionVF; @@ -1432,10 +1440,12 @@ ReductionNode* ReductionNode::make(int opc, Node* ctrl, Node* n1, Node* n2, Basi switch (vopc) { case Op_AddReductionVI: return new AddReductionVINode(ctrl, n1, n2); case Op_AddReductionVL: return new AddReductionVLNode(ctrl, n1, n2); + case Op_AddReductionVHF: return new AddReductionVHFNode(ctrl, n1, n2, requires_strict_order); case Op_AddReductionVF: return new AddReductionVFNode(ctrl, n1, n2, requires_strict_order); case Op_AddReductionVD: return new AddReductionVDNode(ctrl, n1, n2, requires_strict_order); case Op_MulReductionVI: return new MulReductionVINode(ctrl, n1, n2); case Op_MulReductionVL: return new MulReductionVLNode(ctrl, n1, n2); + case Op_MulReductionVHF: return new MulReductionVHFNode(ctrl, n1, n2, requires_strict_order); case Op_MulReductionVF: return new MulReductionVFNode(ctrl, n1, n2, requires_strict_order); case Op_MulReductionVD: return new MulReductionVDNode(ctrl, n1, n2, requires_strict_order); case Op_MinReductionV: return new MinReductionVNode (ctrl, n1, n2); @@ -1613,6 +1623,8 @@ Node* ReductionNode::make_identity_con_scalar(PhaseGVN& gvn, int sopc, BasicType return nullptr; } break; + case Op_AddReductionVHF: + return gvn.makecon(TypeH::ZERO); case Op_AddReductionVI: // fallthrough case Op_AddReductionVL: // fallthrough case Op_AddReductionVF: // fallthrough @@ -1624,6 +1636,8 @@ Node* ReductionNode::make_identity_con_scalar(PhaseGVN& gvn, int sopc, BasicType return gvn.makecon(TypeInt::ONE); case Op_MulReductionVL: return gvn.makecon(TypeLong::ONE); + case Op_MulReductionVHF: + return gvn.makecon(TypeH::ONE); case Op_MulReductionVF: return gvn.makecon(TypeF::ONE); case Op_MulReductionVD: @@ -1716,12 +1730,14 @@ bool ReductionNode::auto_vectorization_requires_strict_order(int vopc) { // These are cases that all have associative operations, which can // thus be reordered, allowing non-strict order reductions. return false; + case Op_AddReductionVHF: + case Op_MulReductionVHF: case Op_AddReductionVF: case Op_MulReductionVF: case Op_AddReductionVD: case Op_MulReductionVD: // Floating-point addition and multiplication are non-associative, - // so AddReductionVF/D and MulReductionVF/D require strict ordering + // so AddReductionVHF/VF/VD and MulReductionVHF/VF/VD require strict ordering // in auto-vectorization. return true; default: diff --git a/src/hotspot/share/opto/vectornode.hpp b/src/hotspot/share/opto/vectornode.hpp index de866898302..91cff9fcae8 100644 --- a/src/hotspot/share/opto/vectornode.hpp +++ b/src/hotspot/share/opto/vectornode.hpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2007, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright 2026 Arm Limited and/or its affiliates. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -322,7 +323,7 @@ class ReductionNode : public Node { virtual uint size_of() const { return sizeof(*this); } // Floating-point addition and multiplication are non-associative, so - // AddReductionVF/D and MulReductionVF/D require strict ordering + // AddReductionVHF/F/D and MulReductionVHF/F/D require strict ordering // in auto-vectorization. Vector API can generate AddReductionVF/D // and MulReductionVF/VD without strict ordering, which can benefit // some platforms. @@ -359,6 +360,35 @@ public: virtual int Opcode() const; }; +// Vector add half float as a reduction +class AddReductionVHFNode : public ReductionNode { +private: + // True if add reduction operation for half floats requires strict ordering. + // As an example - The value is true when add reduction for half floats is auto-vectorized + // as auto-vectorization mandates strict ordering but the value is false when this node + // is generated through VectorAPI as VectorAPI does not impose any such rules on ordering. + const bool _requires_strict_order; + +public: + // _requires_strict_order is set to true by default as mandated by auto-vectorization + AddReductionVHFNode(Node* ctrl, Node* in1, Node* in2, bool requires_strict_order = true) : + ReductionNode(ctrl, in1, in2), _requires_strict_order(requires_strict_order) {} + + int Opcode() const override; + bool requires_strict_order() const override { return _requires_strict_order; } + + uint hash() const override { return Node::hash() + _requires_strict_order; } + + bool cmp(const Node& n) const override { + return Node::cmp(n) && _requires_strict_order == ((ReductionNode&)n).requires_strict_order(); + } + + uint size_of() const override { return sizeof(*this); } + + const Type* bottom_type() const override { return Type::HALF_FLOAT; } + uint ideal_reg() const override { return Op_RegF; } +}; + // Vector add float as a reduction class AddReductionVFNode : public ReductionNode { private: @@ -368,7 +398,7 @@ private: // is generated through VectorAPI as VectorAPI does not impose any such rules on ordering. const bool _requires_strict_order; public: - //_requires_strict_order is set to true by default as mandated by auto-vectorization + // _requires_strict_order is set to true by default as mandated by auto-vectorization AddReductionVFNode(Node* ctrl, Node* in1, Node* in2, bool requires_strict_order = true) : ReductionNode(ctrl, in1, in2), _requires_strict_order(requires_strict_order) {} @@ -394,7 +424,7 @@ private: // is generated through VectorAPI as VectorAPI does not impose any such rules on ordering. const bool _requires_strict_order; public: - //_requires_strict_order is set to true by default as mandated by auto-vectorization + // _requires_strict_order is set to true by default as mandated by auto-vectorization AddReductionVDNode(Node* ctrl, Node* in1, Node* in2, bool requires_strict_order = true) : ReductionNode(ctrl, in1, in2), _requires_strict_order(requires_strict_order) {} @@ -578,6 +608,35 @@ public: virtual int Opcode() const; }; +// Vector multiply half float as a reduction +class MulReductionVHFNode : public ReductionNode { +private: + // True if mul reduction operation for half floats requires strict ordering. + // As an example - The value is true when mul reduction for half floats is auto-vectorized + // as auto-vectorization mandates strict ordering but the value is false when this node + // is generated through VectorAPI as VectorAPI does not impose any such rules on ordering. + const bool _requires_strict_order; + +public: + // _requires_strict_order is set to true by default as mandated by auto-vectorization + MulReductionVHFNode(Node* ctrl, Node* in1, Node* in2, bool requires_strict_order = true) : + ReductionNode(ctrl, in1, in2), _requires_strict_order(requires_strict_order) {} + + int Opcode() const override; + bool requires_strict_order() const override { return _requires_strict_order; } + + uint hash() const override { return Node::hash() + _requires_strict_order; } + + bool cmp(const Node& n) const override { + return Node::cmp(n) && _requires_strict_order == ((ReductionNode&)n).requires_strict_order(); + } + + uint size_of() const override { return sizeof(*this); } + + const Type* bottom_type() const override { return Type::HALF_FLOAT; } + uint ideal_reg() const override { return Op_RegF; } +}; + // Vector multiply float as a reduction class MulReductionVFNode : public ReductionNode { // True if mul reduction operation for floats requires strict ordering. @@ -586,7 +645,7 @@ class MulReductionVFNode : public ReductionNode { // is generated through VectorAPI as VectorAPI does not impose any such rules on ordering. const bool _requires_strict_order; public: - //_requires_strict_order is set to true by default as mandated by auto-vectorization + // _requires_strict_order is set to true by default as mandated by auto-vectorization MulReductionVFNode(Node* ctrl, Node* in1, Node* in2, bool requires_strict_order = true) : ReductionNode(ctrl, in1, in2), _requires_strict_order(requires_strict_order) {} @@ -611,7 +670,7 @@ class MulReductionVDNode : public ReductionNode { // is generated through VectorAPI as VectorAPI does not impose any such rules on ordering. const bool _requires_strict_order; public: - //_requires_strict_order is set to true by default as mandated by auto-vectorization + // _requires_strict_order is set to true by default as mandated by auto-vectorization MulReductionVDNode(Node* ctrl, Node* in1, Node* in2, bool requires_strict_order = true) : ReductionNode(ctrl, in1, in2), _requires_strict_order(requires_strict_order) {} diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java index f3fc4afb170..55d591acdb3 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java @@ -323,6 +323,11 @@ public class IRNode { superWordNodes(ADD_REDUCTION_VF, "AddReductionVF"); } + public static final String ADD_REDUCTION_VHF = PREFIX + "ADD_REDUCTION_VHF" + POSTFIX; + static { + superWordNodes(ADD_REDUCTION_VHF, "AddReductionVHF"); + } + public static final String ADD_REDUCTION_VI = PREFIX + "ADD_REDUCTION_VI" + POSTFIX; static { superWordNodes(ADD_REDUCTION_VI, "AddReductionVI"); @@ -1576,6 +1581,11 @@ public class IRNode { superWordNodes(MUL_REDUCTION_VF, "MulReductionVF"); } + public static final String MUL_REDUCTION_VHF = PREFIX + "MUL_REDUCTION_VHF" + POSTFIX; + static { + superWordNodes(MUL_REDUCTION_VHF, "MulReductionVHF"); + } + public static final String MUL_REDUCTION_VI = PREFIX + "MUL_REDUCTION_VI" + POSTFIX; static { superWordNodes(MUL_REDUCTION_VI, "MulReductionVI"); diff --git a/test/hotspot/jtreg/compiler/loopopts/superword/TestReductions.java b/test/hotspot/jtreg/compiler/loopopts/superword/TestReductions.java index 5c085e6a3a3..97a55ae2074 100644 --- a/test/hotspot/jtreg/compiler/loopopts/superword/TestReductions.java +++ b/test/hotspot/jtreg/compiler/loopopts/superword/TestReductions.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright 2026 Arm Limited and/or its affiliates. * 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 @@ * @test id=no-vectorization * @bug 8340093 8342095 * @summary Test vectorization of reduction loops. + * @modules jdk.incubator.vector * @library /test/lib / * @run driver compiler.loopopts.superword.TestReductions P0 */ @@ -33,6 +35,7 @@ * @test id=vanilla * @bug 8340093 8342095 * @summary Test vectorization of reduction loops. + * @modules jdk.incubator.vector * @library /test/lib / * @run driver compiler.loopopts.superword.TestReductions P1 */ @@ -41,6 +44,7 @@ * @test id=force-vectorization * @bug 8340093 8342095 * @summary Test vectorization of reduction loops. + * @modules jdk.incubator.vector * @library /test/lib / * @run driver compiler.loopopts.superword.TestReductions P2 */ @@ -50,10 +54,14 @@ package compiler.loopopts.superword; import java.util.Map; import java.util.HashMap; +import jdk.incubator.vector.Float16; + import compiler.lib.ir_framework.*; import compiler.lib.verify.*; import static compiler.lib.generators.Generators.G; import compiler.lib.generators.Generator; +import static java.lang.Float.floatToFloat16; +import static jdk.incubator.vector.Float16.*; /** * Note: there is a corresponding JMH benchmark: @@ -65,6 +73,7 @@ public class TestReductions { private static final Generator GEN_L = G.longs(); private static final Generator GEN_F = G.floats(); private static final Generator GEN_D = G.doubles(); + private static final Generator GEN_F16 = G.float16s(); private static byte[] in1B = fillRandom(new byte[SIZE]); private static byte[] in2B = fillRandom(new byte[SIZE]); @@ -89,6 +98,9 @@ public class TestReductions { private static double[] in1D = fillRandom(new double[SIZE]); private static double[] in2D = fillRandom(new double[SIZE]); private static double[] in3D = fillRandom(new double[SIZE]); + private static short[] in1F16 = fillRandomFloat16(new short[SIZE]); + private static short[] in2F16 = fillRandomFloat16(new short[SIZE]); + private static short[] in3F16 = fillRandomFloat16(new short[SIZE]); interface TestFunction { Object run(); @@ -102,6 +114,7 @@ public class TestReductions { public static void main(String[] args) { TestFramework framework = new TestFramework(TestReductions.class); + framework.addFlags("--add-modules=jdk.incubator.vector"); switch (args[0]) { case "P0" -> { framework.addFlags("-XX:+UnlockDiagnosticVMOptions", "-XX:AutoVectorizationOverrideProfitability=0"); } case "P1" -> { framework.addFlags("-XX:+UnlockDiagnosticVMOptions", "-XX:AutoVectorizationOverrideProfitability=1"); } @@ -250,6 +263,13 @@ public class TestReductions { tests.put("doubleMinBig", TestReductions::doubleMinBig); tests.put("doubleMaxBig", TestReductions::doubleMaxBig); + tests.put("float16AddSimple", TestReductions::float16AddSimple); + tests.put("float16MulSimple", TestReductions::float16MulSimple); + tests.put("float16AddDotProduct", TestReductions::float16AddDotProduct); + tests.put("float16MulDotProduct", TestReductions::float16MulDotProduct); + tests.put("float16AddBig", TestReductions::float16AddBig); + tests.put("float16MulBig", TestReductions::float16MulBig); + // Compute gold value for all test methods before compilation for (Map.Entry entry : tests.entrySet()) { String name = entry.getKey(); @@ -394,7 +414,14 @@ public class TestReductions { "doubleAddBig", "doubleMulBig", "doubleMinBig", - "doubleMaxBig"}) + "doubleMaxBig", + + "float16AddSimple", + "float16MulSimple", + "float16AddDotProduct", + "float16MulDotProduct", + "float16AddBig", + "float16MulBig"}) public void runTests() { for (Map.Entry entry : tests.entrySet()) { String name = entry.getKey(); @@ -453,6 +480,13 @@ public class TestReductions { return a; } + static short[] fillRandomFloat16(short[] a) { + for (int i = 0; i < a.length; i++) { + a[i] = GEN_F16.next(); + } + return a; + } + // ---------byte***Simple ------------------------------------------------------------ @Test @IR(counts = {IRNode.LOAD_VECTOR_B, IRNode.VECTOR_SIZE + "min(max_int, max_byte)", "> 0", @@ -2628,5 +2662,110 @@ public class TestReductions { return acc; } + // ---------float16***Simple ------------------------------------------------------------ + @Test + @IR(counts = {IRNode.ADD_REDUCTION_VHF, "> 0"}, + applyIfCPUFeature = {"sve", "true"}, + applyIf = {"AutoVectorizationOverrideProfitability", "> 0"}) + @IR(counts = {IRNode.ADD_REDUCTION_VHF, "> 0"}, + applyIfCPUFeatureAnd = {"fphp", "true", "asimdhp", "true"}, + applyIf = {"AutoVectorizationOverrideProfitability", "> 0"}) + @IR(failOn = IRNode.ADD_REDUCTION_VHF, + applyIf = {"AutoVectorizationOverrideProfitability", "= 0"}) + private static Float16 float16AddSimple() { + short acc = (short)0; // neutral element + for (int i = 0; i < SIZE; i++) { + acc = float16ToRawShortBits(add(shortBitsToFloat16(acc), shortBitsToFloat16(in1F16[i]))); + } + return shortBitsToFloat16(acc); + } + + @Test + @IR(counts = {IRNode.MUL_REDUCTION_VHF, "> 0"}, + applyIfCPUFeatureAnd = {"fphp", "true", "asimdhp", "true"}, + applyIfAnd = {"AutoVectorizationOverrideProfitability", "> 0", "MaxVectorSize", "<=16"}) + @IR(failOn = IRNode.MUL_REDUCTION_VHF, + applyIf = {"AutoVectorizationOverrideProfitability", "= 0"}) + private static Float16 float16MulSimple() { + short acc = floatToFloat16(1.0f); // neutral element + for (int i = 0; i < SIZE; i++) { + acc = float16ToRawShortBits(multiply(shortBitsToFloat16(acc), shortBitsToFloat16(in1F16[i]))); + } + return shortBitsToFloat16(acc); + } + + // ---------float16***DotProduct ------------------------------------------------------------ + @Test + @IR(counts = {IRNode.ADD_REDUCTION_VHF, "> 0"}, + applyIfCPUFeature = {"sve", "true"}, + applyIf = {"AutoVectorizationOverrideProfitability", "> 0"}) + @IR(counts = {IRNode.ADD_REDUCTION_VHF, "> 0"}, + applyIfCPUFeatureAnd = {"fphp", "true", "asimdhp", "true"}, + applyIf = {"AutoVectorizationOverrideProfitability", "> 0"}) + @IR(failOn = IRNode.ADD_REDUCTION_VHF, + applyIf = {"AutoVectorizationOverrideProfitability", "= 0"}) + private static Float16 float16AddDotProduct() { + short acc = (short)0; // neutral element + for (int i = 0; i < SIZE; i++) { + Float16 val = multiply(shortBitsToFloat16(in1F16[i]), shortBitsToFloat16(in2F16[i])); + acc = float16ToRawShortBits(add(shortBitsToFloat16(acc), val)); + } + return shortBitsToFloat16(acc); + } + + @Test + @IR(counts = {IRNode.MUL_REDUCTION_VHF, "> 0"}, + applyIfCPUFeatureAnd = {"fphp", "true", "asimdhp", "true"}, + applyIfAnd = {"AutoVectorizationOverrideProfitability", "> 0", "MaxVectorSize", "<=16"}) + @IR(failOn = IRNode.MUL_REDUCTION_VHF, + applyIf = {"AutoVectorizationOverrideProfitability", "= 0"}) + private static Float16 float16MulDotProduct() { + short acc = floatToFloat16(1.0f); // neutral element + for (int i = 0; i < SIZE; i++) { + Float16 val = multiply(shortBitsToFloat16(in1F16[i]), shortBitsToFloat16(in2F16[i])); + acc = float16ToRawShortBits(multiply(shortBitsToFloat16(acc), val)); + } + return shortBitsToFloat16(acc); + } + + // ---------float16***Big ------------------------------------------------------------ + @Test + @IR(counts = {IRNode.ADD_REDUCTION_VHF, "> 0"}, + applyIfCPUFeature = {"sve", "true"}, + applyIf = {"AutoVectorizationOverrideProfitability", "> 0"}) + @IR(counts = {IRNode.ADD_REDUCTION_VHF, "> 0"}, + applyIfCPUFeatureAnd = {"fphp", "true", "asimdhp", "true"}, + applyIf = {"AutoVectorizationOverrideProfitability", "> 0"}) + @IR(failOn = IRNode.ADD_REDUCTION_VHF, + applyIf = {"AutoVectorizationOverrideProfitability", "= 0"}) + private static Float16 float16AddBig() { + short acc = (short)0; // neutral element + for (int i = 0; i < SIZE; i++) { + Float16 a = shortBitsToFloat16(in1F16[i]); + Float16 b = shortBitsToFloat16(in2F16[i]); + Float16 c = shortBitsToFloat16(in3F16[i]); + Float16 val = add(multiply(a, b), add(multiply(a, c), multiply(b, c))); + acc = float16ToRawShortBits(add(shortBitsToFloat16(acc), val)); + } + return shortBitsToFloat16(acc); + } + + @Test + @IR(counts = {IRNode.MUL_REDUCTION_VHF, "> 0"}, + applyIfCPUFeatureAnd = {"fphp", "true", "asimdhp", "true"}, + applyIfAnd = {"AutoVectorizationOverrideProfitability", "> 0", "MaxVectorSize", "<=16"}) + @IR(failOn = IRNode.MUL_REDUCTION_VHF, + applyIf = {"AutoVectorizationOverrideProfitability", "= 0"}) + private static Float16 float16MulBig() { + short acc = floatToFloat16(1.0f); // neutral element + for (int i = 0; i < SIZE; i++) { + Float16 a = shortBitsToFloat16(in1F16[i]); + Float16 b = shortBitsToFloat16(in2F16[i]); + Float16 c = shortBitsToFloat16(in3F16[i]); + Float16 val = add(multiply(a, b), add(multiply(a, c), multiply(b, c))); + acc = float16ToRawShortBits(multiply(shortBitsToFloat16(acc), val)); + } + return shortBitsToFloat16(acc); + } } diff --git a/test/hotspot/jtreg/compiler/vectorization/TestFloat16VectorOperations.java b/test/hotspot/jtreg/compiler/vectorization/TestFloat16VectorOperations.java index f3c27c4d278..929a70f304a 100644 --- a/test/hotspot/jtreg/compiler/vectorization/TestFloat16VectorOperations.java +++ b/test/hotspot/jtreg/compiler/vectorization/TestFloat16VectorOperations.java @@ -1,6 +1,6 @@ /* * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2025, Arm Limited. All rights reserved. + * Copyright 2025, 2026 Arm Limited and/or its affiliates. * 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,19 +33,21 @@ */ package compiler.vectorization; -import compiler.lib.ir_framework.*; -import jdk.incubator.vector.Float16; -import static jdk.incubator.vector.Float16.*; -import static java.lang.Float.*; -import java.util.Arrays; -import jdk.test.lib.*; import compiler.lib.generators.Generator; +import compiler.lib.ir_framework.*; +import compiler.lib.verify.Verify; +import java.util.Arrays; +import jdk.incubator.vector.Float16; +import jdk.test.lib.*; import static compiler.lib.generators.Generators.G; +import static java.lang.Float.*; +import static jdk.incubator.vector.Float16.*; public class TestFloat16VectorOperations { private short[] input1; private short[] input2; private short[] input3; + private Float16[] input4; private short[] output; private static short FP16_SCALAR = (short)0x7777; private static final int LEN = 2048; @@ -77,6 +79,7 @@ public class TestFloat16VectorOperations { input1 = new short[LEN]; input2 = new short[LEN]; input3 = new short[LEN]; + input4 = new Float16[LEN]; output = new short[LEN]; short min_value = float16ToRawShortBits(Float16.MIN_VALUE); @@ -86,6 +89,7 @@ public class TestFloat16VectorOperations { input1[i] = gen.next(); input2[i] = gen.next(); input3[i] = gen.next(); + input4[i] = shortBitsToFloat16(gen.next()); } } @@ -349,7 +353,9 @@ public class TestFloat16VectorOperations { @Test @Warmup(50) @IR(counts = {IRNode.SUB_VHF, " >0 "}, - applyIfCPUFeature = {"avx512_fp16", "true"}) + applyIfCPUFeatureOr = {"avx512_fp16", "true", "sve", "true"}) + @IR(counts = {IRNode.SUB_VHF, " >0 "}, + applyIfCPUFeatureAnd = {"fphp", "true", "asimdhp", "true"}) public void vectorSubConstInputFloat16() { for (int i = 0; i < LEN; ++i) { output[i] = float16ToRawShortBits(subtract(shortBitsToFloat16(input1[i]), FP16_CONST)); @@ -367,7 +373,9 @@ public class TestFloat16VectorOperations { @Test @Warmup(50) @IR(counts = {IRNode.MUL_VHF, " >0 "}, - applyIfCPUFeature = {"avx512_fp16", "true"}) + applyIfCPUFeatureOr = {"avx512_fp16", "true", "sve", "true"}) + @IR(counts = {IRNode.MUL_VHF, " >0 "}, + applyIfCPUFeatureAnd = {"fphp", "true", "asimdhp", "true"}) public void vectorMulConstantInputFloat16() { for (int i = 0; i < LEN; ++i) { output[i] = float16ToRawShortBits(multiply(FP16_CONST, shortBitsToFloat16(input2[i]))); @@ -385,7 +393,9 @@ public class TestFloat16VectorOperations { @Test @Warmup(50) @IR(counts = {IRNode.DIV_VHF, " >0 "}, - applyIfCPUFeature = {"avx512_fp16", "true"}) + applyIfCPUFeatureOr = {"avx512_fp16", "true", "sve", "true"}) + @IR(counts = {IRNode.DIV_VHF, " >0 "}, + applyIfCPUFeatureAnd = {"fphp", "true", "asimdhp", "true"}) public void vectorDivConstantInputFloat16() { for (int i = 0; i < LEN; ++i) { output[i] = float16ToRawShortBits(divide(FP16_CONST, shortBitsToFloat16(input2[i]))); @@ -403,7 +413,9 @@ public class TestFloat16VectorOperations { @Test @Warmup(50) @IR(counts = {IRNode.MAX_VHF, " >0 "}, - applyIfCPUFeature = {"avx512_fp16", "true"}) + applyIfCPUFeatureOr = {"avx512_fp16", "true", "sve", "true"}) + @IR(counts = {IRNode.MAX_VHF, " >0 "}, + applyIfCPUFeatureAnd = {"fphp", "true", "asimdhp", "true"}) public void vectorMaxConstantInputFloat16() { for (int i = 0; i < LEN; ++i) { output[i] = float16ToRawShortBits(max(FP16_CONST, shortBitsToFloat16(input2[i]))); @@ -421,7 +433,9 @@ public class TestFloat16VectorOperations { @Test @Warmup(50) @IR(counts = {IRNode.MIN_VHF, " >0 "}, - applyIfCPUFeature = {"avx512_fp16", "true"}) + applyIfCPUFeatureOr = {"avx512_fp16", "true", "sve", "true"}) + @IR(counts = {IRNode.MIN_VHF, " >0 "}, + applyIfCPUFeatureAnd = {"fphp", "true", "asimdhp", "true"}) public void vectorMinConstantInputFloat16() { for (int i = 0; i < LEN; ++i) { output[i] = float16ToRawShortBits(min(FP16_CONST, shortBitsToFloat16(input2[i]))); @@ -435,4 +449,206 @@ public class TestFloat16VectorOperations { assertResults(2, float16ToRawShortBits(FP16_CONST), input2[i], expected, output[i]); } } + + @Test + @Warmup(50) + @IR(counts = {IRNode.ADD_REDUCTION_VHF, " >0 "}, + applyIfCPUFeature = {"sve", "true"}) + @IR(counts = {IRNode.ADD_REDUCTION_VHF, " >0 "}, + applyIfCPUFeatureAnd = {"fphp", "true", "asimdhp", "true"}) + public short vectorAddReductionFloat16() { + short result = (short) 0; + for (int i = 0; i < LEN; i++) { + result = float16ToRawShortBits(add(shortBitsToFloat16(result), shortBitsToFloat16(input1[i]))); + } + return result; + } + + @Check(test="vectorAddReductionFloat16") + public void checkResultAddReductionFloat16() { + short expected = (short) 0; + for (int i = 0; i < LEN; ++i) { + expected = floatToFloat16(float16ToFloat(expected) + float16ToFloat(input1[i])); + } + Verify.checkEQ(shortBitsToFloat16(expected), shortBitsToFloat16(vectorAddReductionFloat16())); + } + + @Test + @Warmup(50) + @IR(counts = {IRNode.MUL_REDUCTION_VHF, " >0 "}, + applyIfCPUFeatureAnd = {"fphp", "true", "asimdhp", "true"}, + applyIf = {"MaxVectorSize", "<=16"}) + public short vectorMulReductionFloat16() { + short result = floatToFloat16(1.0f); + for (int i = 0; i < LEN; i++) { + result = float16ToRawShortBits(multiply(shortBitsToFloat16(result), shortBitsToFloat16(input1[i]))); + } + return result; + } + + @Check(test="vectorMulReductionFloat16") + public void checkResultMulReductionFloat16() { + short expected = floatToFloat16(1.0f); + for (int i = 0; i < LEN; ++i) { + expected = floatToFloat16(float16ToFloat(expected) * float16ToFloat(input1[i])); + } + Verify.checkEQ(shortBitsToFloat16(expected), shortBitsToFloat16(vectorMulReductionFloat16())); + } + + // This test case verifies that autovectorization takes place in scenarios where masked + // add reduction instructions are required to be generated on platforms that support + // such masked/partial instructions. + @Test + @Warmup(500) + @IR(counts = {"reduce_addFHF_masked", " >0 "}, phase = {CompilePhase.FINAL_CODE}, + applyIfCPUFeature = {"sve", "true"}) + public short vectorAddReductionFloat16Partial() { + short result = (short) 0; + for (int i = 0; i < LEN; i+=8) { + result = float16ToRawShortBits(add(shortBitsToFloat16(result), shortBitsToFloat16(input1[i]))); + result = float16ToRawShortBits(add(shortBitsToFloat16(result), shortBitsToFloat16(input1[i+1]))); + result = float16ToRawShortBits(add(shortBitsToFloat16(result), shortBitsToFloat16(input1[i+2]))); + result = float16ToRawShortBits(add(shortBitsToFloat16(result), shortBitsToFloat16(input1[i+3]))); + } + return result; + } + + @Check(test="vectorAddReductionFloat16Partial") + public void checkResultAddReductionFloat16Partial() { + short expected = (short) 0; + for (int i = 0; i < LEN; i+=8) { + expected = floatToFloat16(float16ToFloat(expected) + float16ToFloat(input1[i])); + expected = floatToFloat16(float16ToFloat(expected) + float16ToFloat(input1[i+1])); + expected = floatToFloat16(float16ToFloat(expected) + float16ToFloat(input1[i+2])); + expected = floatToFloat16(float16ToFloat(expected) + float16ToFloat(input1[i+3])); + } + Verify.checkEQ(shortBitsToFloat16(expected), shortBitsToFloat16(vectorAddReductionFloat16Partial())); + } + + // Partial multiply reduction for floating point is disabled on AArch64. This test makes sure that code that performs such partial + // multiply reduction operation for FP16 runs without any failures/result mismatch. + @Test + @Warmup(500) + public short vectorMulReductionFloat16Partial() { + short result = floatToFloat16(1.0f); + for (int i = 0; i < LEN; i+=8) { + result = float16ToRawShortBits(multiply(shortBitsToFloat16(result), shortBitsToFloat16(input1[i]))); + result = float16ToRawShortBits(multiply(shortBitsToFloat16(result), shortBitsToFloat16(input1[i+1]))); + result = float16ToRawShortBits(multiply(shortBitsToFloat16(result), shortBitsToFloat16(input1[i+2]))); + result = float16ToRawShortBits(multiply(shortBitsToFloat16(result), shortBitsToFloat16(input1[i+3]))); + } + return result; + } + + @Check(test="vectorMulReductionFloat16Partial") + public void checkResultMulReductionFloat16Partial() { + short expected = floatToFloat16(1.0f); + for (int i = 0; i < LEN; i+=8) { + expected = floatToFloat16(float16ToFloat(expected) * float16ToFloat(input1[i])); + expected = floatToFloat16(float16ToFloat(expected) * float16ToFloat(input1[i+1])); + expected = floatToFloat16(float16ToFloat(expected) * float16ToFloat(input1[i+2])); + expected = floatToFloat16(float16ToFloat(expected) * float16ToFloat(input1[i+3])); + } + Verify.checkEQ(shortBitsToFloat16(expected), shortBitsToFloat16(vectorMulReductionFloat16Partial())); + } + + // This test case verifies that autovectorization does NOT take place when using Float16. + // Filed RFE: JDK-8375321 + @Test + @Warmup(50) + @IR(counts = {IRNode.ADD_REDUCTION_VHF, " =0 "}, + applyIfCPUFeature = {"sve", "true"}) + @IR(counts = {IRNode.ADD_REDUCTION_VHF, " =0 "}, + applyIfCPUFeatureAnd = {"fphp", "true", "asimdhp", "true"}) + public Float16 vectorAddReductionFloat16NotVectorized() { + Float16 result = Float16.valueOf(0.0f); + for (int i = 0; i < LEN; i++) { + result = add(result, input4[i]); + } + return result; + } + + @Check(test="vectorAddReductionFloat16NotVectorized") + public void checkResultAddReductionFloat16NotVectorized() { + Float16 expected = Float16.valueOf(0.0f); + for (int i = 0; i < LEN; ++i) { + expected = Float16.valueOf(expected.floatValue() + input4[i].floatValue()); + } + Verify.checkEQ(expected, vectorAddReductionFloat16NotVectorized()); + } + + @Test + @Warmup(50) + @IR(counts = {IRNode.MUL_REDUCTION_VHF, " =0 "}, + applyIfCPUFeatureAnd = {"fphp", "true", "asimdhp", "true"}, + applyIf = {"MaxVectorSize", "<=16"}) + public Float16 vectorMulReductionFloat16NotVectorized() { + Float16 result = Float16.valueOf(1.0f); + for (int i = 0; i < LEN; i++) { + result = multiply(result, input4[i]); + } + return result; + } + + @Check(test="vectorMulReductionFloat16NotVectorized") + public void checkResultMulReductionFloat16NotVectorized() { + Float16 expected = Float16.valueOf(1.0f); + for (int i = 0; i < LEN; ++i) { + expected = Float16.valueOf(expected.floatValue() * input4[i].floatValue()); + } + Verify.checkEQ(expected, vectorMulReductionFloat16NotVectorized()); + } + + @Test + @Warmup(500) + @IR(counts = {"reduce_addFHF_masked", " =0 "}, phase = {CompilePhase.FINAL_CODE}, + applyIfCPUFeature = {"sve", "true"}) + public Float16 vectorAddReductionFloat16PartialNotVectorized() { + Float16 result = Float16.valueOf(0.0f); + for (int i = 0; i < LEN; i += 8) { + result = add(result, input4[i]); + result = add(result, input4[i + 1]); + result = add(result, input4[i + 2]); + result = add(result, input4[i + 3]); + } + return result; + } + + @Check(test="vectorAddReductionFloat16PartialNotVectorized") + public void checkResultAddReductionFloat16PartialNotVectorized() { + Float16 expected = Float16.valueOf(0.0f); + for (int i = 0; i < LEN; i += 8) { + expected = Float16.valueOf(expected.floatValue() + input4[i].floatValue()); + expected = Float16.valueOf(expected.floatValue() + input4[i + 1].floatValue()); + expected = Float16.valueOf(expected.floatValue() + input4[i + 2].floatValue()); + expected = Float16.valueOf(expected.floatValue() + input4[i + 3].floatValue()); + } + Verify.checkEQ(expected, vectorAddReductionFloat16PartialNotVectorized()); + } + + @Test + @Warmup(500) + public Float16 vectorMulReductionFloat16PartialNotVectorized() { + Float16 result = Float16.valueOf(1.0f); + for (int i = 0; i < LEN; i += 8) { + result = multiply(result, input4[i]); + result = multiply(result, input4[i + 1]); + result = multiply(result, input4[i + 2]); + result = multiply(result, input4[i + 3]); + } + return result; + } + + @Check(test="vectorMulReductionFloat16PartialNotVectorized") + public void checkResultMulReductionFloat16PartialNotVectorized() { + Float16 expected = Float16.valueOf(1.0f); + for (int i = 0; i < LEN; i += 8) { + expected = Float16.valueOf(expected.floatValue() * input4[i].floatValue()); + expected = Float16.valueOf(expected.floatValue() * input4[i + 1].floatValue()); + expected = Float16.valueOf(expected.floatValue() * input4[i + 2].floatValue()); + expected = Float16.valueOf(expected.floatValue() * input4[i + 3].floatValue()); + } + Verify.checkEQ(expected, vectorMulReductionFloat16PartialNotVectorized()); + } + } diff --git a/test/micro/org/openjdk/bench/jdk/incubator/vector/Float16OperationsBenchmark.java b/test/micro/org/openjdk/bench/jdk/incubator/vector/Float16OperationsBenchmark.java index 92c0b58005f..daf18af528e 100644 --- a/test/micro/org/openjdk/bench/jdk/incubator/vector/Float16OperationsBenchmark.java +++ b/test/micro/org/openjdk/bench/jdk/incubator/vector/Float16OperationsBenchmark.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright 2026 Arm Limited and/or its affiliates. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -350,4 +351,22 @@ public class Float16OperationsBenchmark { } return distRes; } + + @Benchmark + public short reductionAddFP16() { + short result = (short) 0; + for (int i = 0; i < vectorDim; i++) { + result = float16ToRawShortBits(add(shortBitsToFloat16(result), shortBitsToFloat16(vector1[i]))); + } + return result; + } + + @Benchmark + public short reductionMulFP16() { + short result = floatToFloat16(1.0f); + for (int i = 0; i < vectorDim; i++) { + result = float16ToRawShortBits(multiply(shortBitsToFloat16(result), shortBitsToFloat16(vector1[i]))); + } + return result; + } } diff --git a/test/micro/org/openjdk/bench/vm/compiler/VectorReduction2.java b/test/micro/org/openjdk/bench/vm/compiler/VectorReduction2.java index 9241aca1dad..0d11705c8ec 100644 --- a/test/micro/org/openjdk/bench/vm/compiler/VectorReduction2.java +++ b/test/micro/org/openjdk/bench/vm/compiler/VectorReduction2.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright 2026 Arm Limited and/or its affiliates. * 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 +28,7 @@ import org.openjdk.jmh.infra.*; import java.util.concurrent.TimeUnit; import java.util.Random; +import jdk.incubator.vector.Float16; /** * Note: there is a corresponding IR test: @@ -64,6 +66,9 @@ public abstract class VectorReduction2 { private double[] in1D; private double[] in2D; private double[] in3D; + private short[] in1F16; + private short[] in2F16; + private short[] in3F16; @Param("0") private int seed; @@ -96,6 +101,9 @@ public abstract class VectorReduction2 { in1D = new double[SIZE]; in2D = new double[SIZE]; in3D = new double[SIZE]; + in1F16 = new short[SIZE]; + in2F16 = new short[SIZE]; + in3F16 = new short[SIZE]; for (int i = 0; i < SIZE; i++) { in1B[i] = (byte)r.nextInt(); @@ -121,6 +129,9 @@ public abstract class VectorReduction2 { in1D[i] = r.nextDouble(); in2D[i] = r.nextDouble(); in3D[i] = r.nextDouble(); + in1F16[i] = Float.floatToFloat16(r.nextFloat()); + in2F16[i] = Float.floatToFloat16(r.nextFloat()); + in3F16[i] = Float.floatToFloat16(r.nextFloat()); } } @@ -1449,10 +1460,86 @@ public abstract class VectorReduction2 { bh.consume(acc); } - @Fork(value = 1, jvmArgs = {"-XX:+UseSuperWord"}) + // ---------float16***Simple ------------------------------------------------------------ + @Benchmark + public void float16AddSimple(Blackhole bh) { + short acc = (short)0; // neutral element + for (int i = 0; i < SIZE; i++) { + acc = Float16.float16ToRawShortBits( + Float16.add(Float16.shortBitsToFloat16(acc), Float16.shortBitsToFloat16(in1F16[i]))); + } + bh.consume(acc); + } + + @Benchmark + public void float16MulSimple(Blackhole bh) { + short acc = Float.floatToFloat16(1.0f); // neutral element + for (int i = 0; i < SIZE; i++) { + acc = Float16.float16ToRawShortBits( + Float16.multiply(Float16.shortBitsToFloat16(acc), Float16.shortBitsToFloat16(in1F16[i]))); + } + bh.consume(acc); + } + + // ---------float16***DotProduct ------------------------------------------------------------ + @Benchmark + public void float16AddDotProduct(Blackhole bh) { + short acc = (short)0; // neutral element + for (int i = 0; i < SIZE; i++) { + Float16 val = Float16.multiply(Float16.shortBitsToFloat16(in1F16[i]), + Float16.shortBitsToFloat16(in2F16[i])); + acc = Float16.float16ToRawShortBits( + Float16.add(Float16.shortBitsToFloat16(acc), val)); + } + bh.consume(acc); + } + + @Benchmark + public void float16MulDotProduct(Blackhole bh) { + short acc = Float.floatToFloat16(1.0f); // neutral element + for (int i = 0; i < SIZE; i++) { + Float16 val = Float16.multiply(Float16.shortBitsToFloat16(in1F16[i]), + Float16.shortBitsToFloat16(in2F16[i])); + acc = Float16.float16ToRawShortBits( + Float16.multiply(Float16.shortBitsToFloat16(acc), val)); + } + bh.consume(acc); + } + + // ---------float16***Big ------------------------------------------------------------ + @Benchmark + public void float16AddBig(Blackhole bh) { + short acc = (short)0; // neutral element + for (int i = 0; i < SIZE; i++) { + Float16 a = Float16.shortBitsToFloat16(in1F16[i]); + Float16 b = Float16.shortBitsToFloat16(in2F16[i]); + Float16 c = Float16.shortBitsToFloat16(in3F16[i]); + Float16 val = Float16.add(Float16.multiply(a, b), + Float16.add(Float16.multiply(a, c), Float16.multiply(b, c))); + acc = Float16.float16ToRawShortBits( + Float16.add(Float16.shortBitsToFloat16(acc), val)); + } + bh.consume(acc); + } + + @Benchmark + public void float16MulBig(Blackhole bh) { + short acc = Float.floatToFloat16(1.0f); // neutral element + for (int i = 0; i < SIZE; i++) { + Float16 a = Float16.shortBitsToFloat16(in1F16[i]); + Float16 b = Float16.shortBitsToFloat16(in2F16[i]); + Float16 c = Float16.shortBitsToFloat16(in3F16[i]); + Float16 val = Float16.add(Float16.multiply(a, b), + Float16.add(Float16.multiply(a, c), Float16.multiply(b, c))); + acc = Float16.float16ToRawShortBits( + Float16.multiply(Float16.shortBitsToFloat16(acc), val)); + } + bh.consume(acc); + } + + @Fork(value = 1, jvmArgs = {"--add-modules=jdk.incubator.vector", "-XX:+UseSuperWord"}) public static class WithSuperword extends VectorReduction2 {} - @Fork(value = 1, jvmArgs = {"-XX:-UseSuperWord"}) + @Fork(value = 1, jvmArgs = {"--add-modules=jdk.incubator.vector", "-XX:-UseSuperWord"}) public static class NoSuperword extends VectorReduction2 {} } - From 0df4bd489de00056736321f1e77389add7a41f83 Mon Sep 17 00:00:00 2001 From: Alexey Ivanov Date: Wed, 15 Apr 2026 13:07:24 +0000 Subject: [PATCH 060/108] 8381208: Init cause with the caught runtime exception Reviewed-by: dmarkov, serb, azvegint --- .../classes/sun/print/RasterPrinterJob.java | 14 +++--- .../ExceptionFromPrintableIsIgnoredTest.java | 45 +++++++++++++++---- 2 files changed, 43 insertions(+), 16 deletions(-) diff --git a/src/java.desktop/share/classes/sun/print/RasterPrinterJob.java b/src/java.desktop/share/classes/sun/print/RasterPrinterJob.java index 32728efde6c..b28723f94a6 100644 --- a/src/java.desktop/share/classes/sun/print/RasterPrinterJob.java +++ b/src/java.desktop/share/classes/sun/print/RasterPrinterJob.java @@ -33,24 +33,23 @@ import java.awt.HeadlessException; import java.awt.KeyboardFocusManager; import java.awt.Rectangle; import java.awt.Shape; +import java.awt.Window; import java.awt.geom.AffineTransform; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; import java.awt.print.Book; -import java.awt.print.Pageable; import java.awt.print.PageFormat; +import java.awt.print.Pageable; import java.awt.print.Paper; import java.awt.print.Printable; import java.awt.print.PrinterAbortException; import java.awt.print.PrinterException; import java.awt.print.PrinterJob; -import java.awt.Window; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Locale; -import sun.awt.image.ByteInterleavedRaster; import javax.print.Doc; import javax.print.DocFlavor; @@ -69,8 +68,8 @@ import javax.print.attribute.ResolutionSyntax; import javax.print.attribute.Size2DSyntax; import javax.print.attribute.standard.Copies; import javax.print.attribute.standard.Destination; -import javax.print.attribute.standard.DialogTypeSelection; import javax.print.attribute.standard.DialogOwner; +import javax.print.attribute.standard.DialogTypeSelection; import javax.print.attribute.standard.Fidelity; import javax.print.attribute.standard.JobName; import javax.print.attribute.standard.JobSheets; @@ -81,15 +80,17 @@ import javax.print.attribute.standard.MediaSizeName; import javax.print.attribute.standard.OrientationRequested; import javax.print.attribute.standard.OutputBin; import javax.print.attribute.standard.PageRanges; +import javax.print.attribute.standard.PrinterIsAcceptingJobs; import javax.print.attribute.standard.PrinterResolution; import javax.print.attribute.standard.PrinterState; import javax.print.attribute.standard.PrinterStateReason; import javax.print.attribute.standard.PrinterStateReasons; -import javax.print.attribute.standard.PrinterIsAcceptingJobs; import javax.print.attribute.standard.RequestingUserName; import javax.print.attribute.standard.SheetCollate; import javax.print.attribute.standard.Sides; +import sun.awt.image.ByteInterleavedRaster; + import static sun.font.FontUtilities.isIgnorableWhitespace; /** @@ -1613,8 +1614,7 @@ public abstract class RasterPrinterJob extends PrinterJob { } catch (PrinterException pe) { throw pe; } catch (Throwable printError) { - throw (PrinterException) - new PrinterException().initCause(printError.getCause()); + throw (PrinterException) new PrinterException().initCause(printError); } finally { // reset previousPaper in case this job is invoked again. previousPaper = null; diff --git a/test/jdk/java/awt/print/PrinterJob/ExceptionFromPrintableIsIgnoredTest.java b/test/jdk/java/awt/print/PrinterJob/ExceptionFromPrintableIsIgnoredTest.java index 4c2ff370cb8..322c19ed7c2 100644 --- a/test/jdk/java/awt/print/PrinterJob/ExceptionFromPrintableIsIgnoredTest.java +++ b/test/jdk/java/awt/print/PrinterJob/ExceptionFromPrintableIsIgnoredTest.java @@ -23,7 +23,7 @@ /* * @test - * @bug 8262731 8268675 + * @bug 8262731 8268675 8381208 * @key printer * @summary Verify that "PrinterJob.print" throws the expected exception, * if "Printable.print" throws an exception. @@ -38,7 +38,12 @@ import java.awt.print.PageFormat; import java.awt.print.Printable; import java.awt.print.PrinterException; import java.awt.print.PrinterJob; +import java.io.File; import java.lang.reflect.InvocationTargetException; + +import javax.print.attribute.HashPrintRequestAttributeSet; +import javax.print.attribute.PrintRequestAttributeSet; +import javax.print.attribute.standard.Destination; import javax.swing.SwingUtilities; public class ExceptionFromPrintableIsIgnoredTest { @@ -47,7 +52,14 @@ public class ExceptionFromPrintableIsIgnoredTest { private volatile Throwable printError; + private volatile PrinterException thrownPE; + private volatile RuntimeException thrownRE; + public static void main(String[] args) { + if (PrinterJob.lookupPrintServices().length == 0) { + throw new RuntimeException("Printer not configured or available."); + } + if (args.length < 2) { throw new RuntimeException("Two arguments are expected:" + " test thread type and test exception type."); @@ -58,7 +70,7 @@ public class ExceptionFromPrintableIsIgnoredTest { TestExceptionType.valueOf(args[1])); } - public ExceptionFromPrintableIsIgnoredTest( + private ExceptionFromPrintableIsIgnoredTest( final TestThreadType threadType, final TestExceptionType exceptionType) { System.out.println(String.format( @@ -87,15 +99,28 @@ public class ExceptionFromPrintableIsIgnoredTest { } else if (!(printError instanceof PrinterException)) { throw new RuntimeException("Unexpected exception was thrown."); } + + if (exceptionType == TestExceptionType.PE + && thrownPE != printError) { + throw new RuntimeException( + "Expected the same instance of PrinterException"); + } + + if (exceptionType == TestExceptionType.RE + && thrownRE != printError.getCause()) { + throw new RuntimeException( + "Expected the cause of PrinterException to be the thrown exception"); + } + System.out.println("Test passed."); } private void runTest(final TestExceptionType exceptionType) { + PrintRequestAttributeSet attrs = new HashPrintRequestAttributeSet(); + final File file = new File("out.prn"); + attrs.add(new Destination(file.toURI())); + PrinterJob job = PrinterJob.getPrinterJob(); - if (job.getPrintService() == null) { - System.out.println("No printers are available."); - return; - } job.setPrintable(new Printable() { @Override @@ -105,10 +130,10 @@ public class ExceptionFromPrintableIsIgnoredTest { return NO_SUCH_PAGE; } if (exceptionType == TestExceptionType.PE) { - throw new PrinterException( + throw thrownPE = new PrinterException( "Exception from 'Printable.print'."); } else if (exceptionType == TestExceptionType.RE) { - throw new RuntimeException( + throw thrownRE = new RuntimeException( "Exception from 'Printable.print'."); } return PAGE_EXISTS; @@ -116,12 +141,14 @@ public class ExceptionFromPrintableIsIgnoredTest { }); try { - job.print(); + job.print(attrs); } catch (Throwable t) { printError = t; System.out.println("'PrinterJob.print' threw the exception:"); t.printStackTrace(System.out); + } finally { + file.delete(); } } } From 152fa85fba4615f036f395b5ae03213b02be56e2 Mon Sep 17 00:00:00 2001 From: Vladimir Kozlov Date: Wed, 15 Apr 2026 14:12:57 +0000 Subject: [PATCH 061/108] 8382134: Replace assert with fatal check in AOTCodeCache::load_strings() to make sure we have valid value in product VM Reviewed-by: iklam, mhaessig, adinn --- src/hotspot/share/code/aotCodeCache.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/hotspot/share/code/aotCodeCache.cpp b/src/hotspot/share/code/aotCodeCache.cpp index a3dd3d2bfd0..c2838917516 100644 --- a/src/hotspot/share/code/aotCodeCache.cpp +++ b/src/hotspot/share/code/aotCodeCache.cpp @@ -2201,6 +2201,10 @@ void AOTCodeCache::load_strings() { if (strings_count == 0) { return; } + if (strings_count > MAX_STR_COUNT) { + fatal("Invalid strings_count loaded from AOT Code Cache: %d > MAX_STR_COUNT [%d]", strings_count, MAX_STR_COUNT); + return; + } uint strings_offset = _load_header->strings_offset(); uint* string_lengths = (uint*)addr(strings_offset); strings_offset += (strings_count * sizeof(uint)); @@ -2211,7 +2215,6 @@ void AOTCodeCache::load_strings() { char* p = NEW_C_HEAP_ARRAY(char, strings_size+1, mtCode); memcpy(p, addr(strings_offset), strings_size); _C_strings_buf = p; - assert(strings_count <= MAX_STR_COUNT, "sanity"); for (uint i = 0; i < strings_count; i++) { _C_strings[i] = p; uint len = string_lengths[i]; From e82f871871885688d42985c1cfe00ba0dc4c35b3 Mon Sep 17 00:00:00 2001 From: Leonid Mesnik Date: Wed, 15 Apr 2026 14:19:14 +0000 Subject: [PATCH 062/108] 8362350: recompute_enable hit assertion "should never come here before live phase" if happens in post_vm_start Reviewed-by: cjplummer, sspitsyn --- .../share/prims/jvmtiEventController.cpp | 5 ++ .../jtreg/ProblemList-jvmti-stress-agent.txt | 2 - .../singlestep02/libsinglestep02.cpp | 48 ++++++++----------- .../SingleStep/singlestep02/singlestep02.java | 18 +------ 4 files changed, 26 insertions(+), 47 deletions(-) diff --git a/src/hotspot/share/prims/jvmtiEventController.cpp b/src/hotspot/share/prims/jvmtiEventController.cpp index cb44b833c48..832c8a33c88 100644 --- a/src/hotspot/share/prims/jvmtiEventController.cpp +++ b/src/hotspot/share/prims/jvmtiEventController.cpp @@ -544,6 +544,11 @@ JvmtiEventControllerPrivate::recompute_env_thread_enabled(JvmtiEnvThreadState* e } switch (JvmtiEnv::get_phase()) { + case JVMTI_PHASE_ONLOAD: + case JVMTI_PHASE_PRIMORDIAL: + case JVMTI_PHASE_START: + now_enabled &= EARLY_EVENT_BITS; + break; case JVMTI_PHASE_DEAD: // no events allowed when dead now_enabled = 0; diff --git a/test/hotspot/jtreg/ProblemList-jvmti-stress-agent.txt b/test/hotspot/jtreg/ProblemList-jvmti-stress-agent.txt index 1e84a39c30b..d8779a873cd 100644 --- a/test/hotspot/jtreg/ProblemList-jvmti-stress-agent.txt +++ b/test/hotspot/jtreg/ProblemList-jvmti-stress-agent.txt @@ -32,8 +32,6 @@ gc/stringdedup/TestStringDeduplicationInterned.java 8362562 generic-all gc/stringdedup/TestStringDeduplicationPrintOptions.java 8362562 generic-all -serviceability/jvmti/events/SingleStep/singlestep02/singlestep02.java 8362350 generic-all -vmTestbase/nsk/jvmti/scenarios/events/EM02/em02t007/TestDescription.java 8362350 generic-all # Incompatbile tests diff --git a/test/hotspot/jtreg/serviceability/jvmti/events/SingleStep/singlestep02/libsinglestep02.cpp b/test/hotspot/jtreg/serviceability/jvmti/events/SingleStep/singlestep02/libsinglestep02.cpp index 984454dfbaf..b52e3412f4e 100644 --- a/test/hotspot/jtreg/serviceability/jvmti/events/SingleStep/singlestep02/libsinglestep02.cpp +++ b/test/hotspot/jtreg/serviceability/jvmti/events/SingleStep/singlestep02/libsinglestep02.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2024, 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 @@ -29,11 +29,6 @@ extern "C" { -#define STATUS_FAILED 2 -#define PASSED 0 - -static volatile jint result = PASSED; -static volatile long wrongStepEv = 0; static jvmtiEnv *jvmti = nullptr; @@ -44,31 +39,25 @@ SingleStep(jvmtiEnv *jvmti, JNIEnv *jni, jthread thread, jmethodID method, jloca jvmtiError err; err = jvmti->GetPhase(&phase); - if (err != JVMTI_ERROR_NONE) { - result = STATUS_FAILED; - COMPLAIN("TEST FAILED: unable to obtain phase of the VM execution during SingleStep callback\n\n"); - } else { - if (phase != JVMTI_PHASE_LIVE) { - wrongStepEv++; - result = STATUS_FAILED; - COMPLAIN("TEST FAILED: SingleStep event received during non-live phase %s\n", TranslatePhase(phase)); - } + check_jvmti_status(jni, err, "Error in GetPhase"); + if (phase != JVMTI_PHASE_LIVE) { + COMPLAIN("TEST FAILED: SingleStep event received during non-live phase %s\n", TranslatePhase(phase)); + jni->FatalError("Event SingleStep event received during non-live phase."); } } +/* + * The ClassLoad event is not used. This is thread filtered event that should + * be sent during start phase. It is enabled to trigger creation of jvmti thread state + * in the START phase. So test ensures that even jvmti state for thread is created + * before live phase the SingleStep event is sent only during live phase. + */ void JNICALL -VMDeath(jvmtiEnv *jvmti, JNIEnv *jni) { - LOG("VMDeath event received\n"); +ClassLoad(jvmtiEnv *jvmti_env, + JNIEnv* jni_env, + jthread thread, + jclass klass) { - if (wrongStepEv != 0) { - LOG("TEST FAILED: there are %ld SingleStep events\n" - "sent during non-live phase of the VM execution\n", wrongStepEv); - jni->FatalError("Test Failed."); - } - - if (result == STATUS_FAILED) { - jni->FatalError("Test Failed."); - } } jint Agent_Initialize(JavaVM *jvm, char *options, void *reserved) { @@ -105,19 +94,20 @@ jint Agent_Initialize(JavaVM *jvm, char *options, void *reserved) { /* set event callback */ LOG("setting event callbacks ...\n"); (void) memset(&callbacks, 0, sizeof(callbacks)); + callbacks.ClassLoad = &ClassLoad; callbacks.SingleStep = &SingleStep; - callbacks.VMDeath = &VMDeath; err = jvmti->SetEventCallbacks(&callbacks, sizeof(callbacks)); if (err != JVMTI_ERROR_NONE) { return JNI_ERR; } LOG("setting event callbacks done\nenabling JVMTI events ...\n"); - err = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_SINGLE_STEP, nullptr); + + err = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_CLASS_LOAD, nullptr); if (err != JVMTI_ERROR_NONE) { return JNI_ERR; } - err = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_VM_DEATH, nullptr); + err = jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_SINGLE_STEP, nullptr); if (err != JVMTI_ERROR_NONE) { return JNI_ERR; } diff --git a/test/hotspot/jtreg/serviceability/jvmti/events/SingleStep/singlestep02/singlestep02.java b/test/hotspot/jtreg/serviceability/jvmti/events/SingleStep/singlestep02/singlestep02.java index 8a7b7cf106a..9d25a7bc090 100644 --- a/test/hotspot/jtreg/serviceability/jvmti/events/SingleStep/singlestep02/singlestep02.java +++ b/test/hotspot/jtreg/serviceability/jvmti/events/SingleStep/singlestep02/singlestep02.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2022, 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 @@ -21,39 +21,25 @@ * questions. */ -import jdk.test.lib.jvmti.DebugeeClass; import java.io.*; /* * @test * * @summary converted from VM Testbase nsk/jvmti/SingleStep/singlestep002. - * VM Testbase keywords: [jpda, jvmti, onload_only_caps, noras] - * VM Testbase readme: - * DESCRIPTION * This test exercises the JVMTI event SingleStep. - * It verifies that this event s sent only during the live phase + * It verifies that this event is sent only during the live phase * of VM execution. * The test works as follows. The tested event is enabled in the * 'OnLoad' phase. Then all received SingleStep events is checked * to be sent only during the live phase via the GetPhase() call. - * COMMENTS * - * @library /test/lib * @run main/othervm/native -agentlib:singlestep02 singlestep02 */ public class singlestep02 { - static { - System.loadLibrary("singlestep02"); - } - public static void main(String[] args) { - new singlestep02().runThis(args); } - private int runThis(String argv[]) { - return DebugeeClass.TEST_PASSED; - } } From a06f3cd469f1343c53ecc9b1dda2c6cbdf5f98f3 Mon Sep 17 00:00:00 2001 From: Quan Anh Mai Date: Wed, 15 Apr 2026 14:27:25 +0000 Subject: [PATCH 063/108] 8373248: C2: CastPP should not change the type of the oop Reviewed-by: bmaillard, dfenacci, rcastanedalo, mchevalier --- src/hotspot/share/opto/castnode.cpp | 42 +++++++++++++++++++++++++ src/hotspot/share/opto/castnode.hpp | 11 +++++-- src/hotspot/share/opto/library_call.cpp | 2 +- src/hotspot/share/opto/node.hpp | 1 + src/hotspot/share/opto/vector.cpp | 14 ++++----- 5 files changed, 58 insertions(+), 12 deletions(-) diff --git a/src/hotspot/share/opto/castnode.cpp b/src/hotspot/share/opto/castnode.cpp index 54269a56c19..7bb6b1dcb77 100644 --- a/src/hotspot/share/opto/castnode.cpp +++ b/src/hotspot/share/opto/castnode.cpp @@ -413,6 +413,43 @@ Node* CastLLNode::Ideal(PhaseGVN* phase, bool can_reshape) { return nullptr; } +// CastPPNodes are removed before matching, while alias classes are needed in global code motion. +// As a result, it is not valid for a CastPPNode to change the oop such that the derived pointers +// lie in different alias classes with and without the node. For example, a CastPPNode c may not +// cast an Object to a Bottom[], because later removal of c would affect the alias class of c's +// array length field (c + arrayOopDesc::length_offset_in_bytes()). +// +// This function verifies that a CastPPNode on an oop does not violate the aforementioned property. +// +// TODO 8382147: Currently, this verification only applies during the construction of a CastPPNode, +// we may want to apply the same verification during IGVN transformations, as well as final graph +// reshaping. +void CastPPNode::verify_type(const Type* in_type, const Type* out_type) { +#ifdef ASSERT + out_type = out_type->join(in_type); + if (in_type->empty() || out_type->empty()) { + return; + } + if (in_type == TypePtr::NULL_PTR || out_type == TypePtr::NULL_PTR) { + return; + } + if (!in_type->isa_oopptr() && !out_type->isa_oopptr()) { + return; + } + + assert(in_type->isa_oopptr() && out_type->isa_oopptr(), "must be both oops or both non-oops"); + if (in_type->isa_aryptr() && out_type->isa_aryptr()) { + const Type* e1 = in_type->is_aryptr()->elem(); + const Type* e2 = out_type->is_aryptr()->elem(); + assert(e1->basic_type() == e2->basic_type(), "must both be arrays of the same primitive type or both be oops arrays"); + return; + } + + assert(in_type->isa_instptr() && out_type->isa_instptr(), "must be both array oops or both non-array oops"); + assert(in_type->is_instptr()->instance_klass() == out_type->is_instptr()->instance_klass(), "must not cast to a different type"); +#endif // ASSERT +} + //------------------------------Value------------------------------------------ // Take 'join' of input and cast-up type, unless working with an Interface const Type* CheckCastPPNode::Value(PhaseGVN* phase) const { @@ -440,6 +477,11 @@ const Type* CheckCastPPNode::Value(PhaseGVN* phase) const { return result; } +Node* CheckCastPPNode::pin_node_under_control_impl() const { + assert(_dependency.is_floating(), "already pinned"); + return new CheckCastPPNode(in(0), in(1), bottom_type(), _dependency.with_pinned_dependency(), _extra_types); +} + //============================================================================= //------------------------------Value------------------------------------------ const Type* CastX2PNode::Value(PhaseGVN* phase) const { diff --git a/src/hotspot/share/opto/castnode.hpp b/src/hotspot/share/opto/castnode.hpp index 38545fd6f41..dce54eb73c0 100644 --- a/src/hotspot/share/opto/castnode.hpp +++ b/src/hotspot/share/opto/castnode.hpp @@ -303,14 +303,18 @@ public: //------------------------------CastPPNode------------------------------------- // cast pointer to pointer (different type) -class CastPPNode: public ConstraintCastNode { - public: - CastPPNode (Node* ctrl, Node* n, const Type* t, const DependencyType& dependency = DependencyType::FloatingNarrowing, const TypeTuple* types = nullptr) +class CastPPNode : public ConstraintCastNode { +public: + CastPPNode(Node* ctrl, Node* n, const Type* t, const DependencyType& dependency = DependencyType::FloatingNarrowing, const TypeTuple* types = nullptr) : ConstraintCastNode(ctrl, n, t, dependency, types) { init_class_id(Class_CastPP); + verify_type(n->bottom_type(), t); } virtual int Opcode() const; virtual uint ideal_reg() const { return Op_RegP; } + +private: + static void verify_type(const Type* in_type, const Type* out_type); }; //------------------------------CheckCastPPNode-------------------------------- @@ -329,6 +333,7 @@ class CheckCastPPNode: public ConstraintCastNode { private: virtual bool depends_only_on_test_impl() const { return !type()->isa_rawptr() && ConstraintCastNode::depends_only_on_test_impl(); } + virtual Node* pin_node_under_control_impl() const; }; diff --git a/src/hotspot/share/opto/library_call.cpp b/src/hotspot/share/opto/library_call.cpp index 26417436ed9..ff7bc2c10d3 100644 --- a/src/hotspot/share/opto/library_call.cpp +++ b/src/hotspot/share/opto/library_call.cpp @@ -4311,7 +4311,7 @@ Node* LibraryCallKit::generate_array_guard_common(Node* kls, RegionNode* region, if (obj != nullptr && is_array_ctrl != nullptr && is_array_ctrl != top()) { // Keep track of the fact that 'obj' is an array to prevent // array specific accesses from floating above the guard. - *obj = _gvn.transform(new CastPPNode(is_array_ctrl, *obj, TypeAryPtr::BOTTOM)); + *obj = _gvn.transform(new CheckCastPPNode(is_array_ctrl, *obj, TypeAryPtr::BOTTOM)); } return ctrl; } diff --git a/src/hotspot/share/opto/node.hpp b/src/hotspot/share/opto/node.hpp index 36855d659ba..8c6622e643e 100644 --- a/src/hotspot/share/opto/node.hpp +++ b/src/hotspot/share/opto/node.hpp @@ -1186,6 +1186,7 @@ public: return nullptr; } assert(!res->depends_only_on_test(), "the result must not depends_only_on_test"); + assert(Opcode() == res->Opcode(), "pinning must result in the same kind of node %s - %s", Name(), res->Name()); return res; } diff --git a/src/hotspot/share/opto/vector.cpp b/src/hotspot/share/opto/vector.cpp index f44df7e6da2..d35717c5922 100644 --- a/src/hotspot/share/opto/vector.cpp +++ b/src/hotspot/share/opto/vector.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 @@ -455,14 +455,12 @@ void PhaseVector::expand_vunbox_node(VectorUnboxNode* vec_unbox) { gvn.record_for_igvn(local_mem); BarrierSetC2* bs = BarrierSet::barrier_set()->barrier_set_c2(); C2OptAccess access(gvn, ctrl, local_mem, decorators, T_OBJECT, obj, addr); - const Type* type = TypeOopPtr::make_from_klass(field->type()->as_klass()); - vec_field_ld = bs->load_at(access, type); - } - // For proper aliasing, attach concrete payload type. - ciKlass* payload_klass = ciTypeArrayKlass::make(bt); - const Type* payload_type = TypeAryPtr::make_from_klass(payload_klass)->cast_to_ptr_type(TypePtr::NotNull); - vec_field_ld = gvn.transform(new CastPPNode(nullptr, vec_field_ld, payload_type)); + // For proper aliasing, attach concrete payload type. + ciKlass* payload_klass = ciTypeArrayKlass::make(bt); + const Type* payload_type = TypeAryPtr::make_from_klass(payload_klass)->cast_to_ptr_type(TypePtr::NotNull); + vec_field_ld = bs->load_at(access, payload_type); + } Node* adr = kit.array_element_address(vec_field_ld, gvn.intcon(0), bt); const TypePtr* adr_type = adr->bottom_type()->is_ptr(); From 974596775b2b1fe048ec7de0e35dc6f8283d844a Mon Sep 17 00:00:00 2001 From: Ashutosh Mehra Date: Wed, 15 Apr 2026 14:30:30 +0000 Subject: [PATCH 064/108] 8381968: Extend cds/appcds/aotCode/AOTCodeFlags.java to run test with different flags in assembly and prod run Reviewed-by: kvn, adinn --- test/hotspot/jtreg/TEST.groups | 3 +- .../AOTCodeCPUFeatureIncompatibilityTest.java | 12 +- .../aotCode/AOTCodeCompressedOopsTest.java | 12 +- .../cds/appcds/aotCode/AOTCodeFlags.java | 137 ++++++------ .../cds/appcds/aotCode/AOTCodeTest.java | 196 ++++++++++++++++++ test/setup_aot/HelloWorld.java | 29 +++ 6 files changed, 310 insertions(+), 79 deletions(-) create mode 100644 test/hotspot/jtreg/runtime/cds/appcds/aotCode/AOTCodeTest.java create mode 100644 test/setup_aot/HelloWorld.java diff --git a/test/hotspot/jtreg/TEST.groups b/test/hotspot/jtreg/TEST.groups index 6e9421e5c09..6623676d2ba 100644 --- a/test/hotspot/jtreg/TEST.groups +++ b/test/hotspot/jtreg/TEST.groups @@ -587,7 +587,8 @@ hotspot_metaspace = \ tier1_runtime_appcds = \ runtime/cds/appcds/aotCache/HelloAOTCache.java \ runtime/cds/appcds/aotCode \ - runtime/cds/appcds/HelloTest.java + runtime/cds/appcds/HelloTest.java \ + -runtime/cds/appcds/aotCode/AOTCodeFlags.java tier1_runtime_appcds_exclude = \ runtime/cds/appcds/ \ diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCode/AOTCodeCPUFeatureIncompatibilityTest.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCode/AOTCodeCPUFeatureIncompatibilityTest.java index e9e610330ad..aa7becdb6f8 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotCode/AOTCodeCPUFeatureIncompatibilityTest.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCode/AOTCodeCPUFeatureIncompatibilityTest.java @@ -31,13 +31,9 @@ * @comment The test verifies AOT checks during VM startup and not code generation. * No need to run it with -Xcomp. * @library /test/lib /test/setup_aot - * @build AOTCodeCPUFeatureIncompatibilityTest JavacBenchApp + * @build AOTCodeCPUFeatureIncompatibilityTest HelloWorld * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox - * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar - * JavacBenchApp - * JavacBenchApp$ClassFile - * JavacBenchApp$FileManager - * JavacBenchApp$SourceFile + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar HelloWorld * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI AOTCodeCPUFeatureIncompatibilityTest */ @@ -98,9 +94,7 @@ public class AOTCodeCPUFeatureIncompatibilityTest { } @Override public String[] appCommandLine(RunMode runMode) { - return new String[] { - "JavacBenchApp", "10" - }; + return new String[] { "HelloWorld" }; } }.runAOTWorkflow("--two-step-training"); } diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCode/AOTCodeCompressedOopsTest.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCode/AOTCodeCompressedOopsTest.java index 3b9458c14da..0fe11235749 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotCode/AOTCodeCompressedOopsTest.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCode/AOTCodeCompressedOopsTest.java @@ -33,12 +33,8 @@ * No need to run it with -Xcomp. It takes a lot of time to complete all * subtests with this flag. * @library /test/lib /test/setup_aot - * @build AOTCodeCompressedOopsTest JavacBenchApp - * @run driver/timeout=480 jdk.test.lib.helpers.ClassFileInstaller -jar app.jar - * JavacBenchApp - * JavacBenchApp$ClassFile - * JavacBenchApp$FileManager - * JavacBenchApp$SourceFile + * @build AOTCodeCompressedOopsTest HelloWorld + * @run driver/timeout=480 jdk.test.lib.helpers.ClassFileInstaller -jar app.jar HelloWorld * @run driver/timeout=480 AOTCodeCompressedOopsTest */ @@ -149,9 +145,7 @@ public class AOTCodeCompressedOopsTest { @Override public String[] appCommandLine(RunMode runMode) { - return new String[] { - "JavacBenchApp", "10" - }; + return new String[] { "HelloWorld" }; } @Override diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCode/AOTCodeFlags.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCode/AOTCodeFlags.java index d3c6a23efa0..01b64a7fff0 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotCode/AOTCodeFlags.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCode/AOTCodeFlags.java @@ -109,39 +109,44 @@ public class AOTCodeFlags { private static String gcName = null; public static void main(String... args) throws Exception { Tester t = new Tester(args.length == 0 ? null : args[0]); - // Run only 2 modes (0 - no AOT code, 1 - AOT adapters) until JDK-8357398 is fixed - for (int mode = 0; mode < 4; mode++) { - t.setTestMode(mode); - t.run(new String[] {"AOT", "--two-step-training"}); + // mode bits 0 and 1 encode AOTAdapterCaching and AOTStubCaching settings + // aMode is used for assembly run, pMode for production run + for (int aMode = 0; aMode < 4; aMode++) { + for (int pMode = 0; pMode < 4; pMode++) { + t.setTestMode(aMode, pMode); + t.run(new String[] {"AOT", "--two-step-training"}); + } } } static class Tester extends CDSAppTester { - private int testMode; + private int aMode, pMode; private String gcName; public Tester(String name) { super("AOTCodeFlags"); - testMode = 0; + aMode = 0; + pMode = 0; gcName = name; } - boolean isAdapterCachingOn() { - return (testMode & 0x1) != 0; + boolean isAdapterCachingOn(int mode) { + return (mode & 0x1) != 0; } - boolean isStubCachingOn() { - return (testMode & 0x2) != 0; + boolean isStubCachingOn(int mode) { + return (mode & 0x2) != 0; } - public void setTestMode(int mode) { - testMode = mode; + public void setTestMode(int aMode, int pMode) { + this.aMode = aMode; + this.pMode = pMode; } - public List getVMArgsForTestMode() { + public List getVMArgsForTestMode(int mode) { List list = new ArrayList(); list.add("-XX:+UnlockDiagnosticVMOptions"); - list.add(isAdapterCachingOn() ? "-XX:+AOTAdapterCaching" : "-XX:-AOTAdapterCaching"); - list.add(isStubCachingOn() ? "-XX:+AOTStubCaching" : "-XX:-AOTStubCaching"); + list.add(isAdapterCachingOn(mode) ? "-XX:+AOTAdapterCaching" : "-XX:-AOTAdapterCaching"); + list.add(isStubCachingOn(mode) ? "-XX:+AOTStubCaching" : "-XX:-AOTStubCaching"); return list; } @@ -170,79 +175,91 @@ public class AOTCodeFlags { @Override public String[] vmArgs(RunMode runMode) { + List args = getGCArgs(); + args.addAll(List.of("-Xlog:aot+codecache+init=debug", + "-Xlog:aot+codecache+exit=debug", + "-Xlog:aot+codecache+stubs=debug")); switch (runMode) { case RunMode.ASSEMBLY: - case RunMode.PRODUCTION: { - List args = getVMArgsForTestMode(); - args.addAll(List.of("-Xlog:aot+codecache+init=debug", - "-Xlog:aot+codecache+exit=debug")); - args.addAll(getGCArgs()); - return args.toArray(new String[0]); - } + args.addAll(getVMArgsForTestMode(aMode)); + break; + case RunMode.PRODUCTION: + args.addAll(getVMArgsForTestMode(pMode)); + break; + default: + break; } - List args = getGCArgs(); return args.toArray(new String[args.size()]); } @Override public String[] appCommandLine(RunMode runMode) { - return new String[] { - "JavacBenchApp", "10" - }; + return new String[] { "JavacBenchApp", "10" }; } @Override public void checkExecution(OutputAnalyzer out, RunMode runMode) throws Exception { - if (!isAdapterCachingOn() && !isStubCachingOn()) { // this is equivalent to completely disable AOT code cache - switch (runMode) { - case RunMode.ASSEMBLY: - case RunMode.PRODUCTION: + if (runMode == RunMode.ASSEMBLY) { + if (!isAdapterCachingOn(aMode) && !isStubCachingOn(aMode)) { // this is equivalent to completely disable AOT code cache out.shouldNotMatch("Adapters:\\s+total"); out.shouldNotMatch("Shared Blobs:\\s+total"); out.shouldNotMatch("C1 Blobs:\\s+total"); out.shouldNotMatch("C2 Blobs:\\s+total"); - break; - } - } else { - if (isAdapterCachingOn()) { - switch (runMode) { - case RunMode.ASSEMBLY: - case RunMode.PRODUCTION: - // AOTAdapterCaching is on, non-zero adapters should be stored/loaded - out.shouldMatch("Adapters:\\s+total=[1-9][0-9]+"); - break; - } } else { - switch (runMode) { - case RunMode.ASSEMBLY: - case RunMode.PRODUCTION: - // AOTAdapterCaching is off, no adapters should be stored/loaded + if (isAdapterCachingOn(aMode)) { + // AOTAdapterCaching is on, non-zero adapters should be stored + out.shouldMatch("Adapters:\\s+total=[1-9][0-9]+"); + } else { + // AOTAdapterCaching is off, no adapters should be stored out.shouldMatch("Adapters:\\s+total=0"); - break; } - } - if (isStubCachingOn()) { - switch (runMode) { - case RunMode.ASSEMBLY: - case RunMode.PRODUCTION: - // AOTStubCaching is on, non-zero stubs should be stored/loaded + if (isStubCachingOn(aMode)) { + // AOTStubCaching is on, non-zero stubs should be stored out.shouldMatch("Shared Blobs:\\s+total=[1-9][0-9]+"); out.shouldMatch("C1 Blobs:\\s+total=[1-9][0-9]+"); // we do not currently load or store C2 stubs // because we are seeing weird memory errors // when loading them -- see JDK-8357593 out.shouldMatch("C2 Blobs:\\s+total=0"); - break; - } - } else { - switch (runMode) { - case RunMode.ASSEMBLY: - case RunMode.PRODUCTION: - // AOTStubCaching is off, no stubs should be stored/loaded + } else { + // AOTStubCaching is off, no stubs should be stored out.shouldMatch("Shared Blobs:\\s+total=0"); out.shouldMatch("C1 Blobs:\\s+total=0"); out.shouldMatch("C2 Blobs:\\s+total=0"); - break; + } + } + } else if (runMode == RunMode.PRODUCTION) { + // Irrespective of assembly run mode, if both adapter and stub caching is disabled + // in production run, then it is equivalent to completely disabling AOT code cache + if (!isAdapterCachingOn(pMode) && !isStubCachingOn(pMode)) { + out.shouldNotMatch("Adapters:\\s+total"); + out.shouldNotMatch("Shared Blobs:\\s+total"); + out.shouldNotMatch("C1 Blobs:\\s+total"); + out.shouldNotMatch("C2 Blobs:\\s+total"); + } else { + // If AOT code cache is effectively disabled in the assembly run, then production run + // would emit empty code cache message. + if (!isAdapterCachingOn(aMode) && !isStubCachingOn(aMode)) { + if (isAdapterCachingOn(pMode) || isStubCachingOn(pMode)) { + out.shouldMatch("AOT Code Cache is empty"); + } + } else { + if (isAdapterCachingOn(aMode)) { + if (isAdapterCachingOn(pMode)) { + out.shouldMatch("Read blob.*kind=Adapter.*"); + } else { + out.shouldNotMatch("Read blob.*kind=Adapter.*"); + } + } + if (isStubCachingOn(aMode)) { + if (isStubCachingOn(pMode)) { + out.shouldMatch("Read blob.*kind=SharedBlob.*"); + out.shouldMatch("Read blob.*kind=C1Blob.*"); + } else { + out.shouldNotMatch("Read blob.*kind=SharedBlob.*"); + out.shouldNotMatch("Read blob.*kind=C1Blob.*"); + } + } } } } diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCode/AOTCodeTest.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCode/AOTCodeTest.java new file mode 100644 index 00000000000..2afcd802fc5 --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCode/AOTCodeTest.java @@ -0,0 +1,196 @@ +/* + * 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. + * + */ + +/** + * @test id=g1 + * @requires vm.gc.G1 + * @summary Sanity test of AOTCodeCache + * @requires vm.cds.supports.aot.code.caching + * @requires vm.compiler1.enabled & vm.compiler2.enabled + * @comment Both C1 and C2 JIT compilers are required because the test verifies + * compiler's runtime blobs generation. + * @requires vm.opt.VerifyOops == null | vm.opt.VerifyOops == false + * @comment VerifyOops flag switch off AOT code generation. Skip it. + * @library /test/lib /test/setup_aot + * @build AOTCodeTest JavacBenchApp + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar + * JavacBenchApp + * JavacBenchApp$ClassFile + * JavacBenchApp$FileManager + * JavacBenchApp$SourceFile + * @run driver/timeout=1500 AOTCodeTest G1 + */ +/** + * @test id=parallel + * @requires vm.gc.Parallel + * @summary Sanity test of AOTCodeCache + * @requires vm.cds.supports.aot.code.caching + * @requires vm.compiler1.enabled & vm.compiler2.enabled + * @comment Both C1 and C2 JIT compilers are required because the test verifies + * compiler's runtime blobs generation. + * @requires vm.opt.VerifyOops == null | vm.opt.VerifyOops == false + * @comment VerifyOops flag switch off AOT code generation. Skip it. + * @library /test/lib /test/setup_aot + * @build AOTCodeTest JavacBenchApp + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar + * JavacBenchApp + * JavacBenchApp$ClassFile + * JavacBenchApp$FileManager + * JavacBenchApp$SourceFile + * @run driver/timeout=1500 AOTCodeTest Parallel + */ +/** + * @test id=serial + * @requires vm.gc.Serial + * @summary Sanity test of AOTCodeCache + * @requires vm.cds.supports.aot.code.caching + * @requires vm.compiler1.enabled & vm.compiler2.enabled + * @comment Both C1 and C2 JIT compilers are required because the test verifies + * compiler's runtime blobs generation. + * @requires vm.opt.VerifyOops == null | vm.opt.VerifyOops == false + * @comment VerifyOops flag switch off AOT code generation. Skip it. + * @library /test/lib /test/setup_aot + * @build AOTCodeTest JavacBenchApp + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar + * JavacBenchApp + * JavacBenchApp$ClassFile + * JavacBenchApp$FileManager + * JavacBenchApp$SourceFile + * @run driver/timeout=1500 AOTCodeTest Serial + */ +/** + * @test id=shenandoah + * @requires vm.gc.Shenandoah + * @summary Sanity test of AOTCodeCache + * @requires vm.cds.supports.aot.code.caching + * @requires vm.compiler1.enabled & vm.compiler2.enabled + * @comment Both C1 and C2 JIT compilers are required because the test verifies + * compiler's runtime blobs generation. + * @requires vm.opt.VerifyOops == null | vm.opt.VerifyOops == false + * @comment VerifyOops flag switch off AOT code generation. Skip it. + * @library /test/lib /test/setup_aot + * @build AOTCodeTest JavacBenchApp + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar + * JavacBenchApp + * JavacBenchApp$ClassFile + * JavacBenchApp$FileManager + * JavacBenchApp$SourceFile + * @run driver/timeout=1500 AOTCodeTest Shenandoah + */ +/** + * @test id=Z + * @requires vm.gc.Z + * @summary Sanity test of AOTCodeCache + * @requires vm.cds.supports.aot.code.caching + * @requires vm.compiler1.enabled & vm.compiler2.enabled + * @comment Both C1 and C2 JIT compilers are required because the test verifies + * compiler's runtime blobs generation. + * @requires vm.opt.VerifyOops == null | vm.opt.VerifyOops == false + * @comment VerifyOops flag switch off AOT code generation. Skip it. + * @library /test/lib /test/setup_aot + * @build AOTCodeTest JavacBenchApp + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar + * JavacBenchApp + * JavacBenchApp$ClassFile + * JavacBenchApp$FileManager + * JavacBenchApp$SourceFile + * @run driver/timeout=1500 AOTCodeTest Z + */ + +import java.util.ArrayList; +import java.util.List; + +import jdk.test.lib.cds.CDSAppTester; +import jdk.test.lib.process.OutputAnalyzer; + +public class AOTCodeTest { + private static String gcName = null; + public static void main(String... args) throws Exception { + if (args.length == 0) { + throw new RuntimeException("Expected GC name"); + } + Tester t = new Tester(args[0]); + t.run(new String[] {"AOT", "--two-step-training"}); + } + + static class Tester extends CDSAppTester { + private String gcName; + + public Tester(String name) { + super("AOTCodeTest"); + gcName = name; + } + + public List getGCArgs() { + List args = new ArrayList(); + args.add("-Xmx100M"); + switch (gcName) { + case "G1": + case "Parallel": + case "Serial": + case "Shenandoah": + case "Z": + args.add("-XX:+Use" + gcName + "GC"); + return args; + default: + throw new RuntimeException("Unexpected GC name " + gcName); + } + } + + @Override + public String classpath(RunMode runMode) { + return "app.jar"; + } + + @Override + public String[] vmArgs(RunMode runMode) { + List args = getGCArgs(); + // Add flags for logs + args.addAll(List.of("-Xlog:aot+codecache+init=debug", + "-Xlog:aot+codecache+exit=debug", + "-Xlog:aot+codecache+stubs=debug")); + // Add diagnostic flags + args.addAll(List.of("-XX:+UnlockDiagnosticVMOptions", + "-XX:+AbortVMOnAOTCodeFailure")); + return args.toArray(new String[args.size()]); + } + + @Override + public String[] appCommandLine(RunMode runMode) { + return new String[] { "JavacBenchApp", "10" }; + } + + @Override + public void checkExecution(OutputAnalyzer out, RunMode runMode) throws Exception { + if (runMode == RunMode.ASSEMBLY) { + out.shouldMatch("aot,codecache,exit.*\\s+AOT code cache size: [1-9]\\d+ bytes"); + } else if (runMode == RunMode.PRODUCTION) { + out.shouldMatch("aot,codecache,init.*\\s+Loaded [1-9]\\d+ AOT code entries from AOT Code Cache"); + out.shouldMatch("aot,codecache,stubs.*\\s+Read blob.*kind=Adapter.*"); + out.shouldMatch("aot,codecache,stubs.*\\s+Read blob.*kind=SharedBlob.*"); + out.shouldMatch("aot,codecache,stubs.*\\s+Read blob.*kind=C1Blob.*"); + } + } + } +} diff --git a/test/setup_aot/HelloWorld.java b/test/setup_aot/HelloWorld.java new file mode 100644 index 00000000000..e243dfb4e8e --- /dev/null +++ b/test/setup_aot/HelloWorld.java @@ -0,0 +1,29 @@ +/* + * 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. + * + */ + +public class HelloWorld { + public static void main(String args[]) { + System.out.println("HelloWorld"); + } +} From 21213024501cb3f19b2c42bd56f5553807dace9b Mon Sep 17 00:00:00 2001 From: Leo Korinth Date: Wed, 15 Apr 2026 14:50:24 +0000 Subject: [PATCH 065/108] 8380164: Fix implicit conversion in nativeInst_aarch64.hpp Co-authored-by: Andrew Haley Reviewed-by: aph, aseoane --- src/hotspot/cpu/aarch64/nativeInst_aarch64.hpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/hotspot/cpu/aarch64/nativeInst_aarch64.hpp b/src/hotspot/cpu/aarch64/nativeInst_aarch64.hpp index fc7274714ad..ab9896fa426 100644 --- a/src/hotspot/cpu/aarch64/nativeInst_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/nativeInst_aarch64.hpp @@ -97,7 +97,7 @@ protected: #define MACOS_WX_WRITE MACOS_AARCH64_ONLY(os::thread_wx_enable_write()) void set_char_at(int offset, char c) { MACOS_WX_WRITE; *addr_at(offset) = (u_char)c; } void set_int_at(int offset, jint i) { MACOS_WX_WRITE; *(jint*)addr_at(offset) = i; } - void set_uint_at(int offset, jint i) { MACOS_WX_WRITE; *(juint*)addr_at(offset) = i; } + void set_uint_at(int offset, juint i) { MACOS_WX_WRITE; *(juint*)addr_at(offset) = i; } void set_ptr_at(int offset, address ptr) { MACOS_WX_WRITE; *(address*)addr_at(offset) = ptr; } void set_oop_at(int offset, oop o) { MACOS_WX_WRITE; *(oop*)addr_at(offset) = o; } #undef MACOS_WX_WRITE @@ -178,13 +178,11 @@ public: address destination() const; void set_destination(address dest) { - int offset = dest - instruction_address(); - unsigned int insn = 0b100101 << 26; + int64_t offset = dest - instruction_address(); + juint insn = 0b100101u << 26u; assert((offset & 3) == 0, "should be"); - offset >>= 2; - offset &= (1 << 26) - 1; // mask off insn part - insn |= offset; - set_int_at(displacement_offset, insn); + Instruction_aarch64::spatch(reinterpret_cast
(&insn), 25, 0, offset >> 2); + set_uint_at(displacement_offset, insn); } void verify_alignment() { ; } From 29434cc6512819a0b0e7f831fee8623995cbc5bc Mon Sep 17 00:00:00 2001 From: Vladimir Ivanov Date: Wed, 15 Apr 2026 16:37:37 +0000 Subject: [PATCH 066/108] 8376421: C2: Missing branch on SubTypeCheck node Reviewed-by: kvn, dfenacci --- src/hotspot/share/opto/parse2.cpp | 20 +++-- ...stSubTypeCheckInterfaceNotImplemented.java | 76 +++++++++++++++++++ 2 files changed, 88 insertions(+), 8 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/types/TestSubTypeCheckInterfaceNotImplemented.java diff --git a/src/hotspot/share/opto/parse2.cpp b/src/hotspot/share/opto/parse2.cpp index ae20418942d..d732e6f04e1 100644 --- a/src/hotspot/share/opto/parse2.cpp +++ b/src/hotspot/share/opto/parse2.cpp @@ -1833,17 +1833,21 @@ void Parse::sharpen_type_after_if(BoolTest::mask btest, &obj, &cast_type)) { assert(obj != nullptr && cast_type != nullptr, "missing type check info"); const Type* obj_type = _gvn.type(obj); - const TypeOopPtr* tboth = obj_type->join_speculative(cast_type)->isa_oopptr(); - if (tboth != nullptr && tboth != obj_type && tboth->higher_equal(obj_type)) { + const Type* tboth = obj_type->filter_speculative(cast_type); + assert(tboth->higher_equal(obj_type) && tboth->higher_equal(cast_type), "sanity"); + if (tboth == Type::TOP && KillPathsReachableByDeadTypeNode) { + // Let dead type node cleaning logic prune effectively dead path for us. + // CheckCastPP::Value() == TOP and it will trigger the cleanup during GVN. + // Don't materialize the cast when cleanup is disabled, because + // it kills data and control leaving IR in broken state. + tboth = cast_type; + } + if (tboth != Type::TOP && tboth != obj_type) { int obj_in_map = map()->find_edge(obj); - JVMState* jvms = this->jvms(); if (obj_in_map >= 0 && - (jvms->is_loc(obj_in_map) || jvms->is_stk(obj_in_map))) { + (jvms()->is_loc(obj_in_map) || jvms()->is_stk(obj_in_map))) { TypeNode* ccast = new CheckCastPPNode(control(), obj, tboth); - const Type* tcc = ccast->as_Type()->type(); - assert(tcc != obj_type && tcc->higher_equal(obj_type), "must improve"); - // Delay transform() call to allow recovery of pre-cast value - // at the control merge. + // Delay transform() call to allow recovery of pre-cast value at the control merge. _gvn.set_type_bottom(ccast); record_for_igvn(ccast); // Here's the payoff. diff --git a/test/hotspot/jtreg/compiler/types/TestSubTypeCheckInterfaceNotImplemented.java b/test/hotspot/jtreg/compiler/types/TestSubTypeCheckInterfaceNotImplemented.java new file mode 100644 index 00000000000..f2828257a2e --- /dev/null +++ b/test/hotspot/jtreg/compiler/types/TestSubTypeCheckInterfaceNotImplemented.java @@ -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. + * + */ + +/* + * @test + * @bug 8376421 + * @summary "C2: Missing branch on SubTypeCheck node" + * + * @run main/othervm -Xbatch -XX:+IgnoreUnrecognizedVMOptions + * -XX:+KillPathsReachableByDeadTypeNode + * compiler.types.TestSubTypeCheckInterfaceNotImplemented + * + * @run main/othervm -Xbatch -XX:+IgnoreUnrecognizedVMOptions + * -XX:-KillPathsReachableByDeadTypeNode + * compiler.types.TestSubTypeCheckInterfaceNotImplemented + */ + +package compiler.types; + +public class TestSubTypeCheckInterfaceNotImplemented { + static abstract class A {} + static abstract class B extends A {} + static final class C extends B {} + + interface I {} + static final class BJ1 extends A implements I {} + static final class BJ2 extends A implements I {} + + static boolean testHelper2(B o) { + return true; + } + static boolean testHelper1(Object o) { + if (o instanceof B) { + return testHelper2((B)o); // a call to place "o" on JVMS, so the map is updated after the check + } else { + return false; + } + } + + static boolean test(A a) { + if (a instanceof I) { + return testHelper1((I)a); // "a" always fails instanceof check against B + } else { + return false; + } + } + + public static void main(String[] args) { + for (int i = 0; i < 20_000; i++) { + testHelper1(new C()); // pollute profile + + test(new BJ1()); test(new BJ2()); test(new C()); + } + } +} From 5acbf8baea3f1a982bdba29b51c8531caa919748 Mon Sep 17 00:00:00 2001 From: Patrick Fontanilla Date: Wed, 15 Apr 2026 19:02:28 +0000 Subject: [PATCH 067/108] 8381871: GenShen: ShenandoahGCHeuristics flag not reset after ignoring non-adaptive value Reviewed-by: xpeng, kdnilsen, ysr, serb, wkemper --- .../gc/shenandoah/shenandoahArguments.cpp | 1 + .../options/TestGenerationalHeuristics.java | 84 +++++++++++++++++++ 2 files changed, 85 insertions(+) create mode 100644 test/hotspot/jtreg/gc/shenandoah/options/TestGenerationalHeuristics.java diff --git a/src/hotspot/share/gc/shenandoah/shenandoahArguments.cpp b/src/hotspot/share/gc/shenandoah/shenandoahArguments.cpp index c1fa4b964b7..e9d6a686694 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahArguments.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahArguments.cpp @@ -200,6 +200,7 @@ void ShenandoahArguments::initialize() { && strcmp(ShenandoahGCHeuristics, "adaptive") != 0) { log_warning(gc)("Ignoring -XX:ShenandoahGCHeuristics input: %s, because generational shenandoah only" " supports adaptive heuristics", ShenandoahGCHeuristics); + FLAG_SET_ERGO(ShenandoahGCHeuristics, "adaptive"); } FullGCForwarding::initialize_flags(MaxHeapSize); diff --git a/test/hotspot/jtreg/gc/shenandoah/options/TestGenerationalHeuristics.java b/test/hotspot/jtreg/gc/shenandoah/options/TestGenerationalHeuristics.java new file mode 100644 index 00000000000..822c3806347 --- /dev/null +++ b/test/hotspot/jtreg/gc/shenandoah/options/TestGenerationalHeuristics.java @@ -0,0 +1,84 @@ +/* + * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +/* + * @test id=generational + * @bug 8381871 + * @summary Test that ShenandoahGCHeuristics is always adaptive in generational mode + * @requires vm.gc.Shenandoah + * @library /test/lib + * @run driver TestGenerationalHeuristics + */ + +import jdk.test.lib.process.ProcessTools; +import jdk.test.lib.process.OutputAnalyzer; + +public class TestGenerationalHeuristics { + public static void main(String[] args) throws Exception { + { + OutputAnalyzer output = ProcessTools.executeLimitedTestJava( + "-XX:+UseShenandoahGC", + "-XX:ShenandoahGCMode=generational", + "-XX:ShenandoahGCHeuristics=adaptive", + "-XX:+PrintFlagsFinal", + "-version"); + output.shouldMatch("ShenandoahGCHeuristics(.*)= adaptive "); + output.shouldHaveExitValue(0); + } + + { + OutputAnalyzer output = ProcessTools.executeLimitedTestJava( + "-XX:+UseShenandoahGC", + "-XX:ShenandoahGCMode=generational", + "-XX:ShenandoahGCHeuristics=static", + "-XX:+PrintFlagsFinal", + "-version"); + output.shouldMatch("ShenandoahGCHeuristics(.*)= adaptive "); + output.shouldHaveExitValue(0); + } + + { + OutputAnalyzer output = ProcessTools.executeLimitedTestJava( + "-XX:+UseShenandoahGC", + "-XX:ShenandoahGCMode=generational", + "-XX:ShenandoahGCHeuristics=aggressive", + "-XX:+PrintFlagsFinal", + "-version"); + output.shouldMatch("ShenandoahGCHeuristics(.*)= adaptive "); + output.shouldHaveExitValue(0); + } + + { + OutputAnalyzer output = ProcessTools.executeLimitedTestJava( + "-XX:+UseShenandoahGC", + "-XX:ShenandoahGCMode=generational", + "-XX:ShenandoahGCHeuristics=compact", + "-XX:+PrintFlagsFinal", + "-version"); + output.shouldMatch("ShenandoahGCHeuristics(.*)= adaptive "); + output.shouldHaveExitValue(0); + } + } + +} From 2a8e3b8e66651cc6ba2f9c53aae32dde41e72a21 Mon Sep 17 00:00:00 2001 From: Vladimir Kozlov Date: Wed, 15 Apr 2026 19:02:58 +0000 Subject: [PATCH 068/108] 8382174: Clarify the meaning of address cast in ADD() macro Reviewed-by: aseoane, adinn, asmehra, dfenacci --- .../cpu/aarch64/stubGenerator_aarch64.cpp | 2 +- .../cpu/aarch64/stubRoutines_aarch64.cpp | 7 ++-- src/hotspot/cpu/x86/c1_LIRAssembler_x86.cpp | 2 +- .../x86/stubGenerator_x86_64_constants.cpp | 5 +-- .../cpu/x86/stubGenerator_x86_64_exp.cpp | 15 +++++---- .../cpu/x86/stubGenerator_x86_64_fmod.cpp | 2 +- .../cpu/x86/stubGenerator_x86_64_ghash.cpp | 2 +- .../cpu/x86/stubGenerator_x86_64_log.cpp | 32 ++++++++++-------- .../cpu/x86/stubGenerator_x86_64_poly1305.cpp | 2 +- .../x86/stubGenerator_x86_64_poly_mont.cpp | 2 +- .../cpu/x86/stubGenerator_x86_64_pow.cpp | 33 +++++++++++-------- .../cpu/x86/stubGenerator_x86_64_sha3.cpp | 2 +- .../cpu/x86/stubGenerator_x86_64_sin.cpp | 2 +- .../cpu/x86/stubGenerator_x86_64_sinh.cpp | 26 ++++++++------- .../cpu/x86/stubGenerator_x86_64_tan.cpp | 8 +++-- .../cpu/x86/stubGenerator_x86_64_tanh.cpp | 22 ++++++++----- src/hotspot/cpu/x86/stubRoutines_x86.cpp | 2 +- 17 files changed, 93 insertions(+), 73 deletions(-) diff --git a/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp b/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp index 5d9b2f1d826..fddb37b7b8d 100644 --- a/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp @@ -12825,7 +12825,7 @@ class StubGenerator: public StubCodeGenerator { #if INCLUDE_CDS static void init_AOTAddressTable(GrowableArray
& external_addresses) { // external data defined in this file -#define ADD(addr) external_addresses.append((address)addr); +#define ADD(addr) external_addresses.append((address)(addr)); ADD(_sha256_round_consts); ADD(_sha512_round_consts); ADD(_sha3_round_consts); diff --git a/src/hotspot/cpu/aarch64/stubRoutines_aarch64.cpp b/src/hotspot/cpu/aarch64/stubRoutines_aarch64.cpp index aaf31d4f911..f02b681ca10 100644 --- a/src/hotspot/cpu/aarch64/stubRoutines_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/stubRoutines_aarch64.cpp @@ -435,10 +435,8 @@ void StubRoutines::init_AOTAddressTable() { AOTCodeCache::publish_external_addresses(external_addresses); } - -#define ADD(addr) external_addresses.append((address)addr); - void StubRoutines::aarch64::init_AOTAddressTable(GrowableArray
& external_addresses) { +#define ADD(addr) external_addresses.append((address)(addr)); ADD(_kyberConsts); ADD(_dilithiumConsts); // this is added in generic code @@ -449,7 +447,6 @@ void StubRoutines::aarch64::init_AOTAddressTable(GrowableArray
& externa ADD(_dcos_coef); ADD(_two_over_pi); ADD(_pio2); -} - #undef ADD +} #endif // INCLUDE_CDS diff --git a/src/hotspot/cpu/x86/c1_LIRAssembler_x86.cpp b/src/hotspot/cpu/x86/c1_LIRAssembler_x86.cpp index 29925e71aaf..5c05b3702bb 100644 --- a/src/hotspot/cpu/x86/c1_LIRAssembler_x86.cpp +++ b/src/hotspot/cpu/x86/c1_LIRAssembler_x86.cpp @@ -74,7 +74,7 @@ static jlong *double_signflip_pool = double_quadword(&fp_signmask_pool[4*2], (jl #if INCLUDE_CDS // publish external addresses defined in this file void LIR_Assembler::init_AOTAddressTable(GrowableArray
& external_addresses) { -#define ADD(addr) external_addresses.append((address)addr); +#define ADD(addr) external_addresses.append((address)(addr)); ADD(float_signmask_pool); ADD(double_signmask_pool); ADD(float_signflip_pool); diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64_constants.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64_constants.cpp index 45c13b7b397..19e1ca680b3 100644 --- a/src/hotspot/cpu/x86/stubGenerator_x86_64_constants.cpp +++ b/src/hotspot/cpu/x86/stubGenerator_x86_64_constants.cpp @@ -247,8 +247,9 @@ void StubGenerator::init_AOTAddressTable_constants(GrowableArray
& exter ADD(_SC_2); ADD(_SC_3); ADD(_SC_4); - ADD(_PI_4); - ADD(((address)_PI_4+8)); + // Use value which was already cast to (address): StubGenerator::PI_4; + ADD(PI_4); + ADD(PI_4 + 8); ADD(_PI32INV); ADD(_NEG_ZERO); ADD(_P_1); diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64_exp.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64_exp.cpp index 2ed9858bf0c..3c8babcbecf 100644 --- a/src/hotspot/cpu/x86/stubGenerator_x86_64_exp.cpp +++ b/src/hotspot/cpu/x86/stubGenerator_x86_64_exp.cpp @@ -397,13 +397,14 @@ address StubGenerator::generate_libmExp() { #if INCLUDE_CDS void StubGenerator::init_AOTAddressTable_exp(GrowableArray
& external_addresses) { -#define ADD(addr) external_addresses.append((address)addr); - ADD(_cv); - ADD(((address)_cv+16)); - ADD(((address)_cv+32)); - ADD(((address)_cv+48)); - ADD(((address)_cv+64)); - ADD(((address)_cv+80)); +#define ADD(addr) external_addresses.append((address)(addr)); + address cv = (address)_cv; + ADD(cv); + ADD(cv + 16); + ADD(cv + 32); + ADD(cv + 48); + ADD(cv + 64); + ADD(cv + 80); ADD(_mmask); ADD(_bias); ADD(_Tbl_addr); diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64_fmod.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64_fmod.cpp index f73c8ed459e..f53985a13b7 100644 --- a/src/hotspot/cpu/x86/stubGenerator_x86_64_fmod.cpp +++ b/src/hotspot/cpu/x86/stubGenerator_x86_64_fmod.cpp @@ -537,7 +537,7 @@ address StubGenerator::generate_libmFmod() { #if INCLUDE_CDS void StubGenerator::init_AOTAddressTable_fmod(GrowableArray
& external_addresses) { -#define ADD(addr) external_addresses.append((address)addr); +#define ADD(addr) external_addresses.append((address)(addr)); ADD(CONST_NaN); ADD(CONST_1p260); ADD(CONST_MAX); diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64_ghash.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64_ghash.cpp index 557fe623351..9ebab07589e 100644 --- a/src/hotspot/cpu/x86/stubGenerator_x86_64_ghash.cpp +++ b/src/hotspot/cpu/x86/stubGenerator_x86_64_ghash.cpp @@ -558,7 +558,7 @@ void StubGenerator::generateHtbl_eight_blocks(Register htbl) { #if INCLUDE_CDS void StubGenerator::init_AOTAddressTable_ghash(GrowableArray
& external_addresses) { -#define ADD(addr) external_addresses.append((address)addr); +#define ADD(addr) external_addresses.append((address)(addr)); ADD(GHASH_SHUFFLE_MASK); ADD(GHASH_LONG_SWAP_MASK); ADD(GHASH_BYTE_SWAP_MASK); diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64_log.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64_log.cpp index 8849597c94b..07683a51e3d 100644 --- a/src/hotspot/cpu/x86/stubGenerator_x86_64_log.cpp +++ b/src/hotspot/cpu/x86/stubGenerator_x86_64_log.cpp @@ -729,22 +729,28 @@ address StubGenerator::generate_libmLog10() { #if INCLUDE_CDS void StubGenerator::init_AOTAddressTable_log(GrowableArray
& external_addresses) { -#define ADD(addr) external_addresses.append((address)addr); +#define ADD(addr) external_addresses.append((address)(addr)); + address log2 = (address)_log2; + address coeff = (address)_coeff; + address LOG10_E = (address)_LOG10_E; + address log2_log10 = (address)_log2_log10; + address coeff_log10 = (address)_coeff_log10; + ADD(_L_tbl); - ADD(_log2); - ADD(((address)_log2+8)); - ADD(_coeff); - ADD(((address)_coeff+16)); - ADD(((address)_coeff+32)); + ADD(log2); + ADD(log2 + 8); + ADD(coeff); + ADD(coeff + 16); + ADD(coeff + 32); ADD(_HIGHSIGMASK_log10); - ADD(_LOG10_E); - ADD(((address)_LOG10_E+8)); + ADD(LOG10_E); + ADD(LOG10_E + 8); ADD(_L_tbl_log10); - ADD(_log2_log10); - ADD(((address)_log2_log10+8)); - ADD(_coeff_log10); - ADD(((address)_coeff_log10+16)); - ADD(((address)_coeff_log10+32)); + ADD(log2_log10); + ADD(log2_log10 + 8); + ADD(coeff_log10); + ADD(coeff_log10 + 16); + ADD(coeff_log10 + 32); #undef ADD } #endif // INCLUDE_CDS diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64_poly1305.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64_poly1305.cpp index 1d0e961c82d..ea7e6d64254 100644 --- a/src/hotspot/cpu/x86/stubGenerator_x86_64_poly1305.cpp +++ b/src/hotspot/cpu/x86/stubGenerator_x86_64_poly1305.cpp @@ -1709,7 +1709,7 @@ void StubGenerator::poly1305_msg_mul_reduce_vec4_avx2( #if INCLUDE_CDS void StubGenerator::init_AOTAddressTable_poly1305(GrowableArray
& external_addresses) { -#define ADD(addr) external_addresses.append((address)addr); +#define ADD(addr) external_addresses.append((address)(addr)); ADD(POLY1305_PAD_MSG); ADD(POLY1305_MASK42); ADD(POLY1305_MASK44); diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64_poly_mont.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64_poly_mont.cpp index 4648fe03aa0..308a8042993 100644 --- a/src/hotspot/cpu/x86/stubGenerator_x86_64_poly_mont.cpp +++ b/src/hotspot/cpu/x86/stubGenerator_x86_64_poly_mont.cpp @@ -788,7 +788,7 @@ address StubGenerator::generate_intpoly_assign() { #if INCLUDE_CDS void StubGenerator::init_AOTAddressTable_poly_mont(GrowableArray
& external_addresses) { -#define ADD(addr) external_addresses.append((address)addr); +#define ADD(addr) external_addresses.append((address)(addr)); // use accessors to retrieve all correct addresses ADD(shift_1L()); ADD(shift_1R()); diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64_pow.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64_pow.cpp index 5ff09e2b377..a9a6dc10da4 100644 --- a/src/hotspot/cpu/x86/stubGenerator_x86_64_pow.cpp +++ b/src/hotspot/cpu/x86/stubGenerator_x86_64_pow.cpp @@ -1875,25 +1875,30 @@ address StubGenerator::generate_libmPow() { #if INCLUDE_CDS void StubGenerator::init_AOTAddressTable_pow(GrowableArray
& external_addresses) { -#define ADD(addr) external_addresses.append((address)addr); +#define ADD(addr) external_addresses.append((address)(addr)); + address HIGHMASK_Y = (address)_HIGHMASK_Y; + address e_coeff = (address)_e_coeff; + address coeff_h = (address)_coeff_h; + address coeff_pow = (address)_coeff_pow; + ADD(_HIGHSIGMASK); ADD(_LOG2_E); - ADD(_HIGHMASK_Y); - ADD((address)_HIGHMASK_Y+8); + ADD(HIGHMASK_Y); + ADD(HIGHMASK_Y + 8); ADD(_T_exp); - ADD(_e_coeff); - ADD((address)_e_coeff+16); - ADD((address)_e_coeff+32); - ADD(_coeff_h); - ADD((address)_coeff_h+8); + ADD(e_coeff); + ADD(e_coeff + 16); + ADD(e_coeff + 32); + ADD(coeff_h); + ADD(coeff_h + 8); ADD(_HIGHMASK_LOG_X); ADD(_HALFMASK); - ADD(_coeff_pow); - ADD((address)_coeff_pow+16); - ADD((address)_coeff_pow+32); - ADD((address)_coeff_pow+48); - ADD((address)_coeff_pow+64); - ADD((address)_coeff_pow+80); + ADD(coeff_pow); + ADD(coeff_pow + 16); + ADD(coeff_pow + 32); + ADD(coeff_pow + 48); + ADD(coeff_pow + 64); + ADD(coeff_pow + 80); ADD(_L_tbl_pow); ADD(_log2_pow); ADD(_DOUBLE2); diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64_sha3.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64_sha3.cpp index 075d25dcac8..58f81652a0c 100644 --- a/src/hotspot/cpu/x86/stubGenerator_x86_64_sha3.cpp +++ b/src/hotspot/cpu/x86/stubGenerator_x86_64_sha3.cpp @@ -530,7 +530,7 @@ void StubGenerator::generate_sha3_stubs() { #if INCLUDE_CDS void StubGenerator::init_AOTAddressTable_sha3(GrowableArray
& external_addresses) { -#define ADD(addr) external_addresses.append((address)addr); +#define ADD(addr) external_addresses.append((address)(addr)); ADD(round_constsAddr()); ADD(permsAndRotsAddr()); #undef ADD diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64_sin.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64_sin.cpp index eaeaea2c566..00c759a369b 100644 --- a/src/hotspot/cpu/x86/stubGenerator_x86_64_sin.cpp +++ b/src/hotspot/cpu/x86/stubGenerator_x86_64_sin.cpp @@ -661,7 +661,7 @@ address StubGenerator::generate_libmSin() { #if INCLUDE_CDS void StubGenerator::init_AOTAddressTable_sin(GrowableArray
& external_addresses) { -#define ADD(addr) external_addresses.append((address)addr); +#define ADD(addr) external_addresses.append((address)(addr)); ADD(_ALL_ONES); #undef ADD } diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64_sinh.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64_sinh.cpp index f6e1d241948..9969866cfc7 100644 --- a/src/hotspot/cpu/x86/stubGenerator_x86_64_sinh.cpp +++ b/src/hotspot/cpu/x86/stubGenerator_x86_64_sinh.cpp @@ -535,21 +535,25 @@ address StubGenerator::generate_libmSinh() { #if INCLUDE_CDS void StubGenerator::init_AOTAddressTable_sinh(GrowableArray
& external_addresses) { -#define ADD(addr) external_addresses.append((address)addr); - ADD(_L2E); - ADD(_L2E + 8); +#define ADD(addr) external_addresses.append((address)(addr)); + address L2E = (address)_L2E; + address cv = (address)_cv; + address pv = (address)_pv; + + ADD(L2E); + ADD(L2E + 8); ADD(_HALFMASK); ADD(_Shifter); - ADD(_cv); - ADD(_cv + 16); - ADD(_cv + 32); - ADD(_cv + 48); - ADD(_cv + 64); + ADD(cv); + ADD(cv + 16); + ADD(cv + 32); + ADD(cv + 48); + ADD(cv + 64); ADD(_T2f); ADD(_T2_neg_f); - ADD(_pv); - ADD(_pv + 16); - ADD(_pv + 32); + ADD(pv); + ADD(pv + 16); + ADD(pv + 32); ADD(_MASK3); #undef ADD } diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64_tan.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64_tan.cpp index 3bfa5a7277f..9f91b9e8f84 100644 --- a/src/hotspot/cpu/x86/stubGenerator_x86_64_tan.cpp +++ b/src/hotspot/cpu/x86/stubGenerator_x86_64_tan.cpp @@ -1041,7 +1041,9 @@ address StubGenerator::generate_libmTan() { #if INCLUDE_CDS void StubGenerator::init_AOTAddressTable_tan(GrowableArray
& external_addresses) { -#define ADD(addr) external_addresses.append((address)addr); +#define ADD(addr) external_addresses.append((address)(addr)); + address PI_4_tan = (address)_PI_4_tan; + ADD(_MUL16); ADD(_sign_mask_tan); ADD(_PI32INV_tan); @@ -1055,8 +1057,8 @@ void StubGenerator::init_AOTAddressTable_tan(GrowableArray
& external_ad ADD(_Q_7_tan); ADD(_Q_5_tan); ADD(_Q_3_tan); - ADD(_PI_4_tan); - ADD(((address)_PI_4_tan+8)); + ADD(PI_4_tan); + ADD(PI_4_tan + 8); ADD(_QQ_2_tan); #undef ADD } diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64_tanh.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64_tanh.cpp index dcf5f3eb824..4f2fe8a460b 100644 --- a/src/hotspot/cpu/x86/stubGenerator_x86_64_tanh.cpp +++ b/src/hotspot/cpu/x86/stubGenerator_x86_64_tanh.cpp @@ -511,20 +511,24 @@ address StubGenerator::generate_libmTanh() { #if INCLUDE_CDS void StubGenerator::init_AOTAddressTable_tanh(GrowableArray
& external_addresses) { -#define ADD(addr) external_addresses.append((address)addr); - ADD(_L2E); - ADD(_L2E + 8); +#define ADD(addr) external_addresses.append((address)(addr)); + address L2E = (address)_L2E; + address cv = (address)_cv; + address pv = (address)_pv; + + ADD(L2E); + ADD(L2E + 8); ADD(_HALFMASK); ADD(_ONEMASK); ADD(_TWOMASK); ADD(_Shifter); - ADD(_cv); - ADD(_cv + 16); - ADD(_cv + 32); + ADD(cv); + ADD(cv + 16); + ADD(cv + 32); ADD(_T2_neg_f); - ADD(_pv); - ADD(_pv + 16); - ADD(_pv + 32); + ADD(pv); + ADD(pv + 16); + ADD(pv + 32); ADD(_MASK3); ADD(_RMASK); #undef ADD diff --git a/src/hotspot/cpu/x86/stubRoutines_x86.cpp b/src/hotspot/cpu/x86/stubRoutines_x86.cpp index aaee01437af..ce11925dde2 100644 --- a/src/hotspot/cpu/x86/stubRoutines_x86.cpp +++ b/src/hotspot/cpu/x86/stubRoutines_x86.cpp @@ -439,7 +439,7 @@ void StubRoutines::init_AOTAddressTable() { // publish addresses of external data defined in this file which may // be referenced from stub or code void StubRoutines::x86::init_AOTAddressTable(GrowableArray
& external_addresses) { -#define ADD(addr) external_addresses.append((address)addr); +#define ADD(addr) external_addresses.append((address)(addr)); ADD(&_mxcsr_std); ADD(&_mxcsr_rz); ADD(crc_by128_masks_addr()); From a2e840fd7413cbcbd663a30cb5681557e6142319 Mon Sep 17 00:00:00 2001 From: Rui Li Date: Wed, 15 Apr 2026 19:03:28 +0000 Subject: [PATCH 069/108] 8342786: Shenandoah: Emit AllocationRequiringGC jfr events Reviewed-by: kdnilsen, serb, wkemper --- .../gc/shenandoah/shenandoahController.cpp | 6 ++- ...tressAllocationGCEventsWithShenandoah.java | 49 +++++++++++++++++++ ...ssBigAllocationGCEventsWithShenandoah.java | 49 +++++++++++++++++++ 3 files changed, 102 insertions(+), 2 deletions(-) create mode 100644 test/hotspot/jtreg/gc/stress/jfr/TestStressAllocationGCEventsWithShenandoah.java create mode 100644 test/hotspot/jtreg/gc/stress/jfr/TestStressBigAllocationGCEventsWithShenandoah.java diff --git a/src/hotspot/share/gc/shenandoah/shenandoahController.cpp b/src/hotspot/share/gc/shenandoah/shenandoahController.cpp index 50aabad7d42..0096aad2570 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahController.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahController.cpp @@ -23,13 +23,13 @@ * */ +#include "gc/shared/allocTracer.hpp" #include "gc/shared/gc_globals.hpp" #include "gc/shenandoah/shenandoahCollectorPolicy.hpp" #include "gc/shenandoah/shenandoahController.hpp" #include "gc/shenandoah/shenandoahHeap.hpp" #include "gc/shenandoah/shenandoahHeapRegion.inline.hpp" - void ShenandoahController::update_gc_id() { _gc_id.add_then_fetch((size_t)1); } @@ -45,10 +45,12 @@ void ShenandoahController::handle_alloc_failure(const ShenandoahAllocRequest& re const GCCause::Cause cause = is_humongous ? GCCause::_shenandoah_humongous_allocation_failure : GCCause::_allocation_failure; ShenandoahHeap* const heap = ShenandoahHeap::heap(); + size_t req_byte = req.size() * HeapWordSize; if (heap->cancel_gc(cause)) { - log_info(gc)("Failed to allocate %s, " PROPERFMT, req.type_string(), PROPERFMTARGS(req.size() * HeapWordSize)); + log_info(gc)("Failed to allocate %s, " PROPERFMT, req.type_string(), PROPERFMTARGS(req_byte)); request_gc(cause); } + AllocTracer::send_allocation_requiring_gc_event(req_byte, checked_cast(get_gc_id())); if (block) { MonitorLocker ml(&_alloc_failure_waiters_lock); diff --git a/test/hotspot/jtreg/gc/stress/jfr/TestStressAllocationGCEventsWithShenandoah.java b/test/hotspot/jtreg/gc/stress/jfr/TestStressAllocationGCEventsWithShenandoah.java new file mode 100644 index 00000000000..743e59bca61 --- /dev/null +++ b/test/hotspot/jtreg/gc/stress/jfr/TestStressAllocationGCEventsWithShenandoah.java @@ -0,0 +1,49 @@ +/* + * Copyright Amazon.com Inc. 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 + * 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. + * + */ +package jdk.jfr.event.gc.detailed; + +/** + * @test id=default + * @key randomness + * @requires vm.hasJFR + * @requires vm.gc.Shenandoah + * @library /test/lib /test/jdk + * @run main/othervm -XX:+UseShenandoahGC -Xmx64m -XX:ActiveProcessorCount=1 jdk.jfr.event.gc.detailed.TestStressAllocationGCEventsWithShenandoah + */ + + /** + * @test id=generational + * @key randomness + * @requires vm.hasJFR + * @requires vm.gc.Shenandoah + * @library /test/lib /test/jdk + * @run main/othervm -XX:+UseShenandoahGC -XX:ShenandoahGCMode=generational -Xmx64m -XX:ActiveProcessorCount=1 jdk.jfr.event.gc.detailed.TestStressAllocationGCEventsWithShenandoah + */ +public class TestStressAllocationGCEventsWithShenandoah { + + public static void main(String[] args) throws Exception { + new StressAllocationGCEvents().run(args); + } +} diff --git a/test/hotspot/jtreg/gc/stress/jfr/TestStressBigAllocationGCEventsWithShenandoah.java b/test/hotspot/jtreg/gc/stress/jfr/TestStressBigAllocationGCEventsWithShenandoah.java new file mode 100644 index 00000000000..cb7e4b5d1ee --- /dev/null +++ b/test/hotspot/jtreg/gc/stress/jfr/TestStressBigAllocationGCEventsWithShenandoah.java @@ -0,0 +1,49 @@ +/* + * Copyright Amazon.com Inc. 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 + * 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. + * + */ +package jdk.jfr.event.gc.detailed; + +/** + * @test id=default + * @key randomness + * @requires vm.hasJFR + * @requires vm.gc.Shenandoah + * @library /test/lib /test/jdk + * @run main/othervm -XX:+UseShenandoahGC -Xmx256m -XX:ActiveProcessorCount=1 jdk.jfr.event.gc.detailed.TestStressBigAllocationGCEventsWithShenandoah 1048576 + */ + + /** + * @test id=generational + * @key randomness + * @requires vm.hasJFR + * @requires vm.gc.Shenandoah + * @library /test/lib /test/jdk + * @run main/othervm -XX:+UseShenandoahGC -XX:ShenandoahGCMode=generational -Xmx256m -XX:ActiveProcessorCount=1 jdk.jfr.event.gc.detailed.TestStressBigAllocationGCEventsWithShenandoah 1048576 + */ +public class TestStressBigAllocationGCEventsWithShenandoah { + + public static void main(String[] args) throws Exception { + new StressAllocationGCEvents().run(args); + } +} From f87bb766a79a4f33a5ac9dd3337d31368c1d5b8f Mon Sep 17 00:00:00 2001 From: Chris Plummer Date: Wed, 15 Apr 2026 19:35:52 +0000 Subject: [PATCH 070/108] 8381385: nsk/jdi/ClassLoaderReference/visibleClasses/visibleclasses002/ fails with --enable-preview and Java Usage Tracker enabled Reviewed-by: dholmes, lmesnik --- .../visibleClasses/visibleclasses002.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jdi/ClassLoaderReference/visibleClasses/visibleclasses002.java b/test/hotspot/jtreg/vmTestbase/nsk/jdi/ClassLoaderReference/visibleClasses/visibleclasses002.java index 8fe41faa886..78cbe664da3 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jdi/ClassLoaderReference/visibleClasses/visibleclasses002.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jdi/ClassLoaderReference/visibleClasses/visibleclasses002.java @@ -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 @@ -138,7 +138,9 @@ public class visibleclasses002 { } } } catch (ClassNotLoadedException e) { - throw new Failure("Unexpected ClassNotLoadedException while getting componentType() of : " + refType); + // ArrayType.componentType() can throw ClassNotLoadedException if the + // type is loaded but not yet prepared. Just swallow the exception. + display("ClassNotLoadedException while getting componentType() of " + refType); } } From f2289d84d3b99686e90756921738d406f543337f Mon Sep 17 00:00:00 2001 From: Chris Plummer Date: Wed, 15 Apr 2026 21:35:02 +0000 Subject: [PATCH 071/108] 8381609: com/sun/jdi/EATests.java should not synchronize on an Integer instance Reviewed-by: rrich, phubner, lmesnik, sspitsyn --- test/jdk/com/sun/jdi/EATests.java | 42 +------------------------------ 1 file changed, 1 insertion(+), 41 deletions(-) diff --git a/test/jdk/com/sun/jdi/EATests.java b/test/jdk/com/sun/jdi/EATests.java index 3a936f288cc..f5289e6b011 100644 --- a/test/jdk/com/sun/jdi/EATests.java +++ b/test/jdk/com/sun/jdi/EATests.java @@ -1,4 +1,5 @@ /* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2025 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -80,20 +81,6 @@ * -XX:-DoEscapeAnalysis -XX:-EliminateAllocations -XX:+EliminateLocks -XX:+EliminateNestedLocks * -XX:+IgnoreUnrecognizedVMOptions -XX:+DeoptimizeObjectsALot * - * @bug 8324881 - * @comment Regression test for using the wrong thread when logging during re-locking from deoptimization. - * - * @comment DiagnoseSyncOnValueBasedClasses=2 will cause logging when locking on \@ValueBased objects. - * @run driver EATests - * -XX:+UnlockDiagnosticVMOptions - * -Xms256m -Xmx256m - * -Xbootclasspath/a:. - * -XX:CompileCommand=dontinline,*::dontinline_* - * -XX:+WhiteBoxAPI - * -Xbatch - * -XX:+DoEscapeAnalysis -XX:+EliminateAllocations -XX:+EliminateLocks -XX:+EliminateNestedLocks - * -XX:DiagnoseSyncOnValueBasedClasses=2 - * * @comment Re-lock may inflate monitors when re-locking, which cause monitorinflation trace logging. * @run driver EATests * -XX:+UnlockDiagnosticVMOptions @@ -259,7 +246,6 @@ class EATestsTarget { new EAGetOwnedMonitorsTarget() .run(); new EAEntryCountTarget() .run(); new EARelockingObjectCurrentlyWaitingOnTarget() .run(); - new EARelockingValueBasedTarget() .run(); // Test cases that require deoptimization even though neither // locks nor allocations are eliminated at the point where @@ -385,7 +371,6 @@ public class EATests extends TestScaffold { new EAGetOwnedMonitors() .run(this); new EAEntryCount() .run(this); new EARelockingObjectCurrentlyWaitingOn() .run(this); - new EARelockingValueBased() .run(this); // Test cases that require deoptimization even though neither // locks nor allocations are eliminated at the point where @@ -2402,31 +2387,6 @@ class EARelockingObjectCurrentlyWaitingOnTarget extends EATestCaseBaseTarget { } -///////////////////////////////////////////////////////////////////////////// - -/** - * Test relocking eliminated @ValueBased object. - */ -class EARelockingValueBased extends EATestCaseBaseDebugger { - - public void runTestCase() throws Exception { - BreakpointEvent bpe = resumeTo(TARGET_TESTCASE_BASE_NAME, "dontinline_brkpt", "()V"); - printStack(bpe.thread()); - @SuppressWarnings("unused") - ObjectReference o = getLocalRef(bpe.thread().frame(1), Integer.class.getName(), "l1"); - } -} - -class EARelockingValueBasedTarget extends EATestCaseBaseTarget { - - public void dontinline_testMethod() { - Integer l1 = new Integer(255); - synchronized (l1) { - dontinline_brkpt(); - } - } -} - ///////////////////////////////////////////////////////////////////////////// // // Test cases that require deoptimization even though neither locks From 533a8a8d2605f19f5f6f867c3ff5d649305d0ee5 Mon Sep 17 00:00:00 2001 From: William Kemper Date: Wed, 15 Apr 2026 21:54:39 +0000 Subject: [PATCH 072/108] 8376839: GenShen: Improve performance of evacuations into the old generation Reviewed-by: kdnilsen --- .../gc/shenandoah/shenandoahConcurrentGC.cpp | 23 +++ .../gc/shenandoah/shenandoahConcurrentGC.hpp | 4 +- .../gc/shenandoah/shenandoahDegeneratedGC.cpp | 5 + .../share/gc/shenandoah/shenandoahFreeSet.cpp | 3 +- .../shenandoahGenerationalEvacuationTask.cpp | 1 + .../shenandoah/shenandoahGenerationalHeap.cpp | 4 - .../share/gc/shenandoah/shenandoahHeap.cpp | 25 +++ .../share/gc/shenandoah/shenandoahHeap.hpp | 1 + .../gc/shenandoah/shenandoahHeapRegion.cpp | 6 + .../gc/shenandoah/shenandoahHeapRegion.hpp | 6 +- .../shenandoahHeapRegionClosures.cpp | 5 + .../shenandoah/shenandoahInPlacePromoter.cpp | 3 + .../gc/shenandoah/shenandoahOldGeneration.cpp | 33 +++- .../gc/shenandoah/shenandoahOldGeneration.hpp | 37 +++- .../share/gc/shenandoah/shenandoahPLAB.cpp | 8 - .../gc/shenandoah/shenandoahPhaseTimings.hpp | 3 +- .../shenandoah/shenandoahScanRemembered.cpp | 165 +++++++++++------- .../shenandoah/shenandoahScanRemembered.hpp | 7 + 18 files changed, 248 insertions(+), 91 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp index f0125c38cae..ba0aa1a1304 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp @@ -112,6 +112,24 @@ void ShenandoahConcurrentGC::entry_concurrent_update_refs_prepare(ShenandoahHeap heap->concurrent_prepare_for_update_refs(); } +void ShenandoahConcurrentGC::entry_update_card_table() { + ShenandoahHeap* const heap = ShenandoahHeap::heap(); + TraceCollectorStats tcs(heap->monitoring_support()->concurrent_collection_counters()); + + static const char* msg = "Concurrent update cards"; + ShenandoahConcurrentPhase gc_phase(msg, ShenandoahPhaseTimings::conc_update_card_table); + EventMark em("%s", msg); + + ShenandoahWorkerScope scope(heap->workers(), + ShenandoahWorkerPolicy::calc_workers_for_conc_evac(), + "concurrent update cards"); + + // Heap needs to be parsable here. + // Also, parallel heap region iterate must have a phase set. + assert(ShenandoahTimingsTracker::is_current_phase_valid(), "Current phase must be set"); + ShenandoahGenerationalHeap::heap()->old_generation()->update_card_table(); +} + bool ShenandoahConcurrentGC::collect(GCCause::Cause cause) { ShenandoahHeap* const heap = ShenandoahHeap::heap(); _generation->ref_processor()->set_soft_reference_policy( @@ -206,6 +224,11 @@ bool ShenandoahConcurrentGC::collect(GCCause::Cause cause) { // Perform update-refs phase. entry_concurrent_update_refs_prepare(heap); + + if (ShenandoahHeap::heap()->mode()->is_generational()) { + entry_update_card_table(); + } + if (ShenandoahVerify) { vmop_entry_init_update_refs(); } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.hpp b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.hpp index 54d43416fdb..ba228901d72 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.hpp @@ -59,8 +59,6 @@ public: bool collect(GCCause::Cause cause) override; ShenandoahDegenPoint degen_point() const; - void entry_concurrent_update_refs_prepare(ShenandoahHeap* heap); - // Return true if this cycle found enough immediate garbage to skip evacuation bool abbreviated() const { return _abbreviated; } @@ -95,6 +93,8 @@ protected: void entry_cleanup_early(); void entry_evacuate(); void entry_update_thread_roots(); + void entry_update_card_table(); + void entry_concurrent_update_refs_prepare(ShenandoahHeap* heap); void entry_update_refs(); void entry_cleanup_complete(); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahDegeneratedGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahDegeneratedGC.cpp index 1873d818093..84b22f13d47 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahDegeneratedGC.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahDegeneratedGC.cpp @@ -277,6 +277,11 @@ void ShenandoahDegenGC::op_degenerated() { _abbreviated = true; } + // labs are retired, walk the old regions and update remembered set + if (ShenandoahHeap::heap()->mode()->is_generational()) { + ShenandoahGenerationalHeap::heap()->old_generation()->update_card_table(); + } + case _degenerated_update_refs: if (heap->has_forwarded_objects()) { op_update_refs(); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahFreeSet.cpp b/src/hotspot/share/gc/shenandoah/shenandoahFreeSet.cpp index a579d6d3694..592c5bffa5a 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahFreeSet.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahFreeSet.cpp @@ -1569,7 +1569,7 @@ HeapWord* ShenandoahFreeSet::try_allocate_in(ShenandoahHeapRegion* r, Shenandoah // 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 + // region's affiliation with FREE after we set the region's affiliation to req.affiliation() below r->try_recycle_under_lock(); in_new_region = r->is_empty(); if (in_new_region) { @@ -1585,7 +1585,6 @@ HeapWord* ShenandoahFreeSet::try_allocate_in(ShenandoahHeapRegion* r, Shenandoah // concurrent preparations for mixed evacuations are completed), we mark this region as not requiring any // coalesce-and-fill processing. r->end_preemptible_coalesce_and_fill(); - _heap->old_generation()->clear_cards_for(r); } #ifdef ASSERT ShenandoahMarkingContext* const ctx = _heap->marking_context(); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalEvacuationTask.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalEvacuationTask.cpp index 6912750378e..ca15c6db443 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalEvacuationTask.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalEvacuationTask.cpp @@ -133,3 +133,4 @@ void ShenandoahGenerationalEvacuationTask::evacuate_and_promote_regions() { } } } + diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp index d5cfa4b7fb9..a51449e91f4 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp @@ -363,10 +363,6 @@ oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, uint // Record that the evacuation succeeded evac_tracker()->end_evacuation(thread, size * HeapWordSize, FROM_GENERATION, TO_GENERATION); } - - if (TO_GENERATION == OLD_GENERATION) { - old_generation()->handle_evacuation(copy, size); - } } else { // Failed to evacuate. We need to deal with the object that is left behind. Since this // new allocation is certainly after TAMS, it will be considered live in the next cycle. diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp index 5bf76505506..75d3ade4e49 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp @@ -1955,6 +1955,26 @@ void ShenandoahHeap::heap_region_iterate(ShenandoahHeapRegionClosure* blk) const } } +class ShenandoahHeapRegionIteratorTask : public WorkerTask { +private: + ShenandoahRegionIterator _regions; + ShenandoahHeapRegionClosure* _closure; + +public: + ShenandoahHeapRegionIteratorTask(ShenandoahHeapRegionClosure* closure) + : WorkerTask("Shenandoah Heap Region Iterator") + , _closure(closure) {} + + void work(uint worker_id) override { + ShenandoahParallelWorkerSession worker_session(worker_id); + ShenandoahHeapRegion* region = _regions.next(); + while (region != nullptr) { + _closure->heap_region_do(region); + region = _regions.next(); + } + } +}; + class ShenandoahParallelHeapRegionTask : public WorkerTask { private: ShenandoahHeap* const _heap; @@ -2011,6 +2031,11 @@ void ShenandoahHeap::parallel_heap_region_iterate(ShenandoahHeapRegionClosure* b } } +void ShenandoahHeap::heap_region_iterator(ShenandoahHeapRegionClosure* closure) const { + ShenandoahHeapRegionIteratorTask task(closure); + workers()->run_task(&task); +} + class ShenandoahRendezvousHandshakeClosure : public HandshakeClosure { public: inline ShenandoahRendezvousHandshakeClosure(const char* name) : HandshakeClosure(name) {} diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp index d4604be0aec..ab7dd00b774 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp @@ -298,6 +298,7 @@ public: void heap_region_iterate(ShenandoahHeapRegionClosure* blk) const; void parallel_heap_region_iterate(ShenandoahHeapRegionClosure* blk) const; + void heap_region_iterator(ShenandoahHeapRegionClosure* blk) const; inline ShenandoahMmuTracker* mmu_tracker() { return &_mmu_tracker; }; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.cpp index afc6b24e168..3db11000af5 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.cpp @@ -67,6 +67,7 @@ ShenandoahHeapRegion::ShenandoahHeapRegion(HeapWord* start, size_t index, bool c _new_top(nullptr), _empty_time(os::elapsedTime()), _top_before_promoted(nullptr), + _top_at_evac_start(start), _state(committed ? _empty_committed : _empty_uncommitted), _top(start), _tlab_allocs(0), @@ -565,12 +566,17 @@ void ShenandoahHeapRegion::recycle_internal() { assert(_recycling.is_set() && is_trash(), "Wrong state"); ShenandoahHeap* heap = ShenandoahHeap::heap(); + _top_at_evac_start = _bottom; _mixed_candidate_garbage_words = 0; set_top(bottom()); clear_live_data(); reset_alloc_metadata(); heap->marking_context()->reset_top_at_mark_start(this); set_update_watermark(bottom()); + if (is_old()) { + heap->old_generation()->clear_cards_for(this); + } + if (ZapUnusedHeapArea) { SpaceMangler::mangle_region(MemRegion(bottom(), end())); } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.hpp b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.hpp index 3a0ac042f57..569b64f756b 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.hpp @@ -246,6 +246,7 @@ private: double _empty_time; HeapWord* _top_before_promoted; + HeapWord* _top_at_evac_start; // Seldom updated fields Atomic _state; @@ -365,12 +366,15 @@ public: } // Returns true iff this region was promoted in place subsequent to the most recent start of concurrent old marking. - inline bool was_promoted_in_place() { + bool was_promoted_in_place() const { return _promoted_in_place; } inline void restore_top_before_promote(); inline size_t garbage_before_padded_for_promote() const; + HeapWord* get_top_at_evac_start() const { return _top_at_evac_start; } + void record_top_at_evac_start() { _top_at_evac_start = _top; } + // If next available memory is not aligned on address that is multiple of alignment, fill the empty space // so that returned object is aligned on an address that is a multiple of alignment_in_bytes. Requested // size is in words. It is assumed that this->is_old(). A pad object is allocated, filled, and registered diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegionClosures.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegionClosures.cpp index 3c6fe1a3df1..7554a9c9a2c 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegionClosures.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegionClosures.cpp @@ -80,6 +80,11 @@ void ShenandoahFinalMarkUpdateRegionStateClosure::heap_region_do(ShenandoahHeapR // Remember limit for updating refs. It's guaranteed that we get no // from-space-refs written from here on. r->set_update_watermark_at_safepoint(r->top()); + + if (r->is_old()) { + // Record where we need to start updating the remembered set + r->record_top_at_evac_start(); + } } else { assert(!r->has_live(), "Region %zu should have no live data", r->index()); assert(_ctx == nullptr || _ctx->top_at_mark_start(r) == r->top(), diff --git a/src/hotspot/share/gc/shenandoah/shenandoahInPlacePromoter.cpp b/src/hotspot/share/gc/shenandoah/shenandoahInPlacePromoter.cpp index 10d61221e87..153193fa3a3 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahInPlacePromoter.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahInPlacePromoter.cpp @@ -238,6 +238,9 @@ void ShenandoahInPlacePromoter::promote(ShenandoahHeapRegion* region) const { // is_collector_free range. We'll add it to that range below. region->restore_top_before_promote(); + // We also need to record where those allocations begin so that we can later update the remembered set. + region->record_top_at_evac_start(); + assert(region->used() + pip_pad_bytes + pip_unpadded == region_size_bytes, "invariant"); // The update_watermark was likely established while we had the artificially high value of top. Make it sane now. diff --git a/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.cpp b/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.cpp index 4ad7d2a1ae5..37de5966554 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.cpp @@ -318,6 +318,11 @@ void ShenandoahOldGeneration::heap_region_iterate(ShenandoahHeapRegionClosure* c ShenandoahHeap::heap()->heap_region_iterate(&old_regions_cl); } +void ShenandoahOldGeneration::heap_region_iterator(ShenandoahHeapRegionClosure* cl) { + ShenandoahIncludeRegionClosure old_regions_cl(cl); + ShenandoahHeap::heap()->heap_region_iterator(&old_regions_cl); +} + void ShenandoahOldGeneration::set_concurrent_mark_in_progress(bool in_progress) { ShenandoahHeap::heap()->set_concurrent_old_mark_in_progress(in_progress); } @@ -326,6 +331,12 @@ bool ShenandoahOldGeneration::is_concurrent_mark_in_progress() { return ShenandoahHeap::heap()->is_concurrent_old_mark_in_progress(); } +void ShenandoahOldGeneration::record_tops_at_evac_start() { + for_each_region([](ShenandoahHeapRegion* region) { + region->record_top_at_evac_start(); + }); +} + void ShenandoahOldGeneration::cancel_marking() { if (is_concurrent_mark_in_progress()) { log_debug(gc)("Abandon SATB buffers"); @@ -662,15 +673,19 @@ void ShenandoahOldGeneration::log_failed_promotion(LogStream& ls, Thread* thread } } -void ShenandoahOldGeneration::handle_evacuation(HeapWord* obj, size_t words) const { - // Only register the copy of the object that won the evacuation race. - _card_scan->register_object_without_lock(obj); - - // Mark the entire range of the evacuated object as dirty. At next remembered set scan, - // we will clear dirty bits that do not hold interesting pointers. It's more efficient to - // do this in batch, in a background GC thread than to try to carefully dirty only cards - // that hold interesting pointers right now. - _card_scan->mark_range_as_dirty(obj, words); +void ShenandoahOldGeneration::update_card_table() { + for_each_region([this](ShenandoahHeapRegion* region) { + if (region->is_regular()) { + // Humongous regions are promoted in place, remembered set maintenance is handled there + // Regular regions that are promoted in place have their rset maintenance handled for + // the objects in the region when it was promoted. We record TEAS for such a region + // when the in-place-promotion is completed. Such a region may be used for additional + // promotions in the same cycle it was itself promoted. + if (region->top() > region->get_top_at_evac_start()) { + _card_scan->update_card_table(region->get_top_at_evac_start(), region->top()); + } + } + }); } bool ShenandoahOldGeneration::has_unprocessed_collection_candidates() { diff --git a/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.hpp b/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.hpp index 0069d38a84e..942f93c5c68 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.hpp @@ -172,8 +172,8 @@ public: void handle_failed_promotion(Thread* thread, size_t size) const; void log_failed_promotion(LogStream& ls, Thread* thread, size_t size) const; - // A successful evacuation re-dirties the cards and registers the object with the remembered set - void handle_evacuation(HeapWord* obj, size_t words) const; + // Iterate over recently promoted objects to update card table and object registrations + void update_card_table(); // Clear the flag after it is consumed by the control thread bool clear_failed_evacuation() { @@ -199,11 +199,36 @@ public: // Mark card for this location as dirty void mark_card_as_dirty(void* location); + template + class ShenandoahHeapRegionLambda : public ShenandoahHeapRegionClosure { + T _region_lambda; + public: + explicit ShenandoahHeapRegionLambda(T region_lambda) : _region_lambda(region_lambda) {} + + void heap_region_do(ShenandoahHeapRegion* r) override { + _region_lambda(r); + } + + bool is_thread_safe() override { + return true; + } + + size_t parallel_region_stride() override { + // Temporarily override to force parallelism when updating card table + return 8; + } + }; + + template + void for_each_region(LambdaT lambda) { + ShenandoahHeapRegionLambda l(lambda); + heap_region_iterator(&l); + } + void parallel_heap_region_iterate(ShenandoahHeapRegionClosure* cl) override; - void parallel_heap_region_iterate_free(ShenandoahHeapRegionClosure* cl) override; - void heap_region_iterate(ShenandoahHeapRegionClosure* cl) override; + void heap_region_iterator(ShenandoahHeapRegionClosure* cl); bool contains(ShenandoahAffiliation affiliation) const override; bool contains(ShenandoahHeapRegion* region) const override; @@ -212,6 +237,10 @@ public: void set_concurrent_mark_in_progress(bool in_progress) override; bool is_concurrent_mark_in_progress() override; + // For old regions, objects between top at evac start and top represent promoted objects. + // These objects will need to have their cards dirtied and their offsets within the cards registered. + void record_tops_at_evac_start(); + bool entry_coalesce_and_fill(); // Global collections touch old regions, so the old generation needs to be informed of this. diff --git a/src/hotspot/share/gc/shenandoah/shenandoahPLAB.cpp b/src/hotspot/share/gc/shenandoah/shenandoahPLAB.cpp index 412cfa9447e..5049113b665 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahPLAB.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahPLAB.cpp @@ -210,12 +210,4 @@ void ShenandoahPLAB::retire() { // plab->retire() overwrites unused memory between plab->top() and plab->hard_end() with a dummy object to make memory parsable. // It adds the size of this unused memory, in words, to plab->waste(). _plab->retire(); - if (top != nullptr && _plab->waste() > original_waste && _heap->is_in_old(top)) { - // If retiring the plab created a filler object, then we need to register it with our card scanner so it can - // safely walk the region backing the plab. - log_debug(gc, plab)("retire_plab() is registering remnant of size %zu at " PTR_FORMAT, - (_plab->waste() - original_waste) * HeapWordSize, p2i(top)); - // No lock is necessary because the PLAB memory is aligned on card boundaries. - _heap->old_generation()->card_scan()->register_object_without_lock(top); - } } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahPhaseTimings.hpp b/src/hotspot/share/gc/shenandoah/shenandoahPhaseTimings.hpp index e890008b916..a454de68f00 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahPhaseTimings.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahPhaseTimings.hpp @@ -109,6 +109,7 @@ class outputStream; f(conc_strong_roots, "Concurrent Strong Roots") \ SHENANDOAH_PAR_PHASE_DO(conc_strong_roots_, " CSR: ", f) \ f(conc_evac, "Concurrent Evacuation") \ + f(conc_update_card_table, "Concurrent Update Cards") \ f(conc_final_roots, "Concurrent Final Roots") \ f(promote_in_place, " Promote Regions") \ f(final_roots_gross, "Pause Verify Final Roots (G)") \ @@ -254,7 +255,7 @@ public: void flush_cycle_to_global(); static const char* phase_name(Phase phase) { - assert(phase >= 0 && phase < _num_phases, "Out of bound"); + assert(phase >= 0 && phase < _num_phases, "Out of bounds: %d", phase); return _phase_names[phase]; } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.cpp b/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.cpp index 9e160d5b294..8d7ba2dc46f 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.cpp @@ -31,6 +31,33 @@ #include "logging/log.hpp" #include "runtime/threads.hpp" +// A closure that takes an oop in the old generation and, if it's pointing +// into the young generation, dirties the corresponding remembered set entry. +class ShenandoahDirtyRememberedSetClosure : public BasicOopIterateClosure { +protected: + ShenandoahGenerationalHeap* const _heap; + ShenandoahScanRemembered* const _scanner; + +public: + ShenandoahDirtyRememberedSetClosure() : + _heap(ShenandoahGenerationalHeap::heap()), + _scanner(_heap->old_generation()->card_scan()) {} + + template + void work(T* p) { + assert(_heap->is_in_old(p), "Expecting to get an old gen address"); + if (T o = RawAccess<>::oop_load(p); !CompressedOops::is_null(o)) { + if (const oop obj = CompressedOops::decode_not_null(o); _heap->is_in_young(obj)) { + // Dirty the card containing the cross-generational pointer. + _scanner->mark_card_as_dirty((HeapWord*) p); + } + } + } + + void do_oop(narrowOop* p) override { work(p); } + void do_oop(oop* p) override { work(p); } +}; + size_t ShenandoahDirectCardMarkRememberedSet::last_valid_index() const { return _card_table->last_valid_index(); } @@ -161,7 +188,6 @@ void ShenandoahCardCluster::register_object_without_lock(HeapWord* address) { uint8_t offset_in_card = checked_cast(pointer_delta(address, card_start_address)); if (!starts_object(card_at_start)) { - set_starts_object_bit(card_at_start); set_first_start(card_at_start, offset_in_card); set_last_start(card_at_start, offset_in_card); } else { @@ -172,6 +198,49 @@ void ShenandoahCardCluster::register_object_without_lock(HeapWord* address) { } } +void ShenandoahCardCluster::update_card_table(HeapWord* start, HeapWord* end) { + HeapWord* address = start; + HeapWord* previous_address = nullptr; + uint8_t previous_offset = 0; + size_t previous_card_index = -1; + ShenandoahDirtyRememberedSetClosure make_cards_dirty; + + log_debug(gc, remset)("Update remembered set from " PTR_FORMAT ", to " PTR_FORMAT, p2i(start), p2i(end)); + _rs->mark_range_as_dirty(start, pointer_delta(end, start)); + + while (address < end) { + + // Compute card and offset in card for this object + const size_t object_card_index = _rs->card_index_for_addr(address); + const HeapWord* card_start_address = _rs->addr_for_card_index(object_card_index); + const uint8_t offset_in_card = checked_cast(pointer_delta(address, card_start_address)); + + if (object_card_index != previous_card_index) { + if (previous_address != nullptr) { + // Register the previous object on the previous card, we are starting a new card here + set_last_start(previous_card_index, previous_offset); + } + + previous_card_index = object_card_index; + if (!starts_object(object_card_index)) { + // The previous cycle may have recorded an earlier start in this card. Do not overwrite it. + set_first_start(object_card_index, offset_in_card); + } + } + + previous_offset = offset_in_card; + previous_address = address; + + const oop obj = cast_to_oop(address); + address += obj->size(); + } + + // Register the last object seen in this range. + if (previous_address != nullptr) { + set_last_start(previous_card_index, previous_offset); + } +} + void ShenandoahCardCluster::coalesce_objects(HeapWord* address, size_t length_in_words) { size_t card_at_start = _rs->card_index_for_addr(address); @@ -641,36 +710,6 @@ void ShenandoahScanRemembered::merge_worker_card_stats_cumulative( } #endif -// A closure that takes an oop in the old generation and, if it's pointing -// into the young generation, dirties the corresponding remembered set entry. -// This is only used to rebuild the remembered set after a full GC. -class ShenandoahDirtyRememberedSetClosure : public BasicOopIterateClosure { -protected: - ShenandoahGenerationalHeap* const _heap; - ShenandoahScanRemembered* const _scanner; - -public: - ShenandoahDirtyRememberedSetClosure() : - _heap(ShenandoahGenerationalHeap::heap()), - _scanner(_heap->old_generation()->card_scan()) {} - - template - inline void work(T* p) { - assert(_heap->is_in_old(p), "Expecting to get an old gen address"); - T o = RawAccess<>::oop_load(p); - if (!CompressedOops::is_null(o)) { - oop obj = CompressedOops::decode_not_null(o); - if (_heap->is_in_young(obj)) { - // Dirty the card containing the cross-generational pointer. - _scanner->mark_card_as_dirty((HeapWord*) p); - } - } - } - - virtual void do_oop(narrowOop* p) { work(p); } - virtual void do_oop(oop* p) { work(p); } -}; - ShenandoahDirectCardMarkRememberedSet::ShenandoahDirectCardMarkRememberedSet(ShenandoahCardTable* card_table, size_t total_card_count) : LogCardValsPerIntPtr(log2i_exact(sizeof(intptr_t)) - log2i_exact(sizeof(CardValue))), LogCardSizeInWords(log2i_exact(CardTable::card_size_in_words())) { @@ -1039,38 +1078,44 @@ void ShenandoahReconstructRememberedSetTask::work(uint worker_id) { ShenandoahDirtyRememberedSetClosure dirty_cards_for_cross_generational_pointers; while (r != nullptr) { - if (r->is_old() && r->is_active()) { - HeapWord* obj_addr = r->bottom(); - if (r->is_humongous_start()) { - // First, clear the remembered set - oop obj = cast_to_oop(obj_addr); - size_t size = obj->size(); - - size_t num_regions = ShenandoahHeapRegion::required_regions(size * HeapWordSize); - size_t region_index = r->index(); - ShenandoahHeapRegion* humongous_region = heap->get_region(region_index); - while (num_regions-- != 0) { - scanner->reset_object_range(humongous_region->bottom(), humongous_region->end()); - region_index++; - humongous_region = heap->get_region(region_index); - } - - // Then register the humongous object and DIRTY relevant remembered set cards - scanner->register_object_without_lock(obj_addr); - obj->oop_iterate(&dirty_cards_for_cross_generational_pointers); - } else if (!r->is_humongous()) { - scanner->reset_object_range(r->bottom(), r->end()); - - // Then iterate over all objects, registering object and DIRTYing relevant remembered set cards - HeapWord* t = r->top(); - while (obj_addr < t) { + if (r->is_active()) { + if (r->is_old()) { + HeapWord* obj_addr = r->bottom(); + if (r->is_humongous_start()) { + // First, clear the remembered set oop obj = cast_to_oop(obj_addr); + size_t size = obj->size(); + + size_t num_regions = ShenandoahHeapRegion::required_regions(size * HeapWordSize); + size_t region_index = r->index(); + ShenandoahHeapRegion* humongous_region = heap->get_region(region_index); + while (num_regions-- != 0) { + scanner->reset_object_range(humongous_region->bottom(), humongous_region->end()); + region_index++; + humongous_region = heap->get_region(region_index); + } + + // Then register the humongous object and DIRTY relevant remembered set cards scanner->register_object_without_lock(obj_addr); - obj_addr += obj->oop_iterate_size(&dirty_cards_for_cross_generational_pointers); - } - } // else, ignore humongous continuation region + obj->oop_iterate(&dirty_cards_for_cross_generational_pointers); + } else if (!r->is_humongous()) { + scanner->reset_object_range(r->bottom(), r->end()); + + // Then iterate over all objects, registering object and DIRTYing relevant remembered set cards + HeapWord* t = r->top(); + while (obj_addr < t) { + oop obj = cast_to_oop(obj_addr); + scanner->register_object_without_lock(obj_addr); + obj_addr += obj->oop_iterate_size(&dirty_cards_for_cross_generational_pointers); + } + } // else, ignore humongous continuation region + } else { + // The region is young, but it may become old again and we don't want stale remembered set data. + assert(r->is_young(), "Region: %zu, is active but free", r->index()); + heap->old_generation()->clear_cards_for(r); + } } - // else, this region is FREE or YOUNG or inactive and we can ignore it. + // else, this region is FREE or inactive and we can ignore it. r = _regions->next(); } } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.hpp b/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.hpp index c2c117e86e6..53f00e64a03 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahScanRemembered.hpp @@ -603,6 +603,9 @@ public: // as address. void register_object_without_lock(HeapWord* address); + // Dirty cards and register objects for the given range in memory. + void update_card_table(HeapWord* start, HeapWord* end); + // During the reference updates phase of GC, we walk through each old-gen memory region that was // not part of the collection set and we invalidate all unmarked objects. As part of this effort, // we coalesce neighboring dead objects in order to make future remembered set scanning more @@ -814,6 +817,10 @@ public: } } + void update_card_table(HeapWord* start, HeapWord* end) const { + _scc->update_card_table(start, end); + } + // Return true iff this object is "properly" registered. bool verify_registration(HeapWord* address, ShenandoahMarkingContext* ctx); From a2261a4b7c84909f8c116552f32a6fb5553b0ce6 Mon Sep 17 00:00:00 2001 From: Ioi Lam Date: Thu, 16 Apr 2026 05:17:06 +0000 Subject: [PATCH 073/108] 8382196: Clean up C++ iterator style in compactHashtable.hpp and trainingData.hpp Reviewed-by: kvn, qamai --- src/hotspot/share/classfile/compactHashtable.hpp | 7 +------ src/hotspot/share/oops/trainingData.hpp | 13 +++---------- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/src/hotspot/share/classfile/compactHashtable.hpp b/src/hotspot/share/classfile/compactHashtable.hpp index 81f2951289d..1711c5f8cd3 100644 --- a/src/hotspot/share/classfile/compactHashtable.hpp +++ b/src/hotspot/share/classfile/compactHashtable.hpp @@ -307,14 +307,9 @@ public: template inline void iterate(ITER* iter) const { iterate([&](V v) { iter->do_value(v); }); } - template - inline void iterate(const Function& function) const { // lambda enabled API - iterate(const_cast(function)); - } - // Iterate through the values in the table, stopping when the lambda returns false. template - inline void iterate(Function& function) const { // lambda enabled API + inline void iterate(Function function) const { // lambda enabled API for (u4 i = 0; i < _bucket_count; i++) { u4 bucket_info = _buckets[i]; u4 bucket_offset = BUCKET_OFFSET(bucket_info); diff --git a/src/hotspot/share/oops/trainingData.hpp b/src/hotspot/share/oops/trainingData.hpp index bd696f52a8b..a6decdce7f0 100644 --- a/src/hotspot/share/oops/trainingData.hpp +++ b/src/hotspot/share/oops/trainingData.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 @@ -217,11 +217,7 @@ public: return *prior; } template - void iterate(const Function& fn) const { // lambda enabled API - iterate(const_cast(fn)); - } - template - void iterate(Function& fn) const { // lambda enabled API + void iterate(Function fn) const { // lambda enabled API return _table.iterate_all([&](const TrainingData::Key* k, TrainingData* td) { fn(td); }); } int size() const { return _table.number_of_entries(); } @@ -304,10 +300,7 @@ private: } template - static void iterate(const Function& fn) { iterate(const_cast(fn)); } - - template - static void iterate(Function& fn) { // lambda enabled API + static void iterate(Function fn) { // lambda enabled API TrainingDataLocker l; if (have_data()) { archived_training_data_dictionary()->iterate_all(fn); From 01bfd427a322c6f92643c7920bbd1b1b35141a69 Mon Sep 17 00:00:00 2001 From: Xiaolong Peng Date: Thu, 16 Apr 2026 06:08:06 +0000 Subject: [PATCH 074/108] 8381382: Shenandoah: assert(capacity > 0) failed: free regions must have allocation capacity Reviewed-by: kdnilsen, wkemper --- .../gc/shenandoah/shenandoahHeapRegion.cpp | 20 +++++++++++++------ .../gc/shenandoah/shenandoahHeapRegion.hpp | 2 +- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.cpp index 3db11000af5..c031569b7c6 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.cpp @@ -568,7 +568,6 @@ void ShenandoahHeapRegion::recycle_internal() { _top_at_evac_start = _bottom; _mixed_candidate_garbage_words = 0; - set_top(bottom()); clear_live_data(); reset_alloc_metadata(); heap->marking_context()->reset_top_at_mark_start(this); @@ -580,16 +579,21 @@ void ShenandoahHeapRegion::recycle_internal() { if (ZapUnusedHeapArea) { SpaceMangler::mangle_region(MemRegion(bottom(), end())); } - - make_empty(); + set_top(bottom()); set_affiliation(FREE); + + // Lastly, set region state to empty + make_empty(); } // Upon return, this region has been recycled. We try to recycle it. // We may fail if some other thread recycled it before we do. void ShenandoahHeapRegion::try_recycle_under_lock() { shenandoah_assert_heaplocked(); - if (is_trash() && _recycling.try_set()) { + if (!is_trash()) { + return; + } + if (_recycling.try_set()) { if (is_trash()) { // At freeset rebuild time, which precedes recycling of collection set, we treat all cset regions as // part of capacity, as empty, as fully available, and as unaffiliated. This provides short-lived optimism @@ -609,6 +613,7 @@ void ShenandoahHeapRegion::try_recycle_under_lock() { os::naked_yield(); } } + assert(!is_trash(), "Must not"); } } @@ -616,7 +621,10 @@ void ShenandoahHeapRegion::try_recycle_under_lock() { // 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()) { + if (!is_trash()) { + return; + } + if (_recycling.try_set()) { // Double check region state after win the race to set recycling flag if (is_trash()) { // At freeset rebuild time, which precedes recycling of collection set, we treat all cset regions as @@ -840,7 +848,7 @@ void ShenandoahHeapRegion::set_state(RegionState to) { evt.set_to(to); evt.commit(); } - _state.store_relaxed(to); + _state.release_store(to); } void ShenandoahHeapRegion::record_pin() { diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.hpp b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.hpp index 569b64f756b..e27bbbb737d 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.hpp @@ -218,7 +218,7 @@ public: bool is_alloc_allowed() const { auto cur_state = state(); return is_empty_state(cur_state) || cur_state == _regular || cur_state == _pinned; } bool is_stw_move_allowed() const { auto cur_state = state(); return cur_state == _regular || cur_state == _cset || (ShenandoahHumongousMoves && cur_state == _humongous_start); } - RegionState state() const { return _state.load_relaxed(); } + RegionState state() const { return _state.load_acquire(); } int state_ordinal() const { return region_state_to_ordinal(state()); } void record_pin(); From 8688c0b65516f0a035ae8d25790489981475d07f Mon Sep 17 00:00:00 2001 From: Anton Seoane Ampudia Date: Thu, 16 Apr 2026 11:18:22 +0000 Subject: [PATCH 075/108] 8379129: C2 crash in LoadNode::can_split_through_phi_base during Escape Analysis (JDK 25.0.1+8) Reviewed-by: kvn, rcastanedalo, dlong --- src/hotspot/share/opto/memnode.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/hotspot/share/opto/memnode.cpp b/src/hotspot/share/opto/memnode.cpp index c7fe4508473..a49d4708d32 100644 --- a/src/hotspot/share/opto/memnode.cpp +++ b/src/hotspot/share/opto/memnode.cpp @@ -1671,11 +1671,15 @@ bool LoadNode::can_split_through_phi_base(PhaseGVN* phase) { intptr_t ignore = 0; Node* base = AddPNode::Ideal_base_and_offset(address, phase, ignore); + if (base == nullptr) { + return false; + } + if (base->is_CastPP()) { base = base->in(1); } - if (req() > 3 || base == nullptr || !base->is_Phi()) { + if (req() > 3 || !base->is_Phi()) { return false; } From 04bb3a91dbfb8fb9db93ea9609210e036a4c5d10 Mon Sep 17 00:00:00 2001 From: Matthew Donovan Date: Thu, 16 Apr 2026 14:11:07 +0000 Subject: [PATCH 076/108] 8381176: Utilities.expectedNegoAppProtocol chooses client ALPN protocol over server's Reviewed-by: djelinski, myankelevich --- .../net/ssl/TLSCommon/interop/Utilities.java | 10 ++++----- .../javax/net/ssl/compatibility/AlpnTest.java | 21 ++++++++++++++++++- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/test/jdk/javax/net/ssl/TLSCommon/interop/Utilities.java b/test/jdk/javax/net/ssl/TLSCommon/interop/Utilities.java index 34cccb5356c..0a825d6cc7d 100644 --- a/test/jdk/javax/net/ssl/TLSCommon/interop/Utilities.java +++ b/test/jdk/javax/net/ssl/TLSCommon/interop/Utilities.java @@ -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 @@ -430,10 +430,10 @@ public class Utilities { public static String expectedNegoAppProtocol(String[] serverAppProtocols, String[] clientAppProtocols) { if (serverAppProtocols != null && clientAppProtocols != null) { - for(String clientAppProtocol : clientAppProtocols) { - for(String serverAppProtocol : serverAppProtocols) { - if (clientAppProtocol.equals(serverAppProtocol)) { - return clientAppProtocol; + for(String serverAppProtocol : serverAppProtocols) { + for(String clientAppProtocol : clientAppProtocols) { + if (serverAppProtocol.equals(clientAppProtocol)) { + return serverAppProtocol; } } } diff --git a/test/jdk/javax/net/ssl/compatibility/AlpnTest.java b/test/jdk/javax/net/ssl/compatibility/AlpnTest.java index 71a60fe5435..86b75f4d2c3 100644 --- a/test/jdk/javax/net/ssl/compatibility/AlpnTest.java +++ b/test/jdk/javax/net/ssl/compatibility/AlpnTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -36,6 +36,8 @@ * @run main/manual AlpnTest false */ +import jtreg.SkippedException; + import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -98,6 +100,19 @@ public class AlpnTest extends ExtInteropTest { clientCase.setCipherSuites(cipherSuite); clientCase.setAppProtocols("h2"); + testCases.add( + new TestCase(serverCase, clientCase)); + + serverCase = ExtUseCase.newInstance(); + serverCase.setCertTuple(certTuple); + serverCase.setAppProtocols("http/1.1", "h2"); + + clientCase = ExtUseCase.newInstance(); + clientCase.setCertTuple(certTuple); + clientCase.setProtocols(protocol); + clientCase.setCipherSuites(cipherSuite); + clientCase.setAppProtocols("h2", "http/1.1"); + testCases.add( new TestCase(serverCase, clientCase)); } @@ -171,6 +186,10 @@ public class AlpnTest extends ExtInteropTest { Boolean defaultJdkAsServer = Boolean.valueOf(args[0]); Set jdkInfos = Utils.jdkInfoList(); + if (jdkInfos.isEmpty()) { + throw new SkippedException("No JDKs to test"); + } + for (JdkInfo jdkInfo : jdkInfos) { AlpnTest test = new AlpnTest( defaultJdkAsServer ? JdkInfo.DEFAULT : jdkInfo, From 26df25ef2db23ce815ca8d3a1fc2f4018ece6a81 Mon Sep 17 00:00:00 2001 From: Vladimir Kozlov Date: Thu, 16 Apr 2026 14:19:26 +0000 Subject: [PATCH 077/108] 8382045: Increase AOT C strings table size in not product VM Reviewed-by: adinn, asmehra --- src/hotspot/share/code/aotCodeCache.cpp | 12 ++++++------ src/hotspot/share/code/codeBlob.hpp | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/hotspot/share/code/aotCodeCache.cpp b/src/hotspot/share/code/aotCodeCache.cpp index c2838917516..d4f12936e96 100644 --- a/src/hotspot/share/code/aotCodeCache.cpp +++ b/src/hotspot/share/code/aotCodeCache.cpp @@ -2184,7 +2184,7 @@ void AOTCodeAddressTable::set_stubgen_stubs_complete() { #ifdef PRODUCT #define MAX_STR_COUNT 200 #else -#define MAX_STR_COUNT 500 +#define MAX_STR_COUNT 2000 #endif #define _c_str_max MAX_STR_COUNT static const int _c_str_base = _all_max; @@ -2296,7 +2296,7 @@ const char* AOTCodeAddressTable::add_C_string(const char* str) { int AOTCodeAddressTable::id_for_C_string(address str) { if (str == nullptr) { - return -1; + return BAD_ADDRESS_ID; } MutexLocker ml(AOTCodeCStrings_lock, Mutex::_no_safepoint_check_flag); for (int i = 0; i < _C_strings_count; i++) { @@ -2314,7 +2314,7 @@ int AOTCodeAddressTable::id_for_C_string(address str) { return id; } } - return -1; + return BAD_ADDRESS_ID; } address AOTCodeAddressTable::address_for_C_string(int idx) { @@ -2381,13 +2381,13 @@ int AOTCodeAddressTable::id_for_address(address addr, RelocIterator reloc, CodeB } // Seach for C string id = id_for_C_string(addr); - if (id >= 0) { + if (id != BAD_ADDRESS_ID) { return id + _c_str_base; } if (StubRoutines::contains(addr) || CodeCache::find_blob(addr) != nullptr) { // Search for a matching stub entry id = search_address(addr, _stubs_addr, _stubs_max); - if (id < 0) { + if (id == BAD_ADDRESS_ID) { StubCodeDesc* desc = StubCodeDesc::desc_for(addr); if (desc == nullptr) { desc = StubCodeDesc::desc_for(addr + frame::pc_return_offset); @@ -2400,7 +2400,7 @@ int AOTCodeAddressTable::id_for_address(address addr, RelocIterator reloc, CodeB } else { // Search in runtime functions id = search_address(addr, _extrs_addr, _extrs_length); - if (id < 0) { + if (id == BAD_ADDRESS_ID) { ResourceMark rm; const int buflen = 1024; char* func_name = NEW_RESOURCE_ARRAY(char, buflen); diff --git a/src/hotspot/share/code/codeBlob.hpp b/src/hotspot/share/code/codeBlob.hpp index d372e72fc23..709623de308 100644 --- a/src/hotspot/share/code/codeBlob.hpp +++ b/src/hotspot/share/code/codeBlob.hpp @@ -226,7 +226,7 @@ public: nmethod* as_nmethod() const { assert(is_nmethod(), "must be nmethod"); return (nmethod*) this; } CodeBlob* as_codeblob() const { return (CodeBlob*) this; } // we may want to force an actual buffer blob or subtype instance - BufferBlob* as_buffer_blob(bool strict = true) const { assert(is_buffer_blob(), "must be %sbuffer blob", (strict ? "strict " : "")); return (BufferBlob*) this; } + BufferBlob* as_buffer_blob(bool strict = true) const { assert(is_buffer_blob(strict), "must be %sbuffer blob", (strict ? "strict " : "")); return (BufferBlob*) this; } AdapterBlob* as_adapter_blob() const { assert(is_adapter_blob(), "must be adapter blob"); return (AdapterBlob*) this; } ExceptionBlob* as_exception_blob() const { assert(is_exception_stub(), "must be exception stub"); return (ExceptionBlob*) this; } // this will always return a subtype instance From 91cde7f160f46d04caa08781eed331875532bce1 Mon Sep 17 00:00:00 2001 From: Yasumasa Suenaga Date: Thu, 16 Apr 2026 14:42:01 +0000 Subject: [PATCH 078/108] 8382327: Disable some SA tests for mixed jstack on musl C Reviewed-by: mbaesken, kevinw --- test/hotspot/jtreg/serviceability/sa/ClhsdbPstack.java | 4 ++++ .../jtreg/serviceability/sa/TestJhsdbJstackMixed.java | 6 ++++++ .../serviceability/sa/TestJhsdbJstackMixedWithXComp.java | 9 ++++++++- .../serviceability/sa/TestJhsdbJstackPrintVMLocks.java | 5 +++++ 4 files changed, 23 insertions(+), 1 deletion(-) diff --git a/test/hotspot/jtreg/serviceability/sa/ClhsdbPstack.java b/test/hotspot/jtreg/serviceability/sa/ClhsdbPstack.java index 51738be3554..6921a7ec0ca 100644 --- a/test/hotspot/jtreg/serviceability/sa/ClhsdbPstack.java +++ b/test/hotspot/jtreg/serviceability/sa/ClhsdbPstack.java @@ -54,6 +54,10 @@ import jtreg.SkippedException; public class ClhsdbPstack { public static void main(String[] args) throws Exception { + if (Platform.isMusl()) { + throw new SkippedException("This test does not work on musl libc."); + } + boolean withCore = Boolean.parseBoolean(args[0]); System.out.println("Starting ClhsdbPstack test: withCore==" + withCore); diff --git a/test/hotspot/jtreg/serviceability/sa/TestJhsdbJstackMixed.java b/test/hotspot/jtreg/serviceability/sa/TestJhsdbJstackMixed.java index e3808aa6706..a5750edf360 100644 --- a/test/hotspot/jtreg/serviceability/sa/TestJhsdbJstackMixed.java +++ b/test/hotspot/jtreg/serviceability/sa/TestJhsdbJstackMixed.java @@ -33,6 +33,8 @@ import jdk.test.lib.Utils; import jdk.test.lib.apps.LingeredApp; import jdk.test.lib.process.OutputAnalyzer; +import jtreg.SkippedException; + /** * @test * @key randomness @@ -174,6 +176,10 @@ public class TestJhsdbJstackMixed { } public static void main(String... args) throws Exception { + if (Platform.isMusl()) { + throw new SkippedException("This test does not work on musl libc."); + } + SATestUtils.skipIfCannotAttach(); // throws SkippedException if attach not expected to work. LingeredApp app = null; diff --git a/test/hotspot/jtreg/serviceability/sa/TestJhsdbJstackMixedWithXComp.java b/test/hotspot/jtreg/serviceability/sa/TestJhsdbJstackMixedWithXComp.java index a3880bc9f0e..70a9e67e406 100644 --- a/test/hotspot/jtreg/serviceability/sa/TestJhsdbJstackMixedWithXComp.java +++ b/test/hotspot/jtreg/serviceability/sa/TestJhsdbJstackMixedWithXComp.java @@ -1,6 +1,6 @@ /* * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2025, NTT DATA + * Copyright (c) 2025, 2026, NTT DATA * 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,11 +27,14 @@ import java.util.Arrays; import java.util.List; import jdk.test.lib.JDKToolLauncher; +import jdk.test.lib.Platform; import jdk.test.lib.SA.SATestUtils; import jdk.test.lib.Utils; import jdk.test.lib.apps.LingeredApp; import jdk.test.lib.process.OutputAnalyzer; +import jtreg.SkippedException; + /** * @test id=xcomp * @bug 8370176 @@ -111,6 +114,10 @@ public class TestJhsdbJstackMixedWithXComp { } public static void main(String... args) throws Exception { + if (Platform.isMusl()) { + throw new SkippedException("This test does not work on musl libc."); + } + SATestUtils.skipIfCannotAttach(); // throws SkippedException if attach not expected to work. LingeredApp app = null; diff --git a/test/hotspot/jtreg/serviceability/sa/TestJhsdbJstackPrintVMLocks.java b/test/hotspot/jtreg/serviceability/sa/TestJhsdbJstackPrintVMLocks.java index f6580d71e05..e0774917f80 100644 --- a/test/hotspot/jtreg/serviceability/sa/TestJhsdbJstackPrintVMLocks.java +++ b/test/hotspot/jtreg/serviceability/sa/TestJhsdbJstackPrintVMLocks.java @@ -22,6 +22,7 @@ */ import jdk.test.lib.JDKToolLauncher; +import jdk.test.lib.Platform; import jdk.test.lib.SA.SATestUtils; import jdk.test.lib.apps.LingeredApp; import jdk.test.lib.process.OutputAnalyzer; @@ -43,6 +44,10 @@ public class TestJhsdbJstackPrintVMLocks { final static int MAX_ATTEMPTS = 5; public static void main(String[] args) throws Exception { + if (Platform.isMusl()) { + throw new SkippedException("This test does not work on musl libc."); + } + SATestUtils.skipIfCannotAttach(); // throws SkippedException if attach not expected to work. LingeredApp theApp = null; From 35ad47b4c09d0812f3f7c57ac7932c1533790fc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20Walln=C3=B6fer?= Date: Thu, 16 Apr 2026 14:42:20 +0000 Subject: [PATCH 079/108] 8373922: Enhance Taglet API to support relative URLs 8379520: Make use of new Taglet.toString method in JDK build taglets Reviewed-by: liach --- make/Docs.gmk | 9 +- .../src/classes/build/tools/taglet/JSpec.java | 26 +- .../build/tools/taglet/SealedGraph.java | 58 +++-- .../classes/build/tools/taglet/ToolGuide.java | 29 +-- .../jdk/javadoc/doclet/StandardDoclet.java | 4 +- .../classes/jdk/javadoc/doclet/Taglet.java | 66 ++++- .../formats/html/HtmlDocletWriter.java | 6 +- .../formats/html/taglets/UserTaglet.java | 14 +- .../TestRelativeURLTaglet.java | 229 ++++++++++++++++++ 9 files changed, 344 insertions(+), 97 deletions(-) create mode 100644 test/langtools/jdk/javadoc/doclet/testRelativeURLTaglet/TestRelativeURLTaglet.java diff --git a/make/Docs.gmk b/make/Docs.gmk index a8d40e078e2..9cee8cd40c1 100644 --- a/make/Docs.gmk +++ b/make/Docs.gmk @@ -1,4 +1,4 @@ -# 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 @@ -93,19 +93,16 @@ JAVADOC_DISABLED_DOCLINT_WARNINGS := missing JAVADOC_DISABLED_DOCLINT_PACKAGES := org.w3c.* javax.smartcardio # The initial set of options for javadoc -# -XDaccessInternalAPI is a temporary workaround, see 8373909 JAVADOC_OPTIONS := -use -keywords -notimestamp \ -serialwarn -encoding utf-8 -docencoding utf-8 -breakiterator \ -splitIndex --system none -javafx --expand-requires transitive \ - --override-methods=summary \ - -XDaccessInternalAPI + --override-methods=summary # The reference options must stay stable to allow for comparisons across the # development cycle. REFERENCE_OPTIONS := -XDignore.symbol.file=true -use -keywords -notimestamp \ -serialwarn -encoding utf-8 -breakiterator -splitIndex --system none \ - -html5 -javafx --expand-requires transitive \ - -XDaccessInternalAPI + -html5 -javafx --expand-requires transitive # Should we add DRAFT stamps to the generated javadoc? ifeq ($(VERSION_IS_GA), true) diff --git a/make/jdk/src/classes/build/tools/taglet/JSpec.java b/make/jdk/src/classes/build/tools/taglet/JSpec.java index 7e1e0ca215e..196aaccb32b 100644 --- a/make/jdk/src/classes/build/tools/taglet/JSpec.java +++ b/make/jdk/src/classes/build/tools/taglet/JSpec.java @@ -25,13 +25,13 @@ package build.tools.taglet; +import java.net.URI; import java.util.EnumSet; import java.util.List; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; -import java.lang.reflect.Field; import javax.lang.model.element.Element; @@ -141,6 +141,11 @@ public class JSpec implements Taglet { @Override public String toString(List tags, Element elem) { + throw new UnsupportedOperationException(); + } + + // @Override - requires JDK-8373922 in build JDK + public String toString(List tags, Element elem, URI docRoot) { if (tags.isEmpty()) return ""; @@ -177,7 +182,7 @@ public class JSpec implements Taglet { 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 rootParent = docRoot.resolve("..").toString(); String url = preview == null ? String.format("%1$s/specs/%2$s/%2$s-%3$s.html#%2$s-%3$s%4$s", @@ -230,23 +235,6 @@ public class JSpec implements Taglet { return sb.toString(); } - private static ThreadLocal CURRENT_PATH = null; - - private String currentPath() { - if (CURRENT_PATH == null) { - try { - Field f = Class.forName("jdk.javadoc.internal.doclets.formats.html.HtmlDocletWriter") - .getField("CURRENT_PATH"); - @SuppressWarnings("unchecked") - ThreadLocal tl = (ThreadLocal) f.get(null); - CURRENT_PATH = tl; - } catch (ReflectiveOperationException e) { - throw new RuntimeException("Cannot determine current path", e); - } - } - return CURRENT_PATH.get(); - } - private String expand(List trees) { return (new SimpleDocTreeVisitor() { public StringBuilder defaultAction(DocTree tree, StringBuilder sb) { diff --git a/make/jdk/src/classes/build/tools/taglet/SealedGraph.java b/make/jdk/src/classes/build/tools/taglet/SealedGraph.java index 2ffd92e3409..300999b77c0 100644 --- a/make/jdk/src/classes/build/tools/taglet/SealedGraph.java +++ b/make/jdk/src/classes/build/tools/taglet/SealedGraph.java @@ -32,7 +32,9 @@ import jdk.javadoc.doclet.Taglet; import javax.lang.model.element.*; import javax.lang.model.type.DeclaredType; +import javax.lang.model.util.Elements; import java.io.IOException; +import java.net.URI; import java.nio.file.Files; import java.nio.file.Path; import java.util.*; @@ -78,6 +80,11 @@ public final class SealedGraph implements Taglet { @Override public String toString(List tags, Element element) { + throw new UnsupportedOperationException(); + } + + // @Override - requires JDK-8373922 in build JDK + public String toString(List tags, Element element, URI docRoot) { if (sealedDotOutputDir == null || sealedDotOutputDir.isEmpty()) { return ""; } @@ -85,9 +92,15 @@ public final class SealedGraph implements Taglet { return ""; } - ModuleElement module = docletEnvironment.getElementUtils().getModuleOf(element); + Elements util = docletEnvironment.getElementUtils(); + ModuleElement module = util.getModuleOf(element); + + // '.' in .DOT file name is converted to '/' in .SVG path, so we use '-' as separator for nested classes. + // module_package.subpackage.Outer-Inner.dot => module/package/subpackage/Outer-Inner-sealed-graph.svg Path dotFile = Path.of(sealedDotOutputDir, - module.getQualifiedName() + "_" + typeElement.getQualifiedName() + ".dot"); + module.getQualifiedName() + "_" + + util.getPackageOf(element).getQualifiedName() + "." + + packagelessCanonicalName(typeElement).replace(".", "-") + ".dot"); Set exports = module.getDirectives().stream() .filter(ModuleElement.ExportsDirective.class::isInstance) @@ -99,7 +112,7 @@ public final class SealedGraph implements Taglet { .map(Objects::toString) .collect(Collectors.toUnmodifiableSet()); - String dotContent = new Renderer().graph(typeElement, exports); + String dotContent = new Renderer().graph(typeElement, exports, docRoot); try { Files.writeString(dotFile, dotContent, WRITE, CREATE, TRUNCATE_EXISTING); @@ -107,8 +120,8 @@ public final class SealedGraph implements Taglet { throw new RuntimeException(e); } - String simpleTypeName = packagelessCanonicalName(typeElement).replace('.', '/'); - String imageFile = simpleTypeName + "-sealed-graph.svg"; + String simpleTypeName = packagelessCanonicalName(typeElement); + String imageFile = simpleTypeName.replace(".", "-") + "-sealed-graph.svg"; int thumbnailHeight = 100; // also appears in the stylesheet String hoverImage = "" + getImage(simpleTypeName, imageFile, -1, true) @@ -137,12 +150,12 @@ public final class SealedGraph implements Taglet { private final class Renderer { // Generates a graph in DOT format - String graph(TypeElement rootClass, Set exports) { + String graph(TypeElement rootClass, Set exports, URI pathToRoot) { if (!isInPublicApi(rootClass, exports)) { // Alternatively we can return "" for the graph since there is no single root to render throw new IllegalArgumentException("Root not in public API: " + rootClass.getQualifiedName()); } - final State state = new State(rootClass); + final State state = new State(pathToRoot); traverse(state, rootClass, exports); return state.render(); } @@ -168,7 +181,7 @@ public final class SealedGraph implements Taglet { private static final String TOOLTIP = "tooltip"; private static final String LINK = "href"; - private final TypeElement rootNode; + private final URI pathToRoot; private final StringBuilder builder; @@ -193,8 +206,8 @@ public final class SealedGraph implements Taglet { } } - public State(TypeElement rootNode) { - this.rootNode = rootNode; + public State(URI pathToRoot) { + this.pathToRoot = pathToRoot; nodeStyleMap = new LinkedHashMap<>(); builder = new StringBuilder() .append("digraph G {") @@ -217,24 +230,15 @@ public final class SealedGraph implements Taglet { var styles = nodeStyleMap.computeIfAbsent(id(node), n -> new LinkedHashMap<>()); styles.put(LABEL, new StyleItem.PlainString(node.getSimpleName().toString())); styles.put(TOOLTIP, new StyleItem.PlainString(node.getQualifiedName().toString())); - styles.put(LINK, new StyleItem.PlainString(relativeLink(node))); + styles.put(LINK, new StyleItem.PlainString(pathToRoot.resolve(relativeLink(node)).toString())); } - // A permitted class must be in the same package or in the same module. - // This implies the module is always the same. private String relativeLink(TypeElement node) { var util = SealedGraph.this.docletEnvironment.getElementUtils(); - var nodePackage = util.getPackageOf(node); - // Note: SVG files for nested types use the simple names of containing types as parent directories. - // We therefore need to convert all dots in the qualified name to "../" below. - var backNavigator = rootNode.getQualifiedName().toString().chars() - .filter(c -> c == '.') - .mapToObj(c -> "../") - .collect(joining()); - var forwardNavigator = nodePackage.getQualifiedName().toString() - .replace(".", "/"); + var path = util.getModuleOf(node).getQualifiedName().toString() + "/" + + util.getPackageOf(node).getQualifiedName().toString().replace(".", "/"); - return backNavigator + forwardNavigator + "/" + packagelessCanonicalName(node) + ".html"; + return path + "/" + packagelessCanonicalName(node) + ".html"; } public void addEdge(TypeElement node, TypeElement subNode) { @@ -286,14 +290,6 @@ public final class SealedGraph implements Taglet { private String quotedId(TypeElement node) { return "\"" + id(node) + "\""; } - - private String simpleName(String name) { - int lastDot = name.lastIndexOf('.'); - return lastDot < 0 - ? name - : name.substring(lastDot); - } - } private static List permittedSubclasses(TypeElement node, Set exports) { diff --git a/make/jdk/src/classes/build/tools/taglet/ToolGuide.java b/make/jdk/src/classes/build/tools/taglet/ToolGuide.java index 8db2aee3092..7ad4f6b9b9f 100644 --- a/make/jdk/src/classes/build/tools/taglet/ToolGuide.java +++ b/make/jdk/src/classes/build/tools/taglet/ToolGuide.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 @@ -25,13 +25,13 @@ package build.tools.taglet; +import java.net.URI; import java.util.EnumSet; import java.util.List; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; -import java.lang.reflect.Field; import javax.lang.model.element.Element; @@ -91,6 +91,11 @@ public class ToolGuide implements Taglet { @Override public String toString(List tags, Element elem) { + throw new UnsupportedOperationException(); + } + + // @Override - requires JDK-8373922 in build JDK + public String toString(List tags, Element elem, URI docRoot) { if (tags.isEmpty()) return ""; @@ -118,7 +123,7 @@ public class ToolGuide implements Taglet { if (label.isEmpty()) { label = name; } - String rootParent = currentPath().replaceAll("[^/]+", ".."); + String rootParent = docRoot.resolve("..").toString(); String url = String.format("%s/%s/%s.html", rootParent, BASE_URL, name); @@ -141,22 +146,4 @@ public class ToolGuide implements Taglet { return sb.toString(); } - - private static ThreadLocal CURRENT_PATH = null; - - private String currentPath() { - if (CURRENT_PATH == null) { - try { - Field f = Class.forName("jdk.javadoc.internal.doclets.formats.html.HtmlDocletWriter") - .getField("CURRENT_PATH"); - @SuppressWarnings("unchecked") - ThreadLocal tl = (ThreadLocal) f.get(null); - CURRENT_PATH = tl; - } catch (ReflectiveOperationException e) { - throw new RuntimeException("Cannot determine current path", e); - } - } - return CURRENT_PATH.get(); - } - } diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/doclet/StandardDoclet.java b/src/jdk.javadoc/share/classes/jdk/javadoc/doclet/StandardDoclet.java index 8f5fec1e4b9..90511db8053 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/doclet/StandardDoclet.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/doclet/StandardDoclet.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2024, 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 @@ -48,7 +48,7 @@ import jdk.javadoc.internal.doclets.formats.html.HtmlDoclet; * in documentation comments. * * Taglets invoked by the standard doclet must return strings from - * {@link Taglet#toString(List,Element) Taglet.toString} as follows: + * {@link Taglet#toString(List,Element,java.net.URI) Taglet.toString} as follows: * *
*
Inline Tags diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/doclet/Taglet.java b/src/jdk.javadoc/share/classes/jdk/javadoc/doclet/Taglet.java index 1ad67a89fef..ec079047dfa 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/doclet/Taglet.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/doclet/Taglet.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2024, 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,6 +25,7 @@ package jdk.javadoc.doclet; +import java.net.URI; import java.util.List; import java.util.Set; @@ -34,7 +35,7 @@ import com.sun.source.doctree.DocTree; /** * The interface for a custom taglet supported by doclets such as - * the {@link jdk.javadoc.doclet.StandardDoclet standard doclet}. + * the {@linkplain StandardDoclet standard doclet}. * Custom taglets are used to handle custom tags in documentation * comments; custom tags can be instantiated individually as either * block tags, which appear at the end of a comment, @@ -55,10 +56,10 @@ import com.sun.source.doctree.DocTree; * {@link #isInlineTag() isInlineTag}, to determine the characteristics * of the tags supported by the taglet. *
  • As appropriate, the doclet calls the - * {@link #toString(List,Element) toString} method on the taglet object, - * giving it a list of tags and the element for which the tags are part - * of the element's documentation comment, from which the taglet can - * determine the string to be included in the documentation. + * {@link #toString(List,Element,URI) toString} method on the taglet object, + * giving it a list of tags, the element whose documentation comment contains + * the tags, and the root URI of the generated output, from which the taglet + * can determine the string to be included in the documentation. * The doclet will typically specify any requirements on the contents of * the string that is returned. * @@ -126,25 +127,70 @@ public interface Taglet { default void init(DocletEnvironment env, Doclet doclet) { } /** - * Returns the string representation of a series of instances of - * this tag to be included in the generated output. + * Returns the string representation of the specified instances of this tag + * to be included in the generated output. * - *

    If this taglet supports {@link #isInlineTag inline} tags, it will + *

    If this taglet supports {@link #isInlineTag inline} tags, this method will * be called once per instance of the inline tag, each time with a singleton list. * If this taglet supports {@link #isBlockTag block} tags, it will be called once * for each comment containing instances of block tags, with a list of all the instances * of the block tag in that comment. * + * @apiNote Taglets that do not need the root URI of the generated output may + * implement this method only. Taglets that require the root URI to link to other + * doclet-generated resources should override {@link #toString(List, Element, URI)}, + * and optionally throw an exception in the implementation of this method. + * * @param tags the list of instances of this tag * @param element the element to which the enclosing comment belongs * @return the string representation of the tags to be included in * the generated output - * + * @throws UnsupportedOperationException if {@link #toString(List, Element, URI)} + * should be invoked instead * @see User-Defined Taglets * for the Standard Doclet + * @see #toString(List, Element, URI) */ String toString(List tags, Element element); + /** + * Returns the string representation of the specified instances of this tag + * to be included in the generated output. + * + *

    If this taglet supports {@link #isInlineTag inline} tags, this method will + * be called once per instance of the inline tag, each time with a singleton list. + * If this taglet supports {@link #isBlockTag block} tags, it will be called once + * for each comment containing instances of block tags, with a list of all the instances + * of the block tag in that comment. + * + *

    The {@code docRoot} argument identifies the root of the generated output + * as seen by the current resource, and may be used to {@linkplain URI#resolve(String) + * resolve} links to other resources generated by the doclet. + * + * @apiNote The exact form of {@code docRoot} is doclet-specific. For the + * {@linkplain StandardDoclet standard doclet}, it is a relative URI from + * the current resource to the root directory of the generated output. + * Taglets intended for use with other doclets should check the validity + * of the {@code docRoot} argument as appropriate. + * + * @implSpec The default implementation invokes {@link #toString(List, Element) + * toString(tags, element)}. + * + * @param tags the list of instances of this tag + * @param element the element to which the enclosing comment belongs + * @param docRoot the root URI of the generated output + * @return the string representation of the tags to be included in + * the generated output + * @throws IllegalArgumentException if {@code docRoot} is not a valid URI + * @see User-Defined Taglets + * for the Standard Doclet + * @see #toString(List, Element) + * @since 27 + */ + default String toString(List tags, Element element, URI docRoot) { + return toString(tags, element); + } + /** * The kind of location in which a tag may be used. */ diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlDocletWriter.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlDocletWriter.java index 86ac3a892fd..594c36c4af2 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlDocletWriter.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/HtmlDocletWriter.java @@ -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 @@ -243,12 +243,8 @@ public abstract class HtmlDocletWriter { if (generating) { writeGenerating(); } - CURRENT_PATH.set(path.getPath()); } - /** Temporary workaround to share current path with taglets, see 8373909 */ - public static final ThreadLocal CURRENT_PATH = new ThreadLocal<>(); - /** * The top-level method to generate and write the page represented by this writer. * diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/UserTaglet.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/UserTaglet.java index d1884c53ccf..8d54aa0585b 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/UserTaglet.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/formats/html/taglets/UserTaglet.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2024, 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,6 +25,7 @@ package jdk.javadoc.internal.doclets.formats.html.taglets; +import java.net.URI; import java.util.List; import java.util.Set; @@ -70,7 +71,8 @@ public final class UserTaglet implements Taglet { @Override public Content getInlineTagOutput(Element element, DocTree tag, TagletWriter tagletWriter) { Content output = tagletWriter.getOutputInstance(); - output.add(RawHtml.of(userTaglet.toString(List.of(tag), element))); + URI pathToRoot = getPathToRoot(tagletWriter); + output.add(RawHtml.of(userTaglet.toString(List.of(tag), element, pathToRoot))); return output; } @@ -80,11 +82,17 @@ public final class UserTaglet implements Taglet { var utils = tagletWriter.utils; List tags = utils.getBlockTags(holder, getName()); if (!tags.isEmpty()) { - String tagString = userTaglet.toString(tags, holder); + URI pathToRoot = getPathToRoot(tagletWriter); + String tagString = userTaglet.toString(tags, holder, pathToRoot); if (tagString != null) { output.add(RawHtml.of(tagString)); } } return output; } + + private URI getPathToRoot(TagletWriter tagletWriter) { + var path = tagletWriter.htmlWriter.pathToRoot.getPath(); + return URI.create(path.isEmpty() || path.endsWith("/") ? path : path + "/"); + } } diff --git a/test/langtools/jdk/javadoc/doclet/testRelativeURLTaglet/TestRelativeURLTaglet.java b/test/langtools/jdk/javadoc/doclet/testRelativeURLTaglet/TestRelativeURLTaglet.java new file mode 100644 index 00000000000..72b1de22cd2 --- /dev/null +++ b/test/langtools/jdk/javadoc/doclet/testRelativeURLTaglet/TestRelativeURLTaglet.java @@ -0,0 +1,229 @@ +/* + * 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. + */ + +/* + * @test + * @bug 8373922 + * @summary Enhance Taglet API to support relative URLs + * @library /tools/lib ../../lib + * @modules jdk.javadoc/jdk.javadoc.internal.tool + * jdk.compiler/com.sun.tools.javac.api + * jdk.compiler/com.sun.tools.javac.main + * @build toolbox.ToolBox javadoc.tester.* + * @run main TestRelativeURLTaglet + */ + +import java.net.URI; +import java.nio.file.Path; +import java.util.EnumSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import javax.lang.model.element.Element; + +import com.sun.source.doctree.DocTree; +import com.sun.source.doctree.UnknownBlockTagTree; +import com.sun.source.doctree.UnknownInlineTagTree; +import javadoc.tester.JavadocTester; +import jdk.javadoc.doclet.Taglet; +import toolbox.ModuleBuilder; +import toolbox.ToolBox; + +public class TestRelativeURLTaglet extends JavadocTester implements Taglet { + + public static void main(String... args) throws Exception { + new TestRelativeURLTaglet().runTests(); + } + + ToolBox tb = new ToolBox(); + + @Test + public void testRelativePackageURL(Path base) throws Exception { + Path src = base.resolve("src"); + tb.writeJavaFiles(src, """ + package p.q; + /** + * First {@test sentence}. + */ + public interface A {} + """, + """ + package p.q.r; + /** + * Comment {@test with} inline {@test tags}. + * + * @test 123 + * @test 456 + */ + public class C {} + """); + tb.writeFile(src.resolve("p/q/doc-files/test.html"), """ + + + HTML {@test file} with {@test inline} tags. + + + """); + + javadoc("-d", base.resolve("out").toString(), + "--source-path", src.toString(), + "-tagletpath", System.getProperty("test.class.path"), + "-taglet", "TestRelativeURLTaglet", + "-subpackages", "p"); + checkExit(Exit.OK); + + checkOutput("p/q/A.html", true, + "First ../../sentence."); + checkOutput("p/q/package-summary.html", true, + "First ../../sentence."); + checkOutput("p/q/doc-files/test.html", true, + "HTML ../../../file with ../../../inline tags."); + checkOutput("p/q/r/C.html", true, + "Comment ../../../with inline ../../../tags.", + "../../../123 ../../../456"); + checkOutput("p/q/r/package-summary.html", true, + "Comment ../../../with inline ../../../tags."); + checkOutput("index-all.html", true, + "First sentence.", + "Comment with inline tags."); + checkOutput("allclasses-index.html", true, + "First sentence.", + "Comment with inline tags."); + } + + @Test + public void testRelativeModuleURL(Path base) throws Exception { + Path src = base.resolve("src"); + new ModuleBuilder(tb, "ma") + .comment("Module {@test ma}.") + .classes( + """ + /** + * Package {@test ma/p/a}. + */ + package p.a; + """, + """ + package p.a; + /** + * Class in {@test ma/p/a}. + * + * @test block1 + * @test block2 + */ + public class A {} + """) + .exports("p.a") + .write(src); + + javadoc("-d", base.resolve("out").toString(), + "--module-source-path", src.toString(), + "-tagletpath", System.getProperty("test.class.path"), + "-taglet", "TestRelativeURLTaglet", + "--module", "ma"); + checkExit(Exit.OK); + + checkOutput("ma/p/a/package-summary.html", true, + "Package ../../../ma/p/a.", + "Class in ../../../ma/p/a."); + checkOutput("ma/p/a/A.html", true, + "Class in ../../../ma/p/a.", + "

    ../../../block1 ../../../block2
    "); + checkOutput("ma/module-summary.html", true, + "Module ../ma.", + "Package ../ma/p/a."); + checkOutput("index-all.html", true, + "Class in ma/p/a.", + "Module ma.", + "Package ma/p/a."); + } + + @Test + public void testUnnamedPackageURL(Path base) throws Exception { + Path src = base.resolve("src"); + tb.writeJavaFiles(src, """ + /** + * Class in unnamed package: {@test tag}. + * + * @test first + * @test second + */ + public class C {} + """); + + javadoc("-d", base.resolve("out").toString(), + "--source-path", src.toString(), + "-tagletpath", System.getProperty("test.class.path"), + "-taglet", "TestRelativeURLTaglet", + src.resolve("C.java").toString()); + checkExit(Exit.OK); + + checkOutput("C.html", true, + "Class in unnamed package: tag.", + "
    first second
    "); + checkOutput("index-all.html", true, + "Class in unnamed package: tag."); + checkOutput("allclasses-index.html", true, + "Class in unnamed package: tag."); + } + + // Taglet implementation + + @Override + public Set getAllowedLocations() { + return EnumSet.allOf(Location.class); + } + + @Override + public boolean isInlineTag() { + return true; + } + + @Override + public boolean isBlockTag() { + return true; + } + + @Override + public String getName() { + return "test"; + } + + @Override + public String toString(List tags, Element element, URI docRoot) { + if (tags.size() == 1 && tags.getFirst() instanceof UnknownInlineTagTree uit) { + return docRoot.resolve(uit.getContent().toString()).toString(); + } else { + return tags.stream() + .map(t -> docRoot.resolve(((UnknownBlockTagTree) t).getContent().toString()).toString()) + .collect(Collectors.joining(" ")); + } + } + + @Override + public String toString(List tags, Element element) { + throw new UnsupportedOperationException(); + } +} + From 8aafaf07e90264f8fef8b0a09cce50e5371c6103 Mon Sep 17 00:00:00 2001 From: Leonid Mesnik Date: Thu, 16 Apr 2026 15:08:49 +0000 Subject: [PATCH 080/108] 8356223: ProblemList-AotJdk.txt should be added automatically if AOT mode is tested Reviewed-by: erikj, epavlova --- make/RunTests.gmk | 3 +++ 1 file changed, 3 insertions(+) diff --git a/make/RunTests.gmk b/make/RunTests.gmk index 02ea632e3ec..d4be5936c41 100644 --- a/make/RunTests.gmk +++ b/make/RunTests.gmk @@ -1020,6 +1020,9 @@ define SetupRunJtregTestBody VM_OPTIONS := $$(JTREG_ALL_OPTIONS) )) $$(call LogWarn, AOT_JDK_CACHE=$$($1_AOT_JDK_CACHE)) $1_JTREG_BASIC_OPTIONS += -vmoption:-XX:AOTCache="$$($1_AOT_JDK_CACHE)" + $1_JTREG_BASIC_OPTIONS += $$(addprefix $$(JTREG_PROBLEM_LIST_PREFIX), $$(wildcard \ + $$(addprefix $$($1_TEST_ROOT)/, ProblemList-AotJdk.txt) \ + )) endif From 0ca24c0e57b0dc40c98533ecbb37a9378bb7aa13 Mon Sep 17 00:00:00 2001 From: Chris Hegarty Date: Thu, 16 Apr 2026 16:19:02 +0000 Subject: [PATCH 081/108] 8382267: VectorMathLibrary uses locale-sensitive String.format to construct native symbol names Reviewed-by: naoto, liach, vlivanov --- .../incubator/vector/VectorMathLibrary.java | 5 +- .../vector/VectorMathLibraryLocaleTest.java | 53 +++++++++++++++++++ 2 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 test/jdk/jdk/incubator/vector/VectorMathLibraryLocaleTest.java diff --git a/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/VectorMathLibrary.java b/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/VectorMathLibrary.java index 823cebd85a9..59697733d86 100644 --- a/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/VectorMathLibrary.java +++ b/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/VectorMathLibrary.java @@ -31,6 +31,7 @@ import jdk.internal.vm.vector.VectorSupport; import java.lang.foreign.MemorySegment; import java.lang.foreign.SymbolLookup; +import java.util.Locale; import java.util.function.IntFunction; import static jdk.incubator.vector.Util.requires; @@ -141,7 +142,7 @@ import static jdk.internal.vm.vector.Utils.debug; String elemType = (vspecies.elementType() == float.class ? "f" : ""); boolean isFloatVector64 = (vspecies.elementType() == float.class) && (vspecies.length() == 2); // FloatVector64 or FloatVectorMax int vlen = (isFloatVector64 ? 4 : vspecies.length()); // reuse 128-bit variant for 64-bit float vectors - return String.format("__jsvml_%s%s%d_ha_%s", op.operatorName(), elemType, vlen, suffix); + return String.format(Locale.ROOT, "__jsvml_%s%s%d_ha_%s", op.operatorName(), elemType, vlen, suffix); } @Override @@ -214,7 +215,7 @@ import static jdk.internal.vm.vector.Utils.debug; boolean isFloatVector64 = (vspecies.elementType() == float.class) && (vspecies.length() == 2); // FloatVector64 or FloatVectorMax int vlen = (isFloatVector64 ? 4 : vspecies.length()); // reuse 128-bit variant for 64-bit float vectors boolean isShapeAgnostic = isRISCV64() || (isAARCH64() && vspecies.vectorBitSize() > 128); - return String.format("%s%s%s_%s%s", op.operatorName(), + return String.format(Locale.ROOT, "%s%s%s_%s%s", op.operatorName(), (vspecies.elementType() == float.class ? "f" : "d"), (isShapeAgnostic ? "x" : Integer.toString(vlen)), precisionLevel(op), diff --git a/test/jdk/jdk/incubator/vector/VectorMathLibraryLocaleTest.java b/test/jdk/jdk/incubator/vector/VectorMathLibraryLocaleTest.java new file mode 100644 index 00000000000..c093df397e5 --- /dev/null +++ b/test/jdk/jdk/incubator/vector/VectorMathLibraryLocaleTest.java @@ -0,0 +1,53 @@ +/* + * 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. + */ + +/* + * @test + * @bug 8382267 + * @summary VectorMathLibrary symbol names must not use locale-sensitive formatting + * @modules jdk.incubator.vector + * @run main/othervm -ea -Duser.language=ar -Duser.country=SA VectorMathLibraryLocaleTest + */ + +import java.util.Locale; + +import jdk.incubator.vector.FloatVector; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +public class VectorMathLibraryLocaleTest { + public static void main(String[] args) { + assert !String.format("%d", 16).equals("16") : "expected non-ASCII digits for locale " + Locale.getDefault(); + + VectorSpecies species = FloatVector.SPECIES_PREFERRED; + FloatVector v = FloatVector.broadcast(species, 1.0f); + + // Without the fix, this throws InternalError due to locale-mangled symbol name. + // The assertion below is just a sanity check on the result. + FloatVector result = v.lanewise(VectorOperators.EXP); + float expected = (float) Math.E; + for (int i = 0; i < species.length(); i++) { + assert result.lane(i) == expected : "lane " + i + ": expected " + expected + ", got " + result.lane(i); + } + } +} From 74c3d426b05ab0a0b2b7d3d6ee3ce9d5f4c977e1 Mon Sep 17 00:00:00 2001 From: Patrick Fontanilla Date: Thu, 16 Apr 2026 17:48:31 +0000 Subject: [PATCH 082/108] 8382103: Shenandoah: Optimize ShenandoahLock size for release build Reviewed-by: xpeng, kdnilsen, wkemper --- src/hotspot/share/gc/shenandoah/shenandoahLock.hpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahLock.hpp b/src/hotspot/share/gc/shenandoah/shenandoahLock.hpp index 7c91df191e5..5041419b2c7 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahLock.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahLock.hpp @@ -38,15 +38,19 @@ private: shenandoah_padding(0); Atomic _state; shenandoah_padding(1); +#ifdef ASSERT Atomic _owner; shenandoah_padding(2); +#endif template void contended_lock_internal(JavaThread* java_thread); static void yield_or_sleep(int &yields); public: - ShenandoahLock() : _state(unlocked), _owner(nullptr) {}; + ShenandoahLock() : _state(unlocked) { + DEBUG_ONLY(_owner.store_relaxed(nullptr);) + }; void lock(bool allow_block_for_safepoint = false) { assert(_owner.load_relaxed() != Thread::current(), "reentrant locking attempt, would deadlock"); From d6a255d74ce2b3cb197343208946fdaed11ca08e Mon Sep 17 00:00:00 2001 From: Patrick Fontanilla Date: Thu, 16 Apr 2026 17:49:05 +0000 Subject: [PATCH 083/108] 8381933: Possible memory leak in src/hotspot/share/gc/shenandoah/shenandoahHeapRegionCounters.cpp Reviewed-by: kdnilsen, wkemper --- src/hotspot/share/gc/shenandoah/shenandoahHeapRegionCounters.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegionCounters.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegionCounters.cpp index ff1e3368e87..aaf152e2890 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegionCounters.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegionCounters.cpp @@ -78,6 +78,7 @@ ShenandoahHeapRegionCounters::ShenandoahHeapRegionCounters() : ShenandoahHeapRegionCounters::~ShenandoahHeapRegionCounters() { if (_name_space != nullptr) FREE_C_HEAP_ARRAY(char, _name_space); + if (_regions_data != nullptr) FREE_C_HEAP_ARRAY(PerfVariable*, _regions_data); } void ShenandoahHeapRegionCounters::write_snapshot(PerfLongVariable** regions, From 70d0651090c52d32349bf8efe0f577870503785f Mon Sep 17 00:00:00 2001 From: Shiv Shah Date: Thu, 16 Apr 2026 17:57:01 +0000 Subject: [PATCH 084/108] 8316439: Several jvmti tests fail with -Xcheck:jni Reviewed-by: lmesnik, dholmes, cjplummer, epavlova, sspitsyn --- .../PopFrame/popframe007/popframe007.cpp | 7 ++++-- .../retransform003/retransform003.cpp | 10 ++++++-- .../jtreg/vmTestbase/nsk/share/aod/aod.cpp | 24 ++++++++++++++++++- 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/PopFrame/popframe007/popframe007.cpp b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/PopFrame/popframe007/popframe007.cpp index a2e9716a513..4b929fd4d03 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/PopFrame/popframe007/popframe007.cpp +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/PopFrame/popframe007/popframe007.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2024, 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 @@ -39,6 +39,7 @@ static jvmtiEventCallbacks callbacks; static jint result = PASSED; static jboolean printdump = JNI_FALSE; static jmethodID mid; +static jclass testThreadClass = nullptr; void JNICALL Breakpoint(jvmtiEnv *jvmti_env, JNIEnv *env, jthread thread, jmethodID method, jlocation location) { @@ -166,6 +167,8 @@ Java_nsk_jvmti_PopFrame_popframe007_getReady(JNIEnv *env, return; } + testThreadClass = (jclass)env->NewGlobalRef(clazz); + err = jvmti->SetBreakpoint(mid, 0); if (err != JVMTI_ERROR_NONE) { printf("(SetBreakpoint) unexpected error: %s (%d)\n", @@ -191,7 +194,7 @@ Java_nsk_jvmti_PopFrame_popframe007_getRes(JNIEnv *env, jclass cls) { JNIEXPORT void JNICALL Java_nsk_jvmti_PopFrame_popframe007_B(JNIEnv *env, jclass cls) { if (mid != nullptr) { - env->CallStaticVoidMethod(cls, mid); + env->CallStaticVoidMethod(testThreadClass, mid); } } diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/RetransformClasses/retransform003/retransform003.cpp b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/RetransformClasses/retransform003/retransform003.cpp index cda8481533d..f7ad0b65ff3 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jvmti/RetransformClasses/retransform003/retransform003.cpp +++ b/test/hotspot/jtreg/vmTestbase/nsk/jvmti/RetransformClasses/retransform003/retransform003.cpp @@ -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 @@ -113,6 +113,12 @@ ClassFileLoadHook ( loader, method_id, class_name_string)) != nullptr)) return; + if (jni->ExceptionCheck()) { + jni->ExceptionDescribe(); + jni->ExceptionClear(); + return; + } + if (!NSK_VERIFY((method_id = jni->GetStaticMethodID( callback_class, "callback", "(Ljava/lang/String;I)V")) != nullptr)) return; @@ -120,7 +126,7 @@ ClassFileLoadHook ( if (!NSK_VERIFY((class_name_string = jni->NewStringUTF(name)) != nullptr)) return; - jni->CallStaticObjectMethod(callback_class, method_id, class_name_string, agent_id); + NSK_JNI_VERIFY_VOID(jni, jni->CallStaticVoidMethod(callback_class, method_id, class_name_string, agent_id)); } diff --git a/test/hotspot/jtreg/vmTestbase/nsk/share/aod/aod.cpp b/test/hotspot/jtreg/vmTestbase/nsk/share/aod/aod.cpp index 7f622c24f75..7f82caa7259 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/share/aod/aod.cpp +++ b/test/hotspot/jtreg/vmTestbase/nsk/share/aod/aod.cpp @@ -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 @@ -31,6 +31,20 @@ extern "C" { static volatile int internalError = 0; +/* + * Check for pending JNI exceptions after a JNI call. + * If an exception is found, describe it, clear it, and return failure. + */ +static int nsk_aod_checkJNIException(JNIEnv* jni) { + if (jni->ExceptionCheck()) { + jni->ExceptionDescribe(); + jni->ExceptionClear(); + NSK_COMPLAIN0("Unexpected exception after JNI call\n"); + return NSK_FALSE; + } + return NSK_TRUE; +} + /* * This function can be used to inform AOD framework that some non critical for test logic * error happened inside shared function (e.g. JVMTI Deallocate failed). @@ -227,6 +241,10 @@ int nsk_aod_agentLoaded(JNIEnv* jni, const char* agentName) { jni->CallStaticVoidMethod(targetAppClass, agentLoadedMethod, agentNameString); + if (!nsk_aod_checkJNIException(jni)) { + return NSK_FALSE; + } + return NSK_TRUE; } @@ -263,6 +281,10 @@ int nsk_aod_agentFinished(JNIEnv* jni, const char* agentName, int success) { jni->CallStaticVoidMethod(targetAppClass, agentFinishedMethod, agentNameString, success ? JNI_TRUE : JNI_FALSE); + if (!nsk_aod_checkJNIException(jni)) { + return NSK_FALSE; + } + return NSK_TRUE; } From 2c30f120800cd8ad2a54ef17780ed57dd3c336ad Mon Sep 17 00:00:00 2001 From: Matias Saavedra Silva Date: Thu, 16 Apr 2026 18:16:10 +0000 Subject: [PATCH 085/108] 8347248: Rename arg_count to arg_size Reviewed-by: dlong, iklam --- src/hotspot/share/ci/ciMethodData.cpp | 12 ++++++------ src/hotspot/share/oops/methodData.cpp | 4 ++-- src/hotspot/share/oops/methodData.hpp | 2 +- src/hotspot/share/oops/methodData.inline.hpp | 4 ++-- src/hotspot/share/prims/jni.cpp | 3 +-- src/hotspot/share/prims/whitebox.cpp | 4 ++-- 6 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/hotspot/share/ci/ciMethodData.cpp b/src/hotspot/share/ci/ciMethodData.cpp index 533e8659968..5e623e2b965 100644 --- a/src/hotspot/share/ci/ciMethodData.cpp +++ b/src/hotspot/share/ci/ciMethodData.cpp @@ -537,8 +537,8 @@ void ciMethodData::clear_escape_info() { if (mdo != nullptr) { mdo->clear_escape_info(); ArgInfoData *aid = arg_info(); - int arg_count = (aid == nullptr) ? 0 : aid->number_of_args(); - for (int i = 0; i < arg_count; i++) { + int arg_size = (aid == nullptr) ? 0 : aid->size_of_args(); + for (int i = 0; i < arg_size; i++) { set_arg_modified(i, 0); } } @@ -554,8 +554,8 @@ void ciMethodData::update_escape_info() { mdo->set_arg_local(_arg_local); mdo->set_arg_stack(_arg_stack); mdo->set_arg_returned(_arg_returned); - int arg_count = mdo->method()->size_of_parameters(); - for (int i = 0; i < arg_count; i++) { + int arg_size = mdo->method()->size_of_parameters(); + for (int i = 0; i < arg_size; i++) { mdo->set_arg_modified(i, arg_modified(i)); } } @@ -652,7 +652,7 @@ void ciMethodData::set_arg_modified(int arg, uint val) { ArgInfoData *aid = arg_info(); if (aid == nullptr) return; - assert(arg >= 0 && arg < aid->number_of_args(), "valid argument number"); + assert(arg >= 0 && arg < aid->size_of_args(), "valid argument number"); aid->set_arg_modified(arg, val); } @@ -672,7 +672,7 @@ uint ciMethodData::arg_modified(int arg) const { ArgInfoData *aid = arg_info(); if (aid == nullptr) return 0; - assert(arg >= 0 && arg < aid->number_of_args(), "valid argument number"); + assert(arg >= 0 && arg < aid->size_of_args(), "valid argument number"); return aid->arg_modified(arg); } diff --git a/src/hotspot/share/oops/methodData.cpp b/src/hotspot/share/oops/methodData.cpp index ad1049ffa34..dc0b8fa9f81 100644 --- a/src/hotspot/share/oops/methodData.cpp +++ b/src/hotspot/share/oops/methodData.cpp @@ -667,8 +667,8 @@ void MultiBranchData::print_data_on(outputStream* st, const char* extra) const { void ArgInfoData::print_data_on(outputStream* st, const char* extra) const { print_shared(st, "ArgInfoData", extra); - int nargs = number_of_args(); - for (int i = 0; i < nargs; i++) { + int args_size = size_of_args(); + for (int i = 0; i < args_size; i++) { st->print(" 0x%x", arg_modified(i)); } st->cr(); diff --git a/src/hotspot/share/oops/methodData.hpp b/src/hotspot/share/oops/methodData.hpp index 196537359b5..45529618afb 100644 --- a/src/hotspot/share/oops/methodData.hpp +++ b/src/hotspot/share/oops/methodData.hpp @@ -1751,7 +1751,7 @@ public: virtual bool is_ArgInfoData() const { return true; } - int number_of_args() const { + int size_of_args() const { return array_len(); } diff --git a/src/hotspot/share/oops/methodData.inline.hpp b/src/hotspot/share/oops/methodData.inline.hpp index dee14d49253..b417ba867fc 100644 --- a/src/hotspot/share/oops/methodData.inline.hpp +++ b/src/hotspot/share/oops/methodData.inline.hpp @@ -59,7 +59,7 @@ inline uint MethodData::arg_modified(int a) { MutexLocker ml(extra_data_lock(), Mutex::_no_safepoint_check_flag); ArgInfoData* aid = arg_info(); assert(aid != nullptr, "arg_info must be not null"); - assert(a >= 0 && a < aid->number_of_args(), "valid argument number"); + assert(a >= 0 && a < aid->size_of_args(), "valid argument number"); return aid->arg_modified(a); } @@ -68,7 +68,7 @@ inline void MethodData::set_arg_modified(int a, uint v) { MutexLocker ml(extra_data_lock(), Mutex::_no_safepoint_check_flag); ArgInfoData* aid = arg_info(); assert(aid != nullptr, "arg_info must be not null"); - assert(a >= 0 && a < aid->number_of_args(), "valid argument number"); + assert(a >= 0 && a < aid->size_of_args(), "valid argument number"); aid->set_arg_modified(a, v); } diff --git a/src/hotspot/share/prims/jni.cpp b/src/hotspot/share/prims/jni.cpp index 85207fddf29..73082c6047d 100644 --- a/src/hotspot/share/prims/jni.cpp +++ b/src/hotspot/share/prims/jni.cpp @@ -867,8 +867,7 @@ static void jni_invoke_static(JNIEnv *env, JavaValue* result, jobject receiver, // Create object to hold arguments for the JavaCall, and associate it with // the jni parser ResourceMark rm(THREAD); - int number_of_parameters = method->size_of_parameters(); - JavaCallArguments java_args(number_of_parameters); + JavaCallArguments java_args(method->size_of_parameters()); assert(method->is_static(), "method should be static"); diff --git a/src/hotspot/share/prims/whitebox.cpp b/src/hotspot/share/prims/whitebox.cpp index f50e6ddb3db..de140fb95ff 100644 --- a/src/hotspot/share/prims/whitebox.cpp +++ b/src/hotspot/share/prims/whitebox.cpp @@ -1280,8 +1280,8 @@ WB_ENTRY(void, WB_ClearMethodState(JNIEnv* env, jobject o, jobject method)) if (mdo != nullptr) { mdo->init(); ResourceMark rm(THREAD); - int arg_count = mdo->method()->size_of_parameters(); - for (int i = 0; i < arg_count; i++) { + int arg_size = mdo->method()->size_of_parameters(); + for (int i = 0; i < arg_size; i++) { mdo->set_arg_modified(i, 0); } mdo->clean_method_data(/*always_clean*/true); From 17ee366ff964ea4e50b419a033bab973c46f601c Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Thu, 16 Apr 2026 18:20:29 +0000 Subject: [PATCH 086/108] 8382150: Shenandoah: Clean up nmethod entry barrier support Reviewed-by: xpeng, kdnilsen, wkemper --- .../gc/shenandoah/shenandoahClosures.hpp | 3 -- .../shenandoah/shenandoahClosures.inline.hpp | 6 +-- .../gc/shenandoah/shenandoahCodeRoots.cpp | 37 ++----------------- .../gc/shenandoah/shenandoahCodeRoots.hpp | 5 +-- .../gc/shenandoah/shenandoahConcurrentGC.cpp | 17 +++------ .../gc/shenandoah/shenandoahRootProcessor.cpp | 5 +-- .../shenandoahRootProcessor.inline.hpp | 6 +-- .../share/gc/shenandoah/shenandoahSTWMark.cpp | 2 +- 8 files changed, 16 insertions(+), 65 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahClosures.hpp b/src/hotspot/share/gc/shenandoah/shenandoahClosures.hpp index fefed0340c4..9ab45380c61 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahClosures.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahClosures.hpp @@ -180,9 +180,6 @@ public: }; class ShenandoahNMethodAndDisarmClosure : public NMethodToOopClosure { -private: - BarrierSetNMethod* const _bs; - public: inline ShenandoahNMethodAndDisarmClosure(OopClosure* cl); inline void do_nmethod(nmethod* nm); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahClosures.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahClosures.inline.hpp index e8d25b1e5a9..96ecbad1145 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahClosures.inline.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahClosures.inline.hpp @@ -209,15 +209,13 @@ void ShenandoahCleanUpdateWeakOopsClosure::do_oo } ShenandoahNMethodAndDisarmClosure::ShenandoahNMethodAndDisarmClosure(OopClosure* cl) : - NMethodToOopClosure(cl, true /* fix_relocations */), - _bs(BarrierSet::barrier_set()->barrier_set_nmethod()) { -} + NMethodToOopClosure(cl, true /* fix_relocations */) {} void ShenandoahNMethodAndDisarmClosure::do_nmethod(nmethod* nm) { assert(nm != nullptr, "Sanity"); assert(!ShenandoahNMethod::gc_data(nm)->is_unregistered(), "Should not be here"); NMethodToOopClosure::do_nmethod(nm); - _bs->disarm(nm); + ShenandoahNMethod::disarm_nmethod(nm); } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahCodeRoots.cpp b/src/hotspot/share/gc/shenandoah/shenandoahCodeRoots.cpp index 64e135e9a4e..7cf60cdf65c 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahCodeRoots.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahCodeRoots.cpp @@ -40,20 +40,6 @@ ShenandoahNMethodTable* ShenandoahCodeRoots::_nmethod_table; int ShenandoahCodeRoots::_disarmed_value = 1; -bool ShenandoahCodeRoots::use_nmethod_barriers_for_mark() { - // Continuations need nmethod barriers for scanning stack chunk nmethods. - if (Continuations::enabled()) return true; - - // Concurrent class unloading needs nmethod barriers. - // When a nmethod is about to be executed, we need to make sure that all its - // metadata are marked. The alternative is to remark thread roots at final mark - // pause, which would cause latency issues. - if (ShenandoahHeap::heap()->unload_classes()) return true; - - // Otherwise, we can go without nmethod barriers. - return false; -} - void ShenandoahCodeRoots::initialize() { _nmethod_table = new ShenandoahNMethodTable(); } @@ -68,27 +54,14 @@ void ShenandoahCodeRoots::unregister_nmethod(nmethod* nm) { _nmethod_table->unregister_nmethod(nm); } -void ShenandoahCodeRoots::arm_nmethods_for_mark() { - if (use_nmethod_barriers_for_mark()) { - BarrierSet::barrier_set()->barrier_set_nmethod()->arm_all_nmethods(); - } -} - -void ShenandoahCodeRoots::arm_nmethods_for_evac() { +void ShenandoahCodeRoots::arm_nmethods() { BarrierSet::barrier_set()->barrier_set_nmethod()->arm_all_nmethods(); } class ShenandoahDisarmNMethodClosure : public NMethodClosure { -private: - BarrierSetNMethod* const _bs; - public: - ShenandoahDisarmNMethodClosure() : - _bs(BarrierSet::barrier_set()->barrier_set_nmethod()) { - } - virtual void do_nmethod(nmethod* nm) { - _bs->disarm(nm); + ShenandoahNMethod::disarm_nmethod(nm); } }; @@ -111,10 +84,8 @@ public: }; void ShenandoahCodeRoots::disarm_nmethods() { - if (use_nmethod_barriers_for_mark()) { - ShenandoahDisarmNMethodsTask task; - ShenandoahHeap::heap()->workers()->run_task(&task); - } + ShenandoahDisarmNMethodsTask task; + ShenandoahHeap::heap()->workers()->run_task(&task); } class ShenandoahNMethodUnlinkClosure : public NMethodClosure { diff --git a/src/hotspot/share/gc/shenandoah/shenandoahCodeRoots.hpp b/src/hotspot/share/gc/shenandoah/shenandoahCodeRoots.hpp index d29c446f210..d395b4516f4 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahCodeRoots.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahCodeRoots.hpp @@ -67,14 +67,11 @@ public: // Concurrent nmethod unloading support static void unlink(WorkerThreads* workers, bool unloading_occurred); static void purge(); - static void arm_nmethods_for_mark(); - static void arm_nmethods_for_evac(); + static void arm_nmethods(); static void disarm_nmethods(); static int disarmed_value() { return _disarmed_value; } static int* disarmed_value_address() { return &_disarmed_value; } - static bool use_nmethod_barriers_for_mark(); - private: static ShenandoahNMethodTable* _nmethod_table; static int _disarmed_value; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp index ba0aa1a1304..af4cec03221 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp @@ -744,9 +744,8 @@ void ShenandoahConcurrentGC::op_init_mark() { // Make above changes visible to worker threads OrderAccess::fence(); - // Arm nmethods for concurrent mark - ShenandoahCodeRoots::arm_nmethods_for_mark(); - + // Arm nmethods/stack for concurrent processing + ShenandoahCodeRoots::arm_nmethods(); ShenandoahStackWatermark::change_epoch_id(); { @@ -805,7 +804,7 @@ void ShenandoahConcurrentGC::op_final_mark() { heap->set_has_forwarded_objects(true); // Arm nmethods/stack for concurrent processing - ShenandoahCodeRoots::arm_nmethods_for_evac(); + ShenandoahCodeRoots::arm_nmethods(); ShenandoahStackWatermark::change_epoch_id(); } else { @@ -1035,14 +1034,10 @@ void ShenandoahConcurrentGC::op_class_unloading() { class ShenandoahEvacUpdateCodeCacheClosure : public NMethodClosure { private: - BarrierSetNMethod* const _bs; ShenandoahEvacuateUpdateMetadataClosure _cl; public: - ShenandoahEvacUpdateCodeCacheClosure() : - _bs(BarrierSet::barrier_set()->barrier_set_nmethod()), - _cl() { - } + ShenandoahEvacUpdateCodeCacheClosure() : _cl() {} void do_nmethod(nmethod* n) { ShenandoahNMethod* data = ShenandoahNMethod::gc_data(n); @@ -1050,8 +1045,8 @@ public: // Setup EvacOOM scope below reentrant lock to avoid deadlock with // nmethod_entry_barrier ShenandoahEvacOOMScope oom; - data->oops_do(&_cl, true/*fix relocation*/); - _bs->disarm(n); + data->oops_do(&_cl, /* fix_relocations = */ true); + ShenandoahNMethod::disarm_nmethod(n); } }; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahRootProcessor.cpp b/src/hotspot/share/gc/shenandoah/shenandoahRootProcessor.cpp index 7b68e6ac625..80825ac43ad 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahRootProcessor.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahRootProcessor.cpp @@ -200,9 +200,6 @@ ShenandoahRootAdjuster::ShenandoahRootAdjuster(uint n_workers, ShenandoahPhaseTi void ShenandoahRootAdjuster::roots_do(uint worker_id, OopClosure* oops) { NMethodToOopClosure code_blob_cl(oops, NMethodToOopClosure::FixRelocations); ShenandoahNMethodAndDisarmClosure nmethods_and_disarm_Cl(oops); - NMethodToOopClosure* adjust_code_closure = ShenandoahCodeRoots::use_nmethod_barriers_for_mark() ? - static_cast(&nmethods_and_disarm_Cl) : - static_cast(&code_blob_cl); CLDToOopClosure adjust_cld_closure(oops, ClassLoaderData::_claim_strong); // Process light-weight/limited parallel roots then @@ -211,7 +208,7 @@ void ShenandoahRootAdjuster::roots_do(uint worker_id, OopClosure* oops) { _cld_roots.cld_do(&adjust_cld_closure, worker_id); // Process heavy-weight/fully parallel roots the last - _code_roots.nmethods_do(adjust_code_closure, worker_id); + _code_roots.nmethods_do(&nmethods_and_disarm_Cl, worker_id); _thread_roots.oops_do(oops, nullptr, worker_id); } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahRootProcessor.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahRootProcessor.inline.hpp index 6aebec28163..4504ac96819 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahRootProcessor.inline.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahRootProcessor.inline.hpp @@ -172,10 +172,6 @@ template void ShenandoahRootUpdater::roots_do(uint worker_id, IsAlive* is_alive, KeepAlive* keep_alive) { NMethodToOopClosure update_nmethods(keep_alive, NMethodToOopClosure::FixRelocations); ShenandoahNMethodAndDisarmClosure nmethods_and_disarm_Cl(keep_alive); - NMethodToOopClosure* codes_cl = ShenandoahCodeRoots::use_nmethod_barriers_for_mark() ? - static_cast(&nmethods_and_disarm_Cl) : - static_cast(&update_nmethods); - CLDToOopClosure clds(keep_alive, ClassLoaderData::_claim_strong); // Process light-weight/limited parallel roots then @@ -184,7 +180,7 @@ void ShenandoahRootUpdater::roots_do(uint worker_id, IsAlive* is_alive, KeepAliv _cld_roots.cld_do(&clds, worker_id); // Process heavy-weight/fully parallel roots the last - _code_roots.nmethods_do(codes_cl, worker_id); + _code_roots.nmethods_do(&nmethods_and_disarm_Cl, worker_id); _thread_roots.oops_do(keep_alive, nullptr, worker_id); } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahSTWMark.cpp b/src/hotspot/share/gc/shenandoah/shenandoahSTWMark.cpp index 117984a6d41..5b4ce6d0bc9 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahSTWMark.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahSTWMark.cpp @@ -74,7 +74,7 @@ void ShenandoahSTWMark::mark() { // Arm all nmethods. Even though this is STW mark, some marking code // piggybacks on nmethod barriers for special instances. - ShenandoahCodeRoots::arm_nmethods_for_mark(); + ShenandoahCodeRoots::arm_nmethods(); // Weak reference processing ShenandoahReferenceProcessor* rp = _generation->ref_processor(); From 10e23dd5823258cfbf500175dde23be12cdd99bf Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Thu, 16 Apr 2026 18:25:35 +0000 Subject: [PATCH 087/108] 8382257: Shenandoah: Clean up stack watermark barrier support Reviewed-by: kdnilsen, wkemper --- .../shenandoah/mode/shenandoahPassiveMode.cpp | 1 - .../gc/shenandoah/mode/shenandoahSATBMode.cpp | 1 - .../gc/shenandoah/shenandoahBarrierSet.cpp | 22 ++++++++----------- .../gc/shenandoah/shenandoah_globals.hpp | 3 --- .../options/TestSelectiveBarrierFlags.java | 3 +-- .../options/TestWrongBarrierDisable.java | 3 +-- .../options/TestWrongBarrierEnable.java | 4 +--- 7 files changed, 12 insertions(+), 25 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/mode/shenandoahPassiveMode.cpp b/src/hotspot/share/gc/shenandoah/mode/shenandoahPassiveMode.cpp index 41b2703730b..cc098bc5a21 100644 --- a/src/hotspot/share/gc/shenandoah/mode/shenandoahPassiveMode.cpp +++ b/src/hotspot/share/gc/shenandoah/mode/shenandoahPassiveMode.cpp @@ -50,7 +50,6 @@ void ShenandoahPassiveMode::initialize_flags() const { SHENANDOAH_ERGO_DISABLE_FLAG(ShenandoahSATBBarrier); SHENANDOAH_ERGO_DISABLE_FLAG(ShenandoahCASBarrier); SHENANDOAH_ERGO_DISABLE_FLAG(ShenandoahCloneBarrier); - SHENANDOAH_ERGO_DISABLE_FLAG(ShenandoahStackWatermarkBarrier); SHENANDOAH_ERGO_DISABLE_FLAG(ShenandoahCardBarrier); } diff --git a/src/hotspot/share/gc/shenandoah/mode/shenandoahSATBMode.cpp b/src/hotspot/share/gc/shenandoah/mode/shenandoahSATBMode.cpp index 7ac2d7b818f..e27aa90542d 100644 --- a/src/hotspot/share/gc/shenandoah/mode/shenandoahSATBMode.cpp +++ b/src/hotspot/share/gc/shenandoah/mode/shenandoahSATBMode.cpp @@ -42,6 +42,5 @@ void ShenandoahSATBMode::initialize_flags() const { SHENANDOAH_CHECK_FLAG_SET(ShenandoahSATBBarrier); SHENANDOAH_CHECK_FLAG_SET(ShenandoahCASBarrier); SHENANDOAH_CHECK_FLAG_SET(ShenandoahCloneBarrier); - SHENANDOAH_CHECK_FLAG_SET(ShenandoahStackWatermarkBarrier); SHENANDOAH_CHECK_FLAG_UNSET(ShenandoahCardBarrier); } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.cpp b/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.cpp index cb6ff795c07..0949959b042 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.cpp @@ -149,11 +149,9 @@ void ShenandoahBarrierSet::on_thread_attach(Thread *thread) { BarrierSetNMethod* bs_nm = barrier_set_nmethod(); thread->set_nmethod_disarmed_guard_value(bs_nm->disarmed_guard_value()); - if (ShenandoahStackWatermarkBarrier) { - JavaThread* const jt = JavaThread::cast(thread); - StackWatermark* const watermark = new ShenandoahStackWatermark(jt); - StackWatermarkSet::add_watermark(jt, watermark); - } + JavaThread* const jt = JavaThread::cast(thread); + StackWatermark* const watermark = new ShenandoahStackWatermark(jt); + StackWatermarkSet::add_watermark(jt, watermark); } } @@ -172,14 +170,12 @@ void ShenandoahBarrierSet::on_thread_detach(Thread *thread) { } // SATB protocol requires to keep alive reachable oops from roots at the beginning of GC - if (ShenandoahStackWatermarkBarrier) { - if (_heap->is_concurrent_mark_in_progress()) { - ShenandoahKeepAliveClosure oops; - StackWatermarkSet::finish_processing(JavaThread::cast(thread), &oops, StackWatermarkKind::gc); - } else if (_heap->is_concurrent_weak_root_in_progress() && _heap->is_evacuation_in_progress()) { - ShenandoahContextEvacuateUpdateRootsClosure oops; - StackWatermarkSet::finish_processing(JavaThread::cast(thread), &oops, StackWatermarkKind::gc); - } + if (_heap->is_concurrent_mark_in_progress()) { + ShenandoahKeepAliveClosure oops; + StackWatermarkSet::finish_processing(JavaThread::cast(thread), &oops, StackWatermarkKind::gc); + } else if (_heap->is_concurrent_weak_root_in_progress() && _heap->is_evacuation_in_progress()) { + ShenandoahContextEvacuateUpdateRootsClosure oops; + StackWatermarkSet::finish_processing(JavaThread::cast(thread), &oops, StackWatermarkKind::gc); } } } diff --git a/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp b/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp index bbfa19934d9..2c5ba726ef2 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp @@ -560,9 +560,6 @@ product(bool, ShenandoahLoadRefBarrier, true, DIAGNOSTIC, \ "Turn on/off load-reference barriers in Shenandoah") \ \ - product(bool, ShenandoahStackWatermarkBarrier, true, DIAGNOSTIC, \ - "Turn on/off stack watermark barriers in Shenandoah") \ - \ develop(bool, ShenandoahVerifyOptoBarriers, trueInDebug, \ "Verify no missing barriers in C2.") \ \ diff --git a/test/hotspot/jtreg/gc/shenandoah/options/TestSelectiveBarrierFlags.java b/test/hotspot/jtreg/gc/shenandoah/options/TestSelectiveBarrierFlags.java index fe29a38c422..da66bb8bbe0 100644 --- a/test/hotspot/jtreg/gc/shenandoah/options/TestSelectiveBarrierFlags.java +++ b/test/hotspot/jtreg/gc/shenandoah/options/TestSelectiveBarrierFlags.java @@ -52,8 +52,7 @@ public class TestSelectiveBarrierFlags { new String[] { "ShenandoahLoadRefBarrier" }, new String[] { "ShenandoahSATBBarrier" }, new String[] { "ShenandoahCASBarrier" }, - new String[] { "ShenandoahCloneBarrier" }, - new String[] { "ShenandoahStackWatermarkBarrier" } + new String[] { "ShenandoahCloneBarrier" } }; int size = 1; diff --git a/test/hotspot/jtreg/gc/shenandoah/options/TestWrongBarrierDisable.java b/test/hotspot/jtreg/gc/shenandoah/options/TestWrongBarrierDisable.java index f461bb4ae5c..b5c30f37202 100644 --- a/test/hotspot/jtreg/gc/shenandoah/options/TestWrongBarrierDisable.java +++ b/test/hotspot/jtreg/gc/shenandoah/options/TestWrongBarrierDisable.java @@ -41,8 +41,7 @@ public class TestWrongBarrierDisable { "ShenandoahLoadRefBarrier", "ShenandoahSATBBarrier", "ShenandoahCASBarrier", - "ShenandoahCloneBarrier", - "ShenandoahStackWatermarkBarrier", + "ShenandoahCloneBarrier" }; String[] generational = { diff --git a/test/hotspot/jtreg/gc/shenandoah/options/TestWrongBarrierEnable.java b/test/hotspot/jtreg/gc/shenandoah/options/TestWrongBarrierEnable.java index 348aa5367e9..1bc382dfbb0 100644 --- a/test/hotspot/jtreg/gc/shenandoah/options/TestWrongBarrierEnable.java +++ b/test/hotspot/jtreg/gc/shenandoah/options/TestWrongBarrierEnable.java @@ -41,8 +41,7 @@ public class TestWrongBarrierEnable { "ShenandoahLoadRefBarrier", "ShenandoahSATBBarrier", "ShenandoahCASBarrier", - "ShenandoahCloneBarrier", - "ShenandoahStackWatermarkBarrier", + "ShenandoahCloneBarrier" }; String[] generational = { "ShenandoahCardBarrier" }; String[] all = { @@ -50,7 +49,6 @@ public class TestWrongBarrierEnable { "ShenandoahSATBBarrier", "ShenandoahCASBarrier", "ShenandoahCloneBarrier", - "ShenandoahStackWatermarkBarrier", "ShenandoahCardBarrier" }; From 115075014272d12333d0955b42b11fee6727d514 Mon Sep 17 00:00:00 2001 From: Fei Yang Date: Fri, 17 Apr 2026 00:34:54 +0000 Subject: [PATCH 088/108] 8382199: Remove unused parameter "dst" from CardTableBarrierSetAssembler::store_check() Reviewed-by: chagedorn, jsikstro --- .../gc/shared/cardTableBarrierSetAssembler_aarch64.cpp | 8 ++++---- .../gc/shared/cardTableBarrierSetAssembler_aarch64.hpp | 4 ++-- .../x86/gc/shared/cardTableBarrierSetAssembler_x86.cpp | 8 ++++---- .../x86/gc/shared/cardTableBarrierSetAssembler_x86.hpp | 4 ++-- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/hotspot/cpu/aarch64/gc/shared/cardTableBarrierSetAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/gc/shared/cardTableBarrierSetAssembler_aarch64.cpp index a9c320912cb..7ce4e0f8aed 100644 --- a/src/hotspot/cpu/aarch64/gc/shared/cardTableBarrierSetAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/gc/shared/cardTableBarrierSetAssembler_aarch64.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 @@ -56,7 +56,7 @@ void CardTableBarrierSetAssembler::store_at(MacroAssembler* masm, DecoratorSet d } } -void CardTableBarrierSetAssembler::store_check(MacroAssembler* masm, Register obj, Address dst, Register tmp1, Register tmp2) { +void CardTableBarrierSetAssembler::store_check(MacroAssembler* masm, Register obj, Register tmp1, Register tmp2) { precond(tmp1 != noreg); precond(tmp2 != noreg); assert_different_registers(obj, tmp1, tmp2); @@ -114,10 +114,10 @@ void CardTableBarrierSetAssembler::oop_store_at(MacroAssembler* masm, DecoratorS if (needs_post_barrier) { // flatten object address if needed if (!precise || (dst.index() == noreg && dst.offset() == 0)) { - store_check(masm, dst.base(), dst, tmp1, tmp2); + store_check(masm, dst.base(), tmp1, tmp2); } else { __ lea(tmp3, dst); - store_check(masm, tmp3, dst, tmp1, tmp2); + store_check(masm, tmp3, tmp1, tmp2); } } } diff --git a/src/hotspot/cpu/aarch64/gc/shared/cardTableBarrierSetAssembler_aarch64.hpp b/src/hotspot/cpu/aarch64/gc/shared/cardTableBarrierSetAssembler_aarch64.hpp index e05e9e90e5d..07016381f78 100644 --- a/src/hotspot/cpu/aarch64/gc/shared/cardTableBarrierSetAssembler_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/gc/shared/cardTableBarrierSetAssembler_aarch64.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 @@ -46,7 +46,7 @@ protected: virtual void store_at(MacroAssembler* masm, DecoratorSet decorators, BasicType type, Address dst, Register val, Register tmp1, Register tmp2, Register tmp3); - void store_check(MacroAssembler* masm, Register obj, Address dst, Register tmp1, Register tmp2); + void store_check(MacroAssembler* masm, Register obj, Register tmp1, Register tmp2); }; #endif // CPU_AARCH64_GC_SHARED_CARDTABLEBARRIERSETASSEMBLER_AARCH64_HPP diff --git a/src/hotspot/cpu/x86/gc/shared/cardTableBarrierSetAssembler_x86.cpp b/src/hotspot/cpu/x86/gc/shared/cardTableBarrierSetAssembler_x86.cpp index 1de147926bb..c05f37a3bea 100644 --- a/src/hotspot/cpu/x86/gc/shared/cardTableBarrierSetAssembler_x86.cpp +++ b/src/hotspot/cpu/x86/gc/shared/cardTableBarrierSetAssembler_x86.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 @@ -130,7 +130,7 @@ __ BIND(L_loop); __ BIND(L_done); } -void CardTableBarrierSetAssembler::store_check(MacroAssembler* masm, Register obj, Address dst, Register rscratch) { +void CardTableBarrierSetAssembler::store_check(MacroAssembler* masm, Register obj, Register rscratch) { // Does a store check for the oop in register obj. The content of // register obj is destroyed afterwards. CardTableBarrierSet* ctbs = CardTableBarrierSet::barrier_set(); @@ -192,10 +192,10 @@ void CardTableBarrierSetAssembler::oop_store_at(MacroAssembler* masm, DecoratorS if (needs_post_barrier) { // flatten object address if needed if (!precise || (dst.index() == noreg && dst.disp() == 0)) { - store_check(masm, dst.base(), dst, tmp2); + store_check(masm, dst.base(), tmp2); } else { __ lea(tmp1, dst); - store_check(masm, tmp1, dst, tmp2); + store_check(masm, tmp1, tmp2); } } } diff --git a/src/hotspot/cpu/x86/gc/shared/cardTableBarrierSetAssembler_x86.hpp b/src/hotspot/cpu/x86/gc/shared/cardTableBarrierSetAssembler_x86.hpp index 201c11062f2..c38e16d4d5f 100644 --- a/src/hotspot/cpu/x86/gc/shared/cardTableBarrierSetAssembler_x86.hpp +++ b/src/hotspot/cpu/x86/gc/shared/cardTableBarrierSetAssembler_x86.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 @@ -33,7 +33,7 @@ protected: virtual void gen_write_ref_array_pre_barrier(MacroAssembler* masm, DecoratorSet decorators, Register addr, Register count) {} - void store_check(MacroAssembler* masm, Register obj, Address dst, Register rscratch); + void store_check(MacroAssembler* masm, Register obj, Register rscratch); virtual void gen_write_ref_array_post_barrier(MacroAssembler* masm, DecoratorSet decorators, Register addr, Register count, Register tmp); From 93aa6462f031f36a71a929175e97e5b745ef94aa Mon Sep 17 00:00:00 2001 From: Jatin Bhateja Date: Fri, 17 Apr 2026 04:34:44 +0000 Subject: [PATCH 089/108] 8367761: [VectorAPI] AssertionError with several vector API mathlib tests with MaxVectorSize=32 Reviewed-by: xgong, sviswanathan, vlivanov --- .../share/classes/jdk/incubator/vector/CPUFeatures.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/CPUFeatures.java b/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/CPUFeatures.java index 0ad7ba1651d..05b5f6f69f4 100644 --- a/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/CPUFeatures.java +++ b/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/CPUFeatures.java @@ -74,9 +74,6 @@ import static jdk.internal.vm.vector.Utils.debug; debug("AVX=%b; AVX2=%b; AVX512F=%b; AVX512DQ=%b", SUPPORTS_AVX, SUPPORTS_AVX2, SUPPORTS_AVX512F, SUPPORTS_AVX512DQ); - assert SUPPORTS_AVX512F == (VectorShape.getMaxVectorBitSize(int.class) == 512); - assert SUPPORTS_AVX2 == (VectorShape.getMaxVectorBitSize(byte.class) >= 256); - assert SUPPORTS_AVX == (VectorShape.getMaxVectorBitSize(float.class) >= 256); } } From cbb85a5237a6fbc6ffe6f9e28d27b5df4a35060e Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Fri, 17 Apr 2026 05:47:55 +0000 Subject: [PATCH 090/108] 8382211: Shenandoah: Clean up and wire up final verification Reviewed-by: kdnilsen, wkemper --- .../gc/shenandoah/shenandoahConcurrentGC.cpp | 34 ++++++++++--------- .../gc/shenandoah/shenandoahConcurrentGC.hpp | 10 +++--- .../gc/shenandoah/shenandoahPhaseTimings.hpp | 4 +-- .../share/gc/shenandoah/shenandoahUtils.hpp | 2 +- .../gc/shenandoah/shenandoahVMOperations.cpp | 8 ++--- .../gc/shenandoah/shenandoahVMOperations.hpp | 10 +++--- .../gc/shenandoah/shenandoahVerifier.cpp | 19 ++++++++++- .../gc/shenandoah/shenandoahVerifier.hpp | 1 + src/hotspot/share/runtime/vmOperation.hpp | 2 +- 9 files changed, 55 insertions(+), 35 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp index af4cec03221..6723bb89021 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp @@ -255,8 +255,10 @@ bool ShenandoahConcurrentGC::collect(GCCause::Cause cause) { return false; } - if (VerifyAfterGC) { - vmop_entry_verify_final_roots(); + // In normal cycle, final-update-refs would verify at the end of the cycle. + // In abbreviated cycle, we need to verify separately. + if (ShenandoahVerify) { + vmop_entry_final_verify(); } } @@ -344,14 +346,14 @@ void ShenandoahConcurrentGC::vmop_entry_final_update_refs() { VMThread::execute(&op); } -void ShenandoahConcurrentGC::vmop_entry_verify_final_roots() { +void ShenandoahConcurrentGC::vmop_entry_final_verify() { ShenandoahHeap* const heap = ShenandoahHeap::heap(); TraceCollectorStats tcs(heap->monitoring_support()->stw_collection_counters()); - ShenandoahTimingsTracker timing(ShenandoahPhaseTimings::final_roots_gross); + ShenandoahTimingsTracker timing(ShenandoahPhaseTimings::final_verify_gross); // This phase does not use workers, no need for setup heap->try_inject_alloc_failure(); - VM_ShenandoahFinalRoots op(this); + VM_ShenandoahFinalVerify op(this); VMThread::execute(&op); } @@ -400,12 +402,12 @@ void ShenandoahConcurrentGC::entry_final_update_refs() { op_final_update_refs(); } -void ShenandoahConcurrentGC::entry_verify_final_roots() { - const char* msg = verify_final_roots_event_message(); - ShenandoahPausePhase gc_phase(msg, ShenandoahPhaseTimings::final_roots); +void ShenandoahConcurrentGC::entry_final_verify() { + const char* msg = verify_final_event_message(); + ShenandoahPausePhase gc_phase(msg, ShenandoahPhaseTimings::final_verify); EventMark em("%s", msg); - op_verify_final_roots(); + op_verify_final(); } void ShenandoahConcurrentGC::entry_reset() { @@ -1263,10 +1265,10 @@ bool ShenandoahConcurrentGC::entry_final_roots() { return true; } -void ShenandoahConcurrentGC::op_verify_final_roots() { - if (VerifyAfterGC) { - Universe::verify(); - } +void ShenandoahConcurrentGC::op_verify_final() { + assert(ShenandoahVerify, "Should have been checked before"); + ShenandoahHeap* const heap = ShenandoahHeap::heap(); + heap->verifier()->verify_after_gc(_generation); } void ShenandoahConcurrentGC::op_cleanup_complete() { @@ -1351,11 +1353,11 @@ const char* ShenandoahConcurrentGC::conc_reset_after_collect_event_message() con } } -const char* ShenandoahConcurrentGC::verify_final_roots_event_message() const { +const char* ShenandoahConcurrentGC::verify_final_event_message() const { if (ShenandoahHeap::heap()->unload_classes()) { - SHENANDOAH_RETURN_EVENT_MESSAGE(_generation->type(), "Pause Verify Final Roots", " (unload classes)"); + SHENANDOAH_RETURN_EVENT_MESSAGE(_generation->type(), "Pause Verify Final", " (unload classes)"); } else { - SHENANDOAH_RETURN_EVENT_MESSAGE(_generation->type(), "Pause Verify Final Roots", ""); + SHENANDOAH_RETURN_EVENT_MESSAGE(_generation->type(), "Pause Verify Final", ""); } } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.hpp b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.hpp index ba228901d72..fde585b4aa9 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.hpp @@ -43,7 +43,7 @@ class ShenandoahConcurrentGC : public ShenandoahGC { friend class VM_ShenandoahFinalMarkStartEvac; friend class VM_ShenandoahInitUpdateRefs; friend class VM_ShenandoahFinalUpdateRefs; - friend class VM_ShenandoahFinalRoots; + friend class VM_ShenandoahFinalVerify; protected: ShenandoahConcurrentMark _mark; @@ -69,7 +69,7 @@ protected: void vmop_entry_final_mark(); void vmop_entry_init_update_refs(); void vmop_entry_final_update_refs(); - void vmop_entry_verify_final_roots(); + void vmop_entry_final_verify(); // Entry methods to normally STW GC operations. These set up logging, monitoring // and workers for next VM operation @@ -77,7 +77,7 @@ protected: void entry_final_mark(); void entry_init_update_refs(); void entry_final_update_refs(); - void entry_verify_final_roots(); + void entry_final_verify(); // Entry methods to normally concurrent GC operations. These set up logging, monitoring // for concurrent operation. @@ -122,7 +122,7 @@ protected: void op_update_thread_roots(); void op_final_update_refs(); - void op_verify_final_roots(); + void op_verify_final(); void op_cleanup_complete(); void op_reset_after_collect(); @@ -143,7 +143,7 @@ private: // passing around the logging/tracing systems const char* init_mark_event_message() const; const char* final_mark_event_message() const; - const char* verify_final_roots_event_message() const; + const char* verify_final_event_message() const; const char* conc_final_roots_event_message() const; const char* conc_mark_event_message() const; const char* conc_reset_event_message() const; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahPhaseTimings.hpp b/src/hotspot/share/gc/shenandoah/shenandoahPhaseTimings.hpp index a454de68f00..6a316e2265a 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahPhaseTimings.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahPhaseTimings.hpp @@ -112,8 +112,8 @@ class outputStream; f(conc_update_card_table, "Concurrent Update Cards") \ f(conc_final_roots, "Concurrent Final Roots") \ f(promote_in_place, " Promote Regions") \ - f(final_roots_gross, "Pause Verify Final Roots (G)") \ - f(final_roots, "Pause Verify Final Roots (N)") \ + f(final_verify_gross, "Pause Final Verify (G)") \ + f(final_verify, "Pause Final Verify (N)") \ \ f(init_update_refs_gross, "Pause Init Update Refs (G)") \ f(init_update_refs, "Pause Init Update Refs (N)") \ diff --git a/src/hotspot/share/gc/shenandoah/shenandoahUtils.hpp b/src/hotspot/share/gc/shenandoah/shenandoahUtils.hpp index 6ef4cd7c702..0169941c3d9 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahUtils.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahUtils.hpp @@ -187,7 +187,7 @@ public: type == VM_Operation::VMOp_ShenandoahFinalMarkStartEvac || type == VM_Operation::VMOp_ShenandoahInitUpdateRefs || type == VM_Operation::VMOp_ShenandoahFinalUpdateRefs || - type == VM_Operation::VMOp_ShenandoahFinalRoots || + type == VM_Operation::VMOp_ShenandoahFinalVerify || type == VM_Operation::VMOp_ShenandoahFullGC || type == VM_Operation::VMOp_ShenandoahDegeneratedGC; } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahVMOperations.cpp b/src/hotspot/share/gc/shenandoah/shenandoahVMOperations.cpp index 6b45842f781..97dd7e5cda1 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahVMOperations.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahVMOperations.cpp @@ -135,12 +135,12 @@ void VM_ShenandoahFinalUpdateRefs::doit() { _gc->entry_final_update_refs(); } -VM_ShenandoahFinalRoots::VM_ShenandoahFinalRoots(ShenandoahConcurrentGC* gc) +VM_ShenandoahFinalVerify::VM_ShenandoahFinalVerify(ShenandoahConcurrentGC* gc) : VM_ShenandoahOperation(gc->generation()), _gc(gc) { } -void VM_ShenandoahFinalRoots::doit() { - ShenandoahGCPauseMark mark(_gc_id, "Final Roots", SvcGCMarker::CONCURRENT); +void VM_ShenandoahFinalVerify::doit() { + ShenandoahGCPauseMark mark(_gc_id, "Final Verify", SvcGCMarker::CONCURRENT); set_active_generation(); - _gc->entry_verify_final_roots(); + _gc->entry_final_verify(); } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahVMOperations.hpp b/src/hotspot/share/gc/shenandoah/shenandoahVMOperations.hpp index d565a3df22c..f8b99c71b14 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahVMOperations.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahVMOperations.hpp @@ -38,7 +38,7 @@ class ShenandoahFullGC; // - VM_ShenandoahFinalMarkStartEvac: finish up concurrent marking, and start evacuation // - VM_ShenandoahInitUpdateRefs: initiate update references // - VM_ShenandoahFinalUpdateRefs: finish up update references -// - VM_ShenandoahFinalRoots: finish up roots on a non-evacuating cycle +// - VM_ShenandoahFinalVerify: final verification at the end of the cycle // - VM_ShenandoahReferenceOperation: // - VM_ShenandoahFullGC: do full GC // - VM_ShenandoahDegeneratedGC: do STW degenerated GC @@ -127,12 +127,12 @@ public: void doit() override; }; -class VM_ShenandoahFinalRoots: public VM_ShenandoahOperation { +class VM_ShenandoahFinalVerify: public VM_ShenandoahOperation { ShenandoahConcurrentGC* const _gc; public: - explicit VM_ShenandoahFinalRoots(ShenandoahConcurrentGC* gc); - VM_Operation::VMOp_Type type() const override { return VMOp_ShenandoahFinalRoots; } - const char* name() const override { return "Shenandoah Final Roots"; } + explicit VM_ShenandoahFinalVerify(ShenandoahConcurrentGC* gc); + VM_Operation::VMOp_Type type() const override { return VMOp_ShenandoahFinalVerify; } + const char* name() const override { return "Shenandoah Final Verify"; } void doit() override; }; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahVerifier.cpp b/src/hotspot/share/gc/shenandoah/shenandoahVerifier.cpp index afef11640c9..8299cbe62c6 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahVerifier.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahVerifier.cpp @@ -1180,7 +1180,7 @@ void ShenandoahVerifier::verify_before_update_refs(ShenandoahGeneration* generat ); } -// We have not yet cleanup (reclaimed) the collection set +// We have not yet cleaned up (reclaimed) the collection set void ShenandoahVerifier::verify_after_update_refs(ShenandoahGeneration* generation) { verify_at_safepoint( generation, @@ -1197,6 +1197,23 @@ void ShenandoahVerifier::verify_after_update_refs(ShenandoahGeneration* generati ); } +// We have not yet cleaned up (reclaimed) the collection set +void ShenandoahVerifier::verify_after_gc(ShenandoahGeneration* generation) { + verify_at_safepoint( + generation, + "After GC", + _verify_remembered_disable, // do not verify remembered set + _verify_forwarded_none, // no forwarded references + _verify_marked_complete, // bitmaps might be stale, but alloc-after-mark should be well + _verify_cset_none, // no cset references, all updated + _verify_liveness_disable, // no reliable liveness data anymore + _verify_regions_nocset, // no cset regions, trash regions have appeared + // expect generation and heap sizes to match exactly, including trash + _verify_size_exact_including_trash, + _verify_gcstate_stable // GC state was turned off + ); +} + void ShenandoahVerifier::verify_after_degenerated(ShenandoahGeneration* generation) { verify_at_safepoint( generation, diff --git a/src/hotspot/share/gc/shenandoah/shenandoahVerifier.hpp b/src/hotspot/share/gc/shenandoah/shenandoahVerifier.hpp index 7e683cf7af8..0479d5f67ce 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahVerifier.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahVerifier.hpp @@ -220,6 +220,7 @@ public: void verify_before_evacuation(ShenandoahGeneration* generation); void verify_before_update_refs(ShenandoahGeneration* generation); void verify_after_update_refs(ShenandoahGeneration* generation); + void verify_after_gc(ShenandoahGeneration* generation); void verify_before_fullgc(ShenandoahGeneration* generation); void verify_after_fullgc(ShenandoahGeneration* generation); void verify_after_degenerated(ShenandoahGeneration* generation); diff --git a/src/hotspot/share/runtime/vmOperation.hpp b/src/hotspot/share/runtime/vmOperation.hpp index 5140d0401fb..6078600c16e 100644 --- a/src/hotspot/share/runtime/vmOperation.hpp +++ b/src/hotspot/share/runtime/vmOperation.hpp @@ -90,7 +90,7 @@ template(ShenandoahFinalMarkStartEvac) \ template(ShenandoahInitUpdateRefs) \ template(ShenandoahFinalUpdateRefs) \ - template(ShenandoahFinalRoots) \ + template(ShenandoahFinalVerify) \ template(ShenandoahDegeneratedGC) \ template(Exit) \ template(LinuxDllLoad) \ From 299973160d55f1318dd6c673267b4dcdefb64ac9 Mon Sep 17 00:00:00 2001 From: Rui Li Date: Fri, 17 Apr 2026 05:51:52 +0000 Subject: [PATCH 091/108] 8382295: Shenandoah: wrong denominator in full gc summary Reviewed-by: shade, kdnilsen --- src/hotspot/share/gc/shenandoah/shenandoahCollectorPolicy.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahCollectorPolicy.cpp b/src/hotspot/share/gc/shenandoah/shenandoahCollectorPolicy.cpp index bbd9dca1513..cfa79fc055e 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahCollectorPolicy.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahCollectorPolicy.cpp @@ -257,10 +257,10 @@ void ShenandoahCollectorPolicy::print_gc_stats(outputStream* out) const { out->print_cr("%5zu Full GCs (%.2f%%)", _success_full_gcs, percent_of(_success_full_gcs, completed_gcs)); if (!ExplicitGCInvokesConcurrent) { - out->print_cr(" %5zu invoked explicitly (%.2f%%)", explicit_requests, percent_of(explicit_requests, _success_concurrent_gcs)); + out->print_cr(" %5zu invoked explicitly (%.2f%%)", explicit_requests, percent_of(explicit_requests, _success_full_gcs)); } if (!ShenandoahImplicitGCInvokesConcurrent) { - out->print_cr(" %5zu invoked implicitly (%.2f%%)", implicit_requests, percent_of(implicit_requests, _success_concurrent_gcs)); + out->print_cr(" %5zu invoked implicitly (%.2f%%)", implicit_requests, percent_of(implicit_requests, _success_full_gcs)); } out->print_cr(" %5zu caused by allocation failure (%.2f%%)", _alloc_failure_full, percent_of(_alloc_failure_full, _success_full_gcs)); out->print_cr(" %5zu upgraded from Degenerated GC (%.2f%%)", _alloc_failure_degenerated_upgrade_to_full, percent_of(_alloc_failure_degenerated_upgrade_to_full, _success_full_gcs)); From 5c403a8029fd8d8c0f817b76b7f3abcfbb4bcabf Mon Sep 17 00:00:00 2001 From: Dusan Balek Date: Fri, 17 Apr 2026 06:10:25 +0000 Subject: [PATCH 092/108] 8380912: "Trailing white space will be removed" incorrectly triggered with \u2028 Reviewed-by: jlahoda --- .../tools/javac/parser/TextBlockSupport.java | 6 +- .../langtools/tools/javac/TextBlockU2028.java | 76 +++++++++++++++++++ 2 files changed, 79 insertions(+), 3 deletions(-) create mode 100644 test/langtools/tools/javac/TextBlockU2028.java diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/TextBlockSupport.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/TextBlockSupport.java index d099ceadba0..5112849d45f 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/TextBlockSupport.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/parser/TextBlockSupport.java @@ -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 @@ -56,8 +56,8 @@ class TextBlockSupport { // No need to check indentation if opting out (last line is empty.) char lastChar = string.charAt(string.length() - 1); boolean optOut = lastChar == '\n' || lastChar == '\r'; - // Split string based at line terminators. - String[] lines = string.split("\\R"); + // Split string using JLS text block line terminators: CRLF, CR, or LF. + String[] lines = string.split("\\r\\n|\\r|\\n"); int length = lines.length; // Extract last line. String lastLine = length == 0 ? "" : lines[length - 1]; diff --git a/test/langtools/tools/javac/TextBlockU2028.java b/test/langtools/tools/javac/TextBlockU2028.java new file mode 100644 index 00000000000..a7abfe43263 --- /dev/null +++ b/test/langtools/tools/javac/TextBlockU2028.java @@ -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. + */ + +/* + * @test + * @bug 8380912 + * @summary Verify that trailing whitespace warning is not reported for \u2028 + * inside text block content + * @library /tools/lib + * @modules + * jdk.compiler/com.sun.tools.javac.api + * jdk.compiler/com.sun.tools.javac.main + * @build toolbox.ToolBox toolbox.JavacTask + * @run junit TextBlockU2028 + */ + +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +import toolbox.JavacTask; +import toolbox.ToolBox; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; + +public class TextBlockU2028 { + Path base; + ToolBox tb = new ToolBox(); + + @Test + void testNoFalseTrailingWhitespaceWarning() throws Exception { + Path classes = base.resolve("classes"); + Files.createDirectories(classes); + new JavacTask(tb) + .options("-d", classes.toString(), "-Xlint:text-blocks", "-XDrawDiagnostics", "-Werror") + .sources(""" + public class Test { + String s = \"\"\" + foo \\u2028 bar + \"\"\"; + } + """) + .run() + .writeAll(); + } + + @BeforeEach + public void setUp(TestInfo info) { + base = Paths.get(".") + .resolve(info.getTestMethod() + .orElseThrow() + .getName()); + } +} From 15a7eabd4af7e2ec60d3cc0511c28d4fb740595d Mon Sep 17 00:00:00 2001 From: jonghoonpark Date: Fri, 17 Apr 2026 06:37:15 +0000 Subject: [PATCH 093/108] 8381924: Fix include guard in lambdaProxyClassDictionary.hpp Reviewed-by: jwaters, stefank --- src/hotspot/share/cds/lambdaProxyClassDictionary.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/hotspot/share/cds/lambdaProxyClassDictionary.hpp b/src/hotspot/share/cds/lambdaProxyClassDictionary.hpp index dfb75532917..b20e998bba6 100644 --- a/src/hotspot/share/cds/lambdaProxyClassDictionary.hpp +++ b/src/hotspot/share/cds/lambdaProxyClassDictionary.hpp @@ -22,8 +22,8 @@ * */ -#ifndef SHARE_CDS_LAMBDAPROXYCLASSINFO_HPP -#define SHARE_CDS_LAMBDAPROXYCLASSINFO_HPP +#ifndef SHARE_CDS_LAMBDAPROXYCLASSDICTIONARY_HPP +#define SHARE_CDS_LAMBDAPROXYCLASSDICTIONARY_HPP #include "cds/aotCompressedPointers.hpp" #include "cds/aotMetaspace.hpp" @@ -331,4 +331,4 @@ public: static void print_statistics(outputStream* st, bool is_static_archive); }; -#endif // SHARE_CDS_LAMBDAPROXYCLASSINFO_HPP +#endif // SHARE_CDS_LAMBDAPROXYCLASSDICTIONARY_HPP From ca643010a27292b99c6f2182764bc7cd4ac93b02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Markus=20Gr=C3=B6nlund?= Date: Fri, 17 Apr 2026 07:51:44 +0000 Subject: [PATCH 094/108] 8382242: JFR: Metadata reconstruction invalidates ConstantMap for java.lang.String Reviewed-by: egahlin --- .../jfr/internal/consumer/ParserFactory.java | 9 +- ...aReconstructionWithRetainedStringPool.java | 106 ++++++++++++++++++ 2 files changed, 112 insertions(+), 3 deletions(-) create mode 100644 test/jdk/jdk/jfr/api/consumer/streaming/TestMetadataReconstructionWithRetainedStringPool.java diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ParserFactory.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ParserFactory.java index e7dd234ca8d..c973dadb3b7 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ParserFactory.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/consumer/ParserFactory.java @@ -132,9 +132,12 @@ final class ParserFactory { case "short" -> new ShortParser(); case "byte" -> new ByteParser(); case "java.lang.String" -> { - ConstantMap pool = new ConstantMap(ObjectFactory.create(type, timeConverter), type); - ConstantLookup lookup = new ConstantLookup(pool, type); - constantLookups.put(type.getId(), lookup); + ConstantLookup lookup = constantLookups.get(type.getId()); + if (lookup == null) { + ConstantMap pool = new ConstantMap(ObjectFactory.create(type, timeConverter), type); + lookup = new ConstantLookup(pool, type); + constantLookups.put(type.getId(), lookup); + } yield new StringParser(lookup, event); } default -> throw new IOException("Unknown primitive type " + type.getName()); diff --git a/test/jdk/jdk/jfr/api/consumer/streaming/TestMetadataReconstructionWithRetainedStringPool.java b/test/jdk/jdk/jfr/api/consumer/streaming/TestMetadataReconstructionWithRetainedStringPool.java new file mode 100644 index 00000000000..8bbadc6db29 --- /dev/null +++ b/test/jdk/jdk/jfr/api/consumer/streaming/TestMetadataReconstructionWithRetainedStringPool.java @@ -0,0 +1,106 @@ +/* + * 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. + */ + +package jdk.jfr.api.consumer.streaming; + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.CountDownLatch; + +import jdk.jfr.Event; +import jdk.jfr.consumer.RecordingStream; + +/** + * @test + * @summary Test that it is possible to register new metadata in a new segment while retaining the string pool. + * @requires vm.flagless + * @requires vm.hasJFR + * @library /test/lib + * @run main/othervm jdk.jfr.api.consumer.streaming.TestMetadataReconstructionWithRetainedStringPool + */ +public class TestMetadataReconstructionWithRetainedStringPool { + /// Minimum string length required to trigger StringPool usage. + /// Mirrors `jdk.jfr.internal.StringPool.MIN_LIMIT`. + private static final int STRING_POOL_MIN_LIMIT = 16; + private static final int EXPECTED_EVENTS = 3; + + // Condition 1: String length > STRING_POOL_MIN_LIMIT triggers CONSTANT_POOL encoding. + private static final String TEXT = "a".repeat(STRING_POOL_MIN_LIMIT + 1);; + + static final class EventA extends Event { + String text = TEXT; + } + + static final class EventB extends Event { + String text = TEXT; + } + + public static void main(String... args) throws InterruptedException { + var aEventsPosted = new CountDownLatch(1); + var readyToPostEventB = new CountDownLatch(1); + var remaining = new CountDownLatch(EXPECTED_EVENTS); + + try (var rs = new RecordingStream()) { + rs.onEvent(e -> { + String textValue = e.getValue("text"); + if (textValue == null) { + throw new RuntimeException("e.getValue(\"text\") returned null"); + } + remaining.countDown(); + System.out.printf("Event #%d [%s]: text=%s%n", + EXPECTED_EVENTS - remaining.getCount(), + e.getEventType().getName(), + textValue); + }); + + rs.onFlush(() -> { + if (aEventsPosted.getCount() == 0) { + readyToPostEventB.countDown(); + } + }); + + rs.startAsync(); + + // Condition 2: Two distinct event types are required. + // First, load EventA as the initial event type and emit its first event. + // This first event looks into the StringPool pre-cache. Although the + // string length qualifies for pooling, because it isn't pre-cached, + // the first event encodes the string inline. + // The second event finds the string in the pre-cache and adds it to the + // pool. A constant pool ID to the pooled string is encoded in the event. + // + new EventA().commit(); + new EventA().commit(); + aEventsPosted.countDown(); + + // Condition 3: Wait for JFR flush. + // The default flush period is ~1 second. + readyToPostEventB.await(); + + // Load the second event type, EventB, AFTER the flush segment containing the two events of type EventA. + // A new metadata description will be constructed, and we verify that the StringPool added in the previous + // segment is still available for the EventB string pool reference to be resolved correctly. + new EventB().commit(); + remaining.await(); + } + } +} From 787ff6787e9e87ed2de4058e1d3a0e06b2126fd5 Mon Sep 17 00:00:00 2001 From: Thomas Schatzl Date: Fri, 17 Apr 2026 07:56:31 +0000 Subject: [PATCH 095/108] 8379620: G1: Typing error in jvmFlagConstraintsG1.cpp Reviewed-by: iwalulya --- src/hotspot/share/gc/g1/jvmFlagConstraintsG1.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hotspot/share/gc/g1/jvmFlagConstraintsG1.cpp b/src/hotspot/share/gc/g1/jvmFlagConstraintsG1.cpp index b56e82fac3c..df6adeb8041 100644 --- a/src/hotspot/share/gc/g1/jvmFlagConstraintsG1.cpp +++ b/src/hotspot/share/gc/g1/jvmFlagConstraintsG1.cpp @@ -70,7 +70,7 @@ JVMFlag::Error G1RemSetHowlMaxNumBucketsConstraintFunc(uint value, bool verbose) } if (!is_power_of_2(G1RemSetHowlMaxNumBuckets)) { JVMFlag::printError(verbose, - "G1RemSetMaxHowlNumBuckets (%u) must be a power of two.\n", + "G1RemSetHowlMaxNumBuckets (%u) must be a power of two.\n", value); return JVMFlag::VIOLATES_CONSTRAINT; } From 877425a15825ae4e1eafa4c90807e05679e48474 Mon Sep 17 00:00:00 2001 From: Volkan Yazici Date: Fri, 17 Apr 2026 08:18:40 +0000 Subject: [PATCH 096/108] 8380989: HttpRequest.Builder#headers() throws an exception if an empty array is passed Reviewed-by: dfuchs --- .../jdk/internal/net/http/HttpRequestBuilderImpl.java | 4 ++-- test/jdk/java/net/httpclient/HttpRequestBuilderTest.java | 6 +++--- test/jdk/java/net/httpclient/RequestBuilderTest.java | 3 +-- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/HttpRequestBuilderImpl.java b/src/java.net.http/share/classes/jdk/internal/net/http/HttpRequestBuilderImpl.java index ef0e2b152bb..c39edf878c9 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/HttpRequestBuilderImpl.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/HttpRequestBuilderImpl.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 @@ -145,7 +145,7 @@ public class HttpRequestBuilderImpl implements HttpRequest.Builder { @Override public HttpRequestBuilderImpl headers(String... params) { requireNonNull(params); - if (params.length == 0 || params.length % 2 != 0) { + if (params.length % 2 != 0) { throw newIAE("wrong number, %d, of parameters", params.length); } for (int i = 0; i < params.length; i += 2) { diff --git a/test/jdk/java/net/httpclient/HttpRequestBuilderTest.java b/test/jdk/java/net/httpclient/HttpRequestBuilderTest.java index 4544c85c5e8..dd1e2495382 100644 --- a/test/jdk/java/net/httpclient/HttpRequestBuilderTest.java +++ b/test/jdk/java/net/httpclient/HttpRequestBuilderTest.java @@ -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 @@ -112,8 +112,8 @@ public class HttpRequestBuilderTest { builder = test1("headers", builder, builder::headers, (String[]) null, NullPointerException.class); - builder = test1("headers", builder, builder::headers, new String[0], - IllegalArgumentException.class); + builder = test1("headers", builder, builder::headers, new String[0] + /* no expected exceptions */); builder = test1("headers", builder, builder::headers, (String[]) new String[] {null, "bar"}, diff --git a/test/jdk/java/net/httpclient/RequestBuilderTest.java b/test/jdk/java/net/httpclient/RequestBuilderTest.java index 6ba00fcd10f..9285bb55bb8 100644 --- a/test/jdk/java/net/httpclient/RequestBuilderTest.java +++ b/test/jdk/java/net/httpclient/RequestBuilderTest.java @@ -202,11 +202,10 @@ public class RequestBuilderTest { public void testHeaders() { HttpRequest.Builder builder = newBuilder(uri); - String[] empty = new String[0]; - assertThrows(IAE, () -> builder.headers(empty).build()); assertThrows(IAE, () -> builder.headers("1").build()); assertThrows(IAE, () -> builder.headers("1", "2", "3").build()); assertThrows(IAE, () -> builder.headers("1", "2", "3", "4", "5").build()); + assertEquals(0, builder.headers(new String[0]).build().headers().map().size()); assertEquals(0, builder.build().headers().map().size()); List requests = List.of( From 8a710ae20178c39ecab1e3aaac72916534ad7811 Mon Sep 17 00:00:00 2001 From: Artur Barashev Date: Fri, 17 Apr 2026 13:27:25 +0000 Subject: [PATCH 097/108] 8372526: Add support for ZLIB TLS Certificate Compression Co-authored-by: Xue-Lei Andrew Fan Reviewed-by: jnimeh, xuelei --- .../share/classes/sun/security/ssl/Alert.java | 4 +- .../sun/security/ssl/CertificateMessage.java | 47 ++- .../sun/security/ssl/CertificateRequest.java | 7 +- .../security/ssl/CompressCertExtension.java | 306 ++++++++++++++++++ .../security/ssl/CompressedCertificate.java | 264 +++++++++++++++ .../security/ssl/CompressionAlgorithm.java | 182 +++++++++++ .../sun/security/ssl/HandshakeContext.java | 7 +- .../sun/security/ssl/QuicTLSEngineImpl.java | 1 + .../sun/security/ssl/SSLContextImpl.java | 10 + .../sun/security/ssl/SSLExtension.java | 23 +- .../sun/security/ssl/SSLHandshake.java | 19 +- .../classes/sun/security/ssl/ServerHello.java | 7 +- test/jdk/java/net/httpclient/HeadTest.java | 17 +- .../net/httpclient/LargeHandshakeTest.java | 35 +- .../BoundDecompressMemory.java | 86 +++++ .../ClientCertCompressionExt.java | 157 +++++++++ .../CompressedCertMsg.java | 151 +++++++++ .../CompressedCertMsgCache.java | 197 +++++++++++ .../jdk/test/lib/security/SecurityUtils.java | 39 ++- 19 files changed, 1518 insertions(+), 41 deletions(-) create mode 100644 src/java.base/share/classes/sun/security/ssl/CompressCertExtension.java create mode 100644 src/java.base/share/classes/sun/security/ssl/CompressedCertificate.java create mode 100644 src/java.base/share/classes/sun/security/ssl/CompressionAlgorithm.java create mode 100644 test/jdk/sun/security/ssl/CertificateCompression/BoundDecompressMemory.java create mode 100644 test/jdk/sun/security/ssl/CertificateCompression/ClientCertCompressionExt.java create mode 100644 test/jdk/sun/security/ssl/CertificateCompression/CompressedCertMsg.java create mode 100644 test/jdk/sun/security/ssl/CertificateCompression/CompressedCertMsgCache.java diff --git a/src/java.base/share/classes/sun/security/ssl/Alert.java b/src/java.base/share/classes/sun/security/ssl/Alert.java index fb06b02a5d4..e9588a09b3d 100644 --- a/src/java.base/share/classes/sun/security/ssl/Alert.java +++ b/src/java.base/share/classes/sun/security/ssl/Alert.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 @@ -281,6 +281,8 @@ public enum Alert { // consumer so the state machine doesn't expect it. tc.handshakeContext.handshakeConsumers.remove( SSLHandshake.CERTIFICATE.id); + tc.handshakeContext.handshakeConsumers.remove( + SSLHandshake.COMPRESSED_CERTIFICATE.id); tc.handshakeContext.handshakeConsumers.remove( SSLHandshake.CERTIFICATE_VERIFY.id); } diff --git a/src/java.base/share/classes/sun/security/ssl/CertificateMessage.java b/src/java.base/share/classes/sun/security/ssl/CertificateMessage.java index c6897d71aa6..af5007d7899 100644 --- a/src/java.base/share/classes/sun/security/ssl/CertificateMessage.java +++ b/src/java.base/share/classes/sun/security/ssl/CertificateMessage.java @@ -781,14 +781,6 @@ final class CertificateMessage { } } - T13CertificateMessage(HandshakeContext handshakeContext, - byte[] requestContext, List certificates) { - super(handshakeContext); - - this.requestContext = requestContext.clone(); - this.certEntries = certificates; - } - T13CertificateMessage(HandshakeContext handshakeContext, ByteBuffer m) throws IOException { super(handshakeContext); @@ -925,16 +917,26 @@ final class CertificateMessage { HandshakeMessage message) throws IOException { // The producing happens in handshake context only. HandshakeContext hc = (HandshakeContext)context; - if (hc.sslConfig.isClientMode) { - return onProduceCertificate( - (ClientHandshakeContext)context, message); - } else { - return onProduceCertificate( + T13CertificateMessage cm = hc.sslConfig.isClientMode ? + onProduceCertificate( + (ClientHandshakeContext)context, message) : + onProduceCertificate( (ServerHandshakeContext)context, message); + + // Output the handshake message. + if (hc.certDeflater == null) { + cm.write(hc.handshakeOutput); + hc.handshakeOutput.flush(); + } else { + // Replace with CompressedCertificate message + CompressedCertificate.handshakeProducer.produce(hc, cm); } + + // The handshake message has been delivered. + return null; } - private byte[] onProduceCertificate(ServerHandshakeContext shc, + private T13CertificateMessage onProduceCertificate(ServerHandshakeContext shc, HandshakeMessage message) throws IOException { ClientHelloMessage clientHello = (ClientHelloMessage)message; @@ -993,12 +995,7 @@ final class CertificateMessage { SSLLogger.fine("Produced server Certificate message", cm); } - // Output the handshake message. - cm.write(shc.handshakeOutput); - shc.handshakeOutput.flush(); - - // The handshake message has been delivered. - return null; + return cm; } private static SSLPossession choosePossession( @@ -1045,7 +1042,7 @@ final class CertificateMessage { return pos; } - private byte[] onProduceCertificate(ClientHandshakeContext chc, + private T13CertificateMessage onProduceCertificate(ClientHandshakeContext chc, HandshakeMessage message) throws IOException { ClientHelloMessage clientHello = (ClientHelloMessage)message; SSLPossession pos = choosePossession(chc, clientHello); @@ -1091,12 +1088,7 @@ final class CertificateMessage { SSLLogger.fine("Produced client Certificate message", cm); } - // Output the handshake message. - cm.write(chc.handshakeOutput); - chc.handshakeOutput.flush(); - - // The handshake message has been delivered. - return null; + return cm; } } @@ -1116,6 +1108,7 @@ final class CertificateMessage { HandshakeContext hc = (HandshakeContext)context; // clean up this consumer + hc.handshakeConsumers.remove(SSLHandshake.COMPRESSED_CERTIFICATE.id); hc.handshakeConsumers.remove(SSLHandshake.CERTIFICATE.id); // Ensure that the Certificate message has not been sent w/o diff --git a/src/java.base/share/classes/sun/security/ssl/CertificateRequest.java b/src/java.base/share/classes/sun/security/ssl/CertificateRequest.java index 039399560cd..2eceb4d9ebd 100644 --- a/src/java.base/share/classes/sun/security/ssl/CertificateRequest.java +++ b/src/java.base/share/classes/sun/security/ssl/CertificateRequest.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 @@ -956,6 +956,11 @@ final class CertificateRequest { // update // shc.certRequestContext = crm.requestContext.clone(); + if (shc.certInflaters != null && !shc.certInflaters.isEmpty()) { + shc.handshakeConsumers.put( + SSLHandshake.COMPRESSED_CERTIFICATE.id, + SSLHandshake.COMPRESSED_CERTIFICATE); + } shc.handshakeConsumers.put(SSLHandshake.CERTIFICATE.id, SSLHandshake.CERTIFICATE); shc.handshakeConsumers.put(SSLHandshake.CERTIFICATE_VERIFY.id, diff --git a/src/java.base/share/classes/sun/security/ssl/CompressCertExtension.java b/src/java.base/share/classes/sun/security/ssl/CompressCertExtension.java new file mode 100644 index 00000000000..eff97857ef0 --- /dev/null +++ b/src/java.base/share/classes/sun/security/ssl/CompressCertExtension.java @@ -0,0 +1,306 @@ +/* + * Copyright (C) 2022 THL A29 Limited, a Tencent company. 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 + * 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.nio.ByteBuffer; +import java.text.MessageFormat; +import java.util.Locale; +import java.util.Map; +import java.util.function.Function; +import javax.net.ssl.SSLProtocolException; +import sun.security.ssl.SSLExtension.ExtensionConsumer; +import sun.security.ssl.SSLExtension.SSLExtensionSpec; +import sun.security.ssl.SSLHandshake.HandshakeMessage; + +/** + * Pack of the "compress_certificate" extensions [RFC 5246]. + */ +final class CompressCertExtension { + + static final HandshakeProducer chNetworkProducer = + new CHCompressCertificateProducer(); + static final ExtensionConsumer chOnLoadConsumer = + new CHCompressCertificateConsumer(); + + static final HandshakeProducer crNetworkProducer = + new CRCompressCertificateProducer(); + static final ExtensionConsumer crOnLoadConsumer = + new CRCompressCertificateConsumer(); + + static final SSLStringizer ccStringizer = + new CompressCertificateStringizer(); + + /** + * The "signature_algorithms" extension. + */ + static final class CertCompressionSpec implements SSLExtensionSpec { + + private final int[] compressionAlgorithms; // non-null + + CertCompressionSpec( + Map> certInflaters) { + compressionAlgorithms = new int[certInflaters.size()]; + int i = 0; + for (Integer id : certInflaters.keySet()) { + compressionAlgorithms[i++] = id; + } + } + + CertCompressionSpec(HandshakeContext hc, + ByteBuffer buffer) throws IOException { + if (buffer.remaining() < 2) { // 2: the length of the list + throw hc.conContext.fatal(Alert.DECODE_ERROR, + new SSLProtocolException( + "Invalid compress_certificate: insufficient data")); + } + + byte[] algs = Record.getBytes8(buffer); + if (buffer.hasRemaining()) { + throw hc.conContext.fatal(Alert.DECODE_ERROR, + new SSLProtocolException( + "Invalid compress_certificate: unknown extra data")); + } + + if (algs.length == 0 || (algs.length & 0x01) != 0) { + throw hc.conContext.fatal(Alert.DECODE_ERROR, + new SSLProtocolException( + "Invalid compress_certificate: incomplete data")); + } + + int[] compressionAlgs = new int[algs.length / 2]; + for (int i = 0, j = 0; i < algs.length; ) { + byte hash = algs[i++]; + byte sign = algs[i++]; + compressionAlgs[j++] = ((hash & 0xFF) << 8) | (sign & 0xFF); + } + + this.compressionAlgorithms = compressionAlgs; + } + + @Override + public String toString() { + MessageFormat messageFormat = new MessageFormat( + "\"compression algorithms\": '['{0}']'", Locale.ENGLISH); + + if (compressionAlgorithms.length == 0) { + Object[] messageFields = { + "" + }; + return messageFormat.format(messageFields); + } else { + StringBuilder builder = new StringBuilder(512); + boolean isFirst = true; + for (int ca : compressionAlgorithms) { + if (isFirst) { + isFirst = false; + } else { + builder.append(", "); + } + + builder.append(CompressionAlgorithm.nameOf(ca)); + } + + Object[] messageFields = { + builder.toString() + }; + + return messageFormat.format(messageFields); + } + } + } + + private static final + class CompressCertificateStringizer implements SSLStringizer { + + @Override + public String toString(HandshakeContext hc, ByteBuffer buffer) { + try { + return (new CertCompressionSpec(hc, buffer)).toString(); + } catch (IOException ioe) { + // For debug logging only, so please swallow exceptions. + return ioe.getMessage(); + } + } + } + + /** + * Network data producer of a "compress_certificate" extension in + * the ClientHello handshake message. + */ + private static final + class CHCompressCertificateProducer implements HandshakeProducer { + + // Prevent instantiation of this class. + private CHCompressCertificateProducer() { + // blank + } + + @Override + public byte[] produce(ConnectionContext context, + HandshakeMessage message) throws IOException { + // The producing happens in client side only. + return produceCompCertExt(context, + SSLExtension.CH_COMPRESS_CERTIFICATE); + } + } + + /** + * Network data consumer of a "compress_certificate" extension in + * the ClientHello handshake message. + */ + private static final + class CHCompressCertificateConsumer implements ExtensionConsumer { + + // Prevent instantiation of this class. + private CHCompressCertificateConsumer() { + // blank + } + + @Override + public void consume(ConnectionContext context, + HandshakeMessage message, ByteBuffer buffer) + throws IOException { + // The consuming happens in server side only. + consumeCompCertExt(context, buffer, + SSLExtension.CH_COMPRESS_CERTIFICATE); + } + } + + /** + * Network data producer of a "compress_certificate" extension in + * the CertificateRequest handshake message. + */ + private static final + class CRCompressCertificateProducer implements HandshakeProducer { + + // Prevent instantiation of this class. + private CRCompressCertificateProducer() { + // blank + } + + @Override + public byte[] produce(ConnectionContext context, + HandshakeMessage message) throws IOException { + // The producing happens in server side only. + return produceCompCertExt(context, + SSLExtension.CR_COMPRESS_CERTIFICATE); + } + } + + /** + * Network data consumer of a "compress_certificate" extension in + * the CertificateRequest handshake message. + */ + private static final + class CRCompressCertificateConsumer implements ExtensionConsumer { + + // Prevent instantiation of this class. + private CRCompressCertificateConsumer() { + // blank + } + + @Override + public void consume(ConnectionContext context, + HandshakeMessage message, ByteBuffer buffer) + throws IOException { + // The consuming happens in client side only. + consumeCompCertExt(context, buffer, + SSLExtension.CR_COMPRESS_CERTIFICATE); + } + } + + private static byte[] produceCompCertExt( + ConnectionContext context, SSLExtension extension) + throws IOException { + + HandshakeContext hc = (HandshakeContext) context; + // Is it a supported and enabled extension? + if (!hc.sslConfig.isAvailable(extension)) { + if (SSLLogger.isOn() && SSLLogger.isOn(SSLLogger.Opt.HANDSHAKE)) { + SSLLogger.fine("Ignore unavailable " + + "compress_certificate extension"); + } + return null; + } + + // Produce the extension. + hc.certInflaters = CompressionAlgorithm.getInflaters(); + + if (hc.certInflaters.isEmpty()) { + if (SSLLogger.isOn() && SSLLogger.isOn(SSLLogger.Opt.HANDSHAKE)) { + SSLLogger.warning("Unable to produce the extension: " + + "no certificate compression inflaters defined"); + } + return null; + } + + int vectorLen = CompressionAlgorithm.sizeInRecord() * + hc.certInflaters.size(); + byte[] extData = new byte[vectorLen + 1]; + ByteBuffer m = ByteBuffer.wrap(extData); + Record.putInt8(m, vectorLen); + for (Integer algId : hc.certInflaters.keySet()) { + Record.putInt16(m, algId); + } + + // Update the context. + hc.handshakeExtensions.put( + extension, new CertCompressionSpec(hc.certInflaters)); + + return extData; + } + + private static void consumeCompCertExt(ConnectionContext context, + ByteBuffer buffer, SSLExtension extension) throws IOException { + + HandshakeContext hc = (HandshakeContext) context; + // Is it a supported and enabled extension? + if (!hc.sslConfig.isAvailable(extension)) { + if (SSLLogger.isOn() && SSLLogger.isOn(SSLLogger.Opt.HANDSHAKE)) { + SSLLogger.fine("Ignore unavailable " + + "compress_certificate extension"); + } + return; // ignore the extension + } + + // Parse the extension. + CertCompressionSpec spec = new CertCompressionSpec(hc, buffer); + + // Update the context. + hc.certDeflater = CompressionAlgorithm.selectDeflater( + spec.compressionAlgorithms); + + if (hc.certDeflater == null) { + if (SSLLogger.isOn() && SSLLogger.isOn(SSLLogger.Opt.HANDSHAKE)) { + SSLLogger.fine("Ignore, no supported " + + "certificate compression algorithms"); + } + } + // No impact on session resumption. + } +} diff --git a/src/java.base/share/classes/sun/security/ssl/CompressedCertificate.java b/src/java.base/share/classes/sun/security/ssl/CompressedCertificate.java new file mode 100644 index 00000000000..067a0344c9d --- /dev/null +++ b/src/java.base/share/classes/sun/security/ssl/CompressedCertificate.java @@ -0,0 +1,264 @@ +/* + * Copyright (C) 2022 THL A29 Limited, a Tencent company. 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 + * 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.nio.ByteBuffer; +import java.text.MessageFormat; +import java.util.Locale; +import java.util.function.Function; +import javax.net.ssl.SSLProtocolException; +import sun.security.ssl.SSLHandshake.HandshakeMessage; +import sun.security.util.Cache; +import sun.security.util.Cache.EqualByteArray; +import sun.security.util.HexDumpEncoder; + +/** + * Pack of the CompressedCertificate handshake message. + */ +final class CompressedCertificate { + + static final SSLConsumer handshakeConsumer = + new CompressedCertConsumer(); + static final HandshakeProducer handshakeProducer = + new CompressedCertProducer(); + + record CompCertCacheKey(EqualByteArray eba, int algId) {} + + /** + * The CompressedCertificate handshake message for TLS 1.3. + */ + static final class CompressedCertMessage extends HandshakeMessage { + + private final int algorithmId; + private final int uncompressedLength; + private final byte[] compressedCert; + + CompressedCertMessage(HandshakeContext context, + int algorithmId, int uncompressedLength, + byte[] compressedCert) { + super(context); + + this.algorithmId = algorithmId; + this.uncompressedLength = uncompressedLength; + this.compressedCert = compressedCert; + } + + CompressedCertMessage(HandshakeContext handshakeContext, + ByteBuffer m) throws IOException { + super(handshakeContext); + + // struct { + // CertificateCompressionAlgorithm algorithm; + // uint24 uncompressed_length; + // opaque compressed_certificate_message<1..2^24-1>; + // } CompressedCertificate; + if (m.remaining() < 9) { + throw new SSLProtocolException( + "Invalid CompressedCertificate message: " + + "insufficient data (length=" + m.remaining() + + ")"); + } + this.algorithmId = Record.getInt16(m); + this.uncompressedLength = Record.getInt24(m); + this.compressedCert = Record.getBytes24(m); + + if (m.hasRemaining()) { + throw handshakeContext.conContext.fatal( + Alert.HANDSHAKE_FAILURE, + "Invalid CompressedCertificate message: " + + "unknown extra data"); + } + } + + @Override + public SSLHandshake handshakeType() { + return SSLHandshake.COMPRESSED_CERTIFICATE; + } + + @Override + public int messageLength() { + return 8 + compressedCert.length; + } + + @Override + public void send(HandshakeOutStream hos) throws IOException { + hos.putInt16(algorithmId); + hos.putInt24(uncompressedLength); + hos.putBytes24(compressedCert); + } + + @Override + public String toString() { + MessageFormat messageFormat = new MessageFormat( + """ + "CompressedCertificate": '{' + "algorithm": "{0}", + "uncompressed_length": {1} + "compressed_certificate_message": [ + {2} + ] + '}'""", + Locale.ENGLISH); + + HexDumpEncoder hexEncoder = new HexDumpEncoder(); + Object[] messageFields = { + CompressionAlgorithm.nameOf(algorithmId), + uncompressedLength, + Utilities.indent(hexEncoder.encode(compressedCert), " ") + }; + + return messageFormat.format(messageFields); + } + } + + /** + * The "CompressedCertificate" handshake message producer for TLS 1.3. + */ + private static final + class CompressedCertProducer implements HandshakeProducer { + + // Prevent instantiation of this class. + private CompressedCertProducer() { + // blank + } + + // Note this is a special producer, which can only be called from + // the CertificateMessage producer. The input 'message' parameter + // represents the Certificate handshake message. + @Override + public byte[] produce(ConnectionContext context, + HandshakeMessage message) throws IOException { + // The producing happens in handshake context only. + HandshakeContext hc = (HandshakeContext) context; + + // Compress the Certificate message. + HandshakeOutStream hos = new HandshakeOutStream(null); + message.send(hos); + byte[] certMsg = hos.toByteArray(); + byte[] compressedCertMsg; + + // First byte is the size of certificate_request_context which + // should be random if present. Don't cache a randomized message. + if (certMsg[0] != 0) { + compressedCertMsg = hc.certDeflater.getValue().apply(certMsg); + } else { + Cache cache = + hc.sslContext.getCompCertCache(); + CompCertCacheKey key = new CompCertCacheKey( + new EqualByteArray(certMsg), hc.certDeflater.getKey()); + compressedCertMsg = cache.get(key); + + if (compressedCertMsg == null) { + compressedCertMsg = + hc.certDeflater.getValue().apply(certMsg); + + if (SSLLogger.isOn() + && SSLLogger.isOn(SSLLogger.Opt.HANDSHAKE)) { + SSLLogger.fine("Caching CompressedCertificate message"); + } + + cache.put(key, compressedCertMsg); + } + } + + if (compressedCertMsg == null || compressedCertMsg.length == 0) { + throw hc.conContext.fatal(Alert.HANDSHAKE_FAILURE, + "No compressed Certificate data"); + } + + CompressedCertMessage ccm = new CompressedCertMessage(hc, + hc.certDeflater.getKey(), certMsg.length, + compressedCertMsg); + + if (SSLLogger.isOn() && SSLLogger.isOn(SSLLogger.Opt.HANDSHAKE)) { + SSLLogger.fine( + "Produced CompressedCertificate handshake message", + ccm); + } + + ccm.write(hc.handshakeOutput); + hc.handshakeOutput.flush(); + + // The handshake message has been delivered. + return null; + } + } + + /** + * The "CompressedCertificate" handshake message consumer for TLS 1.3. + */ + private static final class CompressedCertConsumer implements SSLConsumer { + + // Prevent instantiation of this class. + private CompressedCertConsumer() { + // blank + } + + @Override + public void consume(ConnectionContext context, + ByteBuffer message) throws IOException { + // The consuming happens in handshake context only. + HandshakeContext hc = (HandshakeContext) context; + + // clean up this consumer + hc.handshakeConsumers.remove( + SSLHandshake.COMPRESSED_CERTIFICATE.id); + hc.handshakeConsumers.remove(SSLHandshake.CERTIFICATE.id); + + // Parse the handshake message + CompressedCertMessage ccm = new CompressedCertMessage(hc, message); + if (SSLLogger.isOn() && SSLLogger.isOn(SSLLogger.Opt.HANDSHAKE)) { + SSLLogger.fine( + "Consuming CompressedCertificate handshake message", + ccm); + } + + // check the compression algorithm + Function inflater = + hc.certInflaters.get(ccm.algorithmId); + if (inflater == null) { + throw hc.conContext.fatal(Alert.BAD_CERTIFICATE, + "Unsupported certificate compression algorithm"); + } + + // decompress + byte[] certificateMessage = inflater.apply(ccm.compressedCert); + + // check the uncompressed length + if (certificateMessage == null || + certificateMessage.length != ccm.uncompressedLength) { + throw hc.conContext.fatal(Alert.BAD_CERTIFICATE, + "Improper certificate compression"); + } + + // Call the Certificate handshake message consumer. + CertificateMessage.t13HandshakeConsumer.consume(hc, + ByteBuffer.wrap(certificateMessage)); + } + } +} diff --git a/src/java.base/share/classes/sun/security/ssl/CompressionAlgorithm.java b/src/java.base/share/classes/sun/security/ssl/CompressionAlgorithm.java new file mode 100644 index 00000000000..3e9ef154424 --- /dev/null +++ b/src/java.base/share/classes/sun/security/ssl/CompressionAlgorithm.java @@ -0,0 +1,182 @@ +/* + * Copyright (C) 2022 THL A29 Limited, a Tencent company. 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 + * 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.ByteArrayOutputStream; +import java.util.AbstractMap; +import java.util.Map; +import java.util.function.Function; +import java.util.zip.Deflater; +import java.util.zip.Inflater; + +/** + * Enum for TLS certificate compression algorithms. + * This class also defines internally supported inflate/deflate functions. + */ + +enum CompressionAlgorithm { + ZLIB(1); // Currently only ZLIB is supported. + + final int id; + + CompressionAlgorithm(int id) { + this.id = id; + } + + static CompressionAlgorithm nameOf(int id) { + for (CompressionAlgorithm ca : CompressionAlgorithm.values()) { + if (ca.id == id) { + return ca; + } + } + + return null; + } + + // Return the size of a compression algorithms structure in TLS record. + static int sizeInRecord() { + return 2; + } + + // The size of compression/decompression buffer. + private static final int BUF_SIZE = 1024; + + private static final Map> DEFLATORS = + Map.of(ZLIB.id, (input) -> { + try (Deflater deflater = new Deflater(); + ByteArrayOutputStream outputStream = + new ByteArrayOutputStream(input.length)) { + + deflater.setInput(input); + deflater.finish(); + byte[] buffer = new byte[BUF_SIZE]; + + while (!deflater.finished()) { + int compressedSize = deflater.deflate(buffer); + outputStream.write(buffer, 0, compressedSize); + } + + return outputStream.toByteArray(); + } catch (Exception e) { + if (SSLLogger.isOn() + && SSLLogger.isOn(SSLLogger.Opt.HANDSHAKE)) { + SSLLogger.warning("Exception during certificate " + + "compression: ", e); + } + return null; + } + }); + + static Map.Entry> selectDeflater( + int[] compressionAlgorithmIds) { + + for (var entry : DEFLATORS.entrySet()) { + for (int id : compressionAlgorithmIds) { + if (id == entry.getKey()) { + return new AbstractMap.SimpleImmutableEntry<>(entry); + } + } + } + + return null; + } + + private static final Map> INFLATORS = + Map.of(ZLIB.id, (input) -> { + try (Inflater inflater = new Inflater(); + ByteArrayOutputStream outputStream = + new ByteArrayOutputStream(input.length)) { + + inflater.setInput(input); + byte[] buffer = new byte[BUF_SIZE]; + + while (!inflater.finished()) { + int decompressedSize = inflater.inflate(buffer); + + if (decompressedSize == 0) { + if (inflater.needsDictionary()) { + if (SSLLogger.isOn() + && SSLLogger.isOn( + SSLLogger.Opt.HANDSHAKE)) { + SSLLogger.warning("Compressed input " + + "requires a dictionary"); + } + + return null; + } + + if (inflater.needsInput()) { + if (SSLLogger.isOn() + && SSLLogger.isOn( + SSLLogger.Opt.HANDSHAKE)) { + SSLLogger.warning( + "Incomplete compressed input"); + } + + return null; + } + + // Else just break the loop. + break; + } + + outputStream.write(buffer, 0, decompressedSize); + + // Bound the memory usage. + if (outputStream.size() + > SSLConfiguration.maxHandshakeMessageSize) { + if (SSLLogger.isOn() + && SSLLogger.isOn( + SSLLogger.Opt.HANDSHAKE)) { + SSLLogger.warning("The size of the " + + "uncompressed certificate message " + + "exceeds maximum allowed size of " + + SSLConfiguration.maxHandshakeMessageSize + + " bytes; compressed size: " + + input.length); + } + + return null; + } + } + + return outputStream.toByteArray(); + } catch (Exception e) { + if (SSLLogger.isOn() + && SSLLogger.isOn(SSLLogger.Opt.HANDSHAKE)) { + SSLLogger.warning( + "Exception during certificate decompression: ", + e); + } + return null; + } + }); + + static Map> getInflaters() { + return INFLATORS; + } +} diff --git a/src/java.base/share/classes/sun/security/ssl/HandshakeContext.java b/src/java.base/share/classes/sun/security/ssl/HandshakeContext.java index 54a2650c058..fbf2c00bbb4 100644 --- a/src/java.base/share/classes/sun/security/ssl/HandshakeContext.java +++ b/src/java.base/share/classes/sun/security/ssl/HandshakeContext.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 @@ -33,6 +33,7 @@ import java.security.AlgorithmConstraints; import java.security.CryptoPrimitive; import java.util.*; import java.util.AbstractMap.SimpleImmutableEntry; +import java.util.function.Function; import javax.crypto.SecretKey; import javax.net.ssl.SNIServerName; import javax.net.ssl.SSLHandshakeException; @@ -131,6 +132,10 @@ abstract class HandshakeContext implements ConnectionContext { List peerRequestedSignatureSchemes; List peerRequestedCertSignSchemes; + // CertificateCompressionAlgorithm + Map> certInflaters; + Map.Entry> certDeflater; + // Known authorities X500Principal[] peerSupportedAuthorities = null; diff --git a/src/java.base/share/classes/sun/security/ssl/QuicTLSEngineImpl.java b/src/java.base/share/classes/sun/security/ssl/QuicTLSEngineImpl.java index b5f1eff179c..3384bf5f089 100644 --- a/src/java.base/share/classes/sun/security/ssl/QuicTLSEngineImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/QuicTLSEngineImpl.java @@ -73,6 +73,7 @@ public final class QuicTLSEngineImpl implements QuicTLSEngine, SSLTransport { SSLHandshake.ENCRYPTED_EXTENSIONS.id, HANDSHAKE, SSLHandshake.CERTIFICATE_REQUEST.id, HANDSHAKE, SSLHandshake.CERTIFICATE.id, HANDSHAKE, + SSLHandshake.COMPRESSED_CERTIFICATE.id, HANDSHAKE, SSLHandshake.CERTIFICATE_VERIFY.id, HANDSHAKE, SSLHandshake.FINISHED.id, HANDSHAKE, SSLHandshake.NEW_SESSION_TICKET.id, ONE_RTT); diff --git a/src/java.base/share/classes/sun/security/ssl/SSLContextImpl.java b/src/java.base/share/classes/sun/security/ssl/SSLContextImpl.java index a1cc3ee112f..fdeb94bb496 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLContextImpl.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLContextImpl.java @@ -34,7 +34,9 @@ import java.util.concurrent.locks.ReentrantLock; import java.util.stream.Collectors; import javax.net.ssl.*; import sun.security.provider.certpath.AlgorithmChecker; +import sun.security.ssl.CompressedCertificate.CompCertCacheKey; import sun.security.ssl.SSLAlgorithmConstraints.SIGNATURE_CONSTRAINTS_MODE; +import sun.security.util.Cache; import sun.security.validator.Validator; /** @@ -73,6 +75,10 @@ public abstract class SSLContextImpl extends SSLContextSpi { private final ReentrantLock contextLock = new ReentrantLock(); + // Avoid compressing local certificates repeatedly for every handshake. + private final Cache compCertCache = + Cache.newSoftMemoryCache(12); + SSLContextImpl() { ephemeralKeyManager = new EphemeralKeyManager(); clientCache = new SSLSessionContextImpl(false); @@ -225,6 +231,10 @@ public abstract class SSLContextImpl extends SSLContextSpi { return ephemeralKeyManager; } + Cache getCompCertCache() { + return compCertCache; + } + // Used for DTLS in server mode only. HelloCookieManager getHelloCookieManager(ProtocolVersion protocolVersion) { if (helloCookieManagerBuilder == null) { diff --git a/src/java.base/share/classes/sun/security/ssl/SSLExtension.java b/src/java.base/share/classes/sun/security/ssl/SSLExtension.java index aacb9420748..b13edc0359c 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLExtension.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLExtension.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 @@ -270,6 +270,27 @@ enum SSLExtension implements SSLStringizer { // Extensions defined in RFC 7924 (TLS Cached Information Extension) CACHED_INFO (0x0019, "cached_info"), + // Extensions defined in RFC 8879 (TLS Certificate Compression) + CH_COMPRESS_CERTIFICATE (0x001B, "compress_certificate", + SSLHandshake.CLIENT_HELLO, + ProtocolVersion.PROTOCOLS_OF_13, + CompressCertExtension.chNetworkProducer, + CompressCertExtension.chOnLoadConsumer, + null, + null, + null, + CompressCertExtension.ccStringizer), + + CR_COMPRESS_CERTIFICATE (0x001B, "compress_certificate", + SSLHandshake.CERTIFICATE_REQUEST, + ProtocolVersion.PROTOCOLS_OF_13, + CompressCertExtension.crNetworkProducer, + CompressCertExtension.crOnLoadConsumer, + null, + null, + null, + CompressCertExtension.ccStringizer), + // Extensions defined in RFC 5077 (TLS Session Resumption without Server-Side State) CH_SESSION_TICKET (0x0023, "session_ticket", SSLHandshake.CLIENT_HELLO, diff --git a/src/java.base/share/classes/sun/security/ssl/SSLHandshake.java b/src/java.base/share/classes/sun/security/ssl/SSLHandshake.java index 7c78f6c3005..2c6b58bafa5 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLHandshake.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLHandshake.java @@ -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 @@ -370,7 +370,22 @@ enum SSLHandshake implements SSLConsumer, HandshakeProducer { }), // RFC 8879 - TLS Certificate Compression - COMPRESSED_CERTIFICATE ((byte)0x19, "compressed_certificate"), + @SuppressWarnings({"unchecked", "rawtypes"}) + COMPRESSED_CERTIFICATE ((byte)0x19, "compressed_certificate", + (new Map.Entry[] { + new SimpleImmutableEntry<>( + CompressedCertificate.handshakeConsumer, + ProtocolVersion.PROTOCOLS_OF_13 + ) + }), + (new Map.Entry[] { + // Note that the producing of this message is delegated to + // CertificateMessage producer. + new SimpleImmutableEntry<>( + CertificateMessage.t13HandshakeProducer, + ProtocolVersion.PROTOCOLS_OF_13 + ) + })), // RFC 8870 - Encrypted Key Transport for DTLS/Secure RTP EKT_KEY ((byte)0x1A, "ekt_key"), 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 6980c216697..4bd2b0a059f 100644 --- a/src/java.base/share/classes/sun/security/ssl/ServerHello.java +++ b/src/java.base/share/classes/sun/security/ssl/ServerHello.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 @@ -1461,6 +1461,11 @@ final class ServerHello { chc.handshakeConsumers.put( SSLHandshake.CERTIFICATE_REQUEST.id, SSLHandshake.CERTIFICATE_REQUEST); + if (chc.certInflaters != null && !chc.certInflaters.isEmpty()) { + chc.handshakeConsumers.put( + SSLHandshake.COMPRESSED_CERTIFICATE.id, + SSLHandshake.COMPRESSED_CERTIFICATE); + } chc.handshakeConsumers.put( SSLHandshake.CERTIFICATE.id, SSLHandshake.CERTIFICATE); diff --git a/test/jdk/java/net/httpclient/HeadTest.java b/test/jdk/java/net/httpclient/HeadTest.java index c0bef240520..5694e4f6016 100644 --- a/test/jdk/java/net/httpclient/HeadTest.java +++ b/test/jdk/java/net/httpclient/HeadTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2026, 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 @@ -22,14 +22,25 @@ */ /* - * @test - * @bug 8203433 8276559 + * @test id=withCertificateCompression + * @bug 8203433 8276559 8372526 * @summary Tests Client handles HEAD and 304 responses correctly. * @library /test/lib /test/jdk/java/net/httpclient/lib * @build jdk.test.lib.net.SimpleSSLContext * @run junit/othervm -Djdk.httpclient.HttpClient.log=trace,headers,requests ${test.main.class} */ +/* + * @test id=withoutCertificateCompression + * @bug 8203433 8276559 8372526 + * @summary Tests Client handles HEAD and 304 responses correctly. + * @library /test/lib /test/jdk/java/net/httpclient/lib + * @build jdk.test.lib.net.SimpleSSLContext + * @run junit/othervm -Djdk.tls.client.disableExtensions=compress_certificate + * -Djdk.tls.server.disableExtensions=compress_certificate + * -Djdk.httpclient.HttpClient.log=trace,headers,requests HeadTest + */ + import jdk.test.lib.net.SimpleSSLContext; import javax.net.ssl.SSLContext; diff --git a/test/jdk/java/net/httpclient/LargeHandshakeTest.java b/test/jdk/java/net/httpclient/LargeHandshakeTest.java index 59cdad27cbc..ec4cd8be4aa 100644 --- a/test/jdk/java/net/httpclient/LargeHandshakeTest.java +++ b/test/jdk/java/net/httpclient/LargeHandshakeTest.java @@ -66,9 +66,9 @@ import static java.net.http.HttpClient.Version.HTTP_3; import static java.net.http.HttpOption.Http3DiscoveryMode.HTTP_3_URI_ONLY; import static java.net.http.HttpOption.H3_DISCOVERY; -/** - * @test - * @bug 8231449 +/* + * @test id=withCertificateCompression + * @bug 8231449 8372526 * @summary This test verifies that the HttpClient works correctly when the server * sends a large certificate. This test will not pass without * the fix for JDK-8231449. To regenerate the certificate, modify the @@ -91,6 +91,35 @@ import static java.net.http.HttpOption.H3_DISCOVERY; * ${test.main.class} * */ + +/* + * @test id=withoutCertificateCompression + * @bug 8231449 8372526 + * @summary This test verifies that the HttpClient works correctly when the server + * sends a large certificate. This test will not pass without + * the fix for JDK-8231449. To regenerate the certificate, modify the + * COMMAND constant as you need, possibly changing the start date + * and validity of the certificate in the command, then run the test. + * The test will run with the old certificate, but will print the new command. + * Copy paste the new command printed by this test into a terminal. + * Then modify the at run line to pass the file generated by that command + * as first argument, and copy paste the new values of the COMMAND and + * BASE64_CERT constant printed by the test into the test. + * Then restore the original at run line and test again. + * @library /test/lib /test/jdk/java/net/httpclient/lib + * @build jdk.httpclient.test.lib.common.HttpServerAdapters jdk.test.lib.net.SimpleSSLContext + * DigestEchoServer + * jdk.httpclient.test.lib.common.TestServerConfigurator + * @run main/othervm -Djdk.tls.client.disableExtensions=compress_certificate + * -Djdk.tls.server.disableExtensions=compress_certificate + * -Dtest.requiresHost=true + * -Djdk.httpclient.HttpClient.log=headers + * -Djdk.internal.httpclient.debug=true + * -Djdk.tls.maxHandshakeMessageSize=131072 + * LargeHandshakeTest + * + */ + public class LargeHandshakeTest implements HttpServerAdapters { // Use this command to regenerate the keystore file whose content is diff --git a/test/jdk/sun/security/ssl/CertificateCompression/BoundDecompressMemory.java b/test/jdk/sun/security/ssl/CertificateCompression/BoundDecompressMemory.java new file mode 100644 index 00000000000..92e771f2a4a --- /dev/null +++ b/test/jdk/sun/security/ssl/CertificateCompression/BoundDecompressMemory.java @@ -0,0 +1,86 @@ +/* + * 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. + */ + +import static jdk.test.lib.Asserts.assertEquals; +import static jdk.test.lib.Asserts.assertNotNull; +import static jdk.test.lib.Asserts.assertTrue; +import static jdk.test.lib.Utils.runAndCheckException; +import static jdk.test.lib.security.SecurityUtils.runAndGetLog; + +import java.util.Scanner; +import javax.net.ssl.SSLHandshakeException; + +/* + * @test + * @bug 8372526 + * @summary Bound the memory usage when decompressing CompressedCertificate. + * @modules java.base/sun.security.x509 + * java.base/sun.security.util + * @library /javax/net/ssl/templates + * /test/lib + * @run main/othervm BoundDecompressMemory + */ + +public class BoundDecompressMemory extends CompressedCertMsgCache { + + private static final int MAX_HANDSHAKE_MESSAGE_SIZE = 4096; + + public static void main(String[] args) throws Exception { + System.setProperty("jdk.tls.maxHandshakeMessageSize", + Integer.toString(MAX_HANDSHAKE_MESSAGE_SIZE)); + + String log = runAndGetLog(() -> { + try { + // Use highly compressible subject name for server's certificate. + serverCertSubjectName = "O=Some-Org" + + "A".repeat(MAX_HANDSHAKE_MESSAGE_SIZE) + + ", L=Some-City, ST=Some-State, C=US"; + + setupCertificates(); + serverSslContext = getSSLContext(trustedCert, serverCert, + serverKeys.getPrivate(), "TLSv1.3"); + clientSslContext = getSSLContext(trustedCert, clientCert, + clientKeys.getPrivate(), "TLSv1.3"); + + runAndCheckException(() -> new BoundDecompressMemory().run(), + serverEx -> { + Throwable clientEx = serverEx.getSuppressed()[0]; + assertTrue( + clientEx instanceof SSLHandshakeException); + assertEquals("(bad_certificate) Improper " + + "certificate compression", + clientEx.getMessage()); + } + ); + } catch (Exception _) { + } + }); + + // Check for the specific decompression error message. + assertNotNull(new Scanner(log).findWithinHorizon("The size of the " + + "uncompressed certificate message " + + "exceeds maximum allowed size of " + + MAX_HANDSHAKE_MESSAGE_SIZE + + " bytes; compressed size: \\d+", 0)); + } +} diff --git a/test/jdk/sun/security/ssl/CertificateCompression/ClientCertCompressionExt.java b/test/jdk/sun/security/ssl/CertificateCompression/ClientCertCompressionExt.java new file mode 100644 index 00000000000..2708f1e6872 --- /dev/null +++ b/test/jdk/sun/security/ssl/CertificateCompression/ClientCertCompressionExt.java @@ -0,0 +1,157 @@ +/* + * 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. + */ + +import static jdk.test.lib.Asserts.assertEquals; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/* + * @test + * @bug 8372526 + * @summary Add support for ZLIB TLS Certificate Compression. + * @library /javax/net/ssl/templates + * /test/lib + * + * @run main/othervm ClientCertCompressionExt + * @run main/othervm -Djdk.tls.client.disableExtensions=compress_certificate + * ClientCertCompressionExt + * + */ + +public class ClientCertCompressionExt extends SSLEngineTemplate { + + private static final int CLI_HELLO_MSG = 1; + private static final int COMP_CERT_EXT = 27; + // zlib(1), brotli(2), zstd(3) + private static final List DEFAULT_COMP_ALGS = List.of(1); + + ClientCertCompressionExt() throws Exception { + super(); + } + + public static void main(String[] args) throws Exception { + new ClientCertCompressionExt().runTest( + !System.getProperty("jdk.tls.client.disableExtensions", "") + .contains("compress_certificate")); + } + + private void runTest(boolean shouldProduceExt) throws Exception { + // Produce client_hello + clientEngine.wrap(clientOut, cTOs); + cTOs.flip(); + + checkClientHello(shouldProduceExt); + } + + private void checkClientHello(boolean shouldProduceExt) throws Exception { + List compCertExt = getCompAlgsCliHello( + extractHandshakeMsg(cTOs, CLI_HELLO_MSG, false)); + + if (shouldProduceExt) { + assertEquals(DEFAULT_COMP_ALGS, compCertExt, + "Unexpected compress_certificate extension algorithms: " + + compCertExt); + } else { + assertEquals(compCertExt.size(), 0, + "compress_certificate extension present in ClientHello"); + } + } + + /** + * Parses the ClientHello message and extracts from it a list of + * compression algorithm values. It is assumed that the provided + * ByteBuffer has its position set at the first byte of the ClientHello + * message body (AFTER the handshake header) and contains the entire + * hello message. Upon successful completion of this method the ByteBuffer + * will have its position reset to the initial offset in the buffer. + * If an exception is thrown the position at the time of the exception + * will be preserved. + * + * @param data The ByteBuffer containing the ClientHello bytes. + * @return A List of the compression algorithm values. + */ + private List getCompAlgsCliHello(ByteBuffer data) { + Objects.requireNonNull(data); + data.mark(); + + // Skip over the protocol version and client random + data.position(data.position() + 34); + + // Jump past the session ID (if there is one) + int sessLen = Byte.toUnsignedInt(data.get()); + if (sessLen != 0) { + data.position(data.position() + sessLen); + } + + // Jump past the cipher suites + int csLen = Short.toUnsignedInt(data.getShort()); + if (csLen != 0) { + data.position(data.position() + csLen); + } + + // ...and the compression + int compLen = Byte.toUnsignedInt(data.get()); + if (compLen != 0) { + data.position(data.position() + compLen); + } + + List extSigAlgs = getCompAlgsFromExt(data); + + // We should be at the end of the ClientHello + data.reset(); + return extSigAlgs; + } + + /** + * Gets compression algorithms from the given TLS extension. + * The buffer should be positioned at the start of the extension. + */ + private List getCompAlgsFromExt(ByteBuffer data) { + + List extCompAlgs = new ArrayList<>(); + data.getShort(); // read length + + while (data.hasRemaining()) { + int extType = Short.toUnsignedInt(data.getShort()); + int extLen = Short.toUnsignedInt(data.getShort()); + + if (extType == COMP_CERT_EXT) { + int sigSchemeLen = data.get(); + + for (int ssOff = 0; ssOff < sigSchemeLen; ssOff += 2) { + Integer schemeName = Short.toUnsignedInt(data.getShort()); + extCompAlgs.add(schemeName); + } + } else { + // Not the extension we're looking for. Skip past the + // extension data + data.position(data.position() + extLen); + } + } + + return extCompAlgs; + } +} diff --git a/test/jdk/sun/security/ssl/CertificateCompression/CompressedCertMsg.java b/test/jdk/sun/security/ssl/CertificateCompression/CompressedCertMsg.java new file mode 100644 index 00000000000..265ba3a8fc9 --- /dev/null +++ b/test/jdk/sun/security/ssl/CertificateCompression/CompressedCertMsg.java @@ -0,0 +1,151 @@ +/* + * 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. + */ + +import static jdk.test.lib.Asserts.assertEquals; +import static jdk.test.lib.security.SecurityUtils.countSubstringOccurrences; +import static jdk.test.lib.security.SecurityUtils.runAndGetLog; + +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLServerSocket; + +/* + * @test + * @bug 8372526 + * @summary Add support for ZLIB TLS Certificate Compression + * @library /javax/net/ssl/templates + * /test/lib + * + * @run main/othervm CompressedCertMsg + * @run main/othervm -Djdk.tls.client.disableExtensions=compress_certificate CompressedCertMsg + * @run main/othervm -Djdk.tls.server.disableExtensions=compress_certificate CompressedCertMsg + * @run main/othervm -Djdk.tls.client.disableExtensions=compress_certificate + * -Djdk.tls.server.disableExtensions=compress_certificate + * CompressedCertMsg + */ + +public class CompressedCertMsg extends SSLSocketTemplate { + + private static final String PRODUCED_COMP_CERT_MSG = + """ + Produced CompressedCertificate handshake message ( + "CompressedCertificate": { + "algorithm": "ZLIB", + """; + + private static final String CONSUMING_COMP_CERT_MSG = + """ + Consuming CompressedCertificate handshake message ( + "CompressedCertificate": { + "algorithm": "ZLIB", + """; + + private static final String IGNORE_EXT_MSG = + """ + Ignore unknown or unsupported extension ( + "compress_certificate (27)": { + """; + + private static final String CH_EXT = + """ + }, + "compress_certificate (27)": { + "compression algorithms": [ZLIB] + }, + """; + + private static final String CR_EXT = + """ + "CertificateRequest": { + "certificate_request_context": "", + "extensions": [ + "compress_certificate (27)": { + "compression algorithms": [ZLIB] + """; + + // Server sends CertificateRequest and gets a certificate + // from the client as well as the client from the server. + @Override + protected void configureServerSocket(SSLServerSocket sslServerSocket) { + SSLParameters sslParameters = sslServerSocket.getSSLParameters(); + sslParameters.setNeedClientAuth(true); + sslServerSocket.setSSLParameters(sslParameters); + } + + public static void main(String[] args) throws Exception { + boolean clientSideEnabled = !System.getProperty( + "jdk.tls.client.disableExtensions", "") + .contains("compress_certificate"); + boolean serverSideEnabled = !System.getProperty( + "jdk.tls.server.disableExtensions", "") + .contains("compress_certificate"); + + // Complete 1 handshake. + String log = runAndGetLog(() -> { + try { + new CompressedCertMsg().run(); + } catch (Exception _) { + } + }); + + // To make the test pass on Windows. + log = log.replace("\r\n", "\n"); + + if (clientSideEnabled && serverSideEnabled) { + // Make sure CompressedCertificate message is produced and consumed + // twice - by the server and by the client. + assertEquals(2, countSubstringOccurrences(log, + PRODUCED_COMP_CERT_MSG)); + assertEquals(2, countSubstringOccurrences(log, + CONSUMING_COMP_CERT_MSG)); + // Extensions are produced and consumed, so they appear in the + // log twice. + assertEquals(2, countSubstringOccurrences(log, CH_EXT)); + assertEquals(2, countSubstringOccurrences(log, CR_EXT)); + assertEquals(0, countSubstringOccurrences(log, IGNORE_EXT_MSG)); + } else if (clientSideEnabled) { + assertEquals(0, countSubstringOccurrences(log, + PRODUCED_COMP_CERT_MSG)); + assertEquals(0, countSubstringOccurrences(log, + CONSUMING_COMP_CERT_MSG)); + assertEquals(2, countSubstringOccurrences(log, CH_EXT)); + assertEquals(0, countSubstringOccurrences(log, CR_EXT)); + assertEquals(1, countSubstringOccurrences(log, IGNORE_EXT_MSG)); + } else if (serverSideEnabled) { + assertEquals(0, countSubstringOccurrences(log, + PRODUCED_COMP_CERT_MSG)); + assertEquals(0, countSubstringOccurrences(log, + CONSUMING_COMP_CERT_MSG)); + assertEquals(0, countSubstringOccurrences(log, CH_EXT)); + assertEquals(2, countSubstringOccurrences(log, CR_EXT)); + assertEquals(1, countSubstringOccurrences(log, IGNORE_EXT_MSG)); + } else { + assertEquals(0, countSubstringOccurrences(log, + PRODUCED_COMP_CERT_MSG)); + assertEquals(0, countSubstringOccurrences(log, + CONSUMING_COMP_CERT_MSG)); + assertEquals(0, countSubstringOccurrences(log, CH_EXT)); + assertEquals(0, countSubstringOccurrences(log, CR_EXT)); + assertEquals(0, countSubstringOccurrences(log, IGNORE_EXT_MSG)); + } + } +} diff --git a/test/jdk/sun/security/ssl/CertificateCompression/CompressedCertMsgCache.java b/test/jdk/sun/security/ssl/CertificateCompression/CompressedCertMsgCache.java new file mode 100644 index 00000000000..42e9701c6d0 --- /dev/null +++ b/test/jdk/sun/security/ssl/CertificateCompression/CompressedCertMsgCache.java @@ -0,0 +1,197 @@ +/* + * 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. + */ + +import static jdk.test.lib.Asserts.assertEquals; +import static jdk.test.lib.security.SecurityUtils.countSubstringOccurrences; +import static jdk.test.lib.security.SecurityUtils.runAndGetLog; + +import java.io.IOException; +import java.math.BigInteger; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.SecureRandom; +import java.security.cert.Certificate; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Date; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManagerFactory; +import jdk.test.lib.security.CertificateBuilder; + +/* + * @test + * @bug 8372526 + * @summary Check CompressedCertificate message cache. + * @modules java.base/sun.security.x509 + * java.base/sun.security.util + * @library /javax/net/ssl/templates + * /test/lib + * @run main/othervm CompressedCertMsgCache + */ + +public class CompressedCertMsgCache extends SSLSocketTemplate { + + protected static String clientCertSubjectName = + "CN=localhost, OU=SSL-Client, ST=Some-State, C=US"; + protected static String serverCertSubjectName = + "O=Some-Org, L=Some-City, ST=Some-State, C=US"; + + protected static X509Certificate trustedCert; + protected static X509Certificate serverCert; + protected static X509Certificate clientCert; + protected static KeyPair serverKeys; + protected static KeyPair clientKeys; + protected static SSLContext serverSslContext; + protected static SSLContext clientSslContext; + + public static void main(String[] args) throws Exception { + + // Complete 3 handshakes with the same SSLContext. + String log = runAndGetLog(() -> { + try { + setupCertificates(); + serverSslContext = getSSLContext(trustedCert, serverCert, + serverKeys.getPrivate(), "TLSv1.3"); + clientSslContext = getSSLContext(trustedCert, clientCert, + clientKeys.getPrivate(), "TLSv1.3"); + + new CompressedCertMsgCache().run(); + new CompressedCertMsgCache().run(); + new CompressedCertMsgCache().run(); + } catch (Exception _) { + } + }); + + // The same CompressedCertificate message must be cached only once. + assertEquals(1, countSubstringOccurrences(log, + "Caching CompressedCertificate message")); + + // Make sure CompressedCertificate message is produced 3 times. + assertEquals(3, countSubstringOccurrences(log, + "Produced CompressedCertificate handshake message")); + + // Make sure CompressedCertificate message is consumed 3 times. + assertEquals(3, countSubstringOccurrences(log, + "Consuming CompressedCertificate handshake message")); + } + + @Override + public SSLContext createServerSSLContext() throws Exception { + return serverSslContext; + } + + @Override + public SSLContext createClientSSLContext() throws Exception { + return clientSslContext; + } + + protected static SSLContext getSSLContext( + X509Certificate trustedCertificate, X509Certificate keyCertificate, + PrivateKey privateKey, String protocol) + throws Exception { + + // create a key store + KeyStore ks = KeyStore.getInstance("PKCS12"); + ks.load(null, null); + + // import the trusted cert + ks.setCertificateEntry("TLS Signer", trustedCertificate); + + // generate certificate chain + Certificate[] chain = new Certificate[2]; + chain[0] = keyCertificate; + chain[1] = trustedCertificate; + + // import the key entry. + final char[] passphrase = "passphrase".toCharArray(); + ks.setKeyEntry("Whatever", privateKey, passphrase, chain); + + TrustManagerFactory tmf = TrustManagerFactory.getInstance("PKIX"); + tmf.init(ks); + + // create SSL context + SSLContext ctx = SSLContext.getInstance(protocol); + + KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); + kmf.init(ks, passphrase); + + ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null); + + return ctx; + } + + // Certificate-building helper methods. + + protected static void setupCertificates() throws Exception { + KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC"); + KeyPair caKeys = kpg.generateKeyPair(); + serverKeys = kpg.generateKeyPair(); + clientKeys = kpg.generateKeyPair(); + + trustedCert = createTrustedCert(caKeys); + + serverCert = customCertificateBuilder( + serverCertSubjectName, + serverKeys.getPublic(), caKeys.getPublic()) + .addBasicConstraintsExt(false, false, -1) + .build(trustedCert, caKeys.getPrivate(), "SHA256withECDSA"); + + clientCert = customCertificateBuilder( + clientCertSubjectName, + clientKeys.getPublic(), caKeys.getPublic()) + .addBasicConstraintsExt(false, false, -1) + .build(trustedCert, caKeys.getPrivate(), "SHA256withECDSA"); + } + + private static X509Certificate createTrustedCert(KeyPair caKeys) + throws Exception { + return customCertificateBuilder( + "O=CA-Org, L=Some-City, ST=Some-State, C=US", + caKeys.getPublic(), caKeys.getPublic()) + .addBasicConstraintsExt(true, true, 1) + .build(null, caKeys.getPrivate(), "SHA256withECDSA"); + } + + private static CertificateBuilder customCertificateBuilder( + String subjectName, PublicKey publicKey, PublicKey caKey) + throws CertificateException, IOException { + return new CertificateBuilder() + .setSubjectName(subjectName) + .setPublicKey(publicKey) + .setNotBefore( + Date.from(Instant.now().minus(1, ChronoUnit.HOURS))) + .setNotAfter(Date.from(Instant.now().plus(1, ChronoUnit.HOURS))) + .setSerialNumber(BigInteger.valueOf( + new SecureRandom().nextLong(1000000) + 1)) + .addSubjectKeyIdExt(publicKey) + .addAuthorityKeyIdExt(caKey) + .addKeyUsageExt(new boolean[]{ + true, true, true, true, true, true, true}); + } +} diff --git a/test/lib/jdk/test/lib/security/SecurityUtils.java b/test/lib/jdk/test/lib/security/SecurityUtils.java index be6ff1cc0e3..78dc2aa2729 100644 --- a/test/lib/jdk/test/lib/security/SecurityUtils.java +++ b/test/lib/jdk/test/lib/security/SecurityUtils.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 @@ -23,8 +23,10 @@ package jdk.test.lib.security; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; +import java.io.PrintStream; import java.nio.ByteBuffer; import java.security.KeyStore; import java.security.Security; @@ -219,5 +221,40 @@ public final class SecurityUtils { return ((m.get() & 0xFF) << 8) | (m.get() & 0xFF); } + // Helper method to run and get log. + public static String runAndGetLog(Runnable runnable) { + System.setProperty("javax.net.debug", "ssl"); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintStream err = new PrintStream(baos); + PrintStream origErr = System.err; + System.setErr(err); + + runnable.run(); + err.close(); + + // Save the log output and then print it as usual. + String log = baos.toString(); + System.setErr(origErr); + System.err.print(log); + return log; + } + + // Helper method to find log messages. + public static int countSubstringOccurrences(String str, String sub) { + if (str == null || sub == null || sub.isEmpty()) { + return 0; + } + + int count = 0; + int lastIndex = 0; + + while ((lastIndex = str.indexOf(sub, lastIndex)) != -1) { + count++; + lastIndex += sub.length(); + } + + return count; + } + private SecurityUtils() {} } From 5ddc36786a0797f346610819bb92353ff63abe7a Mon Sep 17 00:00:00 2001 From: Kelvin Nilsen Date: Fri, 17 Apr 2026 13:28:56 +0000 Subject: [PATCH 098/108] 8382095: GenShen: Do not clamp young reserve unnecessarily Reviewed-by: wkemper, shade --- .../share/gc/shenandoah/shenandoahFreeSet.cpp | 7 +++++-- .../gc/shenandoah/shenandoahGeneration.cpp | 9 +------- .../shenandoah/shenandoahGenerationalHeap.cpp | 21 +++++++++++++------ 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahFreeSet.cpp b/src/hotspot/share/gc/shenandoah/shenandoahFreeSet.cpp index 592c5bffa5a..1807383123b 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahFreeSet.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahFreeSet.cpp @@ -2678,8 +2678,11 @@ void ShenandoahFreeSet::reduce_young_reserve(size_t adjusted_young_reserve, size * 1. Memory currently available within old and young * 2. Trashed regions currently residing in young and old, which will become available momentarily * 3. The value of old_generation->get_region_balance() which represents the number of regions that we plan - * to transfer from old generation to young generation. Prior to each invocation of compute_young_and_old_reserves(), - * this value should computed by ShenandoahGenerationalHeap::compute_old_generation_balance(). + * to transfer from old generation to young generation. At the end of each GC cycle, we reset region_balance + * to zero. As we prepare to rebuild free set at the end of update-refs, we call + * ShenandoahGenerationalHeap::compute_old_generation_balance() to compute a new value of region_balance. + * This allows us to expand or shrink the size of the Old Collector reserves based on anticipated needs of + * the next GC cycle. */ void ShenandoahFreeSet::compute_young_and_old_reserves(size_t young_trashed_regions, size_t old_trashed_regions, size_t& young_reserve_result, size_t& old_reserve_result) const { diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp index 7d082e4a8b0..5b26ee67653 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGeneration.cpp @@ -310,16 +310,9 @@ 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()); - - // We are preparing for evacuation. + // At start of evacation, we do NOT compute_old_generation_balance() 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(); - 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->finish_rebuild(young_trashed_regions, old_trashed_regions, num_old); } } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp index a51449e91f4..1694121b955 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp @@ -406,11 +406,14 @@ template oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, uint from_region_age); template oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, uint from_region_age); +// Call this function at the end of a GC cycle in order to establish proper sizes of young and old reserves, +// setting the old-generation balance so that GC can perform the anticipated evacuations. +// // 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 // 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. +// the maximum we're able to transfer from young to old. The mutator_xfer_limit constrains the transfer +// of memory from young to old. It does not limit young reserves. void ShenandoahGenerationalHeap::compute_old_generation_balance(size_t mutator_xfer_limit, size_t old_trashed_regions, size_t young_trashed_regions) { shenandoah_assert_heaplocked(); @@ -462,11 +465,17 @@ void ShenandoahGenerationalHeap::compute_old_generation_balance(size_t mutator_x bound_on_old_reserve)); assert(mutator_xfer_limit <= young_available, "Cannot transfer (%zu) memory that is not available (%zu)", mutator_xfer_limit, young_available); - // Young reserves are to be taken out of the mutator_xfer_limit. - if (young_reserve > mutator_xfer_limit) { - young_reserve = mutator_xfer_limit; + + if (young_reserve > young_available) { + young_reserve = young_available; + } + // We allow young_reserve to exceed mutator_xfer_limit. Essentially, this means the GC is already behind the pace + // of mutator allocations, and we'll need to trigger the next GC as soon as possible. + if (mutator_xfer_limit > young_reserve) { + mutator_xfer_limit -= young_reserve; + } else { + mutator_xfer_limit = 0; } - mutator_xfer_limit -= young_reserve; // Decide how much old space we should reserve for a mixed collection size_t proposed_reserve_for_mixed = 0; From b418cbcdd4b8b200f5fe801469da1e3970eed634 Mon Sep 17 00:00:00 2001 From: Viktor Klang Date: Fri, 17 Apr 2026 14:03:47 +0000 Subject: [PATCH 099/108] 8381418: Clarifying the identity Parameter in the Java Stream.reduce() Method Documentation Reviewed-by: liach, alanb --- src/java.base/share/classes/java/util/stream/Stream.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/java.base/share/classes/java/util/stream/Stream.java b/src/java.base/share/classes/java/util/stream/Stream.java index 1dd13133fe1..645f4f033b7 100644 --- a/src/java.base/share/classes/java/util/stream/Stream.java +++ b/src/java.base/share/classes/java/util/stream/Stream.java @@ -1002,8 +1002,8 @@ public interface Stream extends BaseStream> { /** * Performs a reduction on the - * elements of this stream, using the provided identity, accumulation and - * combining functions. This is equivalent to: + * elements of this stream using the provided identity value, accumulation + * function, and combining function. This is equivalent to: *
    {@code
          *     U result = identity;
          *     for (T element : this stream)
    
    From c3c59413be1a05e5b2362bb84d5fd50a8cbceff0 Mon Sep 17 00:00:00 2001
    From: Quan Anh Mai 
    Date: Fri, 17 Apr 2026 14:09:07 +0000
    Subject: [PATCH 100/108] 8378966: C2: PhaseIdealLoop::pin_nodes_dependent_on
     must not be called with nullptr
    
    Reviewed-by: chagedorn, aseoane
    ---
     src/hotspot/share/opto/loopopts.cpp | 14 ++++++++++++--
     src/hotspot/share/opto/split_if.cpp |  3 +++
     2 files changed, 15 insertions(+), 2 deletions(-)
    
    diff --git a/src/hotspot/share/opto/loopopts.cpp b/src/hotspot/share/opto/loopopts.cpp
    index 11b23d7c365..ccd53129a87 100644
    --- a/src/hotspot/share/opto/loopopts.cpp
    +++ b/src/hotspot/share/opto/loopopts.cpp
    @@ -1307,7 +1307,14 @@ Node* PhaseIdealLoop::place_outside_loop(Node* useblock, IdealLoopTree* loop) co
     
     
     bool PhaseIdealLoop::identical_backtoback_ifs(Node *n) {
    -  if (!n->is_If() || n->is_BaseCountedLoopEnd()) {
    +  if (!n->is_If()) {
    +    return false;
    +  }
    +  if (n->outcnt() != n->as_If()->required_outcnt()) {
    +    assert(false, "malformed IfNode with %d outputs", n->outcnt());
    +    return false;
    +  }
    +  if (n->is_BaseCountedLoopEnd()) {
         return false;
       }
       if (!n->in(0)->is_Region()) {
    @@ -1433,7 +1440,10 @@ void PhaseIdealLoop::split_if_with_blocks_post(Node *n) {
     
         // Check some safety conditions
         if (iff->is_If()) {        // Classic split-if?
    -      if (iff->in(0) != n_ctrl) {
    +      if (iff->outcnt() != iff->as_If()->required_outcnt()) {
    +        assert(false, "malformed IfNode with %d outputs", iff->outcnt());
    +        return;
    +      } else if (iff->in(0) != n_ctrl) {
             return; // Compare must be in same blk as if
           }
         } else if (iff->is_CMove()) { // Trying to split-up a CMOVE
    diff --git a/src/hotspot/share/opto/split_if.cpp b/src/hotspot/share/opto/split_if.cpp
    index c1303f0d0db..e5f8043ae19 100644
    --- a/src/hotspot/share/opto/split_if.cpp
    +++ b/src/hotspot/share/opto/split_if.cpp
    @@ -704,6 +704,9 @@ void PhaseIdealLoop::do_split_if(Node* iff, RegionNode** new_false_region, Regio
           new_true = ifpx;
         }
       }
    +  assert(new_false != nullptr, "iff is malformed");
    +  assert(new_true != nullptr, "iff is malformed");
    +
       _igvn.remove_dead_node(new_iff, PhaseIterGVN::NodeOrigin::Speculative);
       // Lazy replace IDOM info with the region's dominator
       replace_node_and_forward_ctrl(iff, region_dom);
    
    From 26ccc2eaa491531faaecc6214196f3ca082a7e06 Mon Sep 17 00:00:00 2001
    From: Kelvin Nilsen 
    Date: Fri, 17 Apr 2026 15:30:34 +0000
    Subject: [PATCH 101/108] 8380651: [ubsan] gc/logging/TestGCId.java triggers
     runtime error: division by zero in shenandoahAdaptiveHeuristics
    
    Reviewed-by: wkemper, shade, xpeng
    ---
     .../shenandoahAdaptiveHeuristics.cpp          | 34 ++++++++++++-------
     .../gc/shenandoah/shenandoahControlThread.cpp |  3 +-
     .../shenandoahGenerationalControlThread.cpp   |  5 ++-
     .../share/gc/shenandoah/shenandoahHeap.cpp    |  9 +++--
     .../share/gc/shenandoah/shenandoahHeap.hpp    |  2 +-
     .../share/gc/shenandoah/shenandoahUtils.cpp   |  7 ++--
     .../share/gc/shenandoah/shenandoahUtils.hpp   |  3 +-
     7 files changed, 38 insertions(+), 25 deletions(-)
    
    diff --git a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahAdaptiveHeuristics.cpp b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahAdaptiveHeuristics.cpp
    index 6b327d7e4af..c595d1fd9cd 100644
    --- a/src/hotspot/share/gc/shenandoah/heuristics/shenandoahAdaptiveHeuristics.cpp
    +++ b/src/hotspot/share/gc/shenandoah/heuristics/shenandoahAdaptiveHeuristics.cpp
    @@ -209,14 +209,14 @@ void ShenandoahAdaptiveHeuristics::choose_collection_set_from_regiondata(Shenand
       }
     }
     
    -void ShenandoahAdaptiveHeuristics::add_degenerated_gc_time(double timestamp, double gc_time) {
    +void ShenandoahAdaptiveHeuristics::add_degenerated_gc_time(double time_at_start, double gc_time) {
       // Conservatively add sample into linear model If this time is above the predicted concurrent gc time
    -  if (predict_gc_time(timestamp) < gc_time) {
    -    add_gc_time(timestamp, gc_time);
    +  if (predict_gc_time(time_at_start) < gc_time) {
    +    add_gc_time(time_at_start, gc_time);
       }
     }
     
    -void ShenandoahAdaptiveHeuristics::add_gc_time(double timestamp, double gc_time) {
    +void ShenandoahAdaptiveHeuristics::add_gc_time(double time_at_start, double gc_time) {
       // Update best-fit linear predictor of GC time
       uint index = (_gc_time_first_sample_index + _gc_time_num_samples) % GC_TIME_SAMPLE_SIZE;
       if (_gc_time_num_samples == GC_TIME_SAMPLE_SIZE) {
    @@ -225,10 +225,10 @@ void ShenandoahAdaptiveHeuristics::add_gc_time(double timestamp, double gc_time)
         _gc_time_sum_of_xy -= _gc_time_xy[index];
         _gc_time_sum_of_xx -= _gc_time_xx[index];
       }
    -  _gc_time_timestamps[index] = timestamp;
    +  _gc_time_timestamps[index] = time_at_start;
       _gc_time_samples[index] = gc_time;
    -  _gc_time_xy[index] = timestamp * gc_time;
    -  _gc_time_xx[index] = timestamp * timestamp;
    +  _gc_time_xy[index] = time_at_start * gc_time;
    +  _gc_time_xx[index] = time_at_start * time_at_start;
     
       _gc_time_sum_of_timestamps += _gc_time_timestamps[index];
       _gc_time_sum_of_samples += _gc_time_samples[index];
    @@ -247,18 +247,26 @@ void ShenandoahAdaptiveHeuristics::add_gc_time(double timestamp, double gc_time)
         _gc_time_b = gc_time;
         _gc_time_sd = 0.0;
       } else if (_gc_time_num_samples == 2) {
    -    // Two points define a line
    -    double delta_y = gc_time - _gc_time_samples[_gc_time_first_sample_index];
    -    double delta_x = timestamp - _gc_time_timestamps[_gc_time_first_sample_index];
    -    _gc_time_m = delta_y / delta_x;
     
    +    assert(time_at_start > _gc_time_timestamps[_gc_time_first_sample_index],
    +           "Two GC cycles cannot finish at same time: %.6f vs %.6f, with GC times %.6f and %.6f", time_at_start,
    +           _gc_time_timestamps[_gc_time_first_sample_index], gc_time, _gc_time_samples[_gc_time_first_sample_index]);
    +
    +    // Two points define a line
    +    double delta_x = time_at_start - _gc_time_timestamps[_gc_time_first_sample_index];
    +    double delta_y = gc_time - _gc_time_samples[_gc_time_first_sample_index];
    +    _gc_time_m = delta_y / delta_x;
         // y = mx + b
         // so b = y0 - mx0
    -    _gc_time_b = gc_time - _gc_time_m * timestamp;
    +    _gc_time_b = gc_time - _gc_time_m * time_at_start;
         _gc_time_sd = 0.0;
       } else {
    +    // Since timestamps are monotonically increasing, denominator does not equal zero.
    +    double denominator = _gc_time_num_samples * _gc_time_sum_of_xx - _gc_time_sum_of_timestamps * _gc_time_sum_of_timestamps;
    +    assert(denominator != 0.0, "Invariant: samples: %u, sum_of_xx: %.6f, sum_of_timestamps: %.6f",
    +           _gc_time_num_samples, _gc_time_sum_of_xx, _gc_time_sum_of_timestamps);
         _gc_time_m = ((_gc_time_num_samples * _gc_time_sum_of_xy - _gc_time_sum_of_timestamps * _gc_time_sum_of_samples) /
    -                  (_gc_time_num_samples * _gc_time_sum_of_xx - _gc_time_sum_of_timestamps * _gc_time_sum_of_timestamps));
    +                  denominator);
         _gc_time_b = (_gc_time_sum_of_samples - _gc_time_m * _gc_time_sum_of_timestamps) / _gc_time_num_samples;
         double sum_of_squared_deviations = 0.0;
         for (size_t i = 0; i < _gc_time_num_samples; i++) {
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp
    index 4c6e82c86a5..6175f15676c 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp
    @@ -346,7 +346,8 @@ void ShenandoahControlThread::service_stw_full_cycle(GCCause::Cause cause) {
     void ShenandoahControlThread::service_stw_degenerated_cycle(GCCause::Cause cause, ShenandoahGC::ShenandoahDegenPoint point) {
       assert (point != ShenandoahGC::_degenerated_unset, "Degenerated point should be set");
       ShenandoahHeap* const heap = ShenandoahHeap::heap();
    -  ShenandoahGCSession session(cause, heap->global_generation());
    +  ShenandoahGCSession session(cause, heap->global_generation(), true,
    +                              point == ShenandoahGC::ShenandoahDegenPoint::_degenerated_outside_cycle);
     
       heap->increment_total_collections(false);
     
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp
    index aa6a4a9bab2..bbad82de1dc 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp
    @@ -628,11 +628,10 @@ void ShenandoahGenerationalControlThread::service_stw_full_cycle(GCCause::Cause
     
     void ShenandoahGenerationalControlThread::service_stw_degenerated_cycle(const ShenandoahGCRequest& request) {
       assert(_degen_point != ShenandoahGC::_degenerated_unset, "Degenerated point should be set");
    -  request.generation->heuristics()->record_degenerated_cycle_start(ShenandoahGC::ShenandoahDegenPoint::_degenerated_outside_cycle
    -                                                                  == _degen_point);
       _heap->increment_total_collections(false);
     
    -  ShenandoahGCSession session(request.cause, request.generation);
    +  ShenandoahGCSession session(request.cause, request.generation, true,
    +                              _degen_point == ShenandoahGC::ShenandoahDegenPoint::_degenerated_outside_cycle);
       ShenandoahDegenGC gc(_degen_point, request.generation);
       gc.collect(request.cause);
       _degen_point = ShenandoahGC::_degenerated_unset;
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp
    index 75d3ade4e49..4b01ea1cd52 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp
    @@ -1650,7 +1650,8 @@ void ShenandoahHeap::set_active_generation(ShenandoahGeneration* generation) {
       _active_generation = generation;
     }
     
    -void ShenandoahHeap::on_cycle_start(GCCause::Cause cause, ShenandoahGeneration* generation) {
    +void ShenandoahHeap::on_cycle_start(GCCause::Cause cause, ShenandoahGeneration* generation,
    +                                    bool is_degenerated, bool is_out_of_cycle) {
       shenandoah_policy()->record_collection_cause(cause);
     
       const GCCause::Cause current = gc_cause();
    @@ -1659,7 +1660,11 @@ void ShenandoahHeap::on_cycle_start(GCCause::Cause cause, ShenandoahGeneration*
     
       set_gc_cause(cause);
     
    -  generation->heuristics()->record_cycle_start();
    +  if (is_degenerated) {
    +    generation->heuristics()->record_degenerated_cycle_start(is_out_of_cycle);
    +  } else {
    +    generation->heuristics()->record_cycle_start();
    +  }
     }
     
     void ShenandoahHeap::on_cycle_end(ShenandoahGeneration* generation) {
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp
    index ab7dd00b774..bed26a093d0 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp
    @@ -564,7 +564,7 @@ public:
         return _evac_tracker;
       }
     
    -  void on_cycle_start(GCCause::Cause cause, ShenandoahGeneration* generation);
    +  void on_cycle_start(GCCause::Cause cause, ShenandoahGeneration* generation, bool is_degenerated, bool is_out_of_cycle);
       void on_cycle_end(ShenandoahGeneration* generation);
     
       ShenandoahVerifier*        verifier();
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahUtils.cpp b/src/hotspot/share/gc/shenandoah/shenandoahUtils.cpp
    index dea47fcbf4f..5af2e274833 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahUtils.cpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahUtils.cpp
    @@ -56,15 +56,14 @@ const char* ShenandoahGCSession::cycle_end_message(ShenandoahGenerationType type
       }
     }
     
    -ShenandoahGCSession::ShenandoahGCSession(GCCause::Cause cause, ShenandoahGeneration* generation) :
    +ShenandoahGCSession::ShenandoahGCSession(GCCause::Cause cause, ShenandoahGeneration* generation,
    +                                         bool is_degenerated, bool is_out_of_cycle) :
       _heap(ShenandoahHeap::heap()),
       _generation(generation),
       _timer(_heap->gc_timer()),
       _tracer(_heap->tracer()) {
       assert(!ShenandoahGCPhase::is_current_phase_valid(), "No current GC phase");
    -
    -  _heap->on_cycle_start(cause, _generation);
    -
    +  _heap->on_cycle_start(cause, _generation, is_degenerated, is_out_of_cycle);
       _timer->register_gc_start();
       _tracer->report_gc_start(cause, _timer->gc_start());
       _heap->trace_heap_before_gc(_tracer);
    diff --git a/src/hotspot/share/gc/shenandoah/shenandoahUtils.hpp b/src/hotspot/share/gc/shenandoah/shenandoahUtils.hpp
    index 0169941c3d9..1ed6e43e3e1 100644
    --- a/src/hotspot/share/gc/shenandoah/shenandoahUtils.hpp
    +++ b/src/hotspot/share/gc/shenandoah/shenandoahUtils.hpp
    @@ -70,7 +70,8 @@ private:
     
       static const char* cycle_end_message(ShenandoahGenerationType type);
     public:
    -  ShenandoahGCSession(GCCause::Cause cause, ShenandoahGeneration* generation);
    +  ShenandoahGCSession(GCCause::Cause cause, ShenandoahGeneration* generation,
    +                      bool is_degenerated = false, bool is_out_of_cycle = false);
       ~ShenandoahGCSession();
     };
     
    
    From 7f90efd7d4d97c9d556c0089c27ad3a3c0dfca6d Mon Sep 17 00:00:00 2001
    From: Coleen Phillimore 
    Date: Fri, 17 Apr 2026 16:12:21 +0000
    Subject: [PATCH 102/108] 8381145: Missing ResourceMark in Mutex::print_on()
    
    Reviewed-by: stefank, pchilanomate, dholmes
    ---
     src/hotspot/share/runtime/mutex.cpp       | 28 +++++++++++++++--------
     src/hotspot/share/runtime/mutex.hpp       |  6 +++--
     test/hotspot/gtest/runtime/test_mutex.cpp |  5 ++--
     3 files changed, 26 insertions(+), 13 deletions(-)
    
    diff --git a/src/hotspot/share/runtime/mutex.cpp b/src/hotspot/share/runtime/mutex.cpp
    index c455b5f36a2..8e0c1d10e5c 100644
    --- a/src/hotspot/share/runtime/mutex.cpp
    +++ b/src/hotspot/share/runtime/mutex.cpp
    @@ -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
    @@ -293,7 +293,8 @@ Mutex::Mutex(Rank rank, const char * name, bool allow_vm_block) : _owner(nullptr
       _rank            = rank;
       _skip_rank_check = false;
     
    -  assert(_rank >= static_cast(0) && _rank <= safepoint, "Bad lock rank %s: %s", rank_name(), name);
    +  assert(_rank >= static_cast(0) && _rank <= safepoint, "Bad lock rank %d outside [0, %d]: %s",
    +         static_cast(rank), static_cast(safepoint), name);
     
       // The allow_vm_block also includes allowing other non-Java threads to block or
       // allowing Java threads to block in native.
    @@ -324,25 +325,33 @@ static const char* _rank_names[] = { "event", "service", "stackwatermark", "tty"
     
     static const int _num_ranks = 7;
     
    -static const char* rank_name_internal(Mutex::Rank r) {
    +static void print_rank_name_internal(outputStream* st, Mutex::Rank r) {
       // Find closest rank and print out the name
    -  stringStream st;
       for (int i = 0; i < _num_ranks; i++) {
         if (r == _ranks[i]) {
    -      return _rank_names[i];
    +      st->print("%s", _rank_names[i]);
         } else if (r  > _ranks[i] && (i < _num_ranks-1 && r < _ranks[i+1])) {
           int delta = static_cast(_ranks[i+1]) - static_cast(r);
    -      st.print("%s-%d", _rank_names[i+1], delta);
    -      return st.as_string();
    +      st->print("%s-%d", _rank_names[i+1], delta);
         }
       }
    -  return "fail";
    +}
    +
    +// Requires caller to have ResourceMark.
    +static const char* rank_name_internal(Mutex::Rank r) {
    +  stringStream st;
    +  print_rank_name_internal(&st, r);
    +  return st.as_string();
     }
     
     const char* Mutex::rank_name() const {
       return rank_name_internal(_rank);
     }
     
    +// Does not require caller to have ResourceMark.
    +void Mutex::print_rank_name(outputStream* st) const {
    +  print_rank_name_internal(st, _rank);
    +}
     
     void Mutex::assert_no_overlap(Rank orig, Rank adjusted, int adjust) {
       int i = 0;
    @@ -364,7 +373,8 @@ void Mutex::print_on(outputStream* st) const {
       if (_allow_vm_block) {
         st->print("%s", " allow_vm_block");
       }
    -  DEBUG_ONLY(st->print(" %s", rank_name()));
    +  st->print(" ");
    +  DEBUG_ONLY(print_rank_name(st));
       st->cr();
     }
     
    diff --git a/src/hotspot/share/runtime/mutex.hpp b/src/hotspot/share/runtime/mutex.hpp
    index cf2b222d2da..4d30a320cbf 100644
    --- a/src/hotspot/share/runtime/mutex.hpp
    +++ b/src/hotspot/share/runtime/mutex.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
    @@ -130,9 +130,11 @@ class Mutex : public CHeapObj {
         return _skip_rank_check;
       }
     
    +  const char* rank_name() const;
    +  void print_rank_name(outputStream* st) const;
    +
      public:
       Rank   rank() const          { return _rank; }
    -  const char*  rank_name() const;
       Mutex* next()  const         { return _next; }
     #endif // ASSERT
     
    diff --git a/test/hotspot/gtest/runtime/test_mutex.cpp b/test/hotspot/gtest/runtime/test_mutex.cpp
    index 2ed7b1b3a34..64b7a5207d7 100644
    --- a/test/hotspot/gtest/runtime/test_mutex.cpp
    +++ b/test/hotspot/gtest/runtime/test_mutex.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
    @@ -323,11 +323,12 @@ TEST_VM_ASSERT_MSG(MutexSafepoint, possible_safepoint_lock,
         ".* Possible safepoint reached by thread that does not allow it") {
       JavaThread* thread = JavaThread::current();
       ThreadInVMfromNative in_native(thread);
    -  MutexLocker ml(new Mutex(Mutex::nosafepoint, "SpecialTest_lock"),
    +  MutexLocker ml(new Mutex(Mutex::nosafepoint-2, "SpecialTest_lock"),
                        Mutex::_no_safepoint_check_flag);
       thread->print_thread_state_on(tty);
       // If the lock above succeeds, try to safepoint to test the NSV implied with this nosafepoint lock.
       ThreadBlockInVM tbivm(thread);
       thread->print_thread_state_on(tty);
     }
    +
     #endif // ASSERT
    
    From 0a8dbed7ef4d12b09f223e81a9a658ddaeca931c Mon Sep 17 00:00:00 2001
    From: Artur Barashev 
    Date: Fri, 17 Apr 2026 17:02:59 +0000
    Subject: [PATCH 103/108] 8370656: Re-examine use of InterruptedIOException in
     sun.security.ssl code
    
    Reviewed-by: weijun, alanb
    ---
     .../sun/security/ssl/SSLSocketImpl.java       | 67 +++++++------------
     .../security/ssl/SSLSocketInputRecord.java    |  6 +-
     .../sun/security/ssl/SSLTransport.java        |  6 +-
     .../ssl/SSLSession/TestEnabledProtocols.java  | 10 ++-
     4 files changed, 35 insertions(+), 54 deletions(-)
    
    diff --git a/src/java.base/share/classes/sun/security/ssl/SSLSocketImpl.java b/src/java.base/share/classes/sun/security/ssl/SSLSocketImpl.java
    index f603cc22949..cef2f43526a 100644
    --- a/src/java.base/share/classes/sun/security/ssl/SSLSocketImpl.java
    +++ b/src/java.base/share/classes/sun/security/ssl/SSLSocketImpl.java
    @@ -1,5 +1,5 @@
     /*
    - * Copyright (c) 1996, 2025, 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
    @@ -28,13 +28,13 @@ package sun.security.ssl;
     import java.io.EOFException;
     import java.io.IOException;
     import java.io.InputStream;
    -import java.io.InterruptedIOException;
     import java.io.OutputStream;
     import java.net.InetAddress;
     import java.net.InetSocketAddress;
     import java.net.Socket;
     import java.net.SocketAddress;
     import java.net.SocketException;
    +import java.net.SocketTimeoutException;
     import java.nio.ByteBuffer;
     import java.util.List;
     import java.util.concurrent.TimeUnit;
    @@ -77,10 +77,10 @@ public final class SSLSocketImpl
         /**
          * ERROR HANDLING GUIDELINES
          * (which exceptions to throw and catch and which not to throw and catch)
    -     *
    +     * 

    * - if there is an IOException (SocketException) when accessing the * underlying Socket, pass it through - * + *

    * - do not throw IOExceptions, throw SSLExceptions (or a subclass) */ @@ -454,12 +454,12 @@ public final class SSLSocketImpl if (!conContext.isNegotiated) { readHandshakeRecord(); } - } catch (InterruptedIOException iioe) { + } catch (SocketTimeoutException e) { if(resumable){ - handleException(iioe); + handleException(e); } else{ throw conContext.fatal(Alert.HANDSHAKE_FAILURE, - "Couldn't kickstart handshaking", iioe); + "Couldn't kickstart handshaking", e); } } catch (SocketException se) { handleException(se); @@ -1427,7 +1427,7 @@ public final class SSLSocketImpl return 0; } } catch (SSLException | - InterruptedIOException | SocketException se) { + SocketTimeoutException | SocketException se) { // Don't change exception in case of timeouts or interrupts // or SocketException. throw se; @@ -1486,7 +1486,7 @@ public final class SSLSocketImpl return buffer; } } catch (SSLException | - InterruptedIOException | SocketException se) { + SocketTimeoutException | SocketException se) { // Don't change exception in case of timeouts or interrupts // or SocketException. throw se; @@ -1677,40 +1677,23 @@ public final class SSLSocketImpl SSLLogger.warning("handling exception", cause); } - // Don't close the Socket in case of timeouts or interrupts. - if (cause instanceof InterruptedIOException) { - throw (IOException)cause; - } - - // need to perform error shutdown - boolean isSSLException = (cause instanceof SSLException); - Alert alert; - if (isSSLException) { - if (cause instanceof SSLHandshakeException) { - alert = Alert.HANDSHAKE_FAILURE; - } else { - alert = Alert.UNEXPECTED_MESSAGE; + throw switch (cause) { + // Don't close the Socket in case of timeouts. + case SocketTimeoutException ste -> ste; + // Send TLS alert with "fatal", then throw the socket exception. + case SocketException se -> { + try { + throw conContext.fatal(Alert.UNEXPECTED_MESSAGE, se); + } catch (Exception _) { + } + yield se; } - } else { - if (cause instanceof IOException) { - alert = Alert.UNEXPECTED_MESSAGE; - } else { - // RuntimeException - alert = Alert.INTERNAL_ERROR; - } - } - - if (cause instanceof SocketException) { - try { - throw conContext.fatal(alert, cause); - } catch (Exception e) { - // Just delivering the fatal alert, re-throw the socket exception instead. - } - - throw (SocketException)cause; - } - - throw conContext.fatal(alert, cause); + case SSLHandshakeException sslhe -> + conContext.fatal(Alert.HANDSHAKE_FAILURE, sslhe); + case IOException ioe -> + conContext.fatal(Alert.UNEXPECTED_MESSAGE, ioe); + default -> conContext.fatal(Alert.INTERNAL_ERROR, cause); + }; } private Plaintext handleEOF(EOFException eofe) throws IOException { diff --git a/src/java.base/share/classes/sun/security/ssl/SSLSocketInputRecord.java b/src/java.base/share/classes/sun/security/ssl/SSLSocketInputRecord.java index fd9c4b171e7..fc3d9733150 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLSocketInputRecord.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLSocketInputRecord.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, Azul Systems, Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -27,10 +27,10 @@ package sun.security.ssl; import java.io.EOFException; -import java.io.InterruptedIOException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.net.SocketTimeoutException; import java.nio.ByteBuffer; import java.security.GeneralSecurityException; import java.util.ArrayList; @@ -180,7 +180,7 @@ final class SSLSocketInputRecord extends InputRecord implements SSLRecord { if (plaintext == null) { plaintext = decodeInputRecord(); } - } catch(InterruptedIOException e) { + } catch (SocketTimeoutException e) { // do not clean header and recordBody in case of Socket Timeout cleanInBuffer = false; throw e; diff --git a/src/java.base/share/classes/sun/security/ssl/SSLTransport.java b/src/java.base/share/classes/sun/security/ssl/SSLTransport.java index 50bff1e6d21..02551b4a8c1 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLTransport.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLTransport.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 @@ -27,8 +27,8 @@ package sun.security.ssl; import java.io.EOFException; import java.io.IOException; -import java.io.InterruptedIOException; import java.net.SocketException; +import java.net.SocketTimeoutException; import java.nio.ByteBuffer; import javax.crypto.AEADBadTagException; import javax.crypto.BadPaddingException; @@ -138,7 +138,7 @@ interface SSLTransport { } catch (EOFException eofe) { // rethrow EOFException, the call will handle it if needed. throw eofe; - } catch (InterruptedIOException | SocketException se) { + } catch (SocketTimeoutException | SocketException se) { // don't close the Socket in case of timeouts or interrupts or SocketException. throw se; } catch (IOException ioe) { diff --git a/test/jdk/javax/net/ssl/SSLSession/TestEnabledProtocols.java b/test/jdk/javax/net/ssl/SSLSession/TestEnabledProtocols.java index 4e56a9a655b..070991586f3 100644 --- a/test/jdk/javax/net/ssl/SSLSession/TestEnabledProtocols.java +++ b/test/jdk/javax/net/ssl/SSLSession/TestEnabledProtocols.java @@ -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 @@ -40,10 +40,10 @@ import java.io.IOException; import java.io.InputStream; -import java.io.InterruptedIOException; import java.io.OutputStream; import java.net.InetAddress; import java.net.SocketException; +import java.net.SocketTimeoutException; import java.security.Security; import java.util.Arrays; @@ -88,11 +88,9 @@ public class TestEnabledProtocols extends SSLSocketTemplate { // log it for debugging System.out.println("Server SSLHandshakeException:"); se.printStackTrace(System.out); - } catch (InterruptedIOException ioe) { - // must have been interrupted, no harm - } catch (SSLException | SocketException se) { + } catch (SocketTimeoutException | SSLException | SocketException se) { // The client side may have closed the socket. - System.out.println("Server SSLException:"); + System.out.println("Server exception:"); se.printStackTrace(System.out); } catch (Exception e) { System.out.println("Server exception:"); From 2ce2d5886bdccabe4f51d79dd92751933def905a Mon Sep 17 00:00:00 2001 From: Coleen Phillimore Date: Fri, 17 Apr 2026 18:17:52 +0000 Subject: [PATCH 104/108] 8074292: nsk/jdb/kill/kill001: generateOopMap.cpp assert(bb->is_reachable()) failed Co-authored-by: Tom Rodriguez Reviewed-by: sspitsyn, pchilanomate, dlong, never --- .../share/interpreter/interpreterRuntime.cpp | 14 +++ .../share/interpreter/interpreterRuntime.hpp | 4 +- src/hotspot/share/interpreter/oopMapCache.cpp | 15 ++- src/hotspot/share/oops/generateOopMap.cpp | 80 ++++++------- src/hotspot/share/oops/generateOopMap.hpp | 7 +- src/hotspot/share/runtime/globals.hpp | 3 + .../share/runtime/interfaceSupport.cpp | 7 +- .../exceptions/TestAccessErrorInCatch.java | 3 +- .../nsk/jdb/kill/kill003/kill003.java | 106 ++++++++++++++++++ .../nsk/jdb/kill/kill003/kill003a.java | 38 +++++++ 10 files changed, 228 insertions(+), 49 deletions(-) create mode 100644 test/hotspot/jtreg/vmTestbase/nsk/jdb/kill/kill003/kill003.java create mode 100644 test/hotspot/jtreg/vmTestbase/nsk/jdb/kill/kill003/kill003a.java diff --git a/src/hotspot/share/interpreter/interpreterRuntime.cpp b/src/hotspot/share/interpreter/interpreterRuntime.cpp index e7b35b121a2..cd0a062ebc8 100644 --- a/src/hotspot/share/interpreter/interpreterRuntime.cpp +++ b/src/hotspot/share/interpreter/interpreterRuntime.cpp @@ -36,6 +36,7 @@ #include "interpreter/interpreter.hpp" #include "interpreter/interpreterRuntime.hpp" #include "interpreter/linkResolver.hpp" +#include "interpreter/oopMapCache.hpp" #include "interpreter/templateTable.hpp" #include "jvm_io.h" #include "logging/log.hpp" @@ -1527,4 +1528,17 @@ bool InterpreterRuntime::is_preemptable_call(address entry_point) { entry_point == CAST_FROM_FN_PTR(address, InterpreterRuntime::resolve_from_cache) || entry_point == CAST_FROM_FN_PTR(address, InterpreterRuntime::_new); } + +void InterpreterRuntime::generate_oop_map_alot() { + JavaThread* current = JavaThread::current(); + LastFrameAccessor last_frame(current); + if (last_frame.is_interpreted_frame()) { + ResourceMark rm(current); + InterpreterOopMap mask; + methodHandle mh(current, last_frame.method()); + int bci = last_frame.bci(); + log_info(generateoopmap)("Generating oopmap for method %s at bci %d", mh->name_and_sig_as_C_string(), bci); + OopMapCache::compute_one_oop_map(mh, bci, &mask); + } +} #endif // ASSERT diff --git a/src/hotspot/share/interpreter/interpreterRuntime.hpp b/src/hotspot/share/interpreter/interpreterRuntime.hpp index 70ceeb0b2af..3228027fa93 100644 --- a/src/hotspot/share/interpreter/interpreterRuntime.hpp +++ b/src/hotspot/share/interpreter/interpreterRuntime.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 @@ -173,6 +173,8 @@ private: // Virtual Thread Preemption DEBUG_ONLY(static bool is_preemptable_call(address entry_point);) + + DEBUG_ONLY(static void generate_oop_map_alot();) }; diff --git a/src/hotspot/share/interpreter/oopMapCache.cpp b/src/hotspot/share/interpreter/oopMapCache.cpp index 5a1ad7b7883..af45f7f9bed 100644 --- a/src/hotspot/share/interpreter/oopMapCache.cpp +++ b/src/hotspot/share/interpreter/oopMapCache.cpp @@ -92,11 +92,8 @@ class OopMapForCacheEntry: public GenerateOopMap { }; -OopMapForCacheEntry::OopMapForCacheEntry(const methodHandle& method, int bci, OopMapCacheEntry* entry) : GenerateOopMap(method) { - _bci = bci; - _entry = entry; - _stack_top = -1; -} +OopMapForCacheEntry::OopMapForCacheEntry(const methodHandle& method, int bci, OopMapCacheEntry* entry) : + GenerateOopMap(method, /*all_exception_edges*/ true), _entry(entry), _bci(bci), _stack_top(-1) { } bool OopMapForCacheEntry::compute_map(Thread* current) { @@ -107,6 +104,11 @@ bool OopMapForCacheEntry::compute_map(Thread* current) { } else { ResourceMark rm; if (!GenerateOopMap::compute_map(current)) { + // If compute_map fails, print the exception message, which is generated if + // this is a JavaThread, otherwise compute_map calls fatal so we don't get here. + if (exception() != nullptr) { + exception()->print(); + } fatal("Unrecoverable verification or out-of-memory error"); return false; } @@ -315,6 +317,9 @@ void OopMapCacheEntry::fill(const methodHandle& method, int bci) { } else { OopMapForCacheEntry gen(method, bci, this); if (!gen.compute_map(Thread::current())) { + if (gen.exception() != nullptr) { + gen.exception()->print(); + } fatal("Unrecoverable verification or out-of-memory error"); } } diff --git a/src/hotspot/share/oops/generateOopMap.cpp b/src/hotspot/share/oops/generateOopMap.cpp index 09912aeaf63..56f3e7e0325 100644 --- a/src/hotspot/share/oops/generateOopMap.cpp +++ b/src/hotspot/share/oops/generateOopMap.cpp @@ -1168,42 +1168,46 @@ void GenerateOopMap::interp_bb(BasicBlock *bb) { } void GenerateOopMap::do_exception_edge(BytecodeStream* itr) { - // Only check exception edge, if bytecode can trap - if (!Bytecodes::can_trap(itr->code())) return; - switch (itr->code()) { - case Bytecodes::_aload_0: - // These bytecodes can trap for rewriting. We need to assume that - // they do not throw exceptions to make the monitor analysis work. - return; - case Bytecodes::_ireturn: - case Bytecodes::_lreturn: - case Bytecodes::_freturn: - case Bytecodes::_dreturn: - case Bytecodes::_areturn: - case Bytecodes::_return: - // If the monitor stack height is not zero when we leave the method, - // then we are either exiting with a non-empty stack or we have - // found monitor trouble earlier in our analysis. In either case, - // assume an exception could be taken here. - if (_monitor_top == 0) { + // Only check exception edge, if bytecode can trap or if async exceptions can be thrown + // from any bytecode in the interpreter when single stepping. + if (!_all_exception_edges) { + if (!Bytecodes::can_trap(itr->code())) return; + switch (itr->code()) { + case Bytecodes::_aload_0: + // These bytecodes can trap for rewriting. We need to assume that + // they do not throw exceptions to make the monitor analysis work. return; - } - break; - case Bytecodes::_monitorexit: - // If the monitor stack height is bad_monitors, then we have detected a - // monitor matching problem earlier in the analysis. If the - // monitor stack height is 0, we are about to pop a monitor - // off of an empty stack. In either case, the bytecode - // could throw an exception. - if (_monitor_top != bad_monitors && _monitor_top != 0) { - return; - } - break; + case Bytecodes::_ireturn: + case Bytecodes::_lreturn: + case Bytecodes::_freturn: + case Bytecodes::_dreturn: + case Bytecodes::_areturn: + case Bytecodes::_return: + // If the monitor stack height is not zero when we leave the method, + // then we are either exiting with a non-empty stack or we have + // found monitor trouble earlier in our analysis. In either case, + // assume an exception could be taken here. + if (_monitor_top == 0) { + return; + } + break; - default: - break; + case Bytecodes::_monitorexit: + // If the monitor stack height is bad_monitors, then we have detected a + // monitor matching problem earlier in the analysis. If the + // monitor stack height is 0, we are about to pop a monitor + // off of an empty stack. In either case, the bytecode + // could throw an exception. + if (_monitor_top != bad_monitors && _monitor_top != 0) { + return; + } + break; + + default: + break; + } } if (_has_exceptions) { @@ -2055,12 +2059,12 @@ void GenerateOopMap::print_time() { // // ============ Main Entry Point =========== // -GenerateOopMap::GenerateOopMap(const methodHandle& method) { +GenerateOopMap::GenerateOopMap(const methodHandle& method, bool all_exception_edges) : // We have to initialize all variables here, that can be queried directly - _method = method; - _max_locals=0; - _init_vars = nullptr; -} + _method(method), + _max_locals(0), + _all_exception_edges(all_exception_edges), + _init_vars(nullptr) {} bool GenerateOopMap::compute_map(Thread* current) { #ifndef PRODUCT @@ -2187,7 +2191,7 @@ void GenerateOopMap::result_for_basicblock(int bci) { // Find basicblock and report results BasicBlock* bb = get_basic_block_containing(bci); guarantee(bb != nullptr, "no basic block for bci"); - assert(bb->is_reachable(), "getting result from unreachable basicblock %d", bci); + assert(bb->is_reachable(), "getting result from unreachable basicblock at bci %d", bci); bb->set_changed(true); interp_bb(bb); } diff --git a/src/hotspot/share/oops/generateOopMap.hpp b/src/hotspot/share/oops/generateOopMap.hpp index 783e295f08a..f0fdfeda57f 100644 --- a/src/hotspot/share/oops/generateOopMap.hpp +++ b/src/hotspot/share/oops/generateOopMap.hpp @@ -307,6 +307,7 @@ class GenerateOopMap { bool _did_relocation; // was relocation necessary bool _monitor_safe; // The monitors in this method have been determined // to be safe. + bool _all_exception_edges; // All bytecodes can reach containing exception handler. // Working Cell type state int _state_len; // Size of states @@ -455,7 +456,7 @@ class GenerateOopMap { friend class RelocCallback; public: - GenerateOopMap(const methodHandle& method); + GenerateOopMap(const methodHandle& method, bool all_exception_edges); // Compute the map - returns true on success and false on error. bool compute_map(Thread* current); @@ -516,7 +517,7 @@ class ResolveOopMapConflicts: public GenerateOopMap { #endif public: - ResolveOopMapConflicts(const methodHandle& method) : GenerateOopMap(method) { } + ResolveOopMapConflicts(const methodHandle& method) : GenerateOopMap(method, true) { } methodHandle do_potential_rewrite(TRAPS); }; @@ -535,7 +536,7 @@ class GeneratePairingInfo: public GenerateOopMap { CellTypeState* stack, int stack_top) {} public: - GeneratePairingInfo(const methodHandle& method) : GenerateOopMap(method) {}; + GeneratePairingInfo(const methodHandle& method) : GenerateOopMap(method, false) {}; // Call compute_map() to generate info. }; diff --git a/src/hotspot/share/runtime/globals.hpp b/src/hotspot/share/runtime/globals.hpp index ac1ddec7cbc..9d38a44cbd5 100644 --- a/src/hotspot/share/runtime/globals.hpp +++ b/src/hotspot/share/runtime/globals.hpp @@ -871,6 +871,9 @@ const int ObjectAlignmentInBytes = 8; develop(bool, TimeOopMap, false, \ "Time calls to GenerateOopMap::compute_map() in sum") \ \ + develop(bool, GenerateOopMapALot, false, \ + "Generate interpreter oopmaps at all safepoints") \ + \ develop(bool, TraceFinalizerRegistration, false, \ "Trace registration of final references") \ \ diff --git a/src/hotspot/share/runtime/interfaceSupport.cpp b/src/hotspot/share/runtime/interfaceSupport.cpp index 11a7d9fd41f..6ccf63b4c5e 100644 --- a/src/hotspot/share/runtime/interfaceSupport.cpp +++ b/src/hotspot/share/runtime/interfaceSupport.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 @@ -23,6 +23,7 @@ */ #include "gc/shared/collectedHeap.inline.hpp" +#include "interpreter/interpreterRuntime.hpp" #include "logging/log.hpp" #include "memory/resourceArea.hpp" #include "memory/universe.hpp" @@ -65,6 +66,10 @@ VMEntryWrapper::~VMEntryWrapper() { if (VerifyStack) { InterfaceSupport::verify_stack(); } + // Verify interpreter oopmap generation + if (GenerateOopMapALot) { + InterpreterRuntime::generate_oop_map_alot(); + } } VMNativeEntryWrapper::VMNativeEntryWrapper() { diff --git a/test/hotspot/jtreg/compiler/exceptions/TestAccessErrorInCatch.java b/test/hotspot/jtreg/compiler/exceptions/TestAccessErrorInCatch.java index fa81fa93f11..89dfae5c4da 100644 --- a/test/hotspot/jtreg/compiler/exceptions/TestAccessErrorInCatch.java +++ b/test/hotspot/jtreg/compiler/exceptions/TestAccessErrorInCatch.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 @@ -37,6 +37,7 @@ * -XX:+IgnoreUnrecognizedVMOptions -XX:+VerifyStack * -XX:TieredStopAtLevel=3 * TestAccessErrorInCatch + * @run main/othervm -XX:+IgnoreUnrecognizedVMOptions -XX:+GenerateOopMapALot TestAccessErrorInCatch */ import java.lang.invoke.MethodHandle; diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jdb/kill/kill003/kill003.java b/test/hotspot/jtreg/vmTestbase/nsk/jdb/kill/kill003/kill003.java new file mode 100644 index 00000000000..840e8def8bc --- /dev/null +++ b/test/hotspot/jtreg/vmTestbase/nsk/jdb/kill/kill003/kill003.java @@ -0,0 +1,106 @@ +/* + * 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. + */ + + +/* + * @test + * @bug 8074292 + * @summary Reproduce the bb->is_reachable() assert with GetSetLocals call after async exception. + * @library /vmTestbase + * /test/lib + * @compile -g kill003a.java + * @run driver + * nsk.jdb.kill.kill003.kill003 + * -arch=${os.family}-${os.simpleArch} + * -waittime=5 + * -verbose + * -debugee.vmkind=java + * -transport.address=dynamic + * -jdb=${test.jdk}/bin/jdb + * -java.options="${test.vm.opts} ${test.java.opts}" + * -workdir=. + * -debugee.vmkeys="${test.vm.opts} ${test.java.opts}" + */ + +package nsk.jdb.kill.kill003; + +import nsk.share.*; +import nsk.share.jdb.*; + +import java.io.*; +import java.util.*; + +public class kill003 extends JdbTest { + + public static void main (String argv[]) { + debuggeeClass = DEBUGGEE_CLASS; + firstBreak = FIRST_BREAK; + new kill003().runTest(argv); + } + + static final String PACKAGE_NAME = "nsk.jdb.kill.kill003"; + static final String TEST_CLASS = PACKAGE_NAME + ".kill003"; + static final String DEBUGGEE_CLASS = TEST_CLASS + "a"; + static final String FIRST_BREAK = DEBUGGEE_CLASS + ".main"; + static final String MYTHREAD = "MyThread"; + static final String DEBUGGEE_THREAD = PACKAGE_NAME + "." + MYTHREAD; + static final String DEBUGGEE_EXCEPTION = DEBUGGEE_CLASS + ".exception"; + + protected void runCases() { + String[] reply; + String[] threads; + + // At this point we are at the breakpoint triggered by the firstBreak in main + // after creating all the threads. Get the list of debuggee threads. + threads = jdb.getThreadIdsByName("main"); + + // Stopped at kill.main, so step into synchronized block + reply = jdb.receiveReplyFor(JdbCommand.next); + + if (threads.length != 1) { + log.complain("jdb should report " + 1 + " instance of " + DEBUGGEE_THREAD); + log.complain("Found: " + threads.length); + success = false; + } + + // Execution is at a bytecode that is not expected to handle an async exception. Throw one here + // to make sure it gets handled without crashing. The exception will be delivered at the next + // bytecode that can handle the async exception. + reply = jdb.receiveReplyForWithMessageWait(JdbCommand.kill + threads[0] + " " + DEBUGGEE_EXCEPTION, + "killed"); + + // Continue the debuggee - the async exception will be delivered to the debuggee. + reply = jdb.receiveReplyFor(JdbCommand.cont); + + // Ask the debuggee for its local variables at the bytecode where the async exception was delivered, which + // should be reachable. + reply = jdb.receiveReplyForWithMessageWait(JdbCommand.locals, "Local variables"); + + if (jdb.terminated()) { + throw new Failure("Debuggee exited"); + } + + // The lack of exception handler in the debuggee should cause it to exit when continued. + jdb.contToExit(1); + } +} diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jdb/kill/kill003/kill003a.java b/test/hotspot/jtreg/vmTestbase/nsk/jdb/kill/kill003/kill003a.java new file mode 100644 index 00000000000..1e72e02a560 --- /dev/null +++ b/test/hotspot/jtreg/vmTestbase/nsk/jdb/kill/kill003/kill003a.java @@ -0,0 +1,38 @@ +/* + * 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. + */ + +package nsk.jdb.kill.kill003; + +import nsk.share.jdb.*; + +/* This is debuggee application */ +public class kill003a { + static NullPointerException exception = new NullPointerException(); + + public static void main(String args[]) { + synchronized (args) { + } + System.out.println("done"); + System.exit(JdbTest.JCK_STATUS_BASE); + } +} From e95beacb4c2655865f8e8b081ba5137ce9878b82 Mon Sep 17 00:00:00 2001 From: Chad Rakoczy Date: Fri, 17 Apr 2026 18:30:56 +0000 Subject: [PATCH 105/108] 8382146: jdk/jfr/event/compiler/TestCodeCacheFull.java fails when C2 is disabled Reviewed-by: chagedorn --- .../jfr/event/compiler/TestCodeCacheFull.java | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/test/jdk/jdk/jfr/event/compiler/TestCodeCacheFull.java b/test/jdk/jdk/jfr/event/compiler/TestCodeCacheFull.java index 12c9d6c40fa..266eb73a656 100644 --- a/test/jdk/jdk/jfr/event/compiler/TestCodeCacheFull.java +++ b/test/jdk/jdk/jfr/event/compiler/TestCodeCacheFull.java @@ -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,7 @@ import jdk.test.whitebox.WhiteBox; import jdk.test.whitebox.code.BlobType; /** - * @test TestCodeCacheFull + * @test id=Default * @requires vm.hasJFR * @requires vm.opt.UseCodeCacheFlushing == null | vm.opt.UseCodeCacheFlushing == true * @@ -50,6 +50,20 @@ import jdk.test.whitebox.code.BlobType; * @run main/othervm -Xbootclasspath/a:. * -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI * -XX:-SegmentedCodeCache jdk.jfr.event.compiler.TestCodeCacheFull + */ + +/** + * @test id=HotCode + * @requires vm.hasJFR + * @requires vm.opt.UseCodeCacheFlushing == null | vm.opt.UseCodeCacheFlushing == true + * @requires vm.compiler2.enabled + * + * @library /test/lib + * @modules jdk.jfr + * jdk.management.jfr + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * * @run main/othervm -Xbootclasspath/a:. * -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI * -XX:+TieredCompilation -XX:+UnlockExperimentalVMOptions -XX:+HotCodeHeap -XX:HotCodeHeapSize=8M jdk.jfr.event.compiler.TestCodeCacheFull From 2325c3649dd360043f8434d96dd2892c13ba0141 Mon Sep 17 00:00:00 2001 From: Saint Wesonga Date: Fri, 17 Apr 2026 19:55:20 +0000 Subject: [PATCH 106/108] 8378891: Problemlist TestStaticCallStub on Windows AArch64 Reviewed-by: mchevalier, chagedorn --- test/hotspot/jtreg/ProblemList.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/hotspot/jtreg/ProblemList.txt b/test/hotspot/jtreg/ProblemList.txt index b4f9efff830..202570d6486 100644 --- a/test/hotspot/jtreg/ProblemList.txt +++ b/test/hotspot/jtreg/ProblemList.txt @@ -79,7 +79,7 @@ compiler/codecache/CodeCacheFullCountTest.java 8332954 generic-all compiler/interpreter/Test6833129.java 8335266 generic-i586 compiler/intrinsics/TestReturnOopSetForJFRWriteCheckpoint.java 8286300 linux-s390x -compiler/c2/aarch64/TestStaticCallStub.java 8359963 linux-aarch64,macosx-aarch64 +compiler/c2/aarch64/TestStaticCallStub.java 8359963 generic-aarch64 compiler/unsafe/AlignmentGapAccess.java 8373487 generic-all From 804ecc14dcc0465f9cbbfe38608bd989a0df84fd Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Fri, 17 Apr 2026 20:01:09 +0000 Subject: [PATCH 107/108] 8382436: Build failures after JDK-8326205 Reviewed-by: kvn --- src/hotspot/share/runtime/hotCodeCollector.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/hotspot/share/runtime/hotCodeCollector.cpp b/src/hotspot/share/runtime/hotCodeCollector.cpp index 643cf3a8bbb..6bdeee011ce 100644 --- a/src/hotspot/share/runtime/hotCodeCollector.cpp +++ b/src/hotspot/share/runtime/hotCodeCollector.cpp @@ -29,6 +29,7 @@ #include "compiler/compilerDefinitions.inline.hpp" #include "logging/log.hpp" #include "memory/resourceArea.hpp" +#include "oops/method.inline.hpp" #include "runtime/hotCodeCollector.hpp" #include "runtime/hotCodeSampler.hpp" #include "runtime/java.hpp" From 0dd0108c1a7b3658df536adbc2bd68fa5167539d Mon Sep 17 00:00:00 2001 From: Jeremy Wood Date: Fri, 17 Apr 2026 20:53:42 +0000 Subject: [PATCH 108/108] 8377936: VoiceOver Reads Button Incorrectly Reviewed-by: prr, kizune --- .../classes/sun/lwawt/macosx/CAccessible.java | 22 +--- .../8377936/VoiceOverNamesAndStates.java | 107 ++++++++++++++++++ 2 files changed, 112 insertions(+), 17 deletions(-) create mode 100644 test/jdk/javax/accessibility/8377936/VoiceOverNamesAndStates.java diff --git a/src/java.desktop/macosx/classes/sun/lwawt/macosx/CAccessible.java b/src/java.desktop/macosx/classes/sun/lwawt/macosx/CAccessible.java index 5be7f70b981..4315abe6197 100644 --- a/src/java.desktop/macosx/classes/sun/lwawt/macosx/CAccessible.java +++ b/src/java.desktop/macosx/classes/sun/lwawt/macosx/CAccessible.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 @@ -188,10 +188,6 @@ final class CAccessible extends CFRetainedResource implements Accessible { // Do send check box state changes to native side if (thisRole == AccessibleRole.CHECK_BOX) { - if (!Objects.equals(newValue, oldValue)) { - valueChanged(ptr); - } - // Notify native side to handle check box style menuitem if (parentRole == AccessibleRole.POPUP_MENU && newValue != null && ((AccessibleState)newValue) == AccessibleState.FOCUSED) { @@ -201,23 +197,12 @@ final class CAccessible extends CFRetainedResource implements Accessible { // Do send radio button state changes to native side if (thisRole == AccessibleRole.RADIO_BUTTON) { - if (newValue != null && !newValue.equals(oldValue)) { - valueChanged(ptr); - } - // Notify native side to handle radio button style menuitem if (parentRole == AccessibleRole.POPUP_MENU && newValue != null && ((AccessibleState)newValue) == AccessibleState.FOCUSED) { menuItemSelected(ptr); } } - - // Do send toggle button state changes to native side - if (thisRole == AccessibleRole.TOGGLE_BUTTON) { - if (!Objects.equals(newValue, oldValue)) { - valueChanged(ptr); - } - } } else if (name.equals(ACCESSIBLE_NAME_PROPERTY)) { //for now trigger only for JTabbedPane. if (e.getSource() instanceof JTabbedPane) { @@ -227,7 +212,10 @@ final class CAccessible extends CFRetainedResource implements Accessible { AccessibleRole thisRole = accessible.getAccessibleContext() .getAccessibleRole(); if (thisRole == AccessibleRole.SLIDER || - thisRole == AccessibleRole.PROGRESS_BAR) { + thisRole == AccessibleRole.PROGRESS_BAR || + thisRole == AccessibleRole.CHECK_BOX || + thisRole == AccessibleRole.RADIO_BUTTON || + thisRole == AccessibleRole.TOGGLE_BUTTON ) { valueChanged(ptr); } } diff --git a/test/jdk/javax/accessibility/8377936/VoiceOverNamesAndStates.java b/test/jdk/javax/accessibility/8377936/VoiceOverNamesAndStates.java new file mode 100644 index 00000000000..9df68cf0ca0 --- /dev/null +++ b/test/jdk/javax/accessibility/8377936/VoiceOverNamesAndStates.java @@ -0,0 +1,107 @@ +/* + * 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. + */ + +import java.awt.FlowLayout; +import javax.swing.AbstractButton; +import javax.swing.JButton; +import javax.swing.JCheckBox; +import javax.swing.JFrame; + +/* + * @test + * @key headful + * @bug 8377936 + * @summary manual test for VoiceOver reading button names and states + * @requires os.family == "mac" + * @library /java/awt/regtesthelpers + * @build PassFailJFrame + * @run main/manual VoiceOverNamesAndStates + */ + +public class VoiceOverNamesAndStates { + public static void main(String[] args) throws Exception { + String INSTRUCTIONS = """ + INSTRUCTIONS: + + Part A: + 1. Open VoiceOver + 2. Move the VoiceOver cursor to the leftmost button. + 3. Press CTRL + ALT + RIGHT to move to the rightmost button. + + Expected behavior: VoiceOver should announce rightmost button. + (It should NOT announce the leftmost button.) + + Part B (Regression for 8061359, 8348936, 8309733): + 1. Open VoiceOver + 2. Move the VoiceOver cursor to the leftmost button. + 3. Press SPACE to trigger Swing's default KeyListeners. + 4. Repeat step 3 to untoggle button. + 5. Press CTRL + ALT + SPACE to trigger VO's listeners. + 6. Repeat step 5 to untoggle button + + Expected behavior: VoiceOver should announce the change in the + button state in steps 3-6. (When you use VoiceOver to toggle + the button you should hear a small chirp as the button + toggles.) + + Part C (Regression for 8345728, 8283400): + 1. Turn on Screen Magnifier + (See System Settings -> Accessibility -> Hover Text) + 2. Press CMD key and hover mouse over leftmost button + 3. Click the button + 4. Release the CMD key + + Expected behavior: both the button and the "hover text" window + repaint to show a selected button. + + 5. Repeat steps 2-4 to untoggle the button + + Expected behavior: both the button and the "hover text" window + repaint to show a deselected button. + + """; + + PassFailJFrame.builder() + .title("VoiceOverNamesAndStates Instruction") + .instructions(INSTRUCTIONS) + .columns(40) + .testUI(VoiceOverNamesAndStates::createUI) + .build() + .awaitAndCheck(); + } + + private static JFrame createUI() { + JFrame f = new JFrame(); + + f.getContentPane().setLayout(new FlowLayout()); + + AbstractButton button1 = new JCheckBox("chess"); + AbstractButton button2 = new JButton("backgammon"); + + f.getContentPane().add(button1); + f.getContentPane().add(button2); + f.pack(); + f.setVisible(true); + return f; + } +}