From 04c0b130f09c093797895cc928fe020d7e584cb9 Mon Sep 17 00:00:00 2001 From: David Holmes Date: Fri, 18 Jul 2025 02:35:09 +0000 Subject: [PATCH 01/94] 8362565: ProblemList jdk/jfr/event/io/TestIOTopFrame.java Reviewed-by: egahlin --- test/jdk/ProblemList.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/test/jdk/ProblemList.txt b/test/jdk/ProblemList.txt index 84555a6edfb..6fe13c54988 100644 --- a/test/jdk/ProblemList.txt +++ b/test/jdk/ProblemList.txt @@ -772,6 +772,7 @@ jdk/jfr/event/compiler/TestCodeSweeper.java 8338127 generic- jdk/jfr/event/oldobject/TestShenandoah.java 8342951 generic-all jdk/jfr/event/runtime/TestResidentSetSizeEvent.java 8309846 aix-ppc64 jdk/jfr/jvm/TestWaste.java 8282427 generic-all +jdk/jfr/event/io/TestIOTopFrame.java 8362556 generic-all ############################################################################ From a23987fecbddeea9828a9443dddd7bf8f9f0d05d Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Fri, 18 Jul 2025 06:13:06 +0000 Subject: [PATCH 02/94] 8361283: [Accessibility,macOS,VoiceOver] VoiceOver announced Tab items of JTabbedPane as RadioButton on macOS Reviewed-by: asemenov, kizune --- .../awt/a11y/CommonComponentAccessibility.m | 5 +- .../awt/a11y/TabButtonAccessibility.m | 9 ++ .../AccessibleTabbedPaneRoleTest.java | 83 +++++++++++++++++++ 3 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 test/jdk/javax/accessibility/JTabbedPane/AccessibleTabbedPaneRoleTest.java diff --git a/src/java.desktop/macosx/native/libawt_lwawt/awt/a11y/CommonComponentAccessibility.m b/src/java.desktop/macosx/native/libawt_lwawt/awt/a11y/CommonComponentAccessibility.m index 41a3accd0a5..4e2c1ace969 100644 --- a/src/java.desktop/macosx/native/libawt_lwawt/awt/a11y/CommonComponentAccessibility.m +++ b/src/java.desktop/macosx/native/libawt_lwawt/awt/a11y/CommonComponentAccessibility.m @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -126,7 +126,7 @@ static jobject sAccessibilityClass = NULL; /* * Here we should keep all the mapping between the accessibility roles and implementing classes */ - rolesMap = [[NSMutableDictionary alloc] initWithCapacity:51]; + rolesMap = [[NSMutableDictionary alloc] initWithCapacity:52]; [rolesMap setObject:@"ButtonAccessibility" forKey:@"pushbutton"]; [rolesMap setObject:@"ImageAccessibility" forKey:@"icon"]; @@ -153,6 +153,7 @@ static jobject sAccessibilityClass = NULL; [rolesMap setObject:@"NavigableTextAccessibility" forKey:@"dateeditor"]; [rolesMap setObject:@"ComboBoxAccessibility" forKey:@"combobox"]; [rolesMap setObject:@"TabGroupAccessibility" forKey:@"pagetablist"]; + [rolesMap setObject:@"TabButtonAccessibility" forKey:@"pagetab"]; [rolesMap setObject:@"ListAccessibility" forKey:@"list"]; [rolesMap setObject:@"OutlineAccessibility" forKey:@"tree"]; [rolesMap setObject:@"TableAccessibility" forKey:@"table"]; diff --git a/src/java.desktop/macosx/native/libawt_lwawt/awt/a11y/TabButtonAccessibility.m b/src/java.desktop/macosx/native/libawt_lwawt/awt/a11y/TabButtonAccessibility.m index 1507f4884c3..26f381ad475 100644 --- a/src/java.desktop/macosx/native/libawt_lwawt/awt/a11y/TabButtonAccessibility.m +++ b/src/java.desktop/macosx/native/libawt_lwawt/awt/a11y/TabButtonAccessibility.m @@ -106,4 +106,13 @@ return YES; } +- (NSString *)accessibilityRoleDescription +{ + NSString *value = NSAccessibilityRoleDescription([self accessibilityRole], NSAccessibilityTabButtonSubrole); + if (value == nil) { + value = [super accessibilityRoleDescription]; + } + return value; +} + @end diff --git a/test/jdk/javax/accessibility/JTabbedPane/AccessibleTabbedPaneRoleTest.java b/test/jdk/javax/accessibility/JTabbedPane/AccessibleTabbedPaneRoleTest.java new file mode 100644 index 00000000000..56f41f9f84b --- /dev/null +++ b/test/jdk/javax/accessibility/JTabbedPane/AccessibleTabbedPaneRoleTest.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.awt.BorderLayout; + +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JPanel; +import javax.swing.JTabbedPane; + +/* + * @test + * @bug 8361283 + * @library /java/awt/regtesthelpers + * @build PassFailJFrame + * @requires (os.family == "mac") + * @summary VO shouldn't announce the tab items as RadioButton + * @run main/manual AccessibleTabbedPaneRoleTest + */ + +public class AccessibleTabbedPaneRoleTest { + + public static void main(String[] args) throws Exception { + String INSTRUCTIONS = """ + This test is applicable only on macOS. + + Test UI contains a JFrame containing JTabbedPane with multiple tabs. + + Follow these steps to test the behaviour: + + 1. Start the VoiceOver (Press Command + F5) application. + 2. Test Frame should have focus. If not, then bring focus to test frame. + 3. Press Left / Right arrow key to move to next and prevoius tab. + 4. VO should announce "Tab" in stead of "RadioButton" for tab items. + (For e.g. When Tab 1 is selected, VO should announce "Tab 1, selected, + tab, group). + 5. Press Pass if you are able to hear correct announcements + else Fail."""; + + PassFailJFrame.builder() + .instructions(INSTRUCTIONS) + .columns(45) + .testUI(AccessibleTabbedPaneRoleTest::createUI) + .build() + .awaitAndCheck(); + } + + private static JFrame createUI() { + int NUM_TABS = 6; + JFrame frame = new JFrame("Test Frame"); + JTabbedPane tabPane = new JTabbedPane(); + tabPane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT); + tabPane.setTabPlacement(JTabbedPane.TOP); + for (int i = 0; i < NUM_TABS; ++i) { + tabPane.addTab("Tab " + i , new JLabel("Content Area")); + } + JPanel panel = new JPanel(new BorderLayout()); + panel.add(tabPane, BorderLayout.CENTER); + frame.add(panel); + frame.setSize(400, 100); + return frame; + } +} From 4e0b03580d3764e06ec65493143e80c291fa3fbb Mon Sep 17 00:00:00 2001 From: Abhishek Kumar Date: Fri, 18 Jul 2025 06:13:26 +0000 Subject: [PATCH 03/94] 8338282: javax/swing/JMenuBar/TestMenuMnemonicLinuxAndMac.java test failed on macOS and Ubuntu Reviewed-by: tr, dnguyen, serb --- .../javax/swing/JMenuBar/TestMenuMnemonicLinuxAndMac.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/jdk/javax/swing/JMenuBar/TestMenuMnemonicLinuxAndMac.java b/test/jdk/javax/swing/JMenuBar/TestMenuMnemonicLinuxAndMac.java index 5fdf8f9aa27..e8aa15a74c4 100644 --- a/test/jdk/javax/swing/JMenuBar/TestMenuMnemonicLinuxAndMac.java +++ b/test/jdk/javax/swing/JMenuBar/TestMenuMnemonicLinuxAndMac.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 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 @@ -41,7 +41,6 @@ import javax.swing.UIManager; * @key headful * @requires (os.family == "linux" | os.family == "mac") * @library /javax/swing/regtesthelpers - * @build Util * @summary Verifies if menu mnemonic toggle on Alt press in GTK and Aqua LAF * @run main TestMenuMnemonicLinuxAndMac */ @@ -117,5 +116,7 @@ public class TestMenuMnemonicLinuxAndMac { frame.setLocationRelativeTo(null); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); + frame.toFront(); + frame.requestFocus(); } } From 7da274ded4a36c6314702b687fcafcda80ae08c4 Mon Sep 17 00:00:00 2001 From: Shawn M Emery Date: Fri, 18 Jul 2025 10:02:25 +0000 Subject: [PATCH 04/94] 8361961: Typo in ProtectionDomain.implies Reviewed-by: mullan, jpai, hchao --- .../share/classes/java/security/ProtectionDomain.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/java.base/share/classes/java/security/ProtectionDomain.java b/src/java.base/share/classes/java/security/ProtectionDomain.java index 19ee5815db0..c49672d51de 100644 --- a/src/java.base/share/classes/java/security/ProtectionDomain.java +++ b/src/java.base/share/classes/java/security/ProtectionDomain.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 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 @@ -224,7 +224,7 @@ public class ProtectionDomain { * no longer supported. The {@linkplain Policy#getPolicy current policy} * is always a {@code Policy} object that grants no permissions. * - * @param perm the {code Permission} object to check. + * @param perm the {@code Permission} object to check. * * @return {@code true} if {@code perm} is implied by this * {@code ProtectionDomain}. From 6949e345757b010790b2a6f5a975fc1c6bd0e8c6 Mon Sep 17 00:00:00 2001 From: Albert Mingkun Yang Date: Fri, 18 Jul 2025 13:48:44 +0000 Subject: [PATCH 05/94] 8362592: Remove unused argument in nmethod::oops_do Reviewed-by: zgu --- src/hotspot/share/code/nmethod.cpp | 2 +- src/hotspot/share/code/nmethod.hpp | 3 +-- src/hotspot/share/gc/shared/gcBehaviours.cpp | 2 +- src/hotspot/share/gc/shenandoah/shenandoahNMethod.cpp | 4 ++-- src/hotspot/share/gc/shenandoah/shenandoahNMethod.hpp | 2 +- 5 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/hotspot/share/code/nmethod.cpp b/src/hotspot/share/code/nmethod.cpp index 4718d31eb82..c062919f61e 100644 --- a/src/hotspot/share/code/nmethod.cpp +++ b/src/hotspot/share/code/nmethod.cpp @@ -2446,7 +2446,7 @@ void nmethod::do_unloading(bool unloading_occurred) { } } -void nmethod::oops_do(OopClosure* f, bool allow_dead) { +void nmethod::oops_do(OopClosure* f) { // Prevent extra code cache walk for platforms that don't have immediate oops. if (relocInfo::mustIterateImmediateOopsInCode()) { RelocIterator iter(this, oops_reloc_begin()); diff --git a/src/hotspot/share/code/nmethod.hpp b/src/hotspot/share/code/nmethod.hpp index b8407a091ec..301454b6591 100644 --- a/src/hotspot/share/code/nmethod.hpp +++ b/src/hotspot/share/code/nmethod.hpp @@ -919,8 +919,7 @@ public: bool jvmci_skip_profile_deopt() const; #endif - void oops_do(OopClosure* f) { oops_do(f, false); } - void oops_do(OopClosure* f, bool allow_dead); + void oops_do(OopClosure* f); // All-in-one claiming of nmethods: returns true if the caller successfully claimed that // nmethod. diff --git a/src/hotspot/share/gc/shared/gcBehaviours.cpp b/src/hotspot/share/gc/shared/gcBehaviours.cpp index 02971943874..0d10a832261 100644 --- a/src/hotspot/share/gc/shared/gcBehaviours.cpp +++ b/src/hotspot/share/gc/shared/gcBehaviours.cpp @@ -70,6 +70,6 @@ public: bool ClosureIsUnloadingBehaviour::has_dead_oop(nmethod* nm) const { IsCompiledMethodUnloadingOopClosure cl(_cl); - nm->oops_do(&cl, true /* allow_dead */); + nm->oops_do(&cl); return cl.is_unloading(); } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahNMethod.cpp b/src/hotspot/share/gc/shenandoah/shenandoahNMethod.cpp index 53dd3ae1b1e..a08e7ef4b5f 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahNMethod.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahNMethod.cpp @@ -178,9 +178,9 @@ public: } }; -void ShenandoahNMethod::assert_same_oops(bool allow_dead) { +void ShenandoahNMethod::assert_same_oops() { ShenandoahNMethodOopDetector detector; - nm()->oops_do(&detector, allow_dead); + nm()->oops_do(&detector); GrowableArray* oops = detector.oops(); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahNMethod.hpp b/src/hotspot/share/gc/shenandoah/shenandoahNMethod.hpp index c1595396500..5387870c9dc 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahNMethod.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahNMethod.hpp @@ -71,7 +71,7 @@ public: static inline void attach_gc_data(nmethod* nm, ShenandoahNMethod* gc_data); void assert_correct() NOT_DEBUG_RETURN; - void assert_same_oops(bool allow_dead = false) NOT_DEBUG_RETURN; + void assert_same_oops() NOT_DEBUG_RETURN; private: static void detect_reloc_oops(nmethod* nm, GrowableArray& oops, bool& _has_non_immed_oops); From 9dc62825b5e7300542d22df0b87b79116f3562d3 Mon Sep 17 00:00:00 2001 From: Jorn Vernee Date: Fri, 18 Jul 2025 14:54:10 +0000 Subject: [PATCH 06/94] 8362169: Pointer passed to upcall may get wrong scope Reviewed-by: mcimadamore --- .../foreign/abi/BindingSpecializer.java | 6 +-- .../java/foreign/TestUpcallStructScope.java | 39 ++++++++++++++++++- .../java/foreign/libTestUpcallStructScope.c | 4 ++ 3 files changed, 44 insertions(+), 5 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/foreign/abi/BindingSpecializer.java b/src/java.base/share/classes/jdk/internal/foreign/abi/BindingSpecializer.java index a1323e16945..20ccec61fd2 100644 --- a/src/java.base/share/classes/jdk/internal/foreign/abi/BindingSpecializer.java +++ b/src/java.base/share/classes/jdk/internal/foreign/abi/BindingSpecializer.java @@ -297,7 +297,7 @@ public class BindingSpecializer { if (callingSequence.allocationSize() != 0) { cb.loadConstant(callingSequence.allocationSize()) .invokestatic(CD_SharedUtils, "newBoundedArena", MTD_NEW_BOUNDED_ARENA); - } else if (callingSequence.forUpcall() && needsSession()) { + } else if (callingSequence.forUpcall() && anyArgNeedsScope()) { cb.invokestatic(CD_SharedUtils, "newEmptyArena", MTD_NEW_EMPTY_ARENA); } else { cb.getstatic(CD_SharedUtils, "DUMMY_ARENA", CD_Arena); @@ -437,7 +437,7 @@ public class BindingSpecializer { cb.exceptionCatchAll(tryStart, tryEnd, catchStart); } - private boolean needsSession() { + private boolean anyArgNeedsScope() { return callingSequence.argumentBindings() .filter(BoxAddress.class::isInstance) .map(BoxAddress.class::cast) @@ -590,7 +590,7 @@ public class BindingSpecializer { popType(long.class); cb.loadConstant(boxAddress.size()) .loadConstant(boxAddress.align()); - if (needsSession()) { + if (boxAddress.needsScope()) { emitLoadInternalSession(); cb.invokestatic(CD_Utils, "longToAddress", MTD_LONG_TO_ADDRESS_SCOPE); } else { diff --git a/test/jdk/java/foreign/TestUpcallStructScope.java b/test/jdk/java/foreign/TestUpcallStructScope.java index 14809139dff..d9729cb8f3b 100644 --- a/test/jdk/java/foreign/TestUpcallStructScope.java +++ b/test/jdk/java/foreign/TestUpcallStructScope.java @@ -43,13 +43,18 @@ import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BiConsumer; import java.util.function.Consumer; import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertTrue; +import static org.testng.Assert.assertEquals; public class TestUpcallStructScope extends NativeTestHelper { static final MethodHandle MH_do_upcall; + static final MethodHandle MH_do_upcall_ptr; static final MethodHandle MH_Consumer_accept; + static final MethodHandle MH_BiConsumer_accept; static { System.loadLibrary("TestUpcallStructScope"); @@ -57,17 +62,29 @@ public class TestUpcallStructScope extends NativeTestHelper { findNativeOrThrow("do_upcall"), FunctionDescriptor.ofVoid(C_POINTER, S_PDI_LAYOUT) ); + MH_do_upcall_ptr = LINKER.downcallHandle( + findNativeOrThrow("do_upcall_ptr"), + FunctionDescriptor.ofVoid(C_POINTER, S_PDI_LAYOUT, C_POINTER) + ); try { MH_Consumer_accept = MethodHandles.publicLookup().findVirtual(Consumer.class, "accept", MethodType.methodType(void.class, Object.class)); + MH_BiConsumer_accept = MethodHandles.publicLookup().findVirtual(BiConsumer.class, "accept", + MethodType.methodType(void.class, Object.class, Object.class)); } catch (NoSuchMethodException | IllegalAccessException e) { throw new RuntimeException(e); } } - private static MethodHandle methodHandle (Consumer callback) { - return MH_Consumer_accept.bindTo(callback).asType(MethodType.methodType(void.class, MemorySegment.class)); + private static MethodHandle methodHandle(Consumer callback) { + return MH_Consumer_accept.bindTo(callback) + .asType(MethodType.methodType(void.class, MemorySegment.class)); + } + + private static MethodHandle methodHandle(BiConsumer callback) { + return MH_BiConsumer_accept.bindTo(callback) + .asType(MethodType.methodType(void.class, MemorySegment.class, MemorySegment.class)); } @Test @@ -85,4 +102,22 @@ public class TestUpcallStructScope extends NativeTestHelper { assertFalse(captured.scope().isAlive()); } + @Test + public void testOtherPointer() throws Throwable { + AtomicReference capturedSegment = new AtomicReference<>(); + MethodHandle target = methodHandle((_, addr) -> capturedSegment.set(addr)); + FunctionDescriptor upcallDesc = FunctionDescriptor.ofVoid(S_PDI_LAYOUT, C_POINTER); + MemorySegment argAddr = MemorySegment.ofAddress(42); + try (Arena arena = Arena.ofConfined()) { + MemorySegment upcallStub = LINKER.upcallStub(target, upcallDesc, arena); + MemorySegment argSegment = arena.allocate(S_PDI_LAYOUT); + MH_do_upcall_ptr.invoke(upcallStub, argSegment, argAddr); + } + + // We've captured the address '42' from the upcall. This should have + // the global scope, so it should still be alive here. + MemorySegment captured = capturedSegment.get(); + assertEquals(argAddr, captured); + assertTrue(captured.scope().isAlive()); + } } diff --git a/test/jdk/java/foreign/libTestUpcallStructScope.c b/test/jdk/java/foreign/libTestUpcallStructScope.c index e778133b496..e18543d5b1a 100644 --- a/test/jdk/java/foreign/libTestUpcallStructScope.c +++ b/test/jdk/java/foreign/libTestUpcallStructScope.c @@ -28,3 +28,7 @@ struct S_PDI { void* p0; double p1; int p2; }; EXPORT void do_upcall(void (*cb)(struct S_PDI), struct S_PDI a0) { cb(a0); } + +EXPORT void do_upcall_ptr(void (*cb)(struct S_PDI, void*), struct S_PDI a0, void* ptr) { + cb(a0, ptr); +} From 30d20036987c9d68eb76b1e0401821386a76bb07 Mon Sep 17 00:00:00 2001 From: Roger Riggs Date: Fri, 18 Jul 2025 16:40:28 +0000 Subject: [PATCH 07/94] 8357380: java/lang/StringBuilder/RacingSBThreads.java times out with C1 Reviewed-by: jpai --- test/jdk/java/lang/StringBuilder/RacingSBThreads.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/jdk/java/lang/StringBuilder/RacingSBThreads.java b/test/jdk/java/lang/StringBuilder/RacingSBThreads.java index 9177f5de1aa..26f5cf9385a 100644 --- a/test/jdk/java/lang/StringBuilder/RacingSBThreads.java +++ b/test/jdk/java/lang/StringBuilder/RacingSBThreads.java @@ -46,7 +46,7 @@ import java.util.function.BiConsumer; public class RacingSBThreads { private static final int TIMEOUT_SEC = 1; // Duration to run each test case - private static final int N = 10_000_000; // static number of iterations for writes and modifies + private static final int N = 1_000_000; // static number of iterations for writes and modifies private static final int LEN = 100_000; // Length of initial SB // Strings available to be used as the initial contents of a StringBuilder From 60c29ff57b22fa7c0bedb38316067e8e1988a24b Mon Sep 17 00:00:00 2001 From: Jan Kratochvil Date: Fri, 18 Jul 2025 17:13:25 +0000 Subject: [PATCH 08/94] 8362524: Fix confusing but harmless typos in x86 CPU Features Reviewed-by: kbarrett, kvn --- src/hotspot/cpu/x86/vm_version_x86.cpp | 2 +- src/hotspot/share/jvmci/vmStructs_jvmci.cpp | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/hotspot/cpu/x86/vm_version_x86.cpp b/src/hotspot/cpu/x86/vm_version_x86.cpp index 7c2766a8aeb..42661bd7a2b 100644 --- a/src/hotspot/cpu/x86/vm_version_x86.cpp +++ b/src/hotspot/cpu/x86/vm_version_x86.cpp @@ -49,7 +49,7 @@ VM_Version::CpuidInfo VM_Version::_cpuid_info = { 0, }; #define DECLARE_CPU_FEATURE_NAME(id, name, bit) name, const char* VM_Version::_features_names[] = { CPU_FEATURE_FLAGS(DECLARE_CPU_FEATURE_NAME)}; -#undef DECLARE_CPU_FEATURE_FLAG +#undef DECLARE_CPU_FEATURE_NAME // Address of instruction which causes SEGV address VM_Version::_cpuinfo_segv_addr = nullptr; diff --git a/src/hotspot/share/jvmci/vmStructs_jvmci.cpp b/src/hotspot/share/jvmci/vmStructs_jvmci.cpp index bc930b1e1dc..e792ed209b8 100644 --- a/src/hotspot/share/jvmci/vmStructs_jvmci.cpp +++ b/src/hotspot/share/jvmci/vmStructs_jvmci.cpp @@ -1152,7 +1152,6 @@ VMLongConstantEntry JVMCIVMStructs::localHotSpotVMLongConstants[] = { #endif GENERATE_VM_LONG_CONSTANT_LAST_ENTRY() }; -#undef DECLARE_CPU_FEATURE_FLAG VMAddressEntry JVMCIVMStructs::localHotSpotVMAddresses[] = { VM_ADDRESSES(GENERATE_VM_ADDRESS_ENTRY, From a3843e8e6e189447e554759c3ba672530f8c7329 Mon Sep 17 00:00:00 2001 From: Alex Menkov Date: Fri, 18 Jul 2025 18:38:26 +0000 Subject: [PATCH 09/94] 8361751: Test sun/tools/jcmd/TestJcmdSanity.java timed out on Windows Reviewed-by: cjplummer, dholmes, sspitsyn --- test/jdk/sun/tools/jcmd/JcmdBase.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/jdk/sun/tools/jcmd/JcmdBase.java b/test/jdk/sun/tools/jcmd/JcmdBase.java index b0315673133..9a0f97f5bf4 100644 --- a/test/jdk/sun/tools/jcmd/JcmdBase.java +++ b/test/jdk/sun/tools/jcmd/JcmdBase.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 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 @@ -102,6 +102,8 @@ public final class JcmdBase { launcher.addVMArg(vmArg); } } + // Some command output may be lengthy, disable streaming output to avoid deadlocks + launcher.addVMArg("-Djdk.attach.allowStreamingOutput=false"); if (requestToCurrentProcess) { launcher.addToolArg(Long.toString(ProcessTools.getProcessId())); } From 03230f8565a4eea41ce13827165b6bbff5eaec68 Mon Sep 17 00:00:00 2001 From: Alexander Matveev Date: Fri, 18 Jul 2025 20:44:20 +0000 Subject: [PATCH 10/94] 8351073: [macos] jpackage produces invalid Java runtime DMG bundles Reviewed-by: asemenyuk --- .../jdk/jpackage/internal/CodesignConfig.java | 4 +- .../jdk/jpackage/internal/MacAppBundler.java | 2 +- .../jdk/jpackage/internal/MacBundle.java | 79 +++++++ .../jdk/jpackage/internal/MacFromParams.java | 16 +- .../internal/MacPackagingPipeline.java | 125 ++++++++--- .../internal/model/MacApplication.java | 6 +- .../ApplicationRuntime-Info.plist.template | 24 ++ .../resources/MacResources.properties | 1 + .../resources/Runtime-Info.plist.template | 15 ++ .../jdk/jpackage/internal/FromParams.java | 5 +- .../jpackage/internal/PackagingPipeline.java | 12 +- .../jdk/jpackage/internal/model/Package.java | 12 +- .../jdk/jpackage/test/JPackageCommand.java | 6 +- .../helpers/jdk/jpackage/test/MacHelper.java | 23 +- ...SigningPackageFromTwoStepAppImageTest.java | 2 +- .../jpackage/macosx/SigningPackageTest.java | 2 +- .../macosx/SigningPackageTwoStepTest.java | 2 +- .../SigningRuntimeImagePackageTest.java | 211 ++++++++++++++++++ 18 files changed, 480 insertions(+), 67 deletions(-) create mode 100644 src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBundle.java create mode 100644 src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/ApplicationRuntime-Info.plist.template create mode 100644 test/jdk/tools/jpackage/macosx/SigningRuntimeImagePackageTest.java diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/CodesignConfig.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/CodesignConfig.java index b59e6c8ad00..7280f49562c 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/CodesignConfig.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/CodesignConfig.java @@ -44,7 +44,9 @@ record CodesignConfig(Optional identity, Optional ident Objects.requireNonNull(keychain); if (identity.isPresent() != identifierPrefix.isPresent()) { - throw new IllegalArgumentException("Signing identity and identifier prefix mismatch"); + throw new IllegalArgumentException( + "Signing identity (" + identity + ") and identifier prefix (" + + identifierPrefix + ") mismatch"); } identifierPrefix.ifPresent(v -> { diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacAppBundler.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacAppBundler.java index 30c83bcbeab..28d91156059 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacAppBundler.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacAppBundler.java @@ -69,7 +69,7 @@ public class MacAppBundler extends AppImageBundler { } } - if (StandardBundlerParam.getPredefinedAppImage(params) != null) { + if (StandardBundlerParam.hasPredefinedAppImage(params)) { if (!Optional.ofNullable( SIGN_BUNDLE.fetchFrom(params)).orElse(Boolean.FALSE)) { throw new ConfigException( diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBundle.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBundle.java new file mode 100644 index 00000000000..af07a1145dc --- /dev/null +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacBundle.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.jpackage.internal; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Objects; +import jdk.jpackage.internal.model.AppImageLayout; + +/** + * An abstraction of macOS Application bundle. + * + * @see https://en.wikipedia.org/wiki/Bundle_(macOS)#Application_bundles + */ +record MacBundle(Path root) { + + MacBundle { + Objects.requireNonNull(root); + } + + boolean isValid() { + return Files.isDirectory(contentsDir()) && Files.isDirectory(macOsDir()) && Files.isRegularFile(infoPlistFile()); + } + + boolean isSigned() { + return Files.isDirectory(contentsDir().resolve("_CodeSignature")); + } + + Path contentsDir() { + return root.resolve("Contents"); + } + + Path homeDir() { + return contentsDir().resolve("Home"); + } + + Path macOsDir() { + return contentsDir().resolve("MacOS"); + } + + Path resourcesDir() { + return contentsDir().resolve("Resources"); + } + + Path infoPlistFile() { + return contentsDir().resolve("Info.plist"); + } + + static boolean isDirectoryMacBundle(Path dir) { + return new MacBundle(dir).isValid(); + } + + static MacBundle fromAppImageLayout(AppImageLayout layout) { + return new MacBundle(layout.rootDirectory()); + } +} diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacFromParams.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacFromParams.java index c13b9d939df..754d09a7156 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacFromParams.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacFromParams.java @@ -147,7 +147,15 @@ final class MacFromParams { signingBuilder.entitlementsResourceName("sandbox.plist"); } - app.mainLauncher().flatMap(Launcher::startupInfo).ifPresent(signingBuilder::signingIdentifierPrefix); + final var bundleIdentifier = appBuilder.create().bundleIdentifier(); + app.mainLauncher().flatMap(Launcher::startupInfo).ifPresentOrElse( + signingBuilder::signingIdentifierPrefix, + () -> { + // Runtime installer does not have main launcher, so use + // 'bundleIdentifier' as prefix by default. + signingBuilder.signingIdentifierPrefix( + bundleIdentifier + "."); + }); SIGN_IDENTIFIER_PREFIX.copyInto(params, signingBuilder::signingIdentifierPrefix); ENTITLEMENTS.copyInto(params, signingBuilder::entitlements); @@ -168,6 +176,12 @@ final class MacFromParams { .map(MacAppImageFileExtras::signed) .ifPresent(builder::predefinedAppImageSigned); + PREDEFINED_RUNTIME_IMAGE.findIn(params) + .map(MacBundle::new) + .filter(MacBundle::isValid) + .map(MacBundle::isSigned) + .ifPresent(builder::predefinedAppImageSigned); + return builder; } diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPackagingPipeline.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPackagingPipeline.java index eea69825a49..556efdd0fd3 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPackagingPipeline.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/MacPackagingPipeline.java @@ -41,6 +41,7 @@ import java.io.IOException; import java.io.StringWriter; import java.nio.charset.StandardCharsets; import java.nio.file.Files; +import java.nio.file.LinkOption; import java.nio.file.Path; import java.util.ArrayList; import java.util.HashMap; @@ -72,6 +73,7 @@ import jdk.jpackage.internal.model.MacPackage; import jdk.jpackage.internal.model.Package; import jdk.jpackage.internal.model.PackageType; import jdk.jpackage.internal.model.PackagerException; +import jdk.jpackage.internal.util.FileUtils; import jdk.jpackage.internal.util.PathUtils; import jdk.jpackage.internal.util.function.ThrowingConsumer; @@ -91,6 +93,7 @@ final class MacPackagingPipeline { enum MacCopyAppImageTaskID implements TaskID { COPY_PACKAGE_FILE, COPY_RUNTIME_INFO_PLIST, + COPY_RUNTIME_JLILIB, REPLACE_APP_IMAGE_FILE, COPY_SIGN } @@ -115,10 +118,10 @@ final class MacPackagingPipeline { .task(CopyAppImageTaskID.COPY) .copyAction(MacPackagingPipeline::copyAppImage).add() .task(MacBuildApplicationTaskID.RUNTIME_INFO_PLIST) - .applicationAction(MacPackagingPipeline::writeApplicationRuntimeInfoPlist) + .appImageAction(MacPackagingPipeline::writeRuntimeInfoPlist) .addDependent(BuildApplicationTaskID.CONTENT).add() .task(MacBuildApplicationTaskID.COPY_JLILIB) - .applicationAction(MacPackagingPipeline::copyJliLib) + .appImageAction(MacPackagingPipeline::copyJliLib) .addDependency(BuildApplicationTaskID.RUNTIME) .addDependent(BuildApplicationTaskID.CONTENT).add() .task(MacBuildApplicationTaskID.APP_ICON) @@ -138,13 +141,18 @@ final class MacPackagingPipeline { .addDependencies(CopyAppImageTaskID.COPY) .addDependents(PrimaryTaskID.COPY_APP_IMAGE).add() .task(MacCopyAppImageTaskID.COPY_RUNTIME_INFO_PLIST) + .appImageAction(MacPackagingPipeline::writeRuntimeInfoPlist) + .addDependencies(CopyAppImageTaskID.COPY) + .addDependents(PrimaryTaskID.COPY_APP_IMAGE).add() + .task(MacCopyAppImageTaskID.COPY_RUNTIME_JLILIB) + .noaction() .addDependencies(CopyAppImageTaskID.COPY) .addDependents(PrimaryTaskID.COPY_APP_IMAGE).add() .task(MacBuildApplicationTaskID.FA_ICONS) .applicationAction(MacPackagingPipeline::writeFileAssociationIcons) .addDependent(BuildApplicationTaskID.CONTENT).add() .task(MacBuildApplicationTaskID.APP_INFO_PLIST) - .applicationAction(MacPackagingPipeline::writeAppInfoPlist) + .applicationAction(MacPackagingPipeline::writeApplicationInfoPlist) .addDependent(BuildApplicationTaskID.CONTENT).add(); builder.task(MacBuildApplicationTaskID.SIGN) @@ -172,16 +180,38 @@ final class MacPackagingPipeline { disabledTasks.add(MacCopyAppImageTaskID.COPY_PACKAGE_FILE); disabledTasks.add(CopyAppImageTaskID.COPY); disabledTasks.add(PackageTaskID.RUN_POST_IMAGE_USER_SCRIPT); - builder.task(MacCopyAppImageTaskID.REPLACE_APP_IMAGE_FILE).applicationAction(createWriteAppImageFileAction()).add(); + builder.task(MacCopyAppImageTaskID.REPLACE_APP_IMAGE_FILE) + .applicationAction(createWriteAppImageFileAction()).add(); builder.appImageLayoutForPackaging(Package::appImageLayout); - } else if (p.isRuntimeInstaller() || ((MacPackage)p).predefinedAppImageSigned().orElse(false)) { - // If this is a runtime package or a signed predefined app image, - // don't create ".package" file and don't sign it. + } else if (p.isRuntimeInstaller()) { + + builder.task(MacCopyAppImageTaskID.COPY_RUNTIME_JLILIB) + .appImageAction(MacPackagingPipeline::copyJliLib).add(); + + final var predefinedRuntimeBundle = Optional.of( + new MacBundle(p.predefinedAppImage().orElseThrow())).filter(MacBundle::isValid); + + // Don't create ".package" file. disabledTasks.add(MacCopyAppImageTaskID.COPY_PACKAGE_FILE); + + if (predefinedRuntimeBundle.isPresent()) { + // The predefined app image is a macOS bundle. + // Disable all alterations of the input bundle, but keep the signing enabled. + disabledTasks.addAll(List.of(MacCopyAppImageTaskID.values())); + disabledTasks.remove(MacCopyAppImageTaskID.COPY_SIGN); + } + + if (predefinedRuntimeBundle.map(MacBundle::isSigned).orElse(false) && !((MacPackage)p).app().sign()) { + // The predefined app image is a signed bundle; explicit signing is not requested for the package. + // Disable the signing, i.e. don't re-sign the input bundle. + disabledTasks.add(MacCopyAppImageTaskID.COPY_SIGN); + } + } else if (((MacPackage)p).predefinedAppImageSigned().orElse(false)) { + // This is a signed predefined app image. + // Don't create ".package" file. + disabledTasks.add(MacCopyAppImageTaskID.COPY_PACKAGE_FILE); + // Don't sign the image. disabledTasks.add(MacCopyAppImageTaskID.COPY_SIGN); -// if (p.isRuntimeInstaller()) { -// builder.task(MacCopyAppImageTaskID.COPY_RUNTIME_INFO_PLIST).packageAction(MacPackagingPipeline::writeRuntimeRuntimeInfoPlist).add(); -// } } for (final var taskId : disabledTasks) { @@ -208,13 +238,27 @@ final class MacPackagingPipeline { private static void copyAppImage(MacPackage pkg, AppImageDesc srcAppImage, AppImageDesc dstAppImage) throws IOException { - PackagingPipeline.copyAppImage(srcAppImage, dstAppImage, !pkg.predefinedAppImageSigned().orElse(false)); + + boolean predefinedAppImageSigned = pkg.predefinedAppImageSigned().orElse(false); + + var inputRootDirectory = srcAppImage.resolvedAppImagelayout().rootDirectory(); + + if (pkg.isRuntimeInstaller() && MacBundle.isDirectoryMacBundle(inputRootDirectory)) { + // Building runtime package from the input runtime bundle. + // Copy the input bundle verbatim. + FileUtils.copyRecursive( + inputRootDirectory, + dstAppImage.resolvedAppImagelayout().rootDirectory(), + LinkOption.NOFOLLOW_LINKS); + } else { + PackagingPipeline.copyAppImage(srcAppImage, dstAppImage, !predefinedAppImageSigned); + } } private static void copyJliLib( - AppImageBuildEnv env) throws IOException { + AppImageBuildEnv env) throws IOException { - final var runtimeMacOSDir = env.resolvedLayout().runtimeRootDirectory().resolve("Contents/MacOS"); + final var runtimeBundle = runtimeBundle(env); final var jliName = Path.of("libjli.dylib"); @@ -223,8 +267,8 @@ final class MacPackagingPipeline { .filter(file -> file.getFileName().equals(jliName)) .findFirst() .orElseThrow(); - Files.createDirectories(runtimeMacOSDir); - Files.copy(jli, runtimeMacOSDir.resolve(jliName)); + Files.createDirectories(runtimeBundle.macOsDir()); + Files.copy(jli, runtimeBundle.macOsDir().resolve(jliName)); } } @@ -247,36 +291,47 @@ final class MacPackagingPipeline { "APPL????".getBytes(StandardCharsets.ISO_8859_1)); } - private static void writeRuntimeRuntimeInfoPlist(PackageBuildEnv env) throws IOException { - writeRuntimeInfoPlist(env.pkg().app(), env.env(), env.resolvedLayout().rootDirectory()); - } + private static void writeRuntimeInfoPlist( + AppImageBuildEnv env) throws IOException { - private static void writeApplicationRuntimeInfoPlist( - AppImageBuildEnv env) throws IOException { - writeRuntimeInfoPlist(env.app(), env.env(), env.resolvedLayout().runtimeRootDirectory()); - } - - private static void writeRuntimeInfoPlist(MacApplication app, BuildEnv env, Path runtimeRootDirectory) throws IOException { + final var app = env.app(); Map data = new HashMap<>(); data.put("CF_BUNDLE_IDENTIFIER", app.bundleIdentifier()); data.put("CF_BUNDLE_NAME", app.bundleName()); data.put("CF_BUNDLE_VERSION", app.version()); data.put("CF_BUNDLE_SHORT_VERSION_STRING", app.shortVersion().toString()); + if (app.isRuntime()) { + data.put("CF_BUNDLE_VENDOR", app.vendor()); + } - env.createResource("Runtime-Info.plist.template") - .setPublicName("Runtime-Info.plist") - .setCategory(I18N.getString("resource.runtime-info-plist")) + final String template; + final String publicName; + final String category; + + if (app.isRuntime()) { + template = "Runtime-Info.plist.template"; + publicName = "Info.plist"; + category = "resource.runtime-info-plist"; + } else { + template = "ApplicationRuntime-Info.plist.template"; + publicName = "Runtime-Info.plist"; + category = "resource.app-runtime-info-plist"; + } + + env.env().createResource(template) + .setPublicName(publicName) + .setCategory(I18N.getString(category)) .setSubstitutionData(data) - .saveToFile(runtimeRootDirectory.resolve("Contents/Info.plist")); + .saveToFile(runtimeBundle(env).infoPlistFile()); } - private static void writeAppInfoPlist( + private static void writeApplicationInfoPlist( AppImageBuildEnv env) throws IOException { final var app = env.app(); - final var infoPlistFile = env.resolvedLayout().contentDirectory().resolve("Info.plist"); + final var infoPlistFile = MacBundle.fromAppImageLayout(env.resolvedLayout()).infoPlistFile(); Log.verbose(I18N.format("message.preparing-info-plist", PathUtils.normalizedAbsolutePathString(infoPlistFile))); @@ -308,7 +363,7 @@ final class MacPackagingPipeline { .saveToFile(infoPlistFile); } - private static void sign(AppImageBuildEnv env) throws IOException { + private static void sign(AppImageBuildEnv env) throws IOException { final var app = env.app(); @@ -410,6 +465,14 @@ final class MacPackagingPipeline { })); } + private static MacBundle runtimeBundle(AppImageBuildEnv env) { + if (env.app().isRuntime()) { + return new MacBundle(env.resolvedLayout().rootDirectory()); + } else { + return new MacBundle(((MacApplicationLayout)env.resolvedLayout()).runtimeRootDirectory()); + } + } + private static class ApplicationIcon implements ApplicationImageTaskAction { static Path getPath(Application app, ApplicationLayout appLayout) { return appLayout.desktopIntegrationDirectory().resolve(app.name() + ".icns"); diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/model/MacApplication.java b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/model/MacApplication.java index a38ac65bbaf..04ab7042ac5 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/model/MacApplication.java +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/model/MacApplication.java @@ -54,11 +54,13 @@ public interface MacApplication extends Application, MacApplicationMixin { @Override default Path appImageDirName() { + final String suffix; if (isRuntime()) { - return Application.super.appImageDirName(); + suffix = ".jdk"; } else { - return Path.of(Application.super.appImageDirName().toString() + ".app"); + suffix = ".app"; } + return Path.of(Application.super.appImageDirName().toString() + suffix); } /** diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/ApplicationRuntime-Info.plist.template b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/ApplicationRuntime-Info.plist.template new file mode 100644 index 00000000000..e24cc94fa8e --- /dev/null +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/ApplicationRuntime-Info.plist.template @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + libjli.dylib + CFBundleIdentifier + CF_BUNDLE_IDENTIFIER + CFBundleInfoDictionaryVersion + 7.0 + CFBundleName + CF_BUNDLE_NAME + CFBundlePackageType + BNDL + CFBundleShortVersionString + CF_BUNDLE_SHORT_VERSION_STRING + CFBundleSignature + ???? + CFBundleVersion + CF_BUNDLE_VERSION + + diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources.properties b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources.properties index 1325e3be4f4..7fada9e4305 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources.properties +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/MacResources.properties @@ -41,6 +41,7 @@ error.app-image.mac-sign.required=Error: --mac-sign option is required with pred error.tool.failed.with.output=Error: "{0}" failed with following output: resource.bundle-config-file=Bundle config file resource.app-info-plist=Application Info.plist +resource.app-runtime-info-plist=Embedded Java Runtime Info.plist resource.runtime-info-plist=Java Runtime Info.plist resource.entitlements=Mac Entitlements resource.dmg-setup-script=DMG setup script diff --git a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/Runtime-Info.plist.template b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/Runtime-Info.plist.template index e24cc94fa8e..5a1492e2eab 100644 --- a/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/Runtime-Info.plist.template +++ b/src/jdk.jpackage/macosx/classes/jdk/jpackage/internal/resources/Runtime-Info.plist.template @@ -20,5 +20,20 @@ ???? CFBundleVersion CF_BUNDLE_VERSION + NSMicrophoneUsageDescription + The application is requesting access to the microphone. + JavaVM + + JVMCapabilities + + CommandLine + + JVMPlatformVersion + CF_BUNDLE_VERSION + JVMVendor + CF_BUNDLE_VENDOR + JVMVersion + CF_BUNDLE_VERSION + diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FromParams.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FromParams.java index 92059b87590..5e940aba18b 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FromParams.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/FromParams.java @@ -41,12 +41,12 @@ import static jdk.jpackage.internal.StandardBundlerParam.LICENSE_FILE; import static jdk.jpackage.internal.StandardBundlerParam.LIMIT_MODULES; import static jdk.jpackage.internal.StandardBundlerParam.MODULE_PATH; import static jdk.jpackage.internal.StandardBundlerParam.NAME; +import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_APP_IMAGE; import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_APP_IMAGE_FILE; import static jdk.jpackage.internal.StandardBundlerParam.PREDEFINED_RUNTIME_IMAGE; import static jdk.jpackage.internal.StandardBundlerParam.SOURCE_DIR; import static jdk.jpackage.internal.StandardBundlerParam.VENDOR; import static jdk.jpackage.internal.StandardBundlerParam.VERSION; -import static jdk.jpackage.internal.StandardBundlerParam.getPredefinedAppImage; import static jdk.jpackage.internal.StandardBundlerParam.hasPredefinedAppImage; import static jdk.jpackage.internal.StandardBundlerParam.isRuntimeInstaller; @@ -143,7 +143,8 @@ final class FromParams { VERSION.copyInto(params, builder::version); ABOUT_URL.copyInto(params, builder::aboutURL); LICENSE_FILE.findIn(params).map(Path::of).ifPresent(builder::licenseFile); - builder.predefinedAppImage(getPredefinedAppImage(params)); + PREDEFINED_APP_IMAGE.findIn(params).ifPresent(builder::predefinedAppImage); + PREDEFINED_RUNTIME_IMAGE.findIn(params).ifPresent(builder::predefinedAppImage); INSTALL_DIR.findIn(params).map(Path::of).ifPresent(builder::installDir); return builder; diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/PackagingPipeline.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/PackagingPipeline.java index d36aad23886..10590a7aa8b 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/PackagingPipeline.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/PackagingPipeline.java @@ -437,16 +437,8 @@ final class PackagingPipeline { srcAppImageDesc = new AppImageDesc(appImageLayoutForPackaging, env.appImageDir()); dstAppImageDesc = srcAppImageDesc; } else { - srcAppImageDesc = new AppImageDesc(pkg.app().imageLayout(), pkg.predefinedAppImage().orElseGet(() -> { - // No predefined app image and no runtime builder. - // This should be runtime packaging. - if (pkg.isRuntimeInstaller()) { - return env.appImageDir(); - } else { - // Can't create app image without runtime builder. - throw new UnsupportedOperationException(); - } - })); + srcAppImageDesc = new AppImageDesc(pkg.app().imageLayout(), + pkg.predefinedAppImage().orElseThrow(UnsupportedOperationException::new)); if (taskConfig.get(CopyAppImageTaskID.COPY).action().isEmpty()) { // "copy app image" task action is undefined indicating diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/Package.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/Package.java index 35db967400f..6d2fdaf0bbb 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/Package.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/model/Package.java @@ -148,8 +148,16 @@ public interface Package extends BundleSpec { Optional licenseFile(); /** - * Gets the path to a directory with the application app image of this package - * if available or an empty {@link Optional} instance otherwise. + * Gets the path to a directory with the predefined app image of this package if + * available or an empty {@link Optional} instance otherwise. + *

+ * If {@link #isRuntimeInstaller()} returns {@code true}, the method returns the + * path to a directory with the predefined runtime. The layout of this directory + * should be of {@link RuntimeLayout} type. + *

+ * If {@link #isRuntimeInstaller()} returns {@code false}, the method returns + * the path to a directory with the predefined application image. The layout of + * this directory should be of {@link ApplicationLayout} type. * * @return the path to a directory with the application app image of this * package diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java index 7f9feb986b4..169457d6f58 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/JPackageCommand.java @@ -1082,11 +1082,7 @@ public class JPackageCommand extends CommandArguments { TKit.assertDirectoryExists(cmd.appRuntimeDirectory()); if (TKit.isOSX()) { var libjliPath = cmd.appRuntimeDirectory().resolve("Contents/MacOS/libjli.dylib"); - if (cmd.isRuntime()) { - TKit.assertPathExists(libjliPath, false); - } else { - TKit.assertFileExists(libjliPath); - } + TKit.assertFileExists(libjliPath); } }), MAC_BUNDLE_STRUCTURE(cmd -> { diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MacHelper.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MacHelper.java index 7b676737ed3..f4feb3e2fde 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MacHelper.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/MacHelper.java @@ -334,7 +334,7 @@ public final class MacHelper { installLocation = cmd.getArgumentValue("--install-dir", () -> defaultInstallLocation, Path::of); } - return installLocation.resolve(cmd.name() + (cmd.isRuntime() ? "" : ".app")); + return installLocation.resolve(cmd.name() + (cmd.isRuntime() ? ".jdk" : ".app")); } static Path getUninstallCommand(JPackageCommand cmd) { @@ -400,22 +400,27 @@ public final class MacHelper { Executor.of("/usr/bin/xcrun", "--help").executeWithoutExitCodeCheck().getExitCode() == 0; } + private static Set createBundleContents(String... customItems) { + return Stream.concat(Stream.of(customItems), Stream.of( + "MacOS", + "Info.plist", + "_CodeSignature" + )).map(Path::of).collect(toSet()); + } + static final Set CRITICAL_RUNTIME_FILES = Set.of(Path.of( "Contents/Home/lib/server/libjvm.dylib")); private static final Method getServicePListFileName = initGetServicePListFileName(); - private static final Set APP_BUNDLE_CONTENTS = Stream.of( - "Info.plist", - "MacOS", + private static final Set APP_BUNDLE_CONTENTS = createBundleContents( "app", "runtime", "Resources", - "PkgInfo", - "_CodeSignature" - ).map(Path::of).collect(toSet()); + "PkgInfo" + ); - private static final Set RUNTIME_BUNDLE_CONTENTS = Stream.of( + private static final Set RUNTIME_BUNDLE_CONTENTS = createBundleContents( "Home" - ).map(Path::of).collect(toSet()); + ); } diff --git a/test/jdk/tools/jpackage/macosx/SigningPackageFromTwoStepAppImageTest.java b/test/jdk/tools/jpackage/macosx/SigningPackageFromTwoStepAppImageTest.java index a612c36ca62..d25d9a7fa81 100644 --- a/test/jdk/tools/jpackage/macosx/SigningPackageFromTwoStepAppImageTest.java +++ b/test/jdk/tools/jpackage/macosx/SigningPackageFromTwoStepAppImageTest.java @@ -41,7 +41,7 @@ import jdk.jpackage.test.Annotations.Parameter; * jpackagerTest keychain with always allowed access to this keychain for user * which runs test. * note: - * "jpackage.openjdk.java.net" can be over-ridden by systerm property + * "jpackage.openjdk.java.net" can be over-ridden by system property * "jpackage.mac.signing.key.user.name", and * "jpackagerTest" can be over-ridden by system property * "jpackage.mac.signing.keychain" diff --git a/test/jdk/tools/jpackage/macosx/SigningPackageTest.java b/test/jdk/tools/jpackage/macosx/SigningPackageTest.java index e41b0d60397..f0aae601877 100644 --- a/test/jdk/tools/jpackage/macosx/SigningPackageTest.java +++ b/test/jdk/tools/jpackage/macosx/SigningPackageTest.java @@ -39,7 +39,7 @@ import jdk.jpackage.test.Annotations.Parameter; * jpackagerTest keychain with * always allowed access to this keychain for user which runs test. * note: - * "jpackage.openjdk.java.net" can be over-ridden by systerm property + * "jpackage.openjdk.java.net" can be over-ridden by system property * "jpackage.mac.signing.key.user.name", and * "jpackagerTest" can be over-ridden by system property * "jpackage.mac.signing.keychain" diff --git a/test/jdk/tools/jpackage/macosx/SigningPackageTwoStepTest.java b/test/jdk/tools/jpackage/macosx/SigningPackageTwoStepTest.java index ccb78fee9f8..3522d8d43e5 100644 --- a/test/jdk/tools/jpackage/macosx/SigningPackageTwoStepTest.java +++ b/test/jdk/tools/jpackage/macosx/SigningPackageTwoStepTest.java @@ -42,7 +42,7 @@ import jdk.jpackage.test.Annotations.Parameter; * jpackagerTest keychain with * always allowed access to this keychain for user which runs test. * note: - * "jpackage.openjdk.java.net" can be over-ridden by systerm property + * "jpackage.openjdk.java.net" can be over-ridden by system property * "jpackage.mac.signing.key.user.name", and * "jpackagerTest" can be over-ridden by system property * "jpackage.mac.signing.keychain" diff --git a/test/jdk/tools/jpackage/macosx/SigningRuntimeImagePackageTest.java b/test/jdk/tools/jpackage/macosx/SigningRuntimeImagePackageTest.java new file mode 100644 index 00000000000..8032a4532e9 --- /dev/null +++ b/test/jdk/tools/jpackage/macosx/SigningRuntimeImagePackageTest.java @@ -0,0 +1,211 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.IOException; +import java.nio.file.Path; +import java.util.function.Predicate; +import java.util.stream.Stream; +import jdk.jpackage.test.Annotations.Parameter; +import jdk.jpackage.test.Annotations.Test; +import jdk.jpackage.test.Executor; +import jdk.jpackage.test.JPackageCommand; +import jdk.jpackage.test.JavaTool; +import jdk.jpackage.test.MacHelper; +import jdk.jpackage.test.PackageTest; +import jdk.jpackage.test.PackageType; +import jdk.jpackage.test.TKit; + +/** + * Tests generation of dmg and pkg with --mac-sign and related arguments. + * Test will generate pkg and verifies its signature. It verifies that dmg + * is not signed, but runtime image inside dmg is signed. + * + * Note: Specific UNICODE signing is not tested, since it is shared code + * with app image signing and it will be covered by SigningPackageTest. + * + * Following combinations are tested: + * 1) "--runtime-image" points to unsigned JDK bundle and --mac-sign is not + * provided. Expected result: runtime image ad-hoc signed. + * 2) "--runtime-image" points to unsigned JDK bundle and --mac-sign is + * provided. Expected result: Everything is signed with provided certificate. + * 3) "--runtime-image" points to signed JDK bundle and --mac-sign is not + * provided. Expected result: runtime image is signed with original certificate. + * 4) "--runtime-image" points to signed JDK bundle and --mac-sign is provided. + * Expected result: runtime image is signed with provided certificate. + * 5) "--runtime-image" points to JDK image and --mac-sign is not provided. + * Expected result: runtime image ad-hoc signed. + * 6) "--runtime-image" points to JDK image and --mac-sign is provided. + * Expected result: Everything is signed with provided certificate. + * + * This test requires that the machine is configured with test certificate for + * "Developer ID Installer: jpackage.openjdk.java.net" in + * jpackagerTest keychain with + * always allowed access to this keychain for user which runs test. + * note: + * "jpackage.openjdk.java.net" can be over-ridden by system property + * "jpackage.mac.signing.key.user.name", and + * "jpackagerTest" can be over-ridden by system property + * "jpackage.mac.signing.keychain" + */ + +/* + * @test + * @summary jpackage with --type pkg,dmg --runtime-image --mac-sign + * @library /test/jdk/tools/jpackage/helpers + * @library base + * @key jpackagePlatformPackage + * @build SigningBase + * @build jdk.jpackage.test.* + * @build SigningRuntimeImagePackageTest + * @requires (jpackage.test.MacSignTests == "run") + * @run main/othervm/timeout=720 -Xmx512m jdk.jpackage.test.Main + * --jpt-run=SigningRuntimeImagePackageTest + * --jpt-before-run=SigningBase.verifySignTestEnvReady + */ +public class SigningRuntimeImagePackageTest { + + private static JPackageCommand addSignOptions(JPackageCommand cmd, int certIndex) { + if (certIndex != SigningBase.CertIndex.INVALID_INDEX.value()) { + cmd.addArguments( + "--mac-sign", + "--mac-signing-keychain", SigningBase.getKeyChain(), + "--mac-signing-key-user-name", SigningBase.getDevName(certIndex)); + } + return cmd; + } + + private static Path createInputRuntimeImage() throws IOException { + + final Path runtimeImageDir; + + if (JPackageCommand.DEFAULT_RUNTIME_IMAGE != null) { + runtimeImageDir = JPackageCommand.DEFAULT_RUNTIME_IMAGE; + } else { + runtimeImageDir = TKit.createTempDirectory("runtime-image").resolve("data"); + + new Executor().setToolProvider(JavaTool.JLINK) + .dumpOutput() + .addArguments( + "--output", runtimeImageDir.toString(), + "--add-modules", "java.desktop", + "--strip-debug", + "--no-header-files", + "--no-man-pages") + .execute(); + } + + return runtimeImageDir; + } + + private static Path createInputRuntimeBundle(int certIndex) throws IOException { + + final var runtimeImage = createInputRuntimeImage(); + + final var runtimeBundleWorkDir = TKit.createTempDirectory("runtime-bundle"); + + final var unpackadeRuntimeBundleDir = runtimeBundleWorkDir.resolve("unpacked"); + + var cmd = new JPackageCommand() + .useToolProvider(true) + .ignoreDefaultRuntime(true) + .dumpOutput(true) + .setPackageType(PackageType.MAC_DMG) + .setArgumentValue("--name", "foo") + .addArguments("--runtime-image", runtimeImage) + .addArguments("--dest", runtimeBundleWorkDir); + + addSignOptions(cmd, certIndex); + + cmd.execute(); + + MacHelper.withExplodedDmg(cmd, dmgImage -> { + if (dmgImage.endsWith(cmd.appInstallationDirectory().getFileName())) { + Executor.of("cp", "-R") + .addArgument(dmgImage) + .addArgument(unpackadeRuntimeBundleDir) + .execute(0); + } + }); + + return unpackadeRuntimeBundleDir; + } + + @Test + // useJDKBundle - If "true" predefined runtime image will be converted to + // JDK bundle. If "false" JDK image will be used. + // JDKBundleCert - Certificate to sign JDK bundle before calling jpackage. + // signCert - Certificate to sign bundle produced by jpackage. + // 1) unsigned JDK bundle and --mac-sign is not provided + @Parameter({"true", "INVALID_INDEX", "INVALID_INDEX"}) + // 2) unsigned JDK bundle and --mac-sign is provided + @Parameter({"true", "INVALID_INDEX", "ASCII_INDEX"}) + // 3) signed JDK bundle and --mac-sign is not provided + @Parameter({"true", "UNICODE_INDEX", "INVALID_INDEX"}) + // 4) signed JDK bundle and --mac-sign is provided + @Parameter({"true", "UNICODE_INDEX", "ASCII_INDEX"}) + // 5) JDK image and --mac-sign is not provided + @Parameter({"false", "INVALID_INDEX", "INVALID_INDEX"}) + // 6) JDK image and --mac-sign is provided + @Parameter({"false", "INVALID_INDEX", "ASCII_INDEX"}) + public static void test(boolean useJDKBundle, + SigningBase.CertIndex jdkBundleCert, + SigningBase.CertIndex signCert) throws Exception { + + final Path inputRuntime[] = new Path[1]; + + new PackageTest() + .addRunOnceInitializer(() -> { + if (useJDKBundle) { + inputRuntime[0] = createInputRuntimeBundle(jdkBundleCert.value()); + } else { + inputRuntime[0] = createInputRuntimeImage(); + } + }) + .addInitializer(cmd -> { + cmd.addArguments("--runtime-image", inputRuntime[0]); + // Remove --input parameter from jpackage command line as we don't + // create input directory in the test and jpackage fails + // if --input references non existent directory. + cmd.removeArgumentWithValue("--input"); + addSignOptions(cmd, signCert.value()); + }) + .addInstallVerifier(cmd -> { + final var certIndex = Stream.of(signCert, jdkBundleCert) + .filter(Predicate.isEqual(SigningBase.CertIndex.INVALID_INDEX).negate()) + .findFirst().orElse(SigningBase.CertIndex.INVALID_INDEX).value(); + + final var signed = certIndex != SigningBase.CertIndex.INVALID_INDEX.value(); + + final var unfoldedBundleDir = cmd.appRuntimeDirectory(); + + final var libjli = unfoldedBundleDir.resolve("Contents/MacOS/libjli.dylib"); + + SigningBase.verifyCodesign(libjli, signed, certIndex); + SigningBase.verifyCodesign(unfoldedBundleDir, signed, certIndex); + if (signed) { + SigningBase.verifySpctl(unfoldedBundleDir, "exec", certIndex); + } + }) + .run(); + } +} From 9334fe2eca05e852875ed6aad42b5094a32e9b15 Mon Sep 17 00:00:00 2001 From: Ioi Lam Date: Fri, 18 Jul 2025 21:30:21 +0000 Subject: [PATCH 11/94] 8361725: Do not load Java agent with "-Xshare:dump -XX:+AOTClassLinking" Reviewed-by: matsaave, ccheung --- src/hotspot/share/cds/cdsConfig.cpp | 8 ++++ src/hotspot/share/prims/jvmtiAgentList.cpp | 10 ++-- src/hotspot/share/prims/jvmtiAgentList.hpp | 2 +- .../cds/appcds/aotCache/JavaAgent.java | 48 +++++++++++++++++-- .../appcds/aotCache/JavaAgentTransformer.java | 4 ++ 5 files changed, 64 insertions(+), 8 deletions(-) diff --git a/src/hotspot/share/cds/cdsConfig.cpp b/src/hotspot/share/cds/cdsConfig.cpp index ad0374c04eb..85c40df2606 100644 --- a/src/hotspot/share/cds/cdsConfig.cpp +++ b/src/hotspot/share/cds/cdsConfig.cpp @@ -598,6 +598,7 @@ void CDSConfig::check_aotmode_create() { // // Since application is not executed in the assembly phase, there's no need to load // the agents anyway -- no one will notice that the agents are not loaded. + log_info(aot)("Disabled all JVMTI agents during -XX:AOTMode=create"); JvmtiAgentList::disable_agent_list(); } @@ -702,6 +703,13 @@ bool CDSConfig::check_vm_args_consistency(bool patch_mod_javabase, bool mode_fla } } + if (is_dumping_classic_static_archive() && AOTClassLinking) { + if (JvmtiAgentList::disable_agent_list()) { + FLAG_SET_ERGO(AllowArchivingWithJavaAgent, false); + log_warning(cds)("Disabled all JVMTI agents with -Xshare:dump -XX:+AOTClassLinking"); + } + } + return true; } diff --git a/src/hotspot/share/prims/jvmtiAgentList.cpp b/src/hotspot/share/prims/jvmtiAgentList.cpp index 7128dc8f5d1..ec64ccaf70c 100644 --- a/src/hotspot/share/prims/jvmtiAgentList.cpp +++ b/src/hotspot/share/prims/jvmtiAgentList.cpp @@ -273,11 +273,13 @@ JvmtiAgent* JvmtiAgentList::lookup(JvmtiEnv* env, void* f_ptr) { return nullptr; } -void JvmtiAgentList::disable_agent_list() { +bool JvmtiAgentList::disable_agent_list() { #if INCLUDE_CDS - assert(CDSConfig::is_dumping_final_static_archive(), "use this only for -XX:AOTMode=create!"); assert(!Universe::is_bootstrapping() && !Universe::is_fully_initialized(), "must do this very early"); - log_info(aot)("Disabled all JVMTI agents during -XX:AOTMode=create"); - _head = nullptr; // Pretend that no agents have been added. + if (_head != nullptr) { + _head = nullptr; // Pretend that no agents have been added. + return true; + } #endif + return false; } diff --git a/src/hotspot/share/prims/jvmtiAgentList.hpp b/src/hotspot/share/prims/jvmtiAgentList.hpp index f757020c2a1..4395bf9d8f9 100644 --- a/src/hotspot/share/prims/jvmtiAgentList.hpp +++ b/src/hotspot/share/prims/jvmtiAgentList.hpp @@ -83,7 +83,7 @@ class JvmtiAgentList : AllStatic { static Iterator java_agents(); static Iterator native_agents(); static Iterator xrun_agents(); - static void disable_agent_list() NOT_JVMTI_RETURN; + static bool disable_agent_list() NOT_JVMTI_RETURN_(false); }; #endif // SHARE_PRIMS_JVMTIAGENTLIST_HPP diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/JavaAgent.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/JavaAgent.java index 070f6df9834..a0feb76a910 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/JavaAgent.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/JavaAgent.java @@ -24,7 +24,18 @@ /* - * @test + * @test id=static + * @bug 8361725 + * @summary -javaagent should be disabled with -Xshare:dump -XX:+AOTClassLinking + * @requires vm.cds.supports.aot.class.linking + * @library /test/lib /test/hotspot/jtreg/runtime/cds/appcds/test-classes + * @build JavaAgent JavaAgentTransformer Util + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar app.jar JavaAgentApp JavaAgentApp$ShouldBeTransformed + * @run driver JavaAgent STATIC + */ + +/* + * @test id=aot * @summary -javaagent should be allowed in AOT workflow. However, classes transformed/redefined by agents will not * be cached. * @requires vm.cds.supports.aot.class.linking @@ -68,7 +79,13 @@ public class JavaAgent { @Override public String[] vmArgs(RunMode runMode) { - return new String[] { "-javaagent:" + agentJar, "-Xlog:aot,cds"}; + return new String[] { + "-XX:+UnlockDiagnosticVMOptions", + "-XX:+AllowArchivingWithJavaAgent", + "-javaagent:" + agentJar, + "-Xlog:aot,cds", + "-XX:+AOTClassLinking", + }; } @Override @@ -80,7 +97,18 @@ public class JavaAgent { @Override public void checkExecution(OutputAnalyzer out, RunMode runMode) throws Exception { - String agentLoadedMsg = "JavaAgentTransformer.premain() is called"; + if (isAOTWorkflow()) { + checkExecutionForAOTWorkflow(out, runMode); + } else { + checkExecutionForStaticWorkflow(out, runMode); + } + } + + static String agentLoadedMsg = "JavaAgentTransformer.premain() is called"; + static String agentPremainFinished = "JavaAgentTransformer::premain() is finished"; + + public void checkExecutionForAOTWorkflow(OutputAnalyzer out, RunMode runMode) throws Exception { + if (runMode.isApplicationExecuted()) { out.shouldContain(agentLoadedMsg); out.shouldContain("Transforming: JavaAgentApp$ShouldBeTransformed; Class = null"); @@ -91,13 +119,27 @@ public class JavaAgent { switch (runMode) { case RunMode.TRAINING: + out.shouldContain(agentPremainFinished); out.shouldContain("Skipping JavaAgentApp$ShouldBeTransformed: From ClassFileLoadHook"); out.shouldContain("Skipping JavaAgentTransformer: Unsupported location"); break; case RunMode.ASSEMBLY: out.shouldContain("Disabled all JVMTI agents during -XX:AOTMode=create"); + out.shouldNotContain(agentPremainFinished); break; } + + } + + public void checkExecutionForStaticWorkflow(OutputAnalyzer out, RunMode runMode) throws Exception { + switch (runMode) { + case RunMode.DUMP_STATIC: + out.shouldContain("Disabled all JVMTI agents with -Xshare:dump -XX:+AOTClassLinking"); + out.shouldNotContain(agentPremainFinished); + break; + default: + out.shouldContain(agentPremainFinished); + } } } } diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/JavaAgentTransformer.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/JavaAgentTransformer.java index 3c19c0e6c5d..123e4a0d72b 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/JavaAgentTransformer.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/JavaAgentTransformer.java @@ -22,6 +22,7 @@ * */ +import java.lang.System.Logger.Level; import java.lang.instrument.ClassFileTransformer; import java.lang.instrument.IllegalClassFormatException; import java.lang.instrument.Instrumentation; @@ -30,11 +31,14 @@ import java.security.ProtectionDomain; // This class is available on the classpath so it can be accessed by JavaAgentApp public class JavaAgentTransformer implements ClassFileTransformer { private static Instrumentation savedInstrumentation; + private static final System.Logger LOGGER = System.getLogger(JavaAgentTransformer.class.getName()); public static void premain(String agentArguments, Instrumentation instrumentation) { System.out.println("JavaAgentTransformer.premain() is called"); instrumentation.addTransformer(new JavaAgentTransformer(), /*canRetransform=*/true); savedInstrumentation = instrumentation; + + LOGGER.log(Level.WARNING, "JavaAgentTransformer::premain() is finished"); } public static Instrumentation getInstrumentation() { From d83346dcff0824575d580ec421476c0ea5c6e783 Mon Sep 17 00:00:00 2001 From: John R Rose Date: Fri, 18 Jul 2025 21:31:42 +0000 Subject: [PATCH 12/94] 8345836: Stable annotation documentation is incomplete Reviewed-by: liach --- .../jdk/internal/vm/annotation/Stable.java | 352 +++++++++++++++--- 1 file changed, 310 insertions(+), 42 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/vm/annotation/Stable.java b/src/java.base/share/classes/jdk/internal/vm/annotation/Stable.java index 0d1eb97b730..87da0e08403 100644 --- a/src/java.base/share/classes/jdk/internal/vm/annotation/Stable.java +++ b/src/java.base/share/classes/jdk/internal/vm/annotation/Stable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -28,56 +28,324 @@ package jdk.internal.vm.annotation; import java.lang.annotation.*; /** - * A field may be annotated as stable if all of its component variables - * changes value at most once. - * A field's value counts as its component value. - * If the field is typed as an array, then all the non-null components - * of the array, of depth up to the rank of the field's array type, - * also count as component values. - * By extension, any variable (either array or field) which has annotated - * as stable is called a stable variable, and its non-null or non-zero - * value is called a stable value. + * A field may be annotated as "stable" to indicate that it is a + * stable variable, expected to change its value just once. + * All Java fields are initialized by the VM with a default value + * (null or zero). A properly used stable field will be set + * just once to a non-default value, and keep that value forever. + * While the field contains its default (null or zero) value, + * the VM treats it as an ordinary mutable variable. When a + * non-default value is stored into the field, the VM is permitted + * to assume that no more significant changes will occur. This in + * turn enables the VM to optimize uses of the stable variable, treating + * them as constant values. This behavior is a useful building block + * for lazy evaluation or memoization of results. In rare and subtle + * use cases, stable variables may also assume multiple values over + * time, with effects as described below. *

- * Since all fields begin with a default value of null for references - * (resp., zero for primitives), it follows that this annotation indicates - * that the first non-null (resp., non-zero) value stored in the field - * will never be changed. + * (Warning: the {@code @Stable} annotation is intended for use in the + * JDK implemention, and with the HotSpot VM, to support optimization + * of classes and algorithms defined by the JDK. It is unavailable + * outside the JDK.) + * + *

Stable Variable Life Cycle

+ * + * For example, suppose a class has two non-final fields of type + * {@code int} and {@code String}. Annotating the field declarations + * with {@code @Stable} creates a pair of stable variables. The + * fields are initialized to zero and the null reference, + * respectively, in the usual way, but storing a non-zero integer in + * the first field or a non-null reference in the second field will + * enable the VM to expect that the stored value is now the permanent + * value of the field, going forward. This condition may be used by + * the VM compiler to improve code quality more aggressively, + * if the VM compiler runs after the stable variable has been + * given a permanent value, and chooses to observe that value. *

- * If the field is not of an array type, there are no array elements, - * then the value indicated as stable is simply the value of the field. - * If the dynamic type of the field value is an array but the static type - * is not, the components of the array are not regarded as stable. + * Since all heap variables begin with a default null value for + * references (resp., zero for primitives), there is an ambiguity when + * the VM discovers a stable variable holding a null or primitive zero + * value. Does the user intend the VM to constant fold that + * (uninteresting) value? Or is the user waiting until later to + * assign a permanent value to the variable? The VM does not + * systematically record stores of a null (resp., zero) to a stable variable, + * so there is no way for the VM to decide if a field's current value is + * its undisturbed initial value, or has been overwritten with + * an intentionally stored null (resp., zero). This is why the + * programmer should store non-default values into stable variables, + * if the consequent optimization is desired. *

- * If the field is an array type, then both the field value and - * all the components of the field value (if the field value is non-null) - * are indicated to be stable. - * If the field type is an array type with rank {@code N > 1}, - * then each component of the field value (if the field value is non-null), - * is regarded as a stable array of rank {@code N-1}. + * A stable variable may be assigned its permanent value inside a class or + * object initializer, but in general (with lazy data structures) + * stable variables are assigned much later. Depending on the value + * stored and what races are possible, safe publication may require + * special handling with a {@code VarHandle} atomic method. + * (See below.) *

+ * If an application requires constant folding of a stable variable + * whose permanent value may be the default value (null or zero), + * the variable can be refactored to add an extra indirection. + * This would represent the default value in a non-null "box", + * such as {@code Integer.valueOf(0)} or a lambda like + * {@code ()->null}. Such a refactoring should always be possible, + * since stable variables should (obviously) never be part of public + * APIs. + * + *

Stable Arrays

+ * + * So far, stable variables are fields, but they can be array + * components as well. If a stable field is declared as an array + * type with one dimension, both that array as a whole, and its + * eventual components, are treated as independent stable variables. + * When a reference to an array of length N is stored to the + * field, then the array object itself is taken to be a constant, as + * with any stable field. But then all N of the array + * components are also treated as independent stable + * variables. Such a stable array may contain any type, reference or + * primitive. Such an array may be also marked {@code final}, and + * initialized eagerly in the class or object initializer method. + * Whether any (or all) of its components are also initialized eagerly + * is up to the application. + *

+ * More generally, if a stable field is declared as an array type with + * D dimensions, then all the non-null components of the + * array, and of any sub-arrays up to a nesting depth less than + * D, are treated as stable variables. Thus, a stable field + * declared as an array potentially defines a tree (of fixed depth + * D) containing many stable variables, with each such stable + * variable being independently considered for optimization. In this + * way, and depending on program execution, a single {@code Stable} + * annotation can potentially create many independent stable + * variables. Since the top-level array reference is always stable, + * it is in general a bad idea to resize the array, even while keeping + * all existing components unchanged. (This could be relaxed in the + * future, to allow expansion of stable arrays, if there were a use + * case that could deal correctly with races. But it would require + * careful treatment by the compiler, to avoid folding the wrong + * version of an array. Anyway, there are other options, such as + * tree structures, for organizing the expansion of bundles of stable + * variables.) + *

+ * An array is never intrinsically stable. There is no change made to + * an array as it is assigned to a stable variable of array type. + * This is true even though after such an assignment, the compiler may + * observe that array and treat its components as stable variables. + * If the array is aliased to some other variable, uses via that + * variable will not be treated as stable. (Such aliasing is not + * recommended!) Also, storing an array into a stable variable will + * not make that array's components into stable variables, unless the + * variable into which it is stored is statically typed as an array, + * in the declaration of the stable field which refers to that array, + * directly or indirectly. + * + *

Examples of Stable Variables

+ * + * In the following example, the only constant-foldable string stored + * in any stable variable is the string {@code "S"}. All subarrays are + * constant. + * + *
{@code
+ * @Stable String FIELD = null;  // no foldable value yet
+ * @Stable int IDNUM = 0;  // no foldable value yet
+ * @Stable boolean INITIALIZED = false;  // no foldable value yet
+ * @Stable Object[] ARRAY = {
+ *   "S",   // string "S" is foldable
+ *   new String[] { "X", "Y" },  // array is foldable, not elements
+ *   null  // null is not foldable
+ * };
+ * @Stable Object[][] MATRIX = {
+ *   { "S", "S" },   // constant value
+ *   { new String[] { "X", "Y" } },  // array is foldable, not elements
+ *   { null, "S" },  // array is foldable, but not the null
+ *   null       // could be a foldable subarray later
+ * };
+ * }
+ * + * When the following method is called, some of the above stable + * variables will gain their permanent value, a constant-foldable + * string "S", or a non-default primitive value. + * + *
{@code
+ * void publishSomeStables() {
+ *   // store some more foldable "S" values:
+ *   FIELD = "S";
+ *   ARRAY[2] = "S";
+ *   MATRIX[2][0] = "S";
+ *   MATRIX[3] = new Object[] { "S", "S", null };
+ *   // and store some foldable primitives:
+ *   IDNUM = 42;
+ *   INITIALIZED = true;
+ *   VarHandle.releaseFence();  //optional, see below
+ * }
+ * }
+ * + *

+ * Note that a stable boolean variable (i.e., a stable + * field like {@code INITIALIZED}, or a stable boolean + * array element) can be constant-folded, + * but only after it is set to {@code true}. Even this simple + * optimization is sometimes useful for responding to a permanent + * one-shot state change, in such a way that the compiler can remove + * dead code associated with the initial state. As with any stable + * variable, it is in general a bad idea to reset such a variable to + * its default (i.e., {@code false}), since compiled code might have + * captured the {@code true} value as a constant, and as long as that + * compiled code is in use, the reset value will go undetected. + * + *

Final Variables, Stable Variables, and Memory Effects

+ * * Fields which are declared {@code final} may also be annotated as stable. - * Since final fields already behave as stable values, such an annotation + * Since final fields already behave as stable variables, such an annotation * conveys no additional information regarding change of the field's value, but - * still conveys information regarding change of additional components values if + * it conveys information regarding changes to additional component variables if * the type of the field is an array type (as described above). *

- * The HotSpot VM relies on this annotation to promote a non-null (resp., - * non-zero) component value to a constant, thereby enabling superior - * optimizations of code depending on such a value (such as constant folding). - * More specifically, the HotSpot VM will process non-null stable fields (final - * or otherwise) in a similar manner to static final fields with respect to - * promoting the field's value to a constant. Thus, placing aside the - * differences for null/non-null values and arrays, a final stable field is - * treated as if it is really final from both the Java language and the HotSpot - * VM. + * In order to assist refactoring between {@code final} and + * {@code @Stable} field declarations, the Java Memory Model + * freeze operation is applied to both kinds of fields, when + * the assignment occurs in a class or object initializer (i.e., + * static initialization code in {@code } or constructor code + * in {@code }). The freezing of a final or stable field is + * (currently) triggered only when an actual assignment occurs, directly + * from the initializer method ({@code } or {@code }). + * It is implemented in HotSpot by an appropriate memory barrier + * instruction at the return point of the initializer method. In this + * way, any non-null (or non-zero) value stored to a stable variable + * (either field or array component) will appear without races to any + * user of the class or object that has been initialized. *

- * It is (currently) undefined what happens if a field annotated as stable - * is given a third value (by explicitly updating a stable field, a component of - * a stable array, or a final stable field via reflection or other means). - * Since the HotSpot VM promotes a non-null component value to constant, it may - * be that the Java memory model would appear to be broken, if such a constant - * (the second value of the field) is used as the value of the field even after - * the field value has changed (to a third value). + * (Note: The barrier action of a class initializer is implicit in the + * unlocking operation specified in JVMS 5.5, Step 10. The barrier + * action of an instance initializer is specified as a "freeze action" + * in JLS 17.5.1. These disparate barrier actions have parallel + * effects on static and non-static final and stable variables.) + *

+ * There is no such JMM freeze operation applied to stable field stores in + * any other context. This implies that a constructor may choose to + * initialize a stable variable, rather than "leaving it for later". + * Such an initial value will be safely published, as if the field were + * {@code final}. The stored value may (or may not) contain + * additional stable variables, not yet initialized. Note that if a + * stable variable is written outside of the code of a constructor (or + * class initializer), then data races are possible, just the same as + * if there were no {@code @Stable} annotation, and the field were a + * regular mutable field. In fact, the usual case for lazily + * evaluated data structures is to assign to stable variables much + * later than the enclosing data structure is created. This means + * that racing reads and writes might observe nulls (or primitive + * zeroes) as well as non-default values. + * + *

Proper Handling of Stable Variables

+ * + * A stable variable can appear to be in either of two states, + * either uninitialized, or else set to a permanent, foldable value. + * Therefore, most code which reads stable variables should not assume + * that the value has been set, and should dynamically test for a null + * (or zero) value. Code which cannot prove a previous initialization + * must perform a null (or zero) test on a value loaded + * from a stable variable. Code which omits the null (or zero) test should be + * documented as to why the initialization order is reliable. In + * general, some sort of critical section for initialization should be + * documented, as provably preceding all uses of the (unchecked) + * stable variable, or else reasons should be given why races are + * benign, or some other proof given that races are either excluded or + * benign. See below for further discussion. + *

+ * After constant folding, the compiler can make use of many aspects of + * the object: its dynamic type, its length (if it is an array), and + * the values of its fields (if they are themselves constants, either + * final or stable). It is in general a bad idea to reset such + * variables to any other value, since compiled code might have folded + * an earlier stored value, and will never detect the reset value. + *

+ * The HotSpot interpreter is not fully aware of stable annotations, + * and treats annotated fields (and any affected arrays) as regular + * mutable variables. Thus, a field annotated as {@code @Stable} may + * be given a series of values, by explicit assignment, by reflection, + * or by some other means. If the HotSpot compiler constant-folds a + * stable variable, then in some contexts (execution of fully + * optimized code) the variable will appear to have one "historical" + * value, observed, captured, and used within the compiled code to the + * exclusion of any other possible values. Meanwhile, in other less + * optimized contexts, the stable variable will appear to have a more + * recent value. Race conditions, if allowed, will make this even + * more complex, since with races there is no definable "most recent" + * value across all threads. The compiler can observe any racing + * value, as it runs concurrently to the application, in its own + * thread. + *

+ * It is no good to try to "reset" a stable variable by storing its + * default again, because there is (currently) no way to find and + * deoptimize any and all affected compiled code. If you need the + * bookkeeping, try {@code SwitchPoint} or {@code MutableCallSite}, + * which both are able to reset compiled code that has captured an + * intermediate state. + *

+ * Note also each compilation task makes its own decisions about + * whether to observe stable variable values, and how aggressively to + * constant-fold them. And a method that uses a stable variable might + * be inlined by many different compilation tasks. The net result of + * all this is that, if stable variables are multiply assigned, the + * program execution may observe any "historical" value (if it was + * captured by some particular compilation task), as well as a "most + * recent" value observed by the interpreter or less-optimized code. + *

+ * For all these reasons, a user who bends the rules for a stable + * variable, by assigning several values to it, must state the + * intended purposes carefully in warning documentation on the + * relevant stable field declaration. That user's code must function + * correctly when observing any or all of the assigned values, at any + * time. Alternatively, field assignments must be constrained + * appropriately so that unwanted values are not observable by + * compiled code. + *

+ * Any class which uses this annotation is responsible for + * constraining assignments in such a way as not to violate API + * contracts of the class. (If the chosen technique is unusual in + * some way, it should be documented in a comment on the field.) Such + * constraints can be arranged in a variety of ways: + *

  • using the {@code VarHandle} API to perform an explicit + * atomic operation such as {@code compareAndExchange}, + * {@code setRelease}, {@code releaseFence}, or the like. + *
  • using regular variable access under explicit sychronization + *
  • using some other kind of critical section to avoid races + * which could affect compiled code + *
  • allowing multiple assignments under benign races, but + * only of some separately uniquified value + *
  • allowing multiple assignments under benign races, but + * only of semantically equivalent values, perhaps permitting + * occasional duplication of cached values + *
  • concealing the effects of multiple assignments in some + * other API-dependent way + *
  • providing some other internal proof of correctness, while + * accounting for all possible racing API accesses + *
  • making some appropriate disclaimer in the API about + * undefined behavior + *
+ *

+ * There may be special times when constant folding of stable + * variables is disabled. Such times would amount to a critical + * section locking out the compiler from reading stable variables. + * During such a critical section, an uninitialized stable variable + * can be changed in any way, just like a regular mutable variable + * (field or array component). It can even be reset to its default. + * Specifically, this may happen during certain AOT operations. If a + * stable variable can be updated multiple times during such a + * critical section, that fact must be clearly stated as a comment on + * the field declaration. (In the future, there may be explicit + * AOT-related annotations to convey this use case.) If there is no + * such warning, maintainers can safely disregard the possibility of + * an AOT critical section, since the author of the stable variable is + * relying on one of the other techniques listed above. + *

+ * It is possible to imagine markings for foldable methods or fields, + * which can constant-fold a wider variety of states and values. This + * annotation does not readily extend to such things, for the simple + * reason that extra VM bookkeeping would be required to record a + * wider variety of candidate states for constant folding. Such + * higher-level mechanisms may be created in the future. The present + * low-level annotation is designed as a potential building block to + * manage their bookkeeping. * * @implNote * This annotation only takes effect for fields of classes loaded by the boot From ceb51d44449977ecc142f6af03f93162b98adaf6 Mon Sep 17 00:00:00 2001 From: Ioi Lam Date: Sat, 19 Jul 2025 02:05:17 +0000 Subject: [PATCH 13/94] 8362829: Exclude CDS test cases after JDK-8361725 Reviewed-by: ccheung --- test/hotspot/jtreg/TEST.groups | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/hotspot/jtreg/TEST.groups b/test/hotspot/jtreg/TEST.groups index 77f49cc2c47..eeb5110b077 100644 --- a/test/hotspot/jtreg/TEST.groups +++ b/test/hotspot/jtreg/TEST.groups @@ -542,6 +542,7 @@ hotspot_aot_classlinking = \ -runtime/cds/appcds/dynamicArchive/OldClassInBaseArchive.java \ -runtime/cds/appcds/dynamicArchive/OldClassVerifierTrouble.java \ -runtime/cds/appcds/HelloExtTest.java \ + -runtime/cds/appcds/javaldr/ExceptionDuringDumpAtObjectsInitPhase.java \ -runtime/cds/appcds/javaldr/GCDuringDump.java \ -runtime/cds/appcds/javaldr/LockDuringDump.java \ -runtime/cds/appcds/jigsaw/classpathtests/EmptyClassInBootClassPath.java \ @@ -559,6 +560,7 @@ hotspot_aot_classlinking = \ -runtime/cds/appcds/JvmtiAddPath.java \ -runtime/cds/appcds/jvmti \ -runtime/cds/appcds/LambdaProxyClasslist.java \ + -runtime/cds/appcds/LambdaWithJavaAgent.java \ -runtime/cds/appcds/loaderConstraints/LoaderConstraintsTest.java \ -runtime/cds/appcds/methodHandles \ -runtime/cds/appcds/NestHostOldInf.java \ From ee0bcc55269e92e999862ae5c63ffad7a600f6cc Mon Sep 17 00:00:00 2001 From: SendaoYan Date: Sat, 19 Jul 2025 13:26:37 +0000 Subject: [PATCH 14/94] 8362379: Test serviceability/HeapDump/UnmountedVThreadNativeMethodAtTop.java should mark as /native Reviewed-by: sspitsyn, cjplummer --- .../HeapDump/UnmountedVThreadNativeMethodAtTop.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/hotspot/jtreg/serviceability/HeapDump/UnmountedVThreadNativeMethodAtTop.java b/test/hotspot/jtreg/serviceability/HeapDump/UnmountedVThreadNativeMethodAtTop.java index bb08be91dac..ce4572199d1 100644 --- a/test/hotspot/jtreg/serviceability/HeapDump/UnmountedVThreadNativeMethodAtTop.java +++ b/test/hotspot/jtreg/serviceability/HeapDump/UnmountedVThreadNativeMethodAtTop.java @@ -27,7 +27,7 @@ * @requires vm.continuations * @modules jdk.management * @library /test/lib - * @run junit/othervm --enable-native-access=ALL-UNNAMED UnmountedVThreadNativeMethodAtTop + * @run junit/othervm/native --enable-native-access=ALL-UNNAMED UnmountedVThreadNativeMethodAtTop */ import java.lang.management.ManagementFactory; From 441dbde2c3c915ffd916e39a5b4a91df5620d7f3 Mon Sep 17 00:00:00 2001 From: Erik Gahlin Date: Sat, 19 Jul 2025 15:09:28 +0000 Subject: [PATCH 15/94] 8362556: New test jdk/jfr/event/io/TestIOTopFrame.java is failing on all platforms Reviewed-by: mgronlun, shade --- .../share/classes/jdk/jfr/internal/PlatformEventType.java | 4 ++-- test/jdk/ProblemList.txt | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java index 40876fae4e1..dfe87509e14 100644 --- a/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java +++ b/src/jdk.jfr/share/classes/jdk/jfr/internal/PlatformEventType.java @@ -107,9 +107,9 @@ public final class PlatformEventType extends Type { return switch (getName()) { case Type.EVENT_NAME_PREFIX + "SocketRead", Type.EVENT_NAME_PREFIX + "SocketWrite", - Type.EVENT_NAME_PREFIX + "FileRead", Type.EVENT_NAME_PREFIX + "FileWrite" -> 6; - case Type.EVENT_NAME_PREFIX + "FileForce" -> 5; + case Type.EVENT_NAME_PREFIX + "FileRead", + Type.EVENT_NAME_PREFIX + "FileForce" -> 5; default -> 3; }; } diff --git a/test/jdk/ProblemList.txt b/test/jdk/ProblemList.txt index 6fe13c54988..84555a6edfb 100644 --- a/test/jdk/ProblemList.txt +++ b/test/jdk/ProblemList.txt @@ -772,7 +772,6 @@ jdk/jfr/event/compiler/TestCodeSweeper.java 8338127 generic- jdk/jfr/event/oldobject/TestShenandoah.java 8342951 generic-all jdk/jfr/event/runtime/TestResidentSetSizeEvent.java 8309846 aix-ppc64 jdk/jfr/jvm/TestWaste.java 8282427 generic-all -jdk/jfr/event/io/TestIOTopFrame.java 8362556 generic-all ############################################################################ From 9609f57cef684d2f44d3e12a3522811a3c0776f4 Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Mon, 21 Jul 2025 06:04:17 +0000 Subject: [PATCH 16/94] 8361752: Double free in CompileQueue::delete_all after JDK-8357473 Reviewed-by: kvn, vlivanov --- src/hotspot/share/compiler/compileBroker.cpp | 59 ++++++++++---------- src/hotspot/share/compiler/compileBroker.hpp | 2 +- src/hotspot/share/compiler/compileTask.cpp | 2 - src/hotspot/share/compiler/compileTask.hpp | 18 ------ test/hotspot/jtreg/ProblemList.txt | 2 - 5 files changed, 31 insertions(+), 52 deletions(-) diff --git a/src/hotspot/share/compiler/compileBroker.cpp b/src/hotspot/share/compiler/compileBroker.cpp index a7e6d079038..59f41f1c0dc 100644 --- a/src/hotspot/share/compiler/compileBroker.cpp +++ b/src/hotspot/share/compiler/compileBroker.cpp @@ -367,33 +367,31 @@ void CompileQueue::add(CompileTask* task) { */ void CompileQueue::delete_all() { MutexLocker mu(MethodCompileQueue_lock); - CompileTask* next = _first; + CompileTask* current = _first; // Iterate over all tasks in the compile queue - while (next != nullptr) { - CompileTask* current = next; - next = current->next(); - bool found_waiter = false; - { - MutexLocker ct_lock(CompileTaskWait_lock); - assert(current->waiting_for_completion_count() <= 1, "more than one thread are waiting for task"); - if (current->waiting_for_completion_count() > 0) { - // If another thread waits for this task, we must wake them up - // so they will stop waiting and free the task. - CompileTaskWait_lock->notify_all(); - found_waiter = true; - } - } - if (!found_waiter) { - // If no one was waiting for this task, we need to delete it ourselves. - // In this case, the task is also certainly unlocked, because, again, there is no waiter. - // Otherwise, by convention, it's the waiters responsibility to delete the task. + while (current != nullptr) { + if (!current->is_blocking()) { + // Non-blocking task. No one is waiting for it, delete it now. delete current; + } else { + // Blocking task. By convention, it is the waiters responsibility + // to delete the task. We cannot delete it here, because we do not + // coordinate with waiters. We will notify the waiters later. } + current = current->next(); } _first = nullptr; _last = nullptr; + // Wake up all blocking task waiters to deal with remaining blocking + // tasks. This is not a performance sensitive path, so we do this + // unconditionally to simplify coding/testing. + { + MonitorLocker ml(Thread::current(), CompileTaskWait_lock); + ml.notify_all(); + } + // Wake up all threads that block on the queue. MethodCompileQueue_lock->notify_all(); } @@ -1720,23 +1718,26 @@ void CompileBroker::wait_for_completion(CompileTask* task) { } else #endif { - MonitorLocker ml(thread, CompileTaskWait_lock); free_task = true; - task->inc_waiting_for_completion(); + // Wait until the task is complete or compilation is shut down. + MonitorLocker ml(thread, CompileTaskWait_lock); while (!task->is_complete() && !is_compilation_disabled_forever()) { ml.wait(); } - task->dec_waiting_for_completion(); + } + + // It is harmless to check this status without the lock, because + // completion is a stable property. + if (!task->is_complete() && is_compilation_disabled_forever()) { + // Task is not complete, and we are exiting for compilation shutdown. + // The task can still be executed by some compiler thread, therefore + // we cannot delete it. This will leave task allocated, which leaks it. + // At this (degraded) point, it is less risky to abandon the task, + // rather than attempting a more complicated deletion protocol. + free_task = false; } if (free_task) { - if (is_compilation_disabled_forever()) { - delete task; - return; - } - - // It is harmless to check this status without the lock, because - // completion is a stable property (until the task object is deleted). assert(task->is_complete(), "Compilation should have completed"); // By convention, the waiter is responsible for deleting a diff --git a/src/hotspot/share/compiler/compileBroker.hpp b/src/hotspot/share/compiler/compileBroker.hpp index 046b2fa5197..1c936511dca 100644 --- a/src/hotspot/share/compiler/compileBroker.hpp +++ b/src/hotspot/share/compiler/compileBroker.hpp @@ -381,7 +381,7 @@ public: } static bool is_compilation_disabled_forever() { - return _should_compile_new_jobs == shutdown_compilation; + return Atomic::load(&_should_compile_new_jobs) == shutdown_compilation; } static void wait_for_no_active_tasks(); diff --git a/src/hotspot/share/compiler/compileTask.cpp b/src/hotspot/share/compiler/compileTask.cpp index 9c06688d348..c5f1c789039 100644 --- a/src/hotspot/share/compiler/compileTask.cpp +++ b/src/hotspot/share/compiler/compileTask.cpp @@ -56,8 +56,6 @@ CompileTask::CompileTask(int compile_id, _comp_level = comp_level; _num_inlined_bytecodes = 0; - _waiting_count = 0; - _is_complete = false; _is_success = false; diff --git a/src/hotspot/share/compiler/compileTask.hpp b/src/hotspot/share/compiler/compileTask.hpp index 1a3ecbde566..148bdd28009 100644 --- a/src/hotspot/share/compiler/compileTask.hpp +++ b/src/hotspot/share/compiler/compileTask.hpp @@ -99,7 +99,6 @@ class CompileTask : public CHeapObj { // Compilation state for a blocking JVMCI compilation JVMCICompileState* _blocking_jvmci_compile_state; #endif - int _waiting_count; // See waiting_for_completion_count() int _comp_level; int _num_inlined_bytecodes; CompileTask* _next, *_prev; @@ -164,23 +163,6 @@ class CompileTask : public CHeapObj { } #endif - // See how many threads are waiting for this task. Must have lock to read this. - int waiting_for_completion_count() { - assert(CompileTaskWait_lock->owned_by_self(), "must have lock to use waiting_for_completion_count()"); - return _waiting_count; - } - // Indicates that a thread is waiting for this task to complete. Must have lock to use this. - void inc_waiting_for_completion() { - assert(CompileTaskWait_lock->owned_by_self(), "must have lock to use inc_waiting_for_completion()"); - _waiting_count++; - } - // Indicates that a thread stopped waiting for this task to complete. Must have lock to use this. - void dec_waiting_for_completion() { - assert(CompileTaskWait_lock->owned_by_self(), "must have lock to use dec_waiting_for_completion()"); - assert(_waiting_count > 0, "waiting count is not positive"); - _waiting_count--; - } - void mark_complete() { _is_complete = true; } void mark_success() { _is_success = true; } void mark_started(jlong time) { _time_started = time; } diff --git a/test/hotspot/jtreg/ProblemList.txt b/test/hotspot/jtreg/ProblemList.txt index 4f5b264daa1..7692c962bdf 100644 --- a/test/hotspot/jtreg/ProblemList.txt +++ b/test/hotspot/jtreg/ProblemList.txt @@ -82,8 +82,6 @@ compiler/c2/TestVerifyConstraintCasts.java 8355574 generic-all compiler/c2/aarch64/TestStaticCallStub.java 8359963 linux-aarch64,macosx-aarch64 -compiler/debug/TestStressBailout.java 8361752 generic-all - ############################################################################# # :hotspot_gc From 62a58062e5f3d0a723608d98d2412ea779f73897 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Beno=C3=AEt=20Maillard?= Date: Mon, 21 Jul 2025 07:37:31 +0000 Subject: [PATCH 17/94] 8361700: Missed optimization in PhaseIterGVN for mask and shift patterns due to missing notification in PhaseIterGVN::add_users_of_use_to_worklist Reviewed-by: thartmann, mchevalier, mhaessig, jkarthikeyan --- src/hotspot/share/opto/phaseX.cpp | 9 +++ .../compiler/c2/TestMaskAndRShiftReorder.java | 60 +++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 test/hotspot/jtreg/compiler/c2/TestMaskAndRShiftReorder.java diff --git a/src/hotspot/share/opto/phaseX.cpp b/src/hotspot/share/opto/phaseX.cpp index edddd1797b1..541a6ad6ea0 100644 --- a/src/hotspot/share/opto/phaseX.cpp +++ b/src/hotspot/share/opto/phaseX.cpp @@ -2549,6 +2549,15 @@ void PhaseIterGVN::add_users_of_use_to_worklist(Node* n, Node* use, Unique_Node_ } } } + // If changed AndI/AndL inputs, check RShift users for "(x & mask) >> shift" optimization opportunity + if (use_op == Op_AndI || use_op == Op_AndL) { + for (DUIterator_Fast i2max, i2 = use->fast_outs(i2max); i2 < i2max; i2++) { + Node* u = use->fast_out(i2); + if (u->Opcode() == Op_RShiftI || u->Opcode() == Op_RShiftL) { + worklist.push(u); + } + } + } // If changed AddP inputs: // - check Stores for loop invariant, and // - if the changed input is the offset, check constant-offset AddP users for diff --git a/test/hotspot/jtreg/compiler/c2/TestMaskAndRShiftReorder.java b/test/hotspot/jtreg/compiler/c2/TestMaskAndRShiftReorder.java new file mode 100644 index 00000000000..bbb80e2c120 --- /dev/null +++ b/test/hotspot/jtreg/compiler/c2/TestMaskAndRShiftReorder.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8361700 + * @summary An expression of the form "(x & mask) >> shift", where the mask + * is a constant, should be transformed to "(x >> shift) & (mask >> shift)" + * VerifyIterativeGVN checks that this optimization was applied + * @run main/othervm -Xcomp -XX:+IgnoreUnrecognizedVMOptions + * -XX:CompileCommand=compileonly,compiler.c2.TestMaskAndRShiftReorder::test + * -XX:VerifyIterativeGVN=1110 compiler.c2.TestMaskAndRShiftReorder + * @run main compiler.c2.TestMaskAndRShiftReorder + * + */ + +package compiler.c2; + +public class TestMaskAndRShiftReorder { + static long lFld; + + + public static void main(String[] strArr) { + test(); + } + + static long test() { + int x = 10; + int y = -17; + int iArr[] = new int[10]; + for (int i = 1; i < 7; i++) { + for (int j = 1; j < 2; j++) { + x <<= lFld; + } + y &= x; + y >>= 1; + } + return iArr.length; + } +} From 37b70707bd9d4c1eb2db6ed438b5f4f5b49fa202 Mon Sep 17 00:00:00 2001 From: Francesco Andreuzzi Date: Mon, 21 Jul 2025 08:43:30 +0000 Subject: [PATCH 18/94] 8362587: Sort share/oops includes Reviewed-by: shade, dholmes --- src/hotspot/share/oops/compressedOops.cpp | 2 +- src/hotspot/share/oops/compressedOops.hpp | 1 + src/hotspot/share/oops/cpCache.cpp | 2 +- src/hotspot/share/oops/fieldInfo.cpp | 2 +- src/hotspot/share/oops/fieldStreams.hpp | 2 +- src/hotspot/share/oops/instanceKlass.cpp | 6 +++--- src/hotspot/share/oops/instanceOop.hpp | 1 + src/hotspot/share/oops/klassVtable.cpp | 2 +- src/hotspot/share/oops/markWord.inline.hpp | 3 ++- src/hotspot/share/oops/metadata.cpp | 2 +- src/hotspot/share/oops/method.cpp | 6 +++--- src/hotspot/share/oops/method.hpp | 2 +- src/hotspot/share/oops/methodCounters.cpp | 2 +- src/hotspot/share/oops/methodCounters.hpp | 2 +- src/hotspot/share/oops/objArrayOop.hpp | 1 + src/hotspot/share/oops/oop.hpp | 3 ++- src/hotspot/share/oops/oop.inline.hpp | 6 +++--- src/hotspot/share/oops/oopCast.inline.hpp | 2 +- src/hotspot/share/oops/oopHandle.inline.hpp | 2 +- src/hotspot/share/oops/stackChunkOop.inline.hpp | 2 +- src/hotspot/share/oops/trainingData.cpp | 4 ++-- src/hotspot/share/oops/trainingData.hpp | 2 +- src/hotspot/share/oops/typeArrayOop.hpp | 1 + src/hotspot/share/oops/typeArrayOop.inline.hpp | 2 +- test/hotspot/jtreg/sources/TestIncludesAreSorted.java | 1 + 25 files changed, 34 insertions(+), 27 deletions(-) diff --git a/src/hotspot/share/oops/compressedOops.cpp b/src/hotspot/share/oops/compressedOops.cpp index 2c5f1631d85..b09aaa3547d 100644 --- a/src/hotspot/share/oops/compressedOops.cpp +++ b/src/hotspot/share/oops/compressedOops.cpp @@ -22,6 +22,7 @@ * */ +#include "gc/shared/collectedHeap.hpp" #include "logging/log.hpp" #include "logging/logStream.hpp" #include "memory/memRegion.hpp" @@ -29,7 +30,6 @@ #include "memory/resourceArea.hpp" #include "memory/universe.hpp" #include "oops/compressedOops.hpp" -#include "gc/shared/collectedHeap.hpp" #include "runtime/arguments.hpp" #include "runtime/globals.hpp" diff --git a/src/hotspot/share/oops/compressedOops.hpp b/src/hotspot/share/oops/compressedOops.hpp index 33af420305c..44934442374 100644 --- a/src/hotspot/share/oops/compressedOops.hpp +++ b/src/hotspot/share/oops/compressedOops.hpp @@ -29,6 +29,7 @@ #include "memory/memRegion.hpp" #include "oops/oopsHierarchy.hpp" #include "utilities/globalDefinitions.hpp" + #include class outputStream; diff --git a/src/hotspot/share/oops/cpCache.cpp b/src/hotspot/share/oops/cpCache.cpp index a855639e74b..a56e3453970 100644 --- a/src/hotspot/share/oops/cpCache.cpp +++ b/src/hotspot/share/oops/cpCache.cpp @@ -31,8 +31,8 @@ #include "classfile/systemDictionaryShared.hpp" #include "classfile/vmClasses.hpp" #include "code/codeCache.hpp" -#include "interpreter/bytecodeStream.hpp" #include "interpreter/bytecodes.hpp" +#include "interpreter/bytecodeStream.hpp" #include "interpreter/interpreter.hpp" #include "interpreter/linkResolver.hpp" #include "interpreter/rewriter.hpp" diff --git a/src/hotspot/share/oops/fieldInfo.cpp b/src/hotspot/share/oops/fieldInfo.cpp index d0825ba6df8..8c1a9e46d40 100644 --- a/src/hotspot/share/oops/fieldInfo.cpp +++ b/src/hotspot/share/oops/fieldInfo.cpp @@ -22,8 +22,8 @@ * */ -#include "memory/resourceArea.hpp" #include "cds/cdsConfig.hpp" +#include "memory/resourceArea.hpp" #include "oops/fieldInfo.inline.hpp" #include "runtime/atomic.hpp" #include "utilities/packedTable.hpp" diff --git a/src/hotspot/share/oops/fieldStreams.hpp b/src/hotspot/share/oops/fieldStreams.hpp index 0ae828d73d9..23ec156473b 100644 --- a/src/hotspot/share/oops/fieldStreams.hpp +++ b/src/hotspot/share/oops/fieldStreams.hpp @@ -25,8 +25,8 @@ #ifndef SHARE_OOPS_FIELDSTREAMS_HPP #define SHARE_OOPS_FIELDSTREAMS_HPP -#include "oops/instanceKlass.hpp" #include "oops/fieldInfo.hpp" +#include "oops/instanceKlass.hpp" #include "runtime/fieldDescriptor.hpp" // The is the base class for iteration over the fields array diff --git a/src/hotspot/share/oops/instanceKlass.cpp b/src/hotspot/share/oops/instanceKlass.cpp index f4ab8c31409..0c9d6b0bcdc 100644 --- a/src/hotspot/share/oops/instanceKlass.cpp +++ b/src/hotspot/share/oops/instanceKlass.cpp @@ -50,8 +50,8 @@ #include "interpreter/rewriter.hpp" #include "jvm.h" #include "jvmtifiles/jvmti.h" -#include "logging/log.hpp" #include "klass.inline.hpp" +#include "logging/log.hpp" #include "logging/logMessage.hpp" #include "logging/logStream.hpp" #include "memory/allocation.inline.hpp" @@ -61,8 +61,8 @@ #include "memory/oopFactory.hpp" #include "memory/resourceArea.hpp" #include "memory/universe.hpp" -#include "oops/fieldStreams.inline.hpp" #include "oops/constantPool.hpp" +#include "oops/fieldStreams.inline.hpp" #include "oops/instanceClassLoaderKlass.hpp" #include "oops/instanceKlass.inline.hpp" #include "oops/instanceMirrorKlass.hpp" @@ -78,8 +78,8 @@ #include "prims/jvmtiThreadState.hpp" #include "prims/methodComparator.hpp" #include "runtime/arguments.hpp" -#include "runtime/deoptimization.hpp" #include "runtime/atomic.hpp" +#include "runtime/deoptimization.hpp" #include "runtime/fieldDescriptor.inline.hpp" #include "runtime/handles.inline.hpp" #include "runtime/javaCalls.hpp" diff --git a/src/hotspot/share/oops/instanceOop.hpp b/src/hotspot/share/oops/instanceOop.hpp index e97cd00f79f..bfc16ea3a25 100644 --- a/src/hotspot/share/oops/instanceOop.hpp +++ b/src/hotspot/share/oops/instanceOop.hpp @@ -26,6 +26,7 @@ #define SHARE_OOPS_INSTANCEOOP_HPP #include "oops/oop.hpp" + #include // An instanceOop is an instance of a Java Class diff --git a/src/hotspot/share/oops/klassVtable.cpp b/src/hotspot/share/oops/klassVtable.cpp index 8d13310cdc7..e9da33b280e 100644 --- a/src/hotspot/share/oops/klassVtable.cpp +++ b/src/hotspot/share/oops/klassVtable.cpp @@ -40,8 +40,8 @@ #include "oops/objArrayOop.hpp" #include "oops/oop.inline.hpp" #include "runtime/flags/flagSetting.hpp" -#include "runtime/java.hpp" #include "runtime/handles.inline.hpp" +#include "runtime/java.hpp" #include "runtime/safepointVerifiers.hpp" #include "utilities/copy.hpp" diff --git a/src/hotspot/share/oops/markWord.inline.hpp b/src/hotspot/share/oops/markWord.inline.hpp index 27c8cfdeaef..8936ed1d523 100644 --- a/src/hotspot/share/oops/markWord.inline.hpp +++ b/src/hotspot/share/oops/markWord.inline.hpp @@ -25,9 +25,10 @@ #ifndef SHARE_OOPS_MARKWORD_INLINE_HPP #define SHARE_OOPS_MARKWORD_INLINE_HPP -#include "oops/compressedOops.inline.hpp" #include "oops/markWord.hpp" +#include "oops/compressedOops.inline.hpp" + narrowKlass markWord::narrow_klass() const { #ifdef _LP64 assert(UseCompactObjectHeaders, "only used with compact object headers"); diff --git a/src/hotspot/share/oops/metadata.cpp b/src/hotspot/share/oops/metadata.cpp index be3b523c5d5..8f86be292bd 100644 --- a/src/hotspot/share/oops/metadata.cpp +++ b/src/hotspot/share/oops/metadata.cpp @@ -22,8 +22,8 @@ * */ -#include "oops/metadata.hpp" #include "memory/resourceArea.hpp" +#include "oops/metadata.hpp" #include "prims/jvmtiRedefineClasses.hpp" void Metadata::set_on_stack(const bool value) { diff --git a/src/hotspot/share/oops/method.cpp b/src/hotspot/share/oops/method.cpp index 7552bf50ed9..595b4e52882 100644 --- a/src/hotspot/share/oops/method.cpp +++ b/src/hotspot/share/oops/method.cpp @@ -36,9 +36,9 @@ #include "code/debugInfoRec.hpp" #include "compiler/compilationPolicy.hpp" #include "gc/shared/collectedHeap.inline.hpp" +#include "interpreter/bytecodes.hpp" #include "interpreter/bytecodeStream.hpp" #include "interpreter/bytecodeTracer.hpp" -#include "interpreter/bytecodes.hpp" #include "interpreter/interpreter.hpp" #include "interpreter/oopMapCache.hpp" #include "logging/log.hpp" @@ -51,8 +51,8 @@ #include "memory/resourceArea.hpp" #include "memory/universe.hpp" #include "nmt/memTracker.hpp" -#include "oops/constMethod.hpp" #include "oops/constantPool.hpp" +#include "oops/constMethod.hpp" #include "oops/jmethodIDTable.hpp" #include "oops/klass.inline.hpp" #include "oops/method.inline.hpp" @@ -64,8 +64,8 @@ #include "oops/trainingData.hpp" #include "prims/jvmtiExport.hpp" #include "prims/methodHandles.hpp" -#include "runtime/atomic.hpp" #include "runtime/arguments.hpp" +#include "runtime/atomic.hpp" #include "runtime/continuationEntry.hpp" #include "runtime/frame.inline.hpp" #include "runtime/handles.inline.hpp" diff --git a/src/hotspot/share/oops/method.hpp b/src/hotspot/share/oops/method.hpp index b241104b62c..4592cb8a8c0 100644 --- a/src/hotspot/share/oops/method.hpp +++ b/src/hotspot/share/oops/method.hpp @@ -29,8 +29,8 @@ #include "compiler/compilerDefinitions.hpp" #include "oops/annotations.hpp" #include "oops/constantPool.hpp" -#include "oops/methodFlags.hpp" #include "oops/instanceKlass.hpp" +#include "oops/methodFlags.hpp" #include "oops/oop.hpp" #include "utilities/accessFlags.hpp" #include "utilities/align.hpp" diff --git a/src/hotspot/share/oops/methodCounters.cpp b/src/hotspot/share/oops/methodCounters.cpp index c0787cab5e4..8f29f9b7bbd 100644 --- a/src/hotspot/share/oops/methodCounters.cpp +++ b/src/hotspot/share/oops/methodCounters.cpp @@ -26,11 +26,11 @@ #include "compiler/compiler_globals.hpp" #include "compiler/compilerOracle.hpp" #include "memory/metaspaceClosure.hpp" +#include "memory/resourceArea.hpp" #include "oops/method.hpp" #include "oops/methodCounters.hpp" #include "oops/trainingData.hpp" #include "runtime/handles.inline.hpp" -#include "memory/resourceArea.hpp" MethodCounters::MethodCounters(const methodHandle& mh) : _method(mh()), diff --git a/src/hotspot/share/oops/methodCounters.hpp b/src/hotspot/share/oops/methodCounters.hpp index 4f4a1d1948f..df8acefc3eb 100644 --- a/src/hotspot/share/oops/methodCounters.hpp +++ b/src/hotspot/share/oops/methodCounters.hpp @@ -25,9 +25,9 @@ #ifndef SHARE_OOPS_METHODCOUNTERS_HPP #define SHARE_OOPS_METHODCOUNTERS_HPP -#include "oops/metadata.hpp" #include "compiler/compilerDefinitions.hpp" #include "interpreter/invocationCounter.hpp" +#include "oops/metadata.hpp" #include "utilities/align.hpp" class MethodTrainingData; diff --git a/src/hotspot/share/oops/objArrayOop.hpp b/src/hotspot/share/oops/objArrayOop.hpp index 20e2953fee9..8e39b897018 100644 --- a/src/hotspot/share/oops/objArrayOop.hpp +++ b/src/hotspot/share/oops/objArrayOop.hpp @@ -27,6 +27,7 @@ #include "oops/arrayOop.hpp" #include "utilities/align.hpp" + #include class Klass; diff --git a/src/hotspot/share/oops/oop.hpp b/src/hotspot/share/oops/oop.hpp index 8048c8770c2..549b5b0bff8 100644 --- a/src/hotspot/share/oops/oop.hpp +++ b/src/hotspot/share/oops/oop.hpp @@ -27,14 +27,15 @@ #include "memory/iterator.hpp" #include "memory/memRegion.hpp" -#include "oops/compressedKlass.hpp" #include "oops/accessDecorators.hpp" +#include "oops/compressedKlass.hpp" #include "oops/markWord.hpp" #include "oops/metadata.hpp" #include "oops/objLayout.hpp" #include "runtime/atomic.hpp" #include "utilities/globalDefinitions.hpp" #include "utilities/macros.hpp" + #include // oopDesc is the top baseclass for objects classes. The {name}Desc classes describe diff --git a/src/hotspot/share/oops/oop.inline.hpp b/src/hotspot/share/oops/oop.inline.hpp index 3dad778a73a..683792e5201 100644 --- a/src/hotspot/share/oops/oop.inline.hpp +++ b/src/hotspot/share/oops/oop.inline.hpp @@ -27,22 +27,22 @@ #include "oops/oop.hpp" -#include "memory/universe.hpp" #include "memory/iterator.inline.hpp" +#include "memory/universe.hpp" #include "oops/access.inline.hpp" #include "oops/arrayKlass.hpp" #include "oops/arrayOop.hpp" #include "oops/compressedKlass.inline.hpp" #include "oops/instanceKlass.hpp" -#include "oops/objLayout.inline.hpp" #include "oops/markWord.inline.hpp" +#include "oops/objLayout.inline.hpp" #include "oops/oopsHierarchy.hpp" #include "runtime/atomic.hpp" #include "runtime/globals.hpp" #include "utilities/align.hpp" #include "utilities/debug.hpp" -#include "utilities/macros.hpp" #include "utilities/globalDefinitions.hpp" +#include "utilities/macros.hpp" // Implementation of all inlined member functions defined in oop.hpp // We need a separate file to avoid circular references diff --git a/src/hotspot/share/oops/oopCast.inline.hpp b/src/hotspot/share/oops/oopCast.inline.hpp index 64c4ffe1b80..1b21b3fd4e7 100644 --- a/src/hotspot/share/oops/oopCast.inline.hpp +++ b/src/hotspot/share/oops/oopCast.inline.hpp @@ -24,8 +24,8 @@ #ifndef SHARE_OOPS_OOPCAST_INLINE_HPP #define SHARE_OOPS_OOPCAST_INLINE_HPP -#include "oops/oopsHierarchy.hpp" #include "oops/oop.inline.hpp" +#include "oops/oopsHierarchy.hpp" template static bool is_oop_type(oop theOop) { diff --git a/src/hotspot/share/oops/oopHandle.inline.hpp b/src/hotspot/share/oops/oopHandle.inline.hpp index 77dd1790159..343d677ab04 100644 --- a/src/hotspot/share/oops/oopHandle.inline.hpp +++ b/src/hotspot/share/oops/oopHandle.inline.hpp @@ -27,8 +27,8 @@ #include "oops/oopHandle.hpp" -#include "oops/access.inline.hpp" #include "gc/shared/oopStorage.inline.hpp" +#include "oops/access.inline.hpp" inline oop OopHandle::resolve() const { if (_obj == nullptr) { diff --git a/src/hotspot/share/oops/stackChunkOop.inline.hpp b/src/hotspot/share/oops/stackChunkOop.inline.hpp index 4b2c160cd81..384dbefc10b 100644 --- a/src/hotspot/share/oops/stackChunkOop.inline.hpp +++ b/src/hotspot/share/oops/stackChunkOop.inline.hpp @@ -27,9 +27,9 @@ #include "oops/stackChunkOop.hpp" -#include "gc/shared/collectedHeap.hpp" #include "gc/shared/barrierSet.hpp" #include "gc/shared/barrierSetStackChunk.hpp" +#include "gc/shared/collectedHeap.hpp" #include "gc/shared/gc_globals.hpp" #include "memory/memRegion.hpp" #include "memory/universe.hpp" diff --git a/src/hotspot/share/oops/trainingData.cpp b/src/hotspot/share/oops/trainingData.cpp index e82a7a3e2bd..70e8f2437c1 100644 --- a/src/hotspot/share/oops/trainingData.cpp +++ b/src/hotspot/share/oops/trainingData.cpp @@ -22,10 +22,10 @@ * */ -#include "ci/ciEnv.hpp" -#include "ci/ciMetadata.hpp" #include "cds/cdsConfig.hpp" #include "cds/metaspaceShared.hpp" +#include "ci/ciEnv.hpp" +#include "ci/ciMetadata.hpp" #include "classfile/classLoaderData.hpp" #include "classfile/compactHashtable.hpp" #include "classfile/javaClasses.hpp" diff --git a/src/hotspot/share/oops/trainingData.hpp b/src/hotspot/share/oops/trainingData.hpp index 1d03056871b..b4fd5fd61ab 100644 --- a/src/hotspot/share/oops/trainingData.hpp +++ b/src/hotspot/share/oops/trainingData.hpp @@ -28,8 +28,8 @@ #include "cds/cdsConfig.hpp" #include "classfile/classLoaderData.hpp" #include "classfile/compactHashtable.hpp" -#include "compiler/compilerDefinitions.hpp" #include "compiler/compiler_globals.hpp" +#include "compiler/compilerDefinitions.hpp" #include "memory/allocation.hpp" #include "memory/metaspaceClosure.hpp" #include "oops/instanceKlass.hpp" diff --git a/src/hotspot/share/oops/typeArrayOop.hpp b/src/hotspot/share/oops/typeArrayOop.hpp index 0ca63622d1d..c96001e9363 100644 --- a/src/hotspot/share/oops/typeArrayOop.hpp +++ b/src/hotspot/share/oops/typeArrayOop.hpp @@ -27,6 +27,7 @@ #include "oops/arrayOop.hpp" #include "oops/typeArrayKlass.hpp" + #include // A typeArrayOop is an array containing basic types (non oop elements). diff --git a/src/hotspot/share/oops/typeArrayOop.inline.hpp b/src/hotspot/share/oops/typeArrayOop.inline.hpp index 43fb6a06b39..a5a1e3200d1 100644 --- a/src/hotspot/share/oops/typeArrayOop.inline.hpp +++ b/src/hotspot/share/oops/typeArrayOop.inline.hpp @@ -28,8 +28,8 @@ #include "oops/typeArrayOop.hpp" #include "oops/access.inline.hpp" -#include "oops/oop.inline.hpp" #include "oops/arrayOop.hpp" +#include "oops/oop.inline.hpp" size_t typeArrayOopDesc::object_size(const TypeArrayKlass* tk) const { return object_size(tk->layout_helper(), length()); diff --git a/test/hotspot/jtreg/sources/TestIncludesAreSorted.java b/test/hotspot/jtreg/sources/TestIncludesAreSorted.java index ed3712810cb..222b97d6056 100644 --- a/test/hotspot/jtreg/sources/TestIncludesAreSorted.java +++ b/test/hotspot/jtreg/sources/TestIncludesAreSorted.java @@ -47,6 +47,7 @@ public class TestIncludesAreSorted { "share/ci", "share/compiler", "share/jvmci", + "share/oops", "share/opto" }; From 1bd683b5884e65a03d564976a9d9220ad0893776 Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Mon, 21 Jul 2025 09:21:48 +0000 Subject: [PATCH 19/94] 8362582: GHA: Increase bundle retention time to deal with infra overload better Reviewed-by: goetz, jwaters, clanger --- .github/actions/build-jtreg/action.yml | 2 +- .github/actions/upload-bundles/action.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/actions/build-jtreg/action.yml b/.github/actions/build-jtreg/action.yml index 3abfa260c17..0ba9937fb45 100644 --- a/.github/actions/build-jtreg/action.yml +++ b/.github/actions/build-jtreg/action.yml @@ -65,4 +65,4 @@ runs: with: name: bundles-jtreg-${{ steps.version.outputs.value }} path: jtreg/installed - retention-days: 1 + retention-days: 5 diff --git a/.github/actions/upload-bundles/action.yml b/.github/actions/upload-bundles/action.yml index dfa994baac0..ca5366f3d6c 100644 --- a/.github/actions/upload-bundles/action.yml +++ b/.github/actions/upload-bundles/action.yml @@ -91,5 +91,5 @@ runs: with: name: bundles-${{ inputs.platform }}${{ inputs.debug-suffix }}${{ inputs.static-suffix }}${{ inputs.bundle-suffix }} path: bundles - retention-days: 1 + retention-days: 5 if: steps.bundles.outputs.bundles-found == 'true' From 8f1bb59e1a0137fe9a5d4477971d21e645735b4d Mon Sep 17 00:00:00 2001 From: Shaojin Wen Date: Mon, 21 Jul 2025 09:37:56 +0000 Subject: [PATCH 20/94] 8357913: Add `@Stable` to BigInteger and BigDecimal Reviewed-by: rgiulietti, liach --- src/java.base/share/classes/java/math/BigDecimal.java | 9 +++++++++ src/java.base/share/classes/java/math/BigInteger.java | 1 + 2 files changed, 10 insertions(+) diff --git a/src/java.base/share/classes/java/math/BigDecimal.java b/src/java.base/share/classes/java/math/BigDecimal.java index 534f840174c..3b7d9e0d65b 100644 --- a/src/java.base/share/classes/java/math/BigDecimal.java +++ b/src/java.base/share/classes/java/math/BigDecimal.java @@ -44,6 +44,7 @@ import jdk.internal.access.JavaLangAccess; import jdk.internal.access.SharedSecrets; import jdk.internal.math.FormattedFPDecimal; import jdk.internal.util.DecimalDigits; +import jdk.internal.vm.annotation.Stable; /** * Immutable, arbitrary-precision signed decimal numbers. A {@code @@ -409,6 +410,7 @@ public class BigDecimal extends Number implements Comparable { private static final long serialVersionUID = 6108874887143696463L; // Cache of common small BigDecimal values. + @Stable private static final BigDecimal[] ZERO_THROUGH_TEN = { new BigDecimal(BigInteger.ZERO, 0, 0, 1), new BigDecimal(BigInteger.ONE, 1, 0, 1), @@ -424,6 +426,7 @@ public class BigDecimal extends Number implements Comparable { }; // Cache of zero scaled by 0 - 15 + @Stable private static final BigDecimal[] ZERO_SCALED_BY = { ZERO_THROUGH_TEN[0], new BigDecimal(BigInteger.ZERO, 0, 1, 1), @@ -4084,6 +4087,7 @@ public class BigDecimal extends Number implements Comparable { * Powers of 10 which can be represented exactly in {@code * double}. */ + @Stable private static final double[] DOUBLE_10_POW = { 1.0e0, 1.0e1, 1.0e2, 1.0e3, 1.0e4, 1.0e5, 1.0e6, 1.0e7, 1.0e8, 1.0e9, 1.0e10, 1.0e11, @@ -4095,6 +4099,7 @@ public class BigDecimal extends Number implements Comparable { * Powers of 10 which can be represented exactly in {@code * float}. */ + @Stable private static final float[] FLOAT_10_POW = { 1.0e0f, 1.0e1f, 1.0e2f, 1.0e3f, 1.0e4f, 1.0e5f, 1.0e6f, 1.0e7f, 1.0e8f, 1.0e9f, 1.0e10f @@ -4291,6 +4296,7 @@ public class BigDecimal extends Number implements Comparable { } } + @Stable private static final long[] LONG_TEN_POWERS_TABLE = { 1, // 0 / 10^0 10, // 1 / 10^1 @@ -4340,6 +4346,7 @@ public class BigDecimal extends Number implements Comparable { private static final int BIG_TEN_POWERS_TABLE_MAX = 16 * BIG_TEN_POWERS_TABLE_INITLEN; + @Stable private static final long[] THRESHOLDS_TABLE = { Long.MAX_VALUE, // 0 Long.MAX_VALUE/10L, // 1 @@ -5091,6 +5098,7 @@ public class BigDecimal extends Number implements Comparable { /** * {@code FIVE_TO_2_TO[n] == 5^(2^n)} */ + @Stable private static final BigInteger[] FIVE_TO_2_TO = new BigInteger[16 + 1]; static { @@ -5889,6 +5897,7 @@ public class BigDecimal extends Number implements Comparable { return null; } + @Stable private static final long[][] LONGLONG_TEN_POWERS_TABLE = { { 0L, 0x8AC7230489E80000L }, //10^19 { 0x5L, 0x6bc75e2d63100000L }, //10^20 diff --git a/src/java.base/share/classes/java/math/BigInteger.java b/src/java.base/share/classes/java/math/BigInteger.java index a6ce483f3eb..51d935f10c1 100644 --- a/src/java.base/share/classes/java/math/BigInteger.java +++ b/src/java.base/share/classes/java/math/BigInteger.java @@ -1303,6 +1303,7 @@ public class BigInteger extends Number implements Comparable { private static volatile BigInteger[][] powerCache; /** The cache of logarithms of radices for base conversion. */ + @Stable private static final double[] logCache; /** The natural log of 2. This is used in computing cache indices. */ From 13bab09bffc411dde324599c2e15852ef4b53d55 Mon Sep 17 00:00:00 2001 From: Lei Zhu Date: Mon, 21 Jul 2025 09:59:52 +0000 Subject: [PATCH 21/94] 8362532: Test gc/g1/plab/* duplicate command-line options Reviewed-by: tschatzl, ayang --- .../gc/g1/plab/TestPLABEvacuationFailure.java | 1 - test/hotspot/jtreg/gc/g1/plab/lib/PLABUtils.java | 14 ++++++-------- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/test/hotspot/jtreg/gc/g1/plab/TestPLABEvacuationFailure.java b/test/hotspot/jtreg/gc/g1/plab/TestPLABEvacuationFailure.java index f5b61e470b0..67a3f510c21 100644 --- a/test/hotspot/jtreg/gc/g1/plab/TestPLABEvacuationFailure.java +++ b/test/hotspot/jtreg/gc/g1/plab/TestPLABEvacuationFailure.java @@ -99,7 +99,6 @@ public class TestPLABEvacuationFailure { // Set up test GC and PLAB options List testOptions = new ArrayList<>(); Collections.addAll(testOptions, COMMON_OPTIONS); - Collections.addAll(testOptions, Utils.getTestJavaOpts()); Collections.addAll(testOptions, "-XX:ParallelGCThreads=" + parGCThreads, "-XX:ParallelGCBufferWastePct=" + wastePct, diff --git a/test/hotspot/jtreg/gc/g1/plab/lib/PLABUtils.java b/test/hotspot/jtreg/gc/g1/plab/lib/PLABUtils.java index 21a70c5fe05..c9d76d9da42 100644 --- a/test/hotspot/jtreg/gc/g1/plab/lib/PLABUtils.java +++ b/test/hotspot/jtreg/gc/g1/plab/lib/PLABUtils.java @@ -72,14 +72,12 @@ public class PLABUtils { if (options == null) { throw new IllegalArgumentException("Options cannot be null"); } - List executionOtions = new ArrayList<>( - Arrays.asList(Utils.getTestJavaOpts()) - ); - Collections.addAll(executionOtions, WB_DIAGNOSTIC_OPTIONS); - Collections.addAll(executionOtions, G1_PLAB_LOGGING_OPTIONS); - Collections.addAll(executionOtions, GC_TUNE_OPTIONS); - executionOtions.addAll(options); - return executionOtions; + List executionOptions = new ArrayList<>(); + Collections.addAll(executionOptions, WB_DIAGNOSTIC_OPTIONS); + Collections.addAll(executionOptions, G1_PLAB_LOGGING_OPTIONS); + Collections.addAll(executionOptions, GC_TUNE_OPTIONS); + executionOptions.addAll(options); + return executionOptions; } /** From 1b94a3466e7bb3815c0caeeeebff6018b6440455 Mon Sep 17 00:00:00 2001 From: Erik Gahlin Date: Mon, 21 Jul 2025 10:35:43 +0000 Subject: [PATCH 22/94] 8362836: JFR: Broken pipe in jdk/jfr/event/io/TestIOTopFrame.java Reviewed-by: mgronlun --- test/jdk/jdk/jfr/event/io/TestIOTopFrame.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/jdk/jdk/jfr/event/io/TestIOTopFrame.java b/test/jdk/jdk/jfr/event/io/TestIOTopFrame.java index 1184692b723..29d509b9147 100644 --- a/test/jdk/jdk/jfr/event/io/TestIOTopFrame.java +++ b/test/jdk/jdk/jfr/event/io/TestIOTopFrame.java @@ -52,6 +52,7 @@ import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.concurrent.Callable; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -246,8 +247,10 @@ public class TestIOTopFrame { r.enable(EVENT_SOCKET_READ).withStackTrace(); r.enable(EVENT_SOCKET_WRITE).withStackTrace(); r.start(); - Thread readerThread = Thread.ofPlatform().start(() -> readSocketChannel(ssc)); + CountDownLatch latch = new CountDownLatch(1); + Thread readerThread = Thread.ofPlatform().start(() -> readSocketChannel(ssc, latch)); writeSocketChannel(ssc); + latch.countDown(); readerThread.join(); r.stop(); assertTopFrames(r, "readSocket", 6, "readSocketChannel", 2, "writeSocket", 3, "writeSocketChannel", 2); @@ -255,13 +258,14 @@ public class TestIOTopFrame { } } - private static void readSocketChannel(ServerSocketChannel ssc) { + private static void readSocketChannel(ServerSocketChannel ssc, CountDownLatch latch) { ByteBuffer[] buffers = createBuffers(); try (SocketChannel sc = ssc.accept()) { sc.read(buffers[0]); // 1 sc.read(buffers); // 2 try (InputStream is = sc.socket().getInputStream()) { readSocket(is); + latch.await(); } } catch (Exception ioe) { throw new RuntimeException(ioe); From fd7f78a5351a5b00bc9a3173e7671afe2d1e6fe4 Mon Sep 17 00:00:00 2001 From: Hamlin Li Date: Mon, 21 Jul 2025 11:10:20 +0000 Subject: [PATCH 23/94] 8362493: Cleanup CodeBuffer::copy_relocations_to Reviewed-by: mhaessig, kvn --- src/hotspot/share/asm/codeBuffer.cpp | 10 +++------- src/hotspot/share/asm/codeBuffer.hpp | 3 +-- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/hotspot/share/asm/codeBuffer.cpp b/src/hotspot/share/asm/codeBuffer.cpp index 81bfc932147..2c6b7d7e96e 100644 --- a/src/hotspot/share/asm/codeBuffer.cpp +++ b/src/hotspot/share/asm/codeBuffer.cpp @@ -626,7 +626,7 @@ csize_t CodeBuffer::total_relocation_size() const { return (csize_t) align_up(total, HeapWordSize); } -csize_t CodeBuffer::copy_relocations_to(address buf, csize_t buf_limit, bool only_inst) const { +csize_t CodeBuffer::copy_relocations_to(address buf, csize_t buf_limit) const { csize_t buf_offset = 0; csize_t code_end_so_far = 0; csize_t code_point_so_far = 0; @@ -635,10 +635,6 @@ csize_t CodeBuffer::copy_relocations_to(address buf, csize_t buf_limit, bool onl assert(buf_limit % HeapWordSize == 0, "buf must be evenly sized"); for (int n = (int) SECT_FIRST; n < (int)SECT_LIMIT; n++) { - if (only_inst && (n != (int)SECT_INSTS)) { - // Need only relocation info for code. - continue; - } // pull relocs out of each section const CodeSection* cs = code_section(n); assert(!(cs->is_empty() && cs->locs_count() > 0), "sanity"); @@ -705,7 +701,7 @@ csize_t CodeBuffer::copy_relocations_to(address buf, csize_t buf_limit, bool onl buf_offset += sizeof(relocInfo); } - assert(only_inst || code_end_so_far == total_content_size(), "sanity"); + assert(code_end_so_far == total_content_size(), "sanity"); return buf_offset; } @@ -721,7 +717,7 @@ csize_t CodeBuffer::copy_relocations_to(CodeBlob* dest) const { } // if dest is null, this is just the sizing pass // - buf_offset = copy_relocations_to(buf, buf_limit, false); + buf_offset = copy_relocations_to(buf, buf_limit); return buf_offset; } diff --git a/src/hotspot/share/asm/codeBuffer.hpp b/src/hotspot/share/asm/codeBuffer.hpp index 96e9a77a923..ec9f347e334 100644 --- a/src/hotspot/share/asm/codeBuffer.hpp +++ b/src/hotspot/share/asm/codeBuffer.hpp @@ -641,6 +641,7 @@ class CodeBuffer: public StackObj DEBUG_ONLY(COMMA private Scrubber) { // copies combined relocations to the blob, returns bytes copied // (if target is null, it is a dry run only, just for sizing) csize_t copy_relocations_to(CodeBlob* blob) const; + csize_t copy_relocations_to(address buf, csize_t buf_limit) const; // copies combined code to the blob (assumes relocs are already in there) void copy_code_to(CodeBlob* blob); @@ -791,8 +792,6 @@ class CodeBuffer: public StackObj DEBUG_ONLY(COMMA private Scrubber) { int total_skipped_instructions_size() const; - csize_t copy_relocations_to(address buf, csize_t buf_limit, bool only_inst) const; - // allocated size of any and all recorded oops csize_t total_oop_size() const { OopRecorder* recorder = oop_recorder(); From 644e400cd1f8a80df01b4f1755450f86709485f4 Mon Sep 17 00:00:00 2001 From: Lei Zhu Date: Mon, 21 Jul 2025 12:24:49 +0000 Subject: [PATCH 24/94] 8362611: [GCC static analyzer] memory leak in ps_core.c core_handle_note Reviewed-by: dholmes, mbaesken --- src/jdk.hotspot.agent/linux/native/libsaproc/ps_core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jdk.hotspot.agent/linux/native/libsaproc/ps_core.c b/src/jdk.hotspot.agent/linux/native/libsaproc/ps_core.c index 808ef42e069..c3aaaf440a3 100644 --- a/src/jdk.hotspot.agent/linux/native/libsaproc/ps_core.c +++ b/src/jdk.hotspot.agent/linux/native/libsaproc/ps_core.c @@ -286,7 +286,7 @@ static bool core_handle_note(struct ps_prochandle* ph, ELF_PHDR* note_phdr) { if (notep->n_type == NT_PRSTATUS) { if (core_handle_prstatus(ph, descdata, notep->n_descsz) != true) { print_error("failed to handle NT_PRSTATUS note\n"); - return false; + goto err; } } else if (notep->n_type == NT_AUXV) { // Get first segment from entry point From 15b5b54ac707ba0d4e473fd6eb02c38a8efe705c Mon Sep 17 00:00:00 2001 From: Dingli Zhang Date: Mon, 21 Jul 2025 13:34:24 +0000 Subject: [PATCH 25/94] 8357694: RISC-V: Several IR verification tests fail when vlen=128 Reviewed-by: mhaessig, fyang, mli --- .../compiler/c2/irTests/TestIfMinMax.java | 12 ++++++++++-- .../loopopts/superword/RedTest_long.java | 10 +++++----- .../loopopts/superword/SumRed_Long.java | 2 +- .../superword/TestGeneralizedReductions.java | 18 +++++++++++++++--- ...UnorderedReductionPartialVectorization.java | 14 +++++++++++++- .../runner/LoopReductionOpTest.java | 6 +++++- 6 files changed, 49 insertions(+), 13 deletions(-) diff --git a/test/hotspot/jtreg/compiler/c2/irTests/TestIfMinMax.java b/test/hotspot/jtreg/compiler/c2/irTests/TestIfMinMax.java index bb0a1200a7f..fdc0a83fb8b 100644 --- a/test/hotspot/jtreg/compiler/c2/irTests/TestIfMinMax.java +++ b/test/hotspot/jtreg/compiler/c2/irTests/TestIfMinMax.java @@ -296,7 +296,11 @@ public class TestIfMinMax { @Test @IR(applyIf = { "SuperWordReductions", "true" }, - applyIfCPUFeatureOr = { "avx512", "true", "rvv", "true" }, + applyIfCPUFeature = { "avx512", "true" }, + counts = { IRNode.MAX_REDUCTION_V, "> 0" }) + @IR(applyIfPlatform = {"riscv64", "true"}, + applyIfAnd = { "SuperWordReductions", "true", "MaxVectorSize", ">=32" }, + applyIfCPUFeature = { "rvv", "true" }, counts = { IRNode.MAX_REDUCTION_V, "> 0" }) @Arguments(setup = "setupLongArrays") public Object[] testMaxLongReduction(long[] a, long[] b) { @@ -331,7 +335,11 @@ public class TestIfMinMax { @Test @IR(applyIf = { "SuperWordReductions", "true" }, - applyIfCPUFeatureOr = { "avx512", "true", "rvv", "true" }, + applyIfCPUFeature = { "avx512", "true" }, + counts = { IRNode.MIN_REDUCTION_V, "> 0" }) + @IR(applyIfPlatform = {"riscv64", "true"}, + applyIfAnd = { "SuperWordReductions", "true", "MaxVectorSize", ">=32" }, + applyIfCPUFeature = { "rvv", "true" }, counts = { IRNode.MIN_REDUCTION_V, "> 0" }) @Arguments(setup = "setupLongArrays") public Object[] testMinLongReduction(long[] a, long[] b) { diff --git a/test/hotspot/jtreg/compiler/loopopts/superword/RedTest_long.java b/test/hotspot/jtreg/compiler/loopopts/superword/RedTest_long.java index 10cd32bbbc7..cd8e0aa8b7f 100644 --- a/test/hotspot/jtreg/compiler/loopopts/superword/RedTest_long.java +++ b/test/hotspot/jtreg/compiler/loopopts/superword/RedTest_long.java @@ -140,7 +140,7 @@ public class RedTest_long { counts = {IRNode.ADD_REDUCTION_VL, ">= 1", IRNode.ADD_REDUCTION_VL, "<= 2"}) // one for main-loop, one for vector-post-loop @IR(applyIfPlatform = {"riscv64", "true"}, applyIfCPUFeature = {"rvv", "true"}, - applyIfAnd = {"SuperWordReductions", "true", "LoopMaxUnroll", ">= 8"}, + applyIfAnd = {"SuperWordReductions", "true", "LoopMaxUnroll", ">= 8", "MaxVectorSize", ">=32"}, counts = {IRNode.ADD_REDUCTION_VL, ">= 1", IRNode.ADD_REDUCTION_VL, "<= 2"}) // one for main-loop, one for vector-post-loop public static long sumReductionImplement( long[] a, @@ -162,7 +162,7 @@ public class RedTest_long { counts = {IRNode.OR_REDUCTION_V, ">= 1", IRNode.OR_REDUCTION_V, "<= 2"}) // one for main-loop, one for vector-post-loop @IR(applyIfPlatform = {"riscv64", "true"}, applyIfCPUFeature = {"rvv", "true"}, - applyIfAnd = {"SuperWordReductions", "true", "LoopMaxUnroll", ">= 8"}, + applyIfAnd = {"SuperWordReductions", "true", "LoopMaxUnroll", ">= 8", "MaxVectorSize", ">=32"}, counts = {IRNode.OR_REDUCTION_V, ">= 1", IRNode.OR_REDUCTION_V, "<= 2"}) // one for main-loop, one for vector-post-loop public static long orReductionImplement( long[] a, @@ -184,7 +184,7 @@ public class RedTest_long { counts = {IRNode.AND_REDUCTION_V, ">= 1", IRNode.AND_REDUCTION_V, "<= 2"}) // one for main-loop, one for vector-post-loop @IR(applyIfPlatform = {"riscv64", "true"}, applyIfCPUFeature = {"rvv", "true"}, - applyIfAnd = {"SuperWordReductions", "true", "LoopMaxUnroll", ">= 8"}, + applyIfAnd = {"SuperWordReductions", "true", "LoopMaxUnroll", ">= 8", "MaxVectorSize", ">=32"}, counts = {IRNode.AND_REDUCTION_V, ">= 1", IRNode.AND_REDUCTION_V, "<= 2"}) // one for main-loop, one for vector-post-loop public static long andReductionImplement( long[] a, @@ -206,7 +206,7 @@ public class RedTest_long { counts = {IRNode.XOR_REDUCTION_V, ">= 1", IRNode.XOR_REDUCTION_V, "<= 2"}) // one for main-loop, one for vector-post-loop @IR(applyIfPlatform = {"riscv64", "true"}, applyIfCPUFeature = {"rvv", "true"}, - applyIfAnd = {"SuperWordReductions", "true", "LoopMaxUnroll", ">= 8"}, + applyIfAnd = {"SuperWordReductions", "true", "LoopMaxUnroll", ">= 8", "MaxVectorSize", ">=32"}, counts = {IRNode.XOR_REDUCTION_V, ">= 1", IRNode.XOR_REDUCTION_V, "<= 2"}) // one for main-loop, one for vector-post-loop public static long xorReductionImplement( long[] a, @@ -228,7 +228,7 @@ public class RedTest_long { counts = {IRNode.MUL_REDUCTION_VL, ">= 1", IRNode.MUL_REDUCTION_VL, "<= 2"}) // one for main-loop, one for vector-post-loop @IR(applyIfPlatform = {"riscv64", "true"}, applyIfCPUFeature = {"rvv", "true"}, - applyIfAnd = {"SuperWordReductions", "true", "LoopMaxUnroll", ">= 8"}, + applyIfAnd = {"SuperWordReductions", "true", "LoopMaxUnroll", ">= 8", "MaxVectorSize", ">=32"}, counts = {IRNode.MUL_REDUCTION_VL, ">= 1", IRNode.MUL_REDUCTION_VL, "<= 2"}) // one for main-loop, one for vector-post-loop public static long mulReductionImplement( long[] a, diff --git a/test/hotspot/jtreg/compiler/loopopts/superword/SumRed_Long.java b/test/hotspot/jtreg/compiler/loopopts/superword/SumRed_Long.java index e803b33bd4f..64ec0941b22 100644 --- a/test/hotspot/jtreg/compiler/loopopts/superword/SumRed_Long.java +++ b/test/hotspot/jtreg/compiler/loopopts/superword/SumRed_Long.java @@ -98,7 +98,7 @@ public class SumRed_Long { counts = {IRNode.ADD_REDUCTION_VL, ">= 1", IRNode.ADD_REDUCTION_VL, "<= 2"}) // one for main-loop, one for vector-post-loop @IR(applyIfPlatform = {"riscv64", "true"}, applyIfCPUFeature = {"rvv", "true"}, - applyIfAnd = {"SuperWordReductions", "true", "LoopMaxUnroll", ">= 8"}, + applyIfAnd = {"SuperWordReductions", "true", "LoopMaxUnroll", ">= 8", "MaxVectorSize", ">=32"}, counts = {IRNode.ADD_REDUCTION_VL, ">= 1", IRNode.ADD_REDUCTION_VL, "<= 2"}) // one for main-loop, one for vector-post-loop public static long sumReductionImplement( long[] a, diff --git a/test/hotspot/jtreg/compiler/loopopts/superword/TestGeneralizedReductions.java b/test/hotspot/jtreg/compiler/loopopts/superword/TestGeneralizedReductions.java index 92bc4f17446..bda0979a70b 100644 --- a/test/hotspot/jtreg/compiler/loopopts/superword/TestGeneralizedReductions.java +++ b/test/hotspot/jtreg/compiler/loopopts/superword/TestGeneralizedReductions.java @@ -81,10 +81,14 @@ public class TestGeneralizedReductions { } @Test - @IR(applyIfCPUFeatureOr = {"avx2", "true", "rvv", "true"}, + @IR(applyIfCPUFeature = {"avx2", "true"}, applyIf = {"SuperWordReductions", "true"}, applyIfPlatform = {"64-bit", "true"}, counts = {IRNode.ADD_REDUCTION_VI, ">= 1"}) + @IR(applyIfPlatform = {"riscv64", "true"}, + applyIfCPUFeature = {"rvv", "true"}, + applyIfAnd = {"SuperWordReductions", "true", "MaxVectorSize", ">=32"}, + counts = {IRNode.ADD_REDUCTION_VI, ">= 1"}) private static long testReductionOnGlobalAccumulator(long[] array) { acc = 0; for (int i = 0; i < array.length; i++) { @@ -94,10 +98,14 @@ public class TestGeneralizedReductions { } @Test - @IR(applyIfCPUFeatureOr = {"avx2", "true", "rvv", "true"}, + @IR(applyIfCPUFeature = {"avx2", "true"}, applyIf = {"SuperWordReductions", "true"}, applyIfPlatform = {"64-bit", "true"}, counts = {IRNode.ADD_REDUCTION_VI, ">= 1"}) + @IR(applyIfPlatform = {"riscv64", "true"}, + applyIfCPUFeature = {"rvv", "true"}, + applyIfAnd = {"SuperWordReductions", "true", "MaxVectorSize", ">=32"}, + counts = {IRNode.ADD_REDUCTION_VI, ">= 1"}) private static long testReductionOnPartiallyUnrolledLoop(long[] array) { int sum = 0; for (int i = 0; i < array.length / 2; i++) { @@ -108,10 +116,14 @@ public class TestGeneralizedReductions { } @Test - @IR(applyIfCPUFeatureOr = {"avx2", "true", "rvv", "true"}, + @IR(applyIfCPUFeature = {"avx2", "true"}, applyIf = {"SuperWordReductions", "true"}, applyIfPlatform = {"64-bit", "true"}, counts = {IRNode.ADD_REDUCTION_VI, ">= 1"}) + @IR(applyIfPlatform = {"riscv64", "true"}, + applyIfCPUFeature = {"rvv", "true"}, + applyIfAnd = {"SuperWordReductions", "true", "MaxVectorSize", ">=32"}, + counts = {IRNode.ADD_REDUCTION_VI, ">= 1"}) private static long testReductionOnLargePartiallyUnrolledLoop(long[] array) { int sum = 0; for (int i = 0; i < array.length / 8; i++) { diff --git a/test/hotspot/jtreg/compiler/loopopts/superword/TestUnorderedReductionPartialVectorization.java b/test/hotspot/jtreg/compiler/loopopts/superword/TestUnorderedReductionPartialVectorization.java index 0a0f0c3114e..c862704da26 100644 --- a/test/hotspot/jtreg/compiler/loopopts/superword/TestUnorderedReductionPartialVectorization.java +++ b/test/hotspot/jtreg/compiler/loopopts/superword/TestUnorderedReductionPartialVectorization.java @@ -66,7 +66,19 @@ public class TestUnorderedReductionPartialVectorization { IRNode.OR_REDUCTION_V, "> 0",}, applyIfOr = {"AlignVector", "false", "UseCompactObjectHeaders", "false"}, applyIfPlatform = {"64-bit", "true"}, - applyIfCPUFeatureOr = {"avx2", "true", "rvv", "true"}) + applyIfCPUFeature = {"avx2", "true"}) + @IR(counts = {IRNode.LOAD_VECTOR_I, IRNode.VECTOR_SIZE + "min(max_int, max_long)", "> 0", + IRNode.VECTOR_CAST_I2L, IRNode.VECTOR_SIZE + "min(max_int, max_long)", "> 0", + IRNode.OR_REDUCTION_V, "> 0",}, + applyIfAnd = {"AlignVector", "false", "MaxVectorSize", ">=32"}, + applyIfPlatform = {"riscv64", "true"}, + applyIfCPUFeature = {"rvv", "true"}) + @IR(counts = {IRNode.LOAD_VECTOR_I, IRNode.VECTOR_SIZE + "min(max_int, max_long)", "> 0", + IRNode.VECTOR_CAST_I2L, IRNode.VECTOR_SIZE + "min(max_int, max_long)", "> 0", + IRNode.OR_REDUCTION_V, "> 0",}, + applyIfAnd = {"UseCompactObjectHeaders", "false", "MaxVectorSize", ">=32"}, + applyIfPlatform = {"riscv64", "true"}, + applyIfCPUFeature = {"rvv", "true"}) static long test1(int[] data, long sum) { for (int i = 0; i < data.length; i+=2) { // Mixing int and long ops means we only end up allowing half of the int diff --git a/test/hotspot/jtreg/compiler/vectorization/runner/LoopReductionOpTest.java b/test/hotspot/jtreg/compiler/vectorization/runner/LoopReductionOpTest.java index ac8be6bc0ec..546d99f5cce 100644 --- a/test/hotspot/jtreg/compiler/vectorization/runner/LoopReductionOpTest.java +++ b/test/hotspot/jtreg/compiler/vectorization/runner/LoopReductionOpTest.java @@ -177,7 +177,11 @@ public class LoopReductionOpTest extends VectorizationTestRunner { @Test @IR(applyIfCPUFeatureOr = {"asimd", "true", "sse2", "true", "rvv", "true"}, counts = {IRNode.STORE_VECTOR, ">0"}) - @IR(applyIfCPUFeatureOr = {"avx2", "true", "rvv", "true"}, + @IR(applyIfCPUFeature = {"avx2", "true"}, + counts = {IRNode.ADD_REDUCTION_V, ">0"}) + @IR(applyIfPlatform = {"riscv64", "true"}, + applyIfCPUFeature = {"rvv", "true"}, + applyIf = {"MaxVectorSize", ">=32" }, counts = {IRNode.ADD_REDUCTION_V, ">0"}) public long reductionWithNonReductionDifferentSizes() { long res = 0L; From f8c8bcf4fd31509fdb40d32e8e16ba4fba1f987d Mon Sep 17 00:00:00 2001 From: David Briemann Date: Mon, 21 Jul 2025 15:48:06 +0000 Subject: [PATCH 26/94] 8362602: Add test.timeout.factor to CompileFactory to avoid test timeouts Reviewed-by: mhaessig, mbaesken, clanger --- .../jtreg/compiler/lib/compile_framework/Compile.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/test/hotspot/jtreg/compiler/lib/compile_framework/Compile.java b/test/hotspot/jtreg/compiler/lib/compile_framework/Compile.java index 7fde22cd207..e7f9b949a6d 100644 --- a/test/hotspot/jtreg/compiler/lib/compile_framework/Compile.java +++ b/test/hotspot/jtreg/compiler/lib/compile_framework/Compile.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 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 @@ -40,6 +40,7 @@ import jdk.test.lib.JDKToolFinder; */ class Compile { private static final int COMPILE_TIMEOUT = 60; + private static final float timeoutFactor = Float.parseFloat(System.getProperty("test.timeout.factor", "1.0")); private static final String JAVA_PATH = JDKToolFinder.getJDKTool("java"); private static final String JAVAC_PATH = JDKToolFinder.getJDKTool("javac"); @@ -182,7 +183,8 @@ class Compile { int exitCode; try { Process process = builder.start(); - boolean exited = process.waitFor(COMPILE_TIMEOUT, TimeUnit.SECONDS); + long timeout = COMPILE_TIMEOUT * (long)timeoutFactor; + boolean exited = process.waitFor(timeout, TimeUnit.SECONDS); if (!exited) { process.destroyForcibly(); System.out.println("Timeout: compile command: " + String.join(" ", command)); From 9dd93c6a2c5fb4c3a9f2a063a7ab402f9292ad03 Mon Sep 17 00:00:00 2001 From: Andrew Haley Date: Mon, 21 Jul 2025 17:05:50 +0000 Subject: [PATCH 27/94] 8361497: Scoped Values: orElse and orElseThrow do not access the cache Reviewed-by: alanb --- .../share/classes/java/lang/ScopedValue.java | 29 ++++--- .../openjdk/bench/java/lang/ScopedValues.java | 86 ++++++++++++++++++- 2 files changed, 100 insertions(+), 15 deletions(-) diff --git a/src/java.base/share/classes/java/lang/ScopedValue.java b/src/java.base/share/classes/java/lang/ScopedValue.java index 57c6ca29a1e..0d5da3e0e2a 100644 --- a/src/java.base/share/classes/java/lang/ScopedValue.java +++ b/src/java.base/share/classes/java/lang/ScopedValue.java @@ -572,7 +572,7 @@ public final class ScopedValue { @SuppressWarnings("unchecked") private T slowGet() { - var value = findBinding(); + Object value = scopedValueBindings().find(this); if (value == Snapshot.NIL) { throw new NoSuchElementException("ScopedValue not bound"); } @@ -581,32 +581,35 @@ public final class ScopedValue { } /** - * {@return {@code true} if this scoped value is bound in the current thread} + * Return the value of the scoped value or NIL if not bound. + * Consult the cache, and only if the value is not found there + * search the list of bindings. Update the cache if the binding + * was found. */ - public boolean isBound() { + private Object findBinding() { Object[] objects = scopedValueCache(); if (objects != null) { int n = (hash & Cache.Constants.SLOT_MASK) * 2; if (objects[n] == this) { - return true; + return objects[n + 1]; } n = ((hash >>> Cache.INDEX_BITS) & Cache.Constants.SLOT_MASK) * 2; if (objects[n] == this) { - return true; + return objects[n + 1]; } } - var value = findBinding(); - boolean result = (value != Snapshot.NIL); - if (result) Cache.put(this, value); - return result; + Object value = scopedValueBindings().find(this); + boolean found = (value != Snapshot.NIL); + if (found) Cache.put(this, value); + return value; } /** - * Return the value of the scoped value or NIL if not bound. + * {@return {@code true} if this scoped value is bound in the current thread} */ - private Object findBinding() { - Object value = scopedValueBindings().find(this); - return value; + public boolean isBound() { + Object obj = findBinding(); + return obj != Snapshot.NIL; } /** diff --git a/test/micro/org/openjdk/bench/java/lang/ScopedValues.java b/test/micro/org/openjdk/bench/java/lang/ScopedValues.java index 6f88bbcc6b1..710cf87e72f 100644 --- a/test/micro/org/openjdk/bench/java/lang/ScopedValues.java +++ b/test/micro/org/openjdk/bench/java/lang/ScopedValues.java @@ -29,6 +29,7 @@ import java.util.concurrent.TimeUnit; import org.openjdk.jmh.annotations.*; import org.openjdk.jmh.infra.Blackhole; +import static java.lang.ScopedValue.where; import static org.openjdk.bench.java.lang.ScopedValuesData.*; /** @@ -102,6 +103,26 @@ public class ScopedValues { return result; } + @Benchmark + @OutputTimeUnit(TimeUnit.NANOSECONDS) + public int thousandUnboundOrElses(Blackhole bh) throws Exception { + int result = 0; + for (int i = 0; i < 1_000; i++) { + result += ScopedValuesData.unbound.orElse(1); + } + return result; + } + + @Benchmark + @OutputTimeUnit(TimeUnit.NANOSECONDS) + public int thousandBoundOrElses(Blackhole bh) throws Exception { + int result = 0; + for (int i = 0; i < 1_000; i++) { + result += ScopedValuesData.sl1.orElse(1); + } + return result; + } + // Test 2: stress the ScopedValue cache. // The idea here is to use a bunch of bound values cyclically, which // stresses the ScopedValue cache. @@ -137,12 +158,12 @@ public class ScopedValues { @Benchmark @OutputTimeUnit(TimeUnit.NANOSECONDS) public int CreateBindThenGetThenRemove_ScopedValue() throws Exception { - return ScopedValue.where(sl1, THE_ANSWER).call(sl1::get); + return where(sl1, THE_ANSWER).call(sl1::get); } // Create a Carrier ahead of time: might be slightly faster - private static final ScopedValue.Carrier HOLD_42 = ScopedValue.where(sl1, 42); + private static final ScopedValue.Carrier HOLD_42 = where(sl1, 42); @Benchmark @OutputTimeUnit(TimeUnit.NANOSECONDS) public int bindThenGetThenRemove_ScopedValue() throws Exception { @@ -230,4 +251,65 @@ public class ScopedValues { ScopedValue val = ScopedValue.newInstance(); return val; } + + // Test 6: Performance with a large number of bindings + static final long deepCall(ScopedValue outer, long n) { + long result = 0; + if (n > 0) { + ScopedValue sv = ScopedValue.newInstance(); + return where(sv, n).call(() -> deepCall(outer, n - 1)); + } else { + for (int i = 0; i < 1_000_000; i++) { + result += outer.orElse(12); + } + } + return result; + } + + @Benchmark + @OutputTimeUnit(TimeUnit.MILLISECONDS) + public long deepBindingTest1() { + return deepCall(ScopedValuesData.unbound, 1000); + } + + @Benchmark + @OutputTimeUnit(TimeUnit.MILLISECONDS) + public long deepBindingTest2() { + return deepCall(ScopedValuesData.sl1, 1000); + } + + + // Test 7: Performance with a large number of bindings + // Different from Test 6 in that we recursively build a very long + // list of Carriers and invoke Carrier.call() only once. + static final long deepCall2(ScopedValue outer, ScopedValue.Carrier carrier, long n) { + long result = 0; + if (n > 0) { + ScopedValue sv = ScopedValue.newInstance(); + return deepCall2(outer, carrier.where(sv, n), n - 1); + } else { + result = carrier.call(() -> { + long sum = 0; + for (int i = 0; i < 1_000_000; i++) { + sum += outer.orElse(12); + } + return sum; + }); + } + return result; + } + + @Benchmark + @OutputTimeUnit(TimeUnit.MILLISECONDS) + public long deepBindingTest3() { + return deepCall2(ScopedValuesData.unbound, where(ScopedValuesData.sl2,0), 1000); + } + + @Benchmark + @OutputTimeUnit(TimeUnit.MILLISECONDS) + public long deepBindingTest4() { + return deepCall2(ScopedValuesData.sl1, where(ScopedValuesData.sl2, 0), 1000); + } + + } From 48ba9d415f64b55fed2e0ae2f7e3f50b7d8c82f6 Mon Sep 17 00:00:00 2001 From: Koushik Thirupattur Date: Mon, 21 Jul 2025 19:30:03 +0000 Subject: [PATCH 28/94] 8349946: Cipher javadoc could describe AEAD reuse better Reviewed-by: ascarpino --- .../share/classes/javax/crypto/Cipher.java | 125 +++--------------- 1 file changed, 19 insertions(+), 106 deletions(-) diff --git a/src/java.base/share/classes/javax/crypto/Cipher.java b/src/java.base/share/classes/javax/crypto/Cipher.java index 22dc66127e2..cb3d24cd716 100644 --- a/src/java.base/share/classes/javax/crypto/Cipher.java +++ b/src/java.base/share/classes/javax/crypto/Cipher.java @@ -96,49 +96,30 @@ import sun.security.util.KnownOIDs; * provide authenticity assurances for both confidential data and * Additional Associated Data (AAD) that is not encrypted. (Please see * RFC 5116 for more - * information on AEAD and AAD algorithms such as GCM/CCM.) Both + * information on AEAD and AAD algorithms.) Both * confidential and AAD data can be used when calculating the * authentication tag (similar to a {@link Mac}). This tag is appended * to the ciphertext during encryption, and is verified on decryption. *

- * AEAD modes such as GCM/CCM perform all AAD authenticity calculations + * AEAD modes perform all AAD authenticity calculations * before starting the ciphertext authenticity calculations. To avoid * implementations having to internally buffer ciphertext, all AAD data - * must be supplied to GCM/CCM implementations (via the {@code updateAAD} + * must be supplied to their implementations (via the {@code updateAAD} * methods) before the ciphertext is processed (via * the {@code update} and {@code doFinal} methods). *

- * Note that GCM mode has a uniqueness requirement on IVs used in - * encryption with a given key. When IVs are repeated for GCM - * encryption, such usages are subject to forgery attacks. Thus, after - * each encryption operation using GCM mode, callers should re-initialize - * the {@code Cipher} objects with GCM parameters which have a different IV - * value. - *

- *     GCMParameterSpec s = ...;
- *     cipher.init(..., s);
+ * When a {@code doFinal} method completes the operation, the {@code Cipher} object will attempt
+ * to reset the state to the most recent call to {@code init}, allowing for additional
+ * operations. A successful reset depends on the mode ({@code ENCRYPT_MODE} or
+ * {@code DECRYPT_MODE}) and the algorithm. AEAD algorithms may not reset, in order to prevent
+ * forgery attacks due to Key and IV uniqueness requirements.
+ * 

An {@link IllegalStateException} will be thrown when calling {@code update} + * or {@code doFinal} methods if a reset did not occur. A call to {@code init} will + * re-initialize the {@code Cipher} object with new parameters. * - * // If the GCM parameters were generated by the provider, it can - * // be retrieved by: - * // cipher.getParameters().getParameterSpec(GCMParameterSpec.class); + * @see javax.crypto.Cipher + * @see javax.crypto.spec.GCMParameterSpec * - * cipher.updateAAD(...); // AAD - * cipher.update(...); // Multi-part update - * cipher.doFinal(...); // conclusion of operation - * - * // Use a different IV value for every encryption - * byte[] newIv = ...; - * s = new GCMParameterSpec(s.getTLen(), newIv); - * cipher.init(..., s); - * ... - * - *

- * The ChaCha20 and ChaCha20-Poly1305 algorithms have a similar requirement - * for unique nonces with a given key. After each encryption or decryption - * operation, callers should re-initialize their ChaCha20 or ChaCha20-Poly1305 - * ciphers with parameters that specify a different nonce value. Please - * see RFC 7539 for more - * information on the ChaCha20 and ChaCha20-Poly1305 algorithms. *

* Every implementation of the Java platform is required to support * the following standard {@code Cipher} object transformations with @@ -167,8 +148,6 @@ import sun.security.util.KnownOIDs; * * @spec https://www.rfc-editor.org/info/rfc5116 * RFC 5116: An Interface and Algorithms for Authenticated Encryption - * @spec https://www.rfc-editor.org/info/rfc7539 - * RFC 7539: ChaCha20 and Poly1305 for IETF Protocols * @spec security/standard-names.html Java Security Standard Algorithm Names * @author Jan Luehe * @see KeyGenerator @@ -2110,21 +2089,10 @@ public class Cipher { * case of decryption. * The result is stored in a new buffer. * - *

Upon finishing, this method resets this {@code Cipher} object - * to the state it was in when previously initialized via a call to - * {@code init}. - * That is, the object is reset and available to encrypt or decrypt - * (depending on the operation mode that was specified in the call to - * {@code init}) more data. - * - *

Note: if any exception is thrown, this {@code Cipher} object - * may need to be reset before it can be used again. - * * @return the new buffer with the result * * @throws IllegalStateException if this {@code Cipher} object - * is in a wrong state (e.g., has not been initialized, or is not - * in {@code ENCRYPT_MODE} or {@code DECRYPT_MODE}) + * is in an incorrect mode or cannot be reset. * @throws IllegalBlockSizeException if this cipher is a block cipher, * no padding has been requested (only in encryption mode), and the total * input length of the data processed by this cipher is not a multiple of @@ -2224,23 +2192,12 @@ public class Cipher { * case of decryption. * The result is stored in a new buffer. * - *

Upon finishing, this method resets this {@code Cipher} object - * to the state it was in when previously initialized via a call to - * {@code init}. - * That is, the object is reset and available to encrypt or decrypt - * (depending on the operation mode that was specified in the call to - * {@code init}) more data. - * - *

Note: if any exception is thrown, this {@code Cipher} object - * may need to be reset before it can be used again. - * * @param input the input buffer * * @return the new buffer with the result * * @throws IllegalStateException if this {@code Cipher} object - * is in a wrong state (e.g., has not been initialized, or is not - * in {@code ENCRYPT_MODE} or {@code DECRYPT_MODE}) + * is in an incorrect mode or cannot be reset. * @throws IllegalBlockSizeException if this cipher is a block cipher, * no padding has been requested (only in encryption mode), and the total * input length of the data processed by this cipher is not a multiple of @@ -2280,16 +2237,6 @@ public class Cipher { * case of decryption. * The result is stored in a new buffer. * - *

Upon finishing, this method resets this {@code Cipher} object - * to the state it was in when previously initialized via a call to - * {@code init}. - * That is, the object is reset and available to encrypt or decrypt - * (depending on the operation mode that was specified in the call to - * {@code init}) more data. - * - *

Note: if any exception is thrown, this {@code Cipher} object - * may need to be reset before it can be used again. - * * @param input the input buffer * @param inputOffset the offset in {@code input} where the input * starts @@ -2298,8 +2245,7 @@ public class Cipher { * @return the new buffer with the result * * @throws IllegalStateException if this {@code Cipher} object - * is in a wrong state (e.g., has not been initialized, or is not - * in {@code ENCRYPT_MODE} or {@code DECRYPT_MODE}) + * is in an incorrect mode or cannot be reset. * @throws IllegalBlockSizeException if this cipher is a block cipher, * no padding has been requested (only in encryption mode), and the total * input length of the data processed by this cipher is not a multiple of @@ -2346,16 +2292,6 @@ public class Cipher { * {@link #getOutputSize(int) getOutputSize} to determine how big * the output buffer should be. * - *

Upon finishing, this method resets this {@code Cipher} object - * to the state it was in when previously initialized via a call to - * {@code init}. - * That is, the object is reset and available to encrypt or decrypt - * (depending on the operation mode that was specified in the call to - * {@code init}) more data. - * - *

Note: if any exception is thrown, this {@code Cipher} object - * may need to be reset before it can be used again. - * *

Note: this method should be copy-safe, which means the * {@code input} and {@code output} buffers can reference * the same byte array and no unprocessed input data is overwritten @@ -2370,8 +2306,7 @@ public class Cipher { * @return the number of bytes stored in {@code output} * * @throws IllegalStateException if this {@code Cipher} object - * is in a wrong state (e.g., has not been initialized, or is not - * in or {@code ENCRYPT_MODE} or {@code DECRYPT_MODE}) + * is in an incorrect mode or cannot be reset. * @throws IllegalBlockSizeException if this cipher is a block cipher, * no padding has been requested (only in encryption mode), and the total * input length of the data processed by this cipher is not a multiple of @@ -2425,16 +2360,6 @@ public class Cipher { * {@link #getOutputSize(int) getOutputSize} to determine how big * the output buffer should be. * - *

Upon finishing, this method resets this {@code Cipher} object - * to the state it was in when previously initialized via a call to - * {@code init}. - * That is, the object is reset and available to encrypt or decrypt - * (depending on the operation mode that was specified in the call to - * {@code init}) more data. - * - *

Note: if any exception is thrown, this {@code Cipher} object - * may need to be reset before it can be used again. - * *

Note: this method should be copy-safe, which means the * {@code input} and {@code output} buffers can reference * the same byte array and no unprocessed input data is overwritten @@ -2451,8 +2376,7 @@ public class Cipher { * @return the number of bytes stored in {@code output} * * @throws IllegalStateException if this {@code Cipher} object - * is in a wrong state (e.g., has not been initialized, or is not - * in {@code ENCRYPT_MODE} or {@code DECRYPT_MODE}) + * is in an incorrect mode or cannot be reset. * @throws IllegalBlockSizeException if this cipher is a block cipher, * no padding has been requested (only in encryption mode), and the total * input length of the data processed by this cipher is not a multiple of @@ -2507,16 +2431,6 @@ public class Cipher { * {@link #getOutputSize(int) getOutputSize} to determine how big * the output buffer should be. * - *

Upon finishing, this method resets this {@code Cipher} object - * to the state it was in when previously initialized via a call to - * {@code init}. - * That is, the object is reset and available to encrypt or decrypt - * (depending on the operation mode that was specified in the call to - * {@code init}) more data. - * - *

Note: if any exception is thrown, this {@code Cipher} object - * may need to be reset before it can be used again. - * *

Note: this method should be copy-safe, which means the * {@code input} and {@code output} buffers can reference * the same byte array and no unprocessed input data is overwritten @@ -2528,8 +2442,7 @@ public class Cipher { * @return the number of bytes stored in {@code output} * * @throws IllegalStateException if this {@code Cipher} object - * is in a wrong state (e.g., has not been initialized, or is not - * in {@code ENCRYPT_MODE} or {@code DECRYPT_MODE}) + * is in an incorrect mode or cannot be reset. * @throws IllegalArgumentException if input and output are the * same object * @throws ReadOnlyBufferException if the output buffer is read-only From b8da9695f0cc049d6a07a7382afce4d22f8b2b1c Mon Sep 17 00:00:00 2001 From: Phil Race Date: Mon, 21 Jul 2025 19:51:56 +0000 Subject: [PATCH 29/94] 8362659: Remove sun.print.PrintJob2D.finalize() Reviewed-by: serb --- src/java.desktop/share/classes/sun/print/PrintJob2D.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/src/java.desktop/share/classes/sun/print/PrintJob2D.java b/src/java.desktop/share/classes/sun/print/PrintJob2D.java index 373cc7c8b2b..b3811e36367 100644 --- a/src/java.desktop/share/classes/sun/print/PrintJob2D.java +++ b/src/java.desktop/share/classes/sun/print/PrintJob2D.java @@ -912,15 +912,6 @@ public class PrintJob2D extends PrintJob implements Printable, Runnable { } } - /** - * Ends this print job once it is no longer referenced. - * @see #end - */ - @SuppressWarnings("removal") - public void finalize() { - end(); - } - /** * Prints the page at the specified index into the specified * {@link Graphics} context in the specified From 523993e9e8edc8dc84667ee3311a708b8b5da59c Mon Sep 17 00:00:00 2001 From: Phil Race Date: Mon, 21 Jul 2025 21:00:43 +0000 Subject: [PATCH 30/94] 8362291: [macOS] Remove finalize method in CGraphicsEnvironment.java Reviewed-by: bchristi, serb, kizune --- .../classes/sun/awt/CGraphicsEnvironment.java | 20 ++++++++++++------- .../native/libawt_lwawt/awt/CGraphicsEnv.m | 2 +- .../share/classes/sun/java2d/Disposer.java | 6 +++--- 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/src/java.desktop/macosx/classes/sun/awt/CGraphicsEnvironment.java b/src/java.desktop/macosx/classes/sun/awt/CGraphicsEnvironment.java index e11dabc7a10..99e078c3064 100644 --- a/src/java.desktop/macosx/classes/sun/awt/CGraphicsEnvironment.java +++ b/src/java.desktop/macosx/classes/sun/awt/CGraphicsEnvironment.java @@ -37,6 +37,8 @@ import java.util.List; import java.util.ListIterator; import java.util.Map; +import sun.java2d.Disposer; +import sun.java2d.DisposerRecord; import sun.java2d.SunGraphicsEnvironment; /** @@ -82,7 +84,7 @@ public final class CGraphicsEnvironment extends SunGraphicsEnvironment { /** * Remove the instance's registration with CGDisplayRemoveReconfigurationCallback() */ - private native void deregisterDisplayReconfiguration(long context); + private static native void deregisterDisplayReconfiguration(long context); /** Available CoreGraphics displays. */ private final Map devices = new HashMap<>(5); @@ -93,6 +95,7 @@ public final class CGraphicsEnvironment extends SunGraphicsEnvironment { /** Reference to the display reconfiguration callback context. */ private final long displayReconfigContext; + private final Object disposerReferent = new Object(); // list of invalidated graphics devices (those which were removed) private List> oldDevices = new ArrayList<>(); @@ -114,6 +117,7 @@ public final class CGraphicsEnvironment extends SunGraphicsEnvironment { if (displayReconfigContext == 0L) { throw new RuntimeException("Could not register CoreGraphics display reconfiguration callback"); } + Disposer.addRecord(disposerReferent, new CGEDisposerRecord(displayReconfigContext)); } /** @@ -139,12 +143,14 @@ public final class CGraphicsEnvironment extends SunGraphicsEnvironment { rebuildDevices(); } - @Override - @SuppressWarnings("removal") - protected void finalize() throws Throwable { - try { - super.finalize(); - } finally { + private static class CGEDisposerRecord implements DisposerRecord { + private final long displayReconfigContext; + + CGEDisposerRecord(long ptr) { + displayReconfigContext = ptr; + } + + public void dispose() { deregisterDisplayReconfiguration(displayReconfigContext); } } diff --git a/src/java.desktop/macosx/native/libawt_lwawt/awt/CGraphicsEnv.m b/src/java.desktop/macosx/native/libawt_lwawt/awt/CGraphicsEnv.m index 58338cbc6c1..2fea4a1a4f7 100644 --- a/src/java.desktop/macosx/native/libawt_lwawt/awt/CGraphicsEnv.m +++ b/src/java.desktop/macosx/native/libawt_lwawt/awt/CGraphicsEnv.m @@ -162,7 +162,7 @@ JNI_COCOA_EXIT(env); */ JNIEXPORT void JNICALL Java_sun_awt_CGraphicsEnvironment_deregisterDisplayReconfiguration -(JNIEnv *env, jobject this, jlong p) +(JNIEnv *env, jclass clazz, jlong p) { JNI_COCOA_ENTER(env); diff --git a/src/java.desktop/share/classes/sun/java2d/Disposer.java b/src/java.desktop/share/classes/sun/java2d/Disposer.java index 9929238c099..2d58a3cc0e6 100644 --- a/src/java.desktop/share/classes/sun/java2d/Disposer.java +++ b/src/java.desktop/share/classes/sun/java2d/Disposer.java @@ -137,7 +137,7 @@ public class Disposer implements Runnable { obj = null; rec = null; clearDeferredRecords(); - } catch (Exception e) { + } catch (Throwable t) { System.out.println("Exception while removing reference."); } } @@ -157,7 +157,7 @@ public class Disposer implements Runnable { private static void safeDispose(DisposerRecord rec) { try { rec.dispose(); - } catch (final Exception e) { + } catch (final Throwable t) { System.out.println("Exception while disposing deferred rec."); } } @@ -212,7 +212,7 @@ public class Disposer implements Runnable { deferredRecords.offerLast(rec); } } - } catch (Exception e) { + } catch (Throwable t) { System.out.println("Exception while removing reference."); } finally { pollingQueue = false; From 3acdba38cec95ced2b2dd6a183c9b5d22dcc4b26 Mon Sep 17 00:00:00 2001 From: Phil Race Date: Mon, 21 Jul 2025 21:02:47 +0000 Subject: [PATCH 31/94] 8362557: [macOS] Remove CFont.finalize() Reviewed-by: serb, psadhukhan, kizune --- .../macosx/classes/sun/font/CFont.java | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/java.desktop/macosx/classes/sun/font/CFont.java b/src/java.desktop/macosx/classes/sun/font/CFont.java index fbc6afca84f..f187c5bb14c 100644 --- a/src/java.desktop/macosx/classes/sun/font/CFont.java +++ b/src/java.desktop/macosx/classes/sun/font/CFont.java @@ -32,6 +32,8 @@ import java.awt.geom.GeneralPath; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.ArrayList; +import sun.java2d.Disposer; +import sun.java2d.DisposerRecord; // Right now this class is final to avoid a problem with native code. // For some reason the JNI IsInstanceOf was not working correctly @@ -98,6 +100,7 @@ public final class CFont extends PhysicalFont implements FontSubstitution { private boolean isFakeItalic; private String nativeFontName; private long nativeFontPtr; + private final Object disposerReferent = new Object(); private native float getWidthNative(final long nativeFontPtr); private native float getWeightNative(final long nativeFontPtr); @@ -194,6 +197,7 @@ public final class CFont extends PhysicalFont implements FontSubstitution { protected synchronized long getNativeFontPtr() { if (nativeFontPtr == 0L) { nativeFontPtr = createNativeFont(nativeFontName, style); + Disposer.addRecord(disposerReferent, new CFontDisposerRecord(nativeFontPtr)); } return nativeFontPtr; } @@ -256,13 +260,17 @@ public final class CFont extends PhysicalFont implements FontSubstitution { return compFont; } - @Override - @SuppressWarnings("removal") - protected synchronized void finalize() { - if (nativeFontPtr != 0) { + private static class CFontDisposerRecord implements DisposerRecord { + + private final long nativeFontPtr; + + CFontDisposerRecord(long ptr) { + nativeFontPtr = ptr; + } + + public void dispose() { disposeNativeFont(nativeFontPtr); } - nativeFontPtr = 0; } @Override From eceb3bbc80aae5d99155218f755725041edbb8ab Mon Sep 17 00:00:00 2001 From: Phil Race Date: Mon, 21 Jul 2025 21:03:17 +0000 Subject: [PATCH 32/94] 8362452: [macOS] Remove CPrinterJob.finalize() Reviewed-by: serb, psadhukhan, kizune --- .../classes/sun/lwawt/macosx/CPrinterJob.java | 62 ++++++++++++++----- .../native/libawt_lwawt/awt/CPrinterJob.m | 10 +-- 2 files changed, 51 insertions(+), 21 deletions(-) diff --git a/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPrinterJob.java b/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPrinterJob.java index 25ebc82c5f7..7b0c51f078f 100644 --- a/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPrinterJob.java +++ b/src/java.desktop/macosx/classes/sun/lwawt/macosx/CPrinterJob.java @@ -26,14 +26,31 @@ package sun.lwawt.macosx; -import java.awt.*; +import java.awt.Color; +import java.awt.EventQueue; +import java.awt.HeadlessException; +import java.awt.Font; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.GraphicsEnvironment; +import java.awt.SecondaryLoop; +import java.awt.Toolkit; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; -import java.awt.print.*; +import java.awt.print.Pageable; +import java.awt.print.PageFormat; +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.net.URI; import java.util.concurrent.atomic.AtomicReference; -import javax.print.*; +import javax.print.DocFlavor; +import javax.print.PrintService; +import javax.print.PrintServiceLookup; +import javax.print.StreamPrintService; import javax.print.attribute.PrintRequestAttributeSet; import javax.print.attribute.HashPrintRequestAttributeSet; import javax.print.attribute.standard.Chromaticity; @@ -48,8 +65,16 @@ import javax.print.attribute.standard.PageRanges; import javax.print.attribute.standard.Sides; import javax.print.attribute.Attribute; -import sun.java2d.*; -import sun.print.*; +import sun.java2d.Disposer; +import sun.java2d.DisposerRecord; +import sun.java2d.SunGraphics2D; +import sun.java2d.SurfaceData; +import sun.print.CustomMediaTray; +import sun.print.CustomOutputBin; +import sun.print.GrayscaleProxyGraphics2D; +import sun.print.PeekGraphics; +import sun.print.RasterPrinterJob; +import sun.print.SunPageSelection; public final class CPrinterJob extends RasterPrinterJob { // NOTE: This uses RasterPrinterJob as a base, but it doesn't use @@ -82,7 +107,8 @@ public final class CPrinterJob extends RasterPrinterJob { // PageFormat data is passed in and set on the fNSPrintInfo on a per call // basis. private long fNSPrintInfo = -1; - private Object fNSPrintInfoLock = new Object(); + private final Object fNSPrintInfoLock = new Object(); + private final Object disposerReferent = new Object(); static { // AWT has to be initialized for the native code to function correctly. @@ -610,25 +636,29 @@ public final class CPrinterJob extends RasterPrinterJob { // The following methods are CPrinterJob specific. - @Override - @SuppressWarnings("removal") - protected void finalize() { - synchronized (fNSPrintInfoLock) { - if (fNSPrintInfo != -1) { - dispose(fNSPrintInfo); - } - fNSPrintInfo = -1; + static class NSPrintInfoDisposer implements DisposerRecord { + + private final long fNSPrintInfo; + + NSPrintInfoDisposer(long ptr) { + fNSPrintInfo = ptr; + } + + public void dispose() { + CPrinterJob.disposeNSPrintInfo(fNSPrintInfo); } } - private native long createNSPrintInfo(); - private native void dispose(long printInfo); + private static native long createNSPrintInfo(); + private static native void disposeNSPrintInfo(long printInfo); private long getNSPrintInfo() { // This is called from the native side. synchronized (fNSPrintInfoLock) { if (fNSPrintInfo == -1) { fNSPrintInfo = createNSPrintInfo(); + Disposer.addRecord(disposerReferent, + new NSPrintInfoDisposer(fNSPrintInfo)); } return fNSPrintInfo; } diff --git a/src/java.desktop/macosx/native/libawt_lwawt/awt/CPrinterJob.m b/src/java.desktop/macosx/native/libawt_lwawt/awt/CPrinterJob.m index 9333aa8676b..9db38ee21ff 100644 --- a/src/java.desktop/macosx/native/libawt_lwawt/awt/CPrinterJob.m +++ b/src/java.desktop/macosx/native/libawt_lwawt/awt/CPrinterJob.m @@ -617,11 +617,11 @@ JNI_COCOA_EXIT(env); * Signature: ()J */ JNIEXPORT jlong JNICALL Java_sun_lwawt_macosx_CPrinterJob_createNSPrintInfo - (JNIEnv *env, jobject jthis) + (JNIEnv *env, jclass clazz) { jlong result = -1; JNI_COCOA_ENTER(env); - // This is used to create the NSPrintInfo for this PrinterJob. Thread + // This is used to create the NSPrintInfo for a PrinterJob. Thread // safety is assured by the java side of this call. NSPrintInfo* printInfo = createDefaultNSPrintInfo(env, NULL); @@ -634,11 +634,11 @@ JNI_COCOA_EXIT(env); /* * Class: sun_lwawt_macosx_CPrinterJob - * Method: dispose + * Method: disposeNSPrintInfo * Signature: (J)V */ -JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CPrinterJob_dispose - (JNIEnv *env, jobject jthis, jlong nsPrintInfo) +JNIEXPORT void JNICALL Java_sun_lwawt_macosx_CPrinterJob_disposeNSPrintInfo + (JNIEnv *env, jclass clazz, jlong nsPrintInfo) { JNI_COCOA_ENTER(env); if (nsPrintInfo != -1) From 7d7d308d9ab6f06ebdab0f5967a5bfc007d4217f Mon Sep 17 00:00:00 2001 From: Sergey Bylokhov Date: Tue, 22 Jul 2025 00:38:28 +0000 Subject: [PATCH 33/94] 8362572: Delete the usage of "sun.java2d.reftype" from the sun.java2d.Disposer Reviewed-by: prr, aivanov --- .../share/classes/sun/java2d/Disposer.java | 31 ++++--------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/src/java.desktop/share/classes/sun/java2d/Disposer.java b/src/java.desktop/share/classes/sun/java2d/Disposer.java index 2d58a3cc0e6..0f533b851d0 100644 --- a/src/java.desktop/share/classes/sun/java2d/Disposer.java +++ b/src/java.desktop/share/classes/sun/java2d/Disposer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2002, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,15 +25,15 @@ package sun.java2d; -import sun.awt.util.ThreadGroupUtils; - +import java.lang.ref.PhantomReference; import java.lang.ref.Reference; import java.lang.ref.ReferenceQueue; -import java.lang.ref.PhantomReference; import java.lang.ref.WeakReference; import java.util.Hashtable; import java.util.concurrent.ConcurrentLinkedDeque; +import sun.awt.util.ThreadGroupUtils; + /** * This class is used for registering and disposing the native * data associated with java objects. @@ -54,24 +54,11 @@ public class Disposer implements Runnable { private static final Hashtable, DisposerRecord> records = new Hashtable<>(); - private static Disposer disposerInstance; - public static final int WEAK = 0; - public static final int PHANTOM = 1; - public static int refType = PHANTOM; + private static final Disposer disposerInstance; static { System.loadLibrary("awt"); initIDs(); - String type = System.getProperty("sun.java2d.reftype"); - if (type != null) { - if (type.equals("weak")) { - refType = WEAK; - System.err.println("Using WEAK refs"); - } else { - refType = PHANTOM; - System.err.println("Using PHANTOM refs"); - } - } disposerInstance = new Disposer(); String name = "Java2D Disposer"; ThreadGroup rootTG = ThreadGroupUtils.getRootThreadGroup(); @@ -118,13 +105,7 @@ public class Disposer implements Runnable { if (target instanceof DisposerTarget) { target = ((DisposerTarget)target).getDisposerReferent(); } - java.lang.ref.Reference ref; - if (refType == PHANTOM) { - ref = new PhantomReference<>(target, queue); - } else { - ref = new WeakReference<>(target, queue); - } - records.put(ref, rec); + records.put(new PhantomReference<>(target, queue), rec); } public void run() { From 0385975f44fbe9d199677754ff5006bc5784b9c5 Mon Sep 17 00:00:00 2001 From: David Holmes Date: Tue, 22 Jul 2025 00:39:01 +0000 Subject: [PATCH 34/94] 8356941: AbstractMethodError in HotSpot Due to Incorrect Handling of Private Method Reviewed-by: coleenp, heidinga --- src/hotspot/share/classfile/defaultMethods.cpp | 8 +++----- .../vmTestbase/vm/runtime/defmeth/PrivateMethodsTest.java | 8 +++++++- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/hotspot/share/classfile/defaultMethods.cpp b/src/hotspot/share/classfile/defaultMethods.cpp index a9c694c33c1..f84c3b65f5f 100644 --- a/src/hotspot/share/classfile/defaultMethods.cpp +++ b/src/hotspot/share/classfile/defaultMethods.cpp @@ -658,13 +658,11 @@ static void find_empty_vtable_slots(GrowableArray* slots, if (super->default_methods() != nullptr) { for (int i = 0; i < super->default_methods()->length(); ++i) { Method* m = super->default_methods()->at(i); - // m is a method that would have been a miranda if not for the - // default method processing that occurred on behalf of our superclass, - // so it's a method we want to re-examine in this new context. That is, - // unless we have a real implementation of it in the current class. if (!already_in_vtable_slots(slots, m)) { + // m is a method that we need to re-examine, unless we have a valid concrete + // implementation in the current class - see FindMethodsByErasedSig::visit. Method* impl = klass->lookup_method(m->name(), m->signature()); - if (impl == nullptr || impl->is_overpass() || impl->is_static()) { + if (impl == nullptr || impl->is_overpass() || impl->is_static() || impl->is_private()) { slots->append(new EmptyVtableSlot(m)); } } diff --git a/test/hotspot/jtreg/vmTestbase/vm/runtime/defmeth/PrivateMethodsTest.java b/test/hotspot/jtreg/vmTestbase/vm/runtime/defmeth/PrivateMethodsTest.java index 8f204cc13cd..243ce82c9d8 100644 --- a/test/hotspot/jtreg/vmTestbase/vm/runtime/defmeth/PrivateMethodsTest.java +++ b/test/hotspot/jtreg/vmTestbase/vm/runtime/defmeth/PrivateMethodsTest.java @@ -680,6 +680,8 @@ public class PrivateMethodsTest extends DefMethTest { * public class C extends B { } * * TEST: { B b = new C(); b.m()I throws IllegalAccessError; } + * TEST: { I b = new B(); b.m()I returns 3; } + * TEST: { I c = new C(); c.m()I returns 3; } */ public void testPrivateSuperClassMethodDefaultMethodNoOverride(TestBuilder b) { ConcreteClass A = b.clazz("A") @@ -694,6 +696,10 @@ public class PrivateMethodsTest extends DefMethTest { ConcreteClass C = b.clazz("C").extend(B).build(); - b.test().privateCallSite(B, C, "m", "()I").throws_(IllegalAccessError.class).done(); + b.test().privateCallSite(B, C, "m", "()I").throws_(IllegalAccessError.class).done() + .test(). callSite(I, B, "m", "()I").returns(3).done() + .test(). callSite(I, C, "m", "()I").returns(3).done() + ; + } } From 699b8112f8da7ceef2aa2a3ddb326aee88b29f8c Mon Sep 17 00:00:00 2001 From: SendaoYan Date: Tue, 22 Jul 2025 01:05:35 +0000 Subject: [PATCH 35/94] 8362834: Several runtime/Thread tests should mark as /native Reviewed-by: dholmes --- .../jtreg/runtime/Thread/AsyncExceptionOnMonitorEnter.java | 4 ++-- test/hotspot/jtreg/runtime/Thread/AsyncExceptionTest.java | 5 +++-- .../jtreg/runtime/Thread/TestBreakSignalThreadDump.java | 4 ++-- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/test/hotspot/jtreg/runtime/Thread/AsyncExceptionOnMonitorEnter.java b/test/hotspot/jtreg/runtime/Thread/AsyncExceptionOnMonitorEnter.java index 30cd98fa239..8446ffb20fe 100644 --- a/test/hotspot/jtreg/runtime/Thread/AsyncExceptionOnMonitorEnter.java +++ b/test/hotspot/jtreg/runtime/Thread/AsyncExceptionOnMonitorEnter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,7 +26,7 @@ * @bug 8283044 * @summary Stress delivery of asynchronous exceptions while target is at monitorenter * @library /test/hotspot/jtreg/testlibrary - * @run main/othervm AsyncExceptionOnMonitorEnter 0 + * @run main/othervm/native AsyncExceptionOnMonitorEnter 0 * @run main/othervm/native -agentlib:AsyncExceptionOnMonitorEnter AsyncExceptionOnMonitorEnter 1 */ diff --git a/test/hotspot/jtreg/runtime/Thread/AsyncExceptionTest.java b/test/hotspot/jtreg/runtime/Thread/AsyncExceptionTest.java index c7d0d1a27f0..52fc4ca1d56 100644 --- a/test/hotspot/jtreg/runtime/Thread/AsyncExceptionTest.java +++ b/test/hotspot/jtreg/runtime/Thread/AsyncExceptionTest.java @@ -27,13 +27,14 @@ * @requires vm.compiler1.enabled | vm.compiler2.enabled * @summary Stress delivery of asynchronous exceptions. * @library /test/hotspot/jtreg/testlibrary - * @run main/othervm -XX:+IgnoreUnrecognizedVMOptions -XX:+StressCompiledExceptionHandlers + * @run main/othervm/native + * -XX:+IgnoreUnrecognizedVMOptions -XX:+StressCompiledExceptionHandlers * -Xcomp -XX:TieredStopAtLevel=3 * -XX:CompileCommand=dontinline,AsyncExceptionTest::internalRun2 * -XX:CompileCommand=compileonly,AsyncExceptionTest::internalRun1 * -XX:CompileCommand=compileonly,AsyncExceptionTest::internalRun2 * AsyncExceptionTest - * @run main/othervm -Xcomp + * @run main/othervm/native -Xcomp * -XX:CompileCommand=dontinline,AsyncExceptionTest::internalRun2 * -XX:CompileCommand=compileonly,AsyncExceptionTest::internalRun1 * -XX:CompileCommand=compileonly,AsyncExceptionTest::internalRun2 diff --git a/test/hotspot/jtreg/runtime/Thread/TestBreakSignalThreadDump.java b/test/hotspot/jtreg/runtime/Thread/TestBreakSignalThreadDump.java index 011f6979431..3cb4be1b004 100644 --- a/test/hotspot/jtreg/runtime/Thread/TestBreakSignalThreadDump.java +++ b/test/hotspot/jtreg/runtime/Thread/TestBreakSignalThreadDump.java @@ -28,7 +28,7 @@ * @summary Check that Ctrl-\ or Ctrl-Break (on Windows) causes HotSpot VM to print a full thread dump. * @library /vmTestbase * /test/lib - * @run driver TestBreakSignalThreadDump + * @run main/native TestBreakSignalThreadDump */ /* @@ -42,7 +42,7 @@ * @requires !vm.asan * @library /vmTestbase * /test/lib - * @run driver TestBreakSignalThreadDump load_libjsig + * @run main/native TestBreakSignalThreadDump load_libjsig */ import java.nio.file.Files; From dccb1782ec35d1ee95220a237aef29ddfc292cbd Mon Sep 17 00:00:00 2001 From: Yadong Wang Date: Tue, 22 Jul 2025 01:23:37 +0000 Subject: [PATCH 36/94] 8361892: AArch64: Incorrect matching rule leading to improper oop instruction encoding Reviewed-by: shade, adinn --- src/hotspot/cpu/aarch64/aarch64.ad | 32 ------------------------------ 1 file changed, 32 deletions(-) diff --git a/src/hotspot/cpu/aarch64/aarch64.ad b/src/hotspot/cpu/aarch64/aarch64.ad index 456712cf8da..681b14ab068 100644 --- a/src/hotspot/cpu/aarch64/aarch64.ad +++ b/src/hotspot/cpu/aarch64/aarch64.ad @@ -3450,10 +3450,6 @@ encode %{ __ mov(dst_reg, (uint64_t)1); %} - enc_class aarch64_enc_mov_byte_map_base(iRegP dst, immByteMapBase src) %{ - __ load_byte_map_base($dst$$Register); - %} - enc_class aarch64_enc_mov_n(iRegN dst, immN src) %{ Register dst_reg = as_Register($dst$$reg); address con = (address)$src$$constant; @@ -4554,20 +4550,6 @@ operand immP_1() interface(CONST_INTER); %} -// Card Table Byte Map Base -operand immByteMapBase() -%{ - // Get base of card map - predicate(BarrierSet::barrier_set()->is_a(BarrierSet::CardTableBarrierSet) && - SHENANDOAHGC_ONLY(!BarrierSet::barrier_set()->is_a(BarrierSet::ShenandoahBarrierSet) &&) - (CardTable::CardValue*)n->get_ptr() == ((CardTableBarrierSet*)(BarrierSet::barrier_set()))->card_table()->byte_map_base()); - match(ConP); - - op_cost(0); - format %{ %} - interface(CONST_INTER); -%} - // Float and Double operands // Double Immediate operand immD() @@ -6854,20 +6836,6 @@ instruct loadConP1(iRegPNoSp dst, immP_1 con) ins_pipe(ialu_imm); %} -// Load Byte Map Base Constant - -instruct loadByteMapBase(iRegPNoSp dst, immByteMapBase con) -%{ - match(Set dst con); - - ins_cost(INSN_COST); - format %{ "adr $dst, $con\t# Byte Map Base" %} - - ins_encode(aarch64_enc_mov_byte_map_base(dst, con)); - - ins_pipe(ialu_imm); -%} - // Load Narrow Pointer Constant instruct loadConN(iRegNNoSp dst, immN con) From c68697e1786fac37402b729d05a47b2f6296a86c Mon Sep 17 00:00:00 2001 From: Koushik Thirupattur Date: Tue, 22 Jul 2025 02:48:11 +0000 Subject: [PATCH 37/94] 8362957: Fix jdk/javadoc/doccheck/checks/jdkCheckHtml.java (docs) failure Reviewed-by: ascarpino --- src/java.base/share/classes/javax/crypto/Cipher.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/java.base/share/classes/javax/crypto/Cipher.java b/src/java.base/share/classes/javax/crypto/Cipher.java index cb3d24cd716..74971182039 100644 --- a/src/java.base/share/classes/javax/crypto/Cipher.java +++ b/src/java.base/share/classes/javax/crypto/Cipher.java @@ -116,10 +116,6 @@ import sun.security.util.KnownOIDs; *

An {@link IllegalStateException} will be thrown when calling {@code update} * or {@code doFinal} methods if a reset did not occur. A call to {@code init} will * re-initialize the {@code Cipher} object with new parameters. - * - * @see javax.crypto.Cipher - * @see javax.crypto.spec.GCMParameterSpec - * *

* Every implementation of the Java platform is required to support * the following standard {@code Cipher} object transformations with From f155661151fc25cde3be17878aeb24056555961c Mon Sep 17 00:00:00 2001 From: Roland Westrelin Date: Tue, 22 Jul 2025 08:35:36 +0000 Subject: [PATCH 38/94] 8342692: C2: long counted loop/long range checks: don't create loop-nest for short running loops Co-authored-by: Maurizio Cimadamore Co-authored-by: Christian Hagedorn Reviewed-by: chagedorn, thartmann --- src/hotspot/share/jvmci/vmStructs_jvmci.cpp | 1 + src/hotspot/share/opto/c2_globals.hpp | 9 + src/hotspot/share/opto/castnode.cpp | 70 ++- src/hotspot/share/opto/castnode.hpp | 6 + src/hotspot/share/opto/graphKit.cpp | 5 + src/hotspot/share/opto/ifnode.cpp | 4 + src/hotspot/share/opto/loopPredicate.cpp | 4 +- src/hotspot/share/opto/loopTransform.cpp | 76 ++- src/hotspot/share/opto/loopnode.cpp | 231 ++++++- src/hotspot/share/opto/loopnode.hpp | 49 +- src/hotspot/share/opto/predicates.cpp | 32 +- src/hotspot/share/opto/predicates.hpp | 49 +- src/hotspot/share/runtime/deoptimization.cpp | 2 +- src/hotspot/share/runtime/deoptimization.hpp | 3 +- src/hotspot/share/runtime/vmStructs.cpp | 2 +- .../share/utilities/globalDefinitions.hpp | 11 + .../c2/irTests/TestLongRangeChecks.java | 41 +- .../compiler/lib/ir_framework/IRNode.java | 5 + .../TestShortLoopLostLimit.java | 53 ++ ...unningIntLoopWithLongChecksPredicates.java | 66 ++ .../TestShortRunningLongCountedLoop.java | 579 ++++++++++++++++++ ...RunningLongCountedLoopPredicatesClone.java | 62 ++ ...rtRunningLongCountedLoopScaleOverflow.java | 82 +++ ...rtRunningLongCountedLoopVectorization.java | 68 ++ ...TestStressShortRunningLongCountedLoop.java | 66 ++ .../loopopts/superword/TestMemorySegment.java | 38 ++ .../foreign/HeapMismatchManualLoopTest.java | 130 ++++ 27 files changed, 1665 insertions(+), 79 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/longcountedloops/TestShortLoopLostLimit.java create mode 100644 test/hotspot/jtreg/compiler/longcountedloops/TestShortRunningIntLoopWithLongChecksPredicates.java create mode 100644 test/hotspot/jtreg/compiler/longcountedloops/TestShortRunningLongCountedLoop.java create mode 100644 test/hotspot/jtreg/compiler/longcountedloops/TestShortRunningLongCountedLoopPredicatesClone.java create mode 100644 test/hotspot/jtreg/compiler/longcountedloops/TestShortRunningLongCountedLoopScaleOverflow.java create mode 100644 test/hotspot/jtreg/compiler/longcountedloops/TestShortRunningLongCountedLoopVectorization.java create mode 100644 test/hotspot/jtreg/compiler/longcountedloops/TestStressShortRunningLongCountedLoop.java create mode 100644 test/micro/org/openjdk/bench/java/lang/foreign/HeapMismatchManualLoopTest.java diff --git a/src/hotspot/share/jvmci/vmStructs_jvmci.cpp b/src/hotspot/share/jvmci/vmStructs_jvmci.cpp index e792ed209b8..32ef3eb3e14 100644 --- a/src/hotspot/share/jvmci/vmStructs_jvmci.cpp +++ b/src/hotspot/share/jvmci/vmStructs_jvmci.cpp @@ -763,6 +763,7 @@ declare_constant(Deoptimization::Reason_constraint) \ declare_constant(Deoptimization::Reason_div0_check) \ declare_constant(Deoptimization::Reason_loop_limit_check) \ + declare_constant(Deoptimization::Reason_short_running_long_loop) \ declare_constant(Deoptimization::Reason_auto_vectorization_check) \ declare_constant(Deoptimization::Reason_type_checked_inlining) \ declare_constant(Deoptimization::Reason_optimized_type_check) \ diff --git a/src/hotspot/share/opto/c2_globals.hpp b/src/hotspot/share/opto/c2_globals.hpp index 789f906a3af..540b6600a0f 100644 --- a/src/hotspot/share/opto/c2_globals.hpp +++ b/src/hotspot/share/opto/c2_globals.hpp @@ -872,6 +872,15 @@ "could corrupt the graph in rare cases and should be used with " \ "care.") \ \ + product(bool, ShortRunningLongLoop, true, DIAGNOSTIC, \ + "long counted loop/long range checks: don't create loop nest if " \ + "loop runs for small enough number of iterations. Long loop is " \ + "converted to a single int loop.") \ + \ + develop(bool, StressShortRunningLongLoop, false, \ + "Speculate all long counted loops are short running when bounds " \ + "are unknown even if profile data doesn't say so.") \ + \ develop(bool, StressLoopPeeling, false, \ "Randomize loop peeling decision") \ diff --git a/src/hotspot/share/opto/castnode.cpp b/src/hotspot/share/opto/castnode.cpp index 96f5ba7e693..6d899c1f950 100644 --- a/src/hotspot/share/opto/castnode.cpp +++ b/src/hotspot/share/opto/castnode.cpp @@ -26,7 +26,9 @@ #include "opto/addnode.hpp" #include "opto/callnode.hpp" #include "opto/castnode.hpp" +#include "opto/cfgnode.hpp" #include "opto/connode.hpp" +#include "opto/loopnode.hpp" #include "opto/matcher.hpp" #include "opto/phaseX.hpp" #include "opto/subnode.hpp" @@ -323,6 +325,67 @@ const Type* CastLLNode::Value(PhaseGVN* phase) const { return widen_type(phase, res, T_LONG); } +bool CastLLNode::is_inner_loop_backedge(ProjNode* proj) { + if (proj != nullptr) { + Node* ctrl_use = proj->unique_ctrl_out_or_null(); + if (ctrl_use != nullptr && ctrl_use->Opcode() == Op_Loop && + ctrl_use->in(2) == proj && + ctrl_use->as_Loop()->is_loop_nest_inner_loop()) { + return true; + } + } + return false; +} + +bool CastLLNode::cmp_used_at_inner_loop_exit_test(CmpNode* cmp) { + for (DUIterator_Fast imax, i = cmp->fast_outs(imax); i < imax; i++) { + Node* bol = cmp->fast_out(i); + if (bol->Opcode() == Op_Bool) { + for (DUIterator_Fast jmax, j = bol->fast_outs(jmax); j < jmax; j++) { + Node* iff = bol->fast_out(j); + if (iff->Opcode() == Op_If) { + ProjNode* true_proj = iff->as_If()->proj_out_or_null(true); + ProjNode* false_proj = iff->as_If()->proj_out_or_null(false); + if (is_inner_loop_backedge(true_proj) || is_inner_loop_backedge(false_proj)) { + return true; + } + } + } + } + } + return false; +} + +// Find if this is a cast node added by PhaseIdealLoop::create_loop_nest() to narrow the number of iterations of the +// inner loop +bool CastLLNode::used_at_inner_loop_exit_test() const { + for (DUIterator_Fast imax, i = fast_outs(imax); i < imax; i++) { + Node* convl2i = fast_out(i); + if (convl2i->Opcode() == Op_ConvL2I) { + for (DUIterator_Fast jmax, j = convl2i->fast_outs(jmax); j < jmax; j++) { + Node* cmp_or_sub = convl2i->fast_out(j); + if (cmp_or_sub->Opcode() == Op_CmpI) { + if (cmp_used_at_inner_loop_exit_test(cmp_or_sub->as_Cmp())) { + // (Loop .. .. (IfProj (If (Bool (CmpI (ConvL2I (CastLL ))))))) + return true; + } + } else if (cmp_or_sub->Opcode() == Op_SubI && cmp_or_sub->in(1)->find_int_con(-1) == 0) { + for (DUIterator_Fast kmax, k = cmp_or_sub->fast_outs(kmax); k < kmax; k++) { + Node* cmp = cmp_or_sub->fast_out(k); + if (cmp->Opcode() == Op_CmpI) { + if (cmp_used_at_inner_loop_exit_test(cmp->as_Cmp())) { + // (Loop .. .. (IfProj (If (Bool (CmpI (SubI 0 (ConvL2I (CastLL )))))))) + return true; + } + } + } + } + } + } + } + return false; +} + Node* CastLLNode::Ideal(PhaseGVN* phase, bool can_reshape) { Node* progress = ConstraintCastNode::Ideal(phase, can_reshape); if (progress != nullptr) { @@ -352,7 +415,12 @@ Node* CastLLNode::Ideal(PhaseGVN* phase, bool can_reshape) { } } } - return optimize_integer_cast(phase, T_LONG); + // If it's a cast created by PhaseIdealLoop::short_running_loop(), don't transform it until the counted loop is created + // in next loop opts pass + if (!can_reshape || !used_at_inner_loop_exit_test()) { + return optimize_integer_cast(phase, T_LONG); + } + return nullptr; } //------------------------------Value------------------------------------------ diff --git a/src/hotspot/share/opto/castnode.hpp b/src/hotspot/share/opto/castnode.hpp index 1b848e5efdf..3c6ade64aa8 100644 --- a/src/hotspot/share/opto/castnode.hpp +++ b/src/hotspot/share/opto/castnode.hpp @@ -138,6 +138,12 @@ public: } virtual const Type* Value(PhaseGVN* phase) const; + + static bool is_inner_loop_backedge(ProjNode* proj); + + static bool cmp_used_at_inner_loop_exit_test(CmpNode* cmp); + bool used_at_inner_loop_exit_test() const; + virtual Node* Ideal(PhaseGVN* phase, bool can_reshape); virtual int Opcode() const; virtual uint ideal_reg() const { return Op_RegL; } diff --git a/src/hotspot/share/opto/graphKit.cpp b/src/hotspot/share/opto/graphKit.cpp index 482189f7ec8..c58f7824c11 100644 --- a/src/hotspot/share/opto/graphKit.cpp +++ b/src/hotspot/share/opto/graphKit.cpp @@ -4050,6 +4050,11 @@ void GraphKit::add_parse_predicate(Deoptimization::DeoptReason reason, const int // Add Parse Predicates which serve as placeholders to create new Runtime Predicates above them. All // Runtime Predicates inside a Runtime Predicate block share the same uncommon trap as the Parse Predicate. void GraphKit::add_parse_predicates(int nargs) { + if (ShortRunningLongLoop) { + // Will narrow the limit down with a cast node. Predicates added later may depend on the cast so should be last when + // walking up from the loop. + add_parse_predicate(Deoptimization::Reason_short_running_long_loop, nargs); + } if (UseLoopPredicate) { add_parse_predicate(Deoptimization::Reason_predicate, nargs); if (UseProfiledLoopPredicate) { diff --git a/src/hotspot/share/opto/ifnode.cpp b/src/hotspot/share/opto/ifnode.cpp index 9daf2e6741e..b397c2c5852 100644 --- a/src/hotspot/share/opto/ifnode.cpp +++ b/src/hotspot/share/opto/ifnode.cpp @@ -2178,6 +2178,7 @@ ParsePredicateNode::ParsePredicateNode(Node* control, Deoptimization::DeoptReaso case Deoptimization::Reason_profile_predicate: case Deoptimization::Reason_auto_vectorization_check: case Deoptimization::Reason_loop_limit_check: + case Deoptimization::Reason_short_running_long_loop: break; default: assert(false, "unsupported deoptimization reason for Parse Predicate"); @@ -2226,6 +2227,9 @@ void ParsePredicateNode::dump_spec(outputStream* st) const { case Deoptimization::DeoptReason::Reason_loop_limit_check: st->print("Loop_Limit_Check "); break; + case Deoptimization::DeoptReason::Reason_short_running_long_loop: + st->print("Short_Running_Long_Loop "); + break; default: fatal("unknown kind"); } diff --git a/src/hotspot/share/opto/loopPredicate.cpp b/src/hotspot/share/opto/loopPredicate.cpp index 477ea48d419..561f3ce75cb 100644 --- a/src/hotspot/share/opto/loopPredicate.cpp +++ b/src/hotspot/share/opto/loopPredicate.cpp @@ -1054,7 +1054,7 @@ bool PhaseIdealLoop::loop_predication_impl_helper(IdealLoopTree* loop, IfProjNod #ifdef ASSERT const bool exact_trip_count = cl->has_exact_trip_count(); const uint trip_count = cl->trip_count(); - loop->compute_trip_count(this); + loop->compute_trip_count(this, T_INT); assert(exact_trip_count == cl->has_exact_trip_count() && trip_count == cl->trip_count(), "should have computed trip count on Loop Predication entry"); #endif @@ -1171,7 +1171,7 @@ bool PhaseIdealLoop::loop_predication_impl(IdealLoopTree* loop) { // Do nothing for iteration-splitted loops return false; } - loop->compute_trip_count(this); + loop->compute_trip_count(this, T_INT); if (cl->trip_count() == 1) { // Not worth to hoist checks out of a loop that is only run for one iteration since the checks are only going to // be executed once anyway. diff --git a/src/hotspot/share/opto/loopTransform.cpp b/src/hotspot/share/opto/loopTransform.cpp index cc680f66b62..5f5e0520e7e 100644 --- a/src/hotspot/share/opto/loopTransform.cpp +++ b/src/hotspot/share/opto/loopTransform.cpp @@ -96,11 +96,11 @@ void IdealLoopTree::record_for_igvn() { //------------------------------compute_exact_trip_count----------------------- // Compute loop trip count if possible. Do not recalculate trip count for // split loops (pre-main-post) which have their limits and inits behind Opaque node. -void IdealLoopTree::compute_trip_count(PhaseIdealLoop* phase) { - if (!_head->as_Loop()->is_valid_counted_loop(T_INT)) { +void IdealLoopTree::compute_trip_count(PhaseIdealLoop* phase, BasicType loop_bt) { + if (!_head->as_Loop()->is_valid_counted_loop(loop_bt)) { return; } - CountedLoopNode* cl = _head->as_CountedLoop(); + BaseCountedLoopNode* cl = _head->as_BaseCountedLoop(); // Trip count may become nonexact for iteration split loops since // RCE modifies limits. Note, _trip_count value is not reset since // it is used to limit unrolling of main loop. @@ -119,24 +119,62 @@ void IdealLoopTree::compute_trip_count(PhaseIdealLoop* phase) { Node* init_n = cl->init_trip(); Node* limit_n = cl->limit(); if (init_n != nullptr && limit_n != nullptr) { - // Use longs to avoid integer overflow. - int stride_con = cl->stride_con(); - const TypeInt* init_type = phase->_igvn.type(init_n)->is_int(); - const TypeInt* limit_type = phase->_igvn.type(limit_n)->is_int(); - jlong init_con = (stride_con > 0) ? init_type->_lo : init_type->_hi; - jlong limit_con = (stride_con > 0) ? limit_type->_hi : limit_type->_lo; - int stride_m = stride_con - (stride_con > 0 ? 1 : -1); - jlong trip_count = (limit_con - init_con + stride_m)/stride_con; + jlong stride_con = cl->stride_con(); + const TypeInteger* init_type = phase->_igvn.type(init_n)->is_integer(loop_bt); + const TypeInteger* limit_type = phase->_igvn.type(limit_n)->is_integer(loop_bt); + + // compute trip count + // It used to be computed as: + // max(1, limit_con - init_con + stride_m) / stride_con + // with stride_m = stride_con - (stride_con > 0 ? 1 : -1) + // for int counted loops only and by promoting all values to long to avoid overflow + // This implements the computation for int and long counted loops in a way that promotion to the next larger integer + // type is not needed to protect against overflow. + // + // Use unsigned longs to avoid overflow: number of iteration is a positive number but can be really large for + // instance if init_con = min_jint, limit_con = max_jint + jlong init_con = (stride_con > 0) ? init_type->lo_as_long() : init_type->hi_as_long(); + julong uinit_con = init_con; + jlong limit_con = (stride_con > 0) ? limit_type->hi_as_long() : limit_type->lo_as_long(); + julong ulimit_con = limit_con; // The loop body is always executed at least once even if init >= limit (for stride_con > 0) or // init <= limit (for stride_con < 0). - trip_count = MAX2(trip_count, (jlong)1); - if (trip_count < (jlong)max_juint) { + julong udiff = 1; + if (stride_con > 0 && limit_con > init_con) { + udiff = ulimit_con - uinit_con; + } else if (stride_con < 0 && limit_con < init_con) { + udiff = uinit_con - ulimit_con; + } + // The loop runs for one more iteration if the limit is (stride > 0 in this example): + // init + k * stride + small_value, 0 < small_value < stride + julong utrip_count = udiff / ABS(stride_con); + if (utrip_count * ABS(stride_con) != udiff) { + // Guaranteed to not overflow because it can only happen for ABS(stride) > 1 in which case, utrip_count can't be + // max_juint/max_julong + utrip_count++; + } + +#ifdef ASSERT + if (loop_bt == T_INT) { + // Use longs to avoid integer overflow. + jlong init_con = (stride_con > 0) ? init_type->is_int()->_lo : init_type->is_int()->_hi; + jlong limit_con = (stride_con > 0) ? limit_type->is_int()->_hi : limit_type->is_int()->_lo; + int stride_m = stride_con - (stride_con > 0 ? 1 : -1); + jlong trip_count = (limit_con - init_con + stride_m) / stride_con; + // The loop body is always executed at least once even if init >= limit (for stride_con > 0) or + // init <= limit (for stride_con < 0). + trip_count = MAX2(trip_count, (jlong)1); + assert(checked_cast(trip_count) == checked_cast(utrip_count), "incorrect trip count computation"); + } +#endif + + if (utrip_count < max_unsigned_integer(loop_bt)) { if (init_n->is_Con() && limit_n->is_Con()) { // Set exact trip count. - cl->set_exact_trip_count((uint)trip_count); - } else if (cl->unrolled_count() == 1) { + cl->set_exact_trip_count(utrip_count); + } else if (loop_bt == T_LONG || cl->as_CountedLoop()->unrolled_count() == 1) { // Set maximum trip count before unrolling. - cl->set_trip_count((uint)trip_count); + cl->set_trip_count(utrip_count); } } } @@ -1851,7 +1889,7 @@ void PhaseIdealLoop::do_unroll(IdealLoopTree *loop, Node_List &old_new, bool adj #ifndef PRODUCT if (TraceLoopOpts) { if (loop_head->trip_count() < (uint)LoopUnrollLimit) { - tty->print("Unroll %d(%2d) ", loop_head->unrolled_count()*2, loop_head->trip_count()); + tty->print("Unroll %d(" JULONG_FORMAT_W(2) ") ", loop_head->unrolled_count()*2, loop_head->trip_count()); } else { tty->print("Unroll %d ", loop_head->unrolled_count()*2); } @@ -2104,7 +2142,7 @@ void PhaseIdealLoop::do_maximally_unroll(IdealLoopTree *loop, Node_List &old_new assert(cl->trip_count() > 0, ""); #ifndef PRODUCT if (TraceLoopOpts) { - tty->print("MaxUnroll %d ", cl->trip_count()); + tty->print("MaxUnroll " JULONG_FORMAT " ", cl->trip_count()); loop->dump_head(); } #endif @@ -3359,7 +3397,7 @@ bool IdealLoopTree::iteration_split_impl(PhaseIdealLoop *phase, Node_List &old_n return false; } // Compute loop trip count if possible. - compute_trip_count(phase); + compute_trip_count(phase, T_INT); // Convert one-iteration loop into normal code. if (do_one_iteration_loop(phase)) { diff --git a/src/hotspot/share/opto/loopnode.cpp b/src/hotspot/share/opto/loopnode.cpp index e5efdb2a202..2c604e6d478 100644 --- a/src/hotspot/share/opto/loopnode.cpp +++ b/src/hotspot/share/opto/loopnode.cpp @@ -601,7 +601,6 @@ void PhaseIdealLoop::add_parse_predicate(Deoptimization::DeoptReason reason, Nod int trap_request = Deoptimization::make_trap_request(reason, Deoptimization::Action_maybe_recompile); address call_addr = OptoRuntime::uncommon_trap_blob()->entry_point(); const TypePtr* no_memory_effects = nullptr; - JVMState* jvms = sfpt->jvms(); CallNode* unc = new CallStaticJavaNode(OptoRuntime::uncommon_trap_Type(), call_addr, "uncommon_trap", no_memory_effects); @@ -856,8 +855,9 @@ bool PhaseIdealLoop::create_loop_nest(IdealLoopTree* loop, Node_List &old_new) { return false; } + assert(iters_limit > 0, "can't be negative"); + PhiNode* phi = head->phi()->as_Phi(); - Node* incr = head->incr(); Node* back_control = head->in(LoopNode::LoopBackControl); @@ -888,7 +888,7 @@ bool PhaseIdealLoop::create_loop_nest(IdealLoopTree* loop, Node_List &old_new) { // Take what we know about the number of iterations of the long counted loop into account when computing the limit of // the inner loop. - const Node* init = head->init_trip(); + Node* init = head->init_trip(); const TypeInteger* lo = _igvn.type(init)->is_integer(bt); const TypeInteger* hi = _igvn.type(limit)->is_integer(bt); if (stride_con < 0) { @@ -907,7 +907,7 @@ bool PhaseIdealLoop::create_loop_nest(IdealLoopTree* loop, Node_List &old_new) { // going to execute as many range checks once transformed with range checks eliminated (1 peeled iteration with // range checks + 2 predicates per range checks) as it would have not transformed. It also has to pay for the extra // logic on loop entry and for the outer loop. - loop->compute_trip_count(this); + loop->compute_trip_count(this, bt); if (head->is_CountedLoop() && head->as_CountedLoop()->has_exact_trip_count()) { if (head->as_CountedLoop()->trip_count() <= 3) { return false; @@ -920,6 +920,11 @@ bool PhaseIdealLoop::create_loop_nest(IdealLoopTree* loop, Node_List &old_new) { } } + if (try_make_short_running_loop(loop, stride_con, range_checks, iters_limit)) { + C->set_major_progress(); + return true; + } + julong orig_iters = (julong)hi->hi_as_long() - lo->lo_as_long(); iters_limit = checked_cast(MIN2((julong)iters_limit, orig_iters)); @@ -1118,6 +1123,9 @@ bool PhaseIdealLoop::create_loop_nest(IdealLoopTree* loop, Node_List &old_new) { if (safepoint != nullptr) { SafePointNode* cloned_sfpt = old_new[safepoint->_idx]->as_SafePoint(); + if (ShortRunningLongLoop) { + add_parse_predicate(Deoptimization::Reason_short_running_long_loop, inner_head, outer_ilt, cloned_sfpt); + } if (UseLoopPredicate) { add_parse_predicate(Deoptimization::Reason_predicate, inner_head, outer_ilt, cloned_sfpt); if (UseProfiledLoopPredicate) { @@ -1147,6 +1155,215 @@ bool PhaseIdealLoop::create_loop_nest(IdealLoopTree* loop, Node_List &old_new) { return true; } +// Make a copy of Parse/Template Assertion predicates below existing predicates at the loop passed as argument +class CloneShortLoopPredicateVisitor : public PredicateVisitor { + ClonePredicateToTargetLoop _clone_predicate_to_loop; + PhaseIdealLoop* const _phase; + +public: + CloneShortLoopPredicateVisitor(LoopNode* target_loop_head, + const NodeInSingleLoopBody &node_in_loop_body, + PhaseIdealLoop* phase) + : _clone_predicate_to_loop(target_loop_head, node_in_loop_body, phase), + _phase(phase) { + } + NONCOPYABLE(CloneShortLoopPredicateVisitor); + + using PredicateVisitor::visit; + + void visit(const ParsePredicate& parse_predicate) override { + _clone_predicate_to_loop.clone_parse_predicate(parse_predicate, true); + parse_predicate.kill(_phase->igvn()); + } + + void visit(const TemplateAssertionPredicate& template_assertion_predicate) override { + _clone_predicate_to_loop.clone_template_assertion_predicate(template_assertion_predicate); + template_assertion_predicate.kill(_phase->igvn()); + } +}; + +// If the loop is either statically known to run for a small enough number of iterations or if profile data indicates +// that, we don't want an outer loop because the overhead of having an outer loop whose backedge is never taken, has a +// measurable cost. Furthermore, creating the loop nest usually causes one iteration of the loop to be peeled so +// predicates can be set up. If the loop is short running, then it's an extra iteration that's run with range checks +// (compared to an int counted loop with int range checks). +// +// In the short running case, turn the loop into a regular loop again and transform the long range checks: +// - LongCountedLoop: Create LoopNode but keep the loop limit type with a CastLL node to avoid that we later try to +// create a Loop Limit Check when turning the LoopNode into a CountedLoopNode. +// - CountedLoop: Can be reused. +bool PhaseIdealLoop::try_make_short_running_loop(IdealLoopTree* loop, jint stride_con, const Node_List &range_checks, + const uint iters_limit) { + if (!ShortRunningLongLoop) { + return false; + } + BaseCountedLoopNode* head = loop->_head->as_BaseCountedLoop(); + BasicType bt = head->bt(); + Node* entry_control = head->skip_strip_mined()->in(LoopNode::EntryControl); + + loop->compute_trip_count(this, bt); + // Loop must run for no more than iter_limits as it guarantees no overflow of scale * iv in long range checks (see + // comment above PhaseIdealLoop::transform_long_range_checks()). + // iters_limit / ABS(stride_con) is the largest trip count for which we know it's correct to not create a loop nest: + // it's always beneficial to have a single loop rather than a loop nest, so we try to apply this transformation as + // often as possible. + bool known_short_running_loop = head->trip_count() <= iters_limit / ABS(stride_con); + bool profile_short_running_loop = false; + if (!known_short_running_loop) { + loop->compute_profile_trip_cnt(this); + if (StressShortRunningLongLoop) { + profile_short_running_loop = true; + } else { + profile_short_running_loop = !head->is_profile_trip_failed() && head->profile_trip_cnt() <= iters_limit / ABS(stride_con); + } + } + + if (!known_short_running_loop && !profile_short_running_loop) { + return false; + } + + Node* limit = head->limit(); + Node* init = head->init_trip(); + + Node* new_limit; + if (stride_con > 0) { + new_limit = SubNode::make(limit, init, bt); + } else { + new_limit = SubNode::make(init, limit, bt); + } + register_new_node(new_limit, entry_control); + + PhiNode* phi = head->phi()->as_Phi(); + if (profile_short_running_loop) { + // Add a Short Running Long Loop Predicate. It's the first predicate in the predicate chain before entering a loop + // because a cast that's control dependent on the Short Running Long Loop Predicate is added to narrow the limit and + // future predicates may be dependent on the new limit (so have to be between the loop and Short Running Long Loop + // Predicate). The current limit could, itself, be dependent on an existing predicate. Clone parse and template + // assertion predicates below existing predicates to get proper ordering of predicates when walking from the loop + // up: future predicates, Short Running Long Loop Predicate, existing predicates. + // + // Existing Hoisted + // Check Predicates + // | + // New Short Running Long + // Loop Predicate + // | + // Cloned Parse Predicates and + // Template Assertion Predicates + // (future predicates added here) + // | + // Loop + const Predicates predicates_before_cloning(entry_control); + const PredicateBlock* short_running_long_loop_predicate_block = predicates_before_cloning.short_running_long_loop_predicate_block(); + if (!short_running_long_loop_predicate_block->has_parse_predicate()) { // already trapped + return false; + } + PredicateIterator predicate_iterator(entry_control); + NodeInSingleLoopBody node_in_short_loop_body(this, loop); + CloneShortLoopPredicateVisitor clone_short_loop_predicates_visitor(head, node_in_short_loop_body, this); + predicate_iterator.for_each(clone_short_loop_predicates_visitor); + + entry_control = head->skip_strip_mined()->in(LoopNode::EntryControl); + + const Predicates predicates_after_cloning(entry_control); + + ParsePredicateSuccessProj* short_running_loop_predicate_proj = predicates_after_cloning. + short_running_long_loop_predicate_block()-> + parse_predicate_success_proj(); + assert(short_running_loop_predicate_proj->in(0)->is_ParsePredicate(), "must be parse predicate"); + + const jlong iters_limit_long = iters_limit; + Node* cmp_limit = CmpNode::make(new_limit, _igvn.integercon(iters_limit_long, bt), bt); + Node* bol = new BoolNode(cmp_limit, BoolTest::le); + Node* new_predicate_proj = create_new_if_for_predicate(short_running_loop_predicate_proj, + nullptr, + Deoptimization::Reason_short_running_long_loop, + Op_If); + Node* iff = new_predicate_proj->in(0); + _igvn.replace_input_of(iff, 1, bol); + register_new_node(cmp_limit, iff->in(0)); + register_new_node(bol, iff->in(0)); + new_limit = ConstraintCastNode::make_cast_for_basic_type(new_predicate_proj, new_limit, + TypeInteger::make(1, iters_limit_long, Type::WidenMin, bt), + ConstraintCastNode::UnconditionalDependency, bt); + register_new_node(new_limit, new_predicate_proj); + +#ifndef PRODUCT + if (TraceLoopLimitCheck) { + tty->print_cr("Short Long Loop Check Predicate generated:"); + DEBUG_ONLY(bol->dump(2);) + } +#endif + entry_control = head->skip_strip_mined()->in(LoopNode::EntryControl); + } else if (bt == T_LONG) { + // We're turning a long counted loop into a regular loop that will be converted into an int counted loop. That loop + // won't need loop limit check predicates (iters_limit guarantees that). Add a cast to make sure that, whatever + // transformation happens by the time the counted loop is created (in a subsequent pass of loop opts), C2 knows + // enough about the loop's limit that it doesn't try to add loop limit check predicates. + const Predicates predicates(entry_control); + const TypeLong* new_limit_t = new_limit->Value(&_igvn)->is_long(); + new_limit = ConstraintCastNode::make_cast_for_basic_type(predicates.entry(), new_limit, + TypeLong::make(0, new_limit_t->_hi, new_limit_t->_widen), + ConstraintCastNode::UnconditionalDependency, bt); + register_new_node(new_limit, predicates.entry()); + } else { + assert(bt == T_INT && known_short_running_loop, "only CountedLoop statically known to be short running"); + } + IfNode* exit_test = head->loopexit(); + + if (bt == T_LONG) { + // The loop is short running so new_limit fits into an int: either we determined that statically or added a guard + new_limit = new ConvL2INode(new_limit); + register_new_node(new_limit, entry_control); + } + + Node* int_zero = intcon(0); + if (stride_con < 0) { + new_limit = new SubINode(int_zero, new_limit); + register_new_node(new_limit, entry_control); + } + + // Clone the iv data nodes as an integer iv + Node* int_stride = intcon(stride_con); + Node* inner_phi = new PhiNode(head, TypeInt::INT); + Node* inner_incr = new AddINode(inner_phi, int_stride); + Node* inner_cmp = new CmpINode(inner_incr, new_limit); + Node* inner_bol = new BoolNode(inner_cmp, exit_test->in(1)->as_Bool()->_test._test); + inner_phi->set_req(LoopNode::EntryControl, int_zero); + inner_phi->set_req(LoopNode::LoopBackControl, inner_incr); + register_new_node(inner_phi, head); + register_new_node(inner_incr, head); + register_new_node(inner_cmp, head); + register_new_node(inner_bol, head); + + _igvn.replace_input_of(exit_test, 1, inner_bol); + + // Replace inner loop long iv phi as inner loop int iv phi + outer + // loop iv phi + Node* iv_add = loop_nest_replace_iv(phi, inner_phi, init, head, bt); + + LoopNode* inner_head = head; + if (bt == T_LONG) { + // Turn the loop back to a counted loop + inner_head = create_inner_head(loop, head, exit_test); + } else { + // Use existing counted loop + revert_to_normal_loop(head); + } + + if (bt == T_INT) { + init = new ConvI2LNode(init); + register_new_node(init, entry_control); + } + + transform_long_range_checks(stride_con, range_checks, init, new_limit, + inner_phi, iv_add, inner_head); + + inner_head->mark_loop_nest_inner_loop(); + + return true; +} + int PhaseIdealLoop::extract_long_range_checks(const IdealLoopTree* loop, jint stride_con, int iters_limit, PhiNode* phi, Node_List& range_checks) { const jlong min_iters = 2; @@ -1318,7 +1535,6 @@ void PhaseIdealLoop::transform_long_range_checks(int stride_con, const Node_List for (uint i = 0; i < range_checks.size(); i++) { ProjNode* proj = range_checks.at(i)->as_Proj(); - ProjNode* unc_proj = proj->other_if_proj(); RangeCheckNode* rc = proj->in(0)->as_RangeCheck(); jlong scale = 0; Node* offset = nullptr; @@ -4415,6 +4631,9 @@ void IdealLoopTree::dump_head() { if (predicates.loop_limit_check_predicate_block()->is_non_empty()) { tty->print(" limit_check"); } + if (predicates.short_running_long_loop_predicate_block()->is_non_empty()) { + tty->print(" short_running"); + } if (UseLoopPredicate) { if (UseProfiledLoopPredicate && predicates.profiled_loop_predicate_block()->is_non_empty()) { tty->print(" profile_predicated"); @@ -4922,7 +5141,7 @@ void PhaseIdealLoop::build_and_optimize() { for (LoopTreeIterator iter(_ltree_root); !iter.done(); iter.next()) { IdealLoopTree* lpt = iter.current(); if (lpt->is_innermost() && lpt->_allow_optimizations && !lpt->_has_call && lpt->is_counted()) { - lpt->compute_trip_count(this); + lpt->compute_trip_count(this, T_INT); if (!lpt->do_one_iteration_loop(this) && !lpt->do_remove_empty_loop(this)) { AutoNodeBudget node_budget(this); diff --git a/src/hotspot/share/opto/loopnode.hpp b/src/hotspot/share/opto/loopnode.hpp index 15206c1a351..27e397790d4 100644 --- a/src/hotspot/share/opto/loopnode.hpp +++ b/src/hotspot/share/opto/loopnode.hpp @@ -218,6 +218,18 @@ public: jlong stride_con() const; static BaseCountedLoopNode* make(Node* entry, Node* backedge, BasicType bt); + + virtual void set_trip_count(julong tc) = 0; + virtual julong trip_count() const = 0; + + bool has_exact_trip_count() const { return (_loop_flags & HasExactTripCount) != 0; } + void set_exact_trip_count(julong tc) { + set_trip_count(tc); + _loop_flags |= HasExactTripCount; + } + void set_nonexact_trip_count() { + _loop_flags &= ~HasExactTripCount; + } }; @@ -298,26 +310,17 @@ public: int main_idx() const { return _main_idx; } + void set_trip_count(julong tc) { + assert(tc < max_juint, "Cannot set trip count to max_juint"); + _trip_count = checked_cast(tc); + } + julong trip_count() const { return _trip_count; } void set_pre_loop (CountedLoopNode *main) { assert(is_normal_loop(),""); _loop_flags |= Pre ; _main_idx = main->_idx; } void set_main_loop ( ) { assert(is_normal_loop(),""); _loop_flags |= Main; } void set_post_loop (CountedLoopNode *main) { assert(is_normal_loop(),""); _loop_flags |= Post; _main_idx = main->_idx; } void set_normal_loop( ) { _loop_flags &= ~PreMainPostFlagsMask; } - // We use max_juint for the default value of _trip_count to signal it wasn't set. - // We shouldn't set _trip_count to max_juint explicitly. - void set_trip_count(uint tc) { assert(tc < max_juint, "Cannot set trip count to max_juint"); _trip_count = tc; } - uint trip_count() { return _trip_count; } - - bool has_exact_trip_count() const { return (_loop_flags & HasExactTripCount) != 0; } - void set_exact_trip_count(uint tc) { - assert(tc < max_juint, "Cannot set trip count to max_juint"); - _trip_count = tc; - _loop_flags |= HasExactTripCount; - } - void set_nonexact_trip_count() { - _loop_flags &= ~HasExactTripCount; - } void set_notpassed_slp() { _loop_flags &= ~PassedSlpAnalysis; } @@ -380,9 +383,15 @@ public: }; class LongCountedLoopNode : public BaseCountedLoopNode { +private: + virtual uint size_of() const { return sizeof(*this); } + + // Known trip count calculated by compute_exact_trip_count() + julong _trip_count; + public: LongCountedLoopNode(Node *entry, Node *backedge) - : BaseCountedLoopNode(entry, backedge) { + : BaseCountedLoopNode(entry, backedge), _trip_count(max_julong) { init_class_id(Class_LongCountedLoop); } @@ -392,6 +401,12 @@ public: return T_LONG; } + void set_trip_count(julong tc) { + assert(tc < max_julong, "Cannot set trip count to max_julong"); + _trip_count = tc; + } + julong trip_count() const { return _trip_count; } + LongCountedLoopEndNode* loopexit_or_null() const { return (LongCountedLoopEndNode*) BaseCountedLoopNode::loopexit_or_null(); } LongCountedLoopEndNode* loopexit() const { return (LongCountedLoopEndNode*) BaseCountedLoopNode::loopexit(); } }; @@ -778,7 +793,7 @@ public: uint est_loop_unroll_sz(uint factor) const; // Compute loop trip count if possible - void compute_trip_count(PhaseIdealLoop* phase); + void compute_trip_count(PhaseIdealLoop* phase, BasicType bt); // Compute loop trip count from profile data float compute_profile_trip_cnt_helper(Node* n); @@ -1829,6 +1844,8 @@ public: Node* ensure_node_and_inputs_are_above_pre_end(CountedLoopEndNode* pre_end, Node* node); + bool try_make_short_running_loop(IdealLoopTree* loop, jint stride_con, const Node_List& range_checks, const uint iters_limit); + ConINode* intcon(jint i); ConLNode* longcon(jlong i); diff --git a/src/hotspot/share/opto/predicates.cpp b/src/hotspot/share/opto/predicates.cpp index 137d16712d8..da9f704ee8d 100644 --- a/src/hotspot/share/opto/predicates.cpp +++ b/src/hotspot/share/opto/predicates.cpp @@ -82,12 +82,11 @@ ParsePredicateNode* ParsePredicate::init_parse_predicate(const Node* parse_predi return nullptr; } -ParsePredicate ParsePredicate::clone_to_unswitched_loop(Node* new_control, const bool is_false_path_loop, - PhaseIdealLoop* phase) const { +ParsePredicate ParsePredicate::clone_to_loop(Node* new_control, const bool rewire_uncommon_proj_phi_inputs, + PhaseIdealLoop* phase) const { ParsePredicateSuccessProj* success_proj = phase->create_new_if_for_predicate(_success_proj, new_control, _parse_predicate_node->deopt_reason(), - Op_ParsePredicate, is_false_path_loop); - NOT_PRODUCT(trace_cloned_parse_predicate(is_false_path_loop, success_proj)); + Op_ParsePredicate, rewire_uncommon_proj_phi_inputs); return ParsePredicate(success_proj, _parse_predicate_node->deopt_reason()); } @@ -97,11 +96,10 @@ void ParsePredicate::kill(PhaseIterGVN& igvn) const { } #ifndef PRODUCT -void ParsePredicate::trace_cloned_parse_predicate(const bool is_false_path_loop, - const ParsePredicateSuccessProj* success_proj) { - if (TraceLoopPredicate) { +void ParsePredicate::trace_cloned_parse_predicate(const bool is_false_path_loop) const { + if (TraceLoopUnswitching) { tty->print("Parse Predicate cloned to %s path loop: ", is_false_path_loop ? "false" : "true"); - success_proj->in(0)->dump(); + head()->dump(); } } #endif // NOT PRODUCT @@ -126,6 +124,7 @@ bool RuntimePredicate::has_valid_uncommon_trap(const Node* success_proj) { assert(RegularPredicate::may_be_predicate_if(success_proj), "must have been checked before"); const Deoptimization::DeoptReason deopt_reason = uncommon_trap_reason(success_proj->as_IfProj()); return (deopt_reason == Deoptimization::Reason_loop_limit_check || + deopt_reason == Deoptimization::Reason_short_running_long_loop || deopt_reason == Deoptimization::Reason_auto_vectorization_check || deopt_reason == Deoptimization::Reason_predicate || deopt_reason == Deoptimization::Reason_profile_predicate); @@ -941,6 +940,8 @@ void Predicates::dump() const { _profiled_loop_predicate_block.dump(" "); tty->print_cr("- Loop Predicate Block:"); _loop_predicate_block.dump(" "); + tty->print_cr("- Short Running Long Loop Predicate Block:"); + _short_running_long_loop_predicate_block.dump(" "); tty->cr(); } else { tty->print_cr(""); @@ -999,6 +1000,10 @@ InitializedAssertionPredicate CreateAssertionPredicatesVisitor::initialize_from_ return initialized_assertion_predicate; } +bool NodeInSingleLoopBody::check_node_in_loop_body(Node* node) const { + return _phase->is_member(_ilt, _phase->get_ctrl(node)); +} + // Clone the provided Template Assertion Predicate and set '_init' as new input for the OpaqueLoopInitNode. TemplateAssertionPredicate CreateAssertionPredicatesVisitor::clone_template_and_replace_init_input( const TemplateAssertionPredicate& template_assertion_predicate) const { @@ -1108,11 +1113,18 @@ void CloneUnswitchedLoopPredicatesVisitor::visit(const ParsePredicate& parse_pre if (_is_counted_loop && deopt_reason == Deoptimization::Reason_loop_limit_check) { return; } - _clone_predicate_to_true_path_loop.clone_parse_predicate(parse_predicate, false); - _clone_predicate_to_false_path_loop.clone_parse_predicate(parse_predicate, true); + clone_parse_predicate(parse_predicate, false); + clone_parse_predicate(parse_predicate, true); parse_predicate.kill(_phase->igvn()); } +void CloneUnswitchedLoopPredicatesVisitor::clone_parse_predicate(const ParsePredicate& parse_predicate, + const bool is_false_path_loop) { + ClonePredicateToTargetLoop& clone_predicate_to_loop = is_false_path_loop ? _clone_predicate_to_false_path_loop : _clone_predicate_to_true_path_loop; + const ParsePredicate cloned_parse_predicate = clone_predicate_to_loop.clone_parse_predicate(parse_predicate, is_false_path_loop); + NOT_PRODUCT(cloned_parse_predicate.trace_cloned_parse_predicate(is_false_path_loop);) +} + // Clone the Template Assertion Predicate, which is currently found before the newly added unswitched loop selector, // to the true path and false path loop. void CloneUnswitchedLoopPredicatesVisitor::visit(const TemplateAssertionPredicate& template_assertion_predicate) { diff --git a/src/hotspot/share/opto/predicates.hpp b/src/hotspot/share/opto/predicates.hpp index 2181e498bd4..ef7c5600853 100644 --- a/src/hotspot/share/opto/predicates.hpp +++ b/src/hotspot/share/opto/predicates.hpp @@ -73,6 +73,14 @@ class TemplateAssertionPredicate; * counted loop to avoid these overflow problems. * The predicate does not replace an actual check inside the loop. This predicate can only * be added once above the Loop Limit Check Parse Predicate for a loop. + * - Short: This predicate is created when a long counted loop is transformed into an int counted + * Running Long loop. In general, that transformation requires an outer loop to guarantee that the new + * Loop loop nest iterates over the entire range of the loop before transformation. However, if the + * Predicate loop is speculated to run for a small enough number of iterations, the outer loop is not + * needed. This predicate is added to catch mis-speculation in this case. It also applies to + * int counted loops with long range checks for which a loop nest also needs to be created + * in the general case (so the transformation of long range checks to int range checks is + * legal). * - Assertion Predicate: An always true predicate which will never fail (its range is already covered by an earlier * Hoisted Check Predicate or the main-loop entry guard) but is required in order to fold away a * dead sub loop in which some data could be proven to be dead (by the type system) and replaced @@ -288,8 +296,6 @@ class ParsePredicate : public Predicate { } static ParsePredicateNode* init_parse_predicate(const Node* parse_predicate_proj, Deoptimization::DeoptReason deopt_reason); - NOT_PRODUCT(static void trace_cloned_parse_predicate(bool is_false_path_loop, - const ParsePredicateSuccessProj* success_proj);) public: ParsePredicate(Node* parse_predicate_proj, Deoptimization::DeoptReason deopt_reason) @@ -320,8 +326,8 @@ class ParsePredicate : public Predicate { return _success_proj; } - ParsePredicate clone_to_unswitched_loop(Node* new_control, bool is_false_path_loop, - PhaseIdealLoop* phase) const; + ParsePredicate clone_to_loop(Node* new_control, bool rewire_uncommon_proj_phi_inputs, PhaseIdealLoop* phase) const; + NOT_PRODUCT(void trace_cloned_parse_predicate(bool is_false_path_loop) const;) void kill(PhaseIterGVN& igvn) const; }; @@ -786,7 +792,8 @@ class PredicateIterator : public StackObj { PredicateBlockIterator loop_predicate_iterator(current_node, Deoptimization::Reason_predicate); current_node = loop_predicate_iterator.for_each(predicate_visitor); } - return current_node; + PredicateBlockIterator short_running_loop_predicate_iterator(current_node, Deoptimization::Reason_short_running_long_loop); + return short_running_loop_predicate_iterator.for_each(predicate_visitor); } }; @@ -953,6 +960,7 @@ class Predicates : public StackObj { const PredicateBlock _auto_vectorization_check_block; const PredicateBlock _profiled_loop_predicate_block; const PredicateBlock _loop_predicate_block; + const PredicateBlock _short_running_long_loop_predicate_block; Node* const _entry; public: @@ -965,7 +973,9 @@ class Predicates : public StackObj { Deoptimization::Reason_profile_predicate), _loop_predicate_block(_profiled_loop_predicate_block.entry(), Deoptimization::Reason_predicate), - _entry(_loop_predicate_block.entry()) {} + _short_running_long_loop_predicate_block(_loop_predicate_block.entry(), + Deoptimization::Reason_short_running_long_loop), + _entry(_short_running_long_loop_predicate_block.entry()) {} NONCOPYABLE(Predicates); // Returns the control input the first predicate if there are any predicates. If there are no predicates, the same @@ -990,6 +1000,10 @@ class Predicates : public StackObj { return &_loop_limit_check_predicate_block; } + const PredicateBlock* short_running_long_loop_predicate_block() const { + return &_short_running_long_loop_predicate_block; + } + bool has_any() const { return _entry != _tail; } @@ -1082,6 +1096,19 @@ class NodeInClonedLoopBody : public NodeInLoopBody { } }; +// This class checks whether a node is in the loop body passed to the constructor. +class NodeInSingleLoopBody : public NodeInLoopBody { + PhaseIdealLoop* const _phase; + IdealLoopTree* const _ilt; + +public: + NodeInSingleLoopBody(PhaseIdealLoop* phase, IdealLoopTree* ilt) : _phase(phase), _ilt(ilt) { + } + NONCOPYABLE(NodeInSingleLoopBody); + + bool check_node_in_loop_body(Node* node) const override; +}; + // Visitor to create Initialized Assertion Predicates at a target loop from Template Assertion Predicates from a source // loop. This visitor can be used in combination with a PredicateIterator. class CreateAssertionPredicatesVisitor : public PredicateVisitor { @@ -1158,10 +1185,11 @@ public: ClonePredicateToTargetLoop(LoopNode* target_loop_head, const NodeInLoopBody& node_in_loop_body, PhaseIdealLoop* phase); // Clones the provided Parse Predicate to the head of the current predicate chain at the target loop. - void clone_parse_predicate(const ParsePredicate& parse_predicate, bool is_false_path_loop) { - ParsePredicate cloned_parse_predicate = parse_predicate.clone_to_unswitched_loop(_old_target_loop_entry, - is_false_path_loop, _phase); + ParsePredicate clone_parse_predicate(const ParsePredicate& parse_predicate, bool rewire_uncommon_proj_phi_inputs) { + ParsePredicate cloned_parse_predicate = parse_predicate.clone_to_loop(_old_target_loop_entry, + rewire_uncommon_proj_phi_inputs, _phase); _target_loop_predicate_chain.insert_predicate(cloned_parse_predicate); + return cloned_parse_predicate; } void clone_template_assertion_predicate(const TemplateAssertionPredicate& template_assertion_predicate); @@ -1189,6 +1217,9 @@ class CloneUnswitchedLoopPredicatesVisitor : public PredicateVisitor { using PredicateVisitor::visit; void visit(const ParsePredicate& parse_predicate) override; + + void clone_parse_predicate(const ParsePredicate &parse_predicate, + bool is_false_path_loop); void visit(const TemplateAssertionPredicate& template_assertion_predicate) override; }; diff --git a/src/hotspot/share/runtime/deoptimization.cpp b/src/hotspot/share/runtime/deoptimization.cpp index 110f4ca8c07..5f9a80cf100 100644 --- a/src/hotspot/share/runtime/deoptimization.cpp +++ b/src/hotspot/share/runtime/deoptimization.cpp @@ -2767,8 +2767,8 @@ const char* Deoptimization::_trap_reason_name[] = { "unstable_if", "unstable_fused_if", "receiver_constraint", + "short_running_loop" JVMCI_ONLY("_or_aliasing"), #if INCLUDE_JVMCI - "aliasing", "transfer_to_interpreter", "not_compiled_exception_handler", "unresolved", diff --git a/src/hotspot/share/runtime/deoptimization.hpp b/src/hotspot/share/runtime/deoptimization.hpp index 42cf25e5162..5d97e2056ad 100644 --- a/src/hotspot/share/runtime/deoptimization.hpp +++ b/src/hotspot/share/runtime/deoptimization.hpp @@ -117,8 +117,9 @@ class Deoptimization : AllStatic { Reason_unstable_if, // a branch predicted always false was taken Reason_unstable_fused_if, // fused two ifs that had each one untaken branch. One is now taken. Reason_receiver_constraint, // receiver subtype check failed + Reason_short_running_long_loop, // profile reports loop runs for small number of iterations #if INCLUDE_JVMCI - Reason_aliasing, // optimistic assumption about aliasing failed + Reason_aliasing = Reason_short_running_long_loop, // optimistic assumption about aliasing failed Reason_transfer_to_interpreter, // explicit transferToInterpreter() Reason_not_compiled_exception_handler, Reason_unresolved, diff --git a/src/hotspot/share/runtime/vmStructs.cpp b/src/hotspot/share/runtime/vmStructs.cpp index 71cba9ec085..6fc16f9b045 100644 --- a/src/hotspot/share/runtime/vmStructs.cpp +++ b/src/hotspot/share/runtime/vmStructs.cpp @@ -1566,6 +1566,7 @@ declare_constant(Deoptimization::Reason_age) \ declare_constant(Deoptimization::Reason_predicate) \ declare_constant(Deoptimization::Reason_loop_limit_check) \ + declare_constant(Deoptimization::Reason_short_running_long_loop) \ declare_constant(Deoptimization::Reason_auto_vectorization_check) \ declare_constant(Deoptimization::Reason_speculate_class_check) \ declare_constant(Deoptimization::Reason_speculate_null_check) \ @@ -1573,7 +1574,6 @@ declare_constant(Deoptimization::Reason_unstable_if) \ declare_constant(Deoptimization::Reason_unstable_fused_if) \ declare_constant(Deoptimization::Reason_receiver_constraint) \ - NOT_ZERO(JVMCI_ONLY(declare_constant(Deoptimization::Reason_aliasing))) \ NOT_ZERO(JVMCI_ONLY(declare_constant(Deoptimization::Reason_transfer_to_interpreter))) \ NOT_ZERO(JVMCI_ONLY(declare_constant(Deoptimization::Reason_not_compiled_exception_handler))) \ NOT_ZERO(JVMCI_ONLY(declare_constant(Deoptimization::Reason_unresolved))) \ diff --git a/src/hotspot/share/utilities/globalDefinitions.hpp b/src/hotspot/share/utilities/globalDefinitions.hpp index 46daa867644..f6d162a81e4 100644 --- a/src/hotspot/share/utilities/globalDefinitions.hpp +++ b/src/hotspot/share/utilities/globalDefinitions.hpp @@ -148,6 +148,9 @@ class oopDesc; #ifndef JULONG_FORMAT_X #define JULONG_FORMAT_X UINT64_FORMAT_X #endif +#ifndef JULONG_FORMAT_W +#define JULONG_FORMAT_W(width) UINT64_FORMAT_W(width) +#endif // Format pointers and padded integral values which change size between 32- and 64-bit. #ifdef _LP64 @@ -771,6 +774,14 @@ inline jlong min_signed_integer(BasicType bt) { return min_jlong; } +inline julong max_unsigned_integer(BasicType bt) { + if (bt == T_INT) { + return max_juint; + } + assert(bt == T_LONG, "unsupported"); + return max_julong; +} + inline uint bits_per_java_integer(BasicType bt) { if (bt == T_INT) { return BitsPerJavaInteger; diff --git a/test/hotspot/jtreg/compiler/c2/irTests/TestLongRangeChecks.java b/test/hotspot/jtreg/compiler/c2/irTests/TestLongRangeChecks.java index 479a2a45cb9..de829f84775 100644 --- a/test/hotspot/jtreg/compiler/c2/irTests/TestLongRangeChecks.java +++ b/test/hotspot/jtreg/compiler/c2/irTests/TestLongRangeChecks.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2022, Red Hat, Inc. All rights reserved. + * Copyright (c) 2021, 2022, 2025 Red Hat, Inc. All rights reserved. * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -38,6 +38,9 @@ import java.util.Objects; public class TestLongRangeChecks { public static void main(String[] args) { + TestFramework.runWithFlags("-XX:-ShortRunningLongLoop", "-XX:+TieredCompilation", "-XX:-UseCountedLoopSafepoints", "-XX:LoopUnrollLimit=0"); + TestFramework.runWithFlags("-XX:-ShortRunningLongLoop", "-XX:+TieredCompilation", "-XX:+UseCountedLoopSafepoints", "-XX:LoopStripMiningIter=1", "-XX:LoopUnrollLimit=0"); + TestFramework.runWithFlags("-XX:-ShortRunningLongLoop", "-XX:+TieredCompilation", "-XX:+UseCountedLoopSafepoints", "-XX:LoopStripMiningIter=1000", "-XX:LoopUnrollLimit=0"); TestFramework.runWithFlags("-XX:+TieredCompilation", "-XX:-UseCountedLoopSafepoints", "-XX:LoopUnrollLimit=0"); TestFramework.runWithFlags("-XX:+TieredCompilation", "-XX:+UseCountedLoopSafepoints", "-XX:LoopStripMiningIter=1", "-XX:LoopUnrollLimit=0"); TestFramework.runWithFlags("-XX:+TieredCompilation", "-XX:+UseCountedLoopSafepoints", "-XX:LoopStripMiningIter=1000", "-XX:LoopUnrollLimit=0"); @@ -45,7 +48,8 @@ public class TestLongRangeChecks { @Test - @IR(counts = { IRNode.LOOP, "1" }) + @IR(applyIf = { "ShortRunningLongLoop", "false" }, counts = { IRNode.LOOP, "1" }) + @IR(applyIf = { "ShortRunningLongLoop", "true" }, failOn = IRNode.LOOP) @IR(failOn = { IRNode.COUNTED_LOOP}) public static void testStridePosScalePos(long start, long stop, long length, long offset) { final long scale = 1; @@ -66,7 +70,8 @@ public class TestLongRangeChecks { } @Test - @IR(counts = { IRNode.LOOP, "1" }) + @IR(applyIf = { "ShortRunningLongLoop", "false" }, counts = { IRNode.LOOP, "1" }) + @IR(applyIf = { "ShortRunningLongLoop", "true" }, failOn = IRNode.LOOP) @IR(failOn = { IRNode.COUNTED_LOOP}) public static void testStridePosScalePosInIntLoop1(int start, int stop, long length, long offset) { final long scale = 2; @@ -84,7 +89,8 @@ public class TestLongRangeChecks { } @Test - @IR(counts = { IRNode.LOOP, "1" }) + @IR(applyIf = { "ShortRunningLongLoop", "false" }, counts = { IRNode.LOOP, "1" }) + @IR(applyIf = { "ShortRunningLongLoop", "true" }, failOn = IRNode.LOOP) @IR(failOn = { IRNode.COUNTED_LOOP}) public static void testStridePosScalePosInIntLoop2(int start, int stop, long length, long offset) { final int scale = 2; @@ -102,7 +108,8 @@ public class TestLongRangeChecks { } @Test - @IR(counts = { IRNode.LOOP, "1"}) + @IR(applyIf = { "ShortRunningLongLoop", "false" }, counts = { IRNode.LOOP, "1" }) + @IR(applyIf = { "ShortRunningLongLoop", "true" }, failOn = IRNode.LOOP) @IR(failOn = { IRNode.COUNTED_LOOP}) public static void testStrideNegScaleNeg(long start, long stop, long length, long offset) { final long scale = -1; @@ -118,7 +125,8 @@ public class TestLongRangeChecks { } @Test - @IR(counts = { IRNode.LOOP, "1" }) + @IR(applyIf = { "ShortRunningLongLoop", "false" }, counts = { IRNode.LOOP, "1" }) + @IR(applyIf = { "ShortRunningLongLoop", "true" }, failOn = IRNode.LOOP) @IR(failOn = { IRNode.COUNTED_LOOP}) public static void testStrideNegScaleNegInIntLoop1(int start, int stop, long length, long offset) { final long scale = -2; @@ -135,7 +143,8 @@ public class TestLongRangeChecks { } @Test - @IR(counts = { IRNode.LOOP, "1" }) + @IR(applyIf = { "ShortRunningLongLoop", "false" }, counts = { IRNode.LOOP, "1" }) + @IR(applyIf = { "ShortRunningLongLoop", "true" }, failOn = IRNode.LOOP) @IR(failOn = { IRNode.COUNTED_LOOP}) public static void testStrideNegScaleNegInIntLoop2(int start, int stop, long length, long offset) { final int scale = -2; @@ -152,7 +161,8 @@ public class TestLongRangeChecks { } @Test - @IR(counts = { IRNode.LOOP, "1"}) + @IR(applyIf = { "ShortRunningLongLoop", "false" }, counts = { IRNode.LOOP, "1" }) + @IR(applyIf = { "ShortRunningLongLoop", "true" }, failOn = IRNode.LOOP) @IR(failOn = { IRNode.COUNTED_LOOP}) public static void testStrideNegScalePos(long start, long stop, long length, long offset) { final long scale = 1; @@ -168,7 +178,8 @@ public class TestLongRangeChecks { } @Test - @IR(counts = { IRNode.LOOP, "1" }) + @IR(applyIf = { "ShortRunningLongLoop", "false" }, counts = { IRNode.LOOP, "1" }) + @IR(applyIf = { "ShortRunningLongLoop", "true" }, failOn = IRNode.LOOP) @IR(failOn = { IRNode.COUNTED_LOOP}) public static void testStrideNegScalePosInIntLoop1(int start, int stop, long length, long offset) { final long scale = 2; @@ -184,7 +195,8 @@ public class TestLongRangeChecks { } @Test - @IR(counts = { IRNode.LOOP, "1" }) + @IR(applyIf = { "ShortRunningLongLoop", "false" }, counts = { IRNode.LOOP, "1" }) + @IR(applyIf = { "ShortRunningLongLoop", "true" }, failOn = IRNode.LOOP) @IR(failOn = { IRNode.COUNTED_LOOP}) public static void testStrideNegScalePosInIntLoop2(int start, int stop, long length, long offset) { final int scale = 2; @@ -200,7 +212,8 @@ public class TestLongRangeChecks { } @Test - @IR(counts = { IRNode.LOOP, "1"}) + @IR(applyIf = { "ShortRunningLongLoop", "false" }, counts = { IRNode.LOOP, "1" }) + @IR(applyIf = { "ShortRunningLongLoop", "true" }, failOn = IRNode.LOOP) @IR(failOn = { IRNode.COUNTED_LOOP}) public static void testStridePosScaleNeg(long start, long stop, long length, long offset) { final long scale = -1; @@ -216,7 +229,8 @@ public class TestLongRangeChecks { } @Test - @IR(counts = { IRNode.LOOP, "1"}) + @IR(applyIf = { "ShortRunningLongLoop", "false" }, counts = { IRNode.LOOP, "1" }) + @IR(applyIf = { "ShortRunningLongLoop", "true" }, failOn = IRNode.LOOP) @IR(failOn = { IRNode.COUNTED_LOOP}) public static void testStridePosScaleNegInIntLoop1(int start, int stop, long length, long offset) { final long scale = -2; @@ -232,7 +246,8 @@ public class TestLongRangeChecks { } @Test - @IR(counts = { IRNode.LOOP, "1"}) + @IR(applyIf = { "ShortRunningLongLoop", "false" }, counts = { IRNode.LOOP, "1" }) + @IR(applyIf = { "ShortRunningLongLoop", "true" }, failOn = IRNode.LOOP) @IR(failOn = { IRNode.COUNTED_LOOP}) public static void testStridePosScaleNegInIntLoop2(int start, int stop, long length, long offset) { final int scale = -2; diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java index 01bc13482fd..1b843e27587 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java @@ -1663,6 +1663,11 @@ public class IRNode { trapNodes(RANGE_CHECK_TRAP, "range_check"); } + public static final String SHORT_RUNNING_LOOP_TRAP = PREFIX + "SHORT_RUNNING_LOOP_TRAP" + POSTFIX; + static { + trapNodes(SHORT_RUNNING_LOOP_TRAP, "short_running_loop"); + } + public static final String REINTERPRET_S2HF = PREFIX + "REINTERPRET_S2HF" + POSTFIX; static { beforeMatchingNameRegex(REINTERPRET_S2HF, "ReinterpretS2HF"); diff --git a/test/hotspot/jtreg/compiler/longcountedloops/TestShortLoopLostLimit.java b/test/hotspot/jtreg/compiler/longcountedloops/TestShortLoopLostLimit.java new file mode 100644 index 00000000000..117bbab53e5 --- /dev/null +++ b/test/hotspot/jtreg/compiler/longcountedloops/TestShortLoopLostLimit.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2025, Red Hat, 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. + */ + +/** + * @test + * @bug 8342692 + * @summary C2: long counted loop/long range checks: don't create loop-nest for short running loops + * @run main/othervm -XX:-TieredCompilation -XX:-UseOnStackReplacement -XX:-BackgroundCompilation TestShortLoopLostLimit + * @run main/othervm TestShortLoopLostLimit + */ + +public class TestShortLoopLostLimit { + private static volatile int volatileField; + + public static void main(String[] args) { + for (int i = 0; i < 20_000; i++) { + test1(0, 100); + test2(0, 100); + } + } + + private static void test1(int a, long b) { + for (long i = 0; i < a + b; i += 2) { + volatileField = 42; + } + } + + private static void test2(int a, long b) { + for (long i = a + b; i > 0; i -= 2) { + volatileField = 42; + } + } +} diff --git a/test/hotspot/jtreg/compiler/longcountedloops/TestShortRunningIntLoopWithLongChecksPredicates.java b/test/hotspot/jtreg/compiler/longcountedloops/TestShortRunningIntLoopWithLongChecksPredicates.java new file mode 100644 index 00000000000..fcee53371ca --- /dev/null +++ b/test/hotspot/jtreg/compiler/longcountedloops/TestShortRunningIntLoopWithLongChecksPredicates.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2025, Red Hat, 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. + */ + +/** + * @test + * @bug 8342692 + * @summary C2: long counted loop/long range checks: don't create loop-nest for short running loops + * @run main/othervm -XX:-TieredCompilation -XX:-UseOnStackReplacement -XX:-BackgroundCompilation -XX:LoopUnrollLimit=100 + * TestShortRunningIntLoopWithLongChecksPredicates + * @run main/othervm TestShortRunningIntLoopWithLongChecksPredicates + */ + +import java.util.Objects; + +// int RC is first eliminated by predication which causes Assertion +// Predicates to be added. Then the loop is transformed to make it +// possible to optimize long RC. Finally unrolling happen which +// require the Assertion Predicates to have been properly copied when +// the loop was transformed for the long range check. +public class TestShortRunningIntLoopWithLongChecksPredicates { + private static volatile int volatileField; + + public static void main(String[] args) { + int[] array = new int[100]; + for (int i = 0; i < 20_000; i++) { + helper1(100, array, 100); + test1(1, 100); + } + } + + private static void test1(int stop, long range) { + int[] array = new int[3]; + helper1(stop, array, range); + } + + private static void helper1(int stop, int[] array, long range) { + for (int i = 0; i < stop; i++) { + if (i % 2 == 0) { + array[i] += i; + } else { + volatileField = 42; + } + Objects.checkIndex(i, range); + } + } +} diff --git a/test/hotspot/jtreg/compiler/longcountedloops/TestShortRunningLongCountedLoop.java b/test/hotspot/jtreg/compiler/longcountedloops/TestShortRunningLongCountedLoop.java new file mode 100644 index 00000000000..7e55353e0f7 --- /dev/null +++ b/test/hotspot/jtreg/compiler/longcountedloops/TestShortRunningLongCountedLoop.java @@ -0,0 +1,579 @@ +/* + * Copyright (c) 2025, Red Hat, 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 compiler.longcountedloops; +import compiler.lib.ir_framework.*; +import compiler.whitebox.CompilerWhiteBoxTest; +import jdk.test.whitebox.WhiteBox; + +import java.util.Objects; +/* + * @test + * @bug 8342692 + * @summary C2: long counted loop/long range checks: don't create loop-nest for short running loops + * @library /test/lib / + * @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 compiler.longcountedloops.TestShortRunningLongCountedLoop + */ + +public class TestShortRunningLongCountedLoop { + private static volatile int volatileField; + private final static WhiteBox wb = WhiteBox.getWhiteBox(); + + public static void main(String[] args) { + // IR rules expect a single loop so disable unrolling + // IR rules expect strip mined loop to be enabled + // testIntLoopUnknownBoundsShortUnswitchedLoop and testLongLoopUnknownBoundsShortUnswitchedLoop need -XX:-UseProfiledLoopPredicate + TestFramework.runWithFlags("-XX:LoopMaxUnroll=0", "-XX:LoopStripMiningIter=1000", "-XX:+UseCountedLoopSafepoints", "-XX:-UseProfiledLoopPredicate"); + } + + // Check IR only has a counted loop when bounds are known and loop run for a short time + @Test + @IR(counts = { IRNode.COUNTED_LOOP, "1" }) + @IR(failOn = { IRNode.LOOP, IRNode.OUTER_STRIP_MINED_LOOP, IRNode.SHORT_RUNNING_LOOP_TRAP }) + public static int testLongLoopConstantBoundsShortLoop1() { + int j = 0; + for (long i = 0; i < 100; i++) { + volatileField = 42; + j++; + } + return j; + } + + @Check(test = "testLongLoopConstantBoundsShortLoop1") + public static void checkTestLongLoopConstantBoundsShortLoop1(int res) { + if (res != 100) { + throw new RuntimeException("incorrect result: " + res); + } + } + + // Same with stride > 1 + @Test + @IR(counts = { IRNode.COUNTED_LOOP, "1" }) + @IR(failOn = { IRNode.LOOP, IRNode.OUTER_STRIP_MINED_LOOP, IRNode.SHORT_RUNNING_LOOP_TRAP }) + public static int testLongLoopConstantBoundsShortLoop2() { + int j = 0; + for (long i = 0; i < 2000; i += 20) { + volatileField = 42; + j++; + } + return j; + } + + @Check(test = "testLongLoopConstantBoundsShortLoop2") + public static void checkTestLongLoopConstantBoundsShortLoop2(int res) { + if (res != 100) { + throw new RuntimeException("incorrect result: " + res); + } + } + + // Same with loop going downward + @Test + @IR(counts = { IRNode.COUNTED_LOOP, "1" }) + @IR(failOn = { IRNode.LOOP, IRNode.OUTER_STRIP_MINED_LOOP, IRNode.SHORT_RUNNING_LOOP_TRAP }) + public static int testLongLoopConstantBoundsShortLoop3() { + int j = 0; + for (long i = 99; i >= 0; i--) { + volatileField = 42; + j++; + } + return j; + } + + @Check(test = "testLongLoopConstantBoundsShortLoop3") + public static void checkTestLongLoopConstantBoundsShortLoop3(int res) { + if (res != 100) { + throw new RuntimeException("incorrect result: " + res); + } + } + + // Same with loop going downward and stride > 1 + @Test + @IR(counts = { IRNode.COUNTED_LOOP, "1" }) + @IR(failOn = { IRNode.LOOP, IRNode.OUTER_STRIP_MINED_LOOP, IRNode.SHORT_RUNNING_LOOP_TRAP }) + public static int testLongLoopConstantBoundsShortLoop4() { + int j = 0; + for (long i = 1999; i >= 0; i-=20) { + volatileField = 42; + j++; + } + return j; + } + + @Check(test = "testLongLoopConstantBoundsShortLoop4") + public static void checkTestLongLoopConstantBoundsShortLoop4(int res) { + if (res != 100) { + throw new RuntimeException("incorrect result: " + res); + } + } + + // Check IR only has a counted loop when bounds are known but not exact and loop run for a short time + @Test + @IR(counts = { IRNode.COUNTED_LOOP, "1" }) + @IR(failOn = { IRNode.LOOP, IRNode.OUTER_STRIP_MINED_LOOP, IRNode.SHORT_RUNNING_LOOP_TRAP }) + public static int testLongLoopConstantBoundsShortLoop5(int start, int stop) { + start= Integer.max(start, 0); + stop= Integer.min(stop, 999); + int j = 0; + for (long i = start; i < stop; i++) { + volatileField = 42; + j++; + } + return j; + } + + @Run(test = "testLongLoopConstantBoundsShortLoop5") + public static void testLongLoopConstantBoundsShortLoop5_runner() { + int res = testLongLoopConstantBoundsShortLoop5(0, 100); + if (res != 100) { + throw new RuntimeException("incorrect result: " + res); + } + } + + // Check that loop nest is created when bounds are known and loop is not short run + @Test + @IR(counts = { IRNode.COUNTED_LOOP, "1", IRNode.LOOP, "1"}) + @IR(failOn = { IRNode.SHORT_RUNNING_LOOP_TRAP, IRNode.OUTER_STRIP_MINED_LOOP }) + public static int testLongLoopConstantBoundsLongLoop1() { + final long stride = Integer.MAX_VALUE / 1000; + int j = 0; + for (long i = 0; i < stride * 1001; i += stride) { + volatileField = 42; + j++; + } + return j; + } + + @Check(test = "testLongLoopConstantBoundsLongLoop1") + public static void checkTestLongLoopConstantBoundsLongLoop1(int res) { + if (res != 1001) { + throw new RuntimeException("incorrect result: " + res); + } + } + + // Same with negative stride + @Test + @IR(counts = { IRNode.COUNTED_LOOP, "1", IRNode.LOOP, "1"}) + @IR(failOn = { IRNode.SHORT_RUNNING_LOOP_TRAP, IRNode.OUTER_STRIP_MINED_LOOP }) + public static int testLongLoopConstantBoundsLongLoop2() { + final long stride = Integer.MAX_VALUE / 1000; + int j = 0; + for (long i = stride * 1000; i >= 0; i -= stride) { + volatileField = 42; + j++; + } + return j; + } + + @Check(test = "testLongLoopConstantBoundsLongLoop2") + public static void checkTestLongLoopConstantBoundsLongLoop2(int res) { + if (res != 1001) { + throw new RuntimeException("incorrect result: " + res); + } + } + + // Check IR only has a counted loop when bounds are unknown but profile reports a short running loop + @Test + @IR(counts = { IRNode.COUNTED_LOOP, "1", IRNode.SHORT_RUNNING_LOOP_TRAP, "1", IRNode.OUTER_STRIP_MINED_LOOP, "1" }) + @IR(failOn = { IRNode.LOOP }) + public static int testLongLoopUnknownBoundsShortLoop(long start, long stop) { + int j = 0; + for (long i = start; i < stop; i++) { + volatileField = 42; + j++; + } + return j; + } + + @Run(test = "testLongLoopUnknownBoundsShortLoop") + @Warmup(10_000) + public static void testLongLoopUnknownBoundsShortLoop_runner() { + int res = testLongLoopUnknownBoundsShortLoop(0, 100); + if (res != 100) { + throw new RuntimeException("incorrect result: " + res); + } + } + + // same with stride > 1 + @Test + @IR(counts = { IRNode.COUNTED_LOOP, "1", IRNode.SHORT_RUNNING_LOOP_TRAP, "1", IRNode.OUTER_STRIP_MINED_LOOP, "1" }) + @IR(failOn = { IRNode.LOOP }) + public static int testLongLoopUnknownBoundsShortLoop2(long start, long stop) { + int j = 0; + for (long i = start; i < stop; i+=20) { + volatileField = 42; + j++; + } + return j; + } + + @Run(test = "testLongLoopUnknownBoundsShortLoop2") + @Warmup(10_000) + public static void testLongLoopUnknownBoundsShortLoop2_runner() { + int res = testLongLoopUnknownBoundsShortLoop2(0, 2000); + if (res != 100) { + throw new RuntimeException("incorrect result: " + res); + } + } + + // same with negative stride + @Test + @IR(counts = { IRNode.COUNTED_LOOP, "1", IRNode.SHORT_RUNNING_LOOP_TRAP, "1", IRNode.OUTER_STRIP_MINED_LOOP, "1" }) + @IR(failOn = { IRNode.LOOP }) + public static int testLongLoopUnknownBoundsShortLoop3(long start, long stop) { + int j = 0; + for (long i = start; i >= stop; i--) { + volatileField = 42; + j++; + } + return j; + } + + @Run(test = "testLongLoopUnknownBoundsShortLoop3") + @Warmup(10_000) + public static void testLongLoopUnknownBoundsShortLoop3_runner() { + int res = testLongLoopUnknownBoundsShortLoop3(99, 0); + if (res != 100) { + throw new RuntimeException("incorrect result: " + res); + } + } + + // same with negative stride > 1 + @Test + @IR(counts = { IRNode.COUNTED_LOOP, "1", IRNode.SHORT_RUNNING_LOOP_TRAP, "1", IRNode.OUTER_STRIP_MINED_LOOP, "1" }) + @IR(failOn = { IRNode.LOOP }) + public static int testLongLoopUnknownBoundsShortLoop4(long start, long stop) { + int j = 0; + for (long i = start; i >= stop; i -= 20) { + volatileField = 42; + j++; + } + return j; + } + + @Run(test = "testLongLoopUnknownBoundsShortLoop4") + @Warmup(10_000) + public static void testLongLoopUnknownBoundsShortLoop4_runner() { + int res = testLongLoopUnknownBoundsShortLoop4(1999, 0); + if (res != 100) { + throw new RuntimeException("incorrect result: " + res); + } + } + + // Check that loop nest is created when bounds are not known but profile reports loop is not short run + @Test + @IR(counts = { IRNode.COUNTED_LOOP, "1", IRNode.OUTER_STRIP_MINED_LOOP, "1", IRNode.LOOP, "1"}) + @IR(failOn = { IRNode.SHORT_RUNNING_LOOP_TRAP }) + public static int testLongLoopUnknownBoundsLongLoop1(long start, long stop, long range) { + int j = 0; + for (long i = start; i < stop; i++) { + volatileField = 42; + Objects.checkIndex(i * (1024 * 1024), range); // max number of iteration of inner loop is roughly Integer.MAX_VALUE / 1024 / 1024 + j++; + } + return j; + } + + @Run(test = "testLongLoopUnknownBoundsLongLoop1") + @Warmup(10_000) + public static void testLongLoopUnknownBoundsLongLoop1_runner() { + int res = testLongLoopUnknownBoundsLongLoop1(0, 3000, Long.MAX_VALUE); + if (res != 3000) { + throw new RuntimeException("incorrect result: " + res); + } + } + + // same with negative stride + @Test + @IR(counts = { IRNode.COUNTED_LOOP, "1", IRNode.OUTER_STRIP_MINED_LOOP, "1", IRNode.LOOP, "1"}) + @IR(failOn = { IRNode.SHORT_RUNNING_LOOP_TRAP }) + public static int testLongLoopUnknownBoundsLongLoop2(long start, long stop, long range) { + int j = 0; + for (long i = start; i >= stop; i--) { + volatileField = 42; + Objects.checkIndex(i * (1024 * 1024), range); // max number of iteration of inner loop is roughly Integer.MAX_VALUE / 1024 / 1024 + j++; + } + return j; + } + + @Run(test = "testLongLoopUnknownBoundsLongLoop2") + @Warmup(10_000) + public static void testLongLoopUnknownBoundsLongLoop2_runner() { + int res = testLongLoopUnknownBoundsLongLoop2(2999, 0, Long.MAX_VALUE); + if (res != 3000) { + throw new RuntimeException("incorrect result: " + res); + } + } + + // Check IR has a loop nest when bounds are unknown, profile reports a short running loop but trap is taken + @Test + @IR(counts = { IRNode.COUNTED_LOOP, "1", IRNode.LOOP, "1", IRNode.OUTER_STRIP_MINED_LOOP, "1" }) + @IR(failOn = { IRNode.SHORT_RUNNING_LOOP_TRAP }) + public static int testLongLoopUnknownBoundsShortLoopFailedSpeculation(long start, long stop, long range) { + int j = 0; + for (long i = start; i < stop; i++) { + volatileField = 42; + Objects.checkIndex(i * (1024 * 1024), range); // max number of iteration of inner loop is roughly Integer.MAX_VALUE / 1024 / 1024 + j++; + } + return j; + } + + @Run(test = "testLongLoopUnknownBoundsShortLoopFailedSpeculation") + @Warmup(1) + public static void testLongLoopUnknownBoundsShortLoopFailedSpeculation_runner(RunInfo info) { + if (info.isWarmUp()) { + for (int i = 0; i < 10_0000; i++) { + int res = testLongLoopUnknownBoundsShortLoopFailedSpeculation(0, 100, Long.MAX_VALUE); + if (res != 100) { + throw new RuntimeException("incorrect result: " + res); + } + } + wb.enqueueMethodForCompilation(info.getTest(), CompilerWhiteBoxTest.COMP_LEVEL_FULL_OPTIMIZATION); + if (!wb.isMethodCompiled(info.getTest())) { + throw new RuntimeException("Should be compiled now"); + } + for (int i = 0; i < 10; i++) { + int res = testLongLoopUnknownBoundsShortLoopFailedSpeculation(0, 10_000, Long.MAX_VALUE); + if (res != 10_000) { + throw new RuntimeException("incorrect result: " + res); + } + } + } else { + int res = testLongLoopUnknownBoundsShortLoopFailedSpeculation(0, 100, Long.MAX_VALUE); + if (res != 100) { + throw new RuntimeException("incorrect result: " + res); + } + } + } + + // Check IR has a loop nest when bounds are known, is short running loop but trap was taken + @Test + @IR(counts = { IRNode.COUNTED_LOOP, "1" }) + @IR(failOn = { IRNode.LOOP, IRNode.OUTER_STRIP_MINED_LOOP, IRNode.SHORT_RUNNING_LOOP_TRAP }) + public static int testLongLoopKnownBoundsShortLoopFailedSpeculation() { + return testLongLoopKnownBoundsShortLoopFailedSpeculationHelper(0, 100); + } + + @ForceInline + private static int testLongLoopKnownBoundsShortLoopFailedSpeculationHelper(long start, long stop) { + int j = 0; + for (long i = start; i < stop; i++) { + volatileField = 42; + j++; + } + return j; + } + + @Run(test = "testLongLoopKnownBoundsShortLoopFailedSpeculation") + @Warmup(1) + public static void testLongLoopKnownBoundsShortLoopFailedSpeculation_runner(RunInfo info) { + if (info.isWarmUp()) { + for (int i = 0; i < 10_0000; i++) { + int res = testLongLoopKnownBoundsShortLoopFailedSpeculationHelper(0, 100); + if (res != 100) { + throw new RuntimeException("incorrect result: " + res); + } + } + for (int i = 0; i < 10; i++) { + int res = testLongLoopKnownBoundsShortLoopFailedSpeculationHelper(0, 10_000); + if (res != 10_000) { + throw new RuntimeException("incorrect result: " + res); + } + } + for (int i = 0; i < 10_0000; i++) { + int res = testLongLoopKnownBoundsShortLoopFailedSpeculation(); + if (res != 100) { + throw new RuntimeException("incorrect result: " + res); + } + } + } else { + int res = testLongLoopKnownBoundsShortLoopFailedSpeculation(); + if (res != 100) { + throw new RuntimeException("incorrect result: " + res); + } + } + } + + // Check range check can be eliminated by predication + @Test + @IR(counts = { IRNode.PREDICATE_TRAP, "1" }) + @IR(failOn = { IRNode.COUNTED_LOOP, IRNode.LOOP, IRNode.OUTER_STRIP_MINED_LOOP, IRNode.SHORT_RUNNING_LOOP_TRAP }) + public static void testLongLoopConstantBoundsPredication(long range) { + for (long i = 0; i < 100; i++) { + Objects.checkIndex(i, range); + } + } + + @Run(test = "testLongLoopConstantBoundsPredication") + public static void testLongLoopConstantBoundsPredication_runner() { + testLongLoopConstantBoundsPredication(100); + } + + @Test + @IR(counts = { IRNode.SHORT_RUNNING_LOOP_TRAP, "1", IRNode.PREDICATE_TRAP, "1" }) + @IR(failOn = { IRNode.COUNTED_LOOP, IRNode.LOOP, IRNode.OUTER_STRIP_MINED_LOOP }) + public static void testLongLoopUnknownBoundsShortLoopPredication(long start, long stop, long range) { + for (long i = start; i < stop; i++) { + Objects.checkIndex(i, range); + } + } + + @Run(test = "testLongLoopUnknownBoundsShortLoopPredication") + @Warmup(10_000) + public static void testLongLoopUnknownBoundsShortLoopPredication_runner() { + testLongLoopUnknownBoundsShortLoopPredication(0, 100, 100); + } + + // If scale too large, transformation can't happen + static final long veryLargeScale = Integer.MAX_VALUE / 99; + @Test + @IR(counts = { IRNode.LOOP, "1", IRNode.PREDICATE_TRAP, "2"}) + @IR(failOn = { IRNode.COUNTED_LOOP, IRNode.OUTER_STRIP_MINED_LOOP, IRNode.SHORT_RUNNING_LOOP_TRAP }) + public static void testLongLoopConstantBoundsLargeScale(long range) { + for (long i = 0; i < 100; i++) { + Objects.checkIndex(veryLargeScale * i, range); + } + } + + @Run(test = "testLongLoopConstantBoundsLargeScale") + public static void testLongLoopConstantBoundsLargeScale_runner() { + testLongLoopConstantBoundsLargeScale(veryLargeScale * 100); + } + + @Test + @IR(counts = { IRNode.LOOP, "1", IRNode.PREDICATE_TRAP, "2"}) + @IR(failOn = { IRNode.COUNTED_LOOP, IRNode.OUTER_STRIP_MINED_LOOP, IRNode.SHORT_RUNNING_LOOP_TRAP }) + public static void testLongLoopUnknownBoundsShortLoopLargeScale(long start, long stop, long range) { + for (long i = start; i < stop; i++) { + Objects.checkIndex(veryLargeScale * i, range); + } + } + + @Run(test = "testLongLoopUnknownBoundsShortLoopLargeScale") + @Warmup(10_000) + public static void testLongLoopUnknownBoundsShortLoopLargeScale_runner() { + testLongLoopUnknownBoundsShortLoopLargeScale(0, 100, veryLargeScale * 100); + } + + // Check IR only has a counted loop when bounds are known and loop run for a short time (int loop case) + @Test + @IR(counts = { IRNode.COUNTED_LOOP, "1", IRNode.PREDICATE_TRAP, "1" }) + @IR(failOn = { IRNode.LOOP, IRNode.OUTER_STRIP_MINED_LOOP, IRNode.SHORT_RUNNING_LOOP_TRAP }) + public static void testIntLoopConstantBoundsShortLoop1(long range) { + for (int i = 0; i < 100; i++) { + Objects.checkIndex(i, range); + volatileField = 42; + } + } + + @Run(test = "testIntLoopConstantBoundsShortLoop1") + public static void testIntLoopConstantBoundsShortLoop1_runner() { + testIntLoopConstantBoundsShortLoop1(100); + } + + // Check IR only has a counted loop when bounds are unknown but profile reports a short running loop (int loop case) + @Test + @IR(counts = { IRNode.COUNTED_LOOP, "1", IRNode.SHORT_RUNNING_LOOP_TRAP, "1", IRNode.PREDICATE_TRAP, "1", IRNode.OUTER_STRIP_MINED_LOOP, "1" }) + @IR(failOn = { IRNode.LOOP }) + public static void testIntLoopUnknownBoundsShortLoop(int start, int stop, long range) { + for (int i = start; i < stop; i++) { + Objects.checkIndex(i, range); + volatileField = 42; + } + } + + @Run(test = "testIntLoopUnknownBoundsShortLoop") + @Warmup(10_000) + public static void testIntLoopUnknownBoundsShortLoop_runner() { + testIntLoopUnknownBoundsShortLoop(0, 100, 100); + } + + // Same with unswitched loop + @Test + @IR(counts = { IRNode.COUNTED_LOOP, "2", IRNode.SHORT_RUNNING_LOOP_TRAP, "1", IRNode.PREDICATE_TRAP, "1", IRNode.OUTER_STRIP_MINED_LOOP, "2" }) + @IR(failOn = { IRNode.LOOP }) + public static void testIntLoopUnknownBoundsShortUnswitchedLoop(int start, int stop, long range, boolean flag) { + for (int i = start; i < stop; i++) { + if (flag) { + Objects.checkIndex(i, range); + volatileField = 42; + } else { + Objects.checkIndex(i, range); + volatileField = 42; + } + } + } + + @Run(test = "testIntLoopUnknownBoundsShortUnswitchedLoop") + @Warmup(10_000) + public static void testIntLoopUnknownBoundsShortUnswitchedLoop_runner() { + testIntLoopUnknownBoundsShortUnswitchedLoop(0, 100, 100, true); + testIntLoopUnknownBoundsShortUnswitchedLoop(0, 100, 100, false); + } + + @Test + @IR(counts = { IRNode.COUNTED_LOOP, "2", IRNode.SHORT_RUNNING_LOOP_TRAP, "1", IRNode.PREDICATE_TRAP, "1", IRNode.OUTER_STRIP_MINED_LOOP, "2" }) + @IR(failOn = { IRNode.LOOP }) + public static void testLongLoopUnknownBoundsShortUnswitchedLoop(long start, long stop, long range, boolean flag) { + for (long i = start; i < stop; i++) { + if (flag) { + Objects.checkIndex(i, range); + volatileField = 42; + } else { + Objects.checkIndex(i, range); + volatileField = 42; + } + } + } + + @Run(test = "testLongLoopUnknownBoundsShortUnswitchedLoop") + @Warmup(10_000) + public static void testLongLoopUnknownBoundsShortUnswitchedLoop_runner() { + testLongLoopUnknownBoundsShortUnswitchedLoop(0, 100, 100, true); + testLongLoopUnknownBoundsShortUnswitchedLoop(0, 100, 100, false); + } + + @Test + @IR(counts = { IRNode.COUNTED_LOOP, "1", IRNode.SHORT_RUNNING_LOOP_TRAP, "1", IRNode.OUTER_STRIP_MINED_LOOP, "1" }) + @IR(failOn = { IRNode.LOOP }) + public static int testLongLoopUnknownBoundsAddLimitShortLoop(int stop1, long stop2) { + int j = 0; + for (long i = 0; i < stop1 + stop2; i++) { + volatileField = 42; + j++; + } + return j; + } + + @Run(test = "testLongLoopUnknownBoundsAddLimitShortLoop") + @Warmup(10_000) + public static void testLongLoopUnknownBoundsAddLimitShortLoop_runner() { + int res = testLongLoopUnknownBoundsAddLimitShortLoop(100, 0); + if (res != 100) { + throw new RuntimeException("incorrect result: " + res); + } + } +} diff --git a/test/hotspot/jtreg/compiler/longcountedloops/TestShortRunningLongCountedLoopPredicatesClone.java b/test/hotspot/jtreg/compiler/longcountedloops/TestShortRunningLongCountedLoopPredicatesClone.java new file mode 100644 index 00000000000..bc412eb8f89 --- /dev/null +++ b/test/hotspot/jtreg/compiler/longcountedloops/TestShortRunningLongCountedLoopPredicatesClone.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2025, Red Hat, 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. + */ + +/** + * @test + * @bug 8342692 + * @summary C2: long counted loop/long range checks: don't create loop-nest for short running loops + * @run main/othervm -XX:-TieredCompilation -XX:-UseOnStackReplacement -XX:-BackgroundCompilation -XX:LoopMaxUnroll=0 + * TestShortRunningLongCountedLoopPredicatesClone + * @run main/othervm TestShortRunningLongCountedLoopPredicatesClone + */ + +import java.util.Objects; + +// Predicate added after int counted loop is created depends on +// narrowed limit which depends on predicate added before the int +// counted loop was created: predicates need to be properly ordered. +public class TestShortRunningLongCountedLoopPredicatesClone { + public static void main(String[] args) { + A a = new A(100); + for (int i = 0; i < 20_000; i++) { + test1(a, 0); + } + } + + private static void test1(A a, long start) { + long i = start; + do { + synchronized (new Object()) {} + Objects.checkIndex(i, a.range); + i++; + } while (i < a.range); + } + + static class A { + A(long range) { + this.range = range; + } + + long range; + } +} diff --git a/test/hotspot/jtreg/compiler/longcountedloops/TestShortRunningLongCountedLoopScaleOverflow.java b/test/hotspot/jtreg/compiler/longcountedloops/TestShortRunningLongCountedLoopScaleOverflow.java new file mode 100644 index 00000000000..218d8a293d1 --- /dev/null +++ b/test/hotspot/jtreg/compiler/longcountedloops/TestShortRunningLongCountedLoopScaleOverflow.java @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2025, Red Hat, 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. + */ + +/** + * @test + * @bug 8342692 + * @summary C2: long counted loop/long range checks: don't create loop-nest for short running loops + * @run main/othervm -XX:-TieredCompilation -XX:-UseOnStackReplacement -XX:-BackgroundCompilation -XX:LoopMaxUnroll=0 + * -XX:-UseLoopPredicate -XX:-RangeCheckElimination TestShortRunningLongCountedLoopScaleOverflow + * @run main/othervm TestShortRunningLongCountedLoopScaleOverflow + */ + +import java.util.Objects; + +// When scale is large, even if loop is short running having a single +// counted loop is not possible. +public class TestShortRunningLongCountedLoopScaleOverflow { + public static void main(String[] args) { + for (int i = 0; i < 20_000; i++) { + test1(Integer.MAX_VALUE, 0); + test2(Integer.MAX_VALUE, 0, 100); + } + boolean exception = false; + try { + test1(Integer.MAX_VALUE, 10); + } catch (IndexOutOfBoundsException indexOutOfBoundsException) { + exception = true; + } + if (!exception) { + throw new RuntimeException("Expected exception not thrown"); + } + exception = false; + try { + test2(Integer.MAX_VALUE, 10, 100); + } catch (IndexOutOfBoundsException indexOutOfBoundsException) { + exception = true; + } + if (!exception) { + throw new RuntimeException("Expected exception not thrown"); + } + } + + static final long veryLargeScale = 1 << 29; + + private static void test1(long range, long j) { + Objects.checkIndex(0, range); + for (long i = 0; i < 100; i++) { + if (i == j) { + Objects.checkIndex(veryLargeScale * i, range); + } + } + } + + private static void test2(long range, long j, long stop) { + Objects.checkIndex(0, range); + for (long i = 0; i < stop; i++) { + if (i == j) { + Objects.checkIndex(veryLargeScale * i, range); + } + } + } +} diff --git a/test/hotspot/jtreg/compiler/longcountedloops/TestShortRunningLongCountedLoopVectorization.java b/test/hotspot/jtreg/compiler/longcountedloops/TestShortRunningLongCountedLoopVectorization.java new file mode 100644 index 00000000000..4238fe1073c --- /dev/null +++ b/test/hotspot/jtreg/compiler/longcountedloops/TestShortRunningLongCountedLoopVectorization.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2025, Red Hat, 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 compiler.longcountedloops; +import jdk.internal.misc.Unsafe; + +import java.util.Objects; +/* + * @test + * @bug 8342692 + * @summary C2: long counted loop/long range checks: don't create loop-nest for short running loops + * @modules java.base/jdk.internal.misc + * @run main/othervm -XX:-BackgroundCompilation compiler.longcountedloops.TestShortRunningLongCountedLoopVectorization + */ + +public class TestShortRunningLongCountedLoopVectorization { + private static final Unsafe UNSAFE = Unsafe.getUnsafe(); + private static volatile int volatileField; + + public static void main(String[] args) { + for (int i = 0; i < 20_000; i++) { + test1(); + } + } + + static int size = 1024; + static long longSize = size; + static int[] intArray = new int[size]; + + public static void test1() { + boolean doIt = true; + int localSize = Integer.max(Integer.min(size, 10000), 0); + int i = 0; + while (true) { + synchronized (new Object()) {}; + if (i >= localSize) { + break; + } + if (doIt) { + volatileField = 42; + doIt = false; + } + long j = Objects.checkIndex(i, longSize); + UNSAFE.putInt(intArray, Unsafe.ARRAY_INT_BASE_OFFSET + j * Unsafe.ARRAY_INT_INDEX_SCALE, 42); + i++; + } + } +}; diff --git a/test/hotspot/jtreg/compiler/longcountedloops/TestStressShortRunningLongCountedLoop.java b/test/hotspot/jtreg/compiler/longcountedloops/TestStressShortRunningLongCountedLoop.java new file mode 100644 index 00000000000..2e04e13d233 --- /dev/null +++ b/test/hotspot/jtreg/compiler/longcountedloops/TestStressShortRunningLongCountedLoop.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2025, Red Hat, 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 compiler.longcountedloops; + +import compiler.lib.ir_framework.*; + +/* + * @test + * @bug 8342692 + * @summary C2: long counted loop/long range checks: don't create loop-nest for short running loops + * @library /test/lib / + * @run driver compiler.longcountedloops.TestStressShortRunningLongCountedLoop + */ + +public class TestStressShortRunningLongCountedLoop { + private static volatile int volatileField; + + public static void main(String[] args) { + TestFramework.runWithFlags("-XX:LoopMaxUnroll=0", "-XX:+IgnoreUnrecognizedVMOptions", "-XX:+StressShortRunningLongLoop"); + TestFramework.runWithFlags("-XX:LoopMaxUnroll=0", "-XX:+IgnoreUnrecognizedVMOptions", "-XX:-StressShortRunningLongLoop"); + } + + @Test + @IR(applyIf = { "StressShortRunningLongLoop", "true" }, counts = { IRNode.COUNTED_LOOP, "1", IRNode.SHORT_RUNNING_LOOP_TRAP, "1", IRNode.OUTER_STRIP_MINED_LOOP, "1" }) + @IR(applyIf = { "StressShortRunningLongLoop", "true" }, failOn = { IRNode.LOOP }) + @IR(applyIf = { "StressShortRunningLongLoop", "false" }, counts = { IRNode.COUNTED_LOOP, "1", IRNode.LOOP, "1", IRNode.OUTER_STRIP_MINED_LOOP, "1" }) + @IR(applyIf = { "StressShortRunningLongLoop", "false" }, failOn = { IRNode.SHORT_RUNNING_LOOP_TRAP }) + public static int testLongLoopUnknownBoundsShortLoop(long start, long stop) { + int j = 0; + for (long i = start; i < stop; i++) { + volatileField = 42; + j++; + } + return j; + } + + @Run(test = "testLongLoopUnknownBoundsShortLoop") + @Warmup(0) + public static void testLongLoopUnknownBoundsShortLoop_runner() { + int res = testLongLoopUnknownBoundsShortLoop(0, 100); + if (res != 100) { + throw new RuntimeException("incorrect result: " + res); + } + } +} diff --git a/test/hotspot/jtreg/compiler/loopopts/superword/TestMemorySegment.java b/test/hotspot/jtreg/compiler/loopopts/superword/TestMemorySegment.java index b4003cc5e73..7ecc14e8980 100644 --- a/test/hotspot/jtreg/compiler/loopopts/superword/TestMemorySegment.java +++ b/test/hotspot/jtreg/compiler/loopopts/superword/TestMemorySegment.java @@ -47,6 +47,23 @@ import java.lang.foreign.*; * @run driver compiler.loopopts.superword.TestMemorySegment ByteArray AlignVector */ +/* + * @test id=byte-array-NoShortRunningLongLoop + * @bug 8329273 8342692 + * @summary Test vectorization of loops over MemorySegment + * @library /test/lib / + * @run driver compiler.loopopts.superword.TestMemorySegment ByteArray NoShortRunningLongLoop + */ + +/* + * @test id=byte-array-AlignVector-NoShortRunningLongLoop + * @bug 8329273 8348263 8342692 + * @summary Test vectorization of loops over MemorySegment + * @library /test/lib / + * @run driver compiler.loopopts.superword.TestMemorySegment ByteArray AlignVector NoShortRunningLongLoop + */ + + /* * @test id=char-array * @bug 8329273 @@ -172,6 +189,13 @@ public class TestMemorySegment { public static void main(String[] args) { TestFramework framework = new TestFramework(TestMemorySegmentImpl.class); framework.addFlags("-DmemorySegmentProviderNameForTestVM=" + args[0]); + for (int i = 1; i < args.length; i++) { + String tag = args[i]; + switch (tag) { + case "AlignVector" -> framework.addFlags("-XX:+AlignVector"); + case "NoShortRunningLongLoop" -> framework.addFlags("-XX:-ShortRunningLongLoop"); + } + } if (args.length > 1 && args[1].equals("AlignVector")) { framework.addFlags("-XX:+AlignVector"); } @@ -777,6 +801,13 @@ class TestMemorySegmentImpl { @IR(counts = {IRNode.LOAD_VECTOR_I, "= 0", IRNode.ADD_VI, "= 0", IRNode.STORE_VECTOR, "= 0"}, + applyIfAnd = { "ShortRunningLongLoop", "false", "AlignVector", "false" }, + applyIfPlatform = {"64-bit", "true"}, + applyIfCPUFeatureOr = {"sse4.1", "true", "asimd", "true", "rvv", "true"}) + @IR(counts = {IRNode.LOAD_VECTOR_I, "> 0", + IRNode.ADD_VI, "> 0", + IRNode.STORE_VECTOR, "> 0"}, + applyIfAnd = { "ShortRunningLongLoop", "true", "AlignVector", "false" }, applyIfPlatform = {"64-bit", "true"}, applyIfCPUFeatureOr = {"sse4.1", "true", "asimd", "true", "rvv", "true"}) // FAILS: invariants are sorted differently, because of differently inserted Cast. @@ -795,6 +826,13 @@ class TestMemorySegmentImpl { @IR(counts = {IRNode.LOAD_VECTOR_I, "= 0", IRNode.ADD_VI, "= 0", IRNode.STORE_VECTOR, "= 0"}, + applyIfAnd = { "ShortRunningLongLoop", "false", "AlignVector", "false" }, + applyIfPlatform = {"64-bit", "true"}, + applyIfCPUFeatureOr = {"sse4.1", "true", "asimd", "true", "rvv", "true"}) + @IR(counts = {IRNode.LOAD_VECTOR_I, "> 0", + IRNode.ADD_VI, "> 0", + IRNode.STORE_VECTOR, "> 0"}, + applyIfAnd = { "ShortRunningLongLoop", "true", "AlignVector", "false" }, applyIfPlatform = {"64-bit", "true"}, applyIfCPUFeatureOr = {"sse4.1", "true", "asimd", "true", "rvv", "true"}) // FAILS: invariants are sorted differently, because of differently inserted Cast. diff --git a/test/micro/org/openjdk/bench/java/lang/foreign/HeapMismatchManualLoopTest.java b/test/micro/org/openjdk/bench/java/lang/foreign/HeapMismatchManualLoopTest.java new file mode 100644 index 00000000000..d16f4c874b6 --- /dev/null +++ b/test/micro/org/openjdk/bench/java/lang/foreign/HeapMismatchManualLoopTest.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * 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.java.lang.foreign; + +import org.openjdk.jmh.annotations.*; + +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.lang.foreign.ValueLayout; +import java.nio.ByteBuffer; +import java.util.Arrays; +import java.util.concurrent.TimeUnit; +import jdk.internal.misc.Unsafe; +import java.util.Objects; + +@BenchmarkMode(Mode.AverageTime) +@Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS) +@Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS) +@State(org.openjdk.jmh.annotations.Scope.Thread) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@Fork(value = 3, jvmArgs = { "--add-opens=java.base/jdk.internal.misc=ALL-UNNAMED" }) +public class HeapMismatchManualLoopTest { + + @Param({"4", "8", "16", "32", "64", "128"}) + public int ELEM_SIZE; + + static final Unsafe unsafe = Utils.unsafe; + + byte[] srcArray; + byte[] dstArray; + MemorySegment srcSegment; + MemorySegment dstSegment; + ByteBuffer srcBuffer; + ByteBuffer dstBuffer; + long srcByteSize; + long dstByteSize; + + @Setup + public void setup() { + srcArray = new byte[ELEM_SIZE]; + dstArray = new byte[ELEM_SIZE]; + srcSegment = MemorySegment.ofArray(srcArray); + dstSegment = MemorySegment.ofArray(dstArray); + srcBuffer = ByteBuffer.wrap(srcArray); + dstBuffer = ByteBuffer.wrap(dstArray); + srcByteSize = ELEM_SIZE; + dstByteSize = ELEM_SIZE; + } + + @Benchmark + @OutputTimeUnit(TimeUnit.NANOSECONDS) + public int array_mismatch() { + for (int i = 0; i < srcArray.length ; i++) { + if (srcArray[i] != dstArray[i]) { + return i; + } + } + return -1; + } + + @Benchmark + @OutputTimeUnit(TimeUnit.NANOSECONDS) + public long segment_mismatch() { + for (long i = 0; i < srcSegment.byteSize() ; i++) { + if (srcSegment.get(ValueLayout.JAVA_BYTE, i) != dstSegment.get(ValueLayout.JAVA_BYTE, i)) { + return i; + } + } + return -1; + } + + @Benchmark + @OutputTimeUnit(TimeUnit.NANOSECONDS) + public int buffer_mismatch() { + for (int i = 0; i < srcBuffer.capacity() ; i++) { + if (srcBuffer.get(i) != dstBuffer.get(i)) { + return i; + } + } + return -1; + } + + @Benchmark + @OutputTimeUnit(TimeUnit.NANOSECONDS) + public long unsafe_mismatch() { + for (long i = 0; i < srcByteSize ; i++) { + Objects.checkIndex(i, srcByteSize); + Objects.checkIndex(i, dstByteSize); + long offset = Unsafe.ARRAY_BYTE_BASE_OFFSET + i * Unsafe.ARRAY_BYTE_INDEX_SCALE; + if (unsafe.getByte(srcArray, offset) != unsafe.getByte(dstArray, offset)) { + return i; + } + } + return -1; + } + + @Benchmark + @OutputTimeUnit(TimeUnit.NANOSECONDS) + public long unsafe_mismatch2() { + for (long i = 0; i < srcByteSize ; i++) { + long offset = Unsafe.ARRAY_BYTE_BASE_OFFSET + i * Unsafe.ARRAY_BYTE_INDEX_SCALE; + if (unsafe.getByte(srcArray, offset) != unsafe.getByte(dstArray, offset)) { + return i; + } + } + return -1; + } +} From ed70910b0f3e1b19d915ec13ac3434407d01bc5d Mon Sep 17 00:00:00 2001 From: Marc Chevalier Date: Tue, 22 Jul 2025 08:48:07 +0000 Subject: [PATCH 39/94] 8347901: C2 should remove unused leaf / pure runtime calls Reviewed-by: thartmann, vlivanov --- src/hotspot/share/opto/callnode.cpp | 53 +++++++- src/hotspot/share/opto/callnode.hpp | 29 ++++- src/hotspot/share/opto/classes.hpp | 2 + src/hotspot/share/opto/compile.cpp | 19 +++ src/hotspot/share/opto/divnode.cpp | 158 ++++++++++-------------- src/hotspot/share/opto/divnode.hpp | 35 +++--- src/hotspot/share/opto/graphKit.cpp | 29 +++-- src/hotspot/share/opto/graphKit.hpp | 1 + src/hotspot/share/opto/library_call.cpp | 2 +- src/hotspot/share/opto/macro.cpp | 15 +-- src/hotspot/share/opto/multnode.cpp | 13 ++ src/hotspot/share/opto/multnode.hpp | 46 +++++++ src/hotspot/share/opto/node.cpp | 12 +- src/hotspot/share/opto/node.hpp | 5 +- src/hotspot/share/opto/parse2.cpp | 8 +- 15 files changed, 282 insertions(+), 145 deletions(-) diff --git a/src/hotspot/share/opto/callnode.cpp b/src/hotspot/share/opto/callnode.cpp index 8d178c79639..728093851b0 100644 --- a/src/hotspot/share/opto/callnode.cpp +++ b/src/hotspot/share/opto/callnode.cpp @@ -918,7 +918,7 @@ Node *CallNode::result_cast() { } -void CallNode::extract_projections(CallProjections* projs, bool separate_io_proj, bool do_asserts) { +void CallNode::extract_projections(CallProjections* projs, bool separate_io_proj, bool do_asserts) const { projs->fallthrough_proj = nullptr; projs->fallthrough_catchproj = nullptr; projs->fallthrough_ioproj = nullptr; @@ -1303,6 +1303,57 @@ void CallLeafVectorNode::calling_convention( BasicType* sig_bt, VMRegPair *parm_ //============================================================================= +bool CallLeafPureNode::is_unused() const { + return proj_out_or_null(TypeFunc::Parms) == nullptr; +} + +bool CallLeafPureNode::is_dead() const { + return proj_out_or_null(TypeFunc::Control) == nullptr; +} + +/* We make a tuple of the global input state + TOP for the output values. + * We use this to delete a pure function that is not used: by replacing the call with + * such a tuple, we let output Proj's idealization pick the corresponding input of the + * pure call, so jumping over it, and effectively, removing the call from the graph. + * This avoids doing the graph surgery manually, but leaves that to IGVN + * that is specialized for doing that right. We need also tuple components for output + * values of the function to respect the return arity, and in case there is a projection + * that would pick an output (which shouldn't happen at the moment). + */ +TupleNode* CallLeafPureNode::make_tuple_of_input_state_and_top_return_values(const Compile* C) const { + // Transparently propagate input state but parameters + TupleNode* tuple = TupleNode::make( + tf()->range(), + in(TypeFunc::Control), + in(TypeFunc::I_O), + in(TypeFunc::Memory), + in(TypeFunc::FramePtr), + in(TypeFunc::ReturnAdr)); + + // And add TOPs for the return values + for (uint i = TypeFunc::Parms; i < tf()->range()->cnt(); i++) { + tuple->set_req(i, C->top()); + } + + return tuple; +} + +Node* CallLeafPureNode::Ideal(PhaseGVN* phase, bool can_reshape) { + if (is_dead()) { + return nullptr; + } + + // We need to wait until IGVN because during parsing, usages might still be missing + // and we would remove the call immediately. + if (can_reshape && is_unused()) { + // The result is not used. We remove the call by replacing it with a tuple, that + // is later disintegrated by the projections. + return make_tuple_of_input_state_and_top_return_values(phase->C); + } + + return CallRuntimeNode::Ideal(phase, can_reshape); +} + #ifndef PRODUCT void CallLeafNode::dump_spec(outputStream *st) const { st->print("# "); diff --git a/src/hotspot/share/opto/callnode.hpp b/src/hotspot/share/opto/callnode.hpp index 519524826bd..96706683f51 100644 --- a/src/hotspot/share/opto/callnode.hpp +++ b/src/hotspot/share/opto/callnode.hpp @@ -738,7 +738,7 @@ 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); + void extract_projections(CallProjections* projs, bool separate_io_proj, bool do_asserts = true) const; virtual uint match_edge(uint idx) const; @@ -914,6 +914,33 @@ public: #endif }; +/* A pure function call, they are assumed not to be safepoints, not to read or write memory, + * have no exception... They just take parameters, return a value without side effect. It is + * always correct to create some, or remove them, if the result is not used. + * + * They still have control input to allow easy lowering into other kind of calls that require + * a control, but this is more a technical than a moral constraint. + * + * Pure calls must have only control and data input and output: I/O, Memory and so on must be top. + * Nevertheless, pure calls can typically be expensive math operations so care must be taken + * when letting the node float. + */ +class CallLeafPureNode : public CallLeafNode { +protected: + bool is_unused() const; + bool is_dead() const; + TupleNode* make_tuple_of_input_state_and_top_return_values(const Compile* C) const; + +public: + CallLeafPureNode(const TypeFunc* tf, address addr, const char* name, + const TypePtr* adr_type) + : CallLeafNode(tf, addr, name, adr_type) { + init_class_id(Class_CallLeafPure); + } + int Opcode() const override; + Node* Ideal(PhaseGVN* phase, bool can_reshape) override; +}; + //------------------------------CallLeafNoFPNode------------------------------- // CallLeafNode, not using floating point or using it in the same manner as // the generated code diff --git a/src/hotspot/share/opto/classes.hpp b/src/hotspot/share/opto/classes.hpp index bc259eed2d1..587d5fad8f2 100644 --- a/src/hotspot/share/opto/classes.hpp +++ b/src/hotspot/share/opto/classes.hpp @@ -61,6 +61,7 @@ macro(CallDynamicJava) macro(CallJava) macro(CallLeaf) macro(CallLeafNoFP) +macro(CallLeafPure) macro(CallLeafVector) macro(CallRuntime) macro(CallStaticJava) @@ -372,6 +373,7 @@ macro(SubI) macro(SubL) macro(TailCall) macro(TailJump) +macro(Tuple) macro(MacroLogicV) macro(ThreadLocal) macro(Unlock) diff --git a/src/hotspot/share/opto/compile.cpp b/src/hotspot/share/opto/compile.cpp index edb6ddef4f4..4cab6aabbeb 100644 --- a/src/hotspot/share/opto/compile.cpp +++ b/src/hotspot/share/opto/compile.cpp @@ -3303,6 +3303,25 @@ void Compile::final_graph_reshaping_main_switch(Node* n, Final_Reshape_Counts& f case Op_Opaque1: // Remove Opaque Nodes before matching n->subsume_by(n->in(1), this); break; + case Op_CallLeafPure: { + // If the pure call is not supported, then lower to a CallLeaf. + if (!Matcher::match_rule_supported(Op_CallLeafPure)) { + CallNode* call = n->as_Call(); + CallNode* new_call = new CallLeafNode(call->tf(), call->entry_point(), + call->_name, TypeRawPtr::BOTTOM); + new_call->init_req(TypeFunc::Control, call->in(TypeFunc::Control)); + new_call->init_req(TypeFunc::I_O, C->top()); + new_call->init_req(TypeFunc::Memory, C->top()); + new_call->init_req(TypeFunc::ReturnAdr, C->top()); + new_call->init_req(TypeFunc::FramePtr, C->top()); + for (unsigned int i = TypeFunc::Parms; i < call->tf()->domain()->cnt(); i++) { + new_call->init_req(i, call->in(i)); + } + n->subsume_by(new_call, this); + } + frc.inc_call_count(); + break; + } case Op_CallStaticJava: case Op_CallJava: case Op_CallDynamicJava: diff --git a/src/hotspot/share/opto/divnode.cpp b/src/hotspot/share/opto/divnode.cpp index d4e76950503..0d1337909fb 100644 --- a/src/hotspot/share/opto/divnode.cpp +++ b/src/hotspot/share/opto/divnode.cpp @@ -42,19 +42,19 @@ #include -ModFloatingNode::ModFloatingNode(Compile* C, const TypeFunc* tf, const char* name) : CallLeafNode(tf, nullptr, name, TypeRawPtr::BOTTOM) { +ModFloatingNode::ModFloatingNode(Compile* C, const TypeFunc* tf, address addr, const char* name) : CallLeafPureNode(tf, addr, name, TypeRawPtr::BOTTOM) { add_flag(Flag_is_macro); C->add_macro_node(this); } -ModDNode::ModDNode(Compile* C, Node* a, Node* b) : ModFloatingNode(C, OptoRuntime::Math_DD_D_Type(), "drem") { +ModDNode::ModDNode(Compile* C, Node* a, Node* b) : ModFloatingNode(C, OptoRuntime::Math_DD_D_Type(), CAST_FROM_FN_PTR(address, SharedRuntime::drem), "drem") { init_req(TypeFunc::Parms + 0, a); init_req(TypeFunc::Parms + 1, C->top()); init_req(TypeFunc::Parms + 2, b); init_req(TypeFunc::Parms + 3, C->top()); } -ModFNode::ModFNode(Compile* C, Node* a, Node* b) : ModFloatingNode(C, OptoRuntime::modf_Type(), "frem") { +ModFNode::ModFNode(Compile* C, Node* a, Node* b) : ModFloatingNode(C, OptoRuntime::modf_Type(), CAST_FROM_FN_PTR(address, SharedRuntime::frem), "frem") { init_req(TypeFunc::Parms + 0, a); init_req(TypeFunc::Parms + 1, b); } @@ -1511,137 +1511,109 @@ const Type* UModLNode::Value(PhaseGVN* phase) const { return unsigned_mod_value(phase, this); } -Node* ModFNode::Ideal(PhaseGVN* phase, bool can_reshape) { - if (!can_reshape) { - return nullptr; - } - PhaseIterGVN* igvn = phase->is_IterGVN(); - - bool result_is_unused = proj_out_or_null(TypeFunc::Parms) == nullptr; - bool not_dead = proj_out_or_null(TypeFunc::Control) != nullptr; - if (result_is_unused && not_dead) { - return replace_with_con(igvn, TypeF::make(0.)); - } - - // Either input is TOP ==> the result is TOP - const Type* t1 = phase->type(dividend()); - const Type* t2 = phase->type(divisor()); - if (t1 == Type::TOP || t2 == Type::TOP) { - return phase->C->top(); - } - +const Type* ModFNode::get_result_if_constant(const Type* dividend, const Type* divisor) const { // If either number is not a constant, we know nothing. - if ((t1->base() != Type::FloatCon) || (t2->base() != Type::FloatCon)) { + if ((dividend->base() != Type::FloatCon) || (divisor->base() != Type::FloatCon)) { return nullptr; // note: x%x can be either NaN or 0 } - float f1 = t1->getf(); - float f2 = t2->getf(); - jint x1 = jint_cast(f1); // note: *(int*)&f1, not just (int)f1 - jint x2 = jint_cast(f2); + float dividend_f = dividend->getf(); + float divisor_f = divisor->getf(); + jint dividend_i = jint_cast(dividend_f); // note: *(int*)&f1, not just (int)f1 + jint divisor_i = jint_cast(divisor_f); // If either is a NaN, return an input NaN - if (g_isnan(f1)) { - return replace_with_con(igvn, t1); + if (g_isnan(dividend_f)) { + return dividend; } - if (g_isnan(f2)) { - return replace_with_con(igvn, t2); + if (g_isnan(divisor_f)) { + return divisor; } // If an operand is infinity or the divisor is +/- zero, punt. - if (!g_isfinite(f1) || !g_isfinite(f2) || x2 == 0 || x2 == min_jint) { + if (!g_isfinite(dividend_f) || !g_isfinite(divisor_f) || divisor_i == 0 || divisor_i == min_jint) { return nullptr; } // We must be modulo'ing 2 float constants. // Make sure that the sign of the fmod is equal to the sign of the dividend - jint xr = jint_cast(fmod(f1, f2)); - if ((x1 ^ xr) < 0) { + jint xr = jint_cast(fmod(dividend_f, divisor_f)); + if ((dividend_i ^ xr) < 0) { xr ^= min_jint; } - return replace_with_con(igvn, TypeF::make(jfloat_cast(xr))); + return TypeF::make(jfloat_cast(xr)); } -Node* ModDNode::Ideal(PhaseGVN* phase, bool can_reshape) { - if (!can_reshape) { - return nullptr; - } - PhaseIterGVN* igvn = phase->is_IterGVN(); - - bool result_is_unused = proj_out_or_null(TypeFunc::Parms) == nullptr; - bool not_dead = proj_out_or_null(TypeFunc::Control) != nullptr; - if (result_is_unused && not_dead) { - return replace_with_con(igvn, TypeD::make(0.)); - } - - // Either input is TOP ==> the result is TOP - const Type* t1 = phase->type(dividend()); - const Type* t2 = phase->type(divisor()); - if (t1 == Type::TOP || t2 == Type::TOP) { - return nullptr; - } - +const Type* ModDNode::get_result_if_constant(const Type* dividend, const Type* divisor) const { // If either number is not a constant, we know nothing. - if ((t1->base() != Type::DoubleCon) || (t2->base() != Type::DoubleCon)) { + if ((dividend->base() != Type::DoubleCon) || (divisor->base() != Type::DoubleCon)) { return nullptr; // note: x%x can be either NaN or 0 } - double f1 = t1->getd(); - double f2 = t2->getd(); - jlong x1 = jlong_cast(f1); // note: *(long*)&f1, not just (long)f1 - jlong x2 = jlong_cast(f2); + double dividend_d = dividend->getd(); + double divisor_d = divisor->getd(); + jlong dividend_l = jlong_cast(dividend_d); // note: *(long*)&f1, not just (long)f1 + jlong divisor_l = jlong_cast(divisor_d); // If either is a NaN, return an input NaN - if (g_isnan(f1)) { - return replace_with_con(igvn, t1); + if (g_isnan(dividend_d)) { + return dividend; } - if (g_isnan(f2)) { - return replace_with_con(igvn, t2); + if (g_isnan(divisor_d)) { + return divisor; } // If an operand is infinity or the divisor is +/- zero, punt. - if (!g_isfinite(f1) || !g_isfinite(f2) || x2 == 0 || x2 == min_jlong) { + if (!g_isfinite(dividend_d) || !g_isfinite(divisor_d) || divisor_l == 0 || divisor_l == min_jlong) { return nullptr; } // We must be modulo'ing 2 double constants. // Make sure that the sign of the fmod is equal to the sign of the dividend - jlong xr = jlong_cast(fmod(f1, f2)); - if ((x1 ^ xr) < 0) { + jlong xr = jlong_cast(fmod(dividend_d, divisor_d)); + if ((dividend_l ^ xr) < 0) { xr ^= min_jlong; } - return replace_with_con(igvn, TypeD::make(jdouble_cast(xr))); + return TypeD::make(jdouble_cast(xr)); } -Node* ModFloatingNode::replace_with_con(PhaseIterGVN* phase, const Type* con) { - Compile* C = phase->C; +Node* ModFloatingNode::Ideal(PhaseGVN* phase, bool can_reshape) { + if (can_reshape) { + PhaseIterGVN* igvn = phase->is_IterGVN(); + + // Either input is TOP ==> the result is TOP + const Type* dividend_type = phase->type(dividend()); + const Type* divisor_type = phase->type(divisor()); + if (dividend_type == Type::TOP || divisor_type == Type::TOP) { + return phase->C->top(); + } + const Type* constant_result = get_result_if_constant(dividend_type, divisor_type); + if (constant_result != nullptr) { + return make_tuple_of_input_state_and_constant_result(igvn, constant_result); + } + } + + return CallLeafPureNode::Ideal(phase, can_reshape); +} + +/* Give a tuple node for ::Ideal to return, made of the input state (control to return addr) + * and the given constant result. Idealization of projections will make sure to transparently + * propagate the input state and replace the result by the said constant. + */ +TupleNode* ModFloatingNode::make_tuple_of_input_state_and_constant_result(PhaseIterGVN* phase, const Type* con) const { Node* con_node = phase->makecon(con); - CallProjections projs; - extract_projections(&projs, false, false); - phase->replace_node(projs.fallthrough_proj, in(TypeFunc::Control)); - if (projs.fallthrough_catchproj != nullptr) { - phase->replace_node(projs.fallthrough_catchproj, in(TypeFunc::Control)); - } - if (projs.fallthrough_memproj != nullptr) { - phase->replace_node(projs.fallthrough_memproj, in(TypeFunc::Memory)); - } - if (projs.catchall_memproj != nullptr) { - phase->replace_node(projs.catchall_memproj, C->top()); - } - if (projs.fallthrough_ioproj != nullptr) { - phase->replace_node(projs.fallthrough_ioproj, in(TypeFunc::I_O)); - } - assert(projs.catchall_ioproj == nullptr, "no exceptions from floating mod"); - assert(projs.catchall_catchproj == nullptr, "no exceptions from floating mod"); - if (projs.resproj != nullptr) { - phase->replace_node(projs.resproj, con_node); - } - phase->replace_node(this, C->top()); - C->remove_macro_node(this); - disconnect_inputs(C); - return nullptr; + TupleNode* tuple = TupleNode::make( + tf()->range(), + in(TypeFunc::Control), + in(TypeFunc::I_O), + in(TypeFunc::Memory), + in(TypeFunc::FramePtr), + in(TypeFunc::ReturnAdr), + con_node); + + return tuple; } //============================================================================= diff --git a/src/hotspot/share/opto/divnode.hpp b/src/hotspot/share/opto/divnode.hpp index 127e2431b0b..b13460c89f5 100644 --- a/src/hotspot/share/opto/divnode.hpp +++ b/src/hotspot/share/opto/divnode.hpp @@ -156,40 +156,45 @@ public: }; // Base class for float and double modulus -class ModFloatingNode : public CallLeafNode { +class ModFloatingNode : public CallLeafPureNode { + TupleNode* make_tuple_of_input_state_and_constant_result(PhaseIterGVN* phase, const Type* con) const; + protected: - Node* replace_with_con(PhaseIterGVN* phase, const Type* con); + virtual Node* dividend() const = 0; + virtual Node* divisor() const = 0; + virtual const Type* get_result_if_constant(const Type* dividend, const Type* divisor) const = 0; public: - ModFloatingNode(Compile* C, const TypeFunc* tf, const char *name); + ModFloatingNode(Compile* C, const TypeFunc* tf, address addr, const char* name); + Node* Ideal(PhaseGVN* phase, bool can_reshape) override; }; // Float Modulus class ModFNode : public ModFloatingNode { private: - Node* dividend() const { return in(TypeFunc::Parms + 0); } - Node* divisor() const { return in(TypeFunc::Parms + 1); } + Node* dividend() const override { return in(TypeFunc::Parms + 0); } + Node* divisor() const override { return in(TypeFunc::Parms + 1); } + const Type* get_result_if_constant(const Type* dividend, const Type* divisor) const override; public: ModFNode(Compile* C, Node* a, Node* b); - virtual int Opcode() const; - virtual uint ideal_reg() const { return Op_RegF; } - virtual uint size_of() const { return sizeof(*this); } - virtual Node* Ideal(PhaseGVN* phase, bool can_reshape); + int Opcode() const override; + uint ideal_reg() const override { return Op_RegF; } + uint size_of() const override { return sizeof(*this); } }; // Double Modulus class ModDNode : public ModFloatingNode { private: - Node* dividend() const { return in(TypeFunc::Parms + 0); } - Node* divisor() const { return in(TypeFunc::Parms + 2); } + Node* dividend() const override { return in(TypeFunc::Parms + 0); } + Node* divisor() const override { return in(TypeFunc::Parms + 2); } + const Type* get_result_if_constant(const Type* dividend, const Type* divisor) const override; public: ModDNode(Compile* C, Node* a, Node* b); - virtual int Opcode() const; - virtual uint ideal_reg() const { return Op_RegD; } - virtual uint size_of() const { return sizeof(*this); } - virtual Node* Ideal(PhaseGVN* phase, bool can_reshape); + int Opcode() const override; + uint ideal_reg() const override { return Op_RegD; } + uint size_of() const override { return sizeof(*this); } }; //------------------------------UModINode--------------------------------------- diff --git a/src/hotspot/share/opto/graphKit.cpp b/src/hotspot/share/opto/graphKit.cpp index c58f7824c11..902968ef4d4 100644 --- a/src/hotspot/share/opto/graphKit.cpp +++ b/src/hotspot/share/opto/graphKit.cpp @@ -1880,14 +1880,20 @@ Node* GraphKit::set_results_for_java_call(CallJavaNode* call, bool separate_io_p // after the call, if this call has restricted memory effects. Node* GraphKit::set_predefined_input_for_runtime_call(SafePointNode* call, Node* narrow_mem) { // Set fixed predefined input arguments - Node* memory = reset_memory(); - Node* m = narrow_mem == nullptr ? memory : narrow_mem; - call->init_req( TypeFunc::Control, control() ); - call->init_req( TypeFunc::I_O, top() ); // does no i/o - call->init_req( TypeFunc::Memory, m ); // may gc ptrs - call->init_req( TypeFunc::FramePtr, frameptr() ); - call->init_req( TypeFunc::ReturnAdr, top() ); - return memory; + call->init_req(TypeFunc::Control, control()); + call->init_req(TypeFunc::I_O, top()); // does no i/o + call->init_req(TypeFunc::ReturnAdr, top()); + if (call->is_CallLeafPure()) { + call->init_req(TypeFunc::Memory, top()); + call->init_req(TypeFunc::FramePtr, top()); + return nullptr; + } else { + Node* memory = reset_memory(); + Node* m = narrow_mem == nullptr ? memory : narrow_mem; + call->init_req(TypeFunc::Memory, m); // may gc ptrs + call->init_req(TypeFunc::FramePtr, frameptr()); + return memory; + } } //-------------------set_predefined_output_for_runtime_call-------------------- @@ -1905,6 +1911,11 @@ void GraphKit::set_predefined_output_for_runtime_call(Node* call, const TypePtr* hook_mem) { // no i/o set_control(_gvn.transform( new ProjNode(call,TypeFunc::Control) )); + if (call->is_CallLeafPure()) { + // Pure function have only control (for now) and data output, in particular + // they don't touch the memory, so we don't want a memory proj that is set after. + return; + } if (keep_mem) { // First clone the existing memory state set_all_memory(keep_mem); @@ -2491,6 +2502,8 @@ Node* GraphKit::make_runtime_call(int flags, } else if (flags & RC_VECTOR){ uint num_bits = call_type->range()->field_at(TypeFunc::Parms)->is_vect()->length_in_bytes() * BitsPerByte; call = new CallLeafVectorNode(call_type, call_addr, call_name, adr_type, num_bits); + } else if (flags & RC_PURE) { + call = new CallLeafPureNode(call_type, call_addr, call_name, adr_type); } else { call = new CallLeafNode(call_type, call_addr, call_name, adr_type); } diff --git a/src/hotspot/share/opto/graphKit.hpp b/src/hotspot/share/opto/graphKit.hpp index 28773d75333..806a211d7e2 100644 --- a/src/hotspot/share/opto/graphKit.hpp +++ b/src/hotspot/share/opto/graphKit.hpp @@ -784,6 +784,7 @@ class GraphKit : public Phase { RC_NARROW_MEM = 16, // input memory is same as output RC_UNCOMMON = 32, // freq. expected to be like uncommon trap RC_VECTOR = 64, // CallLeafVectorNode + RC_PURE = 128, // CallLeaf is pure RC_LEAF = 0 // null value: no flags set }; diff --git a/src/hotspot/share/opto/library_call.cpp b/src/hotspot/share/opto/library_call.cpp index 266706cdfe8..818e28a3106 100644 --- a/src/hotspot/share/opto/library_call.cpp +++ b/src/hotspot/share/opto/library_call.cpp @@ -1800,7 +1800,7 @@ bool LibraryCallKit::runtime_math(const TypeFunc* call_type, address funcAddr, c Node* b = (call_type == OptoRuntime::Math_DD_D_Type()) ? argument(2) : nullptr; const TypePtr* no_memory_effects = nullptr; - Node* trig = make_runtime_call(RC_LEAF, call_type, funcAddr, funcName, + Node* trig = make_runtime_call(RC_LEAF | RC_PURE, call_type, funcAddr, funcName, no_memory_effects, a, top(), b, b ? top() : nullptr); Node* value = _gvn.transform(new ProjNode(trig, TypeFunc::Parms+0)); diff --git a/src/hotspot/share/opto/macro.cpp b/src/hotspot/share/opto/macro.cpp index 8d99ff96f09..a0b52358bac 100644 --- a/src/hotspot/share/opto/macro.cpp +++ b/src/hotspot/share/opto/macro.cpp @@ -2596,17 +2596,14 @@ bool PhaseMacroExpand::expand_macro_nodes() { switch (n->Opcode()) { case Op_ModD: case Op_ModF: { - bool is_drem = n->Opcode() == Op_ModD; CallNode* mod_macro = n->as_Call(); - CallNode* call = new CallLeafNode(mod_macro->tf(), - is_drem ? CAST_FROM_FN_PTR(address, SharedRuntime::drem) - : CAST_FROM_FN_PTR(address, SharedRuntime::frem), - is_drem ? "drem" : "frem", TypeRawPtr::BOTTOM); + CallNode* call = new CallLeafPureNode(mod_macro->tf(), mod_macro->entry_point(), + mod_macro->_name, TypeRawPtr::BOTTOM); call->init_req(TypeFunc::Control, mod_macro->in(TypeFunc::Control)); - call->init_req(TypeFunc::I_O, mod_macro->in(TypeFunc::I_O)); - call->init_req(TypeFunc::Memory, mod_macro->in(TypeFunc::Memory)); - call->init_req(TypeFunc::ReturnAdr, mod_macro->in(TypeFunc::ReturnAdr)); - call->init_req(TypeFunc::FramePtr, mod_macro->in(TypeFunc::FramePtr)); + call->init_req(TypeFunc::I_O, C->top()); + call->init_req(TypeFunc::Memory, C->top()); + call->init_req(TypeFunc::ReturnAdr, C->top()); + call->init_req(TypeFunc::FramePtr, C->top()); for (unsigned int i = 0; i < mod_macro->tf()->domain()->cnt() - TypeFunc::Parms; i++) { call->init_req(TypeFunc::Parms + i, mod_macro->in(TypeFunc::Parms + i)); } diff --git a/src/hotspot/share/opto/multnode.cpp b/src/hotspot/share/opto/multnode.cpp index 736e84315ee..f429d5daac0 100644 --- a/src/hotspot/share/opto/multnode.cpp +++ b/src/hotspot/share/opto/multnode.cpp @@ -120,6 +120,10 @@ const TypePtr *ProjNode::adr_type() const { if (bottom_type() == Type::MEMORY) { // in(0) might be a narrow MemBar; otherwise we will report TypePtr::BOTTOM Node* ctrl = in(0); + if (ctrl->Opcode() == Op_Tuple) { + // Jumping over Tuples: the i-th projection of a Tuple is the i-th input of the Tuple. + ctrl = ctrl->in(_con); + } if (ctrl == nullptr) return nullptr; // node is dead const TypePtr* adr_type = ctrl->adr_type(); #ifdef ASSERT @@ -163,6 +167,15 @@ void ProjNode::check_con() const { assert(_con < t->is_tuple()->cnt(), "ProjNode::_con must be in range"); } +//------------------------------Identity--------------------------------------- +Node* ProjNode::Identity(PhaseGVN* phase) { + if (in(0) != nullptr && in(0)->Opcode() == Op_Tuple) { + // Jumping over Tuples: the i-th projection of a Tuple is the i-th input of the Tuple. + return in(0)->in(_con); + } + return this; +} + //------------------------------Value------------------------------------------ const Type* ProjNode::Value(PhaseGVN* phase) const { if (in(0) == nullptr) return Type::TOP; diff --git a/src/hotspot/share/opto/multnode.hpp b/src/hotspot/share/opto/multnode.hpp index dff2caed38d..834dcfdca6d 100644 --- a/src/hotspot/share/opto/multnode.hpp +++ b/src/hotspot/share/opto/multnode.hpp @@ -82,6 +82,7 @@ public: virtual const Type *bottom_type() const; virtual const TypePtr *adr_type() const; virtual bool pinned() const; + virtual Node* Identity(PhaseGVN* phase); virtual const Type* Value(PhaseGVN* phase) const; virtual uint ideal_reg() const; virtual const RegMask &out_RegMask() const; @@ -105,4 +106,49 @@ public: ProjNode* other_if_proj() const; }; +/* Tuples are used to avoid manual graph surgery. When a node with Proj outputs (such as a call) + * must be removed and its ouputs replaced by its input, or some other value, we can make its + * ::Ideal return a tuple of what we want for each output: the ::Identity of output Proj will + * take care to jump over the Tuple and directly pick up the right input of the Tuple. + * + * For instance, if a function call is proven to have no side effect and return the constant 0, + * we can replace it with the 6-tuple: + * (control input, IO input, memory input, frame ptr input, return addr input, Con:0) + * all the output projections will pick up the input of the now gone call, except for the result + * projection that is replaced by 0. + * + * Using TupleNode avoid manual graph surgery and leave that to our expert surgeon: IGVN. + * Since the user of a Tuple are expected to be Proj, when creating a tuple during idealization, + * the output Proj should be enqueued for IGVN immediately after, and the tuple should not survive + * after the current IGVN. + */ +class TupleNode : public MultiNode { + const TypeTuple* _tf; + + template + static void make_helper(TupleNode* tn, uint i, Node* node, NN... nn) { + tn->set_req(i, node); + make_helper(tn, i + 1, nn...); + } + + static void make_helper(TupleNode*, uint) {} + +public: + TupleNode(const TypeTuple* tf) : MultiNode(tf->cnt()), _tf(tf) {} + + int Opcode() const override; + const Type* bottom_type() const override { return _tf; } + + /* Give as many `Node*` as you want in the `nn` pack: + * TupleNode::make(tf, input1) + * TupleNode::make(tf, input1, input2, input3, input4) + */ + template + static TupleNode* make(const TypeTuple* tf, NN... nn) { + TupleNode* tn = new TupleNode(tf); + make_helper(tn, 0, nn...); + return tn; + } +}; + #endif // SHARE_OPTO_MULTNODE_HPP diff --git a/src/hotspot/share/opto/node.cpp b/src/hotspot/share/opto/node.cpp index 8f6c67c16f5..5ecc038954d 100644 --- a/src/hotspot/share/opto/node.cpp +++ b/src/hotspot/share/opto/node.cpp @@ -2946,23 +2946,13 @@ bool Node::is_dead_loop_safe() const { bool Node::is_div_or_mod(BasicType bt) const { return Opcode() == Op_Div(bt) || Opcode() == Op_Mod(bt) || Opcode() == Op_UDiv(bt) || Opcode() == Op_UMod(bt); } -bool Node::is_pure_function() const { - switch (Opcode()) { - case Op_ModD: - case Op_ModF: - return true; - default: - return false; - } -} - // `maybe_pure_function` is assumed to be the input of `this`. This is a bit redundant, // but we already have and need maybe_pure_function in all the call sites, so // it makes it obvious that the `maybe_pure_function` is the same node as in the caller, // while it takes more thinking to realize that a locally computed in(0) must be equal to // the local in the caller. 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_pure_function(); + return Opcode() == Op_Proj && as_Proj()->_con == TypeFunc::Parms && maybe_pure_function->is_CallLeafPure(); } //============================================================================= diff --git a/src/hotspot/share/opto/node.hpp b/src/hotspot/share/opto/node.hpp index 2bbb10879f5..dc0ac474c4b 100644 --- a/src/hotspot/share/opto/node.hpp +++ b/src/hotspot/share/opto/node.hpp @@ -54,6 +54,7 @@ class CallDynamicJavaNode; class CallJavaNode; class CallLeafNode; class CallLeafNoFPNode; +class CallLeafPureNode; class CallNode; class CallRuntimeNode; class CallStaticJavaNode; @@ -673,6 +674,7 @@ public: DEFINE_CLASS_ID(CallRuntime, Call, 1) DEFINE_CLASS_ID(CallLeaf, CallRuntime, 0) DEFINE_CLASS_ID(CallLeafNoFP, CallLeaf, 0) + DEFINE_CLASS_ID(CallLeafPure, CallLeaf, 1) DEFINE_CLASS_ID(Allocate, Call, 2) DEFINE_CLASS_ID(AllocateArray, Allocate, 0) DEFINE_CLASS_ID(AbstractLock, Call, 3) @@ -907,6 +909,7 @@ public: DEFINE_CLASS_QUERY(CallJava) DEFINE_CLASS_QUERY(CallLeaf) DEFINE_CLASS_QUERY(CallLeafNoFP) + DEFINE_CLASS_QUERY(CallLeafPure) DEFINE_CLASS_QUERY(CallRuntime) DEFINE_CLASS_QUERY(CallStaticJava) DEFINE_CLASS_QUERY(Catch) @@ -1289,8 +1292,6 @@ public: bool is_div_or_mod(BasicType bt) const; - bool is_pure_function() const; - bool is_data_proj_of_pure_function(const Node* maybe_pure_function) const; //----------------- Printing, etc diff --git a/src/hotspot/share/opto/parse2.cpp b/src/hotspot/share/opto/parse2.cpp index 1a4c3c91c4f..04b6e49b620 100644 --- a/src/hotspot/share/opto/parse2.cpp +++ b/src/hotspot/share/opto/parse2.cpp @@ -1097,11 +1097,11 @@ void Parse::jump_switch_ranges(Node* key_val, SwitchRange *lo, SwitchRange *hi, Node* Parse::floating_point_mod(Node* a, Node* b, BasicType type) { assert(type == BasicType::T_FLOAT || type == BasicType::T_DOUBLE, "only float and double are floating points"); - CallNode* mod = type == BasicType::T_DOUBLE ? static_cast(new ModDNode(C, a, b)) : new ModFNode(C, a, b); + CallLeafPureNode* mod = type == BasicType::T_DOUBLE ? static_cast(new ModDNode(C, a, b)) : new ModFNode(C, a, b); - Node* prev_mem = set_predefined_input_for_runtime_call(mod); - mod = _gvn.transform(mod)->as_Call(); - set_predefined_output_for_runtime_call(mod, prev_mem, TypeRawPtr::BOTTOM); + set_predefined_input_for_runtime_call(mod); + mod = _gvn.transform(mod)->as_CallLeafPure(); + set_predefined_output_for_runtime_call(mod); Node* result = _gvn.transform(new ProjNode(mod, TypeFunc::Parms + 0)); record_for_igvn(mod); return result; From ac141c2fa1d818858e7a12a50837bb282282ecac Mon Sep 17 00:00:00 2001 From: Xiaohong Gong Date: Tue, 22 Jul 2025 09:06:02 +0000 Subject: [PATCH 40/94] 8359419: AArch64: Relax min vector length to 32-bit for short vectors Reviewed-by: aph, fgao, bkilambi, dlunden --- src/hotspot/cpu/aarch64/aarch64.ad | 37 ++++-- src/hotspot/cpu/aarch64/aarch64_vector.ad | 104 +++++++++------ src/hotspot/cpu/aarch64/aarch64_vector_ad.m4 | 118 +++++++++++------- .../cpu/aarch64/c2_MacroAssembler_aarch64.cpp | 31 +++-- .../superword/TestDependencyOffsets.java | 5 +- .../reshape/utils/TestCastMethods.java | 34 ++++- .../TestFloatConversionsVector.java | 38 +++++- .../runner/ArrayTypeConvertTest.java | 17 ++- .../vector/VectorFPtoIntCastOperations.java | 14 ++- .../bench/vm/compiler/VectorTwoShorts.java | 80 ++++++++++++ 10 files changed, 350 insertions(+), 128 deletions(-) create mode 100644 test/micro/org/openjdk/bench/vm/compiler/VectorTwoShorts.java diff --git a/src/hotspot/cpu/aarch64/aarch64.ad b/src/hotspot/cpu/aarch64/aarch64.ad index 681b14ab068..404ab8d9ba4 100644 --- a/src/hotspot/cpu/aarch64/aarch64.ad +++ b/src/hotspot/cpu/aarch64/aarch64.ad @@ -2362,17 +2362,34 @@ int Matcher::max_vector_size(const BasicType bt) { } int Matcher::min_vector_size(const BasicType bt) { - int max_size = max_vector_size(bt); - // Limit the min vector size to 8 bytes. - int size = 8 / type2aelembytes(bt); - if (bt == T_BYTE) { - // To support vector api shuffle/rearrange. - size = 4; - } else if (bt == T_BOOLEAN) { - // To support vector api load/store mask. - size = 2; + // Usually, the shortest vector length supported by AArch64 ISA and + // Vector API species is 64 bits. However, we allow 32-bit or 16-bit + // vectors in a few special cases. + int size; + switch(bt) { + case T_BOOLEAN: + // Load/store a vector mask with only 2 elements for vector types + // such as "2I/2F/2L/2D". + size = 2; + break; + case T_BYTE: + // Generate a "4B" vector, to support vector cast between "8B/16B" + // and "4S/4I/4L/4F/4D". + size = 4; + break; + case T_SHORT: + // Generate a "2S" vector, to support vector cast between "4S/8S" + // and "2I/2L/2F/2D". + size = 2; + break; + default: + // Limit the min vector length to 64-bit. + size = 8 / type2aelembytes(bt); + // The number of elements in a vector should be at least 2. + size = MAX2(size, 2); } - if (size < 2) size = 2; + + int max_size = max_vector_size(bt); return MIN2(size, max_size); } diff --git a/src/hotspot/cpu/aarch64/aarch64_vector.ad b/src/hotspot/cpu/aarch64/aarch64_vector.ad index b4e6d79347f..1b6296ddd8b 100644 --- a/src/hotspot/cpu/aarch64/aarch64_vector.ad +++ b/src/hotspot/cpu/aarch64/aarch64_vector.ad @@ -131,7 +131,7 @@ source %{ // These operations are not profitable to be vectorized on NEON, because no direct // NEON instructions support them. But the match rule support for them is profitable for // Vector API intrinsics. - if ((opcode == Op_VectorCastD2X && bt == T_INT) || + if ((opcode == Op_VectorCastD2X && (bt == T_INT || bt == T_SHORT)) || (opcode == Op_VectorCastL2X && bt == T_FLOAT) || (opcode == Op_CountLeadingZerosV && bt == T_LONG) || (opcode == Op_CountTrailingZerosV && bt == T_LONG) || @@ -189,6 +189,18 @@ source %{ return false; } break; + case Op_AddReductionVI: + case Op_AndReductionV: + case Op_OrReductionV: + case Op_XorReductionV: + case Op_MinReductionV: + case Op_MaxReductionV: + // Reductions with less than 8 bytes vector length are + // not supported. + if (length_in_bytes < 8) { + return false; + } + break; case Op_MulReductionVD: case Op_MulReductionVF: case Op_MulReductionVI: @@ -4244,8 +4256,8 @@ instruct vzeroExtStoX(vReg dst, vReg src) %{ assert(bt == T_INT || bt == T_LONG, "must be"); uint length_in_bytes = Matcher::vector_length_in_bytes(this); if (VM_Version::use_neon_for_vector(length_in_bytes)) { - // 4S to 4I - __ neon_vector_extend($dst$$FloatRegister, T_INT, length_in_bytes, + // 2S to 2I/2L, 4S to 4I + __ neon_vector_extend($dst$$FloatRegister, bt, length_in_bytes, $src$$FloatRegister, T_SHORT, /* is_unsigned */ true); } else { assert(UseSVE > 0, "must be sve"); @@ -4265,11 +4277,11 @@ instruct vzeroExtItoX(vReg dst, vReg src) %{ uint length_in_bytes = Matcher::vector_length_in_bytes(this); if (VM_Version::use_neon_for_vector(length_in_bytes)) { // 2I to 2L - __ neon_vector_extend($dst$$FloatRegister, T_LONG, length_in_bytes, + __ neon_vector_extend($dst$$FloatRegister, bt, length_in_bytes, $src$$FloatRegister, T_INT, /* is_unsigned */ true); } else { assert(UseSVE > 0, "must be sve"); - __ sve_vector_extend($dst$$FloatRegister, __ D, + __ sve_vector_extend($dst$$FloatRegister, __ elemType_to_regVariant(bt), $src$$FloatRegister, __ S, /* is_unsigned */ true); } %} @@ -4343,11 +4355,15 @@ instruct vcvtStoX_extend(vReg dst, vReg src) %{ BasicType bt = Matcher::vector_element_basic_type(this); uint length_in_bytes = Matcher::vector_length_in_bytes(this); if (VM_Version::use_neon_for_vector(length_in_bytes)) { - // 4S to 4I/4F - __ neon_vector_extend($dst$$FloatRegister, T_INT, length_in_bytes, - $src$$FloatRegister, T_SHORT); - if (bt == T_FLOAT) { - __ scvtfv(__ T4S, $dst$$FloatRegister, $dst$$FloatRegister); + if (is_floating_point_type(bt)) { + // 2S to 2F/2D, 4S to 4F + __ neon_vector_extend($dst$$FloatRegister, bt == T_FLOAT ? T_INT : T_LONG, + length_in_bytes, $src$$FloatRegister, T_SHORT); + __ scvtfv(get_arrangement(this), $dst$$FloatRegister, $dst$$FloatRegister); + } else { + // 2S to 2I/2L, 4S to 4I + __ neon_vector_extend($dst$$FloatRegister, bt, length_in_bytes, + $src$$FloatRegister, T_SHORT); } } else { assert(UseSVE > 0, "must be sve"); @@ -4371,7 +4387,7 @@ instruct vcvtItoX_narrow_neon(vReg dst, vReg src) %{ effect(TEMP_DEF dst); format %{ "vcvtItoX_narrow_neon $dst, $src" %} ins_encode %{ - // 4I to 4B/4S + // 2I to 2S, 4I to 4B/4S BasicType bt = Matcher::vector_element_basic_type(this); uint length_in_bytes = Matcher::vector_length_in_bytes(this, $src); __ neon_vector_narrow($dst$$FloatRegister, bt, @@ -4434,28 +4450,29 @@ instruct vcvtItoX(vReg dst, vReg src) %{ // VectorCastL2X -instruct vcvtLtoI_neon(vReg dst, vReg src) %{ - predicate(Matcher::vector_element_basic_type(n) == T_INT && +instruct vcvtLtoX_narrow_neon(vReg dst, vReg src) %{ + predicate((Matcher::vector_element_basic_type(n) == T_INT || + Matcher::vector_element_basic_type(n) == T_SHORT) && VM_Version::use_neon_for_vector(Matcher::vector_length_in_bytes(n->in(1)))); match(Set dst (VectorCastL2X src)); - format %{ "vcvtLtoI_neon $dst, $src" %} + format %{ "vcvtLtoX_narrow_neon $dst, $src" %} ins_encode %{ - // 2L to 2I + // 2L to 2S/2I + BasicType bt = Matcher::vector_element_basic_type(this); uint length_in_bytes = Matcher::vector_length_in_bytes(this, $src); - __ neon_vector_narrow($dst$$FloatRegister, T_INT, + __ neon_vector_narrow($dst$$FloatRegister, bt, $src$$FloatRegister, T_LONG, length_in_bytes); %} ins_pipe(pipe_slow); %} -instruct vcvtLtoI_sve(vReg dst, vReg src, vReg tmp) %{ - predicate((Matcher::vector_element_basic_type(n) == T_INT && - !VM_Version::use_neon_for_vector(Matcher::vector_length_in_bytes(n->in(1)))) || - Matcher::vector_element_basic_type(n) == T_BYTE || - Matcher::vector_element_basic_type(n) == T_SHORT); +instruct vcvtLtoX_narrow_sve(vReg dst, vReg src, vReg tmp) %{ + predicate(!VM_Version::use_neon_for_vector(Matcher::vector_length_in_bytes(n->in(1))) && + !is_floating_point_type(Matcher::vector_element_basic_type(n)) && + type2aelembytes(Matcher::vector_element_basic_type(n)) <= 4); match(Set dst (VectorCastL2X src)); effect(TEMP_DEF dst, TEMP tmp); - format %{ "vcvtLtoI_sve $dst, $src\t# KILL $tmp" %} + format %{ "vcvtLtoX_narrow_sve $dst, $src\t# KILL $tmp" %} ins_encode %{ assert(UseSVE > 0, "must be sve"); BasicType bt = Matcher::vector_element_basic_type(this); @@ -4521,10 +4538,11 @@ instruct vcvtFtoX_narrow_neon(vReg dst, vReg src) %{ effect(TEMP_DEF dst); format %{ "vcvtFtoX_narrow_neon $dst, $src" %} ins_encode %{ - // 4F to 4B/4S + // 2F to 2S, 4F to 4B/4S BasicType bt = Matcher::vector_element_basic_type(this); uint length_in_bytes = Matcher::vector_length_in_bytes(this, $src); - __ fcvtzs($dst$$FloatRegister, __ T4S, $src$$FloatRegister); + __ fcvtzs($dst$$FloatRegister, length_in_bytes == 16 ? __ T4S : __ T2S, + $src$$FloatRegister); __ neon_vector_narrow($dst$$FloatRegister, bt, $dst$$FloatRegister, T_INT, length_in_bytes); %} @@ -4590,12 +4608,14 @@ instruct vcvtFtoX(vReg dst, vReg src) %{ // VectorCastD2X instruct vcvtDtoI_neon(vReg dst, vReg src) %{ - predicate(UseSVE == 0 && Matcher::vector_element_basic_type(n) == T_INT); + predicate(UseSVE == 0 && + (Matcher::vector_element_basic_type(n) == T_INT || + Matcher::vector_element_basic_type(n) == T_SHORT)); match(Set dst (VectorCastD2X src)); effect(TEMP_DEF dst); - format %{ "vcvtDtoI_neon $dst, $src\t# 2D to 2I" %} + format %{ "vcvtDtoI_neon $dst, $src\t# 2D to 2S/2I" %} ins_encode %{ - // 2D to 2I + // 2D to 2S/2I __ ins($dst$$FloatRegister, __ D, $src$$FloatRegister, 0, 1); // We can't use fcvtzs(vector, integer) instruction here because we need // saturation arithmetic. See JDK-8276151. @@ -4603,6 +4623,10 @@ instruct vcvtDtoI_neon(vReg dst, vReg src) %{ __ fcvtzdw(rscratch2, $dst$$FloatRegister); __ fmovs($dst$$FloatRegister, rscratch1); __ mov($dst$$FloatRegister, __ S, 1, rscratch2); + if (Matcher::vector_element_basic_type(this) == T_SHORT) { + __ neon_vector_narrow($dst$$FloatRegister, T_SHORT, + $dst$$FloatRegister, T_INT, 8); + } %} ins_pipe(pipe_slow); %} @@ -4676,7 +4700,7 @@ instruct vcvtHFtoF(vReg dst, vReg src) %{ ins_encode %{ uint length_in_bytes = Matcher::vector_length_in_bytes(this); if (VM_Version::use_neon_for_vector(length_in_bytes)) { - // 4HF to 4F + // 2HF to 2F, 4HF to 4F __ fcvtl($dst$$FloatRegister, __ T4S, $src$$FloatRegister, __ T4H); } else { assert(UseSVE > 0, "must be sve"); @@ -4692,9 +4716,9 @@ instruct vcvtHFtoF(vReg dst, vReg src) %{ instruct vcvtFtoHF_neon(vReg dst, vReg src) %{ predicate(VM_Version::use_neon_for_vector(Matcher::vector_length_in_bytes(n->in(1)))); match(Set dst (VectorCastF2HF src)); - format %{ "vcvtFtoHF_neon $dst, $src\t# 4F to 4HF" %} + format %{ "vcvtFtoHF_neon $dst, $src\t# 2F/4F to 2HF/4HF" %} ins_encode %{ - // 4F to 4HF + // 2F to 2HF, 4F to 4HF __ fcvtn($dst$$FloatRegister, __ T4H, $src$$FloatRegister, __ T4S); %} ins_pipe(pipe_slow); @@ -6396,14 +6420,12 @@ instruct vpopcountI(vReg dst, vReg src) %{ } else { assert(bt == T_SHORT || bt == T_INT, "unsupported"); if (UseSVE == 0) { - assert(length_in_bytes == 8 || length_in_bytes == 16, "unsupported"); - __ cnt($dst$$FloatRegister, length_in_bytes == 16 ? __ T16B : __ T8B, - $src$$FloatRegister); - __ uaddlp($dst$$FloatRegister, length_in_bytes == 16 ? __ T16B : __ T8B, - $dst$$FloatRegister); + assert(length_in_bytes <= 16, "unsupported"); + bool isQ = length_in_bytes == 16; + __ cnt($dst$$FloatRegister, isQ ? __ T16B : __ T8B, $src$$FloatRegister); + __ uaddlp($dst$$FloatRegister, isQ ? __ T16B : __ T8B, $dst$$FloatRegister); if (bt == T_INT) { - __ uaddlp($dst$$FloatRegister, length_in_bytes == 16 ? __ T8H : __ T4H, - $dst$$FloatRegister); + __ uaddlp($dst$$FloatRegister, isQ ? __ T8H : __ T4H, $dst$$FloatRegister); } } else { __ sve_cnt($dst$$FloatRegister, __ elemType_to_regVariant(bt), @@ -6465,7 +6487,7 @@ instruct vblend_neon(vReg dst, vReg src1, vReg src2) %{ format %{ "vblend_neon $dst, $src1, $src2" %} ins_encode %{ uint length_in_bytes = Matcher::vector_length_in_bytes(this); - assert(length_in_bytes == 8 || length_in_bytes == 16, "must be"); + assert(length_in_bytes <= 16, "must be"); __ bsl($dst$$FloatRegister, length_in_bytes == 16 ? __ T16B : __ T8B, $src2$$FloatRegister, $src1$$FloatRegister); %} @@ -6852,7 +6874,7 @@ instruct vcountTrailingZeros(vReg dst, vReg src) %{ } else { assert(bt == T_SHORT || bt == T_INT || bt == T_LONG, "unsupported type"); if (UseSVE == 0) { - assert(length_in_bytes == 8 || length_in_bytes == 16, "unsupported"); + assert(length_in_bytes <= 16, "unsupported"); __ neon_reverse_bits($dst$$FloatRegister, $src$$FloatRegister, bt, /* isQ */ length_in_bytes == 16); if (bt != T_LONG) { @@ -6911,7 +6933,7 @@ instruct vreverse(vReg dst, vReg src) %{ } else { assert(bt == T_SHORT || bt == T_INT || bt == T_LONG, "unsupported type"); if (UseSVE == 0) { - assert(length_in_bytes == 8 || length_in_bytes == 16, "unsupported"); + assert(length_in_bytes <= 16, "unsupported"); __ neon_reverse_bits($dst$$FloatRegister, $src$$FloatRegister, bt, /* isQ */ length_in_bytes == 16); } else { @@ -6947,7 +6969,7 @@ instruct vreverseBytes(vReg dst, vReg src) %{ BasicType bt = Matcher::vector_element_basic_type(this); uint length_in_bytes = Matcher::vector_length_in_bytes(this); if (VM_Version::use_neon_for_vector(length_in_bytes)) { - assert(length_in_bytes == 8 || length_in_bytes == 16, "unsupported"); + assert(length_in_bytes <= 16, "unsupported"); if (bt == T_BYTE) { if ($dst$$FloatRegister != $src$$FloatRegister) { __ orr($dst$$FloatRegister, length_in_bytes == 16 ? __ T16B : __ T8B, diff --git a/src/hotspot/cpu/aarch64/aarch64_vector_ad.m4 b/src/hotspot/cpu/aarch64/aarch64_vector_ad.m4 index cc07e0e4076..efefbf692bd 100644 --- a/src/hotspot/cpu/aarch64/aarch64_vector_ad.m4 +++ b/src/hotspot/cpu/aarch64/aarch64_vector_ad.m4 @@ -121,7 +121,7 @@ source %{ // These operations are not profitable to be vectorized on NEON, because no direct // NEON instructions support them. But the match rule support for them is profitable for // Vector API intrinsics. - if ((opcode == Op_VectorCastD2X && bt == T_INT) || + if ((opcode == Op_VectorCastD2X && (bt == T_INT || bt == T_SHORT)) || (opcode == Op_VectorCastL2X && bt == T_FLOAT) || (opcode == Op_CountLeadingZerosV && bt == T_LONG) || (opcode == Op_CountTrailingZerosV && bt == T_LONG) || @@ -179,6 +179,18 @@ source %{ return false; } break; + case Op_AddReductionVI: + case Op_AndReductionV: + case Op_OrReductionV: + case Op_XorReductionV: + case Op_MinReductionV: + case Op_MaxReductionV: + // Reductions with less than 8 bytes vector length are + // not supported. + if (length_in_bytes < 8) { + return false; + } + break; case Op_MulReductionVD: case Op_MulReductionVF: case Op_MulReductionVI: @@ -2502,31 +2514,31 @@ instruct reinterpret_resize_gt128b(vReg dst, vReg src, pReg ptmp, rFlagsReg cr) %} // ---------------------------- Vector zero extend -------------------------------- -dnl VECTOR_ZERO_EXTEND($1, $2, $3, $4, $5 $6, $7, ) -dnl VECTOR_ZERO_EXTEND(op_name, dst_bt, src_bt, dst_size, src_size, assertion, neon_comment) +dnl VECTOR_ZERO_EXTEND($1, $2, $3, $4, $5, ) +dnl VECTOR_ZERO_EXTEND(op_name, src_bt, src_size, assertion, neon_comment) define(`VECTOR_ZERO_EXTEND', ` instruct vzeroExt$1toX(vReg dst, vReg src) %{ match(Set dst (VectorUCast`$1'2X src)); format %{ "vzeroExt$1toX $dst, $src" %} ins_encode %{ BasicType bt = Matcher::vector_element_basic_type(this); - assert($6, "must be"); + assert($4, "must be"); uint length_in_bytes = Matcher::vector_length_in_bytes(this); if (VM_Version::use_neon_for_vector(length_in_bytes)) { - // $7 - __ neon_vector_extend($dst$$FloatRegister, $2, length_in_bytes, - $src$$FloatRegister, $3, /* is_unsigned */ true); + // $5 + __ neon_vector_extend($dst$$FloatRegister, bt, length_in_bytes, + $src$$FloatRegister, $2, /* is_unsigned */ true); } else { assert(UseSVE > 0, "must be sve"); - __ sve_vector_extend($dst$$FloatRegister, __ $4, - $src$$FloatRegister, __ $5, /* is_unsigned */ true); + __ sve_vector_extend($dst$$FloatRegister, __ elemType_to_regVariant(bt), + $src$$FloatRegister, __ $3, /* is_unsigned */ true); } %} ins_pipe(pipe_slow); %}')dnl -VECTOR_ZERO_EXTEND(B, bt, T_BYTE, elemType_to_regVariant(bt), B, bt == T_SHORT || bt == T_INT || bt == T_LONG, `4B to 4S/4I, 8B to 8S') -VECTOR_ZERO_EXTEND(S, T_INT, T_SHORT, elemType_to_regVariant(bt), H, bt == T_INT || bt == T_LONG, `4S to 4I') -VECTOR_ZERO_EXTEND(I, T_LONG, T_INT, D, S, bt == T_LONG, `2I to 2L') +VECTOR_ZERO_EXTEND(B, T_BYTE, B, bt == T_SHORT || bt == T_INT || bt == T_LONG, `4B to 4S/4I, 8B to 8S') +VECTOR_ZERO_EXTEND(S, T_SHORT, H, bt == T_INT || bt == T_LONG, `2S to 2I/2L, 4S to 4I') +VECTOR_ZERO_EXTEND(I, T_INT, S, bt == T_LONG, `2I to 2L') // ------------------------------ Vector cast ---------------------------------- @@ -2595,11 +2607,15 @@ instruct vcvtStoX_extend(vReg dst, vReg src) %{ BasicType bt = Matcher::vector_element_basic_type(this); uint length_in_bytes = Matcher::vector_length_in_bytes(this); if (VM_Version::use_neon_for_vector(length_in_bytes)) { - // 4S to 4I/4F - __ neon_vector_extend($dst$$FloatRegister, T_INT, length_in_bytes, - $src$$FloatRegister, T_SHORT); - if (bt == T_FLOAT) { - __ scvtfv(__ T4S, $dst$$FloatRegister, $dst$$FloatRegister); + if (is_floating_point_type(bt)) { + // 2S to 2F/2D, 4S to 4F + __ neon_vector_extend($dst$$FloatRegister, bt == T_FLOAT ? T_INT : T_LONG, + length_in_bytes, $src$$FloatRegister, T_SHORT); + __ scvtfv(get_arrangement(this), $dst$$FloatRegister, $dst$$FloatRegister); + } else { + // 2S to 2I/2L, 4S to 4I + __ neon_vector_extend($dst$$FloatRegister, bt, length_in_bytes, + $src$$FloatRegister, T_SHORT); } } else { assert(UseSVE > 0, "must be sve"); @@ -2623,7 +2639,7 @@ instruct vcvtItoX_narrow_neon(vReg dst, vReg src) %{ effect(TEMP_DEF dst); format %{ "vcvtItoX_narrow_neon $dst, $src" %} ins_encode %{ - // 4I to 4B/4S + // 2I to 2S, 4I to 4B/4S BasicType bt = Matcher::vector_element_basic_type(this); uint length_in_bytes = Matcher::vector_length_in_bytes(this, $src); __ neon_vector_narrow($dst$$FloatRegister, bt, @@ -2686,28 +2702,29 @@ instruct vcvtItoX(vReg dst, vReg src) %{ // VectorCastL2X -instruct vcvtLtoI_neon(vReg dst, vReg src) %{ - predicate(Matcher::vector_element_basic_type(n) == T_INT && +instruct vcvtLtoX_narrow_neon(vReg dst, vReg src) %{ + predicate((Matcher::vector_element_basic_type(n) == T_INT || + Matcher::vector_element_basic_type(n) == T_SHORT) && VM_Version::use_neon_for_vector(Matcher::vector_length_in_bytes(n->in(1)))); match(Set dst (VectorCastL2X src)); - format %{ "vcvtLtoI_neon $dst, $src" %} + format %{ "vcvtLtoX_narrow_neon $dst, $src" %} ins_encode %{ - // 2L to 2I + // 2L to 2S/2I + BasicType bt = Matcher::vector_element_basic_type(this); uint length_in_bytes = Matcher::vector_length_in_bytes(this, $src); - __ neon_vector_narrow($dst$$FloatRegister, T_INT, + __ neon_vector_narrow($dst$$FloatRegister, bt, $src$$FloatRegister, T_LONG, length_in_bytes); %} ins_pipe(pipe_slow); %} -instruct vcvtLtoI_sve(vReg dst, vReg src, vReg tmp) %{ - predicate((Matcher::vector_element_basic_type(n) == T_INT && - !VM_Version::use_neon_for_vector(Matcher::vector_length_in_bytes(n->in(1)))) || - Matcher::vector_element_basic_type(n) == T_BYTE || - Matcher::vector_element_basic_type(n) == T_SHORT); +instruct vcvtLtoX_narrow_sve(vReg dst, vReg src, vReg tmp) %{ + predicate(!VM_Version::use_neon_for_vector(Matcher::vector_length_in_bytes(n->in(1))) && + !is_floating_point_type(Matcher::vector_element_basic_type(n)) && + type2aelembytes(Matcher::vector_element_basic_type(n)) <= 4); match(Set dst (VectorCastL2X src)); effect(TEMP_DEF dst, TEMP tmp); - format %{ "vcvtLtoI_sve $dst, $src\t# KILL $tmp" %} + format %{ "vcvtLtoX_narrow_sve $dst, $src\t# KILL $tmp" %} ins_encode %{ assert(UseSVE > 0, "must be sve"); BasicType bt = Matcher::vector_element_basic_type(this); @@ -2773,10 +2790,11 @@ instruct vcvtFtoX_narrow_neon(vReg dst, vReg src) %{ effect(TEMP_DEF dst); format %{ "vcvtFtoX_narrow_neon $dst, $src" %} ins_encode %{ - // 4F to 4B/4S + // 2F to 2S, 4F to 4B/4S BasicType bt = Matcher::vector_element_basic_type(this); uint length_in_bytes = Matcher::vector_length_in_bytes(this, $src); - __ fcvtzs($dst$$FloatRegister, __ T4S, $src$$FloatRegister); + __ fcvtzs($dst$$FloatRegister, length_in_bytes == 16 ? __ T4S : __ T2S, + $src$$FloatRegister); __ neon_vector_narrow($dst$$FloatRegister, bt, $dst$$FloatRegister, T_INT, length_in_bytes); %} @@ -2842,12 +2860,14 @@ instruct vcvtFtoX(vReg dst, vReg src) %{ // VectorCastD2X instruct vcvtDtoI_neon(vReg dst, vReg src) %{ - predicate(UseSVE == 0 && Matcher::vector_element_basic_type(n) == T_INT); + predicate(UseSVE == 0 && + (Matcher::vector_element_basic_type(n) == T_INT || + Matcher::vector_element_basic_type(n) == T_SHORT)); match(Set dst (VectorCastD2X src)); effect(TEMP_DEF dst); - format %{ "vcvtDtoI_neon $dst, $src\t# 2D to 2I" %} + format %{ "vcvtDtoI_neon $dst, $src\t# 2D to 2S/2I" %} ins_encode %{ - // 2D to 2I + // 2D to 2S/2I __ ins($dst$$FloatRegister, __ D, $src$$FloatRegister, 0, 1); // We can't use fcvtzs(vector, integer) instruction here because we need // saturation arithmetic. See JDK-8276151. @@ -2855,6 +2875,10 @@ instruct vcvtDtoI_neon(vReg dst, vReg src) %{ __ fcvtzdw(rscratch2, $dst$$FloatRegister); __ fmovs($dst$$FloatRegister, rscratch1); __ mov($dst$$FloatRegister, __ S, 1, rscratch2); + if (Matcher::vector_element_basic_type(this) == T_SHORT) { + __ neon_vector_narrow($dst$$FloatRegister, T_SHORT, + $dst$$FloatRegister, T_INT, 8); + } %} ins_pipe(pipe_slow); %} @@ -2928,7 +2952,7 @@ instruct vcvtHFtoF(vReg dst, vReg src) %{ ins_encode %{ uint length_in_bytes = Matcher::vector_length_in_bytes(this); if (VM_Version::use_neon_for_vector(length_in_bytes)) { - // 4HF to 4F + // 2HF to 2F, 4HF to 4F __ fcvtl($dst$$FloatRegister, __ T4S, $src$$FloatRegister, __ T4H); } else { assert(UseSVE > 0, "must be sve"); @@ -2944,9 +2968,9 @@ instruct vcvtHFtoF(vReg dst, vReg src) %{ instruct vcvtFtoHF_neon(vReg dst, vReg src) %{ predicate(VM_Version::use_neon_for_vector(Matcher::vector_length_in_bytes(n->in(1)))); match(Set dst (VectorCastF2HF src)); - format %{ "vcvtFtoHF_neon $dst, $src\t# 4F to 4HF" %} + format %{ "vcvtFtoHF_neon $dst, $src\t# 2F/4F to 2HF/4HF" %} ins_encode %{ - // 4F to 4HF + // 2F to 2HF, 4F to 4HF __ fcvtn($dst$$FloatRegister, __ T4H, $src$$FloatRegister, __ T4S); %} ins_pipe(pipe_slow); @@ -4417,14 +4441,12 @@ instruct vpopcountI(vReg dst, vReg src) %{ } else { assert(bt == T_SHORT || bt == T_INT, "unsupported"); if (UseSVE == 0) { - assert(length_in_bytes == 8 || length_in_bytes == 16, "unsupported"); - __ cnt($dst$$FloatRegister, length_in_bytes == 16 ? __ T16B : __ T8B, - $src$$FloatRegister); - __ uaddlp($dst$$FloatRegister, length_in_bytes == 16 ? __ T16B : __ T8B, - $dst$$FloatRegister); + assert(length_in_bytes <= 16, "unsupported"); + bool isQ = length_in_bytes == 16; + __ cnt($dst$$FloatRegister, isQ ? __ T16B : __ T8B, $src$$FloatRegister); + __ uaddlp($dst$$FloatRegister, isQ ? __ T16B : __ T8B, $dst$$FloatRegister); if (bt == T_INT) { - __ uaddlp($dst$$FloatRegister, length_in_bytes == 16 ? __ T8H : __ T4H, - $dst$$FloatRegister); + __ uaddlp($dst$$FloatRegister, isQ ? __ T8H : __ T4H, $dst$$FloatRegister); } } else { __ sve_cnt($dst$$FloatRegister, __ elemType_to_regVariant(bt), @@ -4475,7 +4497,7 @@ instruct vblend_neon(vReg dst, vReg src1, vReg src2) %{ format %{ "vblend_neon $dst, $src1, $src2" %} ins_encode %{ uint length_in_bytes = Matcher::vector_length_in_bytes(this); - assert(length_in_bytes == 8 || length_in_bytes == 16, "must be"); + assert(length_in_bytes <= 16, "must be"); __ bsl($dst$$FloatRegister, length_in_bytes == 16 ? __ T16B : __ T8B, $src2$$FloatRegister, $src1$$FloatRegister); %} @@ -4851,7 +4873,7 @@ instruct vcountTrailingZeros(vReg dst, vReg src) %{ } else { assert(bt == T_SHORT || bt == T_INT || bt == T_LONG, "unsupported type"); if (UseSVE == 0) { - assert(length_in_bytes == 8 || length_in_bytes == 16, "unsupported"); + assert(length_in_bytes <= 16, "unsupported"); __ neon_reverse_bits($dst$$FloatRegister, $src$$FloatRegister, bt, /* isQ */ length_in_bytes == 16); if (bt != T_LONG) { @@ -4910,7 +4932,7 @@ instruct vreverse(vReg dst, vReg src) %{ } else { assert(bt == T_SHORT || bt == T_INT || bt == T_LONG, "unsupported type"); if (UseSVE == 0) { - assert(length_in_bytes == 8 || length_in_bytes == 16, "unsupported"); + assert(length_in_bytes <= 16, "unsupported"); __ neon_reverse_bits($dst$$FloatRegister, $src$$FloatRegister, bt, /* isQ */ length_in_bytes == 16); } else { @@ -4935,7 +4957,7 @@ instruct vreverseBytes(vReg dst, vReg src) %{ BasicType bt = Matcher::vector_element_basic_type(this); uint length_in_bytes = Matcher::vector_length_in_bytes(this); if (VM_Version::use_neon_for_vector(length_in_bytes)) { - assert(length_in_bytes == 8 || length_in_bytes == 16, "unsupported"); + assert(length_in_bytes <= 16, "unsupported"); if (bt == T_BYTE) { if ($dst$$FloatRegister != $src$$FloatRegister) { __ orr($dst$$FloatRegister, length_in_bytes == 16 ? __ T16B : __ T8B, diff --git a/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp index 914967e4009..a4ecd56af08 100644 --- a/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp @@ -1778,19 +1778,21 @@ void C2_MacroAssembler::sve_vmask_lasttrue(Register dst, BasicType bt, PRegister void C2_MacroAssembler::neon_vector_extend(FloatRegister dst, BasicType dst_bt, unsigned dst_vlen_in_bytes, FloatRegister src, BasicType src_bt, bool is_unsigned) { if (src_bt == T_BYTE) { - if (dst_bt == T_SHORT) { - // 4B/8B to 4S/8S - _xshll(is_unsigned, dst, T8H, src, T8B, 0); - } else { - // 4B to 4I - assert(dst_vlen_in_bytes == 16 && dst_bt == T_INT, "unsupported"); - _xshll(is_unsigned, dst, T8H, src, T8B, 0); + // 4B to 4S/4I, 8B to 8S + assert(dst_vlen_in_bytes == 8 || dst_vlen_in_bytes == 16, "unsupported"); + assert(dst_bt == T_SHORT || dst_bt == T_INT, "unsupported"); + _xshll(is_unsigned, dst, T8H, src, T8B, 0); + if (dst_bt == T_INT) { _xshll(is_unsigned, dst, T4S, dst, T4H, 0); } } else if (src_bt == T_SHORT) { - // 4S to 4I - assert(dst_vlen_in_bytes == 16 && dst_bt == T_INT, "unsupported"); + // 2S to 2I/2L, 4S to 4I + assert(dst_vlen_in_bytes == 8 || dst_vlen_in_bytes == 16, "unsupported"); + assert(dst_bt == T_INT || dst_bt == T_LONG, "unsupported"); _xshll(is_unsigned, dst, T4S, src, T4H, 0); + if (dst_bt == T_LONG) { + _xshll(is_unsigned, dst, T2D, dst, T2S, 0); + } } else if (src_bt == T_INT) { // 2I to 2L assert(dst_vlen_in_bytes == 16 && dst_bt == T_LONG, "unsupported"); @@ -1810,18 +1812,21 @@ void C2_MacroAssembler::neon_vector_narrow(FloatRegister dst, BasicType dst_bt, assert(dst_bt == T_BYTE, "unsupported"); xtn(dst, T8B, src, T8H); } else if (src_bt == T_INT) { - // 4I to 4B/4S - assert(src_vlen_in_bytes == 16, "unsupported"); + // 2I to 2S, 4I to 4B/4S + assert(src_vlen_in_bytes == 8 || src_vlen_in_bytes == 16, "unsupported"); assert(dst_bt == T_BYTE || dst_bt == T_SHORT, "unsupported"); xtn(dst, T4H, src, T4S); if (dst_bt == T_BYTE) { xtn(dst, T8B, dst, T8H); } } else if (src_bt == T_LONG) { - // 2L to 2I + // 2L to 2S/2I assert(src_vlen_in_bytes == 16, "unsupported"); - assert(dst_bt == T_INT, "unsupported"); + assert(dst_bt == T_INT || dst_bt == T_SHORT, "unsupported"); xtn(dst, T2S, src, T2D); + if (dst_bt == T_SHORT) { + xtn(dst, T4H, dst, T4S); + } } else { ShouldNotReachHere(); } diff --git a/test/hotspot/jtreg/compiler/loopopts/superword/TestDependencyOffsets.java b/test/hotspot/jtreg/compiler/loopopts/superword/TestDependencyOffsets.java index cfa19ce385a..24a8581434f 100644 --- a/test/hotspot/jtreg/compiler/loopopts/superword/TestDependencyOffsets.java +++ b/test/hotspot/jtreg/compiler/loopopts/superword/TestDependencyOffsets.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 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 @@ -597,8 +597,7 @@ public class TestDependencyOffsets { case "byte" -> new CPUMinVectorWidth[]{new CPUMinVectorWidth(SSE4_ASIMD, 4 )}; case "char" -> new CPUMinVectorWidth[]{new CPUMinVectorWidth(SSE4, 4 ), new CPUMinVectorWidth(ASIMD, 8 )}; - case "short" -> new CPUMinVectorWidth[]{new CPUMinVectorWidth(SSE4, 4 ), - new CPUMinVectorWidth(ASIMD, 8 )}; + case "short" -> new CPUMinVectorWidth[]{new CPUMinVectorWidth(SSE4_ASIMD, 4 )}; case "int" -> new CPUMinVectorWidth[]{new CPUMinVectorWidth(SSE4_ASIMD, 8 )}; case "long" -> new CPUMinVectorWidth[]{new CPUMinVectorWidth(SSE4_ASIMD, 16)}; case "float" -> new CPUMinVectorWidth[]{new CPUMinVectorWidth(SSE4_ASIMD, 8 )}; diff --git a/test/hotspot/jtreg/compiler/vectorapi/reshape/utils/TestCastMethods.java b/test/hotspot/jtreg/compiler/vectorapi/reshape/utils/TestCastMethods.java index fac829c82e4..5a4271cc5b0 100644 --- a/test/hotspot/jtreg/compiler/vectorapi/reshape/utils/TestCastMethods.java +++ b/test/hotspot/jtreg/compiler/vectorapi/reshape/utils/TestCastMethods.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -649,18 +649,25 @@ public class TestCastMethods { makePair(SSPEC128, BSPEC64), makePair(SSPEC256, BSPEC128), makePair(SSPEC512, BSPEC256), + makePair(SSPEC64, ISPEC64), makePair(SSPEC64, ISPEC128), makePair(SSPEC128, ISPEC256), makePair(SSPEC256, ISPEC512), + makePair(SSPEC64, LSPEC128), makePair(SSPEC64, LSPEC256), + makePair(SSPEC128, LSPEC128), makePair(SSPEC128, LSPEC512), + makePair(SSPEC64, FSPEC64), makePair(SSPEC64, FSPEC128), makePair(SSPEC128, FSPEC256), makePair(SSPEC256, FSPEC512), + makePair(SSPEC64, DSPEC128), makePair(SSPEC64, DSPEC256), + makePair(SSPEC128, DSPEC128), makePair(SSPEC128, DSPEC512), makePair(ISPEC256, BSPEC64), makePair(ISPEC512, BSPEC128), + makePair(ISPEC64, SSPEC64), makePair(ISPEC128, SSPEC64), makePair(ISPEC256, SSPEC128), makePair(ISPEC512, SSPEC256), @@ -675,7 +682,9 @@ public class TestCastMethods { makePair(ISPEC128, DSPEC256), makePair(ISPEC256, DSPEC512), makePair(LSPEC512, BSPEC64), + makePair(LSPEC128, SSPEC64), makePair(LSPEC256, SSPEC64), + makePair(LSPEC128, SSPEC128), makePair(LSPEC512, SSPEC128), makePair(LSPEC128, ISPEC64), makePair(LSPEC256, ISPEC128), @@ -688,6 +697,7 @@ public class TestCastMethods { makePair(LSPEC512, DSPEC512), makePair(FSPEC256, BSPEC64), makePair(FSPEC512, BSPEC128), + makePair(FSPEC64, SSPEC64), makePair(FSPEC128, SSPEC64), makePair(FSPEC256, SSPEC128), makePair(FSPEC512, SSPEC256), @@ -702,7 +712,9 @@ public class TestCastMethods { makePair(FSPEC128, DSPEC256), makePair(FSPEC256, DSPEC512), makePair(DSPEC512, BSPEC64), + makePair(DSPEC128, SSPEC64), makePair(DSPEC256, SSPEC64), + makePair(DSPEC128, SSPEC128), makePair(DSPEC512, SSPEC128), makePair(DSPEC128, ISPEC64), makePair(DSPEC256, ISPEC128), @@ -751,14 +763,17 @@ public class TestCastMethods { makePair(BSPEC512, LSPEC256, true), makePair(BSPEC512, LSPEC512, true), + makePair(SSPEC64, ISPEC64, true), makePair(SSPEC64, ISPEC128, true), makePair(SSPEC64, ISPEC256, true), makePair(SSPEC64, ISPEC512, true), + makePair(SSPEC64, LSPEC128, true), makePair(SSPEC64, LSPEC256, true), makePair(SSPEC64, LSPEC512, true), makePair(SSPEC128, ISPEC128, true), makePair(SSPEC128, ISPEC256, true), makePair(SSPEC128, ISPEC512, true), + makePair(SSPEC128, LSPEC128, true), makePair(SSPEC128, LSPEC256, true), makePair(SSPEC128, LSPEC512, true), makePair(SSPEC256, ISPEC128, true), @@ -789,23 +804,35 @@ public class TestCastMethods { makePair(BSPEC64, FSPEC128), makePair(SSPEC64, BSPEC64), makePair(SSPEC128, BSPEC64), + makePair(SSPEC64, ISPEC64), makePair(SSPEC64, ISPEC128), + makePair(SSPEC64, LSPEC128), + makePair(SSPEC128, LSPEC128), + makePair(SSPEC64, FSPEC64), makePair(SSPEC64, FSPEC128), + makePair(SSPEC64, DSPEC128), + makePair(SSPEC128, DSPEC128), makePair(ISPEC128, BSPEC64), makePair(ISPEC128, SSPEC64), - makePair(ISPEC64, LSPEC128), + makePair(ISPEC64, SSPEC64), + makePair(ISPEC64, LSPEC128), makePair(ISPEC64, FSPEC64), makePair(ISPEC128, FSPEC128), makePair(ISPEC64, DSPEC128), + makePair(LSPEC128, SSPEC64), + makePair(LSPEC128, SSPEC128), makePair(LSPEC128, ISPEC64), makePair(LSPEC128, FSPEC64), makePair(LSPEC128, DSPEC128), makePair(FSPEC128, BSPEC64), + makePair(FSPEC64, SSPEC64), makePair(FSPEC128, SSPEC64), makePair(FSPEC64, ISPEC64), makePair(FSPEC128, ISPEC128), makePair(FSPEC64, LSPEC128), makePair(FSPEC64, DSPEC128), + makePair(DSPEC128, SSPEC64), + makePair(DSPEC128, SSPEC128), makePair(DSPEC128, ISPEC64), makePair(DSPEC128, LSPEC128), makePair(DSPEC128, FSPEC64), @@ -816,8 +843,11 @@ public class TestCastMethods { makePair(BSPEC128, SSPEC64, true), makePair(BSPEC128, SSPEC128, true), makePair(BSPEC128, ISPEC128, true), + makePair(SSPEC64, ISPEC64, true), makePair(SSPEC64, ISPEC128, true), + makePair(SSPEC64, LSPEC128, true), makePair(SSPEC128, ISPEC128, true), + makePair(SSPEC128, LSPEC128, true), makePair(ISPEC64, LSPEC128, true) ); } diff --git a/test/hotspot/jtreg/compiler/vectorization/TestFloatConversionsVector.java b/test/hotspot/jtreg/compiler/vectorization/TestFloatConversionsVector.java index f777206bab5..482dcf934c5 100644 --- a/test/hotspot/jtreg/compiler/vectorization/TestFloatConversionsVector.java +++ b/test/hotspot/jtreg/compiler/vectorization/TestFloatConversionsVector.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -84,10 +84,13 @@ public class TestFloatConversionsVector { } @Test + @IR(counts = {IRNode.VECTOR_CAST_F2HF, IRNode.VECTOR_SIZE_2, "> 0"}, + applyIfOr = {"UseCompactObjectHeaders", "false", "AlignVector", "false"}, + applyIfCPUFeature = {"asimd", "true"}) public void test_float_float16_short_vector(short[] sout, float[] finp) { - for (int i = 0; i < finp.length; i+= 4) { - sout[i+0] = Float.floatToFloat16(finp[i+0]); - sout[i+1] = Float.floatToFloat16(finp[i+1]); + for (int i = 0; i < finp.length; i += 4) { + sout[i] = Float.floatToFloat16(finp[i]); + sout[i + 1] = Float.floatToFloat16(finp[i + 1]); } } @@ -124,8 +127,9 @@ public class TestFloatConversionsVector { } // Verifying the result - for (int i = 0; i < ARRLEN; i++) { + for (int i = 0; i < ARRLEN; i += 4) { Asserts.assertEquals(Float.floatToFloat16(finp[i]), sout[i]); + Asserts.assertEquals(Float.floatToFloat16(finp[i + 1]), sout[i + 1]); } } @@ -152,7 +156,19 @@ public class TestFloatConversionsVector { } } - @Run(test = {"test_float16_float", "test_float16_float_strided"}, mode = RunMode.STANDALONE) + @Test + @IR(counts = {IRNode.VECTOR_CAST_HF2F, IRNode.VECTOR_SIZE_2, "> 0"}, + applyIfOr = {"UseCompactObjectHeaders", "false", "AlignVector", "false"}, + applyIfCPUFeature = {"asimd", "true"}) + public void test_float16_float_short_vector(float[] fout, short[] sinp) { + for (int i = 0; i < sinp.length; i += 4) { + fout[i] = Float.float16ToFloat(sinp[i]); + fout[i + 1] = Float.float16ToFloat(sinp[i + 1]); + } + } + + @Run(test = {"test_float16_float", "test_float16_float_strided", + "test_float16_float_short_vector"}, mode = RunMode.STANDALONE) public void kernel_test_float16_float() { sinp = new short[ARRLEN]; fout = new float[ARRLEN]; @@ -178,5 +194,15 @@ public class TestFloatConversionsVector { for (int i = 0; i < ARRLEN/2; i++) { Asserts.assertEquals(Float.float16ToFloat(sinp[i*2]), fout[i*2]); } + + for (int i = 0; i < ITERS; i++) { + test_float16_float_short_vector(fout, sinp); + } + + // Verifying the result + for (int i = 0; i < ARRLEN; i += 4) { + Asserts.assertEquals(Float.float16ToFloat(sinp[i]), fout[i]); + Asserts.assertEquals(Float.float16ToFloat(sinp[i + 1]), fout[i + 1]); + } } } diff --git a/test/hotspot/jtreg/compiler/vectorization/runner/ArrayTypeConvertTest.java b/test/hotspot/jtreg/compiler/vectorization/runner/ArrayTypeConvertTest.java index 11b07d57dd9..3fa636b42f7 100644 --- a/test/hotspot/jtreg/compiler/vectorization/runner/ArrayTypeConvertTest.java +++ b/test/hotspot/jtreg/compiler/vectorization/runner/ArrayTypeConvertTest.java @@ -1,6 +1,6 @@ /* * Copyright (c) 2022, 2023, Arm Limited. All rights reserved. - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 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 @@ -252,9 +252,12 @@ public class ArrayTypeConvertTest extends VectorizationTestRunner { } @Test - @IR(applyIfCPUFeatureOr = {"sve", "true", "avx2", "true", "rvv", "true"}, + @IR(applyIfCPUFeature = {"rvv", "true"}, applyIf = {"MaxVectorSize", ">=32"}, counts = {IRNode.VECTOR_CAST_S2D, IRNode.VECTOR_SIZE + "min(max_short, max_double)", ">0"}) + @IR(applyIfCPUFeatureOr = {"asimd", "true", "avx", "true"}, + applyIf = {"MaxVectorSize", ">=16"}, + counts = {IRNode.VECTOR_CAST_S2D, IRNode.VECTOR_SIZE + "min(max_short, max_double)", ">0"}) public double[] convertShortToDouble() { double[] res = new double[SIZE]; for (int i = 0; i < SIZE; i++) { @@ -374,9 +377,12 @@ public class ArrayTypeConvertTest extends VectorizationTestRunner { } @Test - @IR(applyIfCPUFeatureOr = {"sve", "true", "avx", "true", "rvv", "true"}, + @IR(applyIfCPUFeature = {"rvv", "true"}, applyIf = {"MaxVectorSize", ">=32"}, counts = {IRNode.VECTOR_CAST_D2S, IRNode.VECTOR_SIZE + "min(max_double, max_short)", ">0"}) + @IR(applyIfCPUFeatureOr = {"sve", "true", "avx", "true"}, + applyIf = {"MaxVectorSize", ">=16"}, + counts = {IRNode.VECTOR_CAST_D2S, IRNode.VECTOR_SIZE + "min(max_double, max_short)", ">0"}) public short[] convertDoubleToShort() { short[] res = new short[SIZE]; for (int i = 0; i < SIZE; i++) { @@ -386,9 +392,12 @@ public class ArrayTypeConvertTest extends VectorizationTestRunner { } @Test - @IR(applyIfCPUFeatureOr = {"sve", "true", "avx", "true", "rvv", "true"}, + @IR(applyIfCPUFeature = {"rvv", "true"}, applyIf = {"MaxVectorSize", ">=32"}, counts = {IRNode.VECTOR_CAST_D2S, IRNode.VECTOR_SIZE + "min(max_double, max_char)", ">0"}) + @IR(applyIfCPUFeatureOr = {"sve", "true", "avx", "true"}, + applyIf = {"MaxVectorSize", ">=16"}, + counts = {IRNode.VECTOR_CAST_D2S, IRNode.VECTOR_SIZE + "min(max_double, max_char)", ">0"}) public char[] convertDoubleToChar() { char[] res = new char[SIZE]; for (int i = 0; i < SIZE; i++) { diff --git a/test/micro/org/openjdk/bench/jdk/incubator/vector/VectorFPtoIntCastOperations.java b/test/micro/org/openjdk/bench/jdk/incubator/vector/VectorFPtoIntCastOperations.java index 6c3f004dcd9..6e4a57b79e5 100644 --- a/test/micro/org/openjdk/bench/jdk/incubator/vector/VectorFPtoIntCastOperations.java +++ b/test/micro/org/openjdk/bench/jdk/incubator/vector/VectorFPtoIntCastOperations.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -120,6 +120,18 @@ public class VectorFPtoIntCastOperations { } } + @Benchmark + public void microFloat64ToShort64() { + VectorSpecies ISPECIES = FloatVector.SPECIES_64; + VectorSpecies OSPECIES = ShortVector.SPECIES_64; + for (int i = 0, j = 0; i < ISPECIES.loopBound(SIZE / 2); i += ISPECIES.length(), j += OSPECIES.length()) { + FloatVector.fromArray(ISPECIES, float_arr, i) + .convertShape(VectorOperators.F2S, OSPECIES, 0) + .reinterpretAsShorts() + .intoArray(short_res, j); + } + } + @Benchmark public void microFloat128ToShort128() { VectorSpecies ISPECIES = FloatVector.SPECIES_128; diff --git a/test/micro/org/openjdk/bench/vm/compiler/VectorTwoShorts.java b/test/micro/org/openjdk/bench/vm/compiler/VectorTwoShorts.java new file mode 100644 index 00000000000..445f67552ab --- /dev/null +++ b/test/micro/org/openjdk/bench/vm/compiler/VectorTwoShorts.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2025, 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.vm.compiler; + +import org.openjdk.jmh.annotations.*; + +import java.util.concurrent.TimeUnit; +import java.util.Random; + +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@State(Scope.Thread) +@Warmup(iterations = 4, time = 2, timeUnit = TimeUnit.SECONDS) +@Measurement(iterations = 4, time = 2, timeUnit = TimeUnit.SECONDS) +@Fork(value = 3) +public class VectorTwoShorts { + @Param({"64", "128", "512", "1024"}) + public int LEN; + + private short[] sA; + private short[] sB; + private short[] sC; + + @Param("0") + private int seed; + private Random r = new Random(seed); + + @Setup + public void init() { + sA = new short[LEN]; + sB = new short[LEN]; + sC = new short[LEN]; + + for (int i = 0; i < LEN; i++) { + sA[i] = (short) r.nextInt(); + sB[i] = (short) r.nextInt(); + } + } + + @Benchmark + public void addVec2S() { + for (int i = 0; i < LEN - 3; i++) { + sC[i + 3] = (short) (sA[i] + sB[i]); + } + } + + @Benchmark + public void mulVec2S() { + for (int i = 0; i < LEN - 3; i++) { + sC[i + 3] = (short) (sA[i] * sB[i]); + } + } + + @Benchmark + public void reverseBytesVec2S() { + for (int i = 0; i < LEN - 3; i++) { + sC[i + 3] = (short) Short.reverseBytes(sA[i]); + } + } +} \ No newline at end of file From ce02836232f8c20dc5cb10f0fcf6538563d0d4bd Mon Sep 17 00:00:00 2001 From: Albert Mingkun Yang Date: Tue, 22 Jul 2025 13:29:07 +0000 Subject: [PATCH 41/94] 8363229: Parallel: Remove develop flag GCExpandToAllocateDelayMillis Reviewed-by: shade, tschatzl --- src/hotspot/share/gc/parallel/psOldGen.cpp | 3 --- src/hotspot/share/gc/shared/gc_globals.hpp | 3 --- 2 files changed, 6 deletions(-) diff --git a/src/hotspot/share/gc/parallel/psOldGen.cpp b/src/hotspot/share/gc/parallel/psOldGen.cpp index cedad78be30..44f8f6789f1 100644 --- a/src/hotspot/share/gc/parallel/psOldGen.cpp +++ b/src/hotspot/share/gc/parallel/psOldGen.cpp @@ -172,9 +172,6 @@ bool PSOldGen::expand_for_allocate(size_t word_size) { result = expand(word_size*HeapWordSize); } } - if (GCExpandToAllocateDelayMillis > 0) { - os::naked_sleep(GCExpandToAllocateDelayMillis); - } return result; } diff --git a/src/hotspot/share/gc/shared/gc_globals.hpp b/src/hotspot/share/gc/shared/gc_globals.hpp index b15518ab225..cb2ec87416f 100644 --- a/src/hotspot/share/gc/shared/gc_globals.hpp +++ b/src/hotspot/share/gc/shared/gc_globals.hpp @@ -602,9 +602,6 @@ "space parameters)") \ range(1, UINT_MAX) \ \ - develop(uintx, GCExpandToAllocateDelayMillis, 0, \ - "Delay between expansion and allocation (in milliseconds)") \ - \ product(uint, GCDrainStackTargetSize, 64, \ "Number of entries we will try to leave on the stack " \ "during parallel gc") \ From d714b5d3dad58f7f6550d7a95fdc2b3f964a4129 Mon Sep 17 00:00:00 2001 From: Sean Mullan Date: Tue, 22 Jul 2025 15:13:06 +0000 Subject: [PATCH 42/94] 8356557: Update CodeSource::implies API documentation and deprecate java.net.SocketPermission class for removal Reviewed-by: jpai --- .../classes/java/net/SocketPermission.java | 5 ++- .../classes/java/security/CodeSource.java | 23 +++++++++-- .../jdk/java/security/CodeSource/Implies.java | 39 ++++++++++++++++++- 3 files changed, 59 insertions(+), 8 deletions(-) diff --git a/src/java.base/share/classes/java/net/SocketPermission.java b/src/java.base/share/classes/java/net/SocketPermission.java index eb14099f686..dac9b1b698a 100644 --- a/src/java.base/share/classes/java/net/SocketPermission.java +++ b/src/java.base/share/classes/java/net/SocketPermission.java @@ -111,14 +111,13 @@ import static jdk.internal.util.Exceptions.formatMsg; *

* The actions string is converted to lowercase before processing. * - * @apiNote + * @deprecated * This permission cannot be used for controlling access to resources * as the Security Manager is no longer supported. * * @spec https://www.rfc-editor.org/info/rfc2732 * RFC 2732: Format for Literal IPv6 Addresses in URL's * @see java.security.Permissions - * @see SocketPermission * * * @author Marianne Mueller @@ -128,6 +127,7 @@ import static jdk.internal.util.Exceptions.formatMsg; * @serial exclude */ +@Deprecated(since = "26", forRemoval = true) public final class SocketPermission extends Permission implements java.io.Serializable { @@ -1307,6 +1307,7 @@ else its the cname? * @serial include */ +@SuppressWarnings("removal") final class SocketPermissionCollection extends PermissionCollection implements Serializable { diff --git a/src/java.base/share/classes/java/security/CodeSource.java b/src/java.base/share/classes/java/security/CodeSource.java index 821964cded5..7476b8a1d61 100644 --- a/src/java.base/share/classes/java/security/CodeSource.java +++ b/src/java.base/share/classes/java/security/CodeSource.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 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 @@ -73,6 +73,7 @@ public class CodeSource implements java.io.Serializable { private transient java.security.cert.Certificate[] certs = null; // cached SocketPermission used for matchLocation + @SuppressWarnings("removal") private transient SocketPermission sp; // for generating cert paths @@ -269,9 +270,22 @@ public class CodeSource implements java.io.Serializable { * equal to codesource's protocol, ignoring case. * *

  • If this object's host (getLocation().getHost()) is not null, - * then the SocketPermission - * constructed with this object's host must imply the - * SocketPermission constructed with codesource's host. + * then the following checks are made in order: + *
      + *
    • If this object's host was initialized with a single IP + * address then one of codesource's IP addresses must be + * equal to this object's IP address. + *
    • If this object's host is a wildcard domain (such as + * *.example.com), then codesource's canonical host name + * (the name without any preceding *) must end with this object's + * canonical host name. For example, *.example.com implies + * *.foo.example.com. + *
    • If this object's host was not initialized with a single + * IP address, then one of this object's IP addresses must equal + * one of codesource's IP addresses or this object's + * canonical host name must equal codesource's canonical + * host name. + *
    * *
  • If this object's port (getLocation().getPort()) is not * equal to -1 (that is, if a port is specified), it must equal @@ -387,6 +401,7 @@ public class CodeSource implements java.io.Serializable { * * @param that {@code CodeSource} to compare against */ + @SuppressWarnings("removal") private boolean matchLocation(CodeSource that) { if (location == null) return true; diff --git a/test/jdk/java/security/CodeSource/Implies.java b/test/jdk/java/security/CodeSource/Implies.java index b64db57e2cf..4859bd93172 100644 --- a/test/jdk/java/security/CodeSource/Implies.java +++ b/test/jdk/java/security/CodeSource/Implies.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 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 @@ -23,11 +23,12 @@ /* * @test - * @bug 4866847 7152564 7155693 + * @bug 4866847 7152564 7155693 8356557 * @summary various CodeSource.implies tests */ import java.security.CodeSource; +import java.net.InetAddress; import java.net.URL; public class Implies { @@ -48,6 +49,40 @@ public class Implies { // port check should match default port of thatURL testImplies(thisURL, thatURL, true); + thisURL = new URL("http", "204.160.241.0", "file"); + thatURL = new URL("http", "localhost", "file"); + // ip address should not imply localhost's IP address + testImplies(thisURL, thatURL, false); + + thisURL = new URL("http", "204.160.241.0", "file"); + thatURL = new URL("http", "*.example.com", "file"); + // ip address should not imply wildcarded host + testImplies(thisURL, thatURL, false); + + InetAddress ia = InetAddress.getLocalHost(); + thisURL = new URL("http", ia.getHostAddress(), "file"); + thatURL = new URL("http", ia.getHostName(), "file"); + // ip address should imply host name with same ip address + testImplies(thisURL, thatURL, true); + + thisURL = new URL("http", "*.example.com", "file"); + thatURL = new URL("http", "*.foo.example.com", "file"); + // wildcarded host name should imply wildcarded host name ending with + // same canonical host name + testImplies(thisURL, thatURL, true); + + thisURL = new URL("http", "example.com", "file"); + thatURL = new URL("http", "*.foo.example.com", "file"); + // host name should not imply wildcarded host name ending with same + // canonical host name + testImplies(thisURL, thatURL, false); + + thisURL = new URL("http", "*.example.com", "file"); + thatURL = new URL("http", "foo.example.com", "file"); + // wildcarded host name should imply host name ending with same + // canonical host name + testImplies(thisURL, thatURL, true); + System.out.println("test passed"); } From ea6674fec8702eea481afa7ca7e522cbacd53841 Mon Sep 17 00:00:00 2001 From: Chen Liang Date: Tue, 22 Jul 2025 17:25:00 +0000 Subject: [PATCH 43/94] 8315131: Clarify VarHandle set/get access on 32-bit platforms Reviewed-by: rgiulietti, mcimadamore, jrose, shade, psandoz --- .../share/classes/java/lang/foreign/MemoryLayout.java | 8 ++++---- .../share/classes/java/lang/invoke/MethodHandles.java | 7 ++++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/java.base/share/classes/java/lang/foreign/MemoryLayout.java b/src/java.base/share/classes/java/lang/foreign/MemoryLayout.java index cb51bbaf795..f55deba6e7f 100644 --- a/src/java.base/share/classes/java/lang/foreign/MemoryLayout.java +++ b/src/java.base/share/classes/java/lang/foreign/MemoryLayout.java @@ -276,10 +276,10 @@ import java.util.stream.Stream; * if {@code A >= S}. An aligned var handle is guaranteed to support the following * access modes: *
      - *
    • read write access modes for all {@code T}. On 32-bit platforms, access modes - * {@code get} and {@code set} for {@code long}, {@code double} and {@code MemorySegment} - * are supported but might lead to word tearing, as described in Section {@jls 17.7}. - * of The Java Language Specification. + *
    • read write access modes for all {@code T}. Access modes {@code get} and + * {@code set} for {@code long}, {@code double} and {@code MemorySegment} + * are supported but have no atomicity guarantee, as described in Section + * {@jls 17.7} of The Java Language Specification. *
    • atomic update access modes for {@code int}, {@code long}, * {@code float}, {@code double} and {@link MemorySegment}. * (Future major platform releases of the JDK may support additional diff --git a/src/java.base/share/classes/java/lang/invoke/MethodHandles.java b/src/java.base/share/classes/java/lang/invoke/MethodHandles.java index 3d7ba22e54e..b0f7a53ea71 100644 --- a/src/java.base/share/classes/java/lang/invoke/MethodHandles.java +++ b/src/java.base/share/classes/java/lang/invoke/MethodHandles.java @@ -4305,9 +4305,10 @@ return mh1; * If access is aligned then following access modes are supported and are * guaranteed to support atomic access: *
        - *
      • read write access modes for all {@code T}, with the exception of - * access modes {@code get} and {@code set} for {@code long} and - * {@code double} on 32-bit platforms. + *
      • read write access modes for all {@code T}. Access modes {@code get} + * and {@code set} for {@code long} and {@code double} are supported but + * have no atomicity guarantee, as described in Section {@jls 17.7} of + * The Java Language Specification. *
      • atomic update access modes for {@code int}, {@code long}, * {@code float} or {@code double}. * (Future major platform releases of the JDK may support additional From aae9902234d36049ec99a2f50934c526dd6235eb Mon Sep 17 00:00:00 2001 From: Ioi Lam Date: Tue, 22 Jul 2025 20:17:31 +0000 Subject: [PATCH 44/94] 8360555: Archive all unnamed modules in CDS full module graph Reviewed-by: coleenp, vlivanov --- src/hotspot/share/cds/cdsHeapVerifier.cpp | 4 +- src/hotspot/share/cds/cdsProtectionDomain.cpp | 1 - .../share/classfile/classLoaderDataShared.cpp | 72 +++++++++++++++++-- .../share/classfile/classLoaderDataShared.hpp | 4 ++ src/hotspot/share/classfile/moduleEntry.cpp | 44 +++++++++--- src/hotspot/share/classfile/moduleEntry.hpp | 5 +- src/hotspot/share/classfile/modules.cpp | 34 +++++---- src/hotspot/share/classfile/packageEntry.cpp | 19 +++-- src/hotspot/share/classfile/packageEntry.hpp | 3 +- src/hotspot/share/logging/logTag.hpp | 1 + src/hotspot/share/memory/universe.cpp | 11 ++- .../internal/loader/ArchivedClassLoaders.java | 8 ++- .../jdk/internal/loader/BootLoader.java | 9 ++- 13 files changed, 169 insertions(+), 46 deletions(-) diff --git a/src/hotspot/share/cds/cdsHeapVerifier.cpp b/src/hotspot/share/cds/cdsHeapVerifier.cpp index 2f3c2521b22..0da9e3f2c8d 100644 --- a/src/hotspot/share/cds/cdsHeapVerifier.cpp +++ b/src/hotspot/share/cds/cdsHeapVerifier.cpp @@ -110,11 +110,13 @@ CDSHeapVerifier::CDSHeapVerifier() : _archived_objs(0), _problems(0) ADD_EXCL("java/lang/System", "bootLayer"); // A - ADD_EXCL("java/util/Collections", "EMPTY_LIST"); // E + ADD_EXCL("java/util/Collections", "EMPTY_LIST"); // E // A dummy object used by HashSet. The value doesn't matter and it's never // tested for equality. ADD_EXCL("java/util/HashSet", "PRESENT"); // E + + ADD_EXCL("jdk/internal/loader/BootLoader", "UNNAMED_MODULE"); // A ADD_EXCL("jdk/internal/loader/BuiltinClassLoader", "packageToModule"); // A ADD_EXCL("jdk/internal/loader/ClassLoaders", "BOOT_LOADER", // A "APP_LOADER", // A diff --git a/src/hotspot/share/cds/cdsProtectionDomain.cpp b/src/hotspot/share/cds/cdsProtectionDomain.cpp index d509e8afc12..e907998fa85 100644 --- a/src/hotspot/share/cds/cdsProtectionDomain.cpp +++ b/src/hotspot/share/cds/cdsProtectionDomain.cpp @@ -120,7 +120,6 @@ PackageEntry* CDSProtectionDomain::get_package_entry_from_class(InstanceKlass* i if (CDSConfig::is_using_full_module_graph() && ik->is_shared() && pkg_entry != nullptr) { assert(MetaspaceShared::is_in_shared_metaspace(pkg_entry), "must be"); assert(!ik->defined_by_other_loaders(), "unexpected archived package entry for an unregistered class"); - assert(ik->module()->is_named(), "unexpected archived package entry for a class in an unnamed module"); return pkg_entry; } TempNewSymbol pkg_name = ClassLoader::package_from_class_name(ik->name()); diff --git a/src/hotspot/share/classfile/classLoaderDataShared.cpp b/src/hotspot/share/classfile/classLoaderDataShared.cpp index ac84a24267f..a495327864d 100644 --- a/src/hotspot/share/classfile/classLoaderDataShared.cpp +++ b/src/hotspot/share/classfile/classLoaderDataShared.cpp @@ -24,6 +24,7 @@ #include "cds/aotLogging.hpp" #include "cds/cdsConfig.hpp" +#include "cds/heapShared.hpp" #include "cds/serializeClosure.hpp" #include "classfile/classLoaderData.inline.hpp" #include "classfile/classLoaderDataShared.hpp" @@ -42,6 +43,7 @@ bool ClassLoaderDataShared::_full_module_graph_loaded = false; class ArchivedClassLoaderData { Array* _packages; Array* _modules; + ModuleEntry* _unnamed_module; void assert_valid(ClassLoaderData* loader_data) { // loader_data may be null if the boot layer has loaded no modules for the platform or @@ -52,15 +54,19 @@ class ArchivedClassLoaderData { } } public: - ArchivedClassLoaderData() : _packages(nullptr), _modules(nullptr) {} + ArchivedClassLoaderData() : _packages(nullptr), _modules(nullptr), _unnamed_module(nullptr) {} void iterate_symbols(ClassLoaderData* loader_data, MetaspaceClosure* closure); void allocate(ClassLoaderData* loader_data); void init_archived_entries(ClassLoaderData* loader_data); + ModuleEntry* unnamed_module() { + return _unnamed_module; + } void serialize(SerializeClosure* f) { f->do_ptr(&_packages); f->do_ptr(&_modules); + f->do_ptr(&_unnamed_module); } void restore(ClassLoaderData* loader_data, bool do_entries, bool do_oops); @@ -71,6 +77,8 @@ static ArchivedClassLoaderData _archived_boot_loader_data; static ArchivedClassLoaderData _archived_platform_loader_data; static ArchivedClassLoaderData _archived_system_loader_data; static ModuleEntry* _archived_javabase_moduleEntry = nullptr; +static int _platform_loader_root_index = -1; +static int _system_loader_root_index = -1; void ArchivedClassLoaderData::iterate_symbols(ClassLoaderData* loader_data, MetaspaceClosure* closure) { assert(CDSConfig::is_dumping_full_module_graph(), "must be"); @@ -78,6 +86,7 @@ void ArchivedClassLoaderData::iterate_symbols(ClassLoaderData* loader_data, Meta if (loader_data != nullptr) { loader_data->packages()->iterate_symbols(closure); loader_data->modules() ->iterate_symbols(closure); + loader_data->unnamed_module()->iterate_symbols(closure); } } @@ -91,6 +100,7 @@ void ArchivedClassLoaderData::allocate(ClassLoaderData* loader_data) { // the hashtables using these arrays. _packages = loader_data->packages()->allocate_archived_entries(); _modules = loader_data->modules() ->allocate_archived_entries(); + _unnamed_module = loader_data->unnamed_module()->allocate_archived_entry(); } } @@ -100,6 +110,7 @@ void ArchivedClassLoaderData::init_archived_entries(ClassLoaderData* loader_data if (loader_data != nullptr) { loader_data->packages()->init_archived_entries(_packages); loader_data->modules() ->init_archived_entries(_modules); + _unnamed_module->init_as_archived_entry(); } } @@ -117,6 +128,12 @@ void ArchivedClassLoaderData::restore(ClassLoaderData* loader_data, bool do_entr } if (do_oops) { modules->restore_archived_oops(loader_data, _modules); + if (_unnamed_module != nullptr) { + oop module_oop = _unnamed_module->module_oop(); + assert(module_oop != nullptr, "must be already set"); + assert(_unnamed_module == java_lang_Module::module_entry(module_oop), "must be already set"); + assert(loader_data->class_loader() == java_lang_Module::loader(module_oop), "must be set in dump time"); + } } } } @@ -127,6 +144,9 @@ void ArchivedClassLoaderData::clear_archived_oops() { for (int i = 0; i < _modules->length(); i++) { _modules->at(i)->clear_archived_oops(); } + if (_unnamed_module != nullptr) { + _unnamed_module->clear_archived_oops(); + } } } @@ -177,10 +197,15 @@ void ClassLoaderDataShared::allocate_archived_tables() { void ClassLoaderDataShared::init_archived_tables() { assert(CDSConfig::is_dumping_full_module_graph(), "must be"); + _archived_boot_loader_data.init_archived_entries (null_class_loader_data()); _archived_platform_loader_data.init_archived_entries(java_platform_loader_data_or_null()); _archived_system_loader_data.init_archived_entries (java_system_loader_data_or_null()); + _archived_javabase_moduleEntry = ModuleEntry::get_archived_entry(ModuleEntryTable::javabase_moduleEntry()); + + _platform_loader_root_index = HeapShared::append_root(SystemDictionary::java_platform_loader()); + _system_loader_root_index = HeapShared::append_root(SystemDictionary::java_system_loader()); } void ClassLoaderDataShared::serialize(SerializeClosure* f) { @@ -188,21 +213,54 @@ void ClassLoaderDataShared::serialize(SerializeClosure* f) { _archived_platform_loader_data.serialize(f); _archived_system_loader_data.serialize(f); f->do_ptr(&_archived_javabase_moduleEntry); + f->do_int(&_platform_loader_root_index); + f->do_int(&_system_loader_root_index); +} - if (f->reading() && CDSConfig::is_using_full_module_graph()) { - // Must be done before ClassLoader::create_javabase() - _archived_boot_loader_data.restore(null_class_loader_data(), true, false); - ModuleEntryTable::set_javabase_moduleEntry(_archived_javabase_moduleEntry); - aot_log_info(aot)("use_full_module_graph = true; java.base = " INTPTR_FORMAT, - p2i(_archived_javabase_moduleEntry)); +ModuleEntry* ClassLoaderDataShared::archived_boot_unnamed_module() { + if (CDSConfig::is_using_full_module_graph()) { + return _archived_boot_loader_data.unnamed_module(); + } else { + return nullptr; } } +ModuleEntry* ClassLoaderDataShared::archived_unnamed_module(ClassLoaderData* loader_data) { + ModuleEntry* archived_module = nullptr; + + if (!Universe::is_module_initialized() && CDSConfig::is_using_full_module_graph()) { + precond(_platform_loader_root_index >= 0); + precond(_system_loader_root_index >= 0); + + if (loader_data->class_loader() == HeapShared::get_root(_platform_loader_root_index)) { + archived_module = _archived_platform_loader_data.unnamed_module(); + } else if (loader_data->class_loader() == HeapShared::get_root(_system_loader_root_index)) { + archived_module = _archived_system_loader_data.unnamed_module(); + } + } + + return archived_module; +} + + void ClassLoaderDataShared::clear_archived_oops() { assert(!CDSConfig::is_using_full_module_graph(), "must be"); _archived_boot_loader_data.clear_archived_oops(); _archived_platform_loader_data.clear_archived_oops(); _archived_system_loader_data.clear_archived_oops(); + if (_platform_loader_root_index >= 0) { + HeapShared::clear_root(_platform_loader_root_index); + HeapShared::clear_root(_system_loader_root_index); + } +} + +// Must be done before ClassLoader::create_javabase() +void ClassLoaderDataShared::restore_archived_entries_for_null_class_loader_data() { + precond(CDSConfig::is_using_full_module_graph()); + _archived_boot_loader_data.restore(null_class_loader_data(), true, false); + ModuleEntryTable::set_javabase_moduleEntry(_archived_javabase_moduleEntry); + aot_log_info(aot)("use_full_module_graph = true; java.base = " INTPTR_FORMAT, + p2i(_archived_javabase_moduleEntry)); } oop ClassLoaderDataShared::restore_archived_oops_for_null_class_loader_data() { diff --git a/src/hotspot/share/classfile/classLoaderDataShared.hpp b/src/hotspot/share/classfile/classLoaderDataShared.hpp index b802f751030..4ba1c6c2196 100644 --- a/src/hotspot/share/classfile/classLoaderDataShared.hpp +++ b/src/hotspot/share/classfile/classLoaderDataShared.hpp @@ -30,6 +30,7 @@ class ClassLoaderData; class MetaspaceClosure; +class ModuleEntry; class SerializeClosure; class ClassLoaderDataShared : AllStatic { @@ -42,9 +43,12 @@ public: static void init_archived_tables(); static void serialize(SerializeClosure* f); static void clear_archived_oops(); + static void restore_archived_entries_for_null_class_loader_data(); static oop restore_archived_oops_for_null_class_loader_data(); static void restore_java_platform_loader_from_archive(ClassLoaderData* loader_data); static void restore_java_system_loader_from_archive(ClassLoaderData* loader_data); + static ModuleEntry* archived_boot_unnamed_module(); + static ModuleEntry* archived_unnamed_module(ClassLoaderData* loader_data); static bool is_full_module_graph_loaded() { return _full_module_graph_loaded; } }; diff --git a/src/hotspot/share/classfile/moduleEntry.cpp b/src/hotspot/share/classfile/moduleEntry.cpp index 65d7183dbea..7f9bf09aa51 100644 --- a/src/hotspot/share/classfile/moduleEntry.cpp +++ b/src/hotspot/share/classfile/moduleEntry.cpp @@ -29,9 +29,11 @@ #include "cds/heapShared.hpp" #include "classfile/classLoader.hpp" #include "classfile/classLoaderData.inline.hpp" +#include "classfile/classLoaderDataShared.hpp" #include "classfile/javaClasses.inline.hpp" #include "classfile/moduleEntry.hpp" #include "classfile/systemDictionary.hpp" +#include "classfile/systemDictionaryShared.hpp" #include "jni.h" #include "logging/log.hpp" #include "logging/logStream.hpp" @@ -317,6 +319,15 @@ ModuleEntry* ModuleEntry::create_unnamed_module(ClassLoaderData* cld) { // corresponding unnamed module can be found in the java.lang.ClassLoader object. oop module = java_lang_ClassLoader::unnamedModule(cld->class_loader()); +#if INCLUDE_CDS_JAVA_HEAP + ModuleEntry* archived_unnamed_module = ClassLoaderDataShared::archived_unnamed_module(cld); + if (archived_unnamed_module != nullptr) { + archived_unnamed_module->load_from_archive(cld); + archived_unnamed_module->restore_archived_oops(cld); + return archived_unnamed_module; + } +#endif + // Ensure that the unnamed module was correctly set when the class loader was constructed. // Guarantee will cause a recognizable crash if the user code has circumvented calling the ClassLoader constructor. ResourceMark rm; @@ -333,6 +344,16 @@ ModuleEntry* ModuleEntry::create_unnamed_module(ClassLoaderData* cld) { } ModuleEntry* ModuleEntry::create_boot_unnamed_module(ClassLoaderData* cld) { +#if INCLUDE_CDS_JAVA_HEAP + ModuleEntry* archived_unnamed_module = ClassLoaderDataShared::archived_boot_unnamed_module(); + if (archived_unnamed_module != nullptr) { + archived_unnamed_module->load_from_archive(cld); + // It's too early to call archived_unnamed_module->restore_archived_oops(cld). + // We will do it inside Modules::set_bootloader_unnamed_module() + return archived_unnamed_module; + } +#endif + // For the boot loader, the java.lang.Module for the unnamed module // is not known until a call to JVM_SetBootLoaderUnnamedModule is made. At // this point initially create the ModuleEntry for the unnamed module. @@ -345,7 +366,6 @@ ModuleEntry* ModuleEntry::create_boot_unnamed_module(ClassLoaderData* cld) { // This is okay because the unnamed module gets created before the ClassLoaderData // is available to other threads. ModuleEntry* ModuleEntry::new_unnamed_module_entry(Handle module_handle, ClassLoaderData* cld) { - ModuleEntry* entry = new ModuleEntry(module_handle, /*is_open*/true, /*name*/nullptr, /*version*/ nullptr, /*location*/ nullptr, cld); @@ -395,17 +415,17 @@ static int _num_archived_module_entries = 0; static int _num_inited_module_entries = 0; #endif +bool ModuleEntry::should_be_archived() const { + return SystemDictionaryShared::is_builtin_loader(loader_data()); +} + ModuleEntry* ModuleEntry::allocate_archived_entry() const { - assert(is_named(), "unnamed packages/modules are not archived"); + precond(should_be_archived()); + precond(CDSConfig::is_dumping_full_module_graph()); ModuleEntry* archived_entry = (ModuleEntry*)ArchiveBuilder::rw_region_alloc(sizeof(ModuleEntry)); memcpy((void*)archived_entry, (void*)this, sizeof(ModuleEntry)); - if (CDSConfig::is_dumping_full_module_graph()) { - archived_entry->_archived_module_index = HeapShared::append_root(module_oop()); - } else { - archived_entry->_archived_module_index = -1; - } - + archived_entry->_archived_module_index = HeapShared::append_root(module_oop()); if (_archive_modules_entries == nullptr) { _archive_modules_entries = new (mtClass)ArchivedModuleEntries(); } @@ -489,10 +509,14 @@ void ModuleEntry::init_as_archived_entry() { set_archived_reads(write_growable_array(reads())); _loader_data = nullptr; // re-init at runtime - _shared_path_index = AOTClassLocationConfig::dumptime()->get_module_shared_path_index(_location); if (name() != nullptr) { + _shared_path_index = AOTClassLocationConfig::dumptime()->get_module_shared_path_index(_location); _name = ArchiveBuilder::get_buffered_symbol(_name); ArchivePtrMarker::mark_pointer((address*)&_name); + } else { + // _shared_path_index is used only by SystemDictionary::is_shared_class_visible_impl() + // for checking classes in named modules. + _shared_path_index = -1; } if (_version != nullptr) { _version = ArchiveBuilder::get_buffered_symbol(_version); @@ -741,7 +765,7 @@ void ModuleEntryTable::modules_do(ModuleClosure* closure) { _table.iterate_all(do_f); } -void ModuleEntry::print(outputStream* st) { +void ModuleEntry::print(outputStream* st) const { st->print_cr("entry " PTR_FORMAT " name %s module " PTR_FORMAT " loader %s version %s location %s strict %s", p2i(this), name_as_C_string(), diff --git a/src/hotspot/share/classfile/moduleEntry.hpp b/src/hotspot/share/classfile/moduleEntry.hpp index e66999c3cd9..1ae504577e3 100644 --- a/src/hotspot/share/classfile/moduleEntry.hpp +++ b/src/hotspot/share/classfile/moduleEntry.hpp @@ -186,10 +186,10 @@ public: static ModuleEntry* new_unnamed_module_entry(Handle module_handle, ClassLoaderData* cld); // Note caller requires ResourceMark - const char* name_as_C_string() { + const char* name_as_C_string() const { return is_named() ? name()->as_C_string() : UNNAMED_MODULE; } - void print(outputStream* st = tty); + void print(outputStream* st = tty) const; void verify(); CDS_ONLY(int shared_path_index() { return _shared_path_index;}) @@ -197,6 +197,7 @@ public: JFR_ONLY(DEFINE_TRACE_ID_METHODS;) #if INCLUDE_CDS_JAVA_HEAP + bool should_be_archived() const; void iterate_symbols(MetaspaceClosure* closure); ModuleEntry* allocate_archived_entry() const; void init_as_archived_entry(); diff --git a/src/hotspot/share/classfile/modules.cpp b/src/hotspot/share/classfile/modules.cpp index 72e56b03a58..c7b5a729451 100644 --- a/src/hotspot/share/classfile/modules.cpp +++ b/src/hotspot/share/classfile/modules.cpp @@ -474,6 +474,7 @@ void Modules::define_module(Handle module, jboolean is_open, jstring version, } #if INCLUDE_CDS_JAVA_HEAP +static bool _seen_boot_unnamed_module = false; static bool _seen_platform_unnamed_module = false; static bool _seen_system_unnamed_module = false; @@ -509,24 +510,20 @@ void Modules::check_archived_module_oop(oop orig_module_obj) { // For each named module, we archive both the java.lang.Module oop and the ModuleEntry. assert(orig_module_ent->has_been_archived(), "sanity"); } else { - // We only archive two unnamed module oops (for platform and system loaders). These do NOT have an archived - // ModuleEntry. - // - // At runtime, these oops are fetched from java_lang_ClassLoader::unnamedModule(loader) and - // are initialized in ClassLoaderData::ClassLoaderData() => ModuleEntry::create_unnamed_module(), where - // a new ModuleEntry is allocated. - assert(!loader_data->is_boot_class_loader_data(), "unnamed module for boot loader should be not archived"); - assert(!orig_module_ent->has_been_archived(), "sanity"); + // We always archive unnamed module oop for boot, platform, and system loaders. + precond(orig_module_ent->should_be_archived()); + precond(orig_module_ent->has_been_archived()); - if (SystemDictionary::is_platform_class_loader(loader_data->class_loader())) { + if (loader_data->is_boot_class_loader_data()) { + assert(!_seen_boot_unnamed_module, "only once"); + _seen_boot_unnamed_module = true; + } else if (SystemDictionary::is_platform_class_loader(loader_data->class_loader())) { assert(!_seen_platform_unnamed_module, "only once"); _seen_platform_unnamed_module = true; } else if (SystemDictionary::is_system_class_loader(loader_data->class_loader())) { assert(!_seen_system_unnamed_module, "only once"); _seen_system_unnamed_module = true; } else { - // The java.lang.Module oop and ModuleEntry of the unnamed module of the boot loader are - // not in the archived module graph. These are always allocated at runtime. ShouldNotReachHere(); } } @@ -777,9 +774,18 @@ void Modules::set_bootloader_unnamed_module(Handle module, TRAPS) { ClassLoaderData* boot_loader_data = ClassLoaderData::the_null_class_loader_data(); ModuleEntry* unnamed_module = boot_loader_data->unnamed_module(); assert(unnamed_module != nullptr, "boot loader's unnamed ModuleEntry not defined"); - unnamed_module->set_module_handle(boot_loader_data->add_handle(module)); - // Store pointer to the ModuleEntry in the unnamed module's java.lang.Module object. - java_lang_Module::set_module_entry(module(), unnamed_module); + +#if INCLUDE_CDS_JAVA_HEAP + if (CDSConfig::is_using_full_module_graph()) { + precond(unnamed_module == ClassLoaderDataShared::archived_boot_unnamed_module()); + unnamed_module->restore_archived_oops(boot_loader_data); + } else +#endif + { + unnamed_module->set_module_handle(boot_loader_data->add_handle(module)); + // Store pointer to the ModuleEntry in the unnamed module's java.lang.Module object. + java_lang_Module::set_module_entry(module(), unnamed_module); + } } void Modules::add_module_exports(Handle from_module, jstring package_name, Handle to_module, TRAPS) { diff --git a/src/hotspot/share/classfile/packageEntry.cpp b/src/hotspot/share/classfile/packageEntry.cpp index eaa311c4bd3..26af9a4936c 100644 --- a/src/hotspot/share/classfile/packageEntry.cpp +++ b/src/hotspot/share/classfile/packageEntry.cpp @@ -30,6 +30,7 @@ #include "classfile/packageEntry.hpp" #include "classfile/vmSymbols.hpp" #include "logging/log.hpp" +#include "logging/logStream.hpp" #include "memory/resourceArea.hpp" #include "oops/array.hpp" #include "oops/symbol.hpp" @@ -218,8 +219,12 @@ typedef ResourceHashtable< AnyObj::C_HEAP> ArchivedPackageEntries; static ArchivedPackageEntries* _archived_packages_entries = nullptr; +bool PackageEntry::should_be_archived() const { + return module()->should_be_archived(); +} + PackageEntry* PackageEntry::allocate_archived_entry() const { - assert(!in_unnamed_module(), "unnamed packages/modules are not archived"); + precond(should_be_archived()); PackageEntry* archived_entry = (PackageEntry*)ArchiveBuilder::rw_region_alloc(sizeof(PackageEntry)); memcpy((void*)archived_entry, (void*)this, sizeof(PackageEntry)); @@ -257,6 +262,12 @@ void PackageEntry::init_as_archived_entry() { ArchivePtrMarker::mark_pointer((address*)&_name); ArchivePtrMarker::mark_pointer((address*)&_module); ArchivePtrMarker::mark_pointer((address*)&_qualified_exports); + + LogStreamHandle(Info, aot, package) st; + if (st.is_enabled()) { + st.print("archived "); + print(&st); + } } void PackageEntry::load_from_archive() { @@ -280,7 +291,7 @@ Array* PackageEntryTable::allocate_archived_entries() { // First count the packages in named modules int n = 0; auto count = [&] (const SymbolHandle& key, PackageEntry*& p) { - if (p->module()->is_named()) { + if (p->should_be_archived()) { n++; } }; @@ -290,9 +301,7 @@ Array* PackageEntryTable::allocate_archived_entries() { // reset n n = 0; auto grab = [&] (const SymbolHandle& key, PackageEntry*& p) { - if (p->module()->is_named()) { - // We don't archive unnamed modules, or packages in unnamed modules. They will be - // created on-demand at runtime as classes in such packages are loaded. + if (p->should_be_archived()) { archived_packages->at_put(n++, p); } }; diff --git a/src/hotspot/share/classfile/packageEntry.hpp b/src/hotspot/share/classfile/packageEntry.hpp index 213f115b2d0..039c7f21fa6 100644 --- a/src/hotspot/share/classfile/packageEntry.hpp +++ b/src/hotspot/share/classfile/packageEntry.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -208,6 +208,7 @@ public: void print(outputStream* st = tty); #if INCLUDE_CDS_JAVA_HEAP + bool should_be_archived() const; void iterate_symbols(MetaspaceClosure* closure); PackageEntry* allocate_archived_entry() const; void init_as_archived_entry(); diff --git a/src/hotspot/share/logging/logTag.hpp b/src/hotspot/share/logging/logTag.hpp index 9edf81d3f27..6d0bd117ad9 100644 --- a/src/hotspot/share/logging/logTag.hpp +++ b/src/hotspot/share/logging/logTag.hpp @@ -149,6 +149,7 @@ class outputStream; LOG_TAG(oopstorage) \ LOG_TAG(os) \ LOG_TAG(owner) \ + LOG_TAG(package) \ LOG_TAG(page) \ LOG_TAG(pagesize) \ LOG_TAG(parser) \ diff --git a/src/hotspot/share/memory/universe.cpp b/src/hotspot/share/memory/universe.cpp index 100ed9b42dd..cefe8454fb9 100644 --- a/src/hotspot/share/memory/universe.cpp +++ b/src/hotspot/share/memory/universe.cpp @@ -29,6 +29,7 @@ #include "cds/metaspaceShared.hpp" #include "classfile/classLoader.hpp" #include "classfile/classLoaderDataGraph.hpp" +#include "classfile/classLoaderDataShared.hpp" #include "classfile/javaClasses.hpp" #include "classfile/stringTable.hpp" #include "classfile/symbolTable.hpp" @@ -897,14 +898,20 @@ jint universe_init() { return JNI_EINVAL; } - ClassLoaderData::init_null_class_loader_data(); - #if INCLUDE_CDS if (CDSConfig::is_using_archive()) { // Read the data structures supporting the shared spaces (shared // system dictionary, symbol table, etc.) MetaspaceShared::initialize_shared_spaces(); } +#endif + + ClassLoaderData::init_null_class_loader_data(); + +#if INCLUDE_CDS + if (CDSConfig::is_using_full_module_graph()) { + ClassLoaderDataShared::restore_archived_entries_for_null_class_loader_data(); + } if (CDSConfig::is_dumping_archive()) { CDSConfig::prepare_for_dumping(); } diff --git a/src/java.base/share/classes/jdk/internal/loader/ArchivedClassLoaders.java b/src/java.base/share/classes/jdk/internal/loader/ArchivedClassLoaders.java index 9fcb8cc5943..be3425590fc 100644 --- a/src/java.base/share/classes/jdk/internal/loader/ArchivedClassLoaders.java +++ b/src/java.base/share/classes/jdk/internal/loader/ArchivedClassLoaders.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021, 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 @@ -40,6 +40,7 @@ class ArchivedClassLoaders { private final ClassLoader appLoader; private final ServicesCatalog[] servicesCatalogs; private final Map packageToModule; + private final Module unnamedModuleForBootLoader; private ArchivedClassLoaders() { bootLoader = ClassLoaders.bootLoader(); @@ -52,6 +53,7 @@ class ArchivedClassLoaders { servicesCatalogs[2] = ServicesCatalog.getServicesCatalog(appLoader); packageToModule = BuiltinClassLoader.packageToModule(); + unnamedModuleForBootLoader = BootLoader.getUnnamedModule(); } ClassLoader bootLoader() { @@ -82,6 +84,10 @@ class ArchivedClassLoaders { return packageToModule; } + Module unnamedModuleForBootLoader() { + return unnamedModuleForBootLoader; + } + static void archive() { archivedClassLoaders = new ArchivedClassLoaders(); } diff --git a/src/java.base/share/classes/jdk/internal/loader/BootLoader.java b/src/java.base/share/classes/jdk/internal/loader/BootLoader.java index c845146a838..bc5bd9d4265 100644 --- a/src/java.base/share/classes/jdk/internal/loader/BootLoader.java +++ b/src/java.base/share/classes/jdk/internal/loader/BootLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -61,7 +61,12 @@ public class BootLoader { static { JavaLangAccess jla = SharedSecrets.getJavaLangAccess(); - UNNAMED_MODULE = jla.defineUnnamedModule(null); + ArchivedClassLoaders archivedClassLoaders = ArchivedClassLoaders.get(); + if (archivedClassLoaders != null) { + UNNAMED_MODULE = archivedClassLoaders.unnamedModuleForBootLoader(); + } else { + UNNAMED_MODULE = jla.defineUnnamedModule(null); + } jla.addEnableNativeAccess(UNNAMED_MODULE); setBootLoaderUnnamedModule0(UNNAMED_MODULE); } From 5540a7859b3ae0faf6b6c7f50e53ff611b253a9f Mon Sep 17 00:00:00 2001 From: Justin Lu Date: Tue, 22 Jul 2025 20:23:20 +0000 Subject: [PATCH 45/94] 8360416: Incorrect l10n test case in sun/security/tools/keytool/i18n.java Reviewed-by: weijun, rhalade --- test/jdk/sun/security/tools/keytool/i18n.java | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/test/jdk/sun/security/tools/keytool/i18n.java b/test/jdk/sun/security/tools/keytool/i18n.java index 4ba8c7830b7..6eac0239eee 100644 --- a/test/jdk/sun/security/tools/keytool/i18n.java +++ b/test/jdk/sun/security/tools/keytool/i18n.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 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 @@ -23,7 +23,7 @@ /* * @test - * @bug 4348369 8076069 8294994 + * @bug 4348369 8076069 8294994 8360400 * @summary keytool i18n compliant * @author charlie lai * @modules java.base/sun.security.tools.keytool @@ -33,7 +33,7 @@ /* * @test - * @bug 4348369 8076069 8294994 + * @bug 4348369 8076069 8294994 8360400 * @summary keytool i18n compliant * @author charlie lai * @modules java.base/sun.security.tools.keytool @@ -43,7 +43,7 @@ /* * @test - * @bug 4348369 8076069 8294994 + * @bug 4348369 8076069 8294994 8360400 * @summary keytool i18n compliant * @author charlie lai * @modules java.base/sun.security.tools.keytool @@ -53,7 +53,7 @@ /* * @test - * @bug 4348369 8076069 8294994 + * @bug 4348369 8076069 8294994 8360400 * @summary keytool i18n compliant * @author charlie lai * @modules java.base/sun.security.tools.keytool @@ -95,10 +95,6 @@ public class i18n { + "512-bit DSA key algorithm for CN=Name, OU=Java, " + "O=Oracle, L=City, ST=State C=Country."}, - {"-list -v -storepass a -keystore ./i18n.keystore", - "Output in ${LANG}. Check keytool error:java.io.IOException: " - + "keystore password was incorrect."}, - {"-genkey -keyalg DSA -v -keysize 512 " + "-storepass password " + "-keypass password " From 016694bf74f6920f850330e353df9fd03458cca1 Mon Sep 17 00:00:00 2001 From: DarraghConway Date: Tue, 22 Jul 2025 21:59:11 +0000 Subject: [PATCH 46/94] 8360411: [TEST] open/test/jdk/java/io/File/MaxPathLength.java Refactor extract method to encapsulate Windows specific test logic Reviewed-by: msheppar --- test/jdk/java/io/File/MaxPathLength.java | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/test/jdk/java/io/File/MaxPathLength.java b/test/jdk/java/io/File/MaxPathLength.java index 0e0b099afd9..1110b469759 100644 --- a/test/jdk/java/io/File/MaxPathLength.java +++ b/test/jdk/java/io/File/MaxPathLength.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2002, 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 @@ -60,14 +60,7 @@ public class MaxPathLength { // test long paths on windows // And these long pathes cannot be handled on Solaris and Mac platforms - if (isWindows) { - String name = fileName; - while (name.length() < MAX_LENGTH) { - testLongPath (20, name, false); - testLongPath (20, name, true); - name = getNextName(name); - } - } + testLongPathOnWindows(); } private static String getNextName(String fName) { @@ -199,4 +192,15 @@ public class MaxPathLength { } } } + + private static void testLongPathOnWindows () throws Exception { + if (isWindows) { + String name = fileName; + while (name.length() < MAX_LENGTH) { + testLongPath (20, name, false); + testLongPath (20, name, true); + name = getNextName(name); + } + } + } } From 4994bd594299e91e804438692e068b1c5dd5cc02 Mon Sep 17 00:00:00 2001 From: Srinivas Vamsi Parasa Date: Tue, 22 Jul 2025 22:37:45 +0000 Subject: [PATCH 47/94] 8359965: Enable paired pushp and popp instruction usage for APX enabled CPUs Reviewed-by: sviswanathan, vpaprotski --- src/hotspot/cpu/x86/c1_CodeStubs_x86.cpp | 4 +- src/hotspot/cpu/x86/c1_LIRAssembler_x86.cpp | 48 ++-- src/hotspot/cpu/x86/c1_Runtime1_x86.cpp | 20 +- .../x86/c2_stubGenerator_x86_64_string.cpp | 12 +- .../x86/gc/g1/g1BarrierSetAssembler_x86.cpp | 20 +- .../cpu/x86/gc/z/zBarrierSetAssembler_x86.cpp | 40 ++-- src/hotspot/cpu/x86/icache_x86.cpp | 16 +- src/hotspot/cpu/x86/macroAssembler_x86.cpp | 16 ++ src/hotspot/cpu/x86/macroAssembler_x86.hpp | 3 + src/hotspot/cpu/x86/methodHandles_x86.cpp | 4 +- src/hotspot/cpu/x86/runtime_x86_64.cpp | 4 +- src/hotspot/cpu/x86/stubGenerator_x86_64.cpp | 214 +++++++++--------- .../cpu/x86/stubGenerator_x86_64_aes.cpp | 136 +++++------ .../x86/stubGenerator_x86_64_arraycopy.cpp | 10 +- .../cpu/x86/stubGenerator_x86_64_cos.cpp | 12 +- .../cpu/x86/stubGenerator_x86_64_ghash.cpp | 8 +- .../cpu/x86/stubGenerator_x86_64_kyber.cpp | 4 +- .../cpu/x86/stubGenerator_x86_64_poly1305.cpp | 32 +-- .../x86/stubGenerator_x86_64_poly_mont.cpp | 24 +- .../cpu/x86/stubGenerator_x86_64_sha3.cpp | 12 +- .../cpu/x86/stubGenerator_x86_64_sin.cpp | 12 +- .../cpu/x86/stubGenerator_x86_64_tan.cpp | 12 +- 22 files changed, 341 insertions(+), 322 deletions(-) diff --git a/src/hotspot/cpu/x86/c1_CodeStubs_x86.cpp b/src/hotspot/cpu/x86/c1_CodeStubs_x86.cpp index 6d3cf649233..2fd067a7749 100644 --- a/src/hotspot/cpu/x86/c1_CodeStubs_x86.cpp +++ b/src/hotspot/cpu/x86/c1_CodeStubs_x86.cpp @@ -306,10 +306,10 @@ void PatchingStub::emit_code(LIR_Assembler* ce) { } assert(_obj != noreg, "must be a valid register"); Register tmp = rax; - __ push(tmp); + __ push_ppx(tmp); __ movptr(tmp, Address(_obj, java_lang_Class::klass_offset())); __ cmpptr(r15_thread, Address(tmp, InstanceKlass::init_thread_offset())); - __ pop(tmp); // pop it right away, no matter which path we take + __ pop_ppx(tmp); // pop it right away, no matter which path we take __ jccb(Assembler::notEqual, call_patch); // access_field patches may execute the patched code before it's diff --git a/src/hotspot/cpu/x86/c1_LIRAssembler_x86.cpp b/src/hotspot/cpu/x86/c1_LIRAssembler_x86.cpp index 1a639b243c1..0176ff967ce 100644 --- a/src/hotspot/cpu/x86/c1_LIRAssembler_x86.cpp +++ b/src/hotspot/cpu/x86/c1_LIRAssembler_x86.cpp @@ -1385,11 +1385,11 @@ void LIR_Assembler::emit_typecheck_helper(LIR_OpTypeCheck *op, Label* success, L __ cmpptr(klass_RInfo, k_RInfo); __ jcc(Assembler::equal, *success_target); - __ push(klass_RInfo); - __ push(k_RInfo); + __ push_ppx(klass_RInfo); + __ push_ppx(k_RInfo); __ call(RuntimeAddress(Runtime1::entry_for(StubId::c1_slow_subtype_check_id))); - __ pop(klass_RInfo); - __ pop(klass_RInfo); + __ pop_ppx(klass_RInfo); + __ pop_ppx(klass_RInfo); // result is a boolean __ testl(klass_RInfo, klass_RInfo); __ jcc(Assembler::equal, *failure_target); @@ -1399,11 +1399,11 @@ void LIR_Assembler::emit_typecheck_helper(LIR_OpTypeCheck *op, Label* success, L // perform the fast part of the checking logic __ check_klass_subtype_fast_path(klass_RInfo, k_RInfo, Rtmp1, success_target, failure_target, nullptr); // call out-of-line instance of __ check_klass_subtype_slow_path(...): - __ push(klass_RInfo); - __ push(k_RInfo); + __ push_ppx(klass_RInfo); + __ push_ppx(k_RInfo); __ call(RuntimeAddress(Runtime1::entry_for(StubId::c1_slow_subtype_check_id))); - __ pop(klass_RInfo); - __ pop(k_RInfo); + __ pop_ppx(klass_RInfo); + __ pop_ppx(k_RInfo); // result is a boolean __ testl(k_RInfo, k_RInfo); __ jcc(Assembler::equal, *failure_target); @@ -1478,11 +1478,11 @@ void LIR_Assembler::emit_opTypeCheck(LIR_OpTypeCheck* op) { // perform the fast part of the checking logic __ check_klass_subtype_fast_path(klass_RInfo, k_RInfo, Rtmp1, success_target, failure_target, nullptr); // call out-of-line instance of __ check_klass_subtype_slow_path(...): - __ push(klass_RInfo); - __ push(k_RInfo); + __ push_ppx(klass_RInfo); + __ push_ppx(k_RInfo); __ call(RuntimeAddress(Runtime1::entry_for(StubId::c1_slow_subtype_check_id))); - __ pop(klass_RInfo); - __ pop(k_RInfo); + __ pop_ppx(klass_RInfo); + __ pop_ppx(k_RInfo); // result is a boolean __ testl(k_RInfo, k_RInfo); __ jcc(Assembler::equal, *failure_target); @@ -2536,26 +2536,26 @@ void LIR_Assembler::emit_arraycopy(LIR_OpArrayCopy* op) { // safely do the copy. Label cont, slow; - __ push(src); - __ push(dst); + __ push_ppx(src); + __ push_ppx(dst); __ load_klass(src, src, tmp_load_klass); __ load_klass(dst, dst, tmp_load_klass); __ check_klass_subtype_fast_path(src, dst, tmp, &cont, &slow, nullptr); - __ push(src); - __ push(dst); + __ push_ppx(src); + __ push_ppx(dst); __ call(RuntimeAddress(Runtime1::entry_for(StubId::c1_slow_subtype_check_id))); - __ pop(dst); - __ pop(src); + __ pop_ppx(dst); + __ pop_ppx(src); __ testl(src, src); __ jcc(Assembler::notEqual, cont); __ bind(slow); - __ pop(dst); - __ pop(src); + __ pop_ppx(dst); + __ pop_ppx(src); address copyfunc_addr = StubRoutines::checkcast_arraycopy(); if (copyfunc_addr != nullptr) { // use stub if available @@ -2904,13 +2904,13 @@ void LIR_Assembler::emit_profile_type(LIR_OpProfileType* op) { if (exact_klass != nullptr) { Label ok; __ load_klass(tmp, obj, tmp_load_klass); - __ push(tmp); + __ push_ppx(tmp); __ mov_metadata(tmp, exact_klass->constant_encoding()); __ cmpptr(tmp, Address(rsp, 0)); __ jcc(Assembler::equal, ok); __ stop("exact klass and actual klass differ"); __ bind(ok); - __ pop(tmp); + __ pop_ppx(tmp); } #endif if (!no_conflict) { @@ -2975,7 +2975,7 @@ void LIR_Assembler::emit_profile_type(LIR_OpProfileType* op) { { Label ok; - __ push(tmp); + __ push_ppx(tmp); __ testptr(mdo_addr, TypeEntries::type_mask); __ jcc(Assembler::zero, ok); // may have been set by another thread @@ -2986,7 +2986,7 @@ void LIR_Assembler::emit_profile_type(LIR_OpProfileType* op) { __ stop("unexpected profiling mismatch"); __ bind(ok); - __ pop(tmp); + __ pop_ppx(tmp); } #else __ jccb(Assembler::zero, next); diff --git a/src/hotspot/cpu/x86/c1_Runtime1_x86.cpp b/src/hotspot/cpu/x86/c1_Runtime1_x86.cpp index 3189854f564..96439c71990 100644 --- a/src/hotspot/cpu/x86/c1_Runtime1_x86.cpp +++ b/src/hotspot/cpu/x86/c1_Runtime1_x86.cpp @@ -719,7 +719,7 @@ OopMapSet* Runtime1::generate_patching(StubAssembler* sasm, address target) { // verify callee-saved register #ifdef ASSERT guarantee(thread != rax, "change this code"); - __ push(rax); + __ push_ppx(rax); { Label L; __ get_thread_slow(rax); __ cmpptr(thread, rax); @@ -727,7 +727,7 @@ OopMapSet* Runtime1::generate_patching(StubAssembler* sasm, address target) { __ stop("StubAssembler::call_RT: rdi/r15 not callee saved?"); __ bind(L); } - __ pop(rax); + __ pop_ppx(rax); #endif __ reset_last_Java_frame(true); @@ -1070,10 +1070,10 @@ OopMapSet* Runtime1::generate_code_for(StubId id, StubAssembler* sasm) { }; __ set_info("slow_subtype_check", dont_gc_arguments); - __ push(rdi); - __ push(rsi); - __ push(rcx); - __ push(rax); + __ push_ppx(rdi); + __ push_ppx(rsi); + __ push_ppx(rcx); + __ push_ppx(rax); // This is called by pushing args and not with C abi __ movptr(rsi, Address(rsp, (klass_off) * VMRegImpl::stack_slot_size)); // subclass @@ -1084,10 +1084,10 @@ OopMapSet* Runtime1::generate_code_for(StubId id, StubAssembler* sasm) { // fallthrough on success: __ movptr(Address(rsp, (result_off) * VMRegImpl::stack_slot_size), 1); // result - __ pop(rax); - __ pop(rcx); - __ pop(rsi); - __ pop(rdi); + __ pop_ppx(rax); + __ pop_ppx(rcx); + __ pop_ppx(rsi); + __ pop_ppx(rdi); __ ret(0); __ bind(miss); diff --git a/src/hotspot/cpu/x86/c2_stubGenerator_x86_64_string.cpp b/src/hotspot/cpu/x86/c2_stubGenerator_x86_64_string.cpp index 23dd9f908b5..0951bda0d17 100644 --- a/src/hotspot/cpu/x86/c2_stubGenerator_x86_64_string.cpp +++ b/src/hotspot/cpu/x86/c2_stubGenerator_x86_64_string.cpp @@ -352,8 +352,8 @@ static void generate_string_indexof_stubs(StubGenerator *stubgen, address *fnptr __ movdq(save_r15, r15); __ movdq(save_rbx, rbx); #ifdef _WIN64 - __ push(rsi); - __ push(rdi); + __ push_ppx(rsi); + __ push_ppx(rdi); // Move to Linux-style ABI __ movq(rdi, rcx); @@ -368,7 +368,7 @@ static void generate_string_indexof_stubs(StubGenerator *stubgen, address *fnptr const Register needle_len = rcx; const Register save_ndl_len = r12; - __ push(rbp); + __ push_ppx(rbp); __ subptr(rsp, STACK_SPACE); if (isReallyUL) { @@ -459,10 +459,10 @@ static void generate_string_indexof_stubs(StubGenerator *stubgen, address *fnptr // Restore stack, vzeroupper and return __ bind(L_return); __ addptr(rsp, STACK_SPACE); - __ pop(rbp); + __ pop_ppx(rbp); #ifdef _WIN64 - __ pop(rdi); - __ pop(rsi); + __ pop_ppx(rdi); + __ pop_ppx(rsi); #endif __ movdq(r12, save_r12); __ movdq(r13, save_r13); diff --git a/src/hotspot/cpu/x86/gc/g1/g1BarrierSetAssembler_x86.cpp b/src/hotspot/cpu/x86/gc/g1/g1BarrierSetAssembler_x86.cpp index bc5d6a233d3..c1920b52837 100644 --- a/src/hotspot/cpu/x86/gc/g1/g1BarrierSetAssembler_x86.cpp +++ b/src/hotspot/cpu/x86/gc/g1/g1BarrierSetAssembler_x86.cpp @@ -500,8 +500,8 @@ void G1BarrierSetAssembler::generate_c1_pre_barrier_runtime_stub(StubAssembler* __ prologue("g1_pre_barrier", false); // arg0 : previous value of memory - __ push(rax); - __ push(rdx); + __ push_ppx(rax); + __ push_ppx(rdx); const Register pre_val = rax; const Register thread = r15_thread; @@ -549,8 +549,8 @@ void G1BarrierSetAssembler::generate_c1_pre_barrier_runtime_stub(StubAssembler* __ bind(done); - __ pop(rdx); - __ pop(rax); + __ pop_ppx(rdx); + __ pop_ppx(rax); __ epilogue(); } @@ -573,8 +573,8 @@ void G1BarrierSetAssembler::generate_c1_post_barrier_runtime_stub(StubAssembler* Address queue_index(thread, in_bytes(G1ThreadLocalData::dirty_card_queue_index_offset())); Address buffer(thread, in_bytes(G1ThreadLocalData::dirty_card_queue_buffer_offset())); - __ push(rax); - __ push(rcx); + __ push_ppx(rax); + __ push_ppx(rcx); const Register cardtable = rax; const Register card_addr = rcx; @@ -599,7 +599,7 @@ void G1BarrierSetAssembler::generate_c1_post_barrier_runtime_stub(StubAssembler* __ movb(Address(card_addr, 0), CardTable::dirty_card_val()); const Register tmp = rdx; - __ push(rdx); + __ push_ppx(rdx); __ movptr(tmp, queue_index); __ testptr(tmp, tmp); @@ -618,11 +618,11 @@ void G1BarrierSetAssembler::generate_c1_post_barrier_runtime_stub(StubAssembler* __ pop_call_clobbered_registers(); __ bind(enqueued); - __ pop(rdx); + __ pop_ppx(rdx); __ bind(done); - __ pop(rcx); - __ pop(rax); + __ pop_ppx(rcx); + __ pop_ppx(rax); __ epilogue(); } diff --git a/src/hotspot/cpu/x86/gc/z/zBarrierSetAssembler_x86.cpp b/src/hotspot/cpu/x86/gc/z/zBarrierSetAssembler_x86.cpp index fe7f19e1260..0b19180ea06 100644 --- a/src/hotspot/cpu/x86/gc/z/zBarrierSetAssembler_x86.cpp +++ b/src/hotspot/cpu/x86/gc/z/zBarrierSetAssembler_x86.cpp @@ -288,7 +288,7 @@ void ZBarrierSetAssembler::load_at(MacroAssembler* masm, Register scratch = tmp1; if (tmp1 == noreg) { scratch = r12; - __ push(scratch); + __ push_ppx(scratch); } assert_different_registers(dst, scratch); @@ -348,7 +348,7 @@ void ZBarrierSetAssembler::load_at(MacroAssembler* masm, // Restore scratch register if (tmp1 == noreg) { - __ pop(scratch); + __ pop_ppx(scratch); } BLOCK_COMMENT("} ZBarrierSetAssembler::load_at"); @@ -462,10 +462,10 @@ void ZBarrierSetAssembler::store_barrier_fast(MacroAssembler* masm, __ movptr(rnew_zpointer, rnew_zaddress); } assert_different_registers(rcx, rnew_zpointer); - __ push(rcx); + __ push_ppx(rcx); __ movptr(rcx, ExternalAddress((address)&ZPointerLoadShift)); __ shlq(rnew_zpointer); - __ pop(rcx); + __ pop_ppx(rcx); __ orq(rnew_zpointer, Address(r15_thread, ZThreadLocalData::store_good_mask_offset())); } } @@ -483,7 +483,7 @@ static void store_barrier_buffer_add(MacroAssembler* masm, __ jcc(Assembler::equal, slow_path); Register tmp2 = r15_thread; - __ push(tmp2); + __ push_ppx(tmp2); // Bump the pointer __ movq(tmp2, Address(tmp1, ZStoreBarrierBuffer::current_offset())); @@ -501,7 +501,7 @@ static void store_barrier_buffer_add(MacroAssembler* masm, __ movptr(tmp1, Address(tmp1, 0)); __ movptr(Address(tmp2, in_bytes(ZStoreBarrierEntry::prev_offset())), tmp1); - __ pop(tmp2); + __ pop_ppx(tmp2); } void ZBarrierSetAssembler::store_barrier_medium(MacroAssembler* masm, @@ -528,9 +528,9 @@ void ZBarrierSetAssembler::store_barrier_medium(MacroAssembler* masm, // If we get this far, we know there is a young raw null value in the field. // Try to self-heal null values for atomic accesses - __ push(rax); - __ push(rbx); - __ push(rcx); + __ push_ppx(rax); + __ push_ppx(rbx); + __ push_ppx(rcx); __ lea(rcx, ref_addr); __ xorq(rax, rax); @@ -539,9 +539,9 @@ void ZBarrierSetAssembler::store_barrier_medium(MacroAssembler* masm, __ lock(); __ cmpxchgq(rbx, Address(rcx, 0)); - __ pop(rcx); - __ pop(rbx); - __ pop(rax); + __ pop_ppx(rcx); + __ pop_ppx(rbx); + __ pop_ppx(rax); __ jcc(Assembler::notEqual, slow_path); @@ -583,10 +583,10 @@ void ZBarrierSetAssembler::store_at(MacroAssembler* masm, } else { __ movptr(tmp1, src); } - __ push(rcx); + __ push_ppx(rcx); __ movptr(rcx, ExternalAddress((address)&ZPointerLoadShift)); __ shlq(tmp1); - __ pop(rcx); + __ pop_ppx(rcx); __ orq(tmp1, Address(r15_thread, ZThreadLocalData::store_good_mask_offset())); } else { Label done; @@ -1007,10 +1007,10 @@ void ZBarrierSetAssembler::try_resolve_jobject_in_native(MacroAssembler* masm, __ shrq(tmp); __ movptr(obj, tmp); } else { - __ push(rcx); + __ push_ppx(rcx); __ movptr(rcx, ExternalAddress((address)&ZPointerLoadShift)); __ shrq(obj); - __ pop(rcx); + __ pop_ppx(rcx); } __ bind(done); @@ -1089,7 +1089,7 @@ void ZBarrierSetAssembler::generate_c1_load_barrier_stub(LIR_Assembler* ce, // Save rax unless it is the result or tmp register if (ref != rax && tmp != rax) { - __ push(rax); + __ push_ppx(rax); } // Setup arguments and call runtime stub @@ -1109,7 +1109,7 @@ void ZBarrierSetAssembler::generate_c1_load_barrier_stub(LIR_Assembler* ce, // Restore rax unless it is the result or tmp register if (ref != rax && tmp != rax) { - __ pop(rax); + __ pop_ppx(rax); } // Stub exit @@ -1451,12 +1451,12 @@ void ZBarrierSetAssembler::check_oop(MacroAssembler* masm, Register obj, Registe // Uncolor presumed zpointer assert(obj != rcx, "bad choice of register"); if (rcx != tmp1 && rcx != tmp2) { - __ push(rcx); + __ push_ppx(rcx); } __ movl(rcx, Address(tmp2, tmp1, Address::times_4, 0)); __ shrq(obj); if (rcx != tmp1 && rcx != tmp2) { - __ pop(rcx); + __ pop_ppx(rcx); } __ jmp(check_zaddress); diff --git a/src/hotspot/cpu/x86/icache_x86.cpp b/src/hotspot/cpu/x86/icache_x86.cpp index 889cfb32931..50572ea92e5 100644 --- a/src/hotspot/cpu/x86/icache_x86.cpp +++ b/src/hotspot/cpu/x86/icache_x86.cpp @@ -41,16 +41,16 @@ void x86_generate_icache_fence(MacroAssembler* _masm) { __ sfence(); break; case 4: - __ push(rax); - __ push(rbx); - __ push(rcx); - __ push(rdx); + __ push_ppx(rax); + __ push_ppx(rbx); + __ push_ppx(rcx); + __ push_ppx(rdx); __ xorptr(rax, rax); __ cpuid(); - __ pop(rdx); - __ pop(rcx); - __ pop(rbx); - __ pop(rax); + __ pop_ppx(rdx); + __ pop_ppx(rcx); + __ pop_ppx(rbx); + __ pop_ppx(rax); break; case 5: __ serialize(); diff --git a/src/hotspot/cpu/x86/macroAssembler_x86.cpp b/src/hotspot/cpu/x86/macroAssembler_x86.cpp index c8bf289e9d4..2d1005b8438 100644 --- a/src/hotspot/cpu/x86/macroAssembler_x86.cpp +++ b/src/hotspot/cpu/x86/macroAssembler_x86.cpp @@ -795,6 +795,22 @@ void MacroAssembler::pop_d(XMMRegister r) { addptr(rsp, 2 * Interpreter::stackElementSize); } +void MacroAssembler::push_ppx(Register src) { + if (VM_Version::supports_apx_f()) { + pushp(src); + } else { + Assembler::push(src); + } +} + +void MacroAssembler::pop_ppx(Register dst) { + if (VM_Version::supports_apx_f()) { + popp(dst); + } else { + Assembler::pop(dst); + } +} + void MacroAssembler::andpd(XMMRegister dst, AddressLiteral src, Register rscratch) { // Used in sign-masking with aligned address. assert((UseAVX > 0) || (((intptr_t)src.target() & 15) == 0), "SSE mode requires address alignment 16 bytes"); diff --git a/src/hotspot/cpu/x86/macroAssembler_x86.hpp b/src/hotspot/cpu/x86/macroAssembler_x86.hpp index d75c9b624fd..1c0dbaaefbe 100644 --- a/src/hotspot/cpu/x86/macroAssembler_x86.hpp +++ b/src/hotspot/cpu/x86/macroAssembler_x86.hpp @@ -989,6 +989,9 @@ public: void push_d(XMMRegister r); void pop_d(XMMRegister r); + void push_ppx(Register src); + void pop_ppx(Register dst); + void andpd(XMMRegister dst, XMMRegister src) { Assembler::andpd(dst, src); } void andpd(XMMRegister dst, Address src) { Assembler::andpd(dst, src); } void andpd(XMMRegister dst, AddressLiteral src, Register rscratch = noreg); diff --git a/src/hotspot/cpu/x86/methodHandles_x86.cpp b/src/hotspot/cpu/x86/methodHandles_x86.cpp index f3683e7d09c..0363cb04341 100644 --- a/src/hotspot/cpu/x86/methodHandles_x86.cpp +++ b/src/hotspot/cpu/x86/methodHandles_x86.cpp @@ -131,7 +131,7 @@ void MethodHandles::verify_method(MacroAssembler* _masm, Register method, Regist const Register method_holder = temp; __ load_method_holder(method_holder, method); - __ push(method_holder); // keep holder around for diagnostic purposes + __ push_ppx(method_holder); // keep holder around for diagnostic purposes switch (iid) { case vmIntrinsicID::_invokeBasic: @@ -165,7 +165,7 @@ void MethodHandles::verify_method(MacroAssembler* _masm, Register method, Regist __ STOP("Method holder klass is not initialized"); __ BIND(L_ok); - __ pop(method_holder); // restore stack layout + __ pop_ppx(method_holder); // restore stack layout } BLOCK_COMMENT("} verify_method"); } diff --git a/src/hotspot/cpu/x86/runtime_x86_64.cpp b/src/hotspot/cpu/x86/runtime_x86_64.cpp index 68a8c224659..7b98cf4fad7 100644 --- a/src/hotspot/cpu/x86/runtime_x86_64.cpp +++ b/src/hotspot/cpu/x86/runtime_x86_64.cpp @@ -292,7 +292,7 @@ ExceptionBlob* OptoRuntime::generate_exception_blob() { address start = __ pc(); // Exception pc is 'return address' for stack walker - __ push(rdx); + __ push_ppx(rdx); __ subptr(rsp, SimpleRuntimeFrame::return_off << LogBytesPerInt); // Prolog // Save callee-saved registers. See x86_64.ad. @@ -347,7 +347,7 @@ ExceptionBlob* OptoRuntime::generate_exception_blob() { __ movptr(rbp, Address(rsp, SimpleRuntimeFrame::rbp_off << LogBytesPerInt)); __ addptr(rsp, SimpleRuntimeFrame::return_off << LogBytesPerInt); // Epilog - __ pop(rdx); // No need for exception pc anymore + __ pop_ppx(rdx); // No need for exception pc anymore // rax: exception handler diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64.cpp index 30ce9718378..e1c84d9528f 100644 --- a/src/hotspot/cpu/x86/stubGenerator_x86_64.cpp +++ b/src/hotspot/cpu/x86/stubGenerator_x86_64.cpp @@ -574,7 +574,7 @@ address StubGenerator::generate_verify_mxcsr() { if (CheckJNICalls) { Label ok_ret; ExternalAddress mxcsr_std(StubRoutines::x86::addr_mxcsr_std()); - __ push(rax); + __ push_ppx(rax); __ subptr(rsp, wordSize); // allocate a temp location __ cmp32_mxcsr_std(mxcsr_save, rax, rscratch1); __ jcc(Assembler::equal, ok_ret); @@ -585,7 +585,7 @@ address StubGenerator::generate_verify_mxcsr() { __ bind(ok_ret); __ addptr(rsp, wordSize); - __ pop(rax); + __ pop_ppx(rax); } __ ret(0); @@ -602,10 +602,10 @@ address StubGenerator::generate_f2i_fixup() { Label L; - __ push(rax); - __ push(c_rarg3); - __ push(c_rarg2); - __ push(c_rarg1); + __ push_ppx(rax); + __ push_ppx(c_rarg3); + __ push_ppx(c_rarg2); + __ push_ppx(c_rarg1); __ movl(rax, 0x7f800000); __ xorl(c_rarg3, c_rarg3); @@ -622,10 +622,10 @@ address StubGenerator::generate_f2i_fixup() { __ bind(L); __ movptr(inout, c_rarg3); - __ pop(c_rarg1); - __ pop(c_rarg2); - __ pop(c_rarg3); - __ pop(rax); + __ pop_ppx(c_rarg1); + __ pop_ppx(c_rarg2); + __ pop_ppx(c_rarg3); + __ pop_ppx(rax); __ ret(0); @@ -640,10 +640,10 @@ address StubGenerator::generate_f2l_fixup() { Label L; - __ push(rax); - __ push(c_rarg3); - __ push(c_rarg2); - __ push(c_rarg1); + __ push_ppx(rax); + __ push_ppx(c_rarg3); + __ push_ppx(c_rarg2); + __ push_ppx(c_rarg1); __ movl(rax, 0x7f800000); __ xorl(c_rarg3, c_rarg3); @@ -660,10 +660,10 @@ address StubGenerator::generate_f2l_fixup() { __ bind(L); __ movptr(inout, c_rarg3); - __ pop(c_rarg1); - __ pop(c_rarg2); - __ pop(c_rarg3); - __ pop(rax); + __ pop_ppx(c_rarg1); + __ pop_ppx(c_rarg2); + __ pop_ppx(c_rarg3); + __ pop_ppx(rax); __ ret(0); @@ -679,11 +679,11 @@ address StubGenerator::generate_d2i_fixup() { Label L; - __ push(rax); - __ push(c_rarg3); - __ push(c_rarg2); - __ push(c_rarg1); - __ push(c_rarg0); + __ push_ppx(rax); + __ push_ppx(c_rarg3); + __ push_ppx(c_rarg2); + __ push_ppx(c_rarg1); + __ push_ppx(c_rarg0); __ movl(rax, 0x7ff00000); __ movq(c_rarg2, inout); @@ -707,11 +707,11 @@ address StubGenerator::generate_d2i_fixup() { __ bind(L); __ movptr(inout, c_rarg2); - __ pop(c_rarg0); - __ pop(c_rarg1); - __ pop(c_rarg2); - __ pop(c_rarg3); - __ pop(rax); + __ pop_ppx(c_rarg0); + __ pop_ppx(c_rarg1); + __ pop_ppx(c_rarg2); + __ pop_ppx(c_rarg3); + __ pop_ppx(rax); __ ret(0); @@ -727,11 +727,11 @@ address StubGenerator::generate_d2l_fixup() { Label L; - __ push(rax); - __ push(c_rarg3); - __ push(c_rarg2); - __ push(c_rarg1); - __ push(c_rarg0); + __ push_ppx(rax); + __ push_ppx(c_rarg3); + __ push_ppx(c_rarg2); + __ push_ppx(c_rarg1); + __ push_ppx(c_rarg0); __ movl(rax, 0x7ff00000); __ movq(c_rarg2, inout); @@ -755,11 +755,11 @@ address StubGenerator::generate_d2l_fixup() { __ bind(L); __ movq(inout, c_rarg2); - __ pop(c_rarg0); - __ pop(c_rarg1); - __ pop(c_rarg2); - __ pop(c_rarg3); - __ pop(rax); + __ pop_ppx(c_rarg0); + __ pop_ppx(c_rarg1); + __ pop_ppx(c_rarg2); + __ pop_ppx(c_rarg3); + __ pop_ppx(rax); __ ret(0); @@ -1180,11 +1180,11 @@ address StubGenerator::generate_verify_oop() { __ pushf(); __ incrementl(ExternalAddress((address) StubRoutines::verify_oop_count_addr()), rscratch1); - __ push(r12); + __ push_ppx(r12); // save c_rarg2 and c_rarg3 - __ push(c_rarg2); - __ push(c_rarg3); + __ push_ppx(c_rarg2); + __ push_ppx(c_rarg3); enum { // After previous pushes. @@ -1211,9 +1211,9 @@ address StubGenerator::generate_verify_oop() { __ bind(exit); __ movptr(rax, Address(rsp, saved_rax)); // get saved rax back __ movptr(rscratch1, Address(rsp, saved_r10)); // get saved r10 back - __ pop(c_rarg3); // restore c_rarg3 - __ pop(c_rarg2); // restore c_rarg2 - __ pop(r12); // restore r12 + __ pop_ppx(c_rarg3); // restore c_rarg3 + __ pop_ppx(c_rarg2); // restore c_rarg2 + __ pop_ppx(r12); // restore r12 __ popf(); // restore flags __ ret(4 * wordSize); // pop caller saved stuff @@ -1221,9 +1221,9 @@ address StubGenerator::generate_verify_oop() { __ bind(error); __ movptr(rax, Address(rsp, saved_rax)); // get saved rax back __ movptr(rscratch1, Address(rsp, saved_r10)); // get saved r10 back - __ pop(c_rarg3); // get saved c_rarg3 back - __ pop(c_rarg2); // get saved c_rarg2 back - __ pop(r12); // get saved r12 back + __ pop_ppx(c_rarg3); // get saved c_rarg3 back + __ pop_ppx(c_rarg2); // get saved c_rarg2 back + __ pop_ppx(r12); // get saved r12 back __ popf(); // get saved flags off stack -- // will be ignored @@ -1431,10 +1431,10 @@ address StubGenerator::generate_md5_implCompress(StubId stub_id) { const Address limit_param(rsp, 1 * wordSize + 4); __ enter(); - __ push(rbx); - __ push(rdi); - __ push(rsi); - __ push(r15); + __ push_ppx(rbx); + __ push_ppx(rdi); + __ push_ppx(rsi); + __ push_ppx(r15); __ subptr(rsp, 2 * wordSize); __ movptr(buf_param, c_rarg0); @@ -1446,10 +1446,10 @@ address StubGenerator::generate_md5_implCompress(StubId stub_id) { __ fast_md5(buf_param, state_param, ofs_param, limit_param, multi_block); __ addptr(rsp, 2 * wordSize); - __ pop(r15); - __ pop(rsi); - __ pop(rdi); - __ pop(rbx); + __ pop_ppx(r15); + __ pop_ppx(rsi); + __ pop_ppx(rdi); + __ pop_ppx(rbx); __ leave(); __ ret(0); @@ -1790,10 +1790,10 @@ address StubGenerator::generate_base64_encodeBlock() __ enter(); // Save callee-saved registers before using them - __ push(r12); - __ push(r13); - __ push(r14); - __ push(r15); + __ push_ppx(r12); + __ push_ppx(r13); + __ push_ppx(r14); + __ push_ppx(r15); // arguments const Register source = c_rarg0; // Source Array @@ -2153,10 +2153,10 @@ address StubGenerator::generate_base64_encodeBlock() __ jcc(Assembler::aboveEqual, L_processdata); __ BIND(L_exit); - __ pop(r15); - __ pop(r14); - __ pop(r13); - __ pop(r12); + __ pop_ppx(r15); + __ pop_ppx(r14); + __ pop_ppx(r13); + __ pop_ppx(r12); __ leave(); __ ret(0); @@ -2490,11 +2490,11 @@ address StubGenerator::generate_base64_decodeBlock() { __ enter(); // Save callee-saved registers before using them - __ push(r12); - __ push(r13); - __ push(r14); - __ push(r15); - __ push(rbx); + __ push_ppx(r12); + __ push_ppx(r13); + __ push_ppx(r14); + __ push_ppx(r15); + __ push_ppx(rbx); // arguments const Register source = c_rarg0; // Source Array @@ -2562,7 +2562,7 @@ address StubGenerator::generate_base64_decodeBlock() { // calculate length from offsets __ movl(length, end_offset); __ subl(length, start_offset); - __ push(dest); // Save for return value calc + __ push_ppx(dest); // Save for return value calc // If AVX512 VBMI not supported, just compile non-AVX code if(VM_Version::supports_avx512_vbmi() && @@ -2793,14 +2793,14 @@ address StubGenerator::generate_base64_decodeBlock() { __ BIND(L_exit); __ vzeroupper(); - __ pop(rax); // Get original dest value + __ pop_ppx(rax); // Get original dest value __ subptr(dest, rax); // Number of bytes converted __ movptr(rax, dest); - __ pop(rbx); - __ pop(r15); - __ pop(r14); - __ pop(r13); - __ pop(r12); + __ pop_ppx(rbx); + __ pop_ppx(r15); + __ pop_ppx(r14); + __ pop_ppx(r13); + __ pop_ppx(r12); __ leave(); __ ret(0); @@ -2987,14 +2987,14 @@ address StubGenerator::generate_base64_decodeBlock() { __ jcc(Assembler::positive, L_forceLoop); __ BIND(L_exit_no_vzero); - __ pop(rax); // Get original dest value - __ subptr(dest, rax); // Number of bytes converted + __ pop_ppx(rax); // Get original dest value + __ subptr(dest, rax); // Number of bytes converted __ movptr(rax, dest); - __ pop(rbx); - __ pop(r15); - __ pop(r14); - __ pop(r13); - __ pop(r12); + __ pop_ppx(rbx); + __ pop_ppx(r15); + __ pop_ppx(r14); + __ pop_ppx(r13); + __ pop_ppx(r12); __ leave(); __ ret(0); @@ -3117,8 +3117,8 @@ address StubGenerator::generate_updateBytesCRC32C(bool is_pclmulqdq_supported) { __ bind(L_doSmall); } #ifdef _WIN64 - __ push(y); - __ push(z); + __ push_ppx(y); + __ push_ppx(z); #endif __ crc32c_ipl_alg2_alt2(crc, buf, len, a, j, k, @@ -3126,8 +3126,8 @@ address StubGenerator::generate_updateBytesCRC32C(bool is_pclmulqdq_supported) { c_farg0, c_farg1, c_farg2, is_pclmulqdq_supported); #ifdef _WIN64 - __ pop(z); - __ pop(y); + __ pop_ppx(z); + __ pop_ppx(y); #endif __ bind(L_continue); @@ -3313,7 +3313,7 @@ address StubGenerator::generate_method_entry_barrier() { // save c_rarg0, because we want to use that value. // We could do without it but then we depend on the number of slots used by pusha - __ push(c_rarg0); + __ push_ppx(c_rarg0); __ lea(c_rarg0, Address(rsp, wordSize * 3)); // 1 for cookie, 1 for rbp, 1 for c_rarg0 - this should be the return address @@ -3350,7 +3350,7 @@ address StubGenerator::generate_method_entry_barrier() { __ jcc(Assembler::equal, deoptimize_label); __ popa(); - __ pop(c_rarg0); + __ pop_ppx(c_rarg0); __ leave(); @@ -3361,7 +3361,7 @@ address StubGenerator::generate_method_entry_barrier() { __ BIND(deoptimize_label); __ popa(); - __ pop(c_rarg0); + __ pop_ppx(c_rarg0); __ leave(); @@ -3465,10 +3465,10 @@ address StubGenerator::generate_bigIntegerRightShift() { // For windows, since last argument is on stack, we need to move it to the appropriate register. __ movl(totalNumIter, Address(rsp, 6 * wordSize)); // Save callee save registers. - __ push(tmp3); - __ push(tmp4); + __ push_ppx(tmp3); + __ push_ppx(tmp4); #endif - __ push(tmp5); + __ push_ppx(tmp5); // Rename temps used throughout the code. const Register idx = tmp1; @@ -3541,10 +3541,10 @@ address StubGenerator::generate_bigIntegerRightShift() { __ BIND(Exit); __ vzeroupper(); // Restore callee save registers. - __ pop(tmp5); + __ pop_ppx(tmp5); #ifdef _WIN64 - __ pop(tmp4); - __ pop(tmp3); + __ pop_ppx(tmp4); + __ pop_ppx(tmp3); restore_arg_regs(); #endif __ leave(); // required for proper stackwalking of RuntimeStub frame @@ -3598,10 +3598,10 @@ address StubGenerator::generate_bigIntegerLeftShift() { // For windows, since last argument is on stack, we need to move it to the appropriate register. __ movl(totalNumIter, Address(rsp, 6 * wordSize)); // Save callee save registers. - __ push(tmp3); - __ push(tmp4); + __ push_ppx(tmp3); + __ push_ppx(tmp4); #endif - __ push(tmp5); + __ push_ppx(tmp5); // Rename temps used throughout the code const Register idx = tmp1; @@ -3666,10 +3666,10 @@ address StubGenerator::generate_bigIntegerLeftShift() { __ BIND(Exit); __ vzeroupper(); // Restore callee save registers. - __ pop(tmp5); + __ pop_ppx(tmp5); #ifdef _WIN64 - __ pop(tmp4); - __ pop(tmp3); + __ pop_ppx(tmp4); + __ pop_ppx(tmp3); restore_arg_regs(); #endif __ leave(); // required for proper stackwalking of RuntimeStub frame @@ -3813,7 +3813,7 @@ address StubGenerator::generate_cont_thaw(StubId stub_id) { if (return_barrier) { // Preserve possible return value from a method returning to the return barrier. - __ push(rax); + __ push_ppx(rax); __ push_d(xmm0); } @@ -3826,7 +3826,7 @@ address StubGenerator::generate_cont_thaw(StubId stub_id) { // Restore return value from a method returning to the return barrier. // No safepoint in the call to thaw, so even an oop return value should be OK. __ pop_d(xmm0); - __ pop(rax); + __ pop_ppx(rax); } #ifdef ASSERT @@ -3852,7 +3852,7 @@ address StubGenerator::generate_cont_thaw(StubId stub_id) { if (return_barrier) { // Preserve possible return value from a method returning to the return barrier. (Again.) - __ push(rax); + __ push_ppx(rax); __ push_d(xmm0); } @@ -3866,7 +3866,7 @@ address StubGenerator::generate_cont_thaw(StubId stub_id) { // Restore return value from a method returning to the return barrier. (Again.) // No safepoint in the call to thaw, so even an oop return value should be OK. __ pop_d(xmm0); - __ pop(rax); + __ pop_ppx(rax); } else { // Return 0 (success) from doYield. __ xorptr(rax, rax); @@ -3882,7 +3882,7 @@ address StubGenerator::generate_cont_thaw(StubId stub_id) { __ movptr(c_rarg1, Address(rsp, wordSize)); // return address // rax still holds the original exception oop, save it before the call - __ push(rax); + __ push_ppx(rax); __ call_VM_leaf(CAST_FROM_FN_PTR(address, SharedRuntime::exception_handler_for_return_address), 2); __ movptr(rbx, rax); @@ -3891,7 +3891,7 @@ address StubGenerator::generate_cont_thaw(StubId stub_id) { // rax: exception oop // rbx: exception handler // rdx: exception pc - __ pop(rax); + __ pop_ppx(rax); __ verify_oop(rax); __ pop(rbp); // pop out RBP here too __ pop(rdx); diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64_aes.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64_aes.cpp index 2cd1ed24fcd..6f698b954ad 100644 --- a/src/hotspot/cpu/x86/stubGenerator_x86_64_aes.cpp +++ b/src/hotspot/cpu/x86/stubGenerator_x86_64_aes.cpp @@ -279,14 +279,14 @@ address StubGenerator::generate_galoisCounterMode_AESCrypt() { #endif __ enter(); // Save state before entering routine - __ push(r12);//holds pointer to avx512_subkeyHtbl - __ push(r14);//holds CTR_CHECK value to check for overflow - __ push(r15);//holds number of rounds - __ push(rbx);//scratch register + __ push_ppx(r12);//holds pointer to avx512_subkeyHtbl + __ push_ppx(r14);//holds CTR_CHECK value to check for overflow + __ push_ppx(r15);//holds number of rounds + __ push_ppx(rbx);//scratch register #ifdef _WIN64 // on win64, fill len_reg from stack position - __ push(rsi); - __ push(rdi); + __ push_ppx(rsi); + __ push_ppx(rdi); __ movptr(key, key_mem); __ movptr(state, state_mem); #endif @@ -304,15 +304,15 @@ address StubGenerator::generate_galoisCounterMode_AESCrypt() { // Restore state before leaving routine #ifdef _WIN64 __ lea(rsp, Address(rbp, -6 * wordSize)); - __ pop(rdi); - __ pop(rsi); + __ pop_ppx(rdi); + __ pop_ppx(rsi); #else __ lea(rsp, Address(rbp, -4 * wordSize)); #endif - __ pop(rbx); - __ pop(r15); - __ pop(r14); - __ pop(r12); + __ pop_ppx(rbx); + __ pop_ppx(r15); + __ pop_ppx(r14); + __ pop_ppx(r12); __ leave(); // required for proper stackwalking of RuntimeStub frame __ ret(0); @@ -364,15 +364,15 @@ address StubGenerator::generate_avx2_galoisCounterMode_AESCrypt() { #endif __ enter(); // Save state before entering routine - __ push(r12); - __ push(r13); - __ push(r14); - __ push(r15); - __ push(rbx); + __ push_ppx(r12); + __ push_ppx(r13); + __ push_ppx(r14); + __ push_ppx(r15); + __ push_ppx(rbx); #ifdef _WIN64 // on win64, fill len_reg from stack position - __ push(rsi); - __ push(rdi); + __ push_ppx(rsi); + __ push_ppx(rdi); __ movptr(key, key_mem); __ movptr(state, state_mem); #endif @@ -390,14 +390,14 @@ address StubGenerator::generate_avx2_galoisCounterMode_AESCrypt() { __ movq(rsp, r14); // Restore state before leaving routine #ifdef _WIN64 - __ pop(rdi); - __ pop(rsi); + __ pop_ppx(rdi); + __ pop_ppx(rsi); #endif - __ pop(rbx); - __ pop(r15); - __ pop(r14); - __ pop(r13); - __ pop(r12); + __ pop_ppx(rbx); + __ pop_ppx(r15); + __ pop_ppx(r14); + __ pop_ppx(r13); + __ pop_ppx(r12); __ leave(); // required for proper stackwalking of RuntimeStub frame __ ret(0); @@ -434,10 +434,10 @@ address StubGenerator::generate_counterMode_VectorAESCrypt() { #endif __ enter(); // Save state before entering routine - __ push(r12); - __ push(r13); - __ push(r14); - __ push(r15); + __ push_ppx(r12); + __ push_ppx(r13); + __ push_ppx(r14); + __ push_ppx(r15); #ifdef _WIN64 // on win64, fill len_reg from stack position __ movl(len_reg, len_mem); @@ -445,26 +445,26 @@ address StubGenerator::generate_counterMode_VectorAESCrypt() { __ movptr(used_addr, used_mem); __ movl(used, Address(used_addr, 0)); #else - __ push(len_reg); // Save + __ push_ppx(len_reg); // Save __ movptr(used_addr, used_mem); __ movl(used, Address(used_addr, 0)); #endif - __ push(rbx); + __ push_ppx(rbx); aesctr_encrypt(from, to, key, counter, len_reg, used, used_addr, saved_encCounter_start); __ vzeroupper(); // Restore state before leaving routine - __ pop(rbx); + __ pop_ppx(rbx); #ifdef _WIN64 __ movl(rax, len_mem); // return length #else - __ pop(rax); // return length + __ pop_ppx(rax); // return length #endif - __ pop(r15); - __ pop(r14); - __ pop(r13); - __ pop(r12); + __ pop_ppx(r15); + __ pop_ppx(r14); + __ pop_ppx(r13); + __ pop_ppx(r12); __ leave(); // required for proper stackwalking of RuntimeStub frame __ ret(0); @@ -576,12 +576,12 @@ address StubGenerator::generate_counterMode_AESCrypt_Parallel() { __ movptr(used_addr, used_mem); __ movl(used, Address(used_addr, 0)); #else - __ push(len_reg); // Save + __ push_ppx(len_reg); // Save __ movptr(used_addr, used_mem); __ movl(used, Address(used_addr, 0)); #endif - __ push(rbx); // Save RBX + __ push_ppx(rbx); // Save RBX __ movdqu(xmm_curr_counter, Address(counter, 0x00)); // initialize counter with initial counter __ movdqu(xmm_counter_shuf_mask, ExternalAddress(counter_shuffle_mask_addr()), pos /*rscratch*/); __ pshufb(xmm_curr_counter, xmm_counter_shuf_mask); //counter is shuffled @@ -767,14 +767,14 @@ address StubGenerator::generate_counterMode_AESCrypt_Parallel() { __ BIND(L_exit); __ pshufb(xmm_curr_counter, xmm_counter_shuf_mask); //counter is shuffled back. __ movdqu(Address(counter, 0), xmm_curr_counter); //save counter back - __ pop(rbx); // pop the saved RBX. + __ pop_ppx(rbx); // pop the saved RBX. #ifdef _WIN64 __ movl(rax, len_mem); __ movptr(r13, Address(rsp, saved_r13_offset * wordSize)); __ movptr(r14, Address(rsp, saved_r14_offset * wordSize)); __ addptr(rsp, 2 * wordSize); #else - __ pop(rax); // return 'len' + __ pop_ppx(rax); // return 'len' #endif __ leave(); // required for proper stackwalking of RuntimeStub frame __ ret(0); @@ -810,9 +810,9 @@ address StubGenerator::generate_cipherBlockChaining_decryptVectorAESCrypt() { // on win64, fill len_reg from stack position __ movl(len_reg, len_mem); #else - __ push(len_reg); // Save + __ push_ppx(len_reg); // Save #endif - __ push(rbx); + __ push_ppx(rbx); __ vzeroupper(); // Temporary variable declaration for swapping key bytes @@ -1046,11 +1046,11 @@ address StubGenerator::generate_cipherBlockChaining_decryptVectorAESCrypt() { __ BIND(Lcbc_exit); __ vzeroupper(); - __ pop(rbx); + __ pop_ppx(rbx); #ifdef _WIN64 __ movl(rax, len_mem); #else - __ pop(rax); // return length + __ pop_ppx(rax); // return length #endif __ leave(); // required for proper stackwalking of RuntimeStub frame __ ret(0); @@ -1301,7 +1301,7 @@ address StubGenerator::generate_cipherBlockChaining_encryptAESCrypt() { // on win64, fill len_reg from stack position __ movl(len_reg, len_mem); #else - __ push(len_reg); // Save + __ push_ppx(len_reg); // Save #endif const XMMRegister xmm_key_shuf_mask = xmm_temp; // used temporarily to swap key bytes up front @@ -1343,7 +1343,7 @@ address StubGenerator::generate_cipherBlockChaining_encryptAESCrypt() { #ifdef _WIN64 __ movl(rax, len_mem); #else - __ pop(rax); // return length + __ pop_ppx(rax); // return length #endif __ leave(); // required for proper stackwalking of RuntimeStub frame __ ret(0); @@ -1456,9 +1456,9 @@ address StubGenerator::generate_cipherBlockChaining_decryptAESCrypt_Parallel() { // on win64, fill len_reg from stack position __ movl(len_reg, len_mem); #else - __ push(len_reg); // Save + __ push_ppx(len_reg); // Save #endif - __ push(rbx); + __ push_ppx(rbx); // the java expanded key ordering is rotated one position from what we want // so we start from 0x10 here and hit 0x00 last const XMMRegister xmm_key_shuf_mask = xmm1; // used temporarily to swap key bytes up front @@ -1646,11 +1646,11 @@ __ opc(xmm_result3, src_reg); \ __ BIND(L_exit); __ movdqu(Address(rvec, 0), xmm_prev_block_cipher); // final value of r stored in rvec of CipherBlockChaining object - __ pop(rbx); + __ pop_ppx(rbx); #ifdef _WIN64 __ movl(rax, len_mem); #else - __ pop(rax); // return length + __ pop_ppx(rax); // return length #endif __ leave(); // required for proper stackwalking of RuntimeStub frame __ ret(0); @@ -1801,8 +1801,8 @@ void StubGenerator::aesecb_encrypt(Register src_addr, Register dest_addr, Regist const Register rounds = r12; Label NO_PARTS, LOOP, Loop_start, LOOP2, AES192, END_LOOP, AES256, REMAINDER, LAST2, END, KEY_192, KEY_256, EXIT; - __ push(r13); - __ push(r12); + __ push_ppx(r13); + __ push_ppx(r12); // For EVEX with VL and BW, provide a standard mask, VL = 128 will guide the merge // context for the registers used, where all instructions below are using 128-bit mode @@ -1811,8 +1811,8 @@ void StubGenerator::aesecb_encrypt(Register src_addr, Register dest_addr, Regist __ movl(rax, 0xffff); __ kmovql(k1, rax); } - __ push(len); // Save - __ push(rbx); + __ push_ppx(len); // Save + __ push_ppx(rbx); __ vzeroupper(); @@ -1999,10 +1999,10 @@ void StubGenerator::aesecb_encrypt(Register src_addr, Register dest_addr, Regist __ evpxorq(xmm21, xmm21, xmm21, Assembler::AVX_512bit); __ evpxorq(xmm22, xmm22, xmm22, Assembler::AVX_512bit); __ bind(EXIT); - __ pop(rbx); - __ pop(rax); // return length - __ pop(r12); - __ pop(r13); + __ pop_ppx(rbx); + __ pop_ppx(rax); // return length + __ pop_ppx(r12); + __ pop_ppx(r13); } // AES-ECB Decrypt Operation @@ -2011,8 +2011,8 @@ void StubGenerator::aesecb_decrypt(Register src_addr, Register dest_addr, Regist Label NO_PARTS, LOOP, Loop_start, LOOP2, AES192, END_LOOP, AES256, REMAINDER, LAST2, END, KEY_192, KEY_256, EXIT; const Register pos = rax; const Register rounds = r12; - __ push(r13); - __ push(r12); + __ push_ppx(r13); + __ push_ppx(r12); // For EVEX with VL and BW, provide a standard mask, VL = 128 will guide the merge // context for the registers used, where all instructions below are using 128-bit mode @@ -2022,8 +2022,8 @@ void StubGenerator::aesecb_decrypt(Register src_addr, Register dest_addr, Regist __ kmovql(k1, rax); } - __ push(len); // Save - __ push(rbx); + __ push_ppx(len); // Save + __ push_ppx(rbx); __ vzeroupper(); @@ -2210,10 +2210,10 @@ void StubGenerator::aesecb_decrypt(Register src_addr, Register dest_addr, Regist __ evpxorq(xmm22, xmm22, xmm22, Assembler::AVX_512bit); __ bind(EXIT); - __ pop(rbx); - __ pop(rax); // return length - __ pop(r12); - __ pop(r13); + __ pop_ppx(rbx); + __ pop_ppx(rax); // return length + __ pop_ppx(r12); + __ pop_ppx(r13); } diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64_arraycopy.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64_arraycopy.cpp index 9dac1eab002..743457f87af 100644 --- a/src/hotspot/cpu/x86/stubGenerator_x86_64_arraycopy.cpp +++ b/src/hotspot/cpu/x86/stubGenerator_x86_64_arraycopy.cpp @@ -2943,7 +2943,7 @@ address StubGenerator::generate_generic_copy(address byte_copy_entry, address sh __ enter(); // required for proper stackwalking of RuntimeStub frame #ifdef _WIN64 - __ push(rklass_tmp); // rdi is callee-save on Windows + __ push_ppx(rklass_tmp); // rdi is callee-save on Windows #endif // bump this on entry, not on exit: @@ -3077,7 +3077,7 @@ address StubGenerator::generate_generic_copy(address byte_copy_entry, address sh __ andl(rax_lh, Klass::_lh_log2_element_size_mask); // rax_lh -> rax_elsize #ifdef _WIN64 - __ pop(rklass_tmp); // Restore callee-save rdi + __ pop_ppx(rklass_tmp); // Restore callee-save rdi #endif // next registers should be set before the jump to corresponding stub @@ -3149,7 +3149,7 @@ __ BIND(L_objArray); __ movl2ptr(count, r11_length); // length __ BIND(L_plain_copy); #ifdef _WIN64 - __ pop(rklass_tmp); // Restore callee-save rdi + __ pop_ppx(rklass_tmp); // Restore callee-save rdi #endif __ jump(RuntimeAddress(oop_copy_entry)); @@ -3191,7 +3191,7 @@ __ BIND(L_checkcast_copy); assert_clean_int(sco_temp, rax); #ifdef _WIN64 - __ pop(rklass_tmp); // Restore callee-save rdi + __ pop_ppx(rklass_tmp); // Restore callee-save rdi #endif // the checkcast_copy loop needs two extra arguments: @@ -3204,7 +3204,7 @@ __ BIND(L_checkcast_copy); __ BIND(L_failed); #ifdef _WIN64 - __ pop(rklass_tmp); // Restore callee-save rdi + __ pop_ppx(rklass_tmp); // Restore callee-save rdi #endif __ xorptr(rax, rax); __ notptr(rax); // return -1 diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64_cos.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64_cos.cpp index 67017e7559a..8cb6ead21fd 100644 --- a/src/hotspot/cpu/x86/stubGenerator_x86_64_cos.cpp +++ b/src/hotspot/cpu/x86/stubGenerator_x86_64_cos.cpp @@ -185,11 +185,11 @@ address StubGenerator::generate_libmCos() { __ enter(); // required for proper stackwalking of RuntimeStub frame #ifdef _WIN64 - __ push(rsi); - __ push(rdi); + __ push_ppx(rsi); + __ push_ppx(rdi); #endif - __ push(rbx); + __ push_ppx(rbx); __ subq(rsp, 16); __ movsd(Address(rsp, 8), xmm0); @@ -609,11 +609,11 @@ address StubGenerator::generate_libmCos() { __ bind(B1_4); __ addq(rsp, 16); - __ pop(rbx); + __ pop_ppx(rbx); #ifdef _WIN64 - __ pop(rdi); - __ pop(rsi); + __ pop_ppx(rdi); + __ pop_ppx(rsi); #endif __ leave(); // required for proper stackwalking of RuntimeStub frame diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64_ghash.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64_ghash.cpp index 37485bac1d1..6f05b1ab5e6 100644 --- a/src/hotspot/cpu/x86/stubGenerator_x86_64_ghash.cpp +++ b/src/hotspot/cpu/x86/stubGenerator_x86_64_ghash.cpp @@ -105,7 +105,7 @@ address StubGenerator::generate_ghash_processBlocks() { __ enter(); - __ push(rbx); // scratch + __ push_ppx(rbx); // scratch __ movdqu(xmm_temp10, ExternalAddress(ghash_long_swap_mask_addr()), rbx /*rscratch*/); @@ -206,7 +206,7 @@ address StubGenerator::generate_ghash_processBlocks() { __ pshufb(xmm_temp6, xmm_temp10); // Byte swap 16-byte result __ movdqu(Address(state, 0), xmm_temp6); // store the result - __ pop(rbx); + __ pop_ppx(rbx); __ leave(); __ ret(0); @@ -229,11 +229,11 @@ address StubGenerator::generate_avx_ghash_processBlocks() { const Register data = c_rarg2; const Register blocks = c_rarg3; __ enter(); - __ push(rbx); + __ push_ppx(rbx); avx_ghash(state, htbl, data, blocks); - __ pop(rbx); + __ pop_ppx(rbx); __ leave(); // required for proper stackwalking of RuntimeStub frame __ ret(0); diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64_kyber.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64_kyber.cpp index 6e0af2563fa..3e5593322d5 100644 --- a/src/hotspot/cpu/x86/stubGenerator_x86_64_kyber.cpp +++ b/src/hotspot/cpu/x86/stubGenerator_x86_64_kyber.cpp @@ -603,7 +603,7 @@ address generate_kyberNttMult_avx512(StubGenerator *stubgen, const Register perms = r11; const Register loopCnt = r12; - __ push(r12); + __ push_ppx(r12); __ movl(loopCnt, 2); Label Loop; @@ -692,7 +692,7 @@ address generate_kyberNttMult_avx512(StubGenerator *stubgen, __ subl(loopCnt, 1); __ jcc(Assembler::greater, Loop); - __ pop(r12); + __ pop_ppx(r12); __ leave(); // required for proper stackwalking of RuntimeStub frame __ mov64(rax, 0); // return 0 diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64_poly1305.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64_poly1305.cpp index 461422b8afd..c80b2d16181 100644 --- a/src/hotspot/cpu/x86/stubGenerator_x86_64_poly1305.cpp +++ b/src/hotspot/cpu/x86/stubGenerator_x86_64_poly1305.cpp @@ -916,15 +916,15 @@ address StubGenerator::generate_poly1305_processBlocks() { __ enter(); // Save all 'SOE' registers - __ push(rbx); + __ push_ppx(rbx); #ifdef _WIN64 - __ push(rsi); - __ push(rdi); + __ push_ppx(rsi); + __ push_ppx(rdi); #endif - __ push(r12); - __ push(r13); - __ push(r14); - __ push(r15); + __ push_ppx(r12); + __ push_ppx(r13); + __ push_ppx(r14); + __ push_ppx(r15); // Register Map const Register input = rdi; // msg @@ -1016,15 +1016,15 @@ address StubGenerator::generate_poly1305_processBlocks() { // Write output poly1305_limbs_out(a0, a1, a2, accumulator, t0, t1); - __ pop(r15); - __ pop(r14); - __ pop(r13); - __ pop(r12); + __ pop_ppx(r15); + __ pop_ppx(r14); + __ pop_ppx(r13); + __ pop_ppx(r12); #ifdef _WIN64 - __ pop(rdi); - __ pop(rsi); + __ pop_ppx(rdi); + __ pop_ppx(rsi); #endif - __ pop(rbx); + __ pop_ppx(rbx); __ leave(); __ ret(0); @@ -1169,7 +1169,7 @@ void StubGenerator::poly1305_process_blocks_avx2( // Setup stack frame // Save rbp and rsp - __ push(rbp); + __ push_ppx(rbp); __ movq(rbp, rsp); // Align stack and reserve space __ andq(rsp, -32); @@ -1483,7 +1483,7 @@ void StubGenerator::poly1305_process_blocks_avx2( // Save rbp and rsp; clear stack frame __ movq(rsp, rbp); - __ pop(rbp); + __ pop_ppx(rbp); } 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 d0ac050dbf9..c439e0b370f 100644 --- a/src/hotspot/cpu/x86/stubGenerator_x86_64_poly_mont.cpp +++ b/src/hotspot/cpu/x86/stubGenerator_x86_64_poly_mont.cpp @@ -574,14 +574,14 @@ address StubGenerator::generate_intpoly_montgomeryMult_P256() { montgomeryMultiply(aLimbs, bLimbs, rLimbs, tmp, _masm); } else { assert(VM_Version::supports_avxifma(), "Require AVX_IFMA support"); - __ push(r12); - __ push(r13); - __ push(r14); + __ push_ppx(r12); + __ push_ppx(r13); + __ push_ppx(r14); #ifdef _WIN64 - __ push(rsi); - __ push(rdi); + __ push_ppx(rsi); + __ push_ppx(rdi); #endif - __ push(rbp); + __ push_ppx(rbp); __ movq(rbp, rsp); __ andq(rsp, -32); __ subptr(rsp, 32); @@ -608,14 +608,14 @@ address StubGenerator::generate_intpoly_montgomeryMult_P256() { tmp1, tmp2, tmp3, tmp4, tmp5, tmp6, tmp7, _masm); __ movq(rsp, rbp); - __ pop(rbp); + __ pop_ppx(rbp); #ifdef _WIN64 - __ pop(rdi); - __ pop(rsi); + __ pop_ppx(rdi); + __ pop_ppx(rsi); #endif - __ pop(r14); - __ pop(r13); - __ pop(r12); + __ pop_ppx(r14); + __ pop_ppx(r13); + __ pop_ppx(r12); } __ leave(); diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64_sha3.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64_sha3.cpp index 1e245952118..f9d876f34f3 100644 --- a/src/hotspot/cpu/x86/stubGenerator_x86_64_sha3.cpp +++ b/src/hotspot/cpu/x86/stubGenerator_x86_64_sha3.cpp @@ -130,9 +130,9 @@ static address generate_sha3_implCompress(StubId stub_id, __ enter(); - __ push(r12); - __ push(r13); - __ push(r14); + __ push_ppx(r12); + __ push_ppx(r13); + __ push_ppx(r14); #ifdef _WIN64 // on win64, fill limit from stack position @@ -309,9 +309,9 @@ static address generate_sha3_implCompress(StubId stub_id, __ evmovdquq(Address(state, i * 40), k5, xmm(i), true, Assembler::AVX_512bit); } - __ pop(r14); - __ pop(r13); - __ pop(r12); + __ pop_ppx(r14); + __ pop_ppx(r13); + __ pop_ppx(r12); __ leave(); // required for proper stackwalking of RuntimeStub frame __ ret(0); diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64_sin.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64_sin.cpp index 67ac2fa6b87..5290e737581 100644 --- a/src/hotspot/cpu/x86/stubGenerator_x86_64_sin.cpp +++ b/src/hotspot/cpu/x86/stubGenerator_x86_64_sin.cpp @@ -195,11 +195,11 @@ address StubGenerator::generate_libmSin() { __ enter(); // required for proper stackwalking of RuntimeStub frame #ifdef _WIN64 - __ push(rsi); - __ push(rdi); + __ push_ppx(rsi); + __ push_ppx(rdi); #endif - __ push(rbx); + __ push_ppx(rbx); __ subq(rsp, 16); __ movsd(Address(rsp, 8), xmm0); __ movl(rax, Address(rsp, 12)); @@ -635,11 +635,11 @@ address StubGenerator::generate_libmSin() { __ bind(B1_4); __ addq(rsp, 16); - __ pop(rbx); + __ pop_ppx(rbx); #ifdef _WIN64 - __ pop(rdi); - __ pop(rsi); + __ pop_ppx(rdi); + __ pop_ppx(rsi); #endif __ leave(); // required for proper stackwalking of RuntimeStub frame diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64_tan.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64_tan.cpp index f73885b18c2..4f14414652c 100644 --- a/src/hotspot/cpu/x86/stubGenerator_x86_64_tan.cpp +++ b/src/hotspot/cpu/x86/stubGenerator_x86_64_tan.cpp @@ -483,11 +483,11 @@ address StubGenerator::generate_libmTan() { __ enter(); // required for proper stackwalking of RuntimeStub frame #ifdef _WIN64 - __ push(rsi); - __ push(rdi); + __ push_ppx(rsi); + __ push_ppx(rdi); #endif - __ push(rbx); + __ push_ppx(rbx); __ subq(rsp, 16); __ movsd(Address(rsp, 8), xmm0); @@ -1015,11 +1015,11 @@ address StubGenerator::generate_libmTan() { __ bind(B1_4); __ addq(rsp, 16); - __ pop(rbx); + __ pop_ppx(rbx); #ifdef _WIN64 - __ pop(rdi); - __ pop(rsi); + __ pop_ppx(rdi); + __ pop_ppx(rsi); #endif __ leave(); // required for proper stackwalking of RuntimeStub frame From 79f9d8d832a589b74cc014289ef84a1efe529468 Mon Sep 17 00:00:00 2001 From: "Y. Srinivas Ramakrishna" Date: Wed, 23 Jul 2025 00:23:20 +0000 Subject: [PATCH 48/94] 8350050: Shenandoah: Disable and purge allocation pacing support Reviewed-by: wkemper, shade, kdnilsen --- .../shenandoah/mode/shenandoahPassiveMode.cpp | 3 - .../shenandoah/shenandoahCollectorPolicy.cpp | 2 +- .../gc/shenandoah/shenandoahConcurrentGC.cpp | 15 +- .../gc/shenandoah/shenandoahControlThread.cpp | 20 - .../gc/shenandoah/shenandoahController.cpp | 8 - .../gc/shenandoah/shenandoahController.hpp | 13 +- .../share/gc/shenandoah/shenandoahFreeSet.cpp | 4 - .../shenandoahGenerationalControlThread.cpp | 23 +- .../shenandoahGenerationalEvacuationTask.cpp | 4 - .../shenandoah/shenandoahGenerationalHeap.cpp | 12 - .../share/gc/shenandoah/shenandoahHeap.cpp | 43 +-- .../shenandoahHeapRegion.inline.hpp | 4 - .../share/gc/shenandoah/shenandoahPacer.cpp | 341 ------------------ .../share/gc/shenandoah/shenandoahPacer.hpp | 135 ------- .../gc/shenandoah/shenandoahPacer.inline.hpp | 73 ---- .../gc/shenandoah/shenandoahPhaseTimings.cpp | 11 - .../gc/shenandoah/shenandoahPhaseTimings.hpp | 2 - .../gc/shenandoah/shenandoah_globals.hpp | 34 -- .../generational/TestConcurrentEvac.java | 2 +- .../gc/shenandoah/options/TestPacing.java | 44 --- 20 files changed, 7 insertions(+), 786 deletions(-) delete mode 100644 src/hotspot/share/gc/shenandoah/shenandoahPacer.cpp delete mode 100644 src/hotspot/share/gc/shenandoah/shenandoahPacer.hpp delete mode 100644 src/hotspot/share/gc/shenandoah/shenandoahPacer.inline.hpp delete mode 100644 test/hotspot/jtreg/gc/shenandoah/options/TestPacing.java diff --git a/src/hotspot/share/gc/shenandoah/mode/shenandoahPassiveMode.cpp b/src/hotspot/share/gc/shenandoah/mode/shenandoahPassiveMode.cpp index 296a1979b01..20f8ecc43e8 100644 --- a/src/hotspot/share/gc/shenandoah/mode/shenandoahPassiveMode.cpp +++ b/src/hotspot/share/gc/shenandoah/mode/shenandoahPassiveMode.cpp @@ -36,9 +36,6 @@ void ShenandoahPassiveMode::initialize_flags() const { FLAG_SET_DEFAULT(ExplicitGCInvokesConcurrent, false); FLAG_SET_DEFAULT(ShenandoahImplicitGCInvokesConcurrent, false); - // Passive runs with max speed for allocation, because GC is always STW - SHENANDOAH_ERGO_DISABLE_FLAG(ShenandoahPacing); - // No need for evacuation reserve with Full GC, only for Degenerated GC. if (!ShenandoahDegeneratedGC) { SHENANDOAH_ERGO_OVERRIDE_DEFAULT(ShenandoahEvacReserve, 0); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahCollectorPolicy.cpp b/src/hotspot/share/gc/shenandoah/shenandoahCollectorPolicy.cpp index 0169795d6f6..5136987578a 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahCollectorPolicy.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahCollectorPolicy.cpp @@ -192,7 +192,7 @@ bool ShenandoahCollectorPolicy::should_handle_requested_gc(GCCause::Cause cause) void ShenandoahCollectorPolicy::print_gc_stats(outputStream* out) const { out->print_cr("Under allocation pressure, concurrent cycles may cancel, and either continue cycle"); out->print_cr("under stop-the-world pause or result in stop-the-world Full GC. Increase heap size,"); - out->print_cr("tune GC heuristics, set more aggressive pacing delay, or lower allocation rate"); + out->print_cr("tune GC heuristics, or lower allocation rate"); out->print_cr("to avoid Degenerated and Full GC cycles. Abbreviated cycles are those which found"); out->print_cr("enough regions with no live objects to skip evacuation."); out->cr(); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp index 2f264cae70f..81154aff9f0 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp @@ -205,7 +205,7 @@ bool ShenandoahConcurrentGC::collect(GCCause::Cause cause) { entry_concurrent_update_refs_prepare(heap); // Perform update-refs phase. - if (ShenandoahVerify || ShenandoahPacing) { + if (ShenandoahVerify) { vmop_entry_init_update_refs(); } @@ -629,9 +629,7 @@ void ShenandoahConcurrentGC::entry_reset_after_collect() { void ShenandoahConcurrentGC::op_reset() { ShenandoahHeap* const heap = ShenandoahHeap::heap(); - if (ShenandoahPacing) { - heap->pacer()->setup_for_reset(); - } + // If it is old GC bootstrap cycle, always clear bitmap for global gen // to ensure bitmap for old gen is clear for old GC cycle after this. if (_do_old_gc_bootstrap) { @@ -743,9 +741,6 @@ void ShenandoahConcurrentGC::op_init_mark() { ShenandoahCodeRoots::arm_nmethods_for_mark(); ShenandoahStackWatermark::change_epoch_id(); - if (ShenandoahPacing) { - heap->pacer()->setup_for_mark(); - } { ShenandoahTimingsTracker timing(ShenandoahPhaseTimings::init_propagate_gc_state); @@ -806,9 +801,6 @@ void ShenandoahConcurrentGC::op_final_mark() { ShenandoahCodeRoots::arm_nmethods_for_evac(); ShenandoahStackWatermark::change_epoch_id(); - if (ShenandoahPacing) { - heap->pacer()->setup_for_evac(); - } } else { if (ShenandoahVerify) { ShenandoahTimingsTracker v(ShenandoahPhaseTimings::final_mark_verify); @@ -1136,9 +1128,6 @@ void ShenandoahConcurrentGC::op_init_update_refs() { ShenandoahTimingsTracker v(ShenandoahPhaseTimings::init_update_refs_verify); heap->verifier()->verify_before_update_refs(); } - if (ShenandoahPacing) { - heap->pacer()->setup_for_update_refs(); - } } void ShenandoahConcurrentGC::op_update_refs() { diff --git a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp index c0f3cf1a6a1..795bcc1cf92 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp @@ -34,7 +34,6 @@ #include "gc/shenandoah/shenandoahGeneration.hpp" #include "gc/shenandoah/shenandoahHeap.inline.hpp" #include "gc/shenandoah/shenandoahMonitoringSupport.hpp" -#include "gc/shenandoah/shenandoahPacer.inline.hpp" #include "gc/shenandoah/shenandoahUtils.hpp" #include "logging/log.hpp" #include "memory/metaspaceStats.hpp" @@ -69,9 +68,6 @@ void ShenandoahControlThread::run_service() { const bool is_gc_requested = _gc_requested.is_set(); const GCCause::Cause requested_gc_cause = _requested_gc_cause; - // This control loop iteration has seen this much allocation. - const size_t allocs_seen = reset_allocs_seen(); - // Choose which GC mode to run in. The block below should select a single mode. GCMode mode = none; GCCause::Cause cause = GCCause::_last_gc_cause; @@ -204,9 +200,6 @@ void ShenandoahControlThread::run_service() { // Commit worker statistics to cycle data heap->phase_timings()->flush_par_workers_to_cycle(); - if (ShenandoahPacing) { - heap->pacer()->flush_stats_to_cycle(); - } // Print GC stats for current cycle { @@ -215,9 +208,6 @@ void ShenandoahControlThread::run_service() { ResourceMark rm; LogStream ls(lt); heap->phase_timings()->print_cycle_on(&ls); - if (ShenandoahPacing) { - heap->pacer()->print_cycle_on(&ls); - } } } @@ -226,16 +216,6 @@ void ShenandoahControlThread::run_service() { // Print Metaspace change following GC (if logging is enabled). MetaspaceUtils::print_metaspace_change(meta_sizes); - - // GC is over, we are at idle now - if (ShenandoahPacing) { - heap->pacer()->setup_for_idle(); - } - } else { - // Report to pacer that we have seen this many words allocated - if (ShenandoahPacing && (allocs_seen > 0)) { - heap->pacer()->report_alloc(allocs_seen); - } } // Check if we have seen a new target for soft max heap size or if a gc was requested. diff --git a/src/hotspot/share/gc/shenandoah/shenandoahController.cpp b/src/hotspot/share/gc/shenandoah/shenandoahController.cpp index 52182b092c9..c5aa4e6e44b 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahController.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahController.cpp @@ -29,14 +29,6 @@ #include "gc/shenandoah/shenandoahHeap.hpp" #include "gc/shenandoah/shenandoahHeapRegion.inline.hpp" -void ShenandoahController::pacing_notify_alloc(size_t words) { - assert(ShenandoahPacing, "should only call when pacing is enabled"); - Atomic::add(&_allocs_seen, words, memory_order_relaxed); -} - -size_t ShenandoahController::reset_allocs_seen() { - return Atomic::xchg(&_allocs_seen, (size_t)0, memory_order_relaxed); -} void ShenandoahController::update_gc_id() { Atomic::inc(&_gc_id); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahController.hpp b/src/hotspot/share/gc/shenandoah/shenandoahController.hpp index d24f52cb3f1..a6a699fac3b 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahController.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahController.hpp @@ -37,11 +37,9 @@ class ShenandoahController: public ConcurrentGCThread { private: shenandoah_padding(0); - volatile size_t _allocs_seen; - shenandoah_padding(1); // A monotonically increasing GC count. volatile size_t _gc_id; - shenandoah_padding(2); + shenandoah_padding(1); protected: // While we could have a single lock for these, it may risk unblocking @@ -55,7 +53,6 @@ protected: public: ShenandoahController(): - _allocs_seen(0), _gc_id(0), _alloc_failure_waiters_lock(Mutex::safepoint-2, "ShenandoahAllocFailureGC_lock", true), _gc_waiters_lock(Mutex::safepoint-2, "ShenandoahRequestedGC_lock", true) @@ -76,14 +73,6 @@ public: // Notify threads waiting for GC to complete. void notify_alloc_failure_waiters(); - // This is called for every allocation. The control thread accumulates - // this value when idle. During the gc cycle, the control resets it - // and reports it to the pacer. - void pacing_notify_alloc(size_t words); - - // Zeros out the number of allocations seen since the last GC cycle. - size_t reset_allocs_seen(); - // Return the value of a monotonic increasing GC count, maintained by the control thread. size_t get_gc_id(); }; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahFreeSet.cpp b/src/hotspot/share/gc/shenandoah/shenandoahFreeSet.cpp index 8e303980cef..56a2ff7e01a 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahFreeSet.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahFreeSet.cpp @@ -1263,10 +1263,6 @@ HeapWord* ShenandoahFreeSet::allocate_contiguous(ShenandoahAllocRequest& req) { r->set_top(r->bottom() + used_words); } generation->increase_affiliated_region_count(num); - if (remainder != 0) { - // Record this remainder as allocation waste - _heap->notify_mutator_alloc_words(ShenandoahHeapRegion::region_size_words() - remainder, true); - } // retire_range_from_partition() will adjust bounds on Mutator free set if appropriate _partitions.retire_range_from_partition(ShenandoahFreeSetPartitionId::Mutator, beg, end); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp index 6b33d5207d0..54cf8b978df 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp @@ -37,7 +37,6 @@ #include "gc/shenandoah/shenandoahMonitoringSupport.hpp" #include "gc/shenandoah/shenandoahOldGC.hpp" #include "gc/shenandoah/shenandoahOldGeneration.hpp" -#include "gc/shenandoah/shenandoahPacer.inline.hpp" #include "gc/shenandoah/shenandoahUtils.hpp" #include "gc/shenandoah/shenandoahYoungGeneration.hpp" #include "logging/log.hpp" @@ -61,13 +60,9 @@ ShenandoahGenerationalControlThread::ShenandoahGenerationalControlThread() : void ShenandoahGenerationalControlThread::run_service() { - const int64_t wait_ms = ShenandoahPacing ? ShenandoahControlIntervalMin : 0; ShenandoahGCRequest request; while (!should_terminate()) { - // This control loop iteration has seen this much allocation. - const size_t allocs_seen = reset_allocs_seen(); - // Figure out if we have pending requests. check_for_request(request); @@ -77,11 +72,6 @@ void ShenandoahGenerationalControlThread::run_service() { if (request.cause != GCCause::_no_gc) { run_gc_cycle(request); - } else { - // Report to pacer that we have seen this many words allocated - if (ShenandoahPacing && (allocs_seen > 0)) { - _heap->pacer()->report_alloc(allocs_seen); - } } // If the cycle was cancelled, continue the next iteration to deal with it. Otherwise, @@ -90,7 +80,7 @@ void ShenandoahGenerationalControlThread::run_service() { MonitorLocker ml(&_control_lock, Mutex::_no_safepoint_check_flag); if (_requested_gc_cause == GCCause::_no_gc) { set_gc_mode(ml, none); - ml.wait(wait_ms); + ml.wait(); } } } @@ -309,11 +299,6 @@ void ShenandoahGenerationalControlThread::run_gc_cycle(const ShenandoahGCRequest // Print Metaspace change following GC (if logging is enabled). MetaspaceUtils::print_metaspace_change(meta_sizes); - // GC is over, we are at idle now - if (ShenandoahPacing) { - _heap->pacer()->setup_for_idle(); - } - // Check if we have seen a new target for soft max heap size or if a gc was requested. // Either of these conditions will attempt to uncommit regions. if (ShenandoahUncommit) { @@ -331,9 +316,6 @@ void ShenandoahGenerationalControlThread::run_gc_cycle(const ShenandoahGCRequest void ShenandoahGenerationalControlThread::process_phase_timings() const { // Commit worker statistics to cycle data _heap->phase_timings()->flush_par_workers_to_cycle(); - if (ShenandoahPacing) { - _heap->pacer()->flush_stats_to_cycle(); - } ShenandoahEvacuationTracker* evac_tracker = _heap->evac_tracker(); ShenandoahCycleStats evac_stats = evac_tracker->flush_cycle_to_global(); @@ -347,9 +329,6 @@ void ShenandoahGenerationalControlThread::process_phase_timings() const { _heap->phase_timings()->print_cycle_on(&ls); evac_tracker->print_evacuations_on(&ls, &evac_stats.workers, &evac_stats.mutators); - if (ShenandoahPacing) { - _heap->pacer()->print_cycle_on(&ls); - } } } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalEvacuationTask.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalEvacuationTask.cpp index ba9ef5979a8..29fd3258b6c 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalEvacuationTask.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalEvacuationTask.cpp @@ -29,7 +29,6 @@ #include "gc/shenandoah/shenandoahGenerationalHeap.hpp" #include "gc/shenandoah/shenandoahHeap.inline.hpp" #include "gc/shenandoah/shenandoahOldGeneration.hpp" -#include "gc/shenandoah/shenandoahPacer.hpp" #include "gc/shenandoah/shenandoahScanRemembered.inline.hpp" #include "gc/shenandoah/shenandoahUtils.hpp" #include "gc/shenandoah/shenandoahYoungGeneration.hpp" @@ -127,9 +126,6 @@ void ShenandoahGenerationalEvacuationTask::evacuate_and_promote_regions() { if (r->is_cset()) { assert(r->has_live(), "Region %zu should have been reclaimed early", r->index()); _heap->marked_object_iterate(r, &cl); - if (ShenandoahPacing) { - _heap->pacer()->report_evac(r->used() >> LogHeapWordSize); - } } else { maybe_promote_region(r); } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp index 5bfa526138d..a89fa76ba0f 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp @@ -827,19 +827,15 @@ private: assert(update_watermark >= r->bottom(), "sanity"); log_debug(gc)("Update refs worker " UINT32_FORMAT ", looking at region %zu", worker_id, r->index()); - bool region_progress = false; if (r->is_active() && !r->is_cset()) { if (r->is_young()) { _heap->marked_object_oop_iterate(r, &cl, update_watermark); - region_progress = true; } else if (r->is_old()) { if (gc_generation->is_global()) { _heap->marked_object_oop_iterate(r, &cl, update_watermark); - region_progress = true; } // Otherwise, this is an old region in a young or mixed cycle. Process it during a second phase, below. - // Don't bother to report pacing progress in this case. } else { // Because updating of references runs concurrently, it is possible that a FREE inactive region transitions // to a non-free active region while this loop is executing. Whenever this happens, the changing of a region's @@ -857,10 +853,6 @@ private: } } - if (region_progress && ShenandoahPacing) { - _heap->pacer()->report_update_refs(pointer_delta(update_watermark, r->bottom())); - } - if (_heap->check_cancelled_gc_and_yield(CONCURRENT)) { return; } @@ -916,10 +908,6 @@ private: assert(clusters * cluster_size == assignment._chunk_size, "Chunk assignment must align on cluster boundaries"); scanner->process_region_slice(r, assignment._chunk_offset, clusters, end_of_range, &cl, true, worker_id); } - - if (ShenandoahPacing) { - _heap->pacer()->report_update_refs(pointer_delta(end_of_range, start_of_range)); - } } } } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp index 536f48dff37..2dc768363d1 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp @@ -63,7 +63,6 @@ #include "gc/shenandoah/shenandoahMemoryPool.hpp" #include "gc/shenandoah/shenandoahMonitoringSupport.hpp" #include "gc/shenandoah/shenandoahOldGeneration.hpp" -#include "gc/shenandoah/shenandoahPacer.inline.hpp" #include "gc/shenandoah/shenandoahPadding.hpp" #include "gc/shenandoah/shenandoahParallelCleaning.inline.hpp" #include "gc/shenandoah/shenandoahPhaseTimings.hpp" @@ -470,11 +469,6 @@ jint ShenandoahHeap::initialize() { _phase_timings = new ShenandoahPhaseTimings(max_workers()); ShenandoahCodeRoots::initialize(); - if (ShenandoahPacing) { - _pacer = new ShenandoahPacer(this); - _pacer->setup_for_idle(); - } - initialize_controller(); if (ShenandoahUncommit) { @@ -558,7 +552,6 @@ ShenandoahHeap::ShenandoahHeap(ShenandoahCollectorPolicy* policy) : _shenandoah_policy(policy), _gc_mode(nullptr), _free_set(nullptr), - _pacer(nullptr), _verifier(nullptr), _phase_timings(nullptr), _monitoring_support(nullptr), @@ -716,8 +709,7 @@ void ShenandoahHeap::decrease_committed(size_t bytes) { // require padding in front of the PLAB (a filler object). Because this padding // is included in the region's used memory we include the padding in the usage // accounting as waste. -// * Mutator allocations are used to compute an allocation rate. They are also -// sent to the Pacer for those purposes. +// * Mutator allocations are used to compute an allocation rate. // * There are three sources of waste: // 1. The padding used to align a PLAB on card size // 2. Region's free is less than minimum TLAB size and is retired @@ -738,9 +730,6 @@ void ShenandoahHeap::increase_used(const ShenandoahAllocRequest& req) { // only actual size counts toward usage for mutator allocations increase_used(generation, actual_bytes); - // notify pacer of both actual size and waste - notify_mutator_alloc_words(req.actual_size(), req.waste()); - if (wasted_bytes > 0 && ShenandoahHeapRegion::requires_humongous(req.actual_size())) { increase_humongous_waste(generation,wasted_bytes); } @@ -775,15 +764,6 @@ void ShenandoahHeap::decrease_used(ShenandoahGeneration* generation, size_t byte } } -void ShenandoahHeap::notify_mutator_alloc_words(size_t words, size_t waste) { - if (ShenandoahPacing) { - control_thread()->pacing_notify_alloc(words); - if (waste > 0) { - pacer()->claim_for_alloc(waste); - } - } -} - size_t ShenandoahHeap::capacity() const { return committed(); } @@ -965,15 +945,10 @@ HeapWord* ShenandoahHeap::allocate_new_gclab(size_t min_size, } HeapWord* ShenandoahHeap::allocate_memory(ShenandoahAllocRequest& req) { - intptr_t pacer_epoch = 0; bool in_new_region = false; HeapWord* result = nullptr; if (req.is_mutator_alloc()) { - if (ShenandoahPacing) { - pacer()->pace_for_alloc(req.size()); - pacer_epoch = pacer()->epoch(); - } if (!ShenandoahAllocFailureALot || !should_inject_alloc_failure()) { result = allocate_memory_under_lock(req, in_new_region); @@ -1048,15 +1023,6 @@ HeapWord* ShenandoahHeap::allocate_memory(ShenandoahAllocRequest& req) { assert (req.is_lab_alloc() || (requested == actual), "Only LAB allocations are elastic: %s, requested = %zu, actual = %zu", ShenandoahAllocRequest::alloc_type_to_string(req.type()), requested, actual); - - if (req.is_mutator_alloc()) { - // If we requested more than we were granted, give the rest back to pacer. - // This only matters if we are in the same pacing epoch: do not try to unpace - // over the budget for the other phase. - if (ShenandoahPacing && (pacer_epoch > 0) && (requested > actual)) { - pacer()->unpace_for_alloc(pacer_epoch, requested - actual); - } - } } return result; @@ -1206,10 +1172,6 @@ private: assert(r->has_live(), "Region %zu should have been reclaimed early", r->index()); _sh->marked_object_iterate(r, &cl); - if (ShenandoahPacing) { - _sh->pacer()->report_evac(r->used() >> LogHeapWordSize); - } - if (_sh->check_cancelled_gc_and_yield(_concurrent)) { break; } @@ -2484,9 +2446,6 @@ private: assert (update_watermark >= r->bottom(), "sanity"); if (r->is_active() && !r->is_cset()) { _heap->marked_object_oop_iterate(r, &cl, update_watermark); - if (ShenandoahPacing) { - _heap->pacer()->report_update_refs(pointer_delta(update_watermark, r->bottom())); - } } if (_heap->check_cancelled_gc_and_yield(CONCURRENT)) { return; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.inline.hpp index 0df482c1e2d..503f6656153 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.inline.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeapRegion.inline.hpp @@ -32,7 +32,6 @@ #include "gc/shenandoah/shenandoahGenerationalHeap.hpp" #include "gc/shenandoah/shenandoahHeap.inline.hpp" #include "gc/shenandoah/shenandoahOldGeneration.hpp" -#include "gc/shenandoah/shenandoahPacer.inline.hpp" #include "runtime/atomic.hpp" HeapWord* ShenandoahHeapRegion::allocate_aligned(size_t size, ShenandoahAllocRequest &req, size_t alignment_in_bytes) { @@ -135,9 +134,6 @@ inline void ShenandoahHeapRegion::increase_live_data_alloc_words(size_t s) { inline void ShenandoahHeapRegion::increase_live_data_gc_words(size_t s) { internal_increase_live_data(s); - if (ShenandoahPacing) { - ShenandoahHeap::heap()->pacer()->report_mark(s); - } } inline void ShenandoahHeapRegion::internal_increase_live_data(size_t s) { diff --git a/src/hotspot/share/gc/shenandoah/shenandoahPacer.cpp b/src/hotspot/share/gc/shenandoah/shenandoahPacer.cpp deleted file mode 100644 index 0dda69dd1b8..00000000000 --- a/src/hotspot/share/gc/shenandoah/shenandoahPacer.cpp +++ /dev/null @@ -1,341 +0,0 @@ -/* - * Copyright (c) 2018, 2019, Red Hat, Inc. All rights reserved. - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - * - */ - - -#include "gc/shenandoah/shenandoahFreeSet.hpp" -#include "gc/shenandoah/shenandoahHeap.inline.hpp" -#include "gc/shenandoah/shenandoahPacer.hpp" -#include "gc/shenandoah/shenandoahPhaseTimings.hpp" -#include "runtime/atomic.hpp" -#include "runtime/javaThread.inline.hpp" -#include "runtime/mutexLocker.hpp" -#include "runtime/threadSMR.hpp" - -/* - * In normal concurrent cycle, we have to pace the application to let GC finish. - * - * Here, we do not know how large would be the collection set, and what are the - * relative performances of the each stage in the concurrent cycle, and so we have to - * make some assumptions. - * - * For concurrent mark, there is no clear notion of progress. The moderately accurate - * and easy to get metric is the amount of live objects the mark had encountered. But, - * that does directly correlate with the used heap, because the heap might be fully - * dead or fully alive. We cannot assume either of the extremes: we would either allow - * application to run out of memory if we assume heap is fully dead but it is not, and, - * conversely, we would pacify application excessively if we assume heap is fully alive - * but it is not. So we need to guesstimate the particular expected value for heap liveness. - * The best way to do this is apparently recording the past history. - * - * For concurrent evac and update-refs, we are walking the heap per-region, and so the - * notion of progress is clear: we get reported the "used" size from the processed regions - * and use the global heap-used as the baseline. - * - * The allocatable space when GC is running is "free" at the start of phase, but the - * accounted budget is based on "used". So, we need to adjust the tax knowing that. - */ - -void ShenandoahPacer::setup_for_mark() { - assert(ShenandoahPacing, "Only be here when pacing is enabled"); - - size_t live = update_and_get_progress_history(); - size_t free = _heap->free_set()->available(); - assert(free != ShenandoahFreeSet::FreeSetUnderConstruction, "Avoid this race"); - - size_t non_taxable = free * ShenandoahPacingCycleSlack / 100; - size_t taxable = free - non_taxable; - taxable = MAX2(1, taxable); - - double tax = 1.0 * live / taxable; // base tax for available free space - tax *= 1; // mark can succeed with immediate garbage, claim all available space - tax *= ShenandoahPacingSurcharge; // additional surcharge to help unclutter heap - - restart_with(non_taxable, tax); - - log_info(gc, ergo)("Pacer for Mark. Expected Live: %zu%s, Free: %zu%s, " - "Non-Taxable: %zu%s, Alloc Tax Rate: %.1fx", - byte_size_in_proper_unit(live), proper_unit_for_byte_size(live), - byte_size_in_proper_unit(free), proper_unit_for_byte_size(free), - byte_size_in_proper_unit(non_taxable), proper_unit_for_byte_size(non_taxable), - tax); -} - -void ShenandoahPacer::setup_for_evac() { - assert(ShenandoahPacing, "Only be here when pacing is enabled"); - - size_t used = _heap->collection_set()->used(); - size_t free = _heap->free_set()->available(); - assert(free != ShenandoahFreeSet::FreeSetUnderConstruction, "Avoid this race"); - - size_t non_taxable = free * ShenandoahPacingCycleSlack / 100; - size_t taxable = free - non_taxable; - taxable = MAX2(1, taxable); - - double tax = 1.0 * used / taxable; // base tax for available free space - tax *= 2; // evac is followed by update-refs, claim 1/2 of remaining free - tax = MAX2(1, tax); // never allocate more than GC processes during the phase - tax *= ShenandoahPacingSurcharge; // additional surcharge to help unclutter heap - - restart_with(non_taxable, tax); - - log_info(gc, ergo)("Pacer for Evacuation. Used CSet: %zu%s, Free: %zu%s, " - "Non-Taxable: %zu%s, Alloc Tax Rate: %.1fx", - byte_size_in_proper_unit(used), proper_unit_for_byte_size(used), - byte_size_in_proper_unit(free), proper_unit_for_byte_size(free), - byte_size_in_proper_unit(non_taxable), proper_unit_for_byte_size(non_taxable), - tax); -} - -void ShenandoahPacer::setup_for_update_refs() { - assert(ShenandoahPacing, "Only be here when pacing is enabled"); - - size_t used = _heap->used(); - size_t free = _heap->free_set()->available(); - assert(free != ShenandoahFreeSet::FreeSetUnderConstruction, "Avoid this race"); - - size_t non_taxable = free * ShenandoahPacingCycleSlack / 100; - size_t taxable = free - non_taxable; - taxable = MAX2(1, taxable); - - double tax = 1.0 * used / taxable; // base tax for available free space - tax *= 1; // update-refs is the last phase, claim the remaining free - tax = MAX2(1, tax); // never allocate more than GC processes during the phase - tax *= ShenandoahPacingSurcharge; // additional surcharge to help unclutter heap - - restart_with(non_taxable, tax); - - log_info(gc, ergo)("Pacer for Update Refs. Used: %zu%s, Free: %zu%s, " - "Non-Taxable: %zu%s, Alloc Tax Rate: %.1fx", - byte_size_in_proper_unit(used), proper_unit_for_byte_size(used), - byte_size_in_proper_unit(free), proper_unit_for_byte_size(free), - byte_size_in_proper_unit(non_taxable), proper_unit_for_byte_size(non_taxable), - tax); -} - -/* - * In idle phase, we have to pace the application to let control thread react with GC start. - * - * Here, we have rendezvous with concurrent thread that adds up the budget as it acknowledges - * it had seen recent allocations. It will naturally pace the allocations if control thread is - * not catching up. To bootstrap this feedback cycle, we need to start with some initial budget - * for applications to allocate at. - */ - -void ShenandoahPacer::setup_for_idle() { - assert(ShenandoahPacing, "Only be here when pacing is enabled"); - - size_t initial = _heap->max_capacity() / 100 * ShenandoahPacingIdleSlack; - double tax = 1; - - restart_with(initial, tax); - - log_info(gc, ergo)("Pacer for Idle. Initial: %zu%s, Alloc Tax Rate: %.1fx", - byte_size_in_proper_unit(initial), proper_unit_for_byte_size(initial), - tax); -} - -/* - * There is no useful notion of progress for these operations. To avoid stalling - * the allocators unnecessarily, allow them to run unimpeded. - */ - -void ShenandoahPacer::setup_for_reset() { - assert(ShenandoahPacing, "Only be here when pacing is enabled"); - - size_t initial = _heap->max_capacity(); - restart_with(initial, 1.0); - - log_info(gc, ergo)("Pacer for Reset. Non-Taxable: %zu%s", - byte_size_in_proper_unit(initial), proper_unit_for_byte_size(initial)); -} - -size_t ShenandoahPacer::update_and_get_progress_history() { - if (_progress == -1) { - // First initialization, report some prior - Atomic::store(&_progress, (intptr_t)PACING_PROGRESS_ZERO); - return (size_t) (_heap->max_capacity() * 0.1); - } else { - // Record history, and reply historical data - _progress_history->add(_progress); - Atomic::store(&_progress, (intptr_t)PACING_PROGRESS_ZERO); - return (size_t) (_progress_history->avg() * HeapWordSize); - } -} - -void ShenandoahPacer::restart_with(size_t non_taxable_bytes, double tax_rate) { - size_t initial = (size_t)(non_taxable_bytes * tax_rate) >> LogHeapWordSize; - STATIC_ASSERT(sizeof(size_t) <= sizeof(intptr_t)); - Atomic::xchg(&_budget, (intptr_t)initial, memory_order_relaxed); - Atomic::store(&_tax_rate, tax_rate); - Atomic::inc(&_epoch); - - // Shake up stalled waiters after budget update. - _need_notify_waiters.try_set(); -} - -template -bool ShenandoahPacer::claim_for_alloc(size_t words) { - assert(ShenandoahPacing, "Only be here when pacing is enabled"); - - intptr_t tax = MAX2(1, words * Atomic::load(&_tax_rate)); - - intptr_t cur = 0; - intptr_t new_val = 0; - do { - cur = Atomic::load(&_budget); - if (cur < tax && !FORCE) { - // Progress depleted, alas. - return false; - } - new_val = cur - tax; - } while (Atomic::cmpxchg(&_budget, cur, new_val, memory_order_relaxed) != cur); - return true; -} - -template bool ShenandoahPacer::claim_for_alloc(size_t words); -template bool ShenandoahPacer::claim_for_alloc(size_t words); - -void ShenandoahPacer::unpace_for_alloc(intptr_t epoch, size_t words) { - assert(ShenandoahPacing, "Only be here when pacing is enabled"); - - if (Atomic::load(&_epoch) != epoch) { - // Stale ticket, no need to unpace. - return; - } - - size_t tax = MAX2(1, words * Atomic::load(&_tax_rate)); - add_budget(tax); -} - -intptr_t ShenandoahPacer::epoch() { - return Atomic::load(&_epoch); -} - -void ShenandoahPacer::pace_for_alloc(size_t words) { - assert(ShenandoahPacing, "Only be here when pacing is enabled"); - - // Fast path: try to allocate right away - bool claimed = claim_for_alloc(words); - if (claimed) { - return; - } - - // Threads that are attaching should not block at all: they are not - // fully initialized yet. Blocking them would be awkward. - // This is probably the path that allocates the thread oop itself. - // - // Thread which is not an active Java thread should also not block. - // This can happen during VM init when main thread is still not an - // active Java thread. - JavaThread* current = JavaThread::current(); - if (current->is_attaching_via_jni() || - !current->is_active_Java_thread()) { - claim_for_alloc(words); - return; - } - - jlong const start_time = os::javaTimeNanos(); - jlong const deadline = start_time + (ShenandoahPacingMaxDelay * NANOSECS_PER_MILLISEC); - while (!claimed && os::javaTimeNanos() < deadline) { - // We could instead assist GC, but this would suffice for now. - wait(1); - claimed = claim_for_alloc(words); - } - if (!claimed) { - // Spent local time budget to wait for enough GC progress. - // Force allocating anyway, which may mean we outpace GC, - // and start Degenerated GC cycle. - claimed = claim_for_alloc(words); - assert(claimed, "Should always succeed"); - } - ShenandoahThreadLocalData::add_paced_time(current, (double)(os::javaTimeNanos() - start_time) / NANOSECS_PER_SEC); -} - -void ShenandoahPacer::wait(size_t time_ms) { - // Perform timed wait. It works like like sleep(), except without modifying - // the thread interruptible status. MonitorLocker also checks for safepoints. - assert(time_ms > 0, "Should not call this with zero argument, as it would stall until notify"); - assert(time_ms <= LONG_MAX, "Sanity"); - MonitorLocker locker(_wait_monitor); - _wait_monitor->wait(time_ms); -} - -void ShenandoahPacer::notify_waiters() { - if (_need_notify_waiters.try_unset()) { - MonitorLocker locker(_wait_monitor); - _wait_monitor->notify_all(); - } -} - -void ShenandoahPacer::flush_stats_to_cycle() { - double sum = 0; - for (JavaThreadIteratorWithHandle jtiwh; JavaThread *t = jtiwh.next(); ) { - sum += ShenandoahThreadLocalData::paced_time(t); - } - ShenandoahHeap::heap()->phase_timings()->record_phase_time(ShenandoahPhaseTimings::pacing, sum); -} - -void ShenandoahPacer::print_cycle_on(outputStream* out) { - MutexLocker lock(Threads_lock); - - double now = os::elapsedTime(); - double total = now - _last_time; - _last_time = now; - - out->cr(); - out->print_cr("Allocation pacing accrued:"); - - size_t threads_total = 0; - size_t threads_nz = 0; - double sum = 0; - for (JavaThreadIteratorWithHandle jtiwh; JavaThread *t = jtiwh.next(); ) { - double d = ShenandoahThreadLocalData::paced_time(t); - if (d > 0) { - threads_nz++; - sum += d; - out->print_cr(" %5.0f of %5.0f ms (%5.1f%%): %s", - d * 1000, total * 1000, d/total*100, t->name()); - } - threads_total++; - ShenandoahThreadLocalData::reset_paced_time(t); - } - out->print_cr(" %5.0f of %5.0f ms (%5.1f%%): ", - sum * 1000, total * 1000, sum/total*100); - - if (threads_total > 0) { - out->print_cr(" %5.0f of %5.0f ms (%5.1f%%): ", - sum / threads_total * 1000, total * 1000, sum / threads_total / total * 100); - } - if (threads_nz > 0) { - out->print_cr(" %5.0f of %5.0f ms (%5.1f%%): ", - sum / threads_nz * 1000, total * 1000, sum / threads_nz / total * 100); - } - out->cr(); -} - -void ShenandoahPeriodicPacerNotifyTask::task() { - assert(ShenandoahPacing, "Should not be here otherwise"); - _pacer->notify_waiters(); -} diff --git a/src/hotspot/share/gc/shenandoah/shenandoahPacer.hpp b/src/hotspot/share/gc/shenandoah/shenandoahPacer.hpp deleted file mode 100644 index fd922d55729..00000000000 --- a/src/hotspot/share/gc/shenandoah/shenandoahPacer.hpp +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (c) 2018, 2019, Red Hat, 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. - * - */ - -#ifndef SHARE_GC_SHENANDOAH_SHENANDOAHPACER_HPP -#define SHARE_GC_SHENANDOAH_SHENANDOAHPACER_HPP - -#include "gc/shenandoah/shenandoahNumberSeq.hpp" -#include "gc/shenandoah/shenandoahPadding.hpp" -#include "gc/shenandoah/shenandoahSharedVariables.hpp" -#include "memory/allocation.hpp" -#include "runtime/task.hpp" - -class ShenandoahHeap; -class ShenandoahPacer; - - -// Periodic task to notify blocked paced waiters. -class ShenandoahPeriodicPacerNotifyTask : public PeriodicTask { -private: - ShenandoahPacer* const _pacer; -public: - explicit ShenandoahPeriodicPacerNotifyTask(ShenandoahPacer* pacer) : - PeriodicTask(PeriodicTask::min_interval), - _pacer(pacer) { } - - void task() override; -}; - - -#define PACING_PROGRESS_UNINIT (-1) -#define PACING_PROGRESS_ZERO ( 0) - -/** - * ShenandoahPacer provides allocation pacing mechanism. - * - * Currently it implements simple tax-and-spend pacing policy: GC threads provide - * credit, allocating thread spend the credit, or stall when credit is not available. - */ -class ShenandoahPacer : public CHeapObj { -private: - ShenandoahHeap* _heap; - double _last_time; - TruncatedSeq* _progress_history; - Monitor* _wait_monitor; - ShenandoahSharedFlag _need_notify_waiters; - ShenandoahPeriodicPacerNotifyTask _notify_waiters_task; - - // Set once per phase - volatile intptr_t _epoch; - volatile double _tax_rate; - - // Heavily updated, protect from accidental false sharing - shenandoah_padding(0); - volatile intptr_t _budget; - shenandoah_padding(1); - - // Heavily updated, protect from accidental false sharing - shenandoah_padding(2); - volatile intptr_t _progress; - shenandoah_padding(3); - -public: - explicit ShenandoahPacer(ShenandoahHeap* heap) : - _heap(heap), - _last_time(os::elapsedTime()), - _progress_history(new TruncatedSeq(5)), - _wait_monitor(new Monitor(Mutex::safepoint-1, "ShenandoahWaitMonitor_lock", true)), - _notify_waiters_task(this), - _epoch(0), - _tax_rate(1), - _budget(0), - _progress(PACING_PROGRESS_UNINIT) { - _notify_waiters_task.enroll(); - } - - void setup_for_idle(); - void setup_for_mark(); - void setup_for_evac(); - void setup_for_update_refs(); - - void setup_for_reset(); - - inline void report_mark(size_t words); - inline void report_evac(size_t words); - inline void report_update_refs(size_t words); - - inline void report_alloc(size_t words); - - template - bool claim_for_alloc(size_t words); - - void pace_for_alloc(size_t words); - void unpace_for_alloc(intptr_t epoch, size_t words); - - void notify_waiters(); - - intptr_t epoch(); - - void flush_stats_to_cycle(); - void print_cycle_on(outputStream* out); - -private: - inline void report_internal(size_t words); - inline void report_progress_internal(size_t words); - - inline void add_budget(size_t words); - void restart_with(size_t non_taxable_bytes, double tax_rate); - - size_t update_and_get_progress_history(); - - void wait(size_t time_ms); -}; - -#endif // SHARE_GC_SHENANDOAH_SHENANDOAHPACER_HPP diff --git a/src/hotspot/share/gc/shenandoah/shenandoahPacer.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahPacer.inline.hpp deleted file mode 100644 index 881b8a9590a..00000000000 --- a/src/hotspot/share/gc/shenandoah/shenandoahPacer.inline.hpp +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) 2018, 2019, Red Hat, 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. - * - */ - -#ifndef SHARE_GC_SHENANDOAH_SHENANDOAHPACER_INLINE_HPP -#define SHARE_GC_SHENANDOAH_SHENANDOAHPACER_INLINE_HPP - -#include "gc/shenandoah/shenandoahPacer.hpp" - -#include "runtime/atomic.hpp" - -inline void ShenandoahPacer::report_mark(size_t words) { - report_internal(words); - report_progress_internal(words); -} - -inline void ShenandoahPacer::report_evac(size_t words) { - report_internal(words); -} - -inline void ShenandoahPacer::report_update_refs(size_t words) { - report_internal(words); -} - -inline void ShenandoahPacer::report_alloc(size_t words) { - report_internal(words); -} - -inline void ShenandoahPacer::report_internal(size_t words) { - assert(ShenandoahPacing, "Only be here when pacing is enabled"); - add_budget(words); -} - -inline void ShenandoahPacer::report_progress_internal(size_t words) { - assert(ShenandoahPacing, "Only be here when pacing is enabled"); - STATIC_ASSERT(sizeof(size_t) <= sizeof(intptr_t)); - Atomic::add(&_progress, (intptr_t)words, memory_order_relaxed); -} - -inline void ShenandoahPacer::add_budget(size_t words) { - STATIC_ASSERT(sizeof(size_t) <= sizeof(intptr_t)); - intptr_t inc = (intptr_t) words; - intptr_t new_budget = Atomic::add(&_budget, inc, memory_order_relaxed); - - // Was the budget replenished beyond zero? Then all pacing claims - // are satisfied, notify the waiters. Avoid taking any locks here, - // as it can be called from hot paths and/or while holding other locks. - if (new_budget >= 0 && (new_budget - inc) < 0) { - _need_notify_waiters.try_set(); - } -} - -#endif // SHARE_GC_SHENANDOAH_SHENANDOAHPACER_INLINE_HPP diff --git a/src/hotspot/share/gc/shenandoah/shenandoahPhaseTimings.cpp b/src/hotspot/share/gc/shenandoah/shenandoahPhaseTimings.cpp index 62a25881b5a..ad12bfc5a89 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahPhaseTimings.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahPhaseTimings.cpp @@ -281,17 +281,6 @@ void ShenandoahPhaseTimings::print_global_on(outputStream* out) const { out->print_cr(" all workers. Dividing the over the root stage time estimates parallelism."); out->cr(); - out->print_cr(" Pacing delays are measured from entering the pacing code till exiting it. Therefore,"); - out->print_cr(" observed pacing delays may be higher than the threshold when paced thread spent more"); - out->print_cr(" time in the pacing code. It usually happens when thread is de-scheduled while paced,"); - out->print_cr(" OS takes longer to unblock the thread, or JVM experiences an STW pause."); - out->cr(); - out->print_cr(" Higher delay would prevent application outpacing the GC, but it will hide the GC latencies"); - out->print_cr(" from the STW pause times. Pacing affects the individual threads, and so it would also be"); - out->print_cr(" invisible to the usual profiling tools, but would add up to end-to-end application latency."); - out->print_cr(" Raise max pacing delay with care."); - out->cr(); - for (uint i = 0; i < _num_phases; i++) { if (_global_data[i].maximum() != 0) { out->print_cr(SHENANDOAH_PHASE_NAME_FORMAT " = " SHENANDOAH_S_TIME_FORMAT " s " diff --git a/src/hotspot/share/gc/shenandoah/shenandoahPhaseTimings.hpp b/src/hotspot/share/gc/shenandoah/shenandoahPhaseTimings.hpp index 0a456151318..f4c49000e6e 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahPhaseTimings.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahPhaseTimings.hpp @@ -198,8 +198,6 @@ class outputStream; f(full_gc_heapdump_post, " Post Heap Dump") \ f(full_gc_propagate_gc_state, " Propagate GC State") \ \ - f(pacing, "Pacing") \ - \ f(heap_iteration_roots, "Heap Iteration") \ SHENANDOAH_PAR_PHASE_DO(heap_iteration_roots_, " HI: ", f) \ // end diff --git a/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp b/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp index ad0beeafed7..c51f4f16489 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp @@ -408,40 +408,6 @@ "to be more than this.") \ range(0, 100) \ \ - product(bool, ShenandoahPacing, true, EXPERIMENTAL, \ - "Pace application allocations to give GC chance to start " \ - "and complete before allocation failure is reached.") \ - \ - product(uintx, ShenandoahPacingMaxDelay, 10, EXPERIMENTAL, \ - "Max delay for pacing application allocations. Larger values " \ - "provide more resilience against out of memory, at expense at " \ - "hiding the GC latencies in the allocation path. Time is in " \ - "milliseconds. Setting it to arbitrarily large value makes " \ - "GC effectively stall the threads indefinitely instead of going " \ - "to degenerated or Full GC.") \ - \ - product(uintx, ShenandoahPacingIdleSlack, 2, EXPERIMENTAL, \ - "How much of heap counted as non-taxable allocations during idle "\ - "phases. Larger value makes the pacing milder when collector is " \ - "idle, requiring less rendezvous with control thread. Lower " \ - "value makes the pacing control less responsive to out-of-cycle " \ - "allocs. In percent of total heap size.") \ - range(0, 100) \ - \ - product(uintx, ShenandoahPacingCycleSlack, 10, EXPERIMENTAL, \ - "How much of free space to take as non-taxable allocations " \ - "the GC cycle. Larger value makes the pacing milder at the " \ - "beginning of the GC cycle. Lower value makes the pacing less " \ - "uniform during the cycle. In percent of free space.") \ - range(0, 100) \ - \ - product(double, ShenandoahPacingSurcharge, 1.1, EXPERIMENTAL, \ - "Additional pacing tax surcharge to help unclutter the heap. " \ - "Larger values makes the pacing more aggressive. Lower values " \ - "risk GC cycles finish with less memory than were available at " \ - "the beginning of it.") \ - range(1.0, 100.0) \ - \ product(uintx, ShenandoahCriticalFreeThreshold, 1, EXPERIMENTAL, \ "How much of the heap needs to be free after recovery cycles, " \ "either Degenerated or Full GC to be claimed successful. If this "\ diff --git a/test/hotspot/jtreg/gc/shenandoah/generational/TestConcurrentEvac.java b/test/hotspot/jtreg/gc/shenandoah/generational/TestConcurrentEvac.java index 763c5906f3f..6b73776f93e 100644 --- a/test/hotspot/jtreg/gc/shenandoah/generational/TestConcurrentEvac.java +++ b/test/hotspot/jtreg/gc/shenandoah/generational/TestConcurrentEvac.java @@ -48,7 +48,7 @@ import java.util.HashMap; * -XX:+UseShenandoahGC -XX:ShenandoahGCMode=generational * -XX:NewRatio=1 -XX:+UnlockExperimentalVMOptions * -XX:ShenandoahGuaranteedGCInterval=3000 - * -XX:-UseDynamicNumberOfGCThreads -XX:-ShenandoahPacing + * -XX:-UseDynamicNumberOfGCThreads * gc.shenandoah.generational.TestConcurrentEvac */ diff --git a/test/hotspot/jtreg/gc/shenandoah/options/TestPacing.java b/test/hotspot/jtreg/gc/shenandoah/options/TestPacing.java deleted file mode 100644 index b41419fb0bd..00000000000 --- a/test/hotspot/jtreg/gc/shenandoah/options/TestPacing.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright (c) 2018, Red Hat, 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. - * - */ - -/* - * @test - * @requires vm.gc.Shenandoah - * - * @run main/othervm -XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC -XX:-ShenandoahPacing -Xmx128m TestPacing - * @run main/othervm -XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC -XX:+ShenandoahPacing -Xmx128m TestPacing - */ - -public class TestPacing { - static final long TARGET_MB = Long.getLong("target", 1000); // 1 Gb allocation - - static volatile Object sink; - - public static void main(String[] args) throws Exception { - long count = TARGET_MB * 1024 * 1024 / 16; - for (long c = 0; c < count; c++) { - sink = new Object(); - } - } -} From 0735dc27c71de46896afd2f0f608319304a3d549 Mon Sep 17 00:00:00 2001 From: David Holmes Date: Wed, 23 Jul 2025 00:36:35 +0000 Subject: [PATCH 49/94] 8362846: Windows error reporting for dll_load doesn't check for a null buffer 8362954: Missing error buffer null check in os::dll_load on Linux/BSD Reviewed-by: mgronlun, kbarrett --- src/hotspot/os/bsd/os_bsd.cpp | 5 ++++- src/hotspot/os/linux/os_linux.cpp | 5 +++++ src/hotspot/os/windows/os_windows.cpp | 8 ++++++++ test/hotspot/gtest/runtime/test_os.cpp | 6 ++++++ 4 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/hotspot/os/bsd/os_bsd.cpp b/src/hotspot/os/bsd/os_bsd.cpp index 4b74e7c00f3..eb9b4c3f862 100644 --- a/src/hotspot/os/bsd/os_bsd.cpp +++ b/src/hotspot/os/bsd/os_bsd.cpp @@ -1110,7 +1110,10 @@ void * os::dll_load(const char *filename, char *ebuf, int ebuflen) { if (result != nullptr) { return result; } - + if (ebuf == nullptr || ebuflen < 1) { + // no error reporting requested + return nullptr; + } Events::log_dll_message(nullptr, "Loading shared library %s failed, %s", filename, error_report); log_info(os)("shared library load of %s failed, %s", filename, error_report); int diag_msg_max_length=ebuflen-strlen(ebuf); diff --git a/src/hotspot/os/linux/os_linux.cpp b/src/hotspot/os/linux/os_linux.cpp index b747fe4d88f..b77a1f36954 100644 --- a/src/hotspot/os/linux/os_linux.cpp +++ b/src/hotspot/os/linux/os_linux.cpp @@ -1685,6 +1685,11 @@ void * os::dll_load(const char *filename, char *ebuf, int ebuflen) { return result; } + if (ebuf == nullptr || ebuflen < 1) { + // no error reporting requested + return nullptr; + } + Elf32_Ehdr elf_head; size_t prefix_len = strlen(ebuf); ssize_t diag_msg_max_length = ebuflen - prefix_len; diff --git a/src/hotspot/os/windows/os_windows.cpp b/src/hotspot/os/windows/os_windows.cpp index d1624b51ef3..ac943fd05b4 100644 --- a/src/hotspot/os/windows/os_windows.cpp +++ b/src/hotspot/os/windows/os_windows.cpp @@ -1729,6 +1729,12 @@ void * os::dll_load(const char *name, char *ebuf, int ebuflen) { log_info(os)("shared library load of %s was successful", name); return result; } + + if (ebuf == nullptr || ebuflen < 1) { + // no error reporting requested + return nullptr; + } + DWORD errcode = GetLastError(); // Read system error message into ebuf // It may or may not be overwritten below (in the for loop and just above) @@ -2261,6 +2267,8 @@ void os::jvm_path(char *buf, jint buflen) { // from src/windows/hpi/src/system_md.c size_t os::lasterror(char* buf, size_t len) { + assert(buf != nullptr && len > 0, "invalid buffer passed"); + DWORD errval; if ((errval = GetLastError()) != 0) { diff --git a/test/hotspot/gtest/runtime/test_os.cpp b/test/hotspot/gtest/runtime/test_os.cpp index c52f7a692c7..ce4050ab0f3 100644 --- a/test/hotspot/gtest/runtime/test_os.cpp +++ b/test/hotspot/gtest/runtime/test_os.cpp @@ -1181,3 +1181,9 @@ TEST_VM(os, map_memory_to_file_aligned) { } #endif // !defined(_AIX) + +TEST_VM(os, dll_load_null_error_buf) { + // This should not crash. + void* lib = os::dll_load("NoSuchLib", nullptr, 0); + ASSERT_NULL(lib); +} From 5160cfb49634cc4a1568c200bc5c17ddbe83c2f7 Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Wed, 23 Jul 2025 07:12:12 +0000 Subject: [PATCH 50/94] 8362889: [GCC static analyzer] leak in libstringPlatformChars.c Reviewed-by: rriggs, dholmes --- .../nativeEncoding/libstringPlatformChars.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/test/jdk/java/lang/String/nativeEncoding/libstringPlatformChars.c b/test/jdk/java/lang/String/nativeEncoding/libstringPlatformChars.c index 99dbd92d92e..91c6f8edbc0 100644 --- a/test/jdk/java/lang/String/nativeEncoding/libstringPlatformChars.c +++ b/test/jdk/java/lang/String/nativeEncoding/libstringPlatformChars.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -58,19 +58,23 @@ Java_StringPlatformChars_newString(JNIEnv *env, jclass unused, jbyteArray bytes) char* str; int len = (*env)->GetArrayLength(env, bytes); int i; - jbyte* jbytes; - - str = (char*)malloc(len + 1); - jbytes = (*env)->GetPrimitiveArrayCritical(env, bytes, NULL); + jbyte* jbytes = (*env)->GetPrimitiveArrayCritical(env, bytes, NULL); if (jbytes == NULL) { return NULL; } + str = (char*)malloc(len + 1); + if (str == NULL) { + (*env)->ReleasePrimitiveArrayCritical(env, bytes, (void*)jbytes, 0); + return NULL; + } for (i = 0; i < len; i++) { str[i] = (char)jbytes[i]; } str[len] = '\0'; (*env)->ReleasePrimitiveArrayCritical(env, bytes, (void*)jbytes, 0); - return JNU_NewStringPlatform(env, str); + jstring res = JNU_NewStringPlatform(env, str); + free(str); + return res; } From ceb0c0fc39c17793d13fff74e69f22ef07ec2c0f Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Wed, 23 Jul 2025 07:49:11 +0000 Subject: [PATCH 51/94] 8360941: [ubsan] MemRegion::end() shows runtime error: applying non-zero offset 8388608 to null pointer Co-authored-by: Kim Barrett Co-authored-by: Thomas Stuefe Reviewed-by: kbarrett, lucy --- test/hotspot/gtest/gc/g1/test_freeRegionList.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/hotspot/gtest/gc/g1/test_freeRegionList.cpp b/test/hotspot/gtest/gc/g1/test_freeRegionList.cpp index ea8027df2a7..e61ce43fdb6 100644 --- a/test/hotspot/gtest/gc/g1/test_freeRegionList.cpp +++ b/test/hotspot/gtest/gc/g1/test_freeRegionList.cpp @@ -44,7 +44,10 @@ TEST_OTHER_VM(G1FreeRegionList, length) { // Create a fake heap. It does not need to be valid, as the G1HeapRegion constructor // does not access it. - MemRegion heap(nullptr, num_regions_in_test * G1HeapRegion::GrainWords); + const size_t szw = num_regions_in_test * G1HeapRegion::GrainWords; + const size_t sz = szw * BytesPerWord; + char* addr = os::reserve_memory_aligned(sz, G1HeapRegion::GrainBytes, mtTest); + MemRegion heap((HeapWord*)addr, szw); // Allocate a fake BOT because the G1HeapRegion constructor initializes // the BOT. @@ -87,5 +90,6 @@ TEST_OTHER_VM(G1FreeRegionList, length) { bot_storage->uncommit_regions(0, num_regions_in_test); delete bot_storage; + os::release_memory(addr, sz); FREE_C_HEAP_ARRAY(HeapWord, bot_data); } From 9f796da3774b2e2f92dca178fdccd93989919256 Mon Sep 17 00:00:00 2001 From: Wang Haomin Date: Wed, 23 Jul 2025 08:08:05 +0000 Subject: [PATCH 52/94] 8362972: C2 fails with unexpected node in SuperWord truncation: IsFiniteF, IsFiniteD Reviewed-by: thartmann, jkarthikeyan --- src/hotspot/share/opto/superword.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/hotspot/share/opto/superword.cpp b/src/hotspot/share/opto/superword.cpp index 266a5874cea..510cb1b51de 100644 --- a/src/hotspot/share/opto/superword.cpp +++ b/src/hotspot/share/opto/superword.cpp @@ -2593,6 +2593,8 @@ static bool can_subword_truncate(Node* in, const Type* type) { case Op_ReverseI: case Op_CountLeadingZerosI: case Op_CountTrailingZerosI: + case Op_IsFiniteF: + case Op_IsFiniteD: case Op_IsInfiniteF: case Op_IsInfiniteD: case Op_ExtractS: From e6ac956a7ac613b916c0dbfda7e57856c1b8a83c Mon Sep 17 00:00:00 2001 From: Feilong Jiang Date: Wed, 23 Jul 2025 09:35:26 +0000 Subject: [PATCH 53/94] 8360520: RISC-V: C1: Fix primitive array clone intrinsic regression after JDK-8333154 Reviewed-by: fyang, galder, dlong --- src/hotspot/cpu/riscv/c1_LIRGenerator_riscv.cpp | 2 +- src/hotspot/share/c1/c1_LIR.cpp | 3 ++- src/hotspot/share/c1/c1_LIR.hpp | 2 ++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/hotspot/cpu/riscv/c1_LIRGenerator_riscv.cpp b/src/hotspot/cpu/riscv/c1_LIRGenerator_riscv.cpp index 4c3a6653731..e450c04c47d 100644 --- a/src/hotspot/cpu/riscv/c1_LIRGenerator_riscv.cpp +++ b/src/hotspot/cpu/riscv/c1_LIRGenerator_riscv.cpp @@ -772,7 +772,7 @@ void LIRGenerator::do_ArrayCopy(Intrinsic* x) { ciArrayKlass* expected_type = nullptr; arraycopy_helper(x, &flags, &expected_type); if (x->check_flag(Instruction::OmitChecksFlag)) { - flags = 0; + flags = (flags & LIR_OpArrayCopy::get_initial_copy_flags()); } __ arraycopy(src.result(), src_pos.result(), dst.result(), dst_pos.result(), length.result(), tmp, diff --git a/src/hotspot/share/c1/c1_LIR.cpp b/src/hotspot/share/c1/c1_LIR.cpp index 3db916783f8..4c8ebd5a09d 100644 --- a/src/hotspot/share/c1/c1_LIR.cpp +++ b/src/hotspot/share/c1/c1_LIR.cpp @@ -351,7 +351,8 @@ LIR_OpArrayCopy::LIR_OpArrayCopy(LIR_Opr src, LIR_Opr src_pos, LIR_Opr dst, LIR_ , _expected_type(expected_type) , _flags(flags) { #if defined(X86) || defined(AARCH64) || defined(S390) || defined(RISCV64) || defined(PPC64) - if (expected_type != nullptr && flags == 0) { + if (expected_type != nullptr && + ((flags & ~LIR_OpArrayCopy::get_initial_copy_flags()) == 0)) { _stub = nullptr; } else { _stub = new ArrayCopyStub(this); diff --git a/src/hotspot/share/c1/c1_LIR.hpp b/src/hotspot/share/c1/c1_LIR.hpp index 0de69e658a3..c7726bf5c3f 100644 --- a/src/hotspot/share/c1/c1_LIR.hpp +++ b/src/hotspot/share/c1/c1_LIR.hpp @@ -1282,6 +1282,8 @@ public: int flags() const { return _flags; } ciArrayKlass* expected_type() const { return _expected_type; } ArrayCopyStub* stub() const { return _stub; } + static int get_initial_copy_flags() { return LIR_OpArrayCopy::unaligned | + LIR_OpArrayCopy::overlapping; } virtual void emit_code(LIR_Assembler* masm); virtual LIR_OpArrayCopy* as_OpArrayCopy() { return this; } From 06f9ff047f1d1e832d7379f9750237749479b020 Mon Sep 17 00:00:00 2001 From: Weijun Wang Date: Wed, 23 Jul 2025 12:24:28 +0000 Subject: [PATCH 54/94] 8356997: /etc/krb5.conf parser should not forbid include/includedir directives after sections Reviewed-by: valeriep --- .../classes/sun/security/krb5/Config.java | 292 +++++++++++------- .../krb5/config/DuplicatedIncludes.java | 89 ++++++ .../security/krb5/config/IncludeRandom.java | 139 +++++++++ .../security/krb5/config/IncludeSameKey.java | 75 +++++ 4 files changed, 487 insertions(+), 108 deletions(-) create mode 100644 test/jdk/sun/security/krb5/config/DuplicatedIncludes.java create mode 100644 test/jdk/sun/security/krb5/config/IncludeRandom.java create mode 100644 test/jdk/sun/security/krb5/config/IncludeSameKey.java diff --git a/src/java.security.jgss/share/classes/sun/security/krb5/Config.java b/src/java.security.jgss/share/classes/sun/security/krb5/Config.java index c92a106850b..398a959fb24 100644 --- a/src/java.security.jgss/share/classes/sun/security/krb5/Config.java +++ b/src/java.security.jgss/share/classes/sun/security/krb5/Config.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 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 @@ -70,6 +70,11 @@ public class Config { */ public static final int MAX_REFERRALS; + /** + * Maximum number of files that can be included. + */ + private static final int MAX_INCLUDE_FILE = 100; + static { String disableReferralsProp = SecurityProperties.getOverridableProperty( @@ -96,7 +101,12 @@ public class Config { */ private static Config singleton = null; - /* + /** + * All lines read from all krb5 config files. + */ + private Map> allConfs = new HashMap<>(); + + /** * Hashtable used to store configuration information. */ private Hashtable stanzaTable = new Hashtable<>(); @@ -202,11 +212,10 @@ public class Config { // Always read the Kerberos configuration file try { - List configFile; String fileName = getJavaFileName(); if (fileName != null) { - configFile = loadConfigFile(fileName); - stanzaTable = parseStanzaTable(configFile); + Path p = loadConfigFile(fileName); // p is main entry + parseStanzaTable(p); if (DEBUG != null) { DEBUG.println("Loaded from Java config"); } @@ -224,9 +233,9 @@ public class Config { } } if (!found) { - fileName = getNativeFileName(); - configFile = loadConfigFile(fileName); - stanzaTable = parseStanzaTable(configFile); + fileName = getNativeFileName(); // p is main entry + Path p = loadConfigFile(fileName); + parseStanzaTable(p); if (DEBUG != null) { DEBUG.println("Loaded from native config"); } @@ -564,80 +573,129 @@ public class Config { } /** - * Reads the lines of the configuration file. All include and includedir - * directives are resolved by calling this method recursively. + * Reads a configuration file. All include and includedir directives are + * also read by calling this method recursively. All contents are stored + * in {@link #allConfs} with file name as key. * - * @param file the krb5.conf file, must be absolute - * @param content the lines. Comment and empty lines are removed, - * all lines trimmed, include and includedir - * directives resolved, unknown directives ignored + * Comment and empty lines are removed, all lines are trimmed, include and + * includedir directives are processed and translated to "#include" followed + * by a file name (not a directory name), unknown directives are ignored. + * + * @param file a krb5 config file, must be absolute * @param dups a set of Paths to check for possible infinite loop * @throws IOException if there is an I/O error + * @throws KrbException other errors */ - private static Void readConfigFileLines( - Path file, List content, Set dups) - throws IOException { + private void readConfigFileLines(Path file, Set dups) + throws KrbException, IOException { if (DEBUG != null) { DEBUG.println("Loading krb5 profile at " + file); } + if (!file.isAbsolute()) { - throw new IOException("Profile path not absolute"); + throw new KrbException("Profile path not absolute"); + } + if (allConfs.size() > MAX_INCLUDE_FILE) { + throw new KrbException("Too many include files"); } if (!dups.add(file)) { - throw new IOException("Profile path included more than once"); + throw new KrbException("Recursive include"); } - List lines = Files.readAllLines(file); - - boolean inDirectives = true; - for (String line: lines) { - line = line.trim(); - if (line.isEmpty() || line.startsWith("#") || line.startsWith(";")) { - continue; + try { + if (allConfs.containsKey(file)) { + // Already parsed. Including a file multiple times is allowed. + // Just make sure it cannot be recursive. + return; } - if (inDirectives) { - if (line.charAt(0) == '[') { - inDirectives = false; - content.add(line); - } else if (line.startsWith("includedir ")) { + + List lines = Files.readAllLines(file); + List content = new ArrayList<>(); + + // Add content to map at the beginning to detect duplicates + allConfs.put(file, content); + + boolean inSections = false; + for (String line : lines) { + line = line.trim(); + if (line.isEmpty() || line.startsWith("#") || line.startsWith(";")) { + continue; + } + if (line.startsWith("includedir ")) { Path dir = Paths.get( line.substring("includedir ".length()).trim()); try (Stream files = Files.list(dir)) { - for (Path p: files.sorted().toList()) { + for (Path p : files.sorted().toList()) { if (Files.isDirectory(p)) continue; String name = p.getFileName().toString(); if (name.matches("[a-zA-Z0-9_-]+") || (!name.startsWith(".") && name.endsWith(".conf"))) { // if dir is absolute, so is p - readConfigFileLines(p, content, dups); + readConfigFileLines(p, dups); + content.add("#include " + p); } } } } else if (line.startsWith("include ")) { - readConfigFileLines( - Paths.get(line.substring("include ".length()).trim()), - content, dups); + Path p = Paths.get(line.substring("include ".length()).trim()); + content.add("#include " + p); + readConfigFileLines(p, dups); } else { - // Unsupported directives - if (DEBUG != null) { - DEBUG.println("Unknown directive: " + line); + if (!inSections) { + if (line.charAt(0) == '[') { + inSections = true; + content.add(line); + } else { + // Unsupported directives + if (DEBUG != null) { + DEBUG.println("Line not in any section: " + line); + } + } + } else { + content.add(line); } } - } else { - content.add(line); } + } finally { + dups.remove(file); } - return null; } /** - * Reads the configuration file and return normalized lines. + * Reads the main configuration file. + * + * @param fileName the configuration file + * @return absolute path to the config file + */ + private Path loadConfigFile(final String fileName) + throws IOException, KrbException { + + if (DEBUG != null) { + DEBUG.println("Loading config file from " + fileName); + } + Set dupsCheck = new HashSet<>(); + Path fullp = Paths.get(fileName).toAbsolutePath(); + if (!Files.exists(fullp)) { + // This is OK. There are other ways to get + // Kerberos 5 settings + } else { + readConfigFileLines(fullp, dupsCheck); + } + return fullp; + } + + /** + * Normalizes strings read from one config file. All sections and + * subsections are enclosed in braces. Directives ("#include") are + * kept in the same place. + * * If the original file is: * * [realms] + * includedir /tmp/inc * EXAMPLE.COM = * { * kdc = kerberos.example.com @@ -645,10 +703,24 @@ public class Config { * } * ... * - * The result will be (no indentations): + * The output of readConfigFileLines will be (no indentations): + * + * [realms] + * #include /tmp/inc/conf1 + * #include /tmp/inc/conf2 + * EXAMPLE.COM = + * { + * kdc = kerberos.example.com + * ... + * } + * ... + * + * The output of normalize will be (no indentations): * * { * realms = { + * #include /tmp/inc/conf1 + * #include /tmp/inc/conf2 * EXAMPLE.COM = { * kdc = kerberos.example.com * ... @@ -657,37 +729,32 @@ public class Config { * ... * } * - * @param fileName the configuration file - * @return normalized lines + * @param raw input list of strings + * @return normalized list of strings + * @throws KrbException when the format is not correct */ - private List loadConfigFile(final String fileName) - throws IOException, KrbException { - - if (DEBUG != null) { - DEBUG.println("Loading config file from " + fileName); - } + private static List normalize(List raw) throws KrbException { List result = new ArrayList<>(); - List raw = new ArrayList<>(); - Set dupsCheck = new HashSet<>(); - - Path fullp = Paths.get(fileName).toAbsolutePath(); - Path path = Paths.get(fileName); - if (!Files.exists(path)) { - // This is OK. There are other ways to get - // Kerberos 5 settings - } else { - readConfigFileLines(fullp, raw, dupsCheck); - } - - String previous = null; + List unwritten = new ArrayList<>(); + String previous = null; // unfinished line for (String line: raw) { - if (line.startsWith("[")) { + if (line.startsWith("#")) { // directives like "#include". Do not + // write out immediately, might follow + // a previous line. + if (previous == null) { + result.add(line); + } else { + unwritten.add(line); + } + } else if (line.startsWith("[")) { if (!line.endsWith("]")) { throw new KrbException("Illegal config content:" + line); } if (previous != null) { result.add(previous); + unwritten.forEach(result::add); + unwritten.clear(); result.add("}"); } String title = line.substring( @@ -706,6 +773,8 @@ public class Config { if (line.length() > 1) { // { and content on the same line result.add(previous); + unwritten.forEach(result::add); + unwritten.clear(); previous = line.substring(1).trim(); } } else { @@ -716,11 +785,15 @@ public class Config { "Config file must starts with a section"); } result.add(previous); + unwritten.forEach(result::add); + unwritten.clear(); previous = line; } } if (previous != null) { result.add(previous); + unwritten.forEach(result::add); + unwritten.clear(); result.add("}"); } return result; @@ -734,44 +807,52 @@ public class Config { * another sub-sub-section or a non-empty vector of strings for final values * (even if there is only one value defined). *

        - * For top-level sections with duplicates names, their contents are merged. - * For sub-sections the former overwrites the latter. For final values, - * they are stored in a vector in their appearing order. Please note these - * values must appear in the same sub-section. Otherwise, the sub-section - * appears first should have already overridden the others. + * Contents of duplicated sections are merged. Values for duplicated names + * are stored in a vector in their appearing order. If the same name is used + * as both a section name and a value name, the first appearance decides the + * type and the latter appearances of different types are ignored. *

        - * As a corner case, if the same name is used as both a section name and a - * value name, the first appearance decides the type. That is to say, if the - * first one is for a section, all latter appearances are ignored. If it's - * a value, latter appearances as sections are ignored, but those as values - * are added to the vector. - *

        - * The behavior described above is compatible to other krb5 implementations - * but it's not decumented publicly anywhere. the best practice is not to + * The behavior described above is compatible to other krb5 implementations, + * but it's not documented publicly anywhere. the best practice is not to * assume any kind of override functionality and only specify values for * a particular key in one place. * - * @param v the normalized input as return by loadConfigFile + * @param entry path to config file, could be an included one * @throws KrbException if there is a file format error */ @SuppressWarnings("unchecked") - private Hashtable parseStanzaTable(List v) + private void parseStanzaTable(Path entry) throws KrbException { Hashtable current = stanzaTable; + // Current sections and subsections + Deque> stack = new ArrayDeque<>(); + List v = allConfs.get(entry); + if (v == null) { + // this happens when root krb5.conf is missing + return; + } + v = normalize(v); + if (DEBUG != null) { + DEBUG.println(">>> Begin Kerberos config at " + entry); + v.forEach(DEBUG::println); + DEBUG.println(">>> End Kerberos config at " + entry); + } for (String line: v) { - if (DEBUG != null) { - DEBUG.println(line); - } - // There are only 3 kinds of lines - // 1. a = b - // 2. a = { - // 3. } - if (line.equals("}")) { + // There are only 4 kinds of lines after normalization + // 1. #include + // 2. a = b + // 3. a = { + // 4. } + if (line.startsWith("#include ")) { + // parse in-place at the top level, i.e. included file + // is not considered inside the current section. + parseStanzaTable(Path.of(line.substring(9))); + } else if (line.equals("}")) { // Go back to parent, see below - current = (Hashtable)current.remove(" PARENT "); - if (current == null) { + if (stack.isEmpty()) { throw new KrbException("Unmatched close brace"); } + current = stack.pop(); } else { int pos = line.indexOf('='); if (pos < 0) { @@ -784,37 +865,33 @@ public class Config { if (current == stanzaTable) { key = key.toLowerCase(Locale.US); } - // When there are dup names for sections if (current.containsKey(key)) { - if (current == stanzaTable) { // top-level, merge - // The value at top-level must be another Hashtable - subTable = (Hashtable)current.get(key); - } else { // otherwise, ignored - // read and ignore it (do not put into current) + Object obj = current.get(key); + if (obj instanceof Hashtable) { + // dup section, merge + subTable = (Hashtable) obj; + } else { + // different type, parse and ignore subTable = new Hashtable<>(); } } else { subTable = new Hashtable<>(); current.put(key, subTable); } - // A special entry for its parent. Put whitespaces around, - // so will never be confused with a normal key - subTable.put(" PARENT ", current); + // Remember where I am. + stack.push(current); current = subTable; } else { - Vector values; if (current.containsKey(key)) { Object obj = current.get(key); if (obj instanceof Vector) { - // String values are merged - values = (Vector)obj; - values.add(value); + // dup value, accumulate + ((Vector) obj).add(value); } else { - // If a key shows as section first and then a value, - // ignore the value. + // different type, ignore } } else { - values = new Vector(); + Vector values = new Vector<>(); values.add(value); current.put(key, values); } @@ -824,7 +901,6 @@ public class Config { if (current != stanzaTable) { throw new KrbException("Not closed"); } - return current; } /** diff --git a/test/jdk/sun/security/krb5/config/DuplicatedIncludes.java b/test/jdk/sun/security/krb5/config/DuplicatedIncludes.java new file mode 100644 index 00000000000..4d1457dff0b --- /dev/null +++ b/test/jdk/sun/security/krb5/config/DuplicatedIncludes.java @@ -0,0 +1,89 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import jdk.test.lib.Asserts; +import sun.security.krb5.Config; +import sun.security.krb5.KrbException; + +import java.nio.file.Files; +import java.nio.file.Path; + +/* + * @test + * @bug 8356997 + * @summary Support "include" anywhere + * @modules java.security.jgss/sun.security.krb5 + * @library /test/lib + * @run main/othervm DuplicatedIncludes + */ +public class DuplicatedIncludes { + public static void main(String[] args) throws Exception { + + var cwd = Path.of("").toAbsolutePath().toString(); + System.setProperty("java.security.krb5.conf", "krb5.conf"); + + // It's OK to include a file multiple times + Files.writeString(Path.of("krb5.conf"), String.format(""" + include %1$s/sub + include %1$s/sub + """, cwd)); + + Files.writeString(Path.of("sub"), """ + [a] + b = c + """); + Config.refresh(); + + // But a file cannot include itself + Files.writeString(Path.of("sub"), String.format(""" + include %1$s/sub + """, cwd)); + Asserts.assertThrows(KrbException.class, () -> Config.refresh()); + + // A file also cannot include a file that includes it + Files.writeString(Path.of("sub"), String.format(""" + include %1$s/sub2 + """, cwd)); + Files.writeString(Path.of("sub2"), String.format(""" + include %1$s/sub + """, cwd)); + Asserts.assertThrows(KrbException.class, () -> Config.refresh()); + + // It's OK for a file to include another file that has already + // been included multiple times, as long as it's not on the stack. + // This proves it's necessary to place "dups.remove(file)" in a + // finally block in Config::readConfigFileLines. This case is + // not covered by IncludeRandom.java because of the structured + // include pattern (included always longer than includee) there. + Files.writeString(Path.of("krb5.conf"), String.format(""" + include %1$s/sub + include %1$s/sub + include %1$s/sub2 + """, cwd)); + Files.writeString(Path.of("sub"), ""); + Files.writeString(Path.of("sub2"), String.format(""" + include %1$s/sub + """, cwd)); + Config.refresh(); + } +} diff --git a/test/jdk/sun/security/krb5/config/IncludeRandom.java b/test/jdk/sun/security/krb5/config/IncludeRandom.java new file mode 100644 index 00000000000..11f93f17830 --- /dev/null +++ b/test/jdk/sun/security/krb5/config/IncludeRandom.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8356997 + * @summary Support "include" anywhere + * @modules java.security.jgss/sun.security.krb5 + * @library /test/lib + * @run main/othervm IncludeRandom + */ +import jdk.test.lib.Asserts; +import jdk.test.lib.security.SeededSecureRandom; +import sun.security.krb5.Config; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.SecureRandom; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +// A randomized class to prove that wherever the "include" line is inside +// a krb5.conf file, it can always be parsed correctly. +public class IncludeRandom { + + static SecureRandom sr = SeededSecureRandom.one(); + + // Must be global. Counting in recursive methods + static int nInc = 0; // number of included files + static int nAssign = 0; // number of assignments to the same setting + + public static void main(String[] args) throws Exception { + System.setProperty("java.security.krb5.conf", "f"); + for (var i = 0; i < 10_000; i++) { + test(); + } + } + + static void test() throws Exception { + nInc = 0; + nAssign = 0; + write("f"); + if (nAssign != 0) { + Config.refresh(); + var j = Config.getInstance().getAll("section", "sub", "x"); + var r = readRaw("f", new ArrayList()) + .stream() + .collect(Collectors.joining(" ")); + Asserts.assertEQ(r, j); + } + try (var dir = Files.newDirectoryStream(Path.of("."), "f*")) { + for (var f : dir) { + Files.delete(f); + } + } + } + + // read settings as raw files + static List readRaw(String f, List list) throws IOException { + for (var s : Files.readAllLines(Path.of(f))) { + if (s.startsWith("include ")) { + readRaw(s.substring(8), list); + } + if (s.contains("x = ")) { + list.add(s.substring(s.indexOf("x = ") + 4)); + } + } + return list; + } + + // write krb5.conf with random include + static void write(String f) throws IOException { + var p = Path.of(f); + if (Files.exists(p)) return; // do not overwrite, same file can be + // included twice + var content = new ArrayList(); + content.add("[section]"); // always starts with section + for (var i = 0; i < sr.nextInt(5); i++) { + if (sr.nextBoolean()) { // might have more section(s) + content.add("[section]"); + } + if (sr.nextBoolean()) { // style 1: { on subsection line + content.add("sub = {"); + } else { + content.add("sub = "); + if (sr.nextBoolean()) { + content.add("{"); // style 2: { on individual line + } else { + // style 3: { on key-value line + content.add("{ x = " + sr.nextInt(99999999)); + nAssign++; + } + } + for (var j = 0; j < sr.nextInt(3); j++) { // might have more + content.add("x = " + sr.nextInt(99999999)); + nAssign++; + } + content.add("}"); + } + // randomly throw in include lines + for (var i = 0; i < sr.nextInt(3); i++) { + if (nInc < 98) { + // include file name is random, so there could be dup + // but name length always grows, so no recursive. + // Extra length could be 1 digit or 2 digits, so the + // same file can be included on 2 levels, e.g. f1 includes + // f12 and f123, and f12 includes f123 again. + var inc = f + sr.nextInt(100); + content.add(sr.nextInt(content.size() + 1), + "include " + Path.of(inc).toAbsolutePath()); + nInc++; + write(inc); + } + } + Files.write(p, content); + } +} diff --git a/test/jdk/sun/security/krb5/config/IncludeSameKey.java b/test/jdk/sun/security/krb5/config/IncludeSameKey.java new file mode 100644 index 00000000000..c0802baa177 --- /dev/null +++ b/test/jdk/sun/security/krb5/config/IncludeSameKey.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import jdk.test.lib.Asserts; +import sun.security.krb5.Config; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +/* + * @test + * @bug 8356997 + * @summary Support "include" anywhere + * @modules java.security.jgss/sun.security.krb5 + * @library /test/lib + * @run main/othervm IncludeSameKey + */ +public class IncludeSameKey { + public static void main(String[] args) throws Exception { + var cwd = Path.of("").toAbsolutePath().toString(); + Files.writeString(Path.of("krb5.conf"), String.format(""" + include %1$s/outside + [a] + include %1$s/beginsec + b = { + c = 1 + } + [a] + b = { + c = 2 + } + include %1$s/insec + include %1$s/insec2 + b = { + include %1$s/insubsec + c = 3 + include %1$s/endsubsec + } + include %1$s/endsec + """, cwd)); + for (var inc : List.of("outside", "beginsec", "insec", "insec2", + "insubsec", "endsubsec", "endsec")) { + Files.writeString(Path.of(inc), String.format(""" + [a] + b = { + c = %s + } + """, inc)); + } + System.setProperty("java.security.krb5.conf", "krb5.conf"); + Asserts.assertEQ(Config.getInstance().getAll("a", "b", "c"), + "outside beginsec 1 2 insec insec2 insubsec 3 endsubsec endsec"); + } +} From b02c1256768bc9983d4dba899cd19219e11a380a Mon Sep 17 00:00:00 2001 From: Jatin Bhateja Date: Wed, 23 Jul 2025 13:31:15 +0000 Subject: [PATCH 55/94] 8350896: Integer/Long.compress gets wrong type from CompressBitsNode::Value Co-authored-by: Emanuel Peter Reviewed-by: thartmann --- src/hotspot/share/opto/intrinsicnode.cpp | 193 ++++- .../c2/gvn/TestBitCompressValueTransform.java | 676 ++++++++++++++++++ .../ir_framework/test/IREncodingPrinter.java | 1 + 3 files changed, 833 insertions(+), 37 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/c2/gvn/TestBitCompressValueTransform.java diff --git a/src/hotspot/share/opto/intrinsicnode.cpp b/src/hotspot/share/opto/intrinsicnode.cpp index 4f131a39f38..8674866b23e 100644 --- a/src/hotspot/share/opto/intrinsicnode.cpp +++ b/src/hotspot/share/opto/intrinsicnode.cpp @@ -237,58 +237,167 @@ static const Type* bitshuffle_value(const TypeInteger* src_type, const TypeInteg jlong hi = bt == T_INT ? max_jint : max_jlong; jlong lo = bt == T_INT ? min_jint : min_jlong; + assert(bt == T_INT || bt == T_LONG, ""); - if(mask_type->is_con() && mask_type->get_con_as_long(bt) != -1L) { + // Rule 1: Bit compression selects the source bits corresponding to true mask bits, + // packs them and places them contiguously at destination bit positions + // starting from least significant bit, remaining higher order bits are set + // to zero. + + // Rule 2: Bit expansion is a reverse process, which sequentially reads source bits + // starting from LSB and places them at bit positions in result value where + // corresponding mask bits are 1. Thus, bit expansion for non-negative mask + // value will always generate a +ve value, this is because sign bit of result + // will never be set to 1 as corresponding mask bit is always 0. + + // Case A) Constant mask + if (mask_type->is_con()) { jlong maskcon = mask_type->get_con_as_long(bt); - int bitcount = population_count(static_cast(bt == T_INT ? maskcon & 0xFFFFFFFFL : maskcon)); if (opc == Op_CompressBits) { - // Bit compression selects the source bits corresponding to true mask bits - // and lays them out contiguously at destination bit positions starting from - // LSB, remaining higher order bits are set to zero. - // Thus, it will always generate a +ve value i.e. sign bit set to 0 if - // any bit of constant mask value is zero. - lo = 0L; - hi = (1UL << bitcount) - 1; + // Case A.1 bit compression:- + // For an outlier mask value of -1 upper bound of the result equals + // maximum integral value, for any other mask value its computed using + // following formula + // Result.Hi = 1 << popcount(mask_bits) - 1 + // + // For mask values other than -1, lower bound of the result is estimated + // as zero, by assuming at least one mask bit is zero and corresponding source + // bit will be masked, hence result of bit compression will always be + // non-negative value. For outlier mask value of -1, assume all source bits + // apart from most significant bit were set to 0, thereby resulting in + // a minimum integral value. + // e.g. + // src = 0xXXXXXXXX (non-constant source) + // mask = 0xEFFFFFFF (constant mask) + // result.hi = 0x7FFFFFFF + // result.lo = 0 + if (maskcon != -1L) { + int bitcount = population_count(static_cast(bt == T_INT ? maskcon & 0xFFFFFFFFL : maskcon)); + hi = (1UL << bitcount) - 1; + lo = 0L; + } else { + // preserve originally assigned hi (MAX_INT/LONG) and lo (MIN_INT/LONG) values + // for unknown source bits. + assert(hi == (bt == T_INT ? max_jint : max_jlong), ""); + assert(lo == (bt == T_INT ? min_jint : min_jlong), ""); + } } else { + // Case A.2 bit expansion:- assert(opc == Op_ExpandBits, ""); - // Expansion sequentially reads source bits starting from LSB - // and places them over destination at bit positions corresponding - // set mask bit. Thus bit expansion for non-negative mask value - // will always generate a +ve value. - hi = maskcon >= 0L ? maskcon : maskcon ^ lo; - lo = maskcon >= 0L ? 0L : lo; + if (maskcon >= 0L) { + // Case A.2.1 constant mask >= 0 + // Result.Hi = mask, optimistically assuming all source bits + // read starting from least significant bit positions are 1. + // Result.Lo = 0, because at least one bit in mask is zero. + // e.g. + // src = 0xXXXXXXXX (non-constant source) + // mask = 0x7FFFFFFF (constant mask >= 0) + // result.hi = 0x7FFFFFFF + // result.lo = 0 + hi = maskcon; + lo = 0L; + } else { + // Case A.2.2) mask < 0 + // For constant mask strictly less than zero, the maximum result value will be + // the same as the mask value with its sign bit flipped, assuming all source bits + // except the MSB bit are set(one). + // + // To compute minimum result value we assume all but last read source bit as zero, + // this is because sign bit of result will always be set to 1 while other bit + // corresponding to set mask bit should be zero. + // e.g. + // src = 0xXXXXXXXX (non-constant source) + // mask = 0xEFFFFFFF (constant mask) + // result.hi = 0xEFFFFFFF ^ 0x80000000 = 0x6FFFFFFF + // result.lo = 0x80000000 + // + hi = maskcon ^ lo; + // lo still retains MIN_INT/LONG. + assert(lo == (bt == T_INT ? min_jint : min_jlong), ""); + } } } + // Case B) Non-constant mask. if (!mask_type->is_con()) { - int mask_max_bw; - int max_bw = bt == T_INT ? 32 : 64; - // Case 1) Mask value range includes -1. - if ((mask_type->lo_as_long() < 0L && mask_type->hi_as_long() >= -1L)) { - mask_max_bw = max_bw; - // Case 2) Mask value range is less than -1. - } else if (mask_type->hi_as_long() < -1L) { - mask_max_bw = max_bw - 1; - } else { - // Case 3) Mask value range only includes +ve values. - assert(mask_type->lo_as_long() >= 0, ""); - jlong clz = count_leading_zeros(mask_type->hi_as_long()); - clz = bt == T_INT ? clz - 32 : clz; - mask_max_bw = max_bw - clz; - } if ( opc == Op_CompressBits) { - lo = mask_max_bw == max_bw ? lo : 0L; - // Compress operation is inherently an unsigned operation and - // result value range is primarily dependent on true count - // of participating mask value. - hi = mask_max_bw < max_bw ? (1L << mask_max_bw) - 1 : src_type->hi_as_long(); + int result_bit_width; + int mask_bit_width = bt == T_INT ? 32 : 64; + if ((mask_type->lo_as_long() < 0L && mask_type->hi_as_long() >= -1L)) { + // Case B.1 The mask value range includes -1, hence we may use all bits, + // the result has the whole value range. + result_bit_width = mask_bit_width; + } else if (mask_type->hi_as_long() < -1L) { + // Case B.2 Mask value range is strictly less than -1, this indicates presence of at least + // one unset(zero) bit in mask value, thus as per Rule 1, bit compression will always + // result in a non-negative value. This guarantees that MSB bit of result value will + // always be set to zero. + result_bit_width = mask_bit_width - 1; + } else { + assert(mask_type->lo_as_long() >= 0, ""); + // Case B.3 Mask value range only includes non-negative values. Since all integral + // types honours an invariant that TypeInteger._lo <= TypeInteger._hi, thus computing + // leading zero bits of upper bound of mask value will allow us to ascertain + // optimistic upper bound of result i.e. all the bits other than leading zero bits + // can be assumed holding 1 value. + jlong clz = count_leading_zeros(mask_type->hi_as_long()); + // Here, result of clz is w.r.t to long argument, hence for integer argument + // we explicitly subtract 32 from the result. + clz = bt == T_INT ? clz - 32 : clz; + result_bit_width = mask_bit_width - clz; + } + // If the number of bits required to for the mask value range is less than the + // full bit width of the integral type, then the MSB bit is guaranteed to be zero, + // thus the compression result will never be a -ve value and we can safely set the + // lower bound of the bit compression to zero. + lo = result_bit_width == mask_bit_width ? lo : 0L; + + assert(hi == (bt == T_INT ? max_jint : max_jlong), ""); + assert(lo == (bt == T_INT ? min_jint : min_jlong) || lo == 0, ""); + + if (src_type->lo_as_long() >= 0) { + // Lemma 1: For strictly non-negative src, the result of the compression will never be + // greater than src. + // Proof: Since src is a non-negative value, its most significant bit is always 0. + // Thus even if the corresponding MSB of the mask is one, the result will be a +ve + // value. There are three possible cases + // a. All the mask bits corresponding to set source bits are unset(zero). + // b. All the mask bits corresponding to set source bits are set(one) + // c. Some mask bits corresponding to set source bits are set(one) while others are unset(zero) + // + // Case a. results into an allzero result, while Case b. gives us the upper bound which is equals source + // value, while for Case c. the result will lie within [0, src] + // + hi = src_type->hi_as_long(); + lo = 0L; + } + + if (result_bit_width < mask_bit_width) { + // Rule 3: + // We can further constrain the upper bound of bit compression if the number of bits + // which can be set(one) is less than the maximum number of bits of integral type. + hi = MIN2((jlong)((1UL << result_bit_width) - 1L), hi); + } } else { assert(opc == Op_ExpandBits, ""); jlong max_mask = mask_type->hi_as_long(); + jlong min_mask = mask_type->lo_as_long(); // Since mask here a range and not a constant value, hence being // conservative in determining the value range of result. - lo = mask_type->lo_as_long() >= 0L ? 0L : lo; - hi = mask_type->lo_as_long() >= 0L ? max_mask : hi; + if (min_mask >= 0L) { + // Lemma 2: Based on the integral type invariant ie. TypeInteger.lo <= TypeInteger.hi, + // if the lower bound of non-constant mask is a non-negative value then result can never + // be greater than the mask. + // Proof: Since lower bound of the mask is a non-negative value, hence most significant + // bit of its entire value must be unset(zero). If all the lower order 'n' source bits + // where n corresponds to popcount of mask are set(ones) then upper bound of the result equals + // mask. In order to compute the lower bound, we pssimistically assume all the lower order 'n' + // source bits are unset(zero) there by resuling into a zero value. + hi = max_mask; + lo = 0; + } else { + // preserve the lo and hi bounds estimated till now. + } } } @@ -329,6 +438,11 @@ const Type* CompressBitsNode::Value(PhaseGVN* phase) const { static_cast(TypeLong::make(res)); } + // Result is zero if src is zero irrespective of mask value. + if (src_type == TypeInteger::zero(bt)) { + return TypeInteger::zero(bt); + } + return bitshuffle_value(src_type, mask_type, Op_CompressBits, bt); } @@ -365,5 +479,10 @@ const Type* ExpandBitsNode::Value(PhaseGVN* phase) const { static_cast(TypeLong::make(res)); } + // Result is zero if src is zero irrespective of mask value. + if (src_type == TypeInteger::zero(bt)) { + return TypeInteger::zero(bt); + } + return bitshuffle_value(src_type, mask_type, Op_ExpandBits, bt); } diff --git a/test/hotspot/jtreg/compiler/c2/gvn/TestBitCompressValueTransform.java b/test/hotspot/jtreg/compiler/c2/gvn/TestBitCompressValueTransform.java new file mode 100644 index 00000000000..98f26f120b1 --- /dev/null +++ b/test/hotspot/jtreg/compiler/c2/gvn/TestBitCompressValueTransform.java @@ -0,0 +1,676 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8350896 + * @library /test/lib / + * @summary C2: wrong result: Integer/Long.compress gets wrong type from CompressBitsNode::Value. + * @run driver compiler.c2.gvn.TestBitCompressValueTransform + */ +package compiler.c2.gvn; + +import jdk.test.lib.Asserts; +import compiler.lib.ir_framework.*; +import compiler.lib.generators.*; + +public class TestBitCompressValueTransform { + + public static final int field_I = 0x400_0000; + public static final long field_L = 0x400_0000_0000_0000L; + public static final int gold_I = Integer.valueOf(Integer.compress(0x8000_0000, field_I)); + public static final long gold_L = Long.valueOf(Long.compress(0x8000_0000_0000_0000L, field_L)); + + public static RestrictableGenerator GEN_I = Generators.G.ints(); + public static RestrictableGenerator GEN_L = Generators.G.longs(); + + public final int LIMIT_I1 = GEN_I.next(); + public final int LIMIT_I2 = GEN_I.next(); + public final int LIMIT_I3 = GEN_I.next(); + public final int LIMIT_I4 = GEN_I.next(); + public final int LIMIT_I5 = GEN_I.next(); + public final int LIMIT_I6 = GEN_I.next(); + public final int LIMIT_I7 = GEN_I.next(); + public final int LIMIT_I8 = GEN_I.next(); + + public final long LIMIT_L1 = GEN_L.next(); + public final long LIMIT_L2 = GEN_L.next(); + public final long LIMIT_L3 = GEN_L.next(); + public final long LIMIT_L4 = GEN_L.next(); + public final long LIMIT_L5 = GEN_L.next(); + public final long LIMIT_L6 = GEN_L.next(); + public final long LIMIT_L7 = GEN_L.next(); + public final long LIMIT_L8 = GEN_L.next(); + + public final int BOUND_LO_I = GEN_I.next(); + public final int BOUND_HI_I = GEN_I.next(); + + public final long BOUND_LO_L = GEN_L.next(); + public final long BOUND_HI_L = GEN_L.next(); + + @Test + @IR (counts = { IRNode.COMPRESS_BITS, " >0 " }, applyIfCPUFeature = { "bmi2", "true" }) + public long test1(long value) { + return Long.compress(0x8000_0000_0000_0000L, value); + } + + @Run(test = "test1") + public void run1(RunInfo info) { + long res = 0; + for (int i = 0; i < 10000; i++) { + res |= test1(field_L); + } + Asserts.assertEQ(res, gold_L); + } + + + @Test + @IR (counts = { IRNode.COMPRESS_BITS, " >0 " }, applyIfCPUFeature = { "bmi2", "true" }) + public int test2(int value) { + return Integer.compress(0x8000_0000, value); + } + + @Run(test = "test2") + public void run2(RunInfo info) { + int res = 0; + for (int i = 0; i < 10000; i++) { + res |= test2(field_I); + } + Asserts.assertEQ(res, gold_I); + } + + @Test + @IR (counts = { IRNode.COMPRESS_BITS, " 0 "} , failOn = { IRNode.UNSTABLE_IF_TRAP }, applyIfCPUFeature = { "bmi2", "true" }) + public int test3(int value) { + int filter_bits = value & 0xF; + int compress_bits = Integer.compress(15, filter_bits); + if (compress_bits > 15) { + value = -1; + } + return value; + } + + @Run(test = "test3") + public void run3(RunInfo info) { + int res = 0; + for (int i = 1; i < 10000; i++) { + res |= test3(i); + } + Asserts.assertLTE(0, res); + } + + @Test + @IR (counts = { IRNode.COMPRESS_BITS, " 0 "} , failOn = { IRNode.UNSTABLE_IF_TRAP }, applyIfCPUFeature = { "bmi2", "true" }) + public long test4(long value) { + long filter_bits = value & 0xFL; + long compress_bits = Long.compress(15L, filter_bits); + if (compress_bits > 15L) { + value = -1; + } + return value; + } + + @Run(test = "test4") + public void run4(RunInfo info) { + long res = 0; + for (long i = 1; i < 10000; i++) { + res |= test4(i); + } + Asserts.assertLTE(0L, res); + } + + @Test + @IR (counts = { IRNode.COMPRESS_BITS, " >0 " }, applyIfCPUFeature = { "bmi2", "true" }) + public long test5(long value) { + // Since value range includes -1 hence with mask + // and value as -1 all the result bits will be set. + long mask = Long.min(10000L, Long.max(-10000L, value)); + return Long.compress(value, mask); + } + + @Run(test = "test5") + public void run5(RunInfo info) { + long res = 0; + for (int i = -10000; i < 10000; i++) { + res |= test5((long)i); + } + Asserts.assertEQ(-1L, res); + } + + @Test + @IR (counts = { IRNode.COMPRESS_BITS, " >0 " }, applyIfCPUFeature = { "bmi2", "true" }) + public long test6(long value) { + // For mask within a strictly -ve value range less than -1, + // result of compression will always be a +ve value. + long mask = Long.min(-2L, Long.max(-10000L, value)); + return Long.compress(value, mask); + } + + @Run(test = "test6") + public void run6(RunInfo info) { + long res = 0; + for (int i = -10000; i < 10000; i++) { + res |= test6((long)i); + } + Asserts.assertLTE(0L, res); + } + + @Test + @IR (counts = { IRNode.COMPRESS_BITS, " >0 " }, applyIfCPUFeature = { "bmi2", "true" }) + public long test7(long value) { + // For mask within a strictly +ve value range, + // result of compression will always be a +ve value with + // upper bound capped at max mask value. + long mask = Long.min(10000L, Long.max(0L, value)); + return Long.compress(value, mask); + } + + @Run(test = "test7") + public void run7(RunInfo info) { + long res = Long.MIN_VALUE; + for (int i = -10000; i < 10000; i++) { + res = Long.max(test7((long)i), res); + } + Asserts.assertGTE(10000L, res); + } + + @Test + @IR (counts = { IRNode.COMPRESS_BITS, " >0 " }, applyIfCPUFeature = { "bmi2", "true" }) + public int test8(int value) { + // Since value range includes -1 hence with mask + // and value as -1 all the result bits will be set. + int mask = Integer.min(10000, Integer.max(-10000, value)); + return Integer.compress(value, mask); + } + + @Run(test = "test8") + public void run8(RunInfo info) { + int res = 0; + for (int i = -10000; i < 10000; i++) { + res |= test8(i); + } + Asserts.assertEQ(-1, res); + } + + @Test + @IR (counts = { IRNode.COMPRESS_BITS, " >0 " }, applyIfCPUFeature = { "bmi2", "true" }) + public int test9(int value) { + // For mask within a strictly -ve value range less than -1, + // result of compression will always be a +ve value. + int mask = Integer.min(-2, Integer.max(-10000, value)); + return Integer.compress(value, mask); + } + + @Run(test = "test9") + public void run9(RunInfo info) { + int res = 0; + for (int i = -10000; i < 10000; i++) { + res |= test9(i); + } + Asserts.assertLTE(0, res); + } + + @Test + @IR (counts = { IRNode.COMPRESS_BITS, " >0 " }, applyIfCPUFeature = { "bmi2", "true" }) + public int test10(int value) { + // For mask within a strictly +ve value range, + // result of compression will always be a +ve value with + // upper bound capped at max mask value. + int mask = Integer.min(10000, Integer.max(0, value)); + return Integer.compress(value, mask); + } + + @Run(test = "test10") + public void run10(RunInfo info) { + int res = Integer.MIN_VALUE; + for (int i = -10000; i < 10000; i++) { + res = Integer.max(test10(i), res); + } + Asserts.assertGTE(10000, res); + } + + @Test + @IR (counts = { IRNode.COMPRESS_BITS, " 0 " }) + public int test11(int value) { + // For constant zero input, compress is folded to zero + int mask = Integer.min(10000, Integer.max(0, value)); + return Integer.compress(0, mask); + } + + @Run(test = "test11") + public void run11(RunInfo info) { + int res = 0; + for (int i = -10000; i < 10000; i++) { + res |= test11(i); + } + Asserts.assertEQ(0, res); + } + + @Test + @IR (counts = { IRNode.COMPRESS_BITS, " 0 " }) + public long test12(long value) { + // For constant zero input, compress is folded to zero + long mask = Long.min(10000L, Long.max(0L, value)); + return Long.compress(0L, mask); + } + + @Run(test = "test12") + public void run12(RunInfo info) { + long res = 0; + for (int i = -10000; i < 10000; i++) { + res |= test12(i); + } + Asserts.assertEQ(0L, res); + } + + @Test + @IR (counts = { IRNode.EXPAND_BITS, " 0 " }) + public int test13(int value) { + // For constant zero input, expand is folded to zero + int mask = Integer.min(10000, Integer.max(0, value)); + return Integer.expand(0, mask); + } + + @Run(test = "test13") + public void run13(RunInfo info) { + int res = 0; + for (int i = -10000; i < 10000; i++) { + res |= test13(i); + } + Asserts.assertEQ(0, res); + } + + @Test + @IR (counts = { IRNode.EXPAND_BITS, " 0 " }) + public long test14(long value) { + // For constant zero input, compress is folded to zero + long mask = Long.min(10000L, Long.max(0L, value)); + return Long.expand(0L, mask); + } + + @Run(test = "test14") + public void run14(RunInfo info) { + long res = 0; + for (int i = -10000; i < 10000; i++) { + res |= test14(i); + } + Asserts.assertEQ(0L, res); + } + + @Test + @IR (counts = { IRNode.COMPRESS_BITS, " >0 " }, applyIfCPUFeature = {"bmi2" , "true"}) + public int test15(int src, int mask) { + // src_type = [min_int + 1, -1] + src = Math.max(Integer.MIN_VALUE + 1, Math.min(src, -1)); + return Integer.compress(src, mask); + } + + @Run (test = "test15") + public void run15(RunInfo info) { + int res = 0; + for (int i = 0; i < 10000; i++) { + res |= test15(0, 0); + } + Asserts.assertEQ(0, res); + } + + @DontCompile + public int test16_interpreted(int src, int mask) { + src = Math.max(BOUND_LO_I, Math.min(src, BOUND_HI_I)); + int res = Integer.compress(src, mask); + + if (res > LIMIT_I1) { + res += 1; + } + if (res > LIMIT_I2) { + res += 2; + } + if (res > LIMIT_I3) { + res += 4; + } + if (res > LIMIT_I4) { + res += 8; + } + if (res > LIMIT_I5) { + res += 16; + } + if (res > LIMIT_I6) { + res += 32; + } + if (res > LIMIT_I7) { + res += 64; + } + if (res > LIMIT_I8) { + res += 128; + } + return res; + } + + @Test + @IR (counts = { IRNode.COMPRESS_BITS, " >0 " }, applyIfCPUFeature = {"bmi2" , "true"}) + public int test16(int src, int mask) { + src = Math.max(BOUND_LO_I, Math.min(src, BOUND_HI_I)); + int res = Integer.compress(src, mask); + + // Check the result with some random value ranges, if any of the + // following conditions incorrectly constant folds the result will + // not comply with the interpreter. + + if (res > LIMIT_I1) { + res += 1; + } + if (res > LIMIT_I2) { + res += 2; + } + if (res > LIMIT_I3) { + res += 4; + } + if (res > LIMIT_I4) { + res += 8; + } + if (res > LIMIT_I5) { + res += 16; + } + if (res > LIMIT_I6) { + res += 32; + } + if (res > LIMIT_I7) { + res += 64; + } + if (res > LIMIT_I8) { + res += 128; + } + return res; + } + + @Run (test = "test16") + public void run16(RunInfo info) { + int actual = 0; + int expected = 0; + + for (int i = 0; i < 10000; i++) { + int arg1 = GEN_I.next(); + int arg2 = GEN_I.next(); + + actual += test16(arg1, arg2); + expected += test16_interpreted(arg1, arg2); + } + Asserts.assertEQ(actual, expected); + } + + @DontCompile + public int test17_interpreted(int src, int mask) { + src = Math.max(BOUND_LO_I, Math.min(src, BOUND_HI_I)); + int res = Integer.expand(src, mask); + + if (res > LIMIT_I1) { + res += 1; + } + if (res > LIMIT_I2) { + res += 2; + } + if (res > LIMIT_I3) { + res += 4; + } + if (res > LIMIT_I4) { + res += 8; + } + if (res > LIMIT_I5) { + res += 16; + } + if (res > LIMIT_I6) { + res += 32; + } + if (res > LIMIT_I7) { + res += 64; + } + if (res > LIMIT_I8) { + res += 128; + } + return res; + } + + @Test + @IR (counts = { IRNode.EXPAND_BITS, " >0 " }, applyIfCPUFeature = {"bmi2" , "true"}) + public int test17(int src, int mask) { + src = Math.max(BOUND_LO_I, Math.min(src, BOUND_HI_I)); + int res = Integer.expand(src, mask); + + // Check the result with some random value ranges, if any of the + // following conditions incorrectly constant folds the result will + // not comply with the interpreter. + + if (res > LIMIT_I1) { + res += 1; + } + if (res > LIMIT_I2) { + res += 2; + } + if (res > LIMIT_I3) { + res += 4; + } + if (res > LIMIT_I4) { + res += 8; + } + if (res > LIMIT_I5) { + res += 16; + } + if (res > LIMIT_I6) { + res += 32; + } + if (res > LIMIT_I7) { + res += 64; + } + if (res > LIMIT_I8) { + res += 128; + } + return res; + } + + @Run (test = "test17") + public void run17(RunInfo info) { + int actual = 0; + int expected = 0; + + for (int i = 0; i < 10000; i++) { + int arg1 = GEN_I.next(); + int arg2 = GEN_I.next(); + + actual += test16(arg1, arg2); + expected += test16_interpreted(arg1, arg2); + } + Asserts.assertEQ(actual, expected); + } + + @DontCompile + public long test18_interpreted(long src, long mask) { + src = Math.max(BOUND_LO_L, Math.min(src, BOUND_HI_L)); + long res = Long.compress(src, mask); + + if (res > LIMIT_L1) { + res += 1; + } + if (res > LIMIT_L2) { + res += 2; + } + if (res > LIMIT_L3) { + res += 4; + } + if (res > LIMIT_L4) { + res += 8; + } + if (res > LIMIT_L5) { + res += 16; + } + if (res > LIMIT_L6) { + res += 32; + } + if (res > LIMIT_L7) { + res += 64; + } + if (res > LIMIT_L8) { + res += 128; + } + return res; + } + + @Test + @IR (counts = { IRNode.COMPRESS_BITS, " >0 " }, applyIfCPUFeature = {"bmi2" , "true"}) + public long test18(long src, long mask) { + src = Math.max(BOUND_LO_L, Math.min(src, BOUND_HI_L)); + long res = Long.compress(src, mask); + + // Check the result with some random value ranges, if any of the + // following conditions incorrectly constant folds the result will + // not comply with the interpreter. + + if (res > LIMIT_L1) { + res += 1; + } + if (res > LIMIT_L2) { + res += 2; + } + if (res > LIMIT_L3) { + res += 4; + } + if (res > LIMIT_L4) { + res += 8; + } + if (res > LIMIT_L5) { + res += 16; + } + if (res > LIMIT_L6) { + res += 32; + } + if (res > LIMIT_L7) { + res += 64; + } + if (res > LIMIT_L8) { + res += 128; + } + return res; + } + + @Run (test = "test18") + public void run18(RunInfo info) { + long actual = 0; + long expected = 0; + + for (int i = 0; i < 10000; i++) { + long arg1 = GEN_L.next(); + long arg2 = GEN_L.next(); + + actual += test18(arg1, arg2); + expected += test18_interpreted(arg1, arg2); + } + Asserts.assertEQ(actual, expected); + } + + @DontCompile + public long test19_interpreted(long src, long mask) { + src = Math.max(BOUND_LO_L, Math.min(src, BOUND_HI_L)); + long res = Long.expand(src, mask); + + if (res > LIMIT_L1) { + res += 1; + } + if (res > LIMIT_L2) { + res += 2; + } + if (res > LIMIT_L3) { + res += 4; + } + if (res > LIMIT_L4) { + res += 8; + } + if (res > LIMIT_L5) { + res += 16; + } + if (res > LIMIT_L6) { + res += 32; + } + if (res > LIMIT_L7) { + res += 64; + } + if (res > LIMIT_L8) { + res += 128; + } + return res; + } + + @Test + @IR (counts = { IRNode.EXPAND_BITS, " >0 " }, applyIfCPUFeature = {"bmi2" , "true"}) + public long test19(long src, long mask) { + src = Math.max(BOUND_LO_L, Math.min(src, BOUND_HI_L)); + long res = Long.expand(src, mask); + + // Check the result with some random value ranges, if any of the + // following conditions incorrectly constant folds the result will + // not comply with the interpreter. + + if (res > LIMIT_L1) { + res += 1; + } + if (res > LIMIT_L2) { + res += 2; + } + if (res > LIMIT_L3) { + res += 4; + } + if (res > LIMIT_L4) { + res += 8; + } + if (res > LIMIT_L5) { + res += 16; + } + if (res > LIMIT_L6) { + res += 32; + } + if (res > LIMIT_L7) { + res += 64; + } + if (res > LIMIT_L8) { + res += 128; + } + return res; + } + + @Run (test = "test19") + public void run19(RunInfo info) { + long actual = 0; + long expected = 0; + + for (int i = 0; i < 10000; i++) { + long arg1 = GEN_L.next(); + long arg2 = GEN_L.next(); + + actual += test19(arg1, arg2); + expected += test19_interpreted(arg1, arg2); + } + Asserts.assertEQ(actual, expected); + } + + public static void main(String[] args) { + TestFramework.run(TestBitCompressValueTransform.class); + } +} diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/test/IREncodingPrinter.java b/test/hotspot/jtreg/compiler/lib/ir_framework/test/IREncodingPrinter.java index eef9998ebf8..6662acf8e9e 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/test/IREncodingPrinter.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/test/IREncodingPrinter.java @@ -105,6 +105,7 @@ public class IREncodingPrinter { "avx512f", "avx512_fp16", "avx512_vnni", + "bmi2", // AArch64 "sha3", "asimd", From 743c821289a6562972364b5dcce8dd29a786264a Mon Sep 17 00:00:00 2001 From: Evgeny Astigeevich Date: Wed, 23 Jul 2025 13:51:49 +0000 Subject: [PATCH 56/94] 8362193: Re-work MacOS/AArch64 SpinPause to handle SB Reviewed-by: shade, aph --- src/hotspot/cpu/aarch64/globals_aarch64.hpp | 3 +- .../cpu/aarch64/macroAssembler_aarch64.cpp | 1 + src/hotspot/cpu/aarch64/spin_wait_aarch64.cpp | 52 ++++++++++++++++++ src/hotspot/cpu/aarch64/spin_wait_aarch64.hpp | 8 ++- .../cpu/aarch64/vm_version_aarch64.cpp | 22 ++------ .../os_cpu/bsd_aarch64/os_bsd_aarch64.cpp | 54 +++++++++---------- .../flags/jvmFlagConstraintsRuntime.cpp | 22 ++++++++ .../flags/jvmFlagConstraintsRuntime.hpp | 3 +- .../hotspot/gtest/aarch64/test_spin_pause.cpp | 33 ++++++++++++ .../jtreg/gtest/TestSpinPauseAArch64.java | 46 ++++++++++++++++ 10 files changed, 192 insertions(+), 52 deletions(-) create mode 100644 src/hotspot/cpu/aarch64/spin_wait_aarch64.cpp create mode 100644 test/hotspot/gtest/aarch64/test_spin_pause.cpp create mode 100644 test/hotspot/jtreg/gtest/TestSpinPauseAArch64.java diff --git a/src/hotspot/cpu/aarch64/globals_aarch64.hpp b/src/hotspot/cpu/aarch64/globals_aarch64.hpp index ef741c2007a..8e520314c8b 100644 --- a/src/hotspot/cpu/aarch64/globals_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/globals_aarch64.hpp @@ -117,7 +117,8 @@ define_pd_global(intx, InlineSmallCode, 1000); product(ccstr, OnSpinWaitInst, "yield", DIAGNOSTIC, \ "The instruction to use to implement " \ "java.lang.Thread.onSpinWait()." \ - "Options: none, nop, isb, yield, sb.") \ + "Valid values are: none, nop, isb, yield, sb.") \ + constraint(OnSpinWaitInstNameConstraintFunc, AtParse) \ product(uint, OnSpinWaitInstCount, 1, DIAGNOSTIC, \ "The number of OnSpinWaitInst instructions to generate." \ "It cannot be used with OnSpinWaitInst=none.") \ diff --git a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp index e8290ae10ca..6ae6861e38b 100644 --- a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp @@ -6816,6 +6816,7 @@ void MacroAssembler::spin_wait() { yield(); break; case SpinWait::SB: + assert(VM_Version::supports_sb(), "current CPU does not support SB instruction"); sb(); break; default: diff --git a/src/hotspot/cpu/aarch64/spin_wait_aarch64.cpp b/src/hotspot/cpu/aarch64/spin_wait_aarch64.cpp new file mode 100644 index 00000000000..7da0151d834 --- /dev/null +++ b/src/hotspot/cpu/aarch64/spin_wait_aarch64.cpp @@ -0,0 +1,52 @@ +/* + * 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. + */ + +#include "spin_wait_aarch64.hpp" +#include "utilities/debug.hpp" + +#include + +bool SpinWait::supports(const char *name) { + return name != nullptr && + (strcmp(name, "nop") == 0 || + strcmp(name, "isb") == 0 || + strcmp(name, "yield") == 0 || + strcmp(name, "sb") == 0 || + strcmp(name, "none") == 0); +} + +SpinWait::Inst SpinWait::from_name(const char* name) { + assert(supports(name), "checked by OnSpinWaitInstNameConstraintFunc"); + + if (strcmp(name, "nop") == 0) { + return SpinWait::NOP; + } else if (strcmp(name, "isb") == 0) { + return SpinWait::ISB; + } else if (strcmp(name, "yield") == 0) { + return SpinWait::YIELD; + } else if (strcmp(name, "sb") == 0) { + return SpinWait::SB; + } + + return SpinWait::NONE; +} diff --git a/src/hotspot/cpu/aarch64/spin_wait_aarch64.hpp b/src/hotspot/cpu/aarch64/spin_wait_aarch64.hpp index 08850f05f53..0e96a4b7157 100644 --- a/src/hotspot/cpu/aarch64/spin_wait_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/spin_wait_aarch64.hpp @@ -19,7 +19,6 @@ * 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 CPU_AARCH64_SPIN_WAIT_AARCH64_HPP @@ -39,11 +38,16 @@ private: Inst _inst; int _count; + Inst from_name(const char *name); + public: - SpinWait(Inst inst = NONE, int count = 0) : _inst(inst), _count(count) {} + SpinWait(Inst inst = NONE, int count = 0) : _inst(inst), _count(inst == NONE ? 0 : count) {} + SpinWait(const char *name, int count) : SpinWait(from_name(name), count) {} Inst inst() const { return _inst; } int inst_count() const { return _count; } + + static bool supports(const char *name); }; #endif // CPU_AARCH64_SPIN_WAIT_AARCH64_HPP diff --git a/src/hotspot/cpu/aarch64/vm_version_aarch64.cpp b/src/hotspot/cpu/aarch64/vm_version_aarch64.cpp index 6ee4a0023c6..9321dd0542e 100644 --- a/src/hotspot/cpu/aarch64/vm_version_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/vm_version_aarch64.cpp @@ -51,26 +51,12 @@ uintptr_t VM_Version::_pac_mask; SpinWait VM_Version::_spin_wait; static SpinWait get_spin_wait_desc() { - if (strcmp(OnSpinWaitInst, "nop") == 0) { - return SpinWait(SpinWait::NOP, OnSpinWaitInstCount); - } else if (strcmp(OnSpinWaitInst, "isb") == 0) { - return SpinWait(SpinWait::ISB, OnSpinWaitInstCount); - } else if (strcmp(OnSpinWaitInst, "yield") == 0) { - return SpinWait(SpinWait::YIELD, OnSpinWaitInstCount); - } else if (strcmp(OnSpinWaitInst, "sb") == 0) { - if (!VM_Version::supports_sb()) { - vm_exit_during_initialization("OnSpinWaitInst is SB but current CPU does not support SB instruction"); - } - return SpinWait(SpinWait::SB, OnSpinWaitInstCount); - } else if (strcmp(OnSpinWaitInst, "none") != 0) { - vm_exit_during_initialization("The options for OnSpinWaitInst are nop, isb, yield, sb, and none", OnSpinWaitInst); + SpinWait spin_wait(OnSpinWaitInst, OnSpinWaitInstCount); + if (spin_wait.inst() == SpinWait::SB && !VM_Version::supports_sb()) { + vm_exit_during_initialization("OnSpinWaitInst is SB but current CPU does not support SB instruction"); } - if (!FLAG_IS_DEFAULT(OnSpinWaitInstCount) && OnSpinWaitInstCount > 0) { - vm_exit_during_initialization("OnSpinWaitInstCount cannot be used for OnSpinWaitInst 'none'"); - } - - return SpinWait{}; + return spin_wait; } void VM_Version::initialize() { diff --git a/src/hotspot/os_cpu/bsd_aarch64/os_bsd_aarch64.cpp b/src/hotspot/os_cpu/bsd_aarch64/os_bsd_aarch64.cpp index b7556ca69da..f6e2d39e315 100644 --- a/src/hotspot/os_cpu/bsd_aarch64/os_bsd_aarch64.cpp +++ b/src/hotspot/os_cpu/bsd_aarch64/os_bsd_aarch64.cpp @@ -49,8 +49,10 @@ #include "runtime/sharedRuntime.hpp" #include "runtime/stubRoutines.hpp" #include "runtime/timer.hpp" +#include "runtime/vm_version.hpp" #include "signals_posix.hpp" #include "utilities/align.hpp" +#include "utilities/debug.hpp" #include "utilities/events.hpp" #include "utilities/vmError.hpp" @@ -524,40 +526,32 @@ static inline void atomic_copy64(const volatile void *src, volatile void *dst) { } extern "C" { - // needs local assembler label '1:' to avoid trouble when using linktime optimization int SpinPause() { // We don't use StubRoutines::aarch64::spin_wait stub in order to // avoid a costly call to os::current_thread_enable_wx() on MacOS. // We should return 1 if SpinPause is implemented, and since there - // will be a sequence of 11 instructions for NONE and YIELD and 12 - // instructions for NOP and ISB, SpinPause will always return 1. - uint64_t br_dst; - const int instructions_per_case = 2; - int64_t off = VM_Version::spin_wait_desc().inst() * instructions_per_case * Assembler::instruction_size; - - assert(VM_Version::spin_wait_desc().inst() >= SpinWait::NONE && - VM_Version::spin_wait_desc().inst() <= SpinWait::YIELD, "must be"); - assert(-1 == SpinWait::NONE, "must be"); - assert( 0 == SpinWait::NOP, "must be"); - assert( 1 == SpinWait::ISB, "must be"); - assert( 2 == SpinWait::YIELD, "must be"); - - asm volatile( - " adr %[d], 20 \n" // 20 == PC here + 5 instructions => address - // to entry for case SpinWait::NOP - " add %[d], %[d], %[o] \n" - " br %[d] \n" - " b 1f \n" // case SpinWait::NONE (-1) - " nop \n" // padding - " nop \n" // case SpinWait::NOP ( 0) - " b 1f \n" - " isb \n" // case SpinWait::ISB ( 1) - " b 1f \n" - " yield \n" // case SpinWait::YIELD ( 2) - "1: \n" - : [d]"=&r"(br_dst) - : [o]"r"(off) - : "memory"); + // will be always a sequence of instructions, SpinPause will always return 1. + switch (VM_Version::spin_wait_desc().inst()) { + case SpinWait::NONE: + break; + case SpinWait::NOP: + asm volatile("nop" : : : "memory"); + break; + case SpinWait::ISB: + asm volatile("isb" : : : "memory"); + break; + case SpinWait::YIELD: + asm volatile("yield" : : : "memory"); + break; + case SpinWait::SB: + assert(VM_Version::supports_sb(), "current CPU does not support SB instruction"); + asm volatile(".inst 0xd50330ff" : : : "memory"); + break; +#ifdef ASSERT + default: + ShouldNotReachHere(); +#endif + } return 1; } diff --git a/src/hotspot/share/runtime/flags/jvmFlagConstraintsRuntime.cpp b/src/hotspot/share/runtime/flags/jvmFlagConstraintsRuntime.cpp index 9e0825339c9..444988efdca 100644 --- a/src/hotspot/share/runtime/flags/jvmFlagConstraintsRuntime.cpp +++ b/src/hotspot/share/runtime/flags/jvmFlagConstraintsRuntime.cpp @@ -127,3 +127,25 @@ JVMFlag::Error NUMAInterleaveGranularityConstraintFunc(size_t value, bool verbos return JVMFlag::SUCCESS; } + +JVMFlag::Error OnSpinWaitInstNameConstraintFunc(ccstr value, bool verbose) { +#ifdef AARCH64 + if (value == nullptr) { + JVMFlag::printError(verbose, "OnSpinWaitInst cannot be empty\n"); + return JVMFlag::VIOLATES_CONSTRAINT; + } + + if (strcmp(value, "nop") != 0 && + strcmp(value, "isb") != 0 && + strcmp(value, "yield") != 0 && + strcmp(value, "sb") != 0 && + strcmp(value, "none") != 0) { + JVMFlag::printError(verbose, + "Unrecognized value %s for OnSpinWaitInst. Must be one of the following: " + "nop, isb, yield, sb, none\n", + value); + return JVMFlag::VIOLATES_CONSTRAINT; + } +#endif + return JVMFlag::SUCCESS; +} diff --git a/src/hotspot/share/runtime/flags/jvmFlagConstraintsRuntime.hpp b/src/hotspot/share/runtime/flags/jvmFlagConstraintsRuntime.hpp index 5ca28a73fb0..8425425c768 100644 --- a/src/hotspot/share/runtime/flags/jvmFlagConstraintsRuntime.hpp +++ b/src/hotspot/share/runtime/flags/jvmFlagConstraintsRuntime.hpp @@ -40,7 +40,8 @@ f(int, ObjectAlignmentInBytesConstraintFunc) \ f(int, ContendedPaddingWidthConstraintFunc) \ f(size_t, VMPageSizeConstraintFunc) \ - f(size_t, NUMAInterleaveGranularityConstraintFunc) + f(size_t, NUMAInterleaveGranularityConstraintFunc) \ + f(ccstr, OnSpinWaitInstNameConstraintFunc) RUNTIME_CONSTRAINTS(DECLARE_CONSTRAINT) diff --git a/test/hotspot/gtest/aarch64/test_spin_pause.cpp b/test/hotspot/gtest/aarch64/test_spin_pause.cpp new file mode 100644 index 00000000000..e220362eae9 --- /dev/null +++ b/test/hotspot/gtest/aarch64/test_spin_pause.cpp @@ -0,0 +1,33 @@ +/* + * 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. + */ + +#if defined(AARCH64) && !defined(ZERO) + +#include "utilities/spinYield.hpp" +#include "unittest.hpp" + +TEST_VM(SpinPause, sanity) { + ASSERT_EQ(SpinPause(), 1); +} + +#endif // AARCH64 diff --git a/test/hotspot/jtreg/gtest/TestSpinPauseAArch64.java b/test/hotspot/jtreg/gtest/TestSpinPauseAArch64.java new file mode 100644 index 00000000000..475c86d889f --- /dev/null +++ b/test/hotspot/jtreg/gtest/TestSpinPauseAArch64.java @@ -0,0 +1,46 @@ +/* + * 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=default_armv8_0 + * @bug 8362193 + * @summary Run SpinPause gtest using different instructions for SpinPause + * @library /test/lib + * @requires vm.flagless + * @requires os.arch=="aarch64" + * @run main/native GTestWrapper --gtest_filter=SpinPause* + * @run main/native GTestWrapper --gtest_filter=SpinPause* -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=none + * @run main/native GTestWrapper --gtest_filter=SpinPause* -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=nop + * @run main/native GTestWrapper --gtest_filter=SpinPause* -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=isb + * @run main/native GTestWrapper --gtest_filter=SpinPause* -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=yield + */ + +/* + * @test id=sb_armv8_5 + * @bug 8362193 + * @summary Run SpinPause gtest using SB instruction for SpinPause + * @library /test/lib + * @requires vm.flagless + * @requires (os.arch=="aarch64" & vm.cpu.features ~= ".*sb.*") + * @run main/native GTestWrapper --gtest_filter=SpinPause* -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=sb + */ From 38cd860daa9504bbe5add8c2d045d78c75fb7e38 Mon Sep 17 00:00:00 2001 From: Coleen Phillimore Date: Wed, 23 Jul 2025 14:48:49 +0000 Subject: [PATCH 57/94] 8363816: Refactor array name creation Reviewed-by: shade, ccheung, dholmes --- src/hotspot/share/oops/objArrayKlass.cpp | 40 +++++++++++++----------- src/hotspot/share/oops/objArrayKlass.hpp | 5 +++ 2 files changed, 26 insertions(+), 19 deletions(-) diff --git a/src/hotspot/share/oops/objArrayKlass.cpp b/src/hotspot/share/oops/objArrayKlass.cpp index e1fcc25f150..48d56ddc540 100644 --- a/src/hotspot/share/oops/objArrayKlass.cpp +++ b/src/hotspot/share/oops/objArrayKlass.cpp @@ -53,6 +53,26 @@ ObjArrayKlass* ObjArrayKlass::allocate_klass(ClassLoaderData* loader_data, int n return new (loader_data, size, THREAD) ObjArrayKlass(n, k, name); } +Symbol* ObjArrayKlass::create_element_klass_array_name(JavaThread* current, Klass* element_klass) { + ResourceMark rm(current); + char* name_str = element_klass->name()->as_C_string(); + int len = element_klass->name()->utf8_length(); + char* new_str = NEW_RESOURCE_ARRAY_IN_THREAD(current, char, len + 4); + int idx = 0; + new_str[idx++] = JVM_SIGNATURE_ARRAY; + if (element_klass->is_instance_klass()) { // it could be an array or simple type + new_str[idx++] = JVM_SIGNATURE_CLASS; + } + memcpy(&new_str[idx], name_str, len * sizeof(char)); + idx += len; + if (element_klass->is_instance_klass()) { + new_str[idx++] = JVM_SIGNATURE_ENDCLASS; + } + new_str[idx] = '\0'; + return SymbolTable::new_symbol(new_str); +} + + ObjArrayKlass* ObjArrayKlass::allocate_objArray_klass(ClassLoaderData* loader_data, int n, Klass* element_klass, TRAPS) { @@ -79,25 +99,7 @@ ObjArrayKlass* ObjArrayKlass::allocate_objArray_klass(ClassLoaderData* loader_da } // Create type name for klass. - Symbol* name = nullptr; - { - ResourceMark rm(THREAD); - char *name_str = element_klass->name()->as_C_string(); - int len = element_klass->name()->utf8_length(); - char *new_str = NEW_RESOURCE_ARRAY(char, len + 4); - int idx = 0; - new_str[idx++] = JVM_SIGNATURE_ARRAY; - if (element_klass->is_instance_klass()) { // it could be an array or simple type - new_str[idx++] = JVM_SIGNATURE_CLASS; - } - memcpy(&new_str[idx], name_str, len * sizeof(char)); - idx += len; - if (element_klass->is_instance_klass()) { - new_str[idx++] = JVM_SIGNATURE_ENDCLASS; - } - new_str[idx++] = '\0'; - name = SymbolTable::new_symbol(new_str); - } + Symbol* name = create_element_klass_array_name(THREAD, element_klass); // Initialize instance variables ObjArrayKlass* oak = ObjArrayKlass::allocate_klass(loader_data, n, element_klass, name, CHECK_NULL); diff --git a/src/hotspot/share/oops/objArrayKlass.hpp b/src/hotspot/share/oops/objArrayKlass.hpp index 11fe4f2a521..6db6630cff4 100644 --- a/src/hotspot/share/oops/objArrayKlass.hpp +++ b/src/hotspot/share/oops/objArrayKlass.hpp @@ -52,6 +52,11 @@ class ObjArrayKlass : public ArrayKlass { static ObjArrayKlass* allocate_klass(ClassLoaderData* loader_data, int n, Klass* k, Symbol* name, TRAPS); objArrayOop allocate_instance(int length, TRAPS); + + protected: + // Create array_name for element klass + static Symbol* create_element_klass_array_name(JavaThread* current, Klass* element_klass); + public: // For dummy objects ObjArrayKlass() {} From e6ebefaa404daa4160bdc1c5d9c954c040e2c0c2 Mon Sep 17 00:00:00 2001 From: Anthony Scarpino Date: Wed, 23 Jul 2025 15:24:38 +0000 Subject: [PATCH 58/94] 8333857: Test sun/security/ssl/SSLSessionImpl/ResumeChecksServer.java failed: Existing session was used Reviewed-by: hchao --- .../SSLSessionImpl/ResumeChecksClient.java | 220 ++++++----- .../SSLSessionImpl/ResumeChecksServer.java | 348 ++++++++---------- 2 files changed, 266 insertions(+), 302 deletions(-) diff --git a/test/jdk/sun/security/ssl/SSLSessionImpl/ResumeChecksClient.java b/test/jdk/sun/security/ssl/SSLSessionImpl/ResumeChecksClient.java index e2885e38779..851aa5af59c 100644 --- a/test/jdk/sun/security/ssl/SSLSessionImpl/ResumeChecksClient.java +++ b/test/jdk/sun/security/ssl/SSLSessionImpl/ResumeChecksClient.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2023, 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 @@ -23,7 +23,7 @@ /* * @test - * @bug 8206929 8212885 + * @bug 8206929 8212885 8333857 * @summary ensure that client only resumes a session if certain properties * of the session are compatible with the new connection * @library /javax/net/ssl/templates @@ -47,6 +47,9 @@ import java.io.*; import java.security.*; import java.net.*; import java.util.*; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; public class ResumeChecksClient extends SSLContextTemplate { enum TestMode { @@ -56,49 +59,60 @@ public class ResumeChecksClient extends SSLContextTemplate { CIPHER_SUITE, SIGNATURE_SCHEME } + static TestMode testMode; public static void main(String[] args) throws Exception { - new ResumeChecksClient(TestMode.valueOf(args[0])).run(); + testMode = TestMode.valueOf(args[0]); + new ResumeChecksClient().test(); } - private final TestMode testMode; - public ResumeChecksClient(TestMode mode) { - this.testMode = mode; - } - - private void run() throws Exception { - Server server = startServer(); - server.signal(); + private void test() throws Exception { + Server server = new Server(); SSLContext sslContext = createClientSSLContext(); - while (!server.started) { - Thread.yield(); - } - SSLSession firstSession = connect(sslContext, server.port, testMode, false); + HexFormat hex = HexFormat.of(); + long firstStartTime = System.currentTimeMillis(); + SSLSession firstSession = connect(sslContext, server.port, true); + System.err.println("firstStartTime = " + firstStartTime); + System.err.println("firstId = " + hex.formatHex(firstSession.getId())); + System.err.println("firstSession.getCreationTime() = " + + firstSession.getCreationTime()); - server.signal(); long secondStartTime = System.currentTimeMillis(); - Thread.sleep(10); - SSLSession secondSession = connect(sslContext, server.port, testMode, true); - - server.go = false; - server.signal(); + SSLSession secondSession = connect(sslContext, server.port, false); + System.err.println("secondStartTime = " + secondStartTime); + // Note: Ids will never match with TLS 1.3 due to spec + System.err.println("secondId = " + hex.formatHex(secondSession.getId())); + System.err.println("secondSession.getCreationTime() = " + + secondSession.getCreationTime()); switch (testMode) { case BASIC: // fail if session is not resumed - checkResumedSession(firstSession, secondSession); + try { + checkResumedSession(firstSession, secondSession); + } catch (Exception e) { + throw new AssertionError("secondSession did not resume: FAIL", + e); + } + System.out.println("secondSession used resumption: PASS"); break; case VERSION_2_TO_3: case VERSION_3_TO_2: case CIPHER_SUITE: case SIGNATURE_SCHEME: // fail if a new session is not created - if (secondSession.getCreationTime() <= secondStartTime) { - throw new RuntimeException("Existing session was used"); + try { + checkResumedSession(firstSession, secondSession); + System.err.println("firstSession = " + firstSession); + System.err.println("secondSession = " + secondSession); + throw new AssertionError("Second connection should not " + + "have resumed first session: FAIL"); + } catch (Exception e) { + System.out.println("secondSession didn't use resumption: PASS"); } break; default: - throw new RuntimeException("unknown mode: " + testMode); + throw new AssertionError("unknown mode: " + testMode); } } @@ -134,51 +148,29 @@ public class ResumeChecksClient extends SSLContextTemplate { } private static SSLSession connect(SSLContext sslContext, int port, - TestMode mode, boolean second) { + boolean first) { try { SSLSocket sock = (SSLSocket) sslContext.getSocketFactory().createSocket(); SSLParameters params = sock.getSSLParameters(); - switch (mode) { - case BASIC: - // do nothing to ensure resumption works - break; - case VERSION_2_TO_3: - if (second) { - params.setProtocols(new String[] {"TLSv1.3"}); - } else { - params.setProtocols(new String[] {"TLSv1.2"}); - } - break; - case VERSION_3_TO_2: - if (second) { - params.setProtocols(new String[] {"TLSv1.2"}); - } else { - params.setProtocols(new String[] {"TLSv1.3"}); - } - break; - case CIPHER_SUITE: - if (second) { - params.setCipherSuites( - new String[] {"TLS_AES_256_GCM_SHA384"}); - } else { - params.setCipherSuites( - new String[] {"TLS_AES_128_GCM_SHA256"}); - } - break; - case SIGNATURE_SCHEME: - AlgorithmConstraints constraints = - params.getAlgorithmConstraints(); - if (second) { - params.setAlgorithmConstraints(new NoSig("ecdsa")); - } else { - params.setAlgorithmConstraints(new NoSig("rsa")); - } - break; - default: - throw new RuntimeException("unknown mode: " + mode); + switch (testMode) { + case BASIC -> {} // do nothing + case VERSION_2_TO_3 -> params.setProtocols(new String[]{ + first ? "TLSv1.2" : "TLSv1.3"}); + case VERSION_3_TO_2 -> params.setProtocols(new String[]{ + first ? "TLSv1.3" : "TLSv1.2"}); + case CIPHER_SUITE -> params.setCipherSuites( + new String[]{ + first ? "TLS_AES_128_GCM_SHA256" : + "TLS_AES_256_GCM_SHA384"}); + case SIGNATURE_SCHEME -> + params.setAlgorithmConstraints(new NoSig( + first ? "rsa" : "ecdsa")); + default -> + throw new AssertionError("unknown mode: " + + testMode); } sock.setSSLParameters(params); sock.connect(new InetSocketAddress("localhost", port)); @@ -195,7 +187,7 @@ public class ResumeChecksClient extends SSLContextTemplate { return result; } catch (Exception ex) { // unexpected exception - throw new RuntimeException(ex); + throw new AssertionError(ex); } } @@ -274,65 +266,63 @@ public class ResumeChecksClient extends SSLContextTemplate { } } - private static Server startServer() { - Server server = new Server(); - new Thread(server).start(); - return server; - } + private static class Server extends SSLContextTemplate { + public int port; + private final SSLServerSocket ssock; + ExecutorService threadPool = Executors.newFixedThreadPool(1); + CountDownLatch serverLatch = new CountDownLatch(1); - private static class Server extends SSLContextTemplate implements Runnable { - - public volatile boolean go = true; - private boolean signal = false; - public volatile int port = 0; - public volatile boolean started = false; - - private synchronized void waitForSignal() { - while (!signal) { - try { - wait(); - } catch (InterruptedException ex) { - // do nothing - } - } - signal = false; - } - public synchronized void signal() { - signal = true; - notify(); - } - - @Override - public void run() { + Server() { try { - SSLContext sc = createServerSSLContext(); ServerSocketFactory fac = sc.getServerSocketFactory(); - SSLServerSocket ssock = (SSLServerSocket) - fac.createServerSocket(0); - this.port = ssock.getLocalPort(); + ssock = (SSLServerSocket) fac.createServerSocket(0); + port = ssock.getLocalPort(); - waitForSignal(); - started = true; - while (go) { + // Thread to allow multiple clients to connect + new Thread(() -> { try { - System.out.println("Waiting for connection"); - Socket sock = ssock.accept(); - BufferedReader reader = new BufferedReader( - new InputStreamReader(sock.getInputStream())); - String line = reader.readLine(); - System.out.println("server read: " + line); - PrintWriter out = new PrintWriter( - new OutputStreamWriter(sock.getOutputStream())); - out.println(line); - out.flush(); - waitForSignal(); + System.err.println("Server starting to accept"); + serverLatch.countDown(); + do { + threadPool.submit( + new ServerThread((SSLSocket) ssock.accept())); + } while (true); } catch (Exception ex) { - ex.printStackTrace(); + throw new AssertionError("Server Down", ex); + } finally { + threadPool.close(); } + }).start(); + + } catch (Exception e) { + throw new AssertionError(e); + } + } + + static class ServerThread extends Thread { + SSLSocket sock; + + ServerThread(SSLSocket s) { + this.sock = s; + System.err.println("(Server) client connection on port " + + sock.getPort()); + } + + public void run() { + try { + BufferedReader reader = new BufferedReader( + new InputStreamReader(sock.getInputStream())); + String line = reader.readLine(); + System.out.println("server read: " + line); + PrintWriter out = new PrintWriter( + new OutputStreamWriter(sock.getOutputStream())); + out.println(line); + out.flush(); + out.close(); + } catch (Exception e) { + throw new AssertionError("Server thread error", e); } - } catch (Exception ex) { - throw new RuntimeException(ex); } } } diff --git a/test/jdk/sun/security/ssl/SSLSessionImpl/ResumeChecksServer.java b/test/jdk/sun/security/ssl/SSLSessionImpl/ResumeChecksServer.java index 341dfb11d77..d1918aab7f1 100644 --- a/test/jdk/sun/security/ssl/SSLSessionImpl/ResumeChecksServer.java +++ b/test/jdk/sun/security/ssl/SSLSessionImpl/ResumeChecksServer.java @@ -23,7 +23,7 @@ /* * @test - * @bug 8206929 + * @bug 8206929 8333857 * @summary ensure that server only resumes a session if certain properties * of the session are compatible with the new connection * @modules java.base/sun.security.x509 @@ -49,6 +49,10 @@ import java.io.*; import java.security.*; import java.net.*; import java.util.*; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + import sun.security.x509.X509CertImpl; public class ResumeChecksServer extends SSLContextTemplate { @@ -63,60 +67,56 @@ public class ResumeChecksServer extends SSLContextTemplate { LOCAL_CERTS } + static CountDownLatch latch = new CountDownLatch(1); + static TestMode testMode; + static int serverPort; + public static void main(String[] args) throws Exception { - - new ResumeChecksServer(TestMode.valueOf(args[0])).run(); - } - private final TestMode testMode; - - public ResumeChecksServer(TestMode testMode) { - this.testMode = testMode; + testMode = TestMode.valueOf(args[0]); + new ResumeChecksServer().test(); } - private void run() throws Exception { - SSLSession firstSession; - SSLSession secondSession = null; + private void test() throws Exception { + SSLSession firstSession, secondSession; + HexFormat hex = HexFormat.of(); - SSLContext sslContext = createServerSSLContext(); - ServerSocketFactory fac = sslContext.getServerSocketFactory(); - SSLServerSocket ssock = (SSLServerSocket) - fac.createServerSocket(0); + serverPort = new Server().port; + latch.await(); + Client c = new Client(serverPort); - Client client = startClient(ssock.getLocalPort()); + System.out.println("Waiting for connection"); + long firstStartTime = System.currentTimeMillis(); + firstSession = c.test(); - try { - firstSession = connect(client, ssock, testMode, null); - } catch (Exception ex) { - throw new RuntimeException(ex); - } + System.err.println("firstStartTime = " + firstStartTime); + System.err.println("firstId = " + hex.formatHex(firstSession.getId())); + System.err.println("firstSession.getCreationTime() = " + + firstSession.getCreationTime()); long secondStartTime = System.currentTimeMillis(); - Thread.sleep(10); - try { - secondSession = connect(client, ssock, testMode, firstSession); - } catch (SSLHandshakeException ex) { - // this is expected - } catch (Exception ex) { - throw new RuntimeException(ex); - } + secondSession = c.test(); - client.go = false; - client.signal(); + System.err.println("secondStartTime = " + secondStartTime); + // Note: Ids will never match with TLS 1.3 due to spec + System.err.println("secondId = " + hex.formatHex(secondSession.getId())); + System.err.println("secondSession.getCreationTime() = " + + secondSession.getCreationTime()); switch (testMode) { case BASIC: // fail if session is not resumed - if (secondSession.getCreationTime() > secondStartTime) { - throw new RuntimeException("Session was not reused"); + if (firstSession.getCreationTime() != + secondSession.getCreationTime()) { + throw new AssertionError("Session was not reused: FAIL"); } // Fail if session's certificates are not restored correctly. - if (!java.util.Arrays.equals( + if (!Arrays.equals( firstSession.getLocalCertificates(), secondSession.getLocalCertificates())) { - throw new RuntimeException("Certificates do not match"); + throw new AssertionError("Certificates do not match: FAIL"); } - + System.out.println("secondSession used resumption: PASS"); break; case CLIENT_AUTH: // throws an exception if the client is not authenticated @@ -128,24 +128,23 @@ public class ResumeChecksServer extends SSLContextTemplate { case SIGNATURE_SCHEME: case LOCAL_CERTS: // fail if a new session is not created - if (secondSession.getCreationTime() <= secondStartTime) { - throw new RuntimeException("Existing session was used"); + if (secondSession.getCreationTime() < secondStartTime) { + throw new AssertionError("Existing session was used: FAIL"); } + System.out.println("secondSession not resumed: PASS"); break; default: - throw new RuntimeException("unknown mode: " + testMode); + throw new AssertionError("unknown mode: " + testMode); } } private static class NoSig implements AlgorithmConstraints { - private final String alg; NoSig(String alg) { this.alg = alg; } - private boolean test(String a) { return !a.toLowerCase().contains(alg.toLowerCase()); } @@ -153,176 +152,151 @@ public class ResumeChecksServer extends SSLContextTemplate { public boolean permits(Set primitives, Key key) { return true; } + public boolean permits(Set primitives, String algorithm, AlgorithmParameters parameters) { - return test(algorithm); } + public boolean permits(Set primitives, String algorithm, Key key, AlgorithmParameters parameters) { - return test(algorithm); } } - private static SSLSession connect(Client client, SSLServerSocket ssock, - TestMode mode, SSLSession firstSession) throws Exception { - - boolean second = firstSession != null; - - try { - client.signal(); - System.out.println("Waiting for connection"); - SSLSocket sock = (SSLSocket) ssock.accept(); - SSLParameters params = sock.getSSLParameters(); - - switch (mode) { - case BASIC: - // do nothing to ensure resumption works - break; - case CLIENT_AUTH: - if (second) { - params.setNeedClientAuth(true); - } else { - params.setNeedClientAuth(false); - } - break; - case VERSION_2_TO_3: - if (second) { - params.setProtocols(new String[] {"TLSv1.3"}); - } else { - params.setProtocols(new String[] {"TLSv1.2"}); - } - break; - case VERSION_3_TO_2: - if (second) { - params.setProtocols(new String[] {"TLSv1.2"}); - } else { - params.setProtocols(new String[] {"TLSv1.3"}); - } - break; - case CIPHER_SUITE: - if (second) { - params.setCipherSuites( - new String[] {"TLS_AES_128_GCM_SHA256"}); - } else { - params.setCipherSuites( - new String[] {"TLS_AES_256_GCM_SHA384"}); - } - break; - case SIGNATURE_SCHEME: - params.setNeedClientAuth(true); - AlgorithmConstraints constraints = - params.getAlgorithmConstraints(); - if (second) { - params.setAlgorithmConstraints( - new NoSig("ecdsa_secp384r1_sha384")); - } else { - params.setAlgorithmConstraints( - new NoSig("ecdsa_secp521r1_sha512")); - } - break; - case LOCAL_CERTS: - if (second) { - // Add first session's certificate signature - // algorithm to constraints so local certificates - // can't be restored from the session ticket. - params.setAlgorithmConstraints( - new NoSig(X509CertImpl.toImpl((X509CertImpl) - firstSession.getLocalCertificates()[0]) - .getSigAlgName())); - } - break; - default: - throw new RuntimeException("unknown mode: " + mode); - } - sock.setSSLParameters(params); - BufferedReader reader = new BufferedReader( - new InputStreamReader(sock.getInputStream())); - String line = reader.readLine(); - System.out.println("server read: " + line); - PrintWriter out = new PrintWriter( - new OutputStreamWriter(sock.getOutputStream())); - out.println(line); - out.flush(); - out.close(); - SSLSession result = sock.getSession(); - sock.close(); - return result; - } catch (SSLHandshakeException ex) { - if (!second) { - throw ex; - } - } - return null; - } - - private static Client startClient(int port) { - Client client = new Client(port); - new Thread(client).start(); - return client; - } - - private static class Client extends SSLContextTemplate implements Runnable { - - public volatile boolean go = true; - private boolean signal = false; + private static class Client extends SSLContextTemplate { private final int port; + private final SSLContext sc; + public SSLSession session; - Client(int port) { + Client(int port) throws Exception { + sc = createClientSSLContext(); this.port = port; } - private synchronized void waitForSignal() { - while (!signal) { + public SSLSession test() throws Exception { + SSLSocket sock = null; + latch.await(); + do { try { - wait(); - } catch (InterruptedException ex) { - // do nothing + sock = (SSLSocket) sc.getSocketFactory().createSocket(); + } catch (IOException e) { + // If the server never starts, test will time out. + System.err.println("client trying again to connect"); + Thread.sleep(500); } - } - signal = false; - - try { - Thread.sleep(1000); - } catch (InterruptedException ex) { - // do nothing - } + } while (sock == null); + sock.connect(new InetSocketAddress("localhost", port)); + PrintWriter out = new PrintWriter( + new OutputStreamWriter(sock.getOutputStream())); + out.println("message"); + out.flush(); + BufferedReader reader = new BufferedReader( + new InputStreamReader(sock.getInputStream())); + String inMsg = reader.readLine(); + System.out.println("Client received: " + inMsg); + out.close(); + session = sock.getSession(); + sock.close(); + return session; } - public synchronized void signal() { - signal = true; - notify(); + } + + // The server will only have two connections each tests + private static class Server extends SSLContextTemplate { + public int port; + ExecutorService threadPool = Executors.newFixedThreadPool(1); + // Stores the certs from the first connection in mode LOCAL_CERTS + static X509CertImpl localCerts; + // first connection to the server + static boolean first = true; + + Server() throws Exception { + SSLContext sc = createServerSSLContext(); + ServerSocketFactory fac = sc.getServerSocketFactory(); + SSLServerSocket ssock = (SSLServerSocket) fac.createServerSocket(0); + port = ssock.getLocalPort(); + + // Thread to allow multiple clients to connect + new Thread(() -> { + try { + System.err.println("Server starting to accept"); + latch.countDown(); + do { + threadPool.submit(new ServerThread(ssock.accept())); + } while (true); + } catch (Exception ex) { + throw new AssertionError("Server Down", ex); + } finally { + threadPool.close(); + } + }).start(); } - public void run() { - try { + static class ServerThread implements Runnable { + final SSLSocket sock; - SSLContext sc = createClientSSLContext(); + ServerThread(Socket s) { + this.sock = (SSLSocket) s; + System.err.println("(Server) client connection on port " + + sock.getPort()); + } - waitForSignal(); - while (go) { - try { - SSLSocket sock = (SSLSocket) - sc.getSocketFactory().createSocket(); - sock.connect(new InetSocketAddress("localhost", port)); - PrintWriter out = new PrintWriter( - new OutputStreamWriter(sock.getOutputStream())); - out.println("message"); - out.flush(); - BufferedReader reader = new BufferedReader( - new InputStreamReader(sock.getInputStream())); - String inMsg = reader.readLine(); - System.out.println("Client received: " + inMsg); - out.close(); - sock.close(); - waitForSignal(); - } catch (Exception ex) { - ex.printStackTrace(); + public void run() { + try { + SSLParameters params = sock.getSSLParameters(); + switch (testMode) { + case BASIC -> {} // do nothing + case CLIENT_AUTH -> params.setNeedClientAuth(!first); + case VERSION_2_TO_3 -> params.setProtocols(new String[]{ + first ? "TLSv1.2" : "TLSv1.3"}); + case VERSION_3_TO_2 -> params.setProtocols(new String[]{ + first ? "TLSv1.3" : "TLSv1.2"}); + case CIPHER_SUITE -> params.setCipherSuites( + new String[]{ + first ? "TLS_AES_256_GCM_SHA384" : + "TLS_AES_128_GCM_SHA256"}); + case SIGNATURE_SCHEME -> { + params.setNeedClientAuth(true); + params.setAlgorithmConstraints(new NoSig( + first ? "ecdsa_secp521r1_sha512" : + "ecdsa_secp384r1_sha384")); + } + case LOCAL_CERTS -> { + if (!first) { + // Add first session's certificate signature + // algorithm to constraints so local certificates + // can't be restored from the session ticket. + params.setAlgorithmConstraints( + new NoSig(X509CertImpl.toImpl(localCerts) + .getSigAlgName())); + } + } + default -> + throw new AssertionError("Server: " + + "unknown mode: " + testMode); } + sock.setSSLParameters(params); + BufferedReader reader = new BufferedReader( + new InputStreamReader(sock.getInputStream())); + String line = reader.readLine(); + System.err.println("server read: " + line); + PrintWriter out = new PrintWriter( + new OutputStreamWriter(sock.getOutputStream())); + out.println(line); + out.flush(); + out.close(); + SSLSession session = sock.getSession(); + if (testMode == TestMode.LOCAL_CERTS && first) { + localCerts = (X509CertImpl) session. + getLocalCertificates()[0]; + } + first = false; + System.err.println("server socket closed: " + session); + } catch (Exception e) { + throw new AssertionError("Server error", e); } - } catch (Exception ex) { - throw new RuntimeException(ex); } } } -} +} \ No newline at end of file From 594c080b2bde81a48ecccda85ac765218fc93856 Mon Sep 17 00:00:00 2001 From: Kevin Rushforth Date: Wed, 23 Jul 2025 15:46:47 +0000 Subject: [PATCH 59/94] 8359760: Remove the jdk.jsobject module Reviewed-by: rriggs, iris, alanb --- bin/unshuffle_list.txt | 3 +- make/conf/docs-modules.conf | 3 +- make/conf/module-loader-map.conf | 1 - .../share/classes/module-info.java | 36 ----- .../netscape/javascript/JSException.java | 63 -------- .../classes/netscape/javascript/JSObject.java | 137 ------------------ .../netscape/javascript/package-info.java | 46 ------ .../ctw/modules/jdk_jsobject.java | 38 ----- .../jdk/modules/etc/UpgradeableModules.java | 5 +- .../jdk.jsobject/JdkJsobjectCheckSince.java | 30 ---- .../doclet/testModules/jdk/element-list | 2 - .../doclet/testRecordTypes/jdk17/element-list | 2 - 12 files changed, 4 insertions(+), 362 deletions(-) delete mode 100644 src/jdk.jsobject/share/classes/module-info.java delete mode 100644 src/jdk.jsobject/share/classes/netscape/javascript/JSException.java delete mode 100644 src/jdk.jsobject/share/classes/netscape/javascript/JSObject.java delete mode 100644 src/jdk.jsobject/share/classes/netscape/javascript/package-info.java delete mode 100644 test/hotspot/jtreg/applications/ctw/modules/jdk_jsobject.java delete mode 100644 test/jdk/tools/sincechecker/modules/jdk.jsobject/JdkJsobjectCheckSince.java diff --git a/bin/unshuffle_list.txt b/bin/unshuffle_list.txt index 36ad8feabc4..a910f6b4621 100644 --- a/bin/unshuffle_list.txt +++ b/bin/unshuffle_list.txt @@ -1,5 +1,5 @@ # -# Copyright (c) 2014, 2020, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -78,7 +78,6 @@ src/jdk.jdi : jdk/src/jdk.jdi src/jdk.jdwp.agent : jdk/src/jdk.jdwp.agent src/jdk.jlink : jdk/src/jdk.jlink src/jdk.jshell : langtools/src/jdk.jshell -src/jdk.jsobject : jdk/src/jdk.jsobject src/jdk.jstatd : jdk/src/jdk.jstatd src/jdk.localedata : jdk/src/jdk.localedata src/jdk.management : jdk/src/jdk.management diff --git a/make/conf/docs-modules.conf b/make/conf/docs-modules.conf index b88f6ff6d90..f0bce0ef009 100644 --- a/make/conf/docs-modules.conf +++ b/make/conf/docs-modules.conf @@ -1,5 +1,5 @@ # -# Copyright (c) 2014, 2023, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -51,7 +51,6 @@ DOCS_MODULES= \ jdk.jdwp.agent \ jdk.jfr \ jdk.jlink \ - jdk.jsobject \ jdk.jshell \ jdk.jstatd \ jdk.localedata \ diff --git a/make/conf/module-loader-map.conf b/make/conf/module-loader-map.conf index 92bffc0e9bc..65101af2b8a 100644 --- a/make/conf/module-loader-map.conf +++ b/make/conf/module-loader-map.conf @@ -62,7 +62,6 @@ UPGRADEABLE_PLATFORM_MODULES= \ java.compiler \ jdk.graal.compiler \ jdk.graal.compiler.management \ - jdk.jsobject \ # PLATFORM_MODULES= \ diff --git a/src/jdk.jsobject/share/classes/module-info.java b/src/jdk.jsobject/share/classes/module-info.java deleted file mode 100644 index 13ec8943a21..00000000000 --- a/src/jdk.jsobject/share/classes/module-info.java +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2016, 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. 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. - */ - -/** - * Defines the API for the JavaScript Object. - * - * @moduleGraph - * @since 9 - * @deprecated The jdk.jsobject module will be delivered with JavaFX. - */ -@Deprecated(since = "24", forRemoval = true) -module jdk.jsobject { - exports netscape.javascript; -} diff --git a/src/jdk.jsobject/share/classes/netscape/javascript/JSException.java b/src/jdk.jsobject/share/classes/netscape/javascript/JSException.java deleted file mode 100644 index fe3fd7190e6..00000000000 --- a/src/jdk.jsobject/share/classes/netscape/javascript/JSException.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright (c) 2006, 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. 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 netscape.javascript; - -/** - * Thrown when an exception is raised in the JavaScript engine. This is merely - * a marker class to indicate an exception relating to the JavaScript - * interface. - * @since 1.5 - * @deprecated The jdk.jsobject module will be delivered with JavaFX. - */ -@Deprecated(since = "24", forRemoval = true) -public class JSException extends RuntimeException { - private static final long serialVersionUID = 2778103758223661489L; - - /** - * Constructs a new JavaScript exception with null as it's detail message. - */ - public JSException() { - super(); - } - - /** - * Construct a new JavaScript exception with the specified detail message. - * - * @param s The detail message - */ - public JSException(String s) { - super(s); - } - - /** - * Construct a new JavaScript exception with the specified cause. - * - * @param t Throwable cause - */ - public JSException(Throwable t) { - super(t); - } -} diff --git a/src/jdk.jsobject/share/classes/netscape/javascript/JSObject.java b/src/jdk.jsobject/share/classes/netscape/javascript/JSObject.java deleted file mode 100644 index f038728d7bd..00000000000 --- a/src/jdk.jsobject/share/classes/netscape/javascript/JSObject.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (c) 2006, 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. 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 netscape.javascript; - -/** - *

        - * Allows Java code to manipulate JavaScript objects. - *

        - * - *

        - * When a JavaScript object is passed or returned to Java code, it - * is wrapped in an instance of {@code JSObject}. When a - * {@code JSObject} instance is passed to the JavaScript engine, - * it is unwrapped back to its original JavaScript object. The - * {@code JSObject} class provides a way to invoke JavaScript - * methods and examine JavaScript properties. - *

        - * - *

        Any data returned from the JavaScript engine to Java is - * converted to Java data types. Certain data passed to the JavaScript - * engine is converted to JavaScript data types. - *

        - * @since 1.5 - * @deprecated The jdk.jsobject module will be delivered with JavaFX. - */ -@Deprecated(since = "24", forRemoval = true) -@SuppressWarnings("removal") -public abstract class JSObject { - /** - * Constructs a new JSObject. Users should neither call this method nor - * subclass JSObject. - */ - protected JSObject() { - } - - /** - * Calls a JavaScript method. Equivalent to - * "this.methodName(args[0], args[1], ...)" in JavaScript. - * - * @param methodName The name of the JavaScript method to be invoked. - * @param args the Java objects passed as arguments to the method. - * @return Result of the method. - * @throws JSException when an error is reported from the browser or - * JavaScript engine. - */ - public abstract Object call(String methodName, Object... args) throws JSException; - - /** - * Evaluates a JavaScript expression. The expression is a string of - * JavaScript source code which will be evaluated in the context given by - * "this". - * - * @param s The JavaScript expression. - * @return Result of the JavaScript evaluation. - * @throws JSException when an error is reported from the browser or - * JavaScript engine. - */ - public abstract Object eval(String s) throws JSException; - - /** - * Retrieves a named member of a JavaScript object. Equivalent to - * "this.name" in JavaScript. - * - * @param name The name of the JavaScript property to be accessed. - * @return The value of the property. - * @throws JSException when an error is reported from the browser or - * JavaScript engine. - */ - public abstract Object getMember(String name) throws JSException; - - /** - * Sets a named member of a JavaScript object. Equivalent to - * "this.name = value" in JavaScript. - * - * @param name The name of the JavaScript property to be accessed. - * @param value The value of the property. - * @throws JSException when an error is reported from the browser or - * JavaScript engine. - */ - public abstract void setMember(String name, Object value) throws JSException; - - /** - * Removes a named member of a JavaScript object. Equivalent - * to "delete this.name" in JavaScript. - * - * @param name The name of the JavaScript property to be removed. - * @throws JSException when an error is reported from the browser or - * JavaScript engine. - */ - public abstract void removeMember(String name) throws JSException; - - /** - * Retrieves an indexed member of a JavaScript object. Equivalent to - * "this[index]" in JavaScript. - * - * @param index The index of the array to be accessed. - * @return The value of the indexed member. - * @throws JSException when an error is reported from the browser or - * JavaScript engine. - */ - public abstract Object getSlot(int index) throws JSException; - - /** - * Sets an indexed member of a JavaScript object. Equivalent to - * "this[index] = value" in JavaScript. - * - * @param index The index of the array to be accessed. - * @param value The value to set - * @throws JSException when an error is reported from the browser or - * JavaScript engine. - */ - public abstract void setSlot(int index, Object value) throws JSException; - -} diff --git a/src/jdk.jsobject/share/classes/netscape/javascript/package-info.java b/src/jdk.jsobject/share/classes/netscape/javascript/package-info.java deleted file mode 100644 index f7aae37db50..00000000000 --- a/src/jdk.jsobject/share/classes/netscape/javascript/package-info.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright (c) 2008, 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. 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. - */ - -/** - *

        - * Deprecated, for removal: This API element is subject to removal - * in a future version.
        - * The jdk.jsobject module will be delivered with JavaFX. - *

        - * - *

        - * Provides Java code the ability to access the JavaScript engine and the - * HTML DOM in the web browser. - *

        - * - *

        - * The classes in this package were initially specified by Netscape, and are the - * de facto standard mechanism for calling JavaScript from the Java runtime. - *

        - * - * @since 1.5 - */ - -package netscape.javascript; diff --git a/test/hotspot/jtreg/applications/ctw/modules/jdk_jsobject.java b/test/hotspot/jtreg/applications/ctw/modules/jdk_jsobject.java deleted file mode 100644 index e1d2105919f..00000000000 --- a/test/hotspot/jtreg/applications/ctw/modules/jdk_jsobject.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2017, 2022, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -/* - * @test - * @summary run CTW for all classes from jdk.jsobject module - * - * @library /test/lib / /testlibrary/ctw/src - * @modules java.base/jdk.internal.access - * java.base/jdk.internal.jimage - * java.base/jdk.internal.misc - * java.base/jdk.internal.reflect - * @modules jdk.jsobject - * - * @build jdk.test.whitebox.WhiteBox - * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox - * @run driver/timeout=7200 sun.hotspot.tools.ctw.CtwRunner modules:jdk.jsobject - */ diff --git a/test/jdk/jdk/modules/etc/UpgradeableModules.java b/test/jdk/jdk/modules/etc/UpgradeableModules.java index 8c5fbeafb28..14c84dbf6a5 100644 --- a/test/jdk/jdk/modules/etc/UpgradeableModules.java +++ b/test/jdk/jdk/modules/etc/UpgradeableModules.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -45,8 +45,7 @@ public class UpgradeableModules { private static final List UPGRADEABLE_MODULES = List.of("java.compiler", "jdk.graal.compiler", - "jdk.graal.compiler.management", - "jdk.jsobject"); + "jdk.graal.compiler.management"); public static void main(String... args) { diff --git a/test/jdk/tools/sincechecker/modules/jdk.jsobject/JdkJsobjectCheckSince.java b/test/jdk/tools/sincechecker/modules/jdk.jsobject/JdkJsobjectCheckSince.java deleted file mode 100644 index 705c7259b71..00000000000 --- a/test/jdk/tools/sincechecker/modules/jdk.jsobject/JdkJsobjectCheckSince.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) 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 8343780 - * @summary Test for `@since` in jdk.jsobject module - * @library /test/lib /test/jdk/tools/sincechecker - * @run main SinceChecker jdk.jsobject - */ diff --git a/test/langtools/jdk/javadoc/doclet/testModules/jdk/element-list b/test/langtools/jdk/javadoc/doclet/testModules/jdk/element-list index e3c40a9e121..b41712137d0 100644 --- a/test/langtools/jdk/javadoc/doclet/testModules/jdk/element-list +++ b/test/langtools/jdk/javadoc/doclet/testModules/jdk/element-list @@ -302,8 +302,6 @@ jdk.jshell jdk.jshell.execution jdk.jshell.spi jdk.jshell.tool -module:jdk.jsobject -netscape.javascript module:jdk.jstatd module:jdk.localedata module:jdk.management diff --git a/test/langtools/jdk/javadoc/doclet/testRecordTypes/jdk17/element-list b/test/langtools/jdk/javadoc/doclet/testRecordTypes/jdk17/element-list index 34e3d598edd..bfebb983f2f 100644 --- a/test/langtools/jdk/javadoc/doclet/testRecordTypes/jdk17/element-list +++ b/test/langtools/jdk/javadoc/doclet/testRecordTypes/jdk17/element-list @@ -251,8 +251,6 @@ jdk.jshell jdk.jshell.execution jdk.jshell.spi jdk.jshell.tool -module:jdk.jsobject -netscape.javascript module:jdk.jstatd module:jdk.localedata module:jdk.management From 03e9ea169b7e45ae3c2ac23b5fe73d39ae57506f Mon Sep 17 00:00:00 2001 From: Edoardo Patti Date: Wed, 23 Jul 2025 16:31:14 +0000 Subject: [PATCH 60/94] 8358530: Properties#list should warn against non-String values Reviewed-by: jlu, liach --- .../share/classes/java/util/Properties.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/java.base/share/classes/java/util/Properties.java b/src/java.base/share/classes/java/util/Properties.java index 015cdbc7107..6e02c3f5a23 100644 --- a/src/java.base/share/classes/java/util/Properties.java +++ b/src/java.base/share/classes/java/util/Properties.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1995, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1995, 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 @@ -70,10 +70,10 @@ import jdk.internal.util.xml.PropertiesDefaultHandler; * {@code Properties} object. Their use is strongly discouraged as they * allow the caller to insert entries whose keys or values are not * {@code Strings}. The {@code setProperty} method should be used - * instead. If the {@code store} or {@code save} method is called + * instead. If the {@code store}, {@code save}, or {@code list} method is called * on a "compromised" {@code Properties} object that contains a * non-{@code String} key or value, the call will fail. Similarly, - * the call to the {@code propertyNames} or {@code list} method + * the call to the {@code propertyNames} method * will fail if it is called on a "compromised" {@code Properties} * object that contains a non-{@code String} key. * @@ -1215,8 +1215,8 @@ public class Properties extends Hashtable { * This method is useful for debugging. * * @param out an output stream. - * @throws ClassCastException if any key in this property list - * is not a string. + * @throws ClassCastException if either a key or a value + * in this property list is not a string. */ public void list(PrintStream out) { out.println("-- listing properties --"); @@ -1237,8 +1237,8 @@ public class Properties extends Hashtable { * This method is useful for debugging. * * @param out an output stream. - * @throws ClassCastException if any key in this property list - * is not a string. + * @throws ClassCastException if either a key or a value + * in this property list is not a string. * @since 1.1 */ /* From 2292246f8c11f735f50e2046ec6606e89289e9f5 Mon Sep 17 00:00:00 2001 From: Thomas Schatzl Date: Wed, 23 Jul 2025 17:02:31 +0000 Subject: [PATCH 61/94] 8350621: Code cache stops scheduling GC Co-authored-by: Thomas Schatzl Co-authored-by: Alexandre Jacob Reviewed-by: kbarrett, ayang --- src/hotspot/share/gc/g1/g1CollectedHeap.cpp | 149 +++++++++---- src/hotspot/share/gc/g1/g1CollectedHeap.hpp | 8 + src/hotspot/share/gc/g1/g1CollectorState.hpp | 6 + src/hotspot/share/gc/g1/g1Policy.cpp | 19 +- src/hotspot/share/gc/g1/g1VMOperations.cpp | 9 +- src/hotspot/share/gc/g1/g1VMOperations.hpp | 2 + src/hotspot/share/gc/shared/gcCause.hpp | 5 + .../TestCodeCacheUnloadDuringConcCycle.java | 202 ++++++++++++++++++ 8 files changed, 348 insertions(+), 52 deletions(-) create mode 100644 test/hotspot/jtreg/gc/g1/TestCodeCacheUnloadDuringConcCycle.java diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp index 040f258e6a8..cb4baf078ee 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp @@ -1732,6 +1732,66 @@ static bool gc_counter_less_than(uint x, uint y) { #define LOG_COLLECT_CONCURRENTLY_COMPLETE(cause, result) \ LOG_COLLECT_CONCURRENTLY(cause, "complete %s", BOOL_TO_STR(result)) +bool G1CollectedHeap::wait_full_mark_finished(GCCause::Cause cause, + uint old_marking_started_before, + uint old_marking_started_after, + uint old_marking_completed_after) { + // Request is finished if a full collection (concurrent or stw) + // was started after this request and has completed, e.g. + // started_before < completed_after. + if (gc_counter_less_than(old_marking_started_before, + old_marking_completed_after)) { + LOG_COLLECT_CONCURRENTLY_COMPLETE(cause, true); + return true; + } + + if (old_marking_started_after != old_marking_completed_after) { + // If there is an in-progress cycle (possibly started by us), then + // wait for that cycle to complete, e.g. + // while completed_now < started_after. + LOG_COLLECT_CONCURRENTLY(cause, "wait"); + MonitorLocker ml(G1OldGCCount_lock); + while (gc_counter_less_than(_old_marking_cycles_completed, + old_marking_started_after)) { + ml.wait(); + } + // Request is finished if the collection we just waited for was + // started after this request. + if (old_marking_started_before != old_marking_started_after) { + LOG_COLLECT_CONCURRENTLY(cause, "complete after wait"); + return true; + } + } + return false; +} + +// After calling wait_full_mark_finished(), this method determines whether we +// previously failed for ordinary reasons (concurrent cycle in progress, whitebox +// has control). Returns if this has been such an ordinary reason. +static bool should_retry_vm_op(GCCause::Cause cause, + VM_G1TryInitiateConcMark* op) { + if (op->cycle_already_in_progress()) { + // If VMOp failed because a cycle was already in progress, it + // is now complete. But it didn't finish this user-requested + // GC, so try again. + LOG_COLLECT_CONCURRENTLY(cause, "retry after in-progress"); + return true; + } else if (op->whitebox_attached()) { + // If WhiteBox wants control, wait for notification of a state + // change in the controller, then try again. Don't wait for + // release of control, since collections may complete while in + // control. Note: This won't recognize a STW full collection + // while waiting; we can't wait on multiple monitors. + LOG_COLLECT_CONCURRENTLY(cause, "whitebox control stall"); + MonitorLocker ml(ConcurrentGCBreakpoints::monitor()); + if (ConcurrentGCBreakpoints::is_controlled()) { + ml.wait(); + } + return true; + } + return false; +} + bool G1CollectedHeap::try_collect_concurrently(GCCause::Cause cause, uint gc_counter, uint old_marking_started_before) { @@ -1792,7 +1852,45 @@ bool G1CollectedHeap::try_collect_concurrently(GCCause::Cause cause, LOG_COLLECT_CONCURRENTLY(cause, "ignoring STW full GC"); old_marking_started_before = old_marking_started_after; } + } else if (GCCause::is_codecache_requested_gc(cause)) { + // For a CodeCache requested GC, before marking, progress is ensured as the + // following Remark pause unloads code (and signals the requester such). + // Otherwise we must ensure that it is restarted. + // + // For a CodeCache requested GC, a successful GC operation means that + // (1) marking is in progress. I.e. the VMOp started the marking or a + // Remark pause is pending from a different VM op; we will potentially + // abort a mixed phase if needed. + // (2) a new cycle was started (by this thread or some other), or + // (3) a Full GC was performed. + // + // Cases (2) and (3) are detected together by a change to + // _old_marking_cycles_started. + // + // Compared to other "automatic" GCs (see below), we do not consider being + // in whitebox as sufficient too because we might be anywhere within that + // cycle and we need to make progress. + if (op.mark_in_progress() || + (old_marking_started_before != old_marking_started_after)) { + LOG_COLLECT_CONCURRENTLY_COMPLETE(cause, true); + return true; + } + + if (wait_full_mark_finished(cause, + old_marking_started_before, + old_marking_started_after, + old_marking_completed_after)) { + return true; + } + + if (should_retry_vm_op(cause, &op)) { + continue; + } } else if (!GCCause::is_user_requested_gc(cause)) { + assert(cause == GCCause::_g1_humongous_allocation || + cause == GCCause::_g1_periodic_collection, + "Unsupported cause %s", GCCause::to_string(cause)); + // For an "automatic" (not user-requested) collection, we just need to // ensure that progress is made. // @@ -1804,11 +1902,6 @@ bool G1CollectedHeap::try_collect_concurrently(GCCause::Cause cause, // (5) a Full GC was performed. // Cases (4) and (5) are detected together by a change to // _old_marking_cycles_started. - // - // Note that (1) does not imply (4). If we're still in the mixed - // phase of an earlier concurrent collection, the request to make the - // collection a concurrent start won't be honored. If we don't check for - // both conditions we'll spin doing back-to-back collections. if (op.gc_succeeded() || op.cycle_already_in_progress() || op.whitebox_attached() || @@ -1832,56 +1925,20 @@ bool G1CollectedHeap::try_collect_concurrently(GCCause::Cause cause, BOOL_TO_STR(op.gc_succeeded()), old_marking_started_before, old_marking_started_after); - // Request is finished if a full collection (concurrent or stw) - // was started after this request and has completed, e.g. - // started_before < completed_after. - if (gc_counter_less_than(old_marking_started_before, - old_marking_completed_after)) { - LOG_COLLECT_CONCURRENTLY_COMPLETE(cause, true); + if (wait_full_mark_finished(cause, + old_marking_started_before, + old_marking_started_after, + old_marking_completed_after)) { return true; } - if (old_marking_started_after != old_marking_completed_after) { - // If there is an in-progress cycle (possibly started by us), then - // wait for that cycle to complete, e.g. - // while completed_now < started_after. - LOG_COLLECT_CONCURRENTLY(cause, "wait"); - MonitorLocker ml(G1OldGCCount_lock); - while (gc_counter_less_than(_old_marking_cycles_completed, - old_marking_started_after)) { - ml.wait(); - } - // Request is finished if the collection we just waited for was - // started after this request. - if (old_marking_started_before != old_marking_started_after) { - LOG_COLLECT_CONCURRENTLY(cause, "complete after wait"); - return true; - } - } - // If VMOp was successful then it started a new cycle that the above // wait &etc should have recognized as finishing this request. This // differs from a non-user-request, where gc_succeeded does not imply // a new cycle was started. assert(!op.gc_succeeded(), "invariant"); - if (op.cycle_already_in_progress()) { - // If VMOp failed because a cycle was already in progress, it - // is now complete. But it didn't finish this user-requested - // GC, so try again. - LOG_COLLECT_CONCURRENTLY(cause, "retry after in-progress"); - continue; - } else if (op.whitebox_attached()) { - // If WhiteBox wants control, wait for notification of a state - // change in the controller, then try again. Don't wait for - // release of control, since collections may complete while in - // control. Note: This won't recognize a STW full collection - // while waiting; we can't wait on multiple monitors. - LOG_COLLECT_CONCURRENTLY(cause, "whitebox control stall"); - MonitorLocker ml(ConcurrentGCBreakpoints::monitor()); - if (ConcurrentGCBreakpoints::is_controlled()) { - ml.wait(); - } + if (should_retry_vm_op(cause, &op)) { continue; } } diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.hpp b/src/hotspot/share/gc/g1/g1CollectedHeap.hpp index 1cbd3dfd525..90e0ea8608a 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.hpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.hpp @@ -274,6 +274,14 @@ private: // (e) cause == _g1_periodic_collection and +G1PeriodicGCInvokesConcurrent. bool should_do_concurrent_full_gc(GCCause::Cause cause); + // Wait until a full mark (either currently in progress or one that completed + // after the current request) has finished. Returns whether that full mark started + // after this request. If so, we typically do not need another one. + bool wait_full_mark_finished(GCCause::Cause cause, + uint old_marking_started_before, + uint old_marking_started_after, + uint old_marking_completed_after); + // Attempt to start a concurrent cycle with the indicated cause. // precondition: should_do_concurrent_full_gc(cause) bool try_collect_concurrently(GCCause::Cause cause, diff --git a/src/hotspot/share/gc/g1/g1CollectorState.hpp b/src/hotspot/share/gc/g1/g1CollectorState.hpp index 8f01227206b..fca30792344 100644 --- a/src/hotspot/share/gc/g1/g1CollectorState.hpp +++ b/src/hotspot/share/gc/g1/g1CollectorState.hpp @@ -60,6 +60,9 @@ class G1CollectorState { // do the concurrent start phase work. volatile bool _initiate_conc_mark_if_possible; + // Marking is in progress. Set from start of the concurrent start pause to the + // end of the Remark pause. + bool _mark_in_progress; // Marking or rebuilding remembered set work is in progress. Set from the end // of the concurrent start pause to the end of the Cleanup pause. bool _mark_or_rebuild_in_progress; @@ -78,6 +81,7 @@ public: _in_concurrent_start_gc(false), _initiate_conc_mark_if_possible(false), + _mark_in_progress(false), _mark_or_rebuild_in_progress(false), _clear_bitmap_in_progress(false), _in_full_gc(false) { } @@ -92,6 +96,7 @@ public: void set_initiate_conc_mark_if_possible(bool v) { _initiate_conc_mark_if_possible = v; } + void set_mark_in_progress(bool v) { _mark_in_progress = v; } void set_mark_or_rebuild_in_progress(bool v) { _mark_or_rebuild_in_progress = v; } void set_clear_bitmap_in_progress(bool v) { _clear_bitmap_in_progress = v; } @@ -106,6 +111,7 @@ public: bool initiate_conc_mark_if_possible() const { return _initiate_conc_mark_if_possible; } + bool mark_in_progress() const { return _mark_in_progress; } bool mark_or_rebuild_in_progress() const { return _mark_or_rebuild_in_progress; } bool clear_bitmap_in_progress() const { return _clear_bitmap_in_progress; } diff --git a/src/hotspot/share/gc/g1/g1Policy.cpp b/src/hotspot/share/gc/g1/g1Policy.cpp index 2a752fc4a25..49fab954799 100644 --- a/src/hotspot/share/gc/g1/g1Policy.cpp +++ b/src/hotspot/share/gc/g1/g1Policy.cpp @@ -591,6 +591,7 @@ void G1Policy::record_full_collection_end() { collector_state()->set_in_young_gc_before_mixed(false); collector_state()->set_initiate_conc_mark_if_possible(need_to_start_conc_mark("end of Full GC")); collector_state()->set_in_concurrent_start_gc(false); + collector_state()->set_mark_in_progress(false); collector_state()->set_mark_or_rebuild_in_progress(false); collector_state()->set_clear_bitmap_in_progress(false); @@ -703,6 +704,7 @@ void G1Policy::record_concurrent_mark_remark_end() { double elapsed_time_ms = (end_time_sec - start_time_sec) * 1000.0; _analytics->report_concurrent_mark_remark_times_ms(elapsed_time_ms); record_pause(G1GCPauseType::Remark, start_time_sec, end_time_sec); + collector_state()->set_mark_in_progress(false); } G1CollectionSetCandidates* G1Policy::candidates() const { @@ -936,6 +938,7 @@ void G1Policy::record_young_collection_end(bool concurrent_operation_is_full_mar assert(!(G1GCPauseTypeHelper::is_concurrent_start_pause(this_pause) && collector_state()->mark_or_rebuild_in_progress()), "If the last pause has been concurrent start, we should not have been in the marking window"); if (G1GCPauseTypeHelper::is_concurrent_start_pause(this_pause)) { + collector_state()->set_mark_in_progress(concurrent_operation_is_full_mark); collector_state()->set_mark_or_rebuild_in_progress(concurrent_operation_is_full_mark); } @@ -1222,6 +1225,17 @@ void G1Policy::initiate_conc_mark() { collector_state()->set_initiate_conc_mark_if_possible(false); } +static const char* requester_for_mixed_abort(GCCause::Cause cause) { + if (cause == GCCause::_wb_breakpoint) { + return "run_to breakpoint"; + } else if (GCCause::is_codecache_requested_gc(cause)) { + return "codecache"; + } else { + assert(G1CollectedHeap::heap()->is_user_requested_concurrent_full_gc(cause), "must be"); + return "user"; + } +} + void G1Policy::decide_on_concurrent_start_pause() { // We are about to decide on whether this pause will be a // concurrent start pause. @@ -1254,8 +1268,7 @@ void G1Policy::decide_on_concurrent_start_pause() { initiate_conc_mark(); log_debug(gc, ergo)("Initiate concurrent cycle (concurrent cycle initiation requested)"); } else if (_g1h->is_user_requested_concurrent_full_gc(cause) || - (cause == GCCause::_codecache_GC_threshold) || - (cause == GCCause::_codecache_GC_aggressive) || + GCCause::is_codecache_requested_gc(cause) || (cause == GCCause::_wb_breakpoint)) { // Initiate a concurrent start. A concurrent start must be a young only // GC, so the collector state must be updated to reflect this. @@ -1270,7 +1283,7 @@ void G1Policy::decide_on_concurrent_start_pause() { abort_time_to_mixed_tracking(); initiate_conc_mark(); log_debug(gc, ergo)("Initiate concurrent cycle (%s requested concurrent cycle)", - (cause == GCCause::_wb_breakpoint) ? "run_to breakpoint" : "user"); + requester_for_mixed_abort(cause)); } else { // The concurrent marking thread is still finishing up the // previous cycle. If we start one right now the two cycles diff --git a/src/hotspot/share/gc/g1/g1VMOperations.cpp b/src/hotspot/share/gc/g1/g1VMOperations.cpp index 87fa751691e..6ddeba3d2e2 100644 --- a/src/hotspot/share/gc/g1/g1VMOperations.cpp +++ b/src/hotspot/share/gc/g1/g1VMOperations.cpp @@ -59,6 +59,7 @@ VM_G1TryInitiateConcMark::VM_G1TryInitiateConcMark(uint gc_count_before, GCCause::Cause gc_cause) : VM_GC_Collect_Operation(gc_count_before, gc_cause), _transient_failure(false), + _mark_in_progress(false), _cycle_already_in_progress(false), _whitebox_attached(false), _terminating(false), @@ -83,6 +84,9 @@ void VM_G1TryInitiateConcMark::doit() { // Record for handling by caller. _terminating = g1h->concurrent_mark_is_terminating(); + _mark_in_progress = g1h->collector_state()->mark_in_progress(); + _cycle_already_in_progress = g1h->concurrent_mark()->cm_thread()->in_progress(); + if (_terminating && GCCause::is_user_requested_gc(_gc_cause)) { // When terminating, the request to initiate a concurrent cycle will be // ignored by do_collection_pause_at_safepoint; instead it will just do @@ -91,9 +95,8 @@ void VM_G1TryInitiateConcMark::doit() { // requests the alternative GC might still be needed. } else if (!g1h->policy()->force_concurrent_start_if_outside_cycle(_gc_cause)) { // Failure to force the next GC pause to be a concurrent start indicates - // there is already a concurrent marking cycle in progress. Set flag - // to notify the caller and return immediately. - _cycle_already_in_progress = true; + // there is already a concurrent marking cycle in progress. Flags to indicate + // that were already set, so return immediately. } else if ((_gc_cause != GCCause::_wb_breakpoint) && ConcurrentGCBreakpoints::is_controlled()) { // WhiteBox wants to be in control of concurrent cycles, so don't try to diff --git a/src/hotspot/share/gc/g1/g1VMOperations.hpp b/src/hotspot/share/gc/g1/g1VMOperations.hpp index f2ac4c6f638..05b27c4508c 100644 --- a/src/hotspot/share/gc/g1/g1VMOperations.hpp +++ b/src/hotspot/share/gc/g1/g1VMOperations.hpp @@ -45,6 +45,7 @@ public: class VM_G1TryInitiateConcMark : public VM_GC_Collect_Operation { bool _transient_failure; + bool _mark_in_progress; bool _cycle_already_in_progress; bool _whitebox_attached; bool _terminating; @@ -59,6 +60,7 @@ public: virtual bool doit_prologue(); virtual void doit(); bool transient_failure() const { return _transient_failure; } + bool mark_in_progress() const { return _mark_in_progress; } bool cycle_already_in_progress() const { return _cycle_already_in_progress; } bool whitebox_attached() const { return _whitebox_attached; } bool terminating() const { return _terminating; } diff --git a/src/hotspot/share/gc/shared/gcCause.hpp b/src/hotspot/share/gc/shared/gcCause.hpp index ec7c664bbc1..56070c88143 100644 --- a/src/hotspot/share/gc/shared/gcCause.hpp +++ b/src/hotspot/share/gc/shared/gcCause.hpp @@ -104,6 +104,11 @@ class GCCause : public AllStatic { cause == GCCause::_heap_dump); } + inline static bool is_codecache_requested_gc(GCCause::Cause cause) { + return (cause == _codecache_GC_threshold || + cause == _codecache_GC_aggressive); + } + // Causes for collection of the tenured generation inline static bool is_tenured_allocation_failure_gc(GCCause::Cause cause) { // _allocation_failure is the generic cause a collection which could result diff --git a/test/hotspot/jtreg/gc/g1/TestCodeCacheUnloadDuringConcCycle.java b/test/hotspot/jtreg/gc/g1/TestCodeCacheUnloadDuringConcCycle.java new file mode 100644 index 00000000000..94f65a4328f --- /dev/null +++ b/test/hotspot/jtreg/gc/g1/TestCodeCacheUnloadDuringConcCycle.java @@ -0,0 +1,202 @@ +/* + * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package gc.g1; + +/* + * @test TestCodeCacheUnloadDuringConcCycle + * @bug 8350621 + * @summary Test to make sure that code cache unloading does not hang when receiving + * a request to unload code cache during concurrent mark. + * We do that by triggering a code cache gc request (by triggering compilations) + * during concurrent mark, and verify that after the concurrent cycle additional code + * cache gc requests start more concurrent cycles. + * @requires vm.gc.G1 + * @library /test/lib /testlibrary / + * @modules java.base/jdk.internal.misc + * java.management + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xmx20M -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. gc.g1.TestCodeCacheUnloadDuringConcCycle + */ + +import java.lang.management.ManagementFactory; +import java.lang.management.MemoryPoolMXBean; +import java.lang.management.MemoryUsage; +import java.lang.reflect.Field; + +import java.net.URL; +import java.net.URLClassLoader; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import jdk.test.lib.Asserts; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; +import static jdk.test.lib.Asserts.*; +import jdk.test.whitebox.WhiteBox; + +public class TestCodeCacheUnloadDuringConcCycle { + public static final String AFTER_FIRST_CYCLE_MARKER = "Marker for this test"; + + private static final WhiteBox WB = WhiteBox.getWhiteBox(); + + private static OutputAnalyzer runTest(String concPhase) throws Exception { + OutputAnalyzer output = ProcessTools.executeLimitedTestJava("-XX:+UseG1GC", + "-Xmx20M", + "-XX:+UnlockDiagnosticVMOptions", + "-Xbootclasspath/a:.", + "-Xlog:gc=trace,codecache", + "-XX:+WhiteBoxAPI", + "-XX:ReservedCodeCacheSize=8M", + "-XX:StartAggressiveSweepingAt=50", + "-XX:CompileCommand=compileonly,gc.g1.SomeClass::*", + TestCodeCacheUnloadDuringConcCycleRunner.class.getName(), + concPhase); + return output; + } + + private static void runAndCheckTest(String test) throws Exception { + OutputAnalyzer output; + + output = runTest(test); + output.shouldHaveExitValue(0); + System.out.println(output.getStdout()); + + String[] parts = output.getStdout().split(AFTER_FIRST_CYCLE_MARKER); + + // Either "Threshold" or "Aggressive" CodeCache GC are fine for the test. + final String codecacheGCStart = "Pause Young (Concurrent Start) (CodeCache GC "; + + boolean success = parts.length == 2 && parts[1].indexOf(codecacheGCStart) != -1; + Asserts.assertTrue(success, "Could not find a CodeCache GC Threshold GC after finishing the concurrent cycle"); + } + + private static void allTests() throws Exception { + runAndCheckTest(WB.BEFORE_MARKING_COMPLETED); + runAndCheckTest(WB.G1_BEFORE_REBUILD_COMPLETED); + runAndCheckTest(WB.G1_BEFORE_CLEANUP_COMPLETED); + } + + public static void main(String[] args) throws Exception { + allTests(); + } +} + +class TestCodeCacheUnloadDuringConcCycleRunner { + private static final WhiteBox WB = WhiteBox.getWhiteBox(); + + private static void refClass(Class clazz) throws Exception { + Field name = clazz.getDeclaredField("NAME"); + name.setAccessible(true); + name.get(null); + } + + private static class MyClassLoader extends URLClassLoader { + public MyClassLoader(URL url) { + super(new URL[]{url}, null); + } + protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + try { + return super.loadClass(name, resolve); + } catch (ClassNotFoundException e) { + return Class.forName(name, resolve, TestCodeCacheUnloadDuringConcCycleRunner.class.getClassLoader()); + } + } + } + + private static void triggerCodeCacheGC() throws Exception { + URL url = TestCodeCacheUnloadDuringConcCycleRunner.class.getProtectionDomain().getCodeSource().getLocation(); + + try { + int i = 0; + do { + ClassLoader cl = new MyClassLoader(url); + refClass(cl.loadClass("gc.g1.SomeClass")); + + if (i % 20 == 0) { + System.out.println("Compiled " + i + " classes"); + } + i++; + } while (i < 200); + System.out.println("Compilation done, compiled " + i + " classes"); + } catch (Throwable t) { + } + } + + public static void main(String[] args) throws Exception { + try { + WB.concurrentGCAcquireControl(); + WB.concurrentGCRunTo(args[0]); + + System.out.println("Try to trigger code cache GC"); + + triggerCodeCacheGC(); + + WB.concurrentGCRunToIdle(); + } finally { + WB.concurrentGCReleaseControl(); + } + System.out.println(TestCodeCacheUnloadDuringConcCycle.AFTER_FIRST_CYCLE_MARKER); + Thread.sleep(1000); + triggerCodeCacheGC(); + } +} + +abstract class Foo { + public abstract int foo(); +} + +class Foo1 extends Foo { + private int a; + public int foo() { return a; } +} + +class Foo2 extends Foo { + private int a; + public int foo() { return a; } +} + +class Foo3 extends Foo { + private int a; + public int foo() { return a; } +} + +class Foo4 extends Foo { + private int a; + public int foo() { return a; } +} + +class SomeClass { + static final String NAME = "name"; + + static { + int res =0; + Foo[] foos = new Foo[] { new Foo1(), new Foo2(), new Foo3(), new Foo4() }; + for (int i = 0; i < 100000; i++) { + res = foos[i % foos.length].foo(); + } + } +} From ad510fb25e47098d136515c355164e5177c5b419 Mon Sep 17 00:00:00 2001 From: Albert Mingkun Yang Date: Wed, 23 Jul 2025 20:09:36 +0000 Subject: [PATCH 62/94] 8338977: Parallel: Improve heap resizing heuristics Reviewed-by: zgu, gli, iwalulya --- .../gc/parallel/gcAdaptivePolicyCounters.cpp | 226 ---- .../gc/parallel/gcAdaptivePolicyCounters.hpp | 228 ---- .../share/gc/parallel/parallelArguments.cpp | 5 + .../gc/parallel/parallelScavengeHeap.cpp | 271 +++-- .../gc/parallel/parallelScavengeHeap.hpp | 76 +- .../gc/parallel/psAdaptiveSizePolicy.cpp | 1048 +++-------------- .../gc/parallel/psAdaptiveSizePolicy.hpp | 205 +--- .../parallel/psGCAdaptivePolicyCounters.cpp | 179 --- .../parallel/psGCAdaptivePolicyCounters.hpp | 185 --- src/hotspot/share/gc/parallel/psOldGen.cpp | 29 +- src/hotspot/share/gc/parallel/psOldGen.hpp | 28 +- .../share/gc/parallel/psParallelCompact.cpp | 102 +- .../share/gc/parallel/psParallelCompact.hpp | 9 +- .../share/gc/parallel/psPromotionManager.cpp | 3 +- .../share/gc/parallel/psPromotionManager.hpp | 1 + .../gc/parallel/psPromotionManager.inline.hpp | 3 + src/hotspot/share/gc/parallel/psScavenge.cpp | 146 +-- .../share/gc/parallel/psVirtualspace.cpp | 3 + src/hotspot/share/gc/parallel/psYoungGen.cpp | 682 ++++------- src/hotspot/share/gc/parallel/psYoungGen.hpp | 46 +- .../share/gc/shared/adaptiveSizePolicy.cpp | 435 +------ .../share/gc/shared/adaptiveSizePolicy.hpp | 481 +++----- .../share/gc/shared/gcOverheadChecker.cpp | 104 -- .../share/gc/shared/gcOverheadChecker.hpp | 84 -- .../share/gc/shared/gcPolicyCounters.cpp | 5 - .../share/gc/shared/gcPolicyCounters.hpp | 5 - src/hotspot/share/gc/shared/gc_globals.hpp | 58 - src/hotspot/share/runtime/arguments.cpp | 17 + .../sun/jvmstat/perfdata/resources/aliasmap | 93 -- .../gc/parallel/test_psAdaptiveSizePolicy.cpp | 60 - .../jtreg/gc/parallel/TestDynShrinkHeap.java | 2 +- 31 files changed, 928 insertions(+), 3891 deletions(-) delete mode 100644 src/hotspot/share/gc/parallel/gcAdaptivePolicyCounters.cpp delete mode 100644 src/hotspot/share/gc/parallel/gcAdaptivePolicyCounters.hpp delete mode 100644 src/hotspot/share/gc/parallel/psGCAdaptivePolicyCounters.cpp delete mode 100644 src/hotspot/share/gc/parallel/psGCAdaptivePolicyCounters.hpp delete mode 100644 src/hotspot/share/gc/shared/gcOverheadChecker.cpp delete mode 100644 src/hotspot/share/gc/shared/gcOverheadChecker.hpp delete mode 100644 test/hotspot/gtest/gc/parallel/test_psAdaptiveSizePolicy.cpp diff --git a/src/hotspot/share/gc/parallel/gcAdaptivePolicyCounters.cpp b/src/hotspot/share/gc/parallel/gcAdaptivePolicyCounters.cpp deleted file mode 100644 index 47c09befb40..00000000000 --- a/src/hotspot/share/gc/parallel/gcAdaptivePolicyCounters.cpp +++ /dev/null @@ -1,226 +0,0 @@ -/* - * Copyright (c) 2004, 2025, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - * - */ - -#include "gc/parallel/gcAdaptivePolicyCounters.hpp" -#include "memory/resourceArea.hpp" - -// This class keeps statistical information and computes the -// size of the heap. - -GCAdaptivePolicyCounters::GCAdaptivePolicyCounters(const char* name, - int collectors, - int generations, - AdaptiveSizePolicy* size_policy_arg) - : GCPolicyCounters(name, collectors, generations), - _size_policy(size_policy_arg) { - if (UsePerfData) { - EXCEPTION_MARK; - ResourceMark rm; - - const char* cname = PerfDataManager::counter_name(name_space(), "edenSize"); - _eden_size_counter = PerfDataManager::create_variable(SUN_GC, cname, - PerfData::U_Bytes, _size_policy->calculated_eden_size_in_bytes(), CHECK); - - cname = PerfDataManager::counter_name(name_space(), "promoSize"); - _promo_size_counter = PerfDataManager::create_variable(SUN_GC, cname, - PerfData::U_Bytes, size_policy()->calculated_promo_size_in_bytes(), - CHECK); - - cname = PerfDataManager::counter_name(name_space(), "youngCapacity"); - size_t young_capacity_in_bytes = - _size_policy->calculated_eden_size_in_bytes() + - _size_policy->calculated_survivor_size_in_bytes(); - _young_capacity_counter = PerfDataManager::create_variable(SUN_GC, cname, - PerfData::U_Bytes, young_capacity_in_bytes, CHECK); - - cname = PerfDataManager::counter_name(name_space(), "avgSurvivedAvg"); - _avg_survived_avg_counter = PerfDataManager::create_variable(SUN_GC, cname, - PerfData::U_Bytes, size_policy()->calculated_survivor_size_in_bytes(), - CHECK); - - cname = PerfDataManager::counter_name(name_space(), "avgSurvivedDev"); - _avg_survived_dev_counter = PerfDataManager::create_variable(SUN_GC, cname, - PerfData::U_Bytes, (jlong) 0 , CHECK); - - cname = PerfDataManager::counter_name(name_space(), "avgSurvivedPaddedAvg"); - _avg_survived_padded_avg_counter = - PerfDataManager::create_variable(SUN_GC, cname, PerfData::U_Bytes, - size_policy()->calculated_survivor_size_in_bytes(), CHECK); - - cname = PerfDataManager::counter_name(name_space(), "avgMinorPauseTime"); - _avg_minor_pause_counter = PerfDataManager::create_variable(SUN_GC, cname, - PerfData::U_Ticks, (jlong) _size_policy->_avg_minor_pause->average(), - CHECK); - - cname = PerfDataManager::counter_name(name_space(), "avgMinorIntervalTime"); - _avg_minor_interval_counter = PerfDataManager::create_variable(SUN_GC, - cname, - PerfData::U_Ticks, - (jlong) _size_policy->_avg_minor_interval->average(), - CHECK); - -#ifdef NOT_PRODUCT - // This is a counter for the most recent minor pause time - // (the last sample, not the average). It is useful for - // verifying the average pause time but not worth putting - // into the product. - cname = PerfDataManager::counter_name(name_space(), "minorPauseTime"); - _minor_pause_counter = PerfDataManager::create_variable(SUN_GC, cname, - PerfData::U_Ticks, (jlong) _size_policy->_avg_minor_pause->last_sample(), - CHECK); -#endif - - cname = PerfDataManager::counter_name(name_space(), "minorGcCost"); - _minor_gc_cost_counter = PerfDataManager::create_variable(SUN_GC, - cname, - PerfData::U_Ticks, - (jlong) _size_policy->minor_gc_cost(), - CHECK); - - cname = PerfDataManager::counter_name(name_space(), "mutatorCost"); - _mutator_cost_counter = PerfDataManager::create_variable(SUN_GC, cname, - PerfData::U_Ticks, (jlong) _size_policy->mutator_cost(), CHECK); - - cname = PerfDataManager::counter_name(name_space(), "survived"); - _survived_counter = PerfDataManager::create_variable(SUN_GC, cname, - PerfData::U_Bytes, (jlong) 0, CHECK); - - cname = PerfDataManager::counter_name(name_space(), "promoted"); - _promoted_counter = PerfDataManager::create_variable(SUN_GC, cname, - PerfData::U_Bytes, (jlong) 0, CHECK); - - cname = PerfDataManager::counter_name(name_space(), "avgYoungLive"); - _avg_young_live_counter = PerfDataManager::create_variable(SUN_GC, cname, - PerfData::U_Bytes, (jlong) size_policy()->avg_young_live()->average(), - CHECK); - - cname = PerfDataManager::counter_name(name_space(), "avgOldLive"); - _avg_old_live_counter = PerfDataManager::create_variable(SUN_GC, cname, - PerfData::U_Bytes, (jlong) size_policy()->avg_old_live()->average(), - CHECK); - - cname = PerfDataManager::counter_name(name_space(), "survivorOverflowed"); - _survivor_overflowed_counter = PerfDataManager::create_variable(SUN_GC, cname, - PerfData::U_Events, (jlong)0, CHECK); - - cname = PerfDataManager::counter_name(name_space(), - "decrementTenuringThresholdForGcCost"); - _decrement_tenuring_threshold_for_gc_cost_counter = - PerfDataManager::create_variable(SUN_GC, cname, PerfData::U_Events, - (jlong)0, CHECK); - - cname = PerfDataManager::counter_name(name_space(), - "incrementTenuringThresholdForGcCost"); - _increment_tenuring_threshold_for_gc_cost_counter = - PerfDataManager::create_variable(SUN_GC, cname, PerfData::U_Events, - (jlong)0, CHECK); - - cname = PerfDataManager::counter_name(name_space(), - "decrementTenuringThresholdForSurvivorLimit"); - _decrement_tenuring_threshold_for_survivor_limit_counter = - PerfDataManager::create_variable(SUN_GC, cname, PerfData::U_Events, - (jlong)0, CHECK); - cname = PerfDataManager::counter_name(name_space(), - "changeYoungGenForMinPauses"); - _change_young_gen_for_min_pauses_counter = - PerfDataManager::create_variable(SUN_GC, cname, PerfData::U_Events, - (jlong)0, CHECK); - - cname = PerfDataManager::counter_name(name_space(), - "changeOldGenForMajPauses"); - _change_old_gen_for_maj_pauses_counter = - PerfDataManager::create_variable(SUN_GC, cname, PerfData::U_Events, - (jlong)0, CHECK); - - cname = PerfDataManager::counter_name(name_space(), - "increaseOldGenForThroughput"); - _change_old_gen_for_throughput_counter = - PerfDataManager::create_variable(SUN_GC, cname, PerfData::U_Events, - (jlong)0, CHECK); - - cname = PerfDataManager::counter_name(name_space(), - "increaseYoungGenForThroughput"); - _change_young_gen_for_throughput_counter = - PerfDataManager::create_variable(SUN_GC, cname, PerfData::U_Events, - (jlong)0, CHECK); - - cname = PerfDataManager::counter_name(name_space(), - "decreaseForFootprint"); - _decrease_for_footprint_counter = - PerfDataManager::create_variable(SUN_GC, cname, - PerfData::U_Events, (jlong)0, CHECK); - - cname = PerfDataManager::counter_name(name_space(), "decideAtFullGc"); - _decide_at_full_gc_counter = PerfDataManager::create_variable(SUN_GC, cname, - PerfData::U_None, (jlong)0, CHECK); - - cname = PerfDataManager::counter_name(name_space(), "minorPauseYoungSlope"); - _minor_pause_young_slope_counter = - PerfDataManager::create_variable(SUN_GC, cname, - PerfData::U_None, (jlong) 0, CHECK); - - cname = PerfDataManager::counter_name(name_space(), "majorCollectionSlope"); - _major_collection_slope_counter = - PerfDataManager::create_variable(SUN_GC, cname, - PerfData::U_None, (jlong) 0, CHECK); - - cname = PerfDataManager::counter_name(name_space(), "minorCollectionSlope"); - _minor_collection_slope_counter = - PerfDataManager::create_variable(SUN_GC, cname, - PerfData::U_None, (jlong) 0, CHECK); - } -} - -void GCAdaptivePolicyCounters::update_counters_from_policy() { - if (UsePerfData && (size_policy() != nullptr)) { - update_avg_minor_pause_counter(); - update_avg_minor_interval_counter(); -#ifdef NOT_PRODUCT - update_minor_pause_counter(); -#endif - update_minor_gc_cost_counter(); - update_avg_young_live_counter(); - - update_survivor_size_counters(); - update_avg_survived_avg_counters(); - update_avg_survived_dev_counters(); - update_avg_survived_padded_avg_counters(); - - update_change_old_gen_for_throughput(); - update_change_young_gen_for_throughput(); - update_decrease_for_footprint(); - update_change_young_gen_for_min_pauses(); - update_change_old_gen_for_maj_pauses(); - - update_minor_pause_young_slope_counter(); - update_minor_collection_slope_counter(); - update_major_collection_slope_counter(); - } -} - -void GCAdaptivePolicyCounters::update_counters() { - if (UsePerfData) { - update_counters_from_policy(); - } -} diff --git a/src/hotspot/share/gc/parallel/gcAdaptivePolicyCounters.hpp b/src/hotspot/share/gc/parallel/gcAdaptivePolicyCounters.hpp deleted file mode 100644 index eb792788376..00000000000 --- a/src/hotspot/share/gc/parallel/gcAdaptivePolicyCounters.hpp +++ /dev/null @@ -1,228 +0,0 @@ -/* - * Copyright (c) 2004, 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. - * - */ - -#ifndef SHARE_GC_PARALLEL_GCADAPTIVEPOLICYCOUNTERS_HPP -#define SHARE_GC_PARALLEL_GCADAPTIVEPOLICYCOUNTERS_HPP - -#include "gc/shared/adaptiveSizePolicy.hpp" -#include "gc/shared/gcPolicyCounters.hpp" -#include "utilities/macros.hpp" - -// This class keeps statistical information and computes the -// size of the heap. - -class GCAdaptivePolicyCounters : public GCPolicyCounters { - protected: - PerfVariable* _eden_size_counter; - PerfVariable* _promo_size_counter; - - PerfVariable* _young_capacity_counter; - - PerfVariable* _minor_gc_cost_counter; - PerfVariable* _major_gc_cost_counter; - PerfVariable* _mutator_cost_counter; - - PerfVariable* _avg_young_live_counter; - PerfVariable* _avg_old_live_counter; - - PerfVariable* _avg_minor_pause_counter; - PerfVariable* _avg_minor_interval_counter; - -#ifdef NOT_PRODUCT - PerfVariable* _minor_pause_counter; -#endif - - PerfVariable* _change_young_gen_for_min_pauses_counter; - PerfVariable* _change_young_gen_for_throughput_counter; - PerfVariable* _change_old_gen_for_maj_pauses_counter; - PerfVariable* _change_old_gen_for_throughput_counter; - PerfVariable* _decrease_for_footprint_counter; - - PerfVariable* _minor_pause_young_slope_counter; - - PerfVariable* _decide_at_full_gc_counter; - - PerfVariable* _survived_counter; - PerfVariable* _promoted_counter; - - PerfVariable* _avg_survived_avg_counter; - PerfVariable* _avg_survived_dev_counter; - PerfVariable* _avg_survived_padded_avg_counter; - - PerfVariable* _survivor_overflowed_counter; - PerfVariable* _increment_tenuring_threshold_for_gc_cost_counter; - PerfVariable* _decrement_tenuring_threshold_for_gc_cost_counter; - PerfVariable* _decrement_tenuring_threshold_for_survivor_limit_counter; - - PerfVariable* _minor_collection_slope_counter; - PerfVariable* _major_collection_slope_counter; - - AdaptiveSizePolicy* _size_policy; - - inline void update_eden_size() { - size_t eden_size_in_bytes = size_policy()->calculated_eden_size_in_bytes(); - _eden_size_counter->set_value(eden_size_in_bytes); - } - - inline void update_promo_size() { - _promo_size_counter->set_value( - size_policy()->calculated_promo_size_in_bytes()); - } - - inline void update_avg_minor_pause_counter() { - _avg_minor_pause_counter->set_value((jlong) - (size_policy()->avg_minor_pause()->average() * 1000.0)); - } - inline void update_avg_minor_interval_counter() { - _avg_minor_interval_counter->set_value((jlong) - (size_policy()->avg_minor_interval()->average() * 1000.0)); - } - -#ifdef NOT_PRODUCT - inline void update_minor_pause_counter() { - _minor_pause_counter->set_value((jlong) - (size_policy()->avg_minor_pause()->last_sample() * 1000.0)); - } -#endif - inline void update_minor_gc_cost_counter() { - _minor_gc_cost_counter->set_value((jlong) - (size_policy()->minor_gc_cost() * 100.0)); - } - - inline void update_avg_young_live_counter() { - _avg_young_live_counter->set_value( - (jlong)(size_policy()->avg_young_live()->average()) - ); - } - - inline void update_avg_survived_avg_counters() { - _avg_survived_avg_counter->set_value( - (jlong)(size_policy()->_avg_survived->average()) - ); - } - inline void update_avg_survived_dev_counters() { - _avg_survived_dev_counter->set_value( - (jlong)(size_policy()->_avg_survived->deviation()) - ); - } - inline void update_avg_survived_padded_avg_counters() { - _avg_survived_padded_avg_counter->set_value( - (jlong)(size_policy()->_avg_survived->padded_average()) - ); - } - - inline void update_change_old_gen_for_throughput() { - _change_old_gen_for_throughput_counter->set_value( - size_policy()->change_old_gen_for_throughput()); - } - inline void update_change_young_gen_for_throughput() { - _change_young_gen_for_throughput_counter->set_value( - size_policy()->change_young_gen_for_throughput()); - } - inline void update_decrease_for_footprint() { - _decrease_for_footprint_counter->set_value( - size_policy()->decrease_for_footprint()); - } - - inline void update_decide_at_full_gc_counter() { - _decide_at_full_gc_counter->set_value( - size_policy()->decide_at_full_gc()); - } - - inline void update_minor_pause_young_slope_counter() { - _minor_pause_young_slope_counter->set_value( - (jlong)(size_policy()->minor_pause_young_slope() * 1000) - ); - } - - virtual void update_counters_from_policy(); - - protected: - virtual AdaptiveSizePolicy* size_policy() { return _size_policy; } - - public: - GCAdaptivePolicyCounters(const char* name, - int collectors, - int generations, - AdaptiveSizePolicy* size_policy); - - inline void update_survived(size_t survived) { - _survived_counter->set_value(survived); - } - inline void update_promoted(size_t promoted) { - _promoted_counter->set_value(promoted); - } - inline void update_young_capacity(size_t size_in_bytes) { - _young_capacity_counter->set_value(size_in_bytes); - } - - virtual void update_counters(); - - inline void update_survivor_size_counters() { - desired_survivor_size()->set_value( - size_policy()->calculated_survivor_size_in_bytes()); - } - inline void update_survivor_overflowed(bool survivor_overflowed) { - _survivor_overflowed_counter->set_value(survivor_overflowed); - } - inline void update_tenuring_threshold(uint threshold) { - tenuring_threshold()->set_value(threshold); - } - inline void update_increment_tenuring_threshold_for_gc_cost() { - _increment_tenuring_threshold_for_gc_cost_counter->set_value( - size_policy()->increment_tenuring_threshold_for_gc_cost()); - } - inline void update_decrement_tenuring_threshold_for_gc_cost() { - _decrement_tenuring_threshold_for_gc_cost_counter->set_value( - size_policy()->decrement_tenuring_threshold_for_gc_cost()); - } - inline void update_decrement_tenuring_threshold_for_survivor_limit() { - _decrement_tenuring_threshold_for_survivor_limit_counter->set_value( - size_policy()->decrement_tenuring_threshold_for_survivor_limit()); - } - inline void update_change_young_gen_for_min_pauses() { - _change_young_gen_for_min_pauses_counter->set_value( - size_policy()->change_young_gen_for_min_pauses()); - } - inline void update_change_old_gen_for_maj_pauses() { - _change_old_gen_for_maj_pauses_counter->set_value( - size_policy()->change_old_gen_for_maj_pauses()); - } - - inline void update_minor_collection_slope_counter() { - _minor_collection_slope_counter->set_value( - (jlong)(size_policy()->minor_collection_slope() * 1000) - ); - } - - inline void update_major_collection_slope_counter() { - _major_collection_slope_counter->set_value( - (jlong)(size_policy()->major_collection_slope() * 1000) - ); - } - - void set_size_policy(AdaptiveSizePolicy* v) { _size_policy = v; } -}; - -#endif // SHARE_GC_PARALLEL_GCADAPTIVEPOLICYCOUNTERS_HPP diff --git a/src/hotspot/share/gc/parallel/parallelArguments.cpp b/src/hotspot/share/gc/parallel/parallelArguments.cpp index dcd5b6ae454..780185952b4 100644 --- a/src/hotspot/share/gc/parallel/parallelArguments.cpp +++ b/src/hotspot/share/gc/parallel/parallelArguments.cpp @@ -66,6 +66,11 @@ void ParallelArguments::initialize() { } } + // True in product build, since tests using debug build often stress GC + if (FLAG_IS_DEFAULT(UseGCOverheadLimit)) { + FLAG_SET_DEFAULT(UseGCOverheadLimit, trueInProduct); + } + if (InitialSurvivorRatio < MinSurvivorRatio) { if (FLAG_IS_CMDLINE(InitialSurvivorRatio)) { if (FLAG_IS_CMDLINE(MinSurvivorRatio)) { diff --git a/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp b/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp index 139a5fa52f1..2359ab9e158 100644 --- a/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp +++ b/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp @@ -48,6 +48,7 @@ #include "memory/universe.hpp" #include "oops/oop.inline.hpp" #include "runtime/cpuTimeCounters.hpp" +#include "runtime/globals_extension.hpp" #include "runtime/handles.inline.hpp" #include "runtime/java.hpp" #include "runtime/vmThread.hpp" @@ -58,7 +59,7 @@ PSYoungGen* ParallelScavengeHeap::_young_gen = nullptr; PSOldGen* ParallelScavengeHeap::_old_gen = nullptr; PSAdaptiveSizePolicy* ParallelScavengeHeap::_size_policy = nullptr; -PSGCAdaptivePolicyCounters* ParallelScavengeHeap::_gc_policy_counters = nullptr; +GCPolicyCounters* ParallelScavengeHeap::_gc_policy_counters = nullptr; jint ParallelScavengeHeap::initialize() { const size_t reserved_heap_size = ParallelArguments::heap_reserved_size_bytes(); @@ -100,24 +101,15 @@ jint ParallelScavengeHeap::initialize() { double max_gc_pause_sec = ((double) MaxGCPauseMillis)/1000.0; - const size_t eden_capacity = _young_gen->eden_space()->capacity_in_bytes(); - const size_t old_capacity = _old_gen->capacity_in_bytes(); - const size_t initial_promo_size = MIN2(eden_capacity, old_capacity); - _size_policy = - new PSAdaptiveSizePolicy(eden_capacity, - initial_promo_size, - young_gen()->to_space()->capacity_in_bytes(), - SpaceAlignment, - max_gc_pause_sec, - GCTimeRatio - ); + _size_policy = new PSAdaptiveSizePolicy(SpaceAlignment, + max_gc_pause_sec, + GCTimeRatio); assert((old_gen()->virtual_space()->high_boundary() == young_gen()->virtual_space()->low_boundary()), "Boundaries must meet"); // initialize the policy counters - 2 collectors, 2 generations - _gc_policy_counters = - new PSGCAdaptivePolicyCounters("ParScav:MSC", 2, 2, _size_policy); + _gc_policy_counters = new GCPolicyCounters("ParScav:MSC", 2, 2); if (!PSParallelCompact::initialize_aux_data()) { return JNI_ENOMEM; @@ -190,6 +182,21 @@ void ParallelScavengeHeap::post_initialize() { GCLocker::initialize(); } +void ParallelScavengeHeap::gc_epilogue(bool full) { + if (_is_heap_almost_full) { + // Reset emergency state if eden is empty after a young/full gc + if (_young_gen->eden_space()->is_empty()) { + log_debug(gc)("Leaving memory constrained state; back to normal"); + _is_heap_almost_full = false; + } + } else { + if (full && !_young_gen->eden_space()->is_empty()) { + log_debug(gc)("Non-empty young-gen after full-gc; in memory constrained state"); + _is_heap_almost_full = true; + } + } +} + void ParallelScavengeHeap::update_counters() { young_gen()->update_counters(); old_gen()->update_counters(); @@ -272,18 +279,17 @@ HeapWord* ParallelScavengeHeap::mem_allocate(size_t size, HeapWord* ParallelScavengeHeap::mem_allocate_work(size_t size, bool is_tlab, bool* gc_overhead_limit_was_exceeded) { - - // In general gc_overhead_limit_was_exceeded should be false so - // set it so here and reset it to true only if the gc time - // limit is being exceeded as checked below. - *gc_overhead_limit_was_exceeded = false; - - HeapWord* result = young_gen()->allocate(size); + { + HeapWord* result = young_gen()->allocate(size); + if (result != nullptr) { + return result; + } + } uint loop_count = 0; uint gc_count = 0; - while (result == nullptr) { + while (true) { // We don't want to have multiple collections for a single filled generation. // To prevent this, each thread tracks the total_collections() value, and if // the count has changed, does not do a new collection. @@ -299,21 +305,20 @@ HeapWord* ParallelScavengeHeap::mem_allocate_work(size_t size, MutexLocker ml(Heap_lock); gc_count = total_collections(); - result = young_gen()->allocate(size); + HeapWord* result = young_gen()->allocate(size); if (result != nullptr) { return result; } // If certain conditions hold, try allocating from the old gen. - if (!is_tlab) { - result = mem_allocate_old_gen(size); + if (!is_tlab && !should_alloc_in_eden(size)) { + result = old_gen()->cas_allocate_noexpand(size); if (result != nullptr) { return result; } } } - assert(result == nullptr, "inv"); { VM_ParallelCollectForAllocation op(size, is_tlab, gc_count); VMThread::execute(&op); @@ -324,76 +329,61 @@ HeapWord* ParallelScavengeHeap::mem_allocate_work(size_t size, if (op.gc_succeeded()) { assert(is_in_or_null(op.result()), "result not in heap"); - // Exit the loop if the gc time limit has been exceeded. - // The allocation must have failed above ("result" guarding - // this path is null) and the most recent collection has exceeded the - // gc overhead limit (although enough may have been collected to - // satisfy the allocation). Exit the loop so that an out-of-memory - // will be thrown (return a null ignoring the contents of - // op.result()), - // but clear gc_overhead_limit_exceeded so that the next collection - // starts with a clean slate (i.e., forgets about previous overhead - // excesses). Fill op.result() with a filler object so that the - // heap remains parsable. - const bool limit_exceeded = size_policy()->gc_overhead_limit_exceeded(); - const bool softrefs_clear = soft_ref_policy()->all_soft_refs_clear(); - - if (limit_exceeded && softrefs_clear) { - *gc_overhead_limit_was_exceeded = true; - size_policy()->set_gc_overhead_limit_exceeded(false); - log_trace(gc)("ParallelScavengeHeap::mem_allocate: return null because gc_overhead_limit_exceeded is set"); - if (op.result() != nullptr) { - CollectedHeap::fill_with_object(op.result(), size); - } - return nullptr; - } - return op.result(); } + // Was the gc-overhead reached inside the safepoint? If so, this mutator should return null as well for global consistency. + if (_gc_overhead_counter >= GCOverheadLimitThreshold) { + return nullptr; + } } - // The policy object will prevent us from looping forever. If the - // time spent in gc crosses a threshold, we will bail out. loop_count++; - if ((result == nullptr) && (QueuedAllocationWarningCount > 0) && + if ((QueuedAllocationWarningCount > 0) && (loop_count % QueuedAllocationWarningCount == 0)) { log_warning(gc)("ParallelScavengeHeap::mem_allocate retries %d times", loop_count); log_warning(gc)("\tsize=%zu", size); } } - - return result; -} - -HeapWord* ParallelScavengeHeap::allocate_old_gen_and_record(size_t size) { - assert_locked_or_safepoint(Heap_lock); - HeapWord* res = old_gen()->allocate(size); - if (res != nullptr) { - _size_policy->tenured_allocation(size * HeapWordSize); - } - return res; -} - -HeapWord* ParallelScavengeHeap::mem_allocate_old_gen(size_t size) { - if (!should_alloc_in_eden(size)) { - // Size is too big for eden. - return allocate_old_gen_and_record(size); - } - - return nullptr; } void ParallelScavengeHeap::do_full_collection(bool clear_all_soft_refs) { PSParallelCompact::invoke(clear_all_soft_refs); } -HeapWord* ParallelScavengeHeap::expand_heap_and_allocate(size_t size, bool is_tlab) { - HeapWord* result = nullptr; +static bool check_gc_heap_free_limit(size_t free_bytes, size_t capacity_bytes) { + return (free_bytes * 100 / capacity_bytes) < GCHeapFreeLimit; +} + +bool ParallelScavengeHeap::check_gc_overhead_limit() { + assert(SafepointSynchronize::is_at_safepoint(), "precondition"); + + if (UseGCOverheadLimit) { + // The goal here is to return null prematurely so that apps can exit + // gracefully when GC takes the most time. + bool little_mutator_time = _size_policy->mutator_time_percent() * 100 < (100 - GCTimeLimit); + bool little_free_space = check_gc_heap_free_limit(_young_gen->free_in_bytes(), _young_gen->capacity_in_bytes()) + && check_gc_heap_free_limit( _old_gen->free_in_bytes(), _old_gen->capacity_in_bytes()); + if (little_mutator_time && little_free_space) { + _gc_overhead_counter++; + if (_gc_overhead_counter >= GCOverheadLimitThreshold) { + return true; + } + } else { + _gc_overhead_counter = 0; + } + } + return false; +} + +HeapWord* ParallelScavengeHeap::expand_heap_and_allocate(size_t size, bool is_tlab) { + assert(SafepointSynchronize::is_at_safepoint(), "precondition"); + // We just finished a young/full gc, try everything to satisfy this allocation request. + HeapWord* result = young_gen()->expand_and_allocate(size); - result = young_gen()->allocate(size); if (result == nullptr && !is_tlab) { result = old_gen()->expand_and_allocate(size); } + return result; // Could be null if we are out of space. } @@ -402,13 +392,19 @@ HeapWord* ParallelScavengeHeap::satisfy_failed_allocation(size_t size, bool is_t HeapWord* result = nullptr; - // If young-gen can handle this allocation, attempt young-gc firstly. - bool should_run_young_gc = is_tlab || should_alloc_in_eden(size); - collect_at_safepoint(!should_run_young_gc); + if (!_is_heap_almost_full) { + // If young-gen can handle this allocation, attempt young-gc firstly, as young-gc is usually cheaper. + bool should_run_young_gc = is_tlab || should_alloc_in_eden(size); - result = expand_heap_and_allocate(size, is_tlab); - if (result != nullptr) { - return result; + collect_at_safepoint(!should_run_young_gc); + + // If gc-overhead is reached, we will skip allocation. + if (!check_gc_overhead_limit()) { + result = expand_heap_and_allocate(size, is_tlab); + if (result != nullptr) { + return result; + } + } } // If we reach this point, we're really out of memory. Try every trick @@ -428,18 +424,15 @@ HeapWord* ParallelScavengeHeap::satisfy_failed_allocation(size_t size, bool is_t HeapMaximumCompactionInterval = old_interval; } - result = expand_heap_and_allocate(size, is_tlab); - if (result != nullptr) { - return result; + if (check_gc_overhead_limit()) { + log_info(gc)("GCOverheadLimitThreshold %zu reached.", GCOverheadLimitThreshold); + return nullptr; } - // What else? We might try synchronous finalization later. If the total - // space available is large enough for the allocation, then a more - // complete compaction phase than we've tried so far might be - // appropriate. - return nullptr; -} + result = expand_heap_and_allocate(size, is_tlab); + return result; +} void ParallelScavengeHeap::ensure_parsability(bool retire_tlabs) { CollectedHeap::ensure_parsability(retire_tlabs); @@ -666,7 +659,6 @@ void ParallelScavengeHeap::gc_threads_do(ThreadClosure* tc) const { } void ParallelScavengeHeap::print_tracing_info() const { - AdaptiveSizePolicyOutput::print(); log_debug(gc, heap, exit)("Accumulated young generation GC time %3.7f secs", PSScavenge::accumulated_time()->seconds()); log_debug(gc, heap, exit)("Accumulated old generation GC time %3.7f secs", PSParallelCompact::accumulated_time()->seconds()); } @@ -763,15 +755,96 @@ PSCardTable* ParallelScavengeHeap::card_table() { return static_cast(barrier_set()->card_table()); } -void ParallelScavengeHeap::resize_young_gen(size_t eden_size, - size_t survivor_size) { - // Delegate the resize to the generation. - _young_gen->resize(eden_size, survivor_size); +static size_t calculate_free_from_free_ratio_flag(size_t live, uintx free_percent) { + assert(free_percent != 100, "precondition"); + // We want to calculate how much free memory there can be based on the + // live size. + // percent * (free + live) = free + // => + // free = (live * percent) / (1 - percent) + + const double percent = free_percent / 100.0; + return live * percent / (1.0 - percent); } -void ParallelScavengeHeap::resize_old_gen(size_t desired_free_space) { - // Delegate the resize to the generation. - _old_gen->resize(desired_free_space); +size_t ParallelScavengeHeap::calculate_desired_old_gen_capacity(size_t old_gen_live_size) { + // If min free percent is 100%, the old-gen should always be in its max capacity + if (MinHeapFreeRatio == 100) { + return _old_gen->max_gen_size(); + } + + // Using recorded data to calculate the new capacity of old-gen to avoid + // excessive expansion but also keep footprint low + + size_t promoted_estimate = _size_policy->padded_average_promoted_in_bytes(); + // Should have at least this free room for the next young-gc promotion. + size_t free_size = promoted_estimate; + + size_t largest_live_size = MAX2((size_t)_size_policy->peak_old_gen_used_estimate(), old_gen_live_size); + free_size += largest_live_size - old_gen_live_size; + + // Respect free percent + if (MinHeapFreeRatio != 0) { + size_t min_free = calculate_free_from_free_ratio_flag(old_gen_live_size, MinHeapFreeRatio); + free_size = MAX2(free_size, min_free); + } + + if (MaxHeapFreeRatio != 100) { + size_t max_free = calculate_free_from_free_ratio_flag(old_gen_live_size, MaxHeapFreeRatio); + free_size = MIN2(max_free, free_size); + } + + return old_gen_live_size + free_size; +} + +void ParallelScavengeHeap::resize_old_gen_after_full_gc() { + size_t current_capacity = _old_gen->capacity_in_bytes(); + size_t desired_capacity = calculate_desired_old_gen_capacity(old_gen()->used_in_bytes()); + + // If MinHeapFreeRatio is at its default value; shrink cautiously. Otherwise, users expect prompt shrinking. + if (FLAG_IS_DEFAULT(MinHeapFreeRatio)) { + if (desired_capacity < current_capacity) { + // Shrinking + if (total_full_collections() < AdaptiveSizePolicyReadyThreshold) { + // No enough data for shrinking + return; + } + } + } + + _old_gen->resize(desired_capacity); +} + +void ParallelScavengeHeap::resize_after_young_gc(bool is_survivor_overflowing) { + _young_gen->resize_after_young_gc(is_survivor_overflowing); + + // Consider if should shrink old-gen + if (!is_survivor_overflowing) { + // Upper bound for a single step shrink + size_t max_shrink_bytes = SpaceAlignment; + size_t shrink_bytes = _size_policy->compute_old_gen_shrink_bytes(old_gen()->free_in_bytes(), max_shrink_bytes); + if (shrink_bytes != 0) { + if (MinHeapFreeRatio != 0) { + size_t new_capacity = old_gen()->capacity_in_bytes() - shrink_bytes; + size_t new_free_size = old_gen()->free_in_bytes() - shrink_bytes; + if ((double)new_free_size / new_capacity * 100 < MinHeapFreeRatio) { + // Would violate MinHeapFreeRatio + return; + } + } + old_gen()->shrink(shrink_bytes); + } + } +} + +void ParallelScavengeHeap::resize_after_full_gc() { + resize_old_gen_after_full_gc(); + // We don't resize young-gen after full-gc because: + // 1. eden-size directly affects young-gc frequency (GCTimeRatio), and we + // don't have enough info to determine its desired size. + // 2. eden can contain live objs after a full-gc, which is unsafe for + // resizing. We will perform expansion on allocation if needed, in + // satisfy_failed_allocation(). } HeapWord* ParallelScavengeHeap::allocate_loaded_archive_space(size_t size) { diff --git a/src/hotspot/share/gc/parallel/parallelScavengeHeap.hpp b/src/hotspot/share/gc/parallel/parallelScavengeHeap.hpp index 68900c8dbf2..b0e804edb70 100644 --- a/src/hotspot/share/gc/parallel/parallelScavengeHeap.hpp +++ b/src/hotspot/share/gc/parallel/parallelScavengeHeap.hpp @@ -25,7 +25,7 @@ #ifndef SHARE_GC_PARALLEL_PARALLELSCAVENGEHEAP_HPP #define SHARE_GC_PARALLEL_PARALLELSCAVENGEHEAP_HPP -#include "gc/parallel/psGCAdaptivePolicyCounters.hpp" +#include "gc/parallel/psAdaptiveSizePolicy.hpp" #include "gc/parallel/psOldGen.hpp" #include "gc/parallel/psYoungGen.hpp" #include "gc/shared/cardTableBarrierSet.hpp" @@ -60,11 +60,11 @@ class ReservedSpace; // +-- generation boundary (fixed after startup) // | // |<- old gen (reserved) ->|<- young gen (reserved) ->| -// +---------------+--------+-----------------+--------+--------+--------+ -// | old | | eden | from | to | | -// | | | | (to) | (from) | | -// +---------------+--------+-----------------+--------+--------+--------+ -// |<- committed ->| |<- committed ->| +// +---------------+--------+--------+--------+------------------+-------+ +// | old | | from | to | eden | | +// | | | (to) | (from) | | | +// +---------------+--------+--------+--------+------------------+-------+ +// |<- committed ->| |<- committed ->| // class ParallelScavengeHeap : public CollectedHeap { friend class VMStructs; @@ -74,7 +74,7 @@ class ParallelScavengeHeap : public CollectedHeap { // Sizing policy for entire heap static PSAdaptiveSizePolicy* _size_policy; - static PSGCAdaptivePolicyCounters* _gc_policy_counters; + static GCPolicyCounters* _gc_policy_counters; GCMemoryManager* _young_manager; GCMemoryManager* _old_manager; @@ -85,14 +85,15 @@ class ParallelScavengeHeap : public CollectedHeap { WorkerThreads _workers; + uint _gc_overhead_counter; + + bool _is_heap_almost_full; + void initialize_serviceability() override; void trace_actual_reserved_page_size(const size_t reserved_heap_size, const ReservedSpace rs); void trace_heap(GCWhen::Type when, const GCTracer* tracer) override; - // Allocate in oldgen and record the allocation with the size_policy. - HeapWord* allocate_old_gen_and_record(size_t word_size); - void update_parallel_worker_threads_cpu_time(); bool must_clear_all_soft_refs(); @@ -101,8 +102,6 @@ class ParallelScavengeHeap : public CollectedHeap { inline bool should_alloc_in_eden(size_t size) const; - HeapWord* mem_allocate_old_gen(size_t size); - HeapWord* mem_allocate_work(size_t size, bool is_tlab, bool* gc_overhead_limit_was_exceeded); @@ -111,6 +110,12 @@ class ParallelScavengeHeap : public CollectedHeap { void do_full_collection(bool clear_all_soft_refs) override; + bool check_gc_overhead_limit(); + + size_t calculate_desired_old_gen_capacity(size_t old_gen_live_size); + + void resize_old_gen_after_full_gc(); + void print_tracing_info() const override; void stop() override {}; @@ -122,7 +127,9 @@ public: _eden_pool(nullptr), _survivor_pool(nullptr), _old_pool(nullptr), - _workers("GC Thread", ParallelGCThreads) { } + _workers("GC Thread", ParallelGCThreads), + _gc_overhead_counter(0), + _is_heap_almost_full(false) {} Name kind() const override { return CollectedHeap::Parallel; @@ -132,6 +139,9 @@ public: return "Parallel"; } + // Invoked at gc-pause-end + void gc_epilogue(bool full); + GrowableArray memory_managers() override; GrowableArray memory_pools() override; @@ -140,7 +150,7 @@ public: PSAdaptiveSizePolicy* size_policy() { return _size_policy; } - static PSGCAdaptivePolicyCounters* gc_policy_counters() { return _gc_policy_counters; } + static GCPolicyCounters* gc_policy_counters() { return _gc_policy_counters; } static ParallelScavengeHeap* heap() { return named_heap(CollectedHeap::Parallel); @@ -226,13 +236,8 @@ public: void verify(VerifyOption option /* ignored */) override; - // Resize the young generation. The reserved space for the - // generation may be expanded in preparation for the resize. - void resize_young_gen(size_t eden_size, size_t survivor_size); - - // Resize the old generation. The reserved space for the - // generation may be expanded in preparation for the resize. - void resize_old_gen(size_t desired_free_space); + void resize_after_young_gc(bool is_survivor_overflowing); + void resize_after_full_gc(); GCMemoryManager* old_gc_manager() const { return _old_manager; } GCMemoryManager* young_gc_manager() const { return _young_manager; } @@ -250,33 +255,4 @@ public: void unpin_object(JavaThread* thread, oop obj) override; }; -// Class that can be used to print information about the -// adaptive size policy at intervals specified by -// AdaptiveSizePolicyOutputInterval. Only print information -// if an adaptive size policy is in use. -class AdaptiveSizePolicyOutput : AllStatic { - static bool enabled() { - return UseParallelGC && - UseAdaptiveSizePolicy && - log_is_enabled(Debug, gc, ergo); - } - public: - static void print() { - if (enabled()) { - ParallelScavengeHeap::heap()->size_policy()->print(); - } - } - - static void print(AdaptiveSizePolicy* size_policy, uint count) { - bool do_print = - enabled() && - (AdaptiveSizePolicyOutputInterval > 0) && - (count % AdaptiveSizePolicyOutputInterval) == 0; - - if (do_print) { - size_policy->print(); - } - } -}; - #endif // SHARE_GC_PARALLEL_PARALLELSCAVENGEHEAP_HPP diff --git a/src/hotspot/share/gc/parallel/psAdaptiveSizePolicy.cpp b/src/hotspot/share/gc/parallel/psAdaptiveSizePolicy.cpp index b4f860e053f..fb57f224652 100644 --- a/src/hotspot/share/gc/parallel/psAdaptiveSizePolicy.cpp +++ b/src/hotspot/share/gc/parallel/psAdaptiveSizePolicy.cpp @@ -24,8 +24,8 @@ #include "gc/parallel/parallelScavengeHeap.hpp" #include "gc/parallel/psAdaptiveSizePolicy.hpp" -#include "gc/parallel/psGCAdaptivePolicyCounters.hpp" #include "gc/parallel/psScavenge.hpp" +#include "gc/shared/gcArguments.hpp" #include "gc/shared/gcCause.hpp" #include "gc/shared/gcPolicyCounters.hpp" #include "gc/shared/gcUtil.hpp" @@ -35,971 +35,227 @@ #include -PSAdaptiveSizePolicy::PSAdaptiveSizePolicy(size_t init_eden_size, - size_t init_promo_size, - size_t init_survivor_size, - size_t space_alignment, +PSAdaptiveSizePolicy::PSAdaptiveSizePolicy(size_t space_alignment, double gc_pause_goal_sec, uint gc_cost_ratio) : - AdaptiveSizePolicy(init_eden_size, - init_promo_size, - init_survivor_size, - gc_pause_goal_sec, + AdaptiveSizePolicy(gc_pause_goal_sec, gc_cost_ratio), - _avg_major_pause(new AdaptivePaddedAverage(AdaptiveTimeWeight, PausePadding)), - _avg_base_footprint(new AdaptiveWeightedAverage(AdaptiveSizePolicyWeight)), _avg_promoted(new AdaptivePaddedNoZeroDevAverage(AdaptiveSizePolicyWeight, PromotedPadding)), - _major_pause_old_estimator(new LinearLeastSquareFit(AdaptiveSizePolicyWeight)), - _major_pause_young_estimator(new LinearLeastSquareFit(AdaptiveSizePolicyWeight)), - _latest_major_mutator_interval_seconds(0), _space_alignment(space_alignment), - _live_at_last_full_gc(init_promo_size), - _change_old_gen_for_min_pauses(0), - _change_young_gen_for_maj_pauses(0), - _young_gen_size_increment_supplement(YoungGenerationSizeSupplement), - _old_gen_size_increment_supplement(TenuredGenerationSizeSupplement) -{ - // Start the timers - _major_timer.start(); -} - -size_t PSAdaptiveSizePolicy::calculate_free_based_on_live(size_t live, uintx ratio_as_percentage) { - // We want to calculate how much free memory there can be based on the - // amount of live data currently in the old gen. Using the formula: - // ratio * (free + live) = free - // Some equation solving later we get: - // free = (live * ratio) / (1 - ratio) - - const double ratio = ratio_as_percentage / 100.0; - const double ratio_inverse = 1.0 - ratio; - const double tmp = live * ratio; - size_t free = (size_t)(tmp / ratio_inverse); - - return free; -} - -size_t PSAdaptiveSizePolicy::calculated_old_free_size_in_bytes() const { - size_t free_size = (size_t)(_promo_size + avg_promoted()->padded_average()); - size_t live = ParallelScavengeHeap::heap()->old_gen()->used_in_bytes(); - - if (MinHeapFreeRatio != 0) { - size_t min_free = calculate_free_based_on_live(live, MinHeapFreeRatio); - free_size = MAX2(free_size, min_free); - } - - if (MaxHeapFreeRatio != 100) { - size_t max_free = calculate_free_based_on_live(live, MaxHeapFreeRatio); - free_size = MIN2(max_free, free_size); - } - - return free_size; -} + _young_gen_size_increment_supplement(YoungGenerationSizeSupplement) {} void PSAdaptiveSizePolicy::major_collection_begin() { - // Update the interval time - _major_timer.stop(); - // Save most recent collection time - _latest_major_mutator_interval_seconds = _major_timer.seconds(); _major_timer.reset(); _major_timer.start(); + record_gc_pause_start_instant(); } -void PSAdaptiveSizePolicy::update_minor_pause_old_estimator( - double minor_pause_in_ms) { - double promo_size_in_mbytes = ((double)_promo_size)/((double)M); - _minor_pause_old_estimator->update(promo_size_in_mbytes, - minor_pause_in_ms); -} - -void PSAdaptiveSizePolicy::major_collection_end(size_t amount_live, - GCCause::Cause gc_cause) { +void PSAdaptiveSizePolicy::major_collection_end() { // Update the pause time. _major_timer.stop(); - if (should_update_promo_stats(gc_cause)) { - double major_pause_in_seconds = _major_timer.seconds(); - double major_pause_in_ms = major_pause_in_seconds * MILLIUNITS; + double major_pause_in_seconds = _major_timer.seconds(); - // Sample for performance counter - _avg_major_pause->sample(major_pause_in_seconds); - - // Cost of collection (unit-less) - double collection_cost = 0.0; - if ((_latest_major_mutator_interval_seconds > 0.0) && - (major_pause_in_seconds > 0.0)) { - double interval_in_seconds = - _latest_major_mutator_interval_seconds + major_pause_in_seconds; - collection_cost = - major_pause_in_seconds / interval_in_seconds; - avg_major_gc_cost()->sample(collection_cost); - - // Sample for performance counter - _avg_major_interval->sample(interval_in_seconds); - } - - // Calculate variables used to estimate pause time vs. gen sizes - double eden_size_in_mbytes = ((double)_eden_size)/((double)M); - double promo_size_in_mbytes = ((double)_promo_size)/((double)M); - _major_pause_old_estimator->update(promo_size_in_mbytes, - major_pause_in_ms); - _major_pause_young_estimator->update(eden_size_in_mbytes, - major_pause_in_ms); - - log_trace(gc, ergo)("psAdaptiveSizePolicy::major_collection_end: major gc cost: %f average: %f", - collection_cost,avg_major_gc_cost()->average()); - log_trace(gc, ergo)(" major pause: %f major period %f", - major_pause_in_ms, _latest_major_mutator_interval_seconds * MILLIUNITS); - - // Calculate variable used to estimate collection cost vs. gen sizes - assert(collection_cost >= 0.0, "Expected to be non-negative"); - _major_collection_estimator->update(promo_size_in_mbytes, - collection_cost); - } - - // Update the amount live at the end of a full GC - _live_at_last_full_gc = amount_live; - - // Interval times use this timer to measure the interval that - // the mutator runs. Reset after the GC pause has been measured. - _major_timer.reset(); - _major_timer.start(); + record_gc_duration(major_pause_in_seconds); + _trimmed_major_gc_time_seconds.add(major_pause_in_seconds); } -void PSAdaptiveSizePolicy::clear_generation_free_space_flags() { - - AdaptiveSizePolicy::clear_generation_free_space_flags(); - - set_change_old_gen_for_min_pauses(0); - - set_change_young_gen_for_maj_pauses(0); +void PSAdaptiveSizePolicy::print_stats(bool is_survivor_overflowing) { + log_debug(gc, ergo)("Adaptive: throughput: %.3f, pause: %.1f ms, " + "gc-distance: %.3f (%.3f) s, " + "promoted: %.1f %s (%.1f %s), promotion-rate: %.1f M/s (%.1f M/s), overflowing: %s", + mutator_time_percent(), + minor_gc_time_estimate() * 1000.0, + _gc_distance_seconds_seq.davg(), _gc_distance_seconds_seq.last(), + PROPERFMTARGS(promoted_bytes_estimate()), PROPERFMTARGS(_promoted_bytes.last()), + _promotion_rate_bytes_per_sec.davg()/M, _promotion_rate_bytes_per_sec.last()/M, + is_survivor_overflowing ? "true" : "false"); } -// If this is not a full GC, only test and modify the young generation. - -void PSAdaptiveSizePolicy::compute_generations_free_space( - size_t young_live, - size_t eden_live, - size_t old_live, - size_t cur_eden, - size_t max_old_gen_size, - size_t max_eden_size, - bool is_full_gc) { - compute_eden_space_size(young_live, - eden_live, - cur_eden, - max_eden_size, - is_full_gc); - - compute_old_gen_free_space(old_live, - cur_eden, - max_old_gen_size, - is_full_gc); -} - -void PSAdaptiveSizePolicy::compute_eden_space_size( - size_t young_live, - size_t eden_live, - size_t cur_eden, - size_t max_eden_size, - bool is_full_gc) { - - // Update statistics - avg_young_live()->sample(young_live); - avg_eden_live()->sample(eden_live); - - // This code used to return if the policy was not ready , i.e., - // policy_is_ready() returning false. The intent was that - // decisions below needed major collection times and so could - // not be made before two major collections. A consequence was - // adjustments to the young generation were not done until after - // two major collections even if the minor collections times - // exceeded the requested goals. Now let the young generation - // adjust for the minor collection times. Major collection times - // will be zero for the first collection and will naturally be - // ignored. Tenured generation adjustments are only made at the - // full collections so until the second major collection has - // been reached, no tenured generation adjustments will be made. - - // Until we know better, desired promotion size uses the last calculation - size_t desired_promo_size = _promo_size; - - // Start eden at the current value. The desired value that is stored - // in _eden_size is not bounded by constraints of the heap and can - // run away. - // - // As expected setting desired_eden_size to the current - // value of desired_eden_size as a starting point - // caused desired_eden_size to grow way too large and caused - // an overflow down stream. It may have improved performance in - // some case but is dangerous. - size_t desired_eden_size = cur_eden; - - // Cache some values. There's a bit of work getting these, so - // we might save a little time. - const double major_cost = major_gc_cost(); - const double minor_cost = minor_gc_cost(); - - // This method sets the desired eden size. That plus the - // desired survivor space sizes sets the desired young generation - // size. This methods does not know what the desired survivor - // size is but expects that other policy will attempt to make - // the survivor sizes compatible with the live data in the - // young generation. This limit is an estimate of the space left - // in the young generation after the survivor spaces have been - // subtracted out. - size_t eden_limit = max_eden_size; - - const double gc_cost_limit = GCTimeLimit / 100.0; - - // Which way should we go? - // if pause requirement is not met - // adjust size of any generation with average paus exceeding - // the pause limit. Adjust one pause at a time (the larger) - // and only make adjustments for the major pause at full collections. - // else if throughput requirement not met - // adjust the size of the generation with larger gc time. Only - // adjust one generation at a time. - // else - // adjust down the total heap size. Adjust down the larger of the - // generations. - - // Add some checks for a threshold for a change. For example, - // a change less than the necessary alignment is probably not worth - // attempting. - - - if ((_avg_minor_pause->padded_average() > gc_pause_goal_sec()) || - (_avg_major_pause->padded_average() > gc_pause_goal_sec())) { - // - // Check pauses - // - // Make changes only to affect one of the pauses (the larger) - // at a time. - adjust_eden_for_pause_time(&desired_eden_size); - - } else if (_avg_minor_pause->padded_average() > gc_pause_goal_sec()) { - // Adjust only for the minor pause time goal - adjust_eden_for_minor_pause_time(&desired_eden_size); - - } else if(adjusted_mutator_cost() < _throughput_goal) { - // This branch used to require that (mutator_cost() > 0.0 in 1.4.2. - // This sometimes resulted in skipping to the minimize footprint - // code. Change this to try and reduce GC time if mutator time is - // negative for whatever reason. Or for future consideration, - // bail out of the code if mutator time is negative. - // - // Throughput - // - assert(major_cost >= 0.0, "major cost is < 0.0"); - assert(minor_cost >= 0.0, "minor cost is < 0.0"); - // Try to reduce the GC times. - adjust_eden_for_throughput(is_full_gc, &desired_eden_size); - - } else { - - // Be conservative about reducing the footprint. - // Do a minimum number of major collections first. - // Have reasonable averages for major and minor collections costs. - if (UseAdaptiveSizePolicyFootprintGoal && - young_gen_policy_is_ready() && - avg_major_gc_cost()->average() >= 0.0 && - avg_minor_gc_cost()->average() >= 0.0) { - size_t desired_sum = desired_eden_size + desired_promo_size; - desired_eden_size = adjust_eden_for_footprint(desired_eden_size, desired_sum); - } - } - - // Note we make the same tests as in the code block below; the code - // seems a little easier to read with the printing in another block. - if (desired_eden_size > eden_limit) { - log_debug(gc, ergo)( - "PSAdaptiveSizePolicy::compute_eden_space_size limits:" - " desired_eden_size: %zu" - " old_eden_size: %zu" - " eden_limit: %zu" - " cur_eden: %zu" - " max_eden_size: %zu" - " avg_young_live: %zu", - desired_eden_size, _eden_size, eden_limit, cur_eden, - max_eden_size, (size_t)avg_young_live()->average()); - } - if (gc_cost() > gc_cost_limit) { - log_debug(gc, ergo)( - "PSAdaptiveSizePolicy::compute_eden_space_size: gc time limit" - " gc_cost: %f " - " GCTimeLimit: %u", - gc_cost(), GCTimeLimit); - } - - // Align everything and make a final limit check - desired_eden_size = align_up(desired_eden_size, _space_alignment); - desired_eden_size = MAX2(desired_eden_size, _space_alignment); - - eden_limit = align_down(eden_limit, _space_alignment); - - // And one last limit check, now that we've aligned things. - if (desired_eden_size > eden_limit) { - // If the policy says to get a larger eden but - // is hitting the limit, don't decrease eden. - // This can lead to a general drifting down of the - // eden size. Let the tenuring calculation push more - // into the old gen. - desired_eden_size = MAX2(eden_limit, cur_eden); - } - - log_debug(gc, ergo)("PSAdaptiveSizePolicy::compute_eden_space_size: costs minor_time: %f major_cost: %f mutator_cost: %f throughput_goal: %f", - minor_gc_cost(), major_gc_cost(), mutator_cost(), _throughput_goal); - - log_trace(gc, ergo)("Minor_pause: %f major_pause: %f minor_interval: %f major_interval: %fpause_goal: %f", - _avg_minor_pause->padded_average(), - _avg_major_pause->padded_average(), - _avg_minor_interval->average(), - _avg_major_interval->average(), - gc_pause_goal_sec()); - - log_debug(gc, ergo)("Live_space: %zu free_space: %zu", - live_space(), free_space()); - - log_trace(gc, ergo)("avg_young_live: %zu avg_old_live: %zu", - (size_t)avg_young_live()->average(), - (size_t)avg_old_live()->average()); - - log_debug(gc, ergo)("Old eden_size: %zu desired_eden_size: %zu", - _eden_size, desired_eden_size); - - set_eden_size(desired_eden_size); -} - -void PSAdaptiveSizePolicy::compute_old_gen_free_space( - size_t old_live, - size_t cur_eden, - size_t max_old_gen_size, - bool is_full_gc) { - - // Update statistics - // Time statistics are updated as we go, update footprint stats here - if (is_full_gc) { - // old_live is only accurate after a full gc - avg_old_live()->sample(old_live); - } - - // This code used to return if the policy was not ready , i.e., - // policy_is_ready() returning false. The intent was that - // decisions below needed major collection times and so could - // not be made before two major collections. A consequence was - // adjustments to the young generation were not done until after - // two major collections even if the minor collections times - // exceeded the requested goals. Now let the young generation - // adjust for the minor collection times. Major collection times - // will be zero for the first collection and will naturally be - // ignored. Tenured generation adjustments are only made at the - // full collections so until the second major collection has - // been reached, no tenured generation adjustments will be made. - - // Until we know better, desired promotion size uses the last calculation - size_t desired_promo_size = _promo_size; - - // Start eden at the current value. The desired value that is stored - // in _eden_size is not bounded by constraints of the heap and can - // run away. - // - // As expected setting desired_eden_size to the current - // value of desired_eden_size as a starting point - // caused desired_eden_size to grow way too large and caused - // an overflow down stream. It may have improved performance in - // some case but is dangerous. - size_t desired_eden_size = cur_eden; - - // Cache some values. There's a bit of work getting these, so - // we might save a little time. - const double major_cost = major_gc_cost(); - const double minor_cost = minor_gc_cost(); - - // Limits on our growth - size_t promo_limit = (size_t)(max_old_gen_size - avg_old_live()->average()); - - // But don't force a promo size below the current promo size. Otherwise, - // the promo size will shrink for no good reason. - promo_limit = MAX2(promo_limit, _promo_size); - - const double gc_cost_limit = GCTimeLimit/100.0; - - // Which way should we go? - // if pause requirement is not met - // adjust size of any generation with average paus exceeding - // the pause limit. Adjust one pause at a time (the larger) - // and only make adjustments for the major pause at full collections. - // else if throughput requirement not met - // adjust the size of the generation with larger gc time. Only - // adjust one generation at a time. - // else - // adjust down the total heap size. Adjust down the larger of the - // generations. - - // Add some checks for a threshold for a change. For example, - // a change less than the necessary alignment is probably not worth - // attempting. - - if ((_avg_minor_pause->padded_average() > gc_pause_goal_sec()) || - (_avg_major_pause->padded_average() > gc_pause_goal_sec())) { - // - // Check pauses - // - // Make changes only to affect one of the pauses (the larger) - // at a time. - if (is_full_gc) { - set_decide_at_full_gc(decide_at_full_gc_true); - adjust_promo_for_pause_time(&desired_promo_size); - } - } else if (adjusted_mutator_cost() < _throughput_goal) { - // This branch used to require that (mutator_cost() > 0.0 in 1.4.2. - // This sometimes resulted in skipping to the minimize footprint - // code. Change this to try and reduce GC time if mutator time is - // negative for whatever reason. Or for future consideration, - // bail out of the code if mutator time is negative. - // - // Throughput - // - assert(major_cost >= 0.0, "major cost is < 0.0"); - assert(minor_cost >= 0.0, "minor cost is < 0.0"); - // Try to reduce the GC times. - if (is_full_gc) { - set_decide_at_full_gc(decide_at_full_gc_true); - adjust_promo_for_throughput(is_full_gc, &desired_promo_size); - } - } else { - - // Be conservative about reducing the footprint. - // Do a minimum number of major collections first. - // Have reasonable averages for major and minor collections costs. - if (UseAdaptiveSizePolicyFootprintGoal && - young_gen_policy_is_ready() && - avg_major_gc_cost()->average() >= 0.0 && - avg_minor_gc_cost()->average() >= 0.0) { - if (is_full_gc) { - set_decide_at_full_gc(decide_at_full_gc_true); - size_t desired_sum = desired_eden_size + desired_promo_size; - desired_promo_size = adjust_promo_for_footprint(desired_promo_size, desired_sum); - } - } - } - - // Note we make the same tests as in the code block below; the code - // seems a little easier to read with the printing in another block. - if (desired_promo_size > promo_limit) { - // "free_in_old_gen" was the original value for used for promo_limit - size_t free_in_old_gen = (size_t)(max_old_gen_size - avg_old_live()->average()); - log_debug(gc, ergo)( - "PSAdaptiveSizePolicy::compute_old_gen_free_space limits:" - " desired_promo_size: %zu" - " promo_limit: %zu" - " free_in_old_gen: %zu" - " max_old_gen_size: %zu" - " avg_old_live: %zu", - desired_promo_size, promo_limit, free_in_old_gen, - max_old_gen_size, (size_t) avg_old_live()->average()); - } - if (gc_cost() > gc_cost_limit) { - log_debug(gc, ergo)( - "PSAdaptiveSizePolicy::compute_old_gen_free_space: gc time limit" - " gc_cost: %f " - " GCTimeLimit: %u", - gc_cost(), GCTimeLimit); - } - - // Align everything and make a final limit check - desired_promo_size = align_up(desired_promo_size, _space_alignment); - desired_promo_size = MAX2(desired_promo_size, _space_alignment); - - promo_limit = align_down(promo_limit, _space_alignment); - - // And one last limit check, now that we've aligned things. - desired_promo_size = MIN2(desired_promo_size, promo_limit); - - // Timing stats - log_debug(gc, ergo)("PSAdaptiveSizePolicy::compute_old_gen_free_space: costs minor_time: %f major_cost: %f mutator_cost: %f throughput_goal: %f", - minor_gc_cost(), major_gc_cost(), mutator_cost(), _throughput_goal); - - log_trace(gc, ergo)("Minor_pause: %f major_pause: %f minor_interval: %f major_interval: %f pause_goal: %f", - _avg_minor_pause->padded_average(), - _avg_major_pause->padded_average(), - _avg_minor_interval->average(), - _avg_major_interval->average(), - gc_pause_goal_sec()); - - // Footprint stats - log_debug(gc, ergo)("Live_space: %zu free_space: %zu", - live_space(), free_space()); - - log_trace(gc, ergo)("avg_young_live: %zu avg_old_live: %zu", - (size_t)avg_young_live()->average(), - (size_t)avg_old_live()->average()); - - log_debug(gc, ergo)("Old promo_size: %zu desired_promo_size: %zu", - _promo_size, desired_promo_size); - - set_promo_size(desired_promo_size); -} - -void PSAdaptiveSizePolicy::decay_supplemental_growth(bool is_full_gc) { - // Decay the supplemental increment? Decay the supplement growth - // factor even if it is not used. It is only meant to give a boost - // to the initial growth and if it is not used, then it was not - // needed. - if (is_full_gc) { - // Don't wait for the threshold value for the major collections. If - // here, the supplemental growth term was used and should decay. - if ((_avg_major_pause->count() % TenuredGenerationSizeSupplementDecay) - == 0) { - _old_gen_size_increment_supplement = - _old_gen_size_increment_supplement >> 1; - } - } else { - if ((_avg_minor_pause->count() >= AdaptiveSizePolicyReadyThreshold) && - (_avg_minor_pause->count() % YoungGenerationSizeSupplementDecay) == 0) { - _young_gen_size_increment_supplement = - _young_gen_size_increment_supplement >> 1; - } - } -} - -void PSAdaptiveSizePolicy::adjust_eden_for_minor_pause_time(size_t* desired_eden_size_ptr) { - // Adjust the young generation size to reduce pause time of - // of collections. - // - // The AdaptiveSizePolicyInitializingSteps test is not used - // here. It has not seemed to be needed but perhaps should - // be added for consistency. - if (minor_pause_young_estimator()->decrement_will_decrease()) { - // reduce eden size - set_change_young_gen_for_min_pauses( - decrease_young_gen_for_min_pauses_true); - *desired_eden_size_ptr = *desired_eden_size_ptr - - eden_decrement_aligned_down(*desired_eden_size_ptr); - } -} - -void PSAdaptiveSizePolicy::adjust_promo_for_pause_time(size_t* desired_promo_size_ptr) { - - size_t promo_heap_delta = 0; - // Add some checks for a threshold for a change. For example, - // a change less than the required alignment is probably not worth - // attempting. - - if (_avg_minor_pause->padded_average() <= _avg_major_pause->padded_average()) { - // Adjust for the major pause time only at full gc's because the - // affects of a change can only be seen at full gc's. - - // Reduce old generation size to reduce pause? - if (major_pause_old_estimator()->decrement_will_decrease()) { - // reduce old generation size - set_change_old_gen_for_maj_pauses(decrease_old_gen_for_maj_pauses_true); - promo_heap_delta = promo_decrement_aligned_down(*desired_promo_size_ptr); - *desired_promo_size_ptr = _promo_size - promo_heap_delta; - } - } - - log_trace(gc, ergo)( - "PSAdaptiveSizePolicy::adjust_promo_for_pause_time " - "adjusting gen sizes for major pause (avg %f goal %f). " - "desired_promo_size %zu promo delta %zu", - _avg_major_pause->average(), gc_pause_goal_sec(), - *desired_promo_size_ptr, promo_heap_delta); -} - -void PSAdaptiveSizePolicy::adjust_eden_for_pause_time(size_t* desired_eden_size_ptr) { - - size_t eden_heap_delta = 0; - // Add some checks for a threshold for a change. For example, - // a change less than the required alignment is probably not worth - // attempting. - if (_avg_minor_pause->padded_average() > _avg_major_pause->padded_average()) { - adjust_eden_for_minor_pause_time(desired_eden_size_ptr); - } - log_trace(gc, ergo)( - "PSAdaptiveSizePolicy::adjust_eden_for_pause_time " - "adjusting gen sizes for major pause (avg %f goal %f). " - "desired_eden_size %zu eden delta %zu", - _avg_major_pause->average(), gc_pause_goal_sec(), - *desired_eden_size_ptr, eden_heap_delta); -} - -void PSAdaptiveSizePolicy::adjust_promo_for_throughput(bool is_full_gc, - size_t* desired_promo_size_ptr) { - - // Add some checks for a threshold for a change. For example, - // a change less than the required alignment is probably not worth - // attempting. - - if ((gc_cost() + mutator_cost()) == 0.0) { - return; - } - - log_trace(gc, ergo)("PSAdaptiveSizePolicy::adjust_promo_for_throughput(is_full: %d, promo: %zu): mutator_cost %f major_gc_cost %f minor_gc_cost %f", - is_full_gc, *desired_promo_size_ptr, mutator_cost(), major_gc_cost(), minor_gc_cost()); - - // Tenured generation - if (is_full_gc) { - // Calculate the change to use for the tenured gen. - size_t scaled_promo_heap_delta = 0; - // Can the increment to the generation be scaled? - if (gc_cost() >= 0.0 && major_gc_cost() >= 0.0) { - size_t promo_heap_delta = - promo_increment_with_supplement_aligned_up(*desired_promo_size_ptr); - double scale_by_ratio = major_gc_cost() / gc_cost(); - scaled_promo_heap_delta = - (size_t) (scale_by_ratio * (double) promo_heap_delta); - log_trace(gc, ergo)("Scaled tenured increment: %zu by %f down to %zu", - promo_heap_delta, scale_by_ratio, scaled_promo_heap_delta); - } else if (major_gc_cost() >= 0.0) { - // Scaling is not going to work. If the major gc time is the - // larger, give it a full increment. - if (major_gc_cost() >= minor_gc_cost()) { - scaled_promo_heap_delta = - promo_increment_with_supplement_aligned_up(*desired_promo_size_ptr); - } +size_t PSAdaptiveSizePolicy::compute_desired_eden_size(bool is_survivor_overflowing, size_t cur_eden) { + // Guard against divide-by-zero; 0.001ms + double gc_distance = MAX2(_gc_distance_seconds_seq.last(), 0.000001); + double min_gc_distance = MinGCDistanceSecond; + + if (mutator_time_percent() < _throughput_goal) { + size_t new_eden; + const double expected_gc_distance = _trimmed_minor_gc_time_seconds.last() * GCTimeRatio; + if (gc_distance >= expected_gc_distance) { + // The lastest sample already satisfies throughput goal; keep the current size + new_eden = cur_eden; } else { - // Don't expect to get here but it's ok if it does - // in the product build since the delta will be 0 - // and nothing will change. - assert(false, "Unexpected value for gc costs"); + // Using the latest sample to limit the growth in order to avoid overshoot + new_eden = MIN2((expected_gc_distance / gc_distance) * cur_eden, + (double)increase_eden(cur_eden)); } + log_debug(gc, ergo)("Adaptive: throughput (actual vs goal): %.3f vs %.3f ; eden delta: + %zu K", + mutator_time_percent(), _throughput_goal, (new_eden - cur_eden)/K); + return new_eden; + } - switch (AdaptiveSizeThroughPutPolicy) { - case 1: - // Early in the run the statistics might not be good. Until - // a specific number of collections have been, use the heuristic - // that a larger generation size means lower collection costs. - if (major_collection_estimator()->increment_will_decrease() || - (_old_gen_change_for_major_throughput - <= AdaptiveSizePolicyInitializingSteps)) { - // Increase tenured generation size to reduce major collection cost - if ((*desired_promo_size_ptr + scaled_promo_heap_delta) > - *desired_promo_size_ptr) { - *desired_promo_size_ptr = _promo_size + scaled_promo_heap_delta; - } - set_change_old_gen_for_throughput( - increase_old_gen_for_throughput_true); - _old_gen_change_for_major_throughput++; - } + if (minor_gc_time_estimate() > gc_pause_goal_sec()) { + log_debug(gc, ergo)("Adaptive: pause (ms) (actual vs goal): %.1f vs %.1f", + minor_gc_time_estimate() * 1000.0, gc_pause_goal_sec() * 1000.0); + return decrease_eden_for_minor_pause_time(cur_eden); + } - break; - default: - // Simplest strategy - if ((*desired_promo_size_ptr + scaled_promo_heap_delta) > - *desired_promo_size_ptr) { - *desired_promo_size_ptr = *desired_promo_size_ptr + - scaled_promo_heap_delta; - } - set_change_old_gen_for_throughput( - increase_old_gen_for_throughput_true); - _old_gen_change_for_major_throughput++; + if (gc_distance < min_gc_distance) { + size_t new_eden = MIN2((min_gc_distance / gc_distance) * cur_eden, + (double)increase_eden(cur_eden)); + log_debug(gc, ergo)("Adaptive: gc-distance (predicted vs goal): %.3f vs %.3f", + gc_distance, min_gc_distance); + return new_eden; + } + + // If no overflowing and promotion is small + if (!is_survivor_overflowing && promoted_bytes_estimate() < 1*K) { + size_t delta = MIN2(eden_increment(cur_eden) / AdaptiveSizeDecrementScaleFactor, cur_eden / 2); + double delta_factor = (double) delta / cur_eden; + + const double gc_time_lower_estimate = _trimmed_minor_gc_time_seconds.davg() - _trimmed_minor_gc_time_seconds.dsd(); + // Limit gc-frequency so that promoted rate is < 1M/s + // promoted_bytes_estimate() / (gc_distance + gc_time_lower_estimate) < 1M/s + // ==> promoted_bytes_estimate() / M - gc_time_lower_estimate < gc_distance + + const double gc_distance_target = MAX3(minor_gc_time_conservative_estimate() * GCTimeRatio, + promoted_bytes_estimate() / M - gc_time_lower_estimate, + min_gc_distance); + double predicted_gc_distance = gc_distance * (1 - delta_factor) - _gc_distance_seconds_seq.dsd(); + + if (predicted_gc_distance > gc_distance_target) { + log_debug(gc, ergo)("Adaptive: shrinking gc-distance (predicted vs threshold): %.3f vs %.3f", + predicted_gc_distance, gc_distance_target); + return cur_eden - delta; } + } - log_trace(gc, ergo)("Adjusting tenured gen for throughput (avg %f goal %f). desired_promo_size %zu promo_delta %zu", - mutator_cost(), - _throughput_goal, - *desired_promo_size_ptr, scaled_promo_heap_delta); + log_debug(gc, ergo)("Adaptive: eden unchanged"); + return cur_eden; +} + +size_t PSAdaptiveSizePolicy::compute_desired_survivor_size( + size_t current_survivor_size, + size_t max_gen_size) { + size_t desired_survivor_size = survived_bytes_estimate(); + + if (desired_survivor_size >= current_survivor_size) { + // Increasing survivor + return MIN2(desired_survivor_size, max_survivor_size(max_gen_size)); + } + + size_t delta = current_survivor_size - desired_survivor_size; + return current_survivor_size - delta / AdaptiveSizeDecrementScaleFactor; +} + +size_t PSAdaptiveSizePolicy::compute_old_gen_shrink_bytes(size_t old_gen_free_bytes, size_t max_shrink_bytes) { + // 10min + static constexpr double lookahead_sec = 10 * 60; + + double free_bytes = old_gen_free_bytes; + + double promotion_rate = promotion_rate_bytes_per_sec_estimate(); + + double min_free_bytes = MAX2((double)padded_average_promoted_in_bytes(), + promotion_rate * lookahead_sec); + size_t shrink_bytes = 0; + + if (free_bytes > min_free_bytes) { + shrink_bytes = (free_bytes - min_free_bytes) / 2; + shrink_bytes = MIN2(shrink_bytes, max_shrink_bytes); + } + + log_debug(gc, ergo)("Adaptive: old-gen free bytes: %.0f M, min-free-bytes: %.1f M, shrink-bytes: %zu K", + free_bytes/M, min_free_bytes/M, shrink_bytes/K); + + return shrink_bytes; +} + +void PSAdaptiveSizePolicy::decay_supplemental_growth(uint num_minor_gcs) { + if ((num_minor_gcs >= AdaptiveSizePolicyReadyThreshold) && + (num_minor_gcs % YoungGenerationSizeSupplementDecay) == 0) { + _young_gen_size_increment_supplement = + _young_gen_size_increment_supplement >> 1; } } -void PSAdaptiveSizePolicy::adjust_eden_for_throughput(bool is_full_gc, - size_t* desired_eden_size_ptr) { +size_t PSAdaptiveSizePolicy::decrease_eden_for_minor_pause_time(size_t current_eden_size) { + size_t desired_eden_size = minor_pause_young_estimator()->decrement_will_decrease() + ? current_eden_size - eden_decrement_aligned_down(current_eden_size) + : current_eden_size; - // Add some checks for a threshold for a change. For example, - // a change less than the required alignment is probably not worth - // attempting. + assert(desired_eden_size <= current_eden_size, "postcondition"); - if ((gc_cost() + mutator_cost()) == 0.0) { - return; - } - - log_trace(gc, ergo)("PSAdaptiveSizePolicy::adjust_eden_for_throughput(is_full: %d, cur_eden: %zu): mutator_cost %f major_gc_cost %f minor_gc_cost %f", - is_full_gc, *desired_eden_size_ptr, mutator_cost(), major_gc_cost(), minor_gc_cost()); - - // Young generation - size_t scaled_eden_heap_delta = 0; - // Can the increment to the generation be scaled? - if (gc_cost() >= 0.0 && minor_gc_cost() >= 0.0) { - size_t eden_heap_delta = - eden_increment_with_supplement_aligned_up(*desired_eden_size_ptr); - double scale_by_ratio = minor_gc_cost() / gc_cost(); - assert(scale_by_ratio <= 1.0 && scale_by_ratio >= 0.0, "Scaling is wrong"); - scaled_eden_heap_delta = - (size_t) (scale_by_ratio * (double) eden_heap_delta); - log_trace(gc, ergo)("Scaled eden increment: %zu by %f down to %zu", - eden_heap_delta, scale_by_ratio, scaled_eden_heap_delta); - } else if (minor_gc_cost() >= 0.0) { - // Scaling is not going to work. If the minor gc time is the - // larger, give it a full increment. - if (minor_gc_cost() > major_gc_cost()) { - scaled_eden_heap_delta = - eden_increment_with_supplement_aligned_up(*desired_eden_size_ptr); - } - } else { - // Don't expect to get here but it's ok if it does - // in the product build since the delta will be 0 - // and nothing will change. - assert(false, "Unexpected value for gc costs"); - } - - // Use a heuristic for some number of collections to give - // the averages time to settle down. - switch (AdaptiveSizeThroughPutPolicy) { - case 1: - if (minor_collection_estimator()->increment_will_decrease() || - (_young_gen_change_for_minor_throughput - <= AdaptiveSizePolicyInitializingSteps)) { - // Expand young generation size to reduce frequency of - // of collections. - if ((*desired_eden_size_ptr + scaled_eden_heap_delta) > - *desired_eden_size_ptr) { - *desired_eden_size_ptr = - *desired_eden_size_ptr + scaled_eden_heap_delta; - } - set_change_young_gen_for_throughput( - increase_young_gen_for_througput_true); - _young_gen_change_for_minor_throughput++; - } - break; - default: - if ((*desired_eden_size_ptr + scaled_eden_heap_delta) > - *desired_eden_size_ptr) { - *desired_eden_size_ptr = - *desired_eden_size_ptr + scaled_eden_heap_delta; - } - set_change_young_gen_for_throughput( - increase_young_gen_for_througput_true); - _young_gen_change_for_minor_throughput++; - } - - log_trace(gc, ergo)("Adjusting eden for throughput (avg %f goal %f). desired_eden_size %zu eden delta %zu", - mutator_cost(), _throughput_goal, *desired_eden_size_ptr, scaled_eden_heap_delta); + return desired_eden_size; } -size_t PSAdaptiveSizePolicy::adjust_promo_for_footprint( - size_t desired_promo_size, size_t desired_sum) { - assert(desired_promo_size <= desired_sum, "Inconsistent parameters"); - set_decrease_for_footprint(decrease_old_gen_for_footprint_true); +size_t PSAdaptiveSizePolicy::increase_eden(size_t current_eden_size) { + size_t delta = eden_increment_with_supplement_aligned_up(current_eden_size); - size_t change = promo_decrement(desired_promo_size); - change = scale_down(change, desired_promo_size, desired_sum); + size_t desired_eden_size = current_eden_size + delta; - size_t reduced_size = desired_promo_size - change; + assert(desired_eden_size >= current_eden_size, "postcondition"); - log_trace(gc, ergo)( - "AdaptiveSizePolicy::adjust_promo_for_footprint " - "adjusting tenured gen for footprint. " - "starting promo size %zu" - " reduced promo size %zu" - " promo delta %zu", - desired_promo_size, reduced_size, change ); - - assert(reduced_size <= desired_promo_size, "Inconsistent result"); - return reduced_size; + return desired_eden_size; } -size_t PSAdaptiveSizePolicy::adjust_eden_for_footprint( - size_t desired_eden_size, size_t desired_sum) { - assert(desired_eden_size <= desired_sum, "Inconsistent parameters"); - set_decrease_for_footprint(decrease_young_gen_for_footprint_true); - - size_t change = eden_decrement(desired_eden_size); - change = scale_down(change, desired_eden_size, desired_sum); - - size_t reduced_size = desired_eden_size - change; - - log_trace(gc, ergo)( - "AdaptiveSizePolicy::adjust_eden_for_footprint " - "adjusting eden for footprint. " - " starting eden size %zu" - " reduced eden size %zu" - " eden delta %zu", - desired_eden_size, reduced_size, change); - - assert(reduced_size <= desired_eden_size, "Inconsistent result"); - return reduced_size; -} - -// Scale down "change" by the factor -// part / total -// Don't align the results. - -size_t PSAdaptiveSizePolicy::scale_down(size_t change, - double part, - double total) { - assert(part <= total, "Inconsistent input"); - size_t reduced_change = change; - if (total > 0) { - double fraction = part / total; - reduced_change = (size_t) (fraction * (double) change); - } - assert(reduced_change <= change, "Inconsistent result"); - return reduced_change; -} - -size_t PSAdaptiveSizePolicy::eden_increment_with_supplement_aligned_up( - size_t cur_eden) { +size_t PSAdaptiveSizePolicy::eden_increment_with_supplement_aligned_up(size_t cur_eden) { size_t result = eden_increment(cur_eden, YoungGenerationSizeIncrement + _young_gen_size_increment_supplement); return align_up(result, _space_alignment); } size_t PSAdaptiveSizePolicy::eden_decrement_aligned_down(size_t cur_eden) { - size_t eden_heap_delta = eden_decrement(cur_eden); + size_t eden_heap_delta = eden_increment(cur_eden) / AdaptiveSizeDecrementScaleFactor; return align_down(eden_heap_delta, _space_alignment); } -size_t PSAdaptiveSizePolicy::promo_increment_with_supplement_aligned_up( - size_t cur_promo) { - size_t result = promo_increment(cur_promo, - TenuredGenerationSizeIncrement + _old_gen_size_increment_supplement); - return align_up(result, _space_alignment); -} - -size_t PSAdaptiveSizePolicy::promo_decrement_aligned_down(size_t cur_promo) { - size_t promo_heap_delta = promo_decrement(cur_promo); - return align_down(promo_heap_delta, _space_alignment); -} - -uint PSAdaptiveSizePolicy::compute_survivor_space_size_and_threshold( - bool is_survivor_overflow, - uint tenuring_threshold, - size_t survivor_limit) { - assert(survivor_limit >= _space_alignment, - "survivor_limit too small"); - assert(is_aligned(survivor_limit, _space_alignment), - "survivor_limit not aligned"); - - // This method is called even if the tenuring threshold and survivor - // spaces are not adjusted so that the averages are sampled above. - if (!UsePSAdaptiveSurvivorSizePolicy || - !young_gen_policy_is_ready()) { +uint PSAdaptiveSizePolicy::compute_tenuring_threshold(bool is_survivor_overflowing, + uint tenuring_threshold) { + if (!young_gen_policy_is_ready()) { return tenuring_threshold; } - // We'll decide whether to increase or decrease the tenuring - // threshold based partly on the newly computed survivor size - // (if we hit the maximum limit allowed, we'll always choose to - // decrement the threshold). - bool incr_tenuring_threshold = false; - bool decr_tenuring_threshold = false; - - set_decrement_tenuring_threshold_for_gc_cost(false); - set_increment_tenuring_threshold_for_gc_cost(false); - set_decrement_tenuring_threshold_for_survivor_limit(false); - - if (!is_survivor_overflow) { - // Keep running averages on how much survived - - // We use the tenuring threshold to equalize the cost of major - // and minor collections. - // ThresholdTolerance is used to indicate how sensitive the - // tenuring threshold is to differences in cost between the - // collection types. - - // Get the times of interest. This involves a little work, so - // we cache the values here. - const double major_cost = major_gc_cost(); - const double minor_cost = minor_gc_cost(); - - if (minor_cost > major_cost * _threshold_tolerance_percent) { - // Minor times are getting too long; lower the threshold so - // less survives and more is promoted. - decr_tenuring_threshold = true; - set_decrement_tenuring_threshold_for_gc_cost(true); - } else if (major_cost > minor_cost * _threshold_tolerance_percent) { - // Major times are too long, so we want less promotion. - incr_tenuring_threshold = true; - set_increment_tenuring_threshold_for_gc_cost(true); - } - - } else { - // Survivor space overflow occurred, so promoted and survived are - // not accurate. We'll make our best guess by combining survived - // and promoted and count them as survivors. - // - // We'll lower the tenuring threshold to see if we can correct - // things. Also, set the survivor size conservatively. We're - // trying to avoid many overflows from occurring if defnew size - // is just too small. - - decr_tenuring_threshold = true; + if (is_survivor_overflowing) { + return tenuring_threshold; } - // The padded average also maintains a deviation from the average; - // we use this to see how good of an estimate we have of what survived. - // We're trying to pad the survivor size as little as possible without - // overflowing the survivor spaces. - size_t target_size = align_up((size_t)_avg_survived->padded_average(), - _space_alignment); - target_size = MAX2(target_size, _space_alignment); + bool incr_tenuring_threshold = false; - if (target_size > survivor_limit) { - // Target size is bigger than we can handle. Let's also reduce - // the tenuring threshold. - target_size = survivor_limit; - decr_tenuring_threshold = true; - set_decrement_tenuring_threshold_for_survivor_limit(true); + const double major_cost = major_gc_time_sum(); + const double minor_cost = minor_gc_time_sum(); + + if (minor_cost > major_cost * _threshold_tolerance_percent) { + // nothing; we prefer young-gc over full-gc + } else if (major_cost > minor_cost * _threshold_tolerance_percent) { + // Major times are too long, so we want less promotion. + incr_tenuring_threshold = true; } // Finally, increment or decrement the tenuring threshold, as decided above. // We test for decrementing first, as we might have hit the target size // limit. if (!(AlwaysTenure || NeverTenure)) { - if (decr_tenuring_threshold && tenuring_threshold > 1) { - tenuring_threshold--; - } else if (incr_tenuring_threshold && tenuring_threshold < MaxTenuringThreshold) { + if (incr_tenuring_threshold && tenuring_threshold < MaxTenuringThreshold) { tenuring_threshold++; } } - // We keep a running average of the amount promoted which is used - // to decide when we should collect the old generation (when - // the amount of old gen free space is less than what we expect to - // promote). - - log_trace(gc, ergo)("avg_survived: %f avg_deviation: %f", _avg_survived->average(), _avg_survived->deviation()); - log_debug(gc, ergo)("avg_survived_padded_avg: %f", _avg_survived->padded_average()); - - log_trace(gc, ergo)("avg_promoted_avg: %f avg_promoted_dev: %f", avg_promoted()->average(), avg_promoted()->deviation()); - log_debug(gc, ergo)("avg_promoted_padded_avg: %f avg_pretenured_padded_avg: %f tenuring_thresh: %d target_size: %zu", - avg_promoted()->padded_average(), - _avg_pretenured->padded_average(), - tenuring_threshold, target_size); - - set_survivor_size(target_size); - return tenuring_threshold; } void PSAdaptiveSizePolicy::update_averages(bool is_survivor_overflow, size_t survived, size_t promoted) { - // Update averages if (!is_survivor_overflow) { - // Keep running averages on how much survived - _avg_survived->sample(survived); + _survived_bytes.add(survived); } else { - size_t survived_guess = survived + promoted; - _avg_survived->sample(survived_guess); + // survived is an underestimate + _survived_bytes.add(survived + promoted); } + avg_promoted()->sample(promoted); + _promoted_bytes.add(promoted); - log_trace(gc, ergo)("AdaptiveSizePolicy::update_averages: survived: %zu promoted: %zu overflow: %s", - survived, promoted, is_survivor_overflow ? "true" : "false"); -} - -bool PSAdaptiveSizePolicy::print() const { - - if (!UseAdaptiveSizePolicy) { - return false; - } - - if (AdaptiveSizePolicy::print()) { - AdaptiveSizePolicy::print_tenuring_threshold(PSScavenge::tenuring_threshold()); - return true; - } - - return false; -} + double promotion_rate = promoted / (_gc_distance_seconds_seq.last() + _trimmed_minor_gc_time_seconds.last()); + _promotion_rate_bytes_per_sec.add(promotion_rate); +} \ No newline at end of file diff --git a/src/hotspot/share/gc/parallel/psAdaptiveSizePolicy.hpp b/src/hotspot/share/gc/parallel/psAdaptiveSizePolicy.hpp index 4fab160dcb4..68c736d0716 100644 --- a/src/hotspot/share/gc/parallel/psAdaptiveSizePolicy.hpp +++ b/src/hotspot/share/gc/parallel/psAdaptiveSizePolicy.hpp @@ -26,7 +26,6 @@ #define SHARE_GC_PARALLEL_PSADAPTIVESIZEPOLICY_HPP #include "gc/shared/adaptiveSizePolicy.hpp" -#include "gc/shared/gcCause.hpp" #include "gc/shared/gcUtil.hpp" #include "utilities/align.hpp" @@ -34,151 +33,34 @@ // optimal free space for both the young and old generation // based on current application characteristics (based on gc cost // and application footprint). -// -// It also computes an optimal tenuring threshold between the young -// and old generations, so as to equalize the cost of collections -// of those generations, as well as optimal survivor space sizes -// for the young generation. -// -// While this class is specifically intended for a generational system -// consisting of a young gen (containing an Eden and two semi-spaces) -// and a tenured gen, as well as a perm gen for reflective data, it -// makes NO references to specific generations. -// -// 05/02/2003 Update -// The 1.5 policy makes use of data gathered for the costs of GC on -// specific generations. That data does reference specific -// generation. Also diagnostics specific to generations have -// been added. - -// Forward decls -class elapsedTimer; class PSAdaptiveSizePolicy : public AdaptiveSizePolicy { - friend class PSGCAdaptivePolicyCounters; - private: - // These values are used to record decisions made during the - // policy. For example, if the young generation was decreased - // to decrease the GC cost of minor collections the value - // decrease_young_gen_for_throughput_true is used. - - // Last calculated sizes, in bytes, and aligned - // NEEDS_CLEANUP should use sizes.hpp, but it works in ints, not size_t's - - // Time statistics - AdaptivePaddedAverage* _avg_major_pause; - - // Footprint statistics - AdaptiveWeightedAverage* _avg_base_footprint; - // Statistics for promoted objs AdaptivePaddedNoZeroDevAverage* _avg_promoted; - // Variable for estimating the major and minor pause times. - // These variables represent linear least-squares fits of - // the data. - // major pause time vs. old gen size - LinearLeastSquareFit* _major_pause_old_estimator; - // major pause time vs. young gen size - LinearLeastSquareFit* _major_pause_young_estimator; - - - // These record the most recent collection times. They - // are available as an alternative to using the averages - // for making ergonomic decisions. - double _latest_major_mutator_interval_seconds; - const size_t _space_alignment; // alignment for eden, survivors - // The amount of live data in the heap at the last full GC, used - // as a baseline to help us determine when we need to perform the - // next full GC. - size_t _live_at_last_full_gc; - - // decrease/increase the old generation for minor pause time - int _change_old_gen_for_min_pauses; - - // increase/decrease the young generation for major pause time - int _change_young_gen_for_maj_pauses; - // To facilitate faster growth at start up, supplement the normal // growth percentage for the young gen eden and the // old gen space for promotion with these value which decay // with increasing collections. uint _young_gen_size_increment_supplement; - uint _old_gen_size_increment_supplement; - private: + size_t decrease_eden_for_minor_pause_time(size_t current_eden_size); - void adjust_eden_for_minor_pause_time(size_t* desired_eden_size_ptr); - // Change the generation sizes to achieve a GC pause time goal - // Returned sizes are not necessarily aligned. - void adjust_promo_for_pause_time(size_t* desired_promo_size_ptr); - void adjust_eden_for_pause_time(size_t* desired_eden_size_ptr); - // Change the generation sizes to achieve an application throughput goal - // Returned sizes are not necessarily aligned. - void adjust_promo_for_throughput(bool is_full_gc, - size_t* desired_promo_size_ptr); - void adjust_eden_for_throughput(bool is_full_gc, - size_t* desired_eden_size_ptr); - // Change the generation sizes to achieve minimum footprint - // Returned sizes are not aligned. - size_t adjust_promo_for_footprint(size_t desired_promo_size, - size_t desired_total); - size_t adjust_eden_for_footprint(size_t desired_promo_size, - size_t desired_total); + size_t increase_eden(size_t current_eden_size); // Size in bytes for an increment or decrement of eden. size_t eden_decrement_aligned_down(size_t cur_eden); size_t eden_increment_with_supplement_aligned_up(size_t cur_eden); - // Size in bytes for an increment or decrement of the promotion area - size_t promo_decrement_aligned_down(size_t cur_promo); - size_t promo_increment_with_supplement_aligned_up(size_t cur_promo); - - // Returns a change that has been scaled down. Result - // is not aligned. (If useful, move to some shared - // location.) - size_t scale_down(size_t change, double part, double total); - - protected: - - // Footprint accessors - size_t live_space() const { - return (size_t)(avg_young_live()->average() + - avg_old_live()->average()); - } - size_t free_space() const { - return _eden_size + _promo_size; - } - - void set_promo_size(size_t new_size) { - _promo_size = new_size; - } - - // Update estimators - void update_minor_pause_old_estimator(double minor_pause_in_ms); - - virtual GCPolicyKind kind() const { return _gc_ps_adaptive_size_policy; } - - public: - // Accessors for use by performance counters AdaptivePaddedNoZeroDevAverage* avg_promoted() const { return _avg_promoted; } - AdaptiveWeightedAverage* avg_base_footprint() const { - return _avg_base_footprint; - } + public: - // Input arguments are initial free space sizes for young and old - // generations, the initial survivor space size, the - // alignment values and the pause & throughput goals. - // // NEEDS_CLEANUP this is a singleton object - PSAdaptiveSizePolicy(size_t init_eden_size, - size_t init_promo_size, - size_t init_survivor_size, - size_t space_alignment, + PSAdaptiveSizePolicy(size_t space_alignment, double gc_pause_goal_sec, uint gc_time_ratio); @@ -186,18 +68,11 @@ class PSAdaptiveSizePolicy : public AdaptiveSizePolicy { // called by GC algorithms. It is the responsibility of users of this // policy to call these methods at the correct times! void major_collection_begin(); - void major_collection_end(size_t amount_live, GCCause::Cause gc_cause); + void major_collection_end(); - void tenured_allocation(size_t size) { - _avg_pretenured->sample(size); - } + void print_stats(bool is_survivor_overflowing); // Accessors - // NEEDS_CLEANUP should use sizes.hpp - - static size_t calculate_free_based_on_live(size_t live, uintx ratio_as_percentage); - - size_t calculated_old_free_size_in_bytes() const; size_t average_promoted_in_bytes() const { return (size_t)avg_promoted()->average(); @@ -207,63 +82,14 @@ class PSAdaptiveSizePolicy : public AdaptiveSizePolicy { return (size_t)avg_promoted()->padded_average(); } - int change_young_gen_for_maj_pauses() { - return _change_young_gen_for_maj_pauses; - } - void set_change_young_gen_for_maj_pauses(int v) { - _change_young_gen_for_maj_pauses = v; - } + size_t compute_desired_eden_size(bool is_survivor_overflowing, size_t cur_eden); - int change_old_gen_for_min_pauses() { - return _change_old_gen_for_min_pauses; - } - void set_change_old_gen_for_min_pauses(int v) { - _change_old_gen_for_min_pauses = v; - } + size_t compute_desired_survivor_size(size_t current_survivor_size, size_t max_gen_size); - // Accessors for estimators. The slope of the linear fit is - // currently all that is used for making decisions. + size_t compute_old_gen_shrink_bytes(size_t old_gen_free_bytes, size_t max_shrink_bytes); - LinearLeastSquareFit* major_pause_old_estimator() { - return _major_pause_old_estimator; - } - - virtual void clear_generation_free_space_flags(); - - double major_pause_old_slope() { return _major_pause_old_estimator->slope(); } - double major_pause_young_slope() { - return _major_pause_young_estimator->slope(); - } - - // Calculates optimal (free) space sizes for both the young and old - // generations. Stores results in _eden_size and _promo_size. - // Takes current used space in all generations as input, as well - // as an indication if a full gc has just been performed, for use - // in deciding if an OOM error should be thrown. - void compute_generations_free_space(size_t young_live, - size_t eden_live, - size_t old_live, - size_t cur_eden, // current eden in bytes - size_t max_old_gen_size, - size_t max_eden_size, - bool is_full_gc); - - void compute_eden_space_size(size_t young_live, - size_t eden_live, - size_t cur_eden, // current eden in bytes - size_t max_eden_size, - bool is_full_gc); - - void compute_old_gen_free_space(size_t old_live, - size_t cur_eden, // current eden in bytes - size_t max_old_gen_size, - bool is_full_gc); - - // Calculates new survivor space size; returns a new tenuring threshold - // value. Stores new survivor size in _survivor_size. - uint compute_survivor_space_size_and_threshold(bool is_survivor_overflow, - uint tenuring_threshold, - size_t survivor_limit); + uint compute_tenuring_threshold(bool is_survivor_overflowing, + uint tenuring_threshold); // Return the maximum size of a survivor space if the young generation were of // size gen_size. @@ -279,21 +105,14 @@ class PSAdaptiveSizePolicy : public AdaptiveSizePolicy { return sz > alignment ? align_down(sz, alignment) : alignment; } - size_t live_at_last_full_gc() { - return _live_at_last_full_gc; - } - // Update averages that are always used (even // if adaptive sizing is turned off). void update_averages(bool is_survivor_overflow, size_t survived, size_t promoted); - // Printing support - virtual bool print() const; - // Decay the supplemental growth additive. - void decay_supplemental_growth(bool is_full_gc); + void decay_supplemental_growth(uint num_minor_gcs); }; #endif // SHARE_GC_PARALLEL_PSADAPTIVESIZEPOLICY_HPP diff --git a/src/hotspot/share/gc/parallel/psGCAdaptivePolicyCounters.cpp b/src/hotspot/share/gc/parallel/psGCAdaptivePolicyCounters.cpp deleted file mode 100644 index 561d6009d59..00000000000 --- a/src/hotspot/share/gc/parallel/psGCAdaptivePolicyCounters.cpp +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - * - */ - -#include "gc/parallel/psGCAdaptivePolicyCounters.hpp" -#include "memory/resourceArea.hpp" - -PSGCAdaptivePolicyCounters::PSGCAdaptivePolicyCounters(const char* name_arg, - int collectors, - int generations, - PSAdaptiveSizePolicy* size_policy_arg) - : GCAdaptivePolicyCounters(name_arg, - collectors, - generations, - size_policy_arg) { - if (UsePerfData) { - EXCEPTION_MARK; - ResourceMark rm; - - const char* cname; - - cname = PerfDataManager::counter_name(name_space(), "oldPromoSize"); - _old_promo_size = PerfDataManager::create_variable(SUN_GC, cname, - PerfData::U_Bytes, ps_size_policy()->calculated_promo_size_in_bytes(), CHECK); - - cname = PerfDataManager::counter_name(name_space(), "oldEdenSize"); - _old_eden_size = PerfDataManager::create_variable(SUN_GC, cname, - PerfData::U_Bytes, ps_size_policy()->calculated_eden_size_in_bytes(), CHECK); - - cname = PerfDataManager::counter_name(name_space(), "oldCapacity"); - _old_capacity = PerfDataManager::create_variable(SUN_GC, cname, - PerfData::U_Bytes, (jlong) InitialHeapSize, CHECK); - - cname = PerfDataManager::counter_name(name_space(), "avgPromotedAvg"); - _avg_promoted_avg_counter = - PerfDataManager::create_variable(SUN_GC, cname, PerfData::U_Bytes, - ps_size_policy()->calculated_promo_size_in_bytes(), CHECK); - - cname = PerfDataManager::counter_name(name_space(), "avgPromotedDev"); - _avg_promoted_dev_counter = - PerfDataManager::create_variable(SUN_GC, cname, PerfData::U_Bytes, - (jlong) 0 , CHECK); - - cname = PerfDataManager::counter_name(name_space(), "avgPromotedPaddedAvg"); - _avg_promoted_padded_avg_counter = - PerfDataManager::create_variable(SUN_GC, cname, PerfData::U_Bytes, - ps_size_policy()->calculated_promo_size_in_bytes(), CHECK); - - cname = PerfDataManager::counter_name(name_space(), - "avgPretenuredPaddedAvg"); - _avg_pretenured_padded_avg = - PerfDataManager::create_variable(SUN_GC, cname, PerfData::U_Bytes, - (jlong) 0, CHECK); - - - cname = PerfDataManager::counter_name(name_space(), - "changeYoungGenForMajPauses"); - _change_young_gen_for_maj_pauses_counter = - PerfDataManager::create_variable(SUN_GC, cname, PerfData::U_Events, - (jlong)0, CHECK); - - cname = PerfDataManager::counter_name(name_space(), - "changeOldGenForMinPauses"); - _change_old_gen_for_min_pauses = - PerfDataManager::create_variable(SUN_GC, cname, PerfData::U_Events, - (jlong)0, CHECK); - - - cname = PerfDataManager::counter_name(name_space(), "avgMajorPauseTime"); - _avg_major_pause = PerfDataManager::create_variable(SUN_GC, cname, - PerfData::U_Ticks, (jlong) ps_size_policy()->_avg_major_pause->average(), CHECK); - - cname = PerfDataManager::counter_name(name_space(), "avgMajorIntervalTime"); - _avg_major_interval = PerfDataManager::create_variable(SUN_GC, cname, - PerfData::U_Ticks, (jlong) ps_size_policy()->_avg_major_interval->average(), CHECK); - - cname = PerfDataManager::counter_name(name_space(), "majorGcCost"); - _major_gc_cost_counter = PerfDataManager::create_variable(SUN_GC, cname, - PerfData::U_Ticks, (jlong) ps_size_policy()->major_gc_cost(), CHECK); - - cname = PerfDataManager::counter_name(name_space(), "liveSpace"); - _live_space = PerfDataManager::create_variable(SUN_GC, cname, - PerfData::U_Bytes, ps_size_policy()->live_space(), CHECK); - - cname = PerfDataManager::counter_name(name_space(), "freeSpace"); - _free_space = PerfDataManager::create_variable(SUN_GC, cname, - PerfData::U_Bytes, ps_size_policy()->free_space(), CHECK); - - cname = PerfDataManager::counter_name(name_space(), "liveAtLastFullGc"); - _live_at_last_full_gc_counter = - PerfDataManager::create_variable(SUN_GC, cname, - PerfData::U_Bytes, ps_size_policy()->live_at_last_full_gc(), CHECK); - - cname = PerfDataManager::counter_name(name_space(), "majorPauseOldSlope"); - _major_pause_old_slope = PerfDataManager::create_variable(SUN_GC, cname, - PerfData::U_None, (jlong) 0, CHECK); - - cname = PerfDataManager::counter_name(name_space(), "minorPauseOldSlope"); - _minor_pause_old_slope = PerfDataManager::create_variable(SUN_GC, cname, - PerfData::U_None, (jlong) 0, CHECK); - - cname = PerfDataManager::counter_name(name_space(), "majorPauseYoungSlope"); - _major_pause_young_slope = PerfDataManager::create_variable(SUN_GC, cname, - PerfData::U_None, (jlong) 0, CHECK); - - _counter_time_stamp.update(); - } - - assert(size_policy()->is_gc_ps_adaptive_size_policy(), - "Wrong type of size policy"); -} - -void PSGCAdaptivePolicyCounters::update_counters_from_policy() { - if (UsePerfData) { - GCAdaptivePolicyCounters::update_counters_from_policy(); - update_eden_size(); - update_promo_size(); - update_avg_old_live(); - update_survivor_size_counters(); - update_avg_promoted_avg(); - update_avg_promoted_dev(); - update_avg_promoted_padded_avg(); - update_avg_pretenured_padded_avg(); - - update_avg_major_pause(); - update_avg_major_interval(); - update_minor_gc_cost_counter(); - update_major_gc_cost_counter(); - update_mutator_cost_counter(); - update_decrement_tenuring_threshold_for_gc_cost(); - update_increment_tenuring_threshold_for_gc_cost(); - update_decrement_tenuring_threshold_for_survivor_limit(); - update_live_space(); - update_free_space(); - - update_change_old_gen_for_maj_pauses(); - update_change_young_gen_for_maj_pauses(); - update_change_old_gen_for_min_pauses(); - - update_change_old_gen_for_throughput(); - update_change_young_gen_for_throughput(); - - update_decrease_for_footprint(); - update_decide_at_full_gc_counter(); - - update_major_pause_old_slope(); - update_minor_pause_old_slope(); - update_major_pause_young_slope(); - update_minor_collection_slope_counter(); - update_gc_overhead_limit_exceeded_counter(); - update_live_at_last_full_gc_counter(); - } -} - -void PSGCAdaptivePolicyCounters::update_counters() { - if (UsePerfData) { - update_counters_from_policy(); - } -} diff --git a/src/hotspot/share/gc/parallel/psGCAdaptivePolicyCounters.hpp b/src/hotspot/share/gc/parallel/psGCAdaptivePolicyCounters.hpp deleted file mode 100644 index 217cd7b9c5c..00000000000 --- a/src/hotspot/share/gc/parallel/psGCAdaptivePolicyCounters.hpp +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright (c) 2003, 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. - * - */ - -#ifndef SHARE_GC_PARALLEL_PSGCADAPTIVEPOLICYCOUNTERS_HPP -#define SHARE_GC_PARALLEL_PSGCADAPTIVEPOLICYCOUNTERS_HPP - -#include "gc/parallel/gcAdaptivePolicyCounters.hpp" -#include "gc/parallel/psAdaptiveSizePolicy.hpp" -#include "gc/shared/gcPolicyCounters.hpp" - -// PSGCAdaptivePolicyCounters is a holder class for performance counters -// that track the data and decisions for the ergonomics policy for the -// parallel scavenge collector. - -class PSGCAdaptivePolicyCounters : public GCAdaptivePolicyCounters { - friend class VMStructs; - - private: - // survivor space vs. tenuring threshold - PerfVariable* _old_promo_size; - PerfVariable* _old_eden_size; - PerfVariable* _avg_promoted_avg_counter; - PerfVariable* _avg_promoted_dev_counter; - PerfVariable* _avg_promoted_padded_avg_counter; - PerfVariable* _avg_pretenured_padded_avg; - - // young gen vs. old gen sizing - PerfVariable* _avg_major_pause; - PerfVariable* _avg_major_interval; - PerfVariable* _live_space; - PerfVariable* _free_space; - PerfVariable* _live_at_last_full_gc_counter; - PerfVariable* _old_capacity; - - PerfVariable* _change_old_gen_for_min_pauses; - PerfVariable* _change_young_gen_for_maj_pauses_counter; - - PerfVariable* _major_pause_old_slope; - PerfVariable* _minor_pause_old_slope; - PerfVariable* _major_pause_young_slope; - - // Use this time stamp if the gc time stamp is not available. - TimeStamp _counter_time_stamp; - - protected: - PSAdaptiveSizePolicy* ps_size_policy() { - return (PSAdaptiveSizePolicy*)_size_policy; - } - - public: - PSGCAdaptivePolicyCounters(const char* name, int collectors, int generations, - PSAdaptiveSizePolicy* size_policy); - inline void update_old_capacity(size_t size_in_bytes) { - _old_capacity->set_value(size_in_bytes); - } - inline void update_old_eden_size(size_t old_size) { - _old_eden_size->set_value(old_size); - } - inline void update_old_promo_size(size_t old_size) { - _old_promo_size->set_value(old_size); - } - inline void update_avg_promoted_avg() { - _avg_promoted_avg_counter->set_value( - (jlong)(ps_size_policy()->avg_promoted()->average()) - ); - } - inline void update_avg_promoted_dev() { - _avg_promoted_dev_counter->set_value( - (jlong)(ps_size_policy()->avg_promoted()->deviation()) - ); - } - inline void update_avg_promoted_padded_avg() { - _avg_promoted_padded_avg_counter->set_value( - (jlong)(ps_size_policy()->avg_promoted()->padded_average()) - ); - } - - inline void update_avg_pretenured_padded_avg() { - _avg_pretenured_padded_avg->set_value( - (jlong)(ps_size_policy()->_avg_pretenured->padded_average()) - ); - } - inline void update_change_young_gen_for_maj_pauses() { - _change_young_gen_for_maj_pauses_counter->set_value( - ps_size_policy()->change_young_gen_for_maj_pauses()); - } - inline void update_change_old_gen_for_min_pauses() { - _change_old_gen_for_min_pauses->set_value( - ps_size_policy()->change_old_gen_for_min_pauses()); - } - - // compute_generations_free_space() statistics - - inline void update_avg_major_pause() { - _avg_major_pause->set_value( - (jlong)(ps_size_policy()->_avg_major_pause->average() * 1000.0) - ); - } - inline void update_avg_major_interval() { - _avg_major_interval->set_value( - (jlong)(ps_size_policy()->_avg_major_interval->average() * 1000.0) - ); - } - - inline void update_major_gc_cost_counter() { - _major_gc_cost_counter->set_value( - (jlong)(ps_size_policy()->major_gc_cost() * 100.0) - ); - } - inline void update_mutator_cost_counter() { - _mutator_cost_counter->set_value( - (jlong)(ps_size_policy()->mutator_cost() * 100.0) - ); - } - - inline void update_live_space() { - _live_space->set_value(ps_size_policy()->live_space()); - } - inline void update_free_space() { - _free_space->set_value(ps_size_policy()->free_space()); - } - - inline void update_avg_old_live() { - _avg_old_live_counter->set_value( - (jlong)(ps_size_policy()->avg_old_live()->average()) - ); - } - // Scale up all the slopes - inline void update_major_pause_old_slope() { - _major_pause_old_slope->set_value( - (jlong)(ps_size_policy()->major_pause_old_slope() * 1000) - ); - } - inline void update_minor_pause_old_slope() { - _minor_pause_old_slope->set_value( - (jlong)(ps_size_policy()->minor_pause_old_slope() * 1000) - ); - } - inline void update_major_pause_young_slope() { - _major_pause_young_slope->set_value( - (jlong)(ps_size_policy()->major_pause_young_slope() * 1000) - ); - } - inline void update_gc_overhead_limit_exceeded_counter() { - gc_overhead_limit_exceeded_counter()->set_value( - (jlong) ps_size_policy()->gc_overhead_limit_exceeded()); - } - inline void update_live_at_last_full_gc_counter() { - _live_at_last_full_gc_counter->set_value( - (jlong)(ps_size_policy()->live_at_last_full_gc())); - } - - // Update all the counters that can be updated from the size policy. - // This should be called after all policy changes have been made - // and reflected internally in the size policy. - void update_counters_from_policy(); - - // Update counters that can be updated from fields internal to the - // counter or from globals. This is distinguished from counters - // that are updated via input parameters. - void update_counters(); -}; - -#endif // SHARE_GC_PARALLEL_PSGCADAPTIVEPOLICYCOUNTERS_HPP diff --git a/src/hotspot/share/gc/parallel/psOldGen.cpp b/src/hotspot/share/gc/parallel/psOldGen.cpp index 44f8f6789f1..89f22b72b69 100644 --- a/src/hotspot/share/gc/parallel/psOldGen.cpp +++ b/src/hotspot/share/gc/parallel/psOldGen.cpp @@ -175,6 +175,21 @@ bool PSOldGen::expand_for_allocate(size_t word_size) { return result; } +void PSOldGen::try_expand_till_size(size_t target_capacity_bytes) { + if (target_capacity_bytes <= capacity_in_bytes()) { + // Current capacity is enough + return; + } + + if (capacity_in_bytes() == max_gen_size()) { + // Already at max size + return; + } + + size_t to_expand_bytes = target_capacity_bytes - capacity_in_bytes(); + expand(to_expand_bytes); +} + bool PSOldGen::expand(size_t bytes) { #ifdef ASSERT if (!Thread::current()->is_VM_thread()) { @@ -281,14 +296,10 @@ void PSOldGen::complete_loaded_archive_space(MemRegion archive_space) { } } -void PSOldGen::resize(size_t desired_free_space) { +void PSOldGen::resize(size_t desired_capacity) { const size_t alignment = virtual_space()->alignment(); const size_t size_before = virtual_space()->committed_size(); - size_t new_size = used_in_bytes() + desired_free_space; - if (new_size < used_in_bytes()) { - // Overflowed the addition. - new_size = max_gen_size(); - } + size_t new_size = desired_capacity; // Adjust according to our min and max new_size = clamp(new_size, min_gen_size(), max_gen_size()); @@ -297,10 +308,10 @@ void PSOldGen::resize(size_t desired_free_space) { const size_t current_size = capacity_in_bytes(); log_trace(gc, ergo)("AdaptiveSizePolicy::old generation size: " - "desired free: %zu used: %zu" - " new size: %zu current size %zu" + "used: %zu" + " capacity %zu -> %zu" " gen limits: %zu / %zu", - desired_free_space, used_in_bytes(), new_size, current_size, + used_in_bytes(), current_size, new_size, max_gen_size(), min_gen_size()); if (new_size == current_size) { diff --git a/src/hotspot/share/gc/parallel/psOldGen.hpp b/src/hotspot/share/gc/parallel/psOldGen.hpp index c1bfe2f1972..23fde1f2fe0 100644 --- a/src/hotspot/share/gc/parallel/psOldGen.hpp +++ b/src/hotspot/share/gc/parallel/psOldGen.hpp @@ -52,22 +52,11 @@ class PSOldGen : public CHeapObj { // Block size for parallel iteration static const size_t IterateBlockSize = 1024 * 1024; - HeapWord* cas_allocate_noexpand(size_t word_size) { - assert_locked_or_safepoint(Heap_lock); - HeapWord* res = object_space()->cas_allocate(word_size); - if (res != nullptr) { - _start_array->update_for_block(res, res + word_size); - } - return res; - } - bool expand_for_allocate(size_t word_size); bool expand(size_t bytes); bool expand_by(size_t bytes); bool expand_to_reserved(); - void shrink(size_t bytes); - void post_resize(); void initialize(ReservedSpace rs, size_t initial_size, size_t alignment); @@ -93,6 +82,8 @@ class PSOldGen : public CHeapObj { size_t max_gen_size() const { return _max_gen_size; } size_t min_gen_size() const { return _min_gen_size; } + void try_expand_till_size(size_t live_bytes); + bool is_in(const void* p) const { return _virtual_space->is_in_committed((void *)p); } @@ -108,11 +99,14 @@ class PSOldGen : public CHeapObj { // Size info size_t capacity_in_bytes() const { return object_space()->capacity_in_bytes(); } size_t used_in_bytes() const { return object_space()->used_in_bytes(); } + size_t free_in_bytes() const { return object_space()->free_in_bytes(); } void complete_loaded_archive_space(MemRegion archive_space); // Calculating new sizes - void resize(size_t desired_free_space); + void resize(size_t desired_capacity); + + void shrink(size_t bytes); // Invoked by mutators and GC-workers. HeapWord* allocate(size_t word_size) { @@ -124,6 +118,16 @@ class PSOldGen : public CHeapObj { return res; } + // Invoked by mutators before attempting GC. + HeapWord* cas_allocate_noexpand(size_t word_size) { + assert_locked_or_safepoint(Heap_lock); + HeapWord* res = object_space()->cas_allocate(word_size); + if (res != nullptr) { + _start_array->update_for_block(res, res + word_size); + } + return res; + } + // Invoked by VM thread inside a safepoint. HeapWord* expand_and_allocate(size_t word_size); diff --git a/src/hotspot/share/gc/parallel/psParallelCompact.cpp b/src/hotspot/share/gc/parallel/psParallelCompact.cpp index 303951ba469..d672b2b690c 100644 --- a/src/hotspot/share/gc/parallel/psParallelCompact.cpp +++ b/src/hotspot/share/gc/parallel/psParallelCompact.cpp @@ -659,7 +659,6 @@ void PSParallelCompact::pre_compact() _space_info[from_space_id].set_space(heap->young_gen()->from_space()); _space_info[to_space_id].set_space(heap->young_gen()->to_space()); - // Increment the invocation count heap->increment_total_collections(true); CodeCache::on_gc_marking_cycle_start(); @@ -834,8 +833,8 @@ bool PSParallelCompact::check_maximum_compaction(size_t total_live_words, bool is_max_on_system_gc = UseMaximumCompactionOnSystemGC && GCCause::is_user_requested_gc(heap->gc_cause()); - // Check if all live objs are larger than old-gen. - const bool is_old_gen_overflowing = (total_live_words > old_space->capacity_in_words()); + // Check if all live objs are too much for old-gen. + const bool is_old_gen_too_full = (total_live_words >= old_space->capacity_in_words()); // JVM flags const uint total_invocations = heap->total_full_collections(); @@ -847,7 +846,7 @@ bool PSParallelCompact::check_maximum_compaction(size_t total_live_words, const bool is_region_full = full_region_prefix_end >= _summary_data.region_align_down(old_space->top()); - if (is_max_on_system_gc || is_old_gen_overflowing || is_interval_ended || is_region_full) { + if (is_max_on_system_gc || is_old_gen_too_full || is_interval_ended || is_region_full) { _maximum_compaction_gc_num = total_invocations; return true; } @@ -881,6 +880,14 @@ void PSParallelCompact::summary_phase() bool maximum_compaction = check_maximum_compaction(total_live_words, old_space, full_region_prefix_end); + { + GCTraceTime(Info, gc, phases) tm("Summary Phase: expand", &_gc_timer); + // Try to expand old-gen in order to fit all live objs and waste. + size_t target_capacity_bytes = total_live_words * HeapWordSize + + old_space->capacity_in_bytes() * (MarkSweepDeadRatio / 100); + ParallelScavengeHeap::heap()->old_gen()->try_expand_till_size(target_capacity_bytes); + } + HeapWord* dense_prefix_end = maximum_compaction ? full_region_prefix_end : compute_dense_prefix_for_old_space(old_space, @@ -991,7 +998,6 @@ bool PSParallelCompact::invoke_no_policy(bool clear_all_soft_refs) { _gc_tracer.report_gc_start(heap->gc_cause(), _gc_timer.gc_start()); GCCause::Cause gc_cause = heap->gc_cause(); - PSYoungGen* young_gen = heap->young_gen(); PSOldGen* old_gen = heap->old_gen(); PSAdaptiveSizePolicy* size_policy = heap->size_policy(); @@ -1057,78 +1063,12 @@ bool PSParallelCompact::invoke_no_policy(bool clear_all_soft_refs) { // done before resizing. post_compact(); - // Let the size policy know we're done - size_policy->major_collection_end(old_gen->used_in_bytes(), gc_cause); + size_policy->major_collection_end(); + + size_policy->sample_old_gen_used_bytes(MAX2(pre_gc_values.old_gen_used(), old_gen->used_in_bytes())); if (UseAdaptiveSizePolicy) { - log_debug(gc, ergo)("AdaptiveSizeStart: collection: %d ", heap->total_collections()); - log_trace(gc, ergo)("old_gen_capacity: %zu young_gen_capacity: %zu", - old_gen->capacity_in_bytes(), young_gen->capacity_in_bytes()); - - // Don't check if the size_policy is ready here. Let - // the size_policy check that internally. - if (UseAdaptiveGenerationSizePolicyAtMajorCollection && - AdaptiveSizePolicy::should_update_promo_stats(gc_cause)) { - // Swap the survivor spaces if from_space is empty. The - // resize_young_gen() called below is normally used after - // a successful young GC and swapping of survivor spaces; - // otherwise, it will fail to resize the young gen with - // the current implementation. - if (young_gen->from_space()->is_empty()) { - young_gen->from_space()->clear(SpaceDecorator::Mangle); - young_gen->swap_spaces(); - } - - // Calculate optimal free space amounts - assert(young_gen->max_gen_size() > - young_gen->from_space()->capacity_in_bytes() + - young_gen->to_space()->capacity_in_bytes(), - "Sizes of space in young gen are out-of-bounds"); - - size_t young_live = young_gen->used_in_bytes(); - size_t eden_live = young_gen->eden_space()->used_in_bytes(); - size_t old_live = old_gen->used_in_bytes(); - size_t cur_eden = young_gen->eden_space()->capacity_in_bytes(); - size_t max_old_gen_size = old_gen->max_gen_size(); - size_t max_eden_size = young_gen->max_gen_size() - - young_gen->from_space()->capacity_in_bytes() - - young_gen->to_space()->capacity_in_bytes(); - - // Used for diagnostics - size_policy->clear_generation_free_space_flags(); - - size_policy->compute_generations_free_space(young_live, - eden_live, - old_live, - cur_eden, - max_old_gen_size, - max_eden_size, - true /* full gc*/); - - size_policy->check_gc_overhead_limit(eden_live, - max_old_gen_size, - max_eden_size, - true /* full gc*/, - gc_cause, - heap->soft_ref_policy()); - - size_policy->decay_supplemental_growth(true /* full gc*/); - - heap->resize_old_gen( - size_policy->calculated_old_free_size_in_bytes()); - - heap->resize_young_gen(size_policy->calculated_eden_size_in_bytes(), - size_policy->calculated_survivor_size_in_bytes()); - } - - log_debug(gc, ergo)("AdaptiveSizeStop: collection: %d ", heap->total_collections()); - } - - if (UsePerfData) { - PSGCAdaptivePolicyCounters* const counters = heap->gc_policy_counters(); - counters->update_counters(); - counters->update_old_capacity(old_gen->capacity_in_bytes()); - counters->update_young_capacity(young_gen->capacity_in_bytes()); + heap->resize_after_full_gc(); } heap->resize_all_tlabs(); @@ -1147,8 +1087,12 @@ bool PSParallelCompact::invoke_no_policy(bool clear_all_soft_refs) { heap->update_counters(); heap->post_full_gc_dump(&_gc_timer); + + size_policy->record_gc_pause_end_instant(); } + heap->gc_epilogue(true); + if (VerifyAfterGC && heap->total_collections() >= VerifyGCStartAt) { Universe::verify("After GC"); } @@ -1156,8 +1100,6 @@ bool PSParallelCompact::invoke_no_policy(bool clear_all_soft_refs) { heap->print_after_gc(); heap->trace_heap_after_gc(&_gc_tracer); - AdaptiveSizePolicyOutput::print(size_policy, heap->total_collections()); - _gc_timer.register_gc_end(); _gc_tracer.report_dense_prefix(dense_prefix(old_space_id)); @@ -1583,11 +1525,11 @@ void PSParallelCompact::forward_to_new_addr() { HeapWord* top = sp->top(); if (dense_prefix_addr == top) { + // Empty space continue; } const SplitInfo& split_info = _space_info[SpaceId(id)].split_info(); - size_t dense_prefix_region = _summary_data.addr_to_region_idx(dense_prefix_addr); size_t top_region = _summary_data.addr_to_region_idx(_summary_data.region_align_up(top)); size_t start_region; @@ -1631,7 +1573,7 @@ void PSParallelCompact::forward_to_new_addr() { #ifdef ASSERT void PSParallelCompact::verify_forward() { - HeapWord* old_dense_prefix_addr = dense_prefix(SpaceId(old_space_id)); + HeapWord* const old_dense_prefix_addr = dense_prefix(SpaceId(old_space_id)); RegionData* old_region = _summary_data.region(_summary_data.addr_to_region_idx(old_dense_prefix_addr)); HeapWord* bump_ptr = old_region->partial_obj_size() != 0 ? old_dense_prefix_addr + old_region->partial_obj_size() @@ -1724,7 +1666,7 @@ void PSParallelCompact::prepare_region_draining_tasks(uint parallel_gc_threads) // id + 1 is used to test termination so unsigned can // be used with an old_space_id == 0. FillableRegionLogger region_logger; - for (unsigned int id = to_space_id; id + 1 > old_space_id; --id) { + for (unsigned int id = last_space_id - 1; id + 1 > old_space_id; --id) { SpaceInfo* const space_info = _space_info + id; HeapWord* const new_top = space_info->new_top(); diff --git a/src/hotspot/share/gc/parallel/psParallelCompact.hpp b/src/hotspot/share/gc/parallel/psParallelCompact.hpp index 290dd809ddd..0584fc64d73 100644 --- a/src/hotspot/share/gc/parallel/psParallelCompact.hpp +++ b/src/hotspot/share/gc/parallel/psParallelCompact.hpp @@ -683,12 +683,15 @@ public: // Convenient access to type names. typedef ParallelCompactData::RegionData RegionData; + // By the end of full-gc, all live objs are compacted into the first three spaces, old, eden, and from. typedef enum { - old_space_id, eden_space_id, - from_space_id, to_space_id, last_space_id + old_space_id, + eden_space_id, + from_space_id, + to_space_id, + last_space_id } SpaceId; -public: // Inline closure decls // class IsAliveClosure: public BoolObjectClosure { diff --git a/src/hotspot/share/gc/parallel/psPromotionManager.cpp b/src/hotspot/share/gc/parallel/psPromotionManager.cpp index d27525c15b2..0a463ab7516 100644 --- a/src/hotspot/share/gc/parallel/psPromotionManager.cpp +++ b/src/hotspot/share/gc/parallel/psPromotionManager.cpp @@ -193,6 +193,7 @@ void PSPromotionManager::reset() { // Do not prefill the LAB's, save heap wastage! HeapWord* lab_base = young_space()->top(); _young_lab.initialize(MemRegion(lab_base, (size_t)0)); + _young_gen_has_alloc_failure = false; _young_gen_is_full = false; lab_base = old_gen()->object_space()->top(); @@ -251,7 +252,7 @@ void PSPromotionManager::flush_labs() { _old_lab.flush(); // Let PSScavenge know if we overflowed - if (_young_gen_is_full) { + if (_young_gen_is_full || _young_gen_has_alloc_failure) { PSScavenge::set_survivor_overflow(true); } } diff --git a/src/hotspot/share/gc/parallel/psPromotionManager.hpp b/src/hotspot/share/gc/parallel/psPromotionManager.hpp index 9397ad52a9b..78bb7dde66a 100644 --- a/src/hotspot/share/gc/parallel/psPromotionManager.hpp +++ b/src/hotspot/share/gc/parallel/psPromotionManager.hpp @@ -74,6 +74,7 @@ class PSPromotionManager { PSYoungPromotionLAB _young_lab; PSOldPromotionLAB _old_lab; + bool _young_gen_has_alloc_failure; bool _young_gen_is_full; bool _old_gen_is_full; diff --git a/src/hotspot/share/gc/parallel/psPromotionManager.inline.hpp b/src/hotspot/share/gc/parallel/psPromotionManager.inline.hpp index 4f3d135c919..4946b0fde82 100644 --- a/src/hotspot/share/gc/parallel/psPromotionManager.inline.hpp +++ b/src/hotspot/share/gc/parallel/psPromotionManager.inline.hpp @@ -207,6 +207,9 @@ inline oop PSPromotionManager::copy_unmarked_to_survivor_space(oop o, _young_gen_is_full = true; } } + if (new_obj == nullptr && !_young_gen_is_full && !_young_gen_has_alloc_failure) { + _young_gen_has_alloc_failure = true; + } } } } diff --git a/src/hotspot/share/gc/parallel/psScavenge.cpp b/src/hotspot/share/gc/parallel/psScavenge.cpp index 2c678648c70..3a47d5864f3 100644 --- a/src/hotspot/share/gc/parallel/psScavenge.cpp +++ b/src/hotspot/share/gc/parallel/psScavenge.cpp @@ -322,6 +322,7 @@ bool PSScavenge::invoke(bool clear_soft_refs) { // Check for potential problems. if (!should_attempt_scavenge()) { + log_info(gc, ergo)("Young-gc might fail so skipping"); return false; } @@ -347,10 +348,8 @@ bool PSScavenge::invoke(bool clear_soft_refs) { heap->increment_total_collections(); - if (AdaptiveSizePolicy::should_update_eden_stats(gc_cause)) { - // Gather the feedback data for eden occupancy. - young_gen->eden_space()->accumulate_statistics(); - } + // Gather the feedback data for eden occupancy. + young_gen->eden_space()->accumulate_statistics(); heap->print_before_gc(); heap->trace_heap_before_gc(&_gc_tracer); @@ -423,7 +422,7 @@ bool PSScavenge::invoke(bool clear_soft_refs) { { GCTraceTime(Debug, gc, phases) tm("Weak Processing", &_gc_timer); PSAdjustWeakRootsClosure root_closure; - WeakProcessor::weak_oops_do(&ParallelScavengeHeap::heap()->workers(), &_is_alive_closure, &root_closure, 1); + WeakProcessor::weak_oops_do(&heap->workers(), &_is_alive_closure, &root_closure, 1); } // Finally, flush the promotion_manager's labs, and deallocate its stacks. @@ -435,10 +434,9 @@ bool PSScavenge::invoke(bool clear_soft_refs) { _gc_tracer.report_tenuring_threshold(tenuring_threshold()); - // Let the size policy know we're done. Note that we count promotion - // failure cleanup time as part of the collection (otherwise, we're - // implicitly saying it's mutator time). - size_policy->minor_collection_end(gc_cause); + // This is an underestimate, since it excludes time on auto-resizing. The + // most expensive part in auto-resizing is commit/uncommit OS API calls. + size_policy->minor_collection_end(young_gen->eden_space()->capacity_in_bytes()); if (!promotion_failure_occurred) { // Swap the survivor spaces. @@ -447,111 +445,35 @@ bool PSScavenge::invoke(bool clear_soft_refs) { young_gen->swap_spaces(); size_t survived = young_gen->from_space()->used_in_bytes(); + assert(old_gen->used_in_bytes() >= pre_gc_values.old_gen_used(), "inv"); size_t promoted = old_gen->used_in_bytes() - pre_gc_values.old_gen_used(); size_policy->update_averages(_survivor_overflow, survived, promoted); + size_policy->sample_old_gen_used_bytes(old_gen->used_in_bytes()); - // A successful scavenge should restart the GC time limit count which is - // for full GC's. - size_policy->reset_gc_overhead_limit_count(); if (UseAdaptiveSizePolicy) { - // Calculate the new survivor size and tenuring threshold + _tenuring_threshold = size_policy->compute_tenuring_threshold(_survivor_overflow, + _tenuring_threshold); - log_debug(gc, ergo)("AdaptiveSizeStart: collection: %d ", heap->total_collections()); - log_trace(gc, ergo)("old_gen_capacity: %zu young_gen_capacity: %zu", - old_gen->capacity_in_bytes(), young_gen->capacity_in_bytes()); + log_debug(gc, age)("New threshold %u (max threshold %u)", _tenuring_threshold, MaxTenuringThreshold); + + if (young_gen->is_from_to_layout()) { + size_policy->print_stats(_survivor_overflow); + heap->resize_after_young_gc(_survivor_overflow); + } if (UsePerfData) { - PSGCAdaptivePolicyCounters* counters = heap->gc_policy_counters(); - counters->update_old_eden_size( - size_policy->calculated_eden_size_in_bytes()); - counters->update_old_promo_size( - size_policy->calculated_promo_size_in_bytes()); - counters->update_old_capacity(old_gen->capacity_in_bytes()); - counters->update_young_capacity(young_gen->capacity_in_bytes()); - counters->update_survived(survived); - counters->update_promoted(promoted); - counters->update_survivor_overflowed(_survivor_overflow); + GCPolicyCounters* counters = ParallelScavengeHeap::gc_policy_counters(); + counters->tenuring_threshold()->set_value(_tenuring_threshold); + counters->desired_survivor_size()->set_value(young_gen->from_space()->capacity_in_bytes()); } - size_t max_young_size = young_gen->max_gen_size(); - - // Deciding a free ratio in the young generation is tricky, so if - // MinHeapFreeRatio or MaxHeapFreeRatio are in use (implicating - // that the old generation size may have been limited because of them) we - // should then limit our young generation size using NewRatio to have it - // follow the old generation size. - if (MinHeapFreeRatio != 0 || MaxHeapFreeRatio != 100) { - max_young_size = MIN2(old_gen->capacity_in_bytes() / NewRatio, - young_gen->max_gen_size()); + { + // In case the counter overflows + uint num_minor_gcs = heap->total_collections() > heap->total_full_collections() + ? heap->total_collections() - heap->total_full_collections() + : 1; + size_policy->decay_supplemental_growth(num_minor_gcs); } - - size_t survivor_limit = - size_policy->max_survivor_size(max_young_size); - _tenuring_threshold = - size_policy->compute_survivor_space_size_and_threshold(_survivor_overflow, - _tenuring_threshold, - survivor_limit); - - log_debug(gc, age)("Desired survivor size %zu bytes, new threshold %u (max threshold %u)", - size_policy->calculated_survivor_size_in_bytes(), - _tenuring_threshold, MaxTenuringThreshold); - - if (UsePerfData) { - PSGCAdaptivePolicyCounters* counters = heap->gc_policy_counters(); - counters->update_tenuring_threshold(_tenuring_threshold); - counters->update_survivor_size_counters(); - } - - // Do call at minor collections? - // Don't check if the size_policy is ready at this - // level. Let the size_policy check that internally. - if (UseAdaptiveGenerationSizePolicyAtMinorCollection && - AdaptiveSizePolicy::should_update_eden_stats(gc_cause)) { - // Calculate optimal free space amounts - assert(young_gen->max_gen_size() > - young_gen->from_space()->capacity_in_bytes() + - young_gen->to_space()->capacity_in_bytes(), - "Sizes of space in young gen are out-of-bounds"); - - size_t young_live = young_gen->used_in_bytes(); - size_t eden_live = young_gen->eden_space()->used_in_bytes(); - size_t cur_eden = young_gen->eden_space()->capacity_in_bytes(); - size_t max_old_gen_size = old_gen->max_gen_size(); - size_t max_eden_size = max_young_size - - young_gen->from_space()->capacity_in_bytes() - - young_gen->to_space()->capacity_in_bytes(); - - // Used for diagnostics - size_policy->clear_generation_free_space_flags(); - - size_policy->compute_eden_space_size(young_live, - eden_live, - cur_eden, - max_eden_size, - false /* not full gc*/); - - size_policy->check_gc_overhead_limit(eden_live, - max_old_gen_size, - max_eden_size, - false /* not full gc*/, - gc_cause, - heap->soft_ref_policy()); - - size_policy->decay_supplemental_growth(false /* not full gc*/); - } - // Resize the young generation at every collection - // even if new sizes have not been calculated. This is - // to allow resizes that may have been inhibited by the - // relative location of the "to" and "from" spaces. - - // Resizing the old gen at young collections can cause increases - // that don't feed back to the generation sizing policy until - // a full collection. Don't resize the old gen here. - - heap->resize_young_gen(size_policy->calculated_eden_size_in_bytes(), - size_policy->calculated_survivor_size_in_bytes()); - - log_debug(gc, ergo)("AdaptiveSizeStop: collection: %d ", heap->total_collections()); } // Update the structure of the eden. With NUMA-eden CPU hotplugging or offlining can @@ -560,17 +482,19 @@ bool PSScavenge::invoke(bool clear_soft_refs) { assert(young_gen->eden_space()->is_empty(), "eden space should be empty now"); young_gen->eden_space()->update(); - heap->gc_policy_counters()->update_counters(); - heap->resize_all_tlabs(); assert(young_gen->to_space()->is_empty(), "to space should be empty now"); + + heap->gc_epilogue(false); } #if COMPILER2_OR_JVMCI DerivedPointerTable::update_pointers(); #endif + size_policy->record_gc_pause_end_instant(); + if (log_is_enabled(Debug, gc, heap, exit)) { accumulated_time()->stop(); } @@ -589,8 +513,6 @@ bool PSScavenge::invoke(bool clear_soft_refs) { heap->print_after_gc(); heap->trace_heap_after_gc(&_gc_tracer); - AdaptiveSizePolicyOutput::print(size_policy, heap->total_collections()); - _gc_timer.register_gc_end(); _gc_tracer.report_gc_end(_gc_timer.gc_end(), _gc_timer.time_partitions()); @@ -612,7 +534,7 @@ bool PSScavenge::should_attempt_scavenge() { PSOldGen* old_gen = heap->old_gen(); if (!young_gen->to_space()->is_empty()) { - // To-space is not empty; should run full-gc instead. + log_debug(gc, ergo)("To-space is not empty; should run full-gc instead."); return false; } @@ -625,7 +547,7 @@ bool PSScavenge::should_attempt_scavenge() { size_t free_in_old_gen = old_gen->max_gen_size() - old_gen->used_in_bytes(); bool result = promotion_estimate < free_in_old_gen; - log_trace(ergo)("%s scavenge: average_promoted %zu padded_average_promoted %zu free in old gen %zu", + log_trace(gc, ergo)("%s scavenge: average_promoted %zu padded_average_promoted %zu free in old gen %zu", result ? "Do" : "Skip", (size_t) policy->average_promoted_in_bytes(), (size_t) policy->padded_average_promoted_in_bytes(), free_in_old_gen); @@ -659,9 +581,9 @@ void PSScavenge::initialize() { PSOldGen* old_gen = heap->old_gen(); // Set boundary between young_gen and old_gen - assert(old_gen->reserved().end() <= young_gen->eden_space()->bottom(), + assert(old_gen->reserved().end() == young_gen->reserved().start(), "old above young"); - set_young_generation_boundary(young_gen->eden_space()->bottom()); + set_young_generation_boundary(young_gen->reserved().start()); // Initialize ref handling object for scavenging. _span_based_discoverer.set_span(young_gen->reserved()); diff --git a/src/hotspot/share/gc/parallel/psVirtualspace.cpp b/src/hotspot/share/gc/parallel/psVirtualspace.cpp index 8b98c0d096e..3be90b370d1 100644 --- a/src/hotspot/share/gc/parallel/psVirtualspace.cpp +++ b/src/hotspot/share/gc/parallel/psVirtualspace.cpp @@ -23,6 +23,7 @@ */ #include "gc/parallel/psVirtualspace.hpp" +#include "logging/log.hpp" #include "memory/reservedSpace.hpp" #include "runtime/os.hpp" #include "utilities/align.hpp" @@ -61,6 +62,8 @@ bool PSVirtualSpace::expand_by(size_t bytes) { os::commit_memory(base_addr, bytes, alignment(), !ExecMem); if (result) { _committed_high_addr += bytes; + } else { + log_warning(gc)("PSVirtualSpace::expand_by: to commit %zu bytes failed", bytes); } return result; diff --git a/src/hotspot/share/gc/parallel/psYoungGen.cpp b/src/hotspot/share/gc/parallel/psYoungGen.cpp index 4024109a4a0..21d023f8207 100644 --- a/src/hotspot/share/gc/parallel/psYoungGen.cpp +++ b/src/hotspot/share/gc/parallel/psYoungGen.cpp @@ -171,21 +171,22 @@ void PSYoungGen::set_space_boundaries(size_t eden_size, size_t survivor_size) { assert(eden_size < virtual_space()->committed_size(), "just checking"); assert(eden_size > 0 && survivor_size > 0, "just checking"); - // Initial layout is Eden, to, from. After swapping survivor spaces, - // that leaves us with Eden, from, to, which is step one in our two - // step resize-with-live-data procedure. - char *eden_start = virtual_space()->low(); - char *to_start = eden_start + eden_size; - char *from_start = to_start + survivor_size; + // Layout: to, from, eden + char *to_start = virtual_space()->low(); + char *to_end = to_start + survivor_size; + char *from_start = to_end; char *from_end = from_start + survivor_size; + char *eden_start = from_end; + char *eden_end = eden_start + eden_size; + + assert(eden_end == virtual_space()->high(), "just checking"); - assert(from_end == virtual_space()->high(), "just checking"); assert(is_object_aligned(eden_start), "checking alignment"); assert(is_object_aligned(to_start), "checking alignment"); assert(is_object_aligned(from_start), "checking alignment"); - MemRegion eden_mr((HeapWord*)eden_start, (HeapWord*)to_start); - MemRegion to_mr ((HeapWord*)to_start, (HeapWord*)from_start); + MemRegion eden_mr((HeapWord*)eden_start, (HeapWord*)eden_end); + MemRegion to_mr ((HeapWord*)to_start, (HeapWord*)to_end); MemRegion from_mr((HeapWord*)from_start, (HeapWord*)from_end); WorkerThreads& pretouch_workers = ParallelScavengeHeap::heap()->workers(); @@ -196,61 +197,199 @@ void PSYoungGen::set_space_boundaries(size_t eden_size, size_t survivor_size) { #ifndef PRODUCT void PSYoungGen::space_invariants() { - // Currently, our eden size cannot shrink to zero guarantee(eden_space()->capacity_in_bytes() >= SpaceAlignment, "eden too small"); guarantee(from_space()->capacity_in_bytes() >= SpaceAlignment, "from too small"); - guarantee(to_space()->capacity_in_bytes() >= SpaceAlignment, "to too small"); + assert(from_space()->capacity_in_bytes() == to_space()->capacity_in_bytes(), "inv"); - // Relationship of spaces to each other - char* eden_start = (char*)eden_space()->bottom(); - char* eden_end = (char*)eden_space()->end(); - char* from_start = (char*)from_space()->bottom(); - char* from_end = (char*)from_space()->end(); - char* to_start = (char*)to_space()->bottom(); - char* to_end = (char*)to_space()->end(); + HeapWord* eden_bottom = eden_space()->bottom(); + HeapWord* eden_end = eden_space()->end(); + HeapWord* eden_top = eden_space()->top(); - guarantee(eden_start >= virtual_space()->low(), "eden bottom"); - guarantee(eden_start < eden_end, "eden space consistency"); - guarantee(from_start < from_end, "from space consistency"); - guarantee(to_start < to_end, "to space consistency"); + HeapWord* from_bottom = from_space()->bottom(); + HeapWord* from_end = from_space()->end(); + HeapWord* from_top = from_space()->top(); + + HeapWord* to_bottom = to_space()->bottom(); + HeapWord* to_end = to_space()->end(); + HeapWord* to_top = to_space()->top(); + + assert(eden_bottom <= eden_top && eden_top <= eden_end, "inv"); + assert(from_bottom <= from_top && from_top <= from_end, "inv"); + assert(to_bottom <= to_top && to_top <= to_end, "inv"); + + // Relationship of spaces to each other; from/to, eden + guarantee((char*)MIN2(from_bottom, to_bottom) == virtual_space()->low(), "inv"); + + guarantee(is_aligned(eden_bottom, SpaceAlignment), "inv"); + guarantee(is_aligned(from_bottom, SpaceAlignment), "inv"); + guarantee(is_aligned( to_bottom, SpaceAlignment), "inv"); // Check whether from space is below to space - if (from_start < to_start) { - // Eden, from, to - guarantee(eden_end <= from_start, "eden/from boundary"); - guarantee(from_end <= to_start, "from/to boundary"); - guarantee(to_end <= virtual_space()->high(), "to end"); + if (from_bottom < to_bottom) { + // from, to + guarantee(from_end == to_bottom, "inv"); + guarantee(to_end == eden_bottom, "inv"); } else { - // Eden, to, from - guarantee(eden_end <= to_start, "eden/to boundary"); - guarantee(to_end <= from_start, "to/from boundary"); - guarantee(from_end <= virtual_space()->high(), "from end"); + // to, from + guarantee(to_end == from_bottom, "inv"); + guarantee(from_end == eden_bottom, "inv"); } + guarantee((char*)eden_end <= virtual_space()->high(), "inv"); + guarantee(is_aligned(eden_end, SpaceAlignment), "inv"); // More checks that the virtual space is consistent with the spaces assert(virtual_space()->committed_size() >= - (eden_space()->capacity_in_bytes() + - to_space()->capacity_in_bytes() + - from_space()->capacity_in_bytes()), "Committed size is inconsistent"); + (eden_space()->capacity_in_bytes() + 2 * from_space()->capacity_in_bytes()), "Committed size is inconsistent"); assert(virtual_space()->committed_size() <= virtual_space()->reserved_size(), "Space invariant"); - char* eden_top = (char*)eden_space()->top(); - char* from_top = (char*)from_space()->top(); - char* to_top = (char*)to_space()->top(); - assert(eden_top <= virtual_space()->high(), "eden top"); - assert(from_top <= virtual_space()->high(), "from top"); - assert(to_top <= virtual_space()->high(), "to top"); virtual_space()->verify(); } #endif -void PSYoungGen::resize(size_t eden_size, size_t survivor_size) { - // Resize the generation if needed. If the generation resize - // reports false, do not attempt to resize the spaces. - if (resize_generation(eden_size, survivor_size)) { - // Then we lay out the spaces inside the generation - resize_spaces(eden_size, survivor_size); +bool PSYoungGen::try_expand_to_hold(size_t word_size) { + assert(eden_space()->free_in_words() < word_size, "precondition"); + + // For logging purpose + size_t original_committed_size = virtual_space()->committed_size(); + + assert(is_aligned(virtual_space()->committed_high_addr(), SpaceAlignment), "inv"); + if (pointer_delta(virtual_space()->committed_high_addr(), eden_space()->top(), sizeof(HeapWord)) >= word_size) { + // eden needs expansion but no OS committing + assert(virtual_space()->committed_high_addr() > (char*)eden_space()->end(), "inv"); + } else { + // eden needs OS committing and expansion + assert(virtual_space()->reserved_high_addr() > virtual_space()->committed_high_addr(), "inv"); + + const size_t existing_free_in_eden = eden_space()->free_in_words(); + assert(existing_free_in_eden < word_size, "inv"); + + size_t delta_words = word_size - existing_free_in_eden; + size_t delta_bytes = delta_words * HeapWordSize; + delta_bytes = align_up(delta_bytes, virtual_space()->alignment()); + if (!virtual_space()->expand_by(delta_bytes)) { + // Expansion fails at OS level. + return false; + } + + assert(is_aligned(virtual_space()->committed_high_addr(), SpaceAlignment), "inv"); + } + + HeapWord* new_eden_end = (HeapWord*) virtual_space()->committed_high_addr(); + assert(new_eden_end > eden_space()->end(), "inv"); + MemRegion edenMR = MemRegion(eden_space()->bottom(), new_eden_end); + + eden_space()->initialize(edenMR, + eden_space()->is_empty(), + SpaceDecorator::DontMangle, + MutableSpace::SetupPages, + &ParallelScavengeHeap::heap()->workers()); + + if (ZapUnusedHeapArea) { + eden_space()->mangle_unused_area(); + } + post_resize(); + log_debug(gc, ergo)("PSYoung size changed (eden expansion): %zuK->%zuK", + original_committed_size / K, virtual_space()->committed_size() / K); + return true; +} + +HeapWord* PSYoungGen::expand_and_allocate(size_t word_size) { + assert(SafepointSynchronize::is_at_safepoint(), "precondition"); + assert(Thread::current()->is_VM_thread(), "precondition"); + + { + size_t available_word_size = pointer_delta(virtual_space()->reserved_high_addr(), + eden_space()->top(), + sizeof(HeapWord)); + if (word_size > available_word_size) { + return nullptr; + } + } + + if (eden_space()->free_in_words() < word_size) { + if (!try_expand_to_hold(word_size)) { + return nullptr; + } + } + + HeapWord* result = eden_space()->cas_allocate(word_size); + assert(result, "inv"); + return result; +} + +void PSYoungGen::compute_desired_sizes(bool is_survivor_overflowing, + size_t& eden_size, + size_t& survivor_size) { + assert(eden_space()->is_empty() && to_space()->is_empty(), "precondition"); + assert(is_from_to_layout(), "precondition"); + + // Current sizes for all three spaces + const size_t current_eden_size = eden_space()->capacity_in_bytes(); + assert(from_space()->capacity_in_bytes() == to_space()->capacity_in_bytes(), "inv"); + const size_t current_survivor_size = from_space()->capacity_in_bytes(); + assert(current_eden_size + 2 * current_survivor_size <= max_gen_size(), "inv"); + + PSAdaptiveSizePolicy* size_policy = ParallelScavengeHeap::heap()->size_policy(); + + // eden-space + eden_size = size_policy->compute_desired_eden_size(is_survivor_overflowing, current_eden_size); + eden_size = align_up(eden_size, SpaceAlignment); + assert(eden_size >= SpaceAlignment, "inv"); + + survivor_size = size_policy->compute_desired_survivor_size(current_survivor_size, max_gen_size()); + survivor_size = MAX3(survivor_size, + from_space()->used_in_bytes(), + SpaceAlignment); + survivor_size = align_up(survivor_size, SpaceAlignment); + + log_debug(gc, ergo)("Desired size eden: %zu K, survivor: %zu K", eden_size/K, survivor_size/K); + + const size_t new_gen_size = eden_size + 2 * survivor_size; + if (new_gen_size < min_gen_size()) { + // Keep survivor and adjust eden to meet min-gen-size + eden_size = min_gen_size() - 2 * survivor_size; + } else if (max_gen_size() < new_gen_size) { + log_info(gc, ergo)("Requested sizes exceeds MaxNewSize (K): %zu vs %zu)", new_gen_size/K, max_gen_size()/K); + // New capacity would exceed max; need to revise these desired sizes. + // Favor survivor over eden in order to reduce promotion (overflow). + if (2 * survivor_size >= max_gen_size()) { + // If requested survivor size is too large + survivor_size = align_down((max_gen_size() - SpaceAlignment) / 2, SpaceAlignment); + eden_size = max_gen_size() - 2 * survivor_size; + } else { + // Respect survivor size and reduce eden + eden_size = max_gen_size() - 2 * survivor_size; + } + } + + assert(eden_size >= SpaceAlignment, "inv"); + assert(survivor_size >= SpaceAlignment, "inv"); + + assert(is_aligned(eden_size, SpaceAlignment), "inv"); + assert(is_aligned(survivor_size, SpaceAlignment), "inv"); +} + +void PSYoungGen::resize_inner(size_t desired_eden_size, + size_t desired_survivor_size) { + assert(desired_eden_size != 0, "precondition"); + assert(desired_survivor_size != 0, "precondition"); + + size_t desired_young_gen_size = desired_eden_size + 2 * desired_survivor_size; + + assert(desired_young_gen_size >= min_gen_size(), "precondition"); + assert(desired_young_gen_size <= max_gen_size(), "precondition"); + + if (eden_space()->capacity_in_bytes() == desired_eden_size + && from_space()->capacity_in_bytes() == desired_survivor_size) { + // no change + return; + } + + bool resize_success = resize_generation(desired_young_gen_size); + + if (resize_success) { + resize_spaces(desired_eden_size, desired_survivor_size); space_invariants(); @@ -258,30 +397,35 @@ void PSYoungGen::resize(size_t eden_size, size_t survivor_size) { "desired eden: %zu survivor: %zu" " used: %zu capacity: %zu" " gen limits: %zu / %zu", - eden_size, survivor_size, used_in_bytes(), capacity_in_bytes(), + desired_eden_size, desired_survivor_size, used_in_bytes(), capacity_in_bytes(), max_gen_size(), min_gen_size()); } } +void PSYoungGen::resize_after_young_gc(bool is_survivor_overflowing) { + assert(eden_space()->is_empty(), "precondition"); + assert(to_space()->is_empty(), "precondition"); -bool PSYoungGen::resize_generation(size_t eden_size, size_t survivor_size) { + size_t desired_eden_size = 0; + size_t desired_survivor_size = 0; + + compute_desired_sizes(is_survivor_overflowing, + desired_eden_size, + desired_survivor_size); + + resize_inner(desired_eden_size, desired_survivor_size); +} + +bool PSYoungGen::resize_generation(size_t desired_young_gen_size) { const size_t alignment = virtual_space()->alignment(); size_t orig_size = virtual_space()->committed_size(); bool size_changed = false; - // There used to be this guarantee there. - // guarantee ((eden_size + 2*survivor_size) <= max_gen_size(), "incorrect input arguments"); - // Code below forces this requirement. In addition the desired eden - // size and desired survivor sizes are desired goals and may - // exceed the total generation size. - assert(min_gen_size() <= orig_size && orig_size <= max_gen_size(), "just checking"); - // Adjust new generation size - const size_t eden_plus_survivors = - align_up(eden_size + 2 * survivor_size, alignment); - size_t desired_size = clamp(eden_plus_survivors, min_gen_size(), max_gen_size()); - assert(desired_size <= max_gen_size(), "just checking"); + size_t desired_size = clamp(align_up(desired_young_gen_size, alignment), + min_gen_size(), + max_gen_size()); if (desired_size > orig_size) { // Grow the generation @@ -303,15 +447,8 @@ bool PSYoungGen::resize_generation(size_t eden_size, size_t survivor_size) { } else if (desired_size < orig_size) { size_t desired_change = orig_size - desired_size; assert(desired_change % alignment == 0, "just checking"); - - desired_change = limit_gen_shrink(desired_change); - - if (desired_change > 0) { - virtual_space()->shrink_by(desired_change); - reset_survivors_after_shrink(); - - size_changed = true; - } + virtual_space()->shrink_by(desired_change); + size_changed = true; } else { if (orig_size == max_gen_size()) { log_trace(gc)("PSYoung generation size at maximum: %zuK", orig_size/K); @@ -326,307 +463,46 @@ bool PSYoungGen::resize_generation(size_t eden_size, size_t survivor_size) { orig_size/K, virtual_space()->committed_size()/K); } - guarantee(eden_plus_survivors <= virtual_space()->committed_size() || + guarantee(desired_young_gen_size <= virtual_space()->committed_size() || virtual_space()->committed_size() == max_gen_size(), "Sanity"); return true; } -#ifndef PRODUCT -// In the numa case eden is not mangled so a survivor space -// moving into a region previously occupied by a survivor -// may find an unmangled region. Also in the PS case eden -// to-space and from-space may not touch (i.e., there may be -// gaps between them due to movement while resizing the -// spaces). Those gaps must be mangled. -void PSYoungGen::mangle_survivors(MutableSpace* s1, - MemRegion s1MR, - MutableSpace* s2, - MemRegion s2MR) { - // Check eden and gap between eden and from-space, in deciding - // what to mangle in from-space. Check the gap between from-space - // and to-space when deciding what to mangle. - // - // +--------+ +----+ +---+ - // | eden | |s1 | |s2 | - // +--------+ +----+ +---+ - // +-------+ +-----+ - // |s1MR | |s2MR | - // +-------+ +-----+ - // All of survivor-space is properly mangled so find the - // upper bound on the mangling for any portion above current s1. - HeapWord* delta_end = MIN2(s1->bottom(), s1MR.end()); - MemRegion delta1_left; - if (s1MR.start() < delta_end) { - delta1_left = MemRegion(s1MR.start(), delta_end); - s1->mangle_region(delta1_left); - } - // Find any portion to the right of the current s1. - HeapWord* delta_start = MAX2(s1->end(), s1MR.start()); - MemRegion delta1_right; - if (delta_start < s1MR.end()) { - delta1_right = MemRegion(delta_start, s1MR.end()); - s1->mangle_region(delta1_right); - } - - // Similarly for the second survivor space except that - // any of the new region that overlaps with the current - // region of the first survivor space has already been - // mangled. - delta_end = MIN2(s2->bottom(), s2MR.end()); - delta_start = MAX2(s2MR.start(), s1->end()); - MemRegion delta2_left; - if (s2MR.start() < delta_end) { - delta2_left = MemRegion(s2MR.start(), delta_end); - s2->mangle_region(delta2_left); - } - delta_start = MAX2(s2->end(), s2MR.start()); - MemRegion delta2_right; - if (delta_start < s2MR.end()) { - s2->mangle_region(delta2_right); - } - - // s1 - log_develop_trace(gc)("Current region: [" PTR_FORMAT ", " PTR_FORMAT ") " - "New region: [" PTR_FORMAT ", " PTR_FORMAT ")", - p2i(s1->bottom()), p2i(s1->end()), - p2i(s1MR.start()), p2i(s1MR.end())); - log_develop_trace(gc)(" Mangle before: [" PTR_FORMAT ", " - PTR_FORMAT ") Mangle after: [" PTR_FORMAT ", " PTR_FORMAT ")", - p2i(delta1_left.start()), p2i(delta1_left.end()), - p2i(delta1_right.start()), p2i(delta1_right.end())); - - // s2 - log_develop_trace(gc)("Current region: [" PTR_FORMAT ", " PTR_FORMAT ") " - "New region: [" PTR_FORMAT ", " PTR_FORMAT ")", - p2i(s2->bottom()), p2i(s2->end()), - p2i(s2MR.start()), p2i(s2MR.end())); - log_develop_trace(gc)(" Mangle before: [" PTR_FORMAT ", " - PTR_FORMAT ") Mangle after: [" PTR_FORMAT ", " PTR_FORMAT ")", - p2i(delta2_left.start()), p2i(delta2_left.end()), - p2i(delta2_right.start()), p2i(delta2_right.end())); -} -#endif // NOT PRODUCT - void PSYoungGen::resize_spaces(size_t requested_eden_size, size_t requested_survivor_size) { - assert(UseAdaptiveSizePolicy, "sanity check"); - assert(requested_eden_size > 0 && requested_survivor_size > 0, - "just checking"); + assert(requested_eden_size > 0 && requested_survivor_size > 0, + "precondition"); + assert(is_aligned(requested_eden_size, SpaceAlignment), "precondition"); + assert(is_aligned(requested_survivor_size, SpaceAlignment), "precondition"); + assert(from_space()->bottom() < to_space()->bottom(), "precondition"); - // We require eden and to space to be empty - if ((!eden_space()->is_empty()) || (!to_space()->is_empty())) { - return; - } + // layout: from, to, eden + char* from_start = virtual_space()->low(); + char* from_end = from_start + requested_survivor_size; + char* to_start = from_end; + char* to_end = to_start + requested_survivor_size; + char* eden_start = to_end; + char* eden_end = eden_start + requested_eden_size; - log_trace(gc, ergo)("PSYoungGen::resize_spaces(requested_eden_size: %zu, requested_survivor_size: %zu)", - requested_eden_size, requested_survivor_size); - log_trace(gc, ergo)(" eden: [" PTR_FORMAT ".." PTR_FORMAT ") %zu", - p2i(eden_space()->bottom()), - p2i(eden_space()->end()), - pointer_delta(eden_space()->end(), - eden_space()->bottom(), - sizeof(char))); - log_trace(gc, ergo)(" from: [" PTR_FORMAT ".." PTR_FORMAT ") %zu", - p2i(from_space()->bottom()), - p2i(from_space()->end()), - pointer_delta(from_space()->end(), - from_space()->bottom(), - sizeof(char))); - log_trace(gc, ergo)(" to: [" PTR_FORMAT ".." PTR_FORMAT ") %zu", - p2i(to_space()->bottom()), - p2i(to_space()->end()), - pointer_delta( to_space()->end(), - to_space()->bottom(), - sizeof(char))); - - // There's nothing to do if the new sizes are the same as the current - if (requested_survivor_size == to_space()->capacity_in_bytes() && - requested_survivor_size == from_space()->capacity_in_bytes() && - requested_eden_size == eden_space()->capacity_in_bytes()) { - log_trace(gc, ergo)(" capacities are the right sizes, returning"); - return; - } - - char* eden_start = (char*)eden_space()->bottom(); - char* eden_end = (char*)eden_space()->end(); - char* from_start = (char*)from_space()->bottom(); - char* from_end = (char*)from_space()->end(); - char* to_start = (char*)to_space()->bottom(); - char* to_end = (char*)to_space()->end(); - - const bool maintain_minimum = - (requested_eden_size + 2 * requested_survivor_size) <= min_gen_size(); - - bool eden_from_to_order = from_start < to_start; - // Check whether from space is below to space - if (eden_from_to_order) { - // Eden, from, to - eden_from_to_order = true; - log_trace(gc, ergo)(" Eden, from, to:"); - - // Set eden - // "requested_eden_size" is a goal for the size of eden - // and may not be attainable. "eden_size" below is - // calculated based on the location of from-space and - // the goal for the size of eden. from-space is - // fixed in place because it contains live data. - // The calculation is done this way to avoid 32bit - // overflow (i.e., eden_start + requested_eden_size - // may too large for representation in 32bits). - size_t eden_size; - if (maintain_minimum) { - // Only make eden larger than the requested size if - // the minimum size of the generation has to be maintained. - // This could be done in general but policy at a higher - // level is determining a requested size for eden and that - // should be honored unless there is a fundamental reason. - eden_size = pointer_delta(from_start, - eden_start, - sizeof(char)); - } else { - eden_size = MIN2(requested_eden_size, - pointer_delta(from_start, eden_start, sizeof(char))); - } - - eden_end = eden_start + eden_size; - assert(eden_end >= eden_start, "addition overflowed"); - - // To may resize into from space as long as it is clear of live data. - // From space must remain page aligned, though, so we need to do some - // extra calculations. - - // First calculate an optimal to-space - to_end = (char*)virtual_space()->high(); - to_start = (char*)pointer_delta(to_end, (char*)requested_survivor_size, - sizeof(char)); - - // Does the optimal to-space overlap from-space? - if (to_start < (char*)from_space()->end()) { - // Calculate the minimum offset possible for from_end - size_t from_size = pointer_delta(from_space()->top(), from_start, sizeof(char)); - - // Should we be in this method if from_space is empty? Why not the set_space method? FIX ME! - if (from_size == 0) { - from_size = SpaceAlignment; - } else { - from_size = align_up(from_size, SpaceAlignment); - } - - from_end = from_start + from_size; - assert(from_end > from_start, "addition overflow or from_size problem"); - - guarantee(from_end <= (char*)from_space()->end(), "from_end moved to the right"); - - // Now update to_start with the new from_end - to_start = MAX2(from_end, to_start); - } - - guarantee(to_start != to_end, "to space is zero sized"); - - log_trace(gc, ergo)(" [eden_start .. eden_end): [" PTR_FORMAT " .. " PTR_FORMAT ") %zu", - p2i(eden_start), - p2i(eden_end), - pointer_delta(eden_end, eden_start, sizeof(char))); - log_trace(gc, ergo)(" [from_start .. from_end): [" PTR_FORMAT " .. " PTR_FORMAT ") %zu", - p2i(from_start), - p2i(from_end), - pointer_delta(from_end, from_start, sizeof(char))); - log_trace(gc, ergo)(" [ to_start .. to_end): [" PTR_FORMAT " .. " PTR_FORMAT ") %zu", - p2i(to_start), - p2i(to_end), - pointer_delta( to_end, to_start, sizeof(char))); - } else { - // Eden, to, from - log_trace(gc, ergo)(" Eden, to, from:"); - - // To space gets priority over eden resizing. Note that we position - // to space as if we were able to resize from space, even though from - // space is not modified. - // Giving eden priority was tried and gave poorer performance. - to_end = (char*)pointer_delta(virtual_space()->high(), - (char*)requested_survivor_size, - sizeof(char)); - to_end = MIN2(to_end, from_start); - to_start = (char*)pointer_delta(to_end, (char*)requested_survivor_size, - sizeof(char)); - // if the space sizes are to be increased by several times then - // 'to_start' will point beyond the young generation. In this case - // 'to_start' should be adjusted. - to_start = MAX2(to_start, eden_start + SpaceAlignment); - - // Compute how big eden can be, then adjust end. - // See comments above on calculating eden_end. - size_t eden_size; - if (maintain_minimum) { - eden_size = pointer_delta(to_start, eden_start, sizeof(char)); - } else { - eden_size = MIN2(requested_eden_size, - pointer_delta(to_start, eden_start, sizeof(char))); - } - eden_end = eden_start + eden_size; - assert(eden_end >= eden_start, "addition overflowed"); - - // Could choose to not let eden shrink - // to_start = MAX2(to_start, eden_end); - - // Don't let eden shrink down to 0 or less. - eden_end = MAX2(eden_end, eden_start + SpaceAlignment); - to_start = MAX2(to_start, eden_end); - - log_trace(gc, ergo)(" [eden_start .. eden_end): [" PTR_FORMAT " .. " PTR_FORMAT ") %zu", - p2i(eden_start), - p2i(eden_end), - pointer_delta(eden_end, eden_start, sizeof(char))); - log_trace(gc, ergo)(" [ to_start .. to_end): [" PTR_FORMAT " .. " PTR_FORMAT ") %zu", - p2i(to_start), - p2i(to_end), - pointer_delta( to_end, to_start, sizeof(char))); - log_trace(gc, ergo)(" [from_start .. from_end): [" PTR_FORMAT " .. " PTR_FORMAT ") %zu", - p2i(from_start), - p2i(from_end), - pointer_delta(from_end, from_start, sizeof(char))); - } - - - guarantee((HeapWord*)from_start <= from_space()->bottom(), - "from start moved to the right"); - guarantee((HeapWord*)from_end >= from_space()->top(), - "from end moved into live data"); - assert(is_object_aligned(eden_start), "checking alignment"); - assert(is_object_aligned(from_start), "checking alignment"); - assert(is_object_aligned(to_start), "checking alignment"); + assert(eden_end <= virtual_space()->high(), "inv"); MemRegion edenMR((HeapWord*)eden_start, (HeapWord*)eden_end); - MemRegion toMR ((HeapWord*)to_start, (HeapWord*)to_end); MemRegion fromMR((HeapWord*)from_start, (HeapWord*)from_end); + MemRegion toMR ((HeapWord*)to_start, (HeapWord*)to_end); - // Let's make sure the call to initialize doesn't reset "top"! - HeapWord* old_from_top = from_space()->top(); - - // For logging block below - size_t old_from = from_space()->capacity_in_bytes(); - size_t old_to = to_space()->capacity_in_bytes(); - - if (ZapUnusedHeapArea) { - // NUMA is a special case because a numa space is not mangled - // in order to not prematurely bind its address to memory to - // the wrong memory (i.e., don't want the GC thread to first - // touch the memory). The survivor spaces are not numa - // spaces and are mangled. - if (UseNUMA) { - if (eden_from_to_order) { - mangle_survivors(from_space(), fromMR, to_space(), toMR); - } else { - mangle_survivors(to_space(), toMR, from_space(), fromMR); - } - } +#ifdef ASSERT + if (!from_space()->is_empty()) { + assert(fromMR.start() == from_space()->bottom(), "inv"); + assert(fromMR.contains(from_space()->used_region()), "inv"); } +#endif + // For logging below + size_t old_from_capacity = from_space()->capacity_in_bytes(); + size_t old_to_capacity = to_space()->capacity_in_bytes(); WorkerThreads* workers = &ParallelScavengeHeap::heap()->workers(); - // When an existing space is being initialized, it is not - // mangled because the space has been previously mangled. eden_space()->initialize(edenMR, SpaceDecorator::Clear, SpaceDecorator::DontMangle, @@ -638,16 +514,21 @@ void PSYoungGen::resize_spaces(size_t requested_eden_size, MutableSpace::SetupPages, workers); from_space()->initialize(fromMR, - SpaceDecorator::DontClear, + from_space()->is_empty(), SpaceDecorator::DontMangle, MutableSpace::SetupPages, workers); - assert(from_space()->top() == old_from_top, "from top changed!"); + if (ZapUnusedHeapArea) { + if (!UseNUMA) { + eden_space()->mangle_unused_area(); + } + to_space()->mangle_unused_area(); + from_space()->mangle_unused_area(); + } - log_trace(gc, ergo)("AdaptiveSizePolicy::survivor space sizes: collection: %d (%zu, %zu) -> (%zu, %zu) ", - ParallelScavengeHeap::heap()->total_collections(), - old_from, old_to, + log_trace(gc, ergo)("AdaptiveSizePolicy::survivor sizes: (%zu, %zu) -> (%zu, %zu) ", + old_from_capacity, old_to_capacity, from_space()->capacity_in_bytes(), to_space()->capacity_in_bytes()); } @@ -710,101 +591,14 @@ void PSYoungGen::print_on(outputStream* st) const { to_space()->print_on(st, "to "); } -size_t PSYoungGen::available_to_min_gen() { - assert(virtual_space()->committed_size() >= min_gen_size(), "Invariant"); - return virtual_space()->committed_size() - min_gen_size(); -} - -// This method assumes that from-space has live data and that -// any shrinkage of the young gen is limited by location of -// from-space. -size_t PSYoungGen::available_to_live() { - size_t delta_in_survivor = 0; - MutableSpace* space_shrinking = nullptr; - if (from_space()->end() > to_space()->end()) { - space_shrinking = from_space(); - } else { - space_shrinking = to_space(); - } - - // Include any space that is committed but not included in - // the survivor spaces. - assert(((HeapWord*)virtual_space()->high()) >= space_shrinking->end(), - "Survivor space beyond high end"); - size_t unused_committed = pointer_delta(virtual_space()->high(), - space_shrinking->end(), sizeof(char)); - - if (space_shrinking->is_empty()) { - // Don't let the space shrink to 0 - assert(space_shrinking->capacity_in_bytes() >= SpaceAlignment, - "Space is too small"); - delta_in_survivor = space_shrinking->capacity_in_bytes() - SpaceAlignment; - } else { - delta_in_survivor = pointer_delta(space_shrinking->end(), - space_shrinking->top(), - sizeof(char)); - } - - size_t delta_in_bytes = unused_committed + delta_in_survivor; - delta_in_bytes = align_down(delta_in_bytes, SpaceAlignment); - return delta_in_bytes; -} - -// Return the number of bytes available for resizing down the young -// generation. This is the minimum of -// input "bytes" -// bytes to the minimum young gen size -// bytes to the size currently being used + some small extra -size_t PSYoungGen::limit_gen_shrink(size_t bytes) { - // Allow shrinkage into the current eden but keep eden large enough - // to maintain the minimum young gen size - bytes = MIN3(bytes, available_to_min_gen(), available_to_live()); - return align_down(bytes, virtual_space()->alignment()); -} - -void PSYoungGen::reset_survivors_after_shrink() { - _reserved = MemRegion((HeapWord*)virtual_space()->low_boundary(), - (HeapWord*)virtual_space()->high_boundary()); - PSScavenge::set_subject_to_discovery_span(_reserved); - - MutableSpace* space_shrinking = nullptr; - if (from_space()->end() > to_space()->end()) { - space_shrinking = from_space(); - } else { - space_shrinking = to_space(); - } - - HeapWord* new_end = (HeapWord*)virtual_space()->high(); - assert(new_end >= space_shrinking->bottom(), "Shrink was too large"); - // Was there a shrink of the survivor space? - if (new_end < space_shrinking->end()) { - MemRegion mr(space_shrinking->bottom(), new_end); - - space_shrinking->initialize(mr, - SpaceDecorator::DontClear, - SpaceDecorator::Mangle, - MutableSpace::SetupPages, - &ParallelScavengeHeap::heap()->workers()); - } -} - -// This method currently does not expect to expand into eden (i.e., -// the virtual space boundaries is expected to be consistent -// with the eden boundaries.. void PSYoungGen::post_resize() { assert_locked_or_safepoint(Heap_lock); - assert((eden_space()->bottom() < to_space()->bottom()) && - (eden_space()->bottom() < from_space()->bottom()), - "Eden is assumed to be below the survivor spaces"); MemRegion cmr((HeapWord*)virtual_space()->low(), (HeapWord*)virtual_space()->high()); ParallelScavengeHeap::heap()->card_table()->resize_covered_region(cmr); - space_invariants(); } - - void PSYoungGen::update_counters() { if (UsePerfData) { _eden_counters->update_all(); diff --git a/src/hotspot/share/gc/parallel/psYoungGen.hpp b/src/hotspot/share/gc/parallel/psYoungGen.hpp index 5140ea08bd1..981b4a90874 100644 --- a/src/hotspot/share/gc/parallel/psYoungGen.hpp +++ b/src/hotspot/share/gc/parallel/psYoungGen.hpp @@ -61,26 +61,28 @@ class PSYoungGen : public CHeapObj { // Space boundary helper void set_space_boundaries(size_t eden_size, size_t survivor_size); - bool resize_generation(size_t eden_size, size_t survivor_size); - void resize_spaces(size_t eden_size, size_t survivor_size); + bool resize_generation(size_t desired_young_gen_size); + void resize_spaces(size_t requested_eden_size, + size_t requested_survivor_size); + + // Try to expand eden to hold at least word_size. + // Return true iff the expansion is successful. + bool try_expand_to_hold(size_t word_size); // Adjust the spaces to be consistent with the virtual space. void post_resize(); - // Given a desired shrinkage in the size of the young generation, - // return the actual size available for shrinkage. - size_t limit_gen_shrink(size_t desired_change); - // returns the number of bytes available from the current size - // down to the minimum generation size. - size_t available_to_min_gen(); - // Return the number of bytes available for shrinkage considering - // the location the live data in the generation. - size_t available_to_live(); - void initialize(ReservedSpace rs, size_t inital_size, size_t alignment); void initialize_work(); void initialize_virtual_space(ReservedSpace rs, size_t initial_size, size_t alignment); + void compute_desired_sizes(bool is_survivor_overflowing, + size_t& eden_size, + size_t& survivor_size); + + void resize_inner(size_t desired_eden_size, + size_t desired_survivor_size); + public: // Initialize the generation. PSYoungGen(ReservedSpace rs, @@ -106,11 +108,11 @@ class PSYoungGen : public CHeapObj { // Called during/after GC void swap_spaces(); - // Resize generation using suggested free space size and survivor size - // NOTE: "eden_size" and "survivor_size" are suggestions only. Current - // heap layout (particularly, live objects in from space) might - // not allow us to use these values. - void resize(size_t eden_size, size_t survivor_size); + bool is_from_to_layout() const { + return from_space()->bottom() < to_space()->bottom(); + } + + void resize_after_young_gc(bool is_survivor_overflowing); // Size info size_t capacity_in_bytes() const; @@ -130,11 +132,11 @@ class PSYoungGen : public CHeapObj { return result; } + HeapWord* expand_and_allocate(size_t word_size); + // Iteration. void object_iterate(ObjectClosure* cl); - void reset_survivors_after_shrink(); - // Performance Counter support void update_counters(); @@ -147,12 +149,6 @@ class PSYoungGen : public CHeapObj { // Space boundary invariant checker void space_invariants() PRODUCT_RETURN; - - // Helper for mangling survivor spaces. - void mangle_survivors(MutableSpace* s1, - MemRegion s1MR, - MutableSpace* s2, - MemRegion s2MR) PRODUCT_RETURN; }; #endif // SHARE_GC_PARALLEL_PSYOUNGGEN_HPP diff --git a/src/hotspot/share/gc/shared/adaptiveSizePolicy.cpp b/src/hotspot/share/gc/shared/adaptiveSizePolicy.cpp index 3ebcdaaaf21..9a699921caa 100644 --- a/src/hotspot/share/gc/shared/adaptiveSizePolicy.cpp +++ b/src/hotspot/share/gc/shared/adaptiveSizePolicy.cpp @@ -38,432 +38,55 @@ elapsedTimer AdaptiveSizePolicy::_major_timer; // For example a gc_cost_ratio of 4 translates into a // throughput goal of .80 -AdaptiveSizePolicy::AdaptiveSizePolicy(size_t init_eden_size, - size_t init_promo_size, - size_t init_survivor_size, - double gc_pause_goal_sec, +AdaptiveSizePolicy::AdaptiveSizePolicy(double gc_pause_goal_sec, uint gc_cost_ratio) : - _throughput_goal(1.0 - double(1.0 / (1.0 + (double) gc_cost_ratio))), - _eden_size(init_eden_size), - _promo_size(init_promo_size), - _survivor_size(init_survivor_size), - _avg_minor_pause(new AdaptivePaddedAverage(AdaptiveTimeWeight, PausePadding)), - _avg_minor_interval(new AdaptiveWeightedAverage(AdaptiveTimeWeight)), - _avg_minor_gc_cost(new AdaptiveWeightedAverage(AdaptiveTimeWeight)), - _avg_major_interval(new AdaptiveWeightedAverage(AdaptiveTimeWeight)), - _avg_major_gc_cost(new AdaptiveWeightedAverage(AdaptiveTimeWeight)), - _avg_young_live(new AdaptiveWeightedAverage(AdaptiveSizePolicyWeight)), - _avg_eden_live(new AdaptiveWeightedAverage(AdaptiveSizePolicyWeight)), - _avg_old_live(new AdaptiveWeightedAverage(AdaptiveSizePolicyWeight)), - _avg_survived(new AdaptivePaddedAverage(AdaptiveSizePolicyWeight, SurvivorPadding)), - _avg_pretenured(new AdaptivePaddedNoZeroDevAverage(AdaptiveSizePolicyWeight, SurvivorPadding)), - _minor_pause_old_estimator(new LinearLeastSquareFit(AdaptiveSizePolicyWeight)), - _minor_pause_young_estimator(new LinearLeastSquareFit(AdaptiveSizePolicyWeight)), - _minor_collection_estimator(new LinearLeastSquareFit(AdaptiveSizePolicyWeight)), - _major_collection_estimator(new LinearLeastSquareFit(AdaptiveSizePolicyWeight)), - _latest_minor_mutator_interval_seconds(0), - _threshold_tolerance_percent(1.0 + ThresholdTolerance/100.0), - _gc_pause_goal_sec(gc_pause_goal_sec), - _young_gen_policy_is_ready(false), - _change_young_gen_for_min_pauses(0), - _change_old_gen_for_maj_pauses(0), - _change_old_gen_for_throughput(0), - _change_young_gen_for_throughput(0), - _increment_tenuring_threshold_for_gc_cost(false), - _decrement_tenuring_threshold_for_gc_cost(false), - _decrement_tenuring_threshold_for_survivor_limit(false), - _decrease_for_footprint(0), - _decide_at_full_gc(0), - _young_gen_change_for_minor_throughput(0), - _old_gen_change_for_major_throughput(0) { - - // Start the timers - _minor_timer.start(); -} - -bool AdaptiveSizePolicy::tenuring_threshold_change() const { - return decrement_tenuring_threshold_for_gc_cost() || - increment_tenuring_threshold_for_gc_cost() || - decrement_tenuring_threshold_for_survivor_limit(); -} + _throughput_goal(1.0 - double(1.0 / (1.0 + (double) gc_cost_ratio))), + _gc_distance_timer(), + _gc_distance_seconds_seq(seq_default_alpha_value), + _trimmed_minor_gc_time_seconds(NumOfGCSample, seq_default_alpha_value), + _trimmed_major_gc_time_seconds(NumOfGCSample, seq_default_alpha_value), + _gc_samples(), + _promoted_bytes(seq_default_alpha_value), + _survived_bytes(seq_default_alpha_value), + _promotion_rate_bytes_per_sec(seq_default_alpha_value), + _peak_old_used_bytes_seq(seq_default_alpha_value), + _minor_pause_young_estimator(new LinearLeastSquareFit(AdaptiveSizePolicyWeight)), + _threshold_tolerance_percent(1.0 + ThresholdTolerance/100.0), + _gc_pause_goal_sec(gc_pause_goal_sec), + _young_gen_policy_is_ready(false) {} void AdaptiveSizePolicy::minor_collection_begin() { - // Update the interval time - _minor_timer.stop(); - // Save most recent collection time - _latest_minor_mutator_interval_seconds = _minor_timer.seconds(); _minor_timer.reset(); _minor_timer.start(); + record_gc_pause_start_instant(); } -void AdaptiveSizePolicy::update_minor_pause_young_estimator( - double minor_pause_in_ms) { - double eden_size_in_mbytes = ((double)_eden_size)/((double)M); - _minor_pause_young_estimator->update(eden_size_in_mbytes, - minor_pause_in_ms); -} - -void AdaptiveSizePolicy::minor_collection_end(GCCause::Cause gc_cause) { - // Update the pause time. +void AdaptiveSizePolicy::minor_collection_end(size_t eden_capacity_in_bytes) { _minor_timer.stop(); - if (!GCCause::is_user_requested_gc(gc_cause) || - UseAdaptiveSizePolicyWithSystemGC) { - double minor_pause_in_seconds = _minor_timer.seconds(); - double minor_pause_in_ms = minor_pause_in_seconds * MILLIUNITS; + double minor_pause_in_seconds = _minor_timer.seconds(); + double minor_pause_in_ms = minor_pause_in_seconds * MILLIUNITS; - // Sample for performance counter - _avg_minor_pause->sample(minor_pause_in_seconds); - - // Cost of collection (unit-less) - double collection_cost = 0.0; - if ((_latest_minor_mutator_interval_seconds > 0.0) && - (minor_pause_in_seconds > 0.0)) { - double interval_in_seconds = - _latest_minor_mutator_interval_seconds + minor_pause_in_seconds; - collection_cost = - minor_pause_in_seconds / interval_in_seconds; - _avg_minor_gc_cost->sample(collection_cost); - // Sample for performance counter - _avg_minor_interval->sample(interval_in_seconds); - } + record_gc_duration(minor_pause_in_seconds); + _trimmed_minor_gc_time_seconds.add(minor_pause_in_seconds); + if (!_young_gen_policy_is_ready) { // The policy does not have enough data until at least some // young collections have been done. - _young_gen_policy_is_ready = - (_avg_minor_gc_cost->count() >= AdaptiveSizePolicyReadyThreshold); - - // Calculate variables used to estimate pause time vs. gen sizes - double eden_size_in_mbytes = ((double)_eden_size) / ((double)M); - update_minor_pause_young_estimator(minor_pause_in_ms); - update_minor_pause_old_estimator(minor_pause_in_ms); - - log_trace(gc, ergo)("AdaptiveSizePolicy::minor_collection_end: minor gc cost: %f average: %f", - collection_cost, _avg_minor_gc_cost->average()); - log_trace(gc, ergo)(" minor pause: %f minor period %f", - minor_pause_in_ms, _latest_minor_mutator_interval_seconds * MILLIUNITS); - - // Calculate variable used to estimate collection cost vs. gen sizes - assert(collection_cost >= 0.0, "Expected to be non-negative"); - _minor_collection_estimator->update(eden_size_in_mbytes, collection_cost); + _young_gen_policy_is_ready = GCId::current() >= AdaptiveSizePolicyReadyThreshold; } - // Interval times use this timer to measure the mutator time. - // Reset the timer after the GC pause. - _minor_timer.reset(); - _minor_timer.start(); + { + double eden_size_in_mbytes = ((double)eden_capacity_in_bytes)/((double)M); + _minor_pause_young_estimator->update(eden_size_in_mbytes, minor_pause_in_ms); + } } size_t AdaptiveSizePolicy::eden_increment(size_t cur_eden, uint percent_change) { - size_t eden_heap_delta; - eden_heap_delta = cur_eden / 100 * percent_change; + size_t eden_heap_delta = cur_eden * percent_change / 100; return eden_heap_delta; } size_t AdaptiveSizePolicy::eden_increment(size_t cur_eden) { return eden_increment(cur_eden, YoungGenerationSizeIncrement); -} - -size_t AdaptiveSizePolicy::eden_decrement(size_t cur_eden) { - size_t eden_heap_delta = eden_increment(cur_eden) / - AdaptiveSizeDecrementScaleFactor; - return eden_heap_delta; -} - -size_t AdaptiveSizePolicy::promo_increment(size_t cur_promo, uint percent_change) { - size_t promo_heap_delta; - promo_heap_delta = cur_promo / 100 * percent_change; - return promo_heap_delta; -} - -size_t AdaptiveSizePolicy::promo_increment(size_t cur_promo) { - return promo_increment(cur_promo, TenuredGenerationSizeIncrement); -} - -size_t AdaptiveSizePolicy::promo_decrement(size_t cur_promo) { - size_t promo_heap_delta = promo_increment(cur_promo); - promo_heap_delta = promo_heap_delta / AdaptiveSizeDecrementScaleFactor; - return promo_heap_delta; -} - -double AdaptiveSizePolicy::time_since_major_gc() const { - _major_timer.stop(); - double result = _major_timer.seconds(); - _major_timer.start(); - return result; -} - -// Linear decay of major gc cost -double AdaptiveSizePolicy::decaying_major_gc_cost() const { - double major_interval = major_gc_interval_average_for_decay(); - double major_gc_cost_average = major_gc_cost(); - double decayed_major_gc_cost = major_gc_cost_average; - if(time_since_major_gc() > 0.0) { - decayed_major_gc_cost = major_gc_cost() * - (((double) AdaptiveSizeMajorGCDecayTimeScale) * major_interval) - / time_since_major_gc(); - } - - // The decayed cost should always be smaller than the - // average cost but the vagaries of finite arithmetic could - // produce a larger value in decayed_major_gc_cost so protect - // against that. - return MIN2(major_gc_cost_average, decayed_major_gc_cost); -} - -// Use a value of the major gc cost that has been decayed -// by the factor -// -// average-interval-between-major-gc * AdaptiveSizeMajorGCDecayTimeScale / -// time-since-last-major-gc -// -// if the average-interval-between-major-gc * AdaptiveSizeMajorGCDecayTimeScale -// is less than time-since-last-major-gc. -// -// In cases where there are initial major gc's that -// are of a relatively high cost but no later major -// gc's, the total gc cost can remain high because -// the major gc cost remains unchanged (since there are no major -// gc's). In such a situation the value of the unchanging -// major gc cost can keep the mutator throughput below -// the goal when in fact the major gc cost is becoming diminishingly -// small. Use the decaying gc cost only to decide whether to -// adjust for throughput. Using it also to determine the adjustment -// to be made for throughput also seems reasonable but there is -// no test case to use to decide if it is the right thing to do -// don't do it yet. - -double AdaptiveSizePolicy::decaying_gc_cost() const { - double decayed_major_gc_cost = major_gc_cost(); - double avg_major_interval = major_gc_interval_average_for_decay(); - if (UseAdaptiveSizeDecayMajorGCCost && - (AdaptiveSizeMajorGCDecayTimeScale > 0) && - (avg_major_interval > 0.00)) { - double time_since_last_major_gc = time_since_major_gc(); - - // Decay the major gc cost? - if (time_since_last_major_gc > - ((double) AdaptiveSizeMajorGCDecayTimeScale) * avg_major_interval) { - - // Decay using the time-since-last-major-gc - decayed_major_gc_cost = decaying_major_gc_cost(); - log_trace(gc, ergo)("decaying_gc_cost: major interval average: %f time since last major gc: %f", - avg_major_interval, time_since_last_major_gc); - log_trace(gc, ergo)(" major gc cost: %f decayed major gc cost: %f", - major_gc_cost(), decayed_major_gc_cost); - } - } - double result = MIN2(1.0, decayed_major_gc_cost + minor_gc_cost()); - return result; -} - - -void AdaptiveSizePolicy::clear_generation_free_space_flags() { - set_change_young_gen_for_min_pauses(0); - set_change_old_gen_for_maj_pauses(0); - - set_change_old_gen_for_throughput(0); - set_change_young_gen_for_throughput(0); - set_decrease_for_footprint(0); - set_decide_at_full_gc(0); -} - -class AdaptiveSizePolicyTimeOverheadTester: public GCOverheadTester { - double _gc_cost; - - public: - AdaptiveSizePolicyTimeOverheadTester(double gc_cost) : _gc_cost(gc_cost) {} - - bool is_exceeded() { - return _gc_cost > (GCTimeLimit / 100.0); - } -}; - -class AdaptiveSizePolicySpaceOverheadTester: public GCOverheadTester { - size_t _eden_live; - size_t _max_old_gen_size; - size_t _max_eden_size; - size_t _promo_size; - double _avg_eden_live; - double _avg_old_live; - - public: - AdaptiveSizePolicySpaceOverheadTester(size_t eden_live, - size_t max_old_gen_size, - size_t max_eden_size, - size_t promo_size, - double avg_eden_live, - double avg_old_live) : - _eden_live(eden_live), - _max_old_gen_size(max_old_gen_size), - _max_eden_size(max_eden_size), - _promo_size(promo_size), - _avg_eden_live(avg_eden_live), - _avg_old_live(avg_old_live) {} - - bool is_exceeded() { - // _max_eden_size is the upper limit on the size of eden based on - // the maximum size of the young generation and the sizes - // of the survivor space. - // The question being asked is whether the space being recovered by - // a collection is low. - // free_in_eden is the free space in eden after a collection and - // free_in_old_gen is the free space in the old generation after - // a collection. - // - // Use the minimum of the current value of the live in eden - // or the average of the live in eden. - // If the current value drops quickly, that should be taken - // into account (i.e., don't trigger if the amount of free - // space has suddenly jumped up). If the current is much - // higher than the average, use the average since it represents - // the longer term behavior. - const size_t live_in_eden = - MIN2(_eden_live, (size_t)_avg_eden_live); - const size_t free_in_eden = _max_eden_size > live_in_eden ? - _max_eden_size - live_in_eden : 0; - const size_t free_in_old_gen = (size_t)(_max_old_gen_size - _avg_old_live); - const size_t total_free_limit = free_in_old_gen + free_in_eden; - const size_t total_mem = _max_old_gen_size + _max_eden_size; - const double free_limit_ratio = GCHeapFreeLimit / 100.0; - const double mem_free_limit = total_mem * free_limit_ratio; - const double mem_free_old_limit = _max_old_gen_size * free_limit_ratio; - const double mem_free_eden_limit = _max_eden_size * free_limit_ratio; - size_t promo_limit = (size_t)(_max_old_gen_size - _avg_old_live); - // But don't force a promo size below the current promo size. Otherwise, - // the promo size will shrink for no good reason. - promo_limit = MAX2(promo_limit, _promo_size); - - log_trace(gc, ergo)( - "AdaptiveSizePolicySpaceOverheadTester::is_exceeded:" - " promo_limit: %zu" - " total_free_limit: %zu" - " max_old_gen_size: %zu" - " max_eden_size: %zu" - " mem_free_limit: %zu", - promo_limit, total_free_limit, - _max_old_gen_size, _max_eden_size, - (size_t)mem_free_limit); - - return free_in_old_gen < (size_t)mem_free_old_limit && - free_in_eden < (size_t)mem_free_eden_limit; - } -}; - -void AdaptiveSizePolicy::check_gc_overhead_limit( - size_t eden_live, - size_t max_old_gen_size, - size_t max_eden_size, - bool is_full_gc, - GCCause::Cause gc_cause, - SoftRefPolicy* soft_ref_policy) { - - AdaptiveSizePolicyTimeOverheadTester time_overhead(gc_cost()); - AdaptiveSizePolicySpaceOverheadTester space_overhead(eden_live, - max_old_gen_size, - max_eden_size, - _promo_size, - avg_eden_live()->average(), - avg_old_live()->average()); - _overhead_checker.check_gc_overhead_limit(&time_overhead, - &space_overhead, - is_full_gc, - gc_cause, - soft_ref_policy); -} -// Printing - -bool AdaptiveSizePolicy::print() const { - assert(UseAdaptiveSizePolicy, "UseAdaptiveSizePolicy need to be enabled."); - - if (!log_is_enabled(Debug, gc, ergo)) { - return false; - } - - // Print goal for which action is needed. - char* action = nullptr; - bool change_for_pause = false; - if ((change_old_gen_for_maj_pauses() == - decrease_old_gen_for_maj_pauses_true) || - (change_young_gen_for_min_pauses() == - decrease_young_gen_for_min_pauses_true)) { - action = (char*) " *** pause time goal ***"; - change_for_pause = true; - } else if ((change_old_gen_for_throughput() == - increase_old_gen_for_throughput_true) || - (change_young_gen_for_throughput() == - increase_young_gen_for_througput_true)) { - action = (char*) " *** throughput goal ***"; - } else if (decrease_for_footprint()) { - action = (char*) " *** reduced footprint ***"; - } else { - // No actions were taken. This can legitimately be the - // situation if not enough data has been gathered to make - // decisions. - return false; - } - - // Pauses - // Currently the size of the old gen is only adjusted to - // change the major pause times. - char* young_gen_action = nullptr; - char* tenured_gen_action = nullptr; - - char* shrink_msg = (char*) "(attempted to shrink)"; - char* grow_msg = (char*) "(attempted to grow)"; - char* no_change_msg = (char*) "(no change)"; - if (change_young_gen_for_min_pauses() == - decrease_young_gen_for_min_pauses_true) { - young_gen_action = shrink_msg; - } else if (change_for_pause) { - young_gen_action = no_change_msg; - } - - if (change_old_gen_for_maj_pauses() == decrease_old_gen_for_maj_pauses_true) { - tenured_gen_action = shrink_msg; - } else if (change_for_pause) { - tenured_gen_action = no_change_msg; - } - - // Throughput - if (change_old_gen_for_throughput() == increase_old_gen_for_throughput_true) { - assert(change_young_gen_for_throughput() == - increase_young_gen_for_througput_true, - "Both generations should be growing"); - young_gen_action = grow_msg; - tenured_gen_action = grow_msg; - } else if (change_young_gen_for_throughput() == - increase_young_gen_for_througput_true) { - // Only the young generation may grow at start up (before - // enough full collections have been done to grow the old generation). - young_gen_action = grow_msg; - tenured_gen_action = no_change_msg; - } - - // Minimum footprint - if (decrease_for_footprint() != 0) { - young_gen_action = shrink_msg; - tenured_gen_action = shrink_msg; - } - - log_debug(gc, ergo)("UseAdaptiveSizePolicy actions to meet %s", action); - log_debug(gc, ergo)(" GC overhead (%%)"); - log_debug(gc, ergo)(" Young generation: %7.2f\t %s", - 100.0 * avg_minor_gc_cost()->average(), young_gen_action); - log_debug(gc, ergo)(" Tenured generation: %7.2f\t %s", - 100.0 * avg_major_gc_cost()->average(), tenured_gen_action); - return true; -} - -void AdaptiveSizePolicy::print_tenuring_threshold( uint new_tenuring_threshold_arg) const { - // Tenuring threshold - if (decrement_tenuring_threshold_for_survivor_limit()) { - log_debug(gc, ergo)("Tenuring threshold: (attempted to decrease to avoid survivor space overflow) = %u", new_tenuring_threshold_arg); - } else if (decrement_tenuring_threshold_for_gc_cost()) { - log_debug(gc, ergo)("Tenuring threshold: (attempted to decrease to balance GC costs) = %u", new_tenuring_threshold_arg); - } else if (increment_tenuring_threshold_for_gc_cost()) { - log_debug(gc, ergo)("Tenuring threshold: (attempted to increase to balance GC costs) = %u", new_tenuring_threshold_arg); - } else { - assert(!tenuring_threshold_change(), "(no change was attempted)"); - } -} +} \ No newline at end of file diff --git a/src/hotspot/share/gc/shared/adaptiveSizePolicy.hpp b/src/hotspot/share/gc/shared/adaptiveSizePolicy.hpp index 37dac1a4ee6..a3848079a76 100644 --- a/src/hotspot/share/gc/shared/adaptiveSizePolicy.hpp +++ b/src/hotspot/share/gc/shared/adaptiveSizePolicy.hpp @@ -25,54 +25,32 @@ #ifndef SHARE_GC_SHARED_ADAPTIVESIZEPOLICY_HPP #define SHARE_GC_SHARED_ADAPTIVESIZEPOLICY_HPP +#include "gc/shared/gc_globals.hpp" #include "gc/shared/gcCause.hpp" -#include "gc/shared/gcOverheadChecker.hpp" #include "gc/shared/gcUtil.hpp" #include "memory/allocation.hpp" +#include "runtime/os.hpp" +#include "utilities/numberSeq.hpp" // This class keeps statistical information and computes the // size of the heap. -// Forward decls -class elapsedTimer; - class AdaptiveSizePolicy : public CHeapObj { - friend class GCAdaptivePolicyCounters; - friend class PSGCAdaptivePolicyCounters; protected: + // [0, 1]; closer to 1 means assigning more weight to most recent samples. + constexpr static double seq_default_alpha_value = 0.75; - enum GCPolicyKind { - _gc_adaptive_size_policy, - _gc_ps_adaptive_size_policy - }; - virtual GCPolicyKind kind() const { return _gc_adaptive_size_policy; } - - enum SizePolicyTrueValues { - decrease_young_gen_for_min_pauses_true = 1, - decrease_old_gen_for_maj_pauses_true = 2, - - increase_old_gen_for_throughput_true = 4, - increase_young_gen_for_througput_true = 5, - - decrease_young_gen_for_footprint_true = 6, - decrease_old_gen_for_footprint_true = 7, - decide_at_full_gc_true = 8 - }; + // Minimal distance between two consecutive GC pauses; shorter distance (more + // frequent gc) can hinder app throughput. Additionally, too frequent gc + // means objs haven't got time to die yet, so #promoted objs will be high. + // Default: 100ms. + static constexpr double MinGCDistanceSecond = 0.100; + static_assert(MinGCDistanceSecond >= 0.001, "inv"); // Goal for the fraction of the total time during which application // threads run const double _throughput_goal; - // Last calculated sizes, in bytes, and aligned - size_t _eden_size; // calculated eden free space in bytes - size_t _promo_size; // calculated promoted free space in bytes - - size_t _survivor_size; // calculated survivor size in bytes - - // Support for UseGCOverheadLimit - GCOverheadChecker _overhead_checker; - - // Minor collection timers used to determine both // pause and interval times for collections static elapsedTimer _minor_timer; @@ -80,44 +58,79 @@ class AdaptiveSizePolicy : public CHeapObj { // pause and interval times for collections static elapsedTimer _major_timer; - // Time statistics - AdaptivePaddedAverage* _avg_minor_pause; - AdaptiveWeightedAverage* _avg_minor_interval; - AdaptiveWeightedAverage* _avg_minor_gc_cost; + // To measure wall-clock time between two GCs, i.e. mutator running time, and record them. + elapsedTimer _gc_distance_timer; + NumberSeq _gc_distance_seconds_seq; - AdaptiveWeightedAverage* _avg_major_interval; - AdaptiveWeightedAverage* _avg_major_gc_cost; + static constexpr uint NumOfGCSample = 32; + // Recording the last NumOfGCSample number of minor/major gc durations + TruncatedSeq _trimmed_minor_gc_time_seconds; + TruncatedSeq _trimmed_major_gc_time_seconds; - // Footprint statistics - AdaptiveWeightedAverage* _avg_young_live; - AdaptiveWeightedAverage* _avg_eden_live; - AdaptiveWeightedAverage* _avg_old_live; + // A ring buffer with fixed size (NumOfGCSample) to record the most recent + // samples of gc-duration (minor and major) so that we can calculate + // mutator-wall-clock-time percentage for the given window. + class GCSampleRingBuffer { + double _start_instants[NumOfGCSample]; + double _durations[NumOfGCSample]; + double _duration_sum; + uint _sample_index; + uint _num_of_samples; - // Statistics for survivor space calculation for young generation - AdaptivePaddedAverage* _avg_survived; + public: + GCSampleRingBuffer() + : _duration_sum(0.0), _sample_index(0), _num_of_samples(0) {} - // Objects that have been directly allocated in the old generation - AdaptivePaddedNoZeroDevAverage* _avg_pretenured; + double duration_sum() const { return _duration_sum; } + + void record_sample(double gc_duration) { + if (_num_of_samples < NumOfGCSample) { + _num_of_samples++; + } else { + assert(_num_of_samples == NumOfGCSample, "inv"); + _duration_sum -= _durations[_sample_index]; + } + + double gc_start_instant = os::elapsedTime() - gc_duration; + _start_instants[_sample_index] = gc_start_instant; + _durations[_sample_index] = gc_duration; + _duration_sum += gc_duration; + + _sample_index = (_sample_index + 1) % NumOfGCSample; + } + + double trimmed_window_duration() const { + double current_time = os::elapsedTime(); + double oldest_gc_start_instant; + if (_num_of_samples < NumOfGCSample) { + oldest_gc_start_instant = _start_instants[0]; + } else { + oldest_gc_start_instant = _start_instants[_sample_index]; + } + return current_time - oldest_gc_start_instant; + } + }; + + GCSampleRingBuffer _gc_samples; + + // The number of bytes promoted to old-gen after a young-gc + NumberSeq _promoted_bytes; + + // The number of bytes in to-space after a young-gc + NumberSeq _survived_bytes; + + // The rate of promotion to old-gen + NumberSeq _promotion_rate_bytes_per_sec; + + // The peak of used bytes in old-gen before/after young/full-gc + NumberSeq _peak_old_used_bytes_seq; // Variable for estimating the major and minor pause times. // These variables represent linear least-squares fits of // the data. - // minor pause time vs. old gen size - LinearLeastSquareFit* _minor_pause_old_estimator; // minor pause time vs. young gen size LinearLeastSquareFit* _minor_pause_young_estimator; - // Variables for estimating the major and minor collection costs - // minor collection time vs. young gen size - LinearLeastSquareFit* _minor_collection_estimator; - // major collection time vs. old gen size - LinearLeastSquareFit* _major_collection_estimator; - - // These record the most recent collection times. They - // are available as an alternative to using the averages - // for making ergonomic decisions. - double _latest_minor_mutator_interval_seconds; - // Allowed difference between major and minor GC times, used // for computing tenuring_threshold const double _threshold_tolerance_percent; @@ -127,308 +140,110 @@ class AdaptiveSizePolicy : public CHeapObj { // Flag indicating that the adaptive policy is ready to use bool _young_gen_policy_is_ready; - // Decrease/increase the young generation for minor pause time - int _change_young_gen_for_min_pauses; - - // Decrease/increase the old generation for major pause time - int _change_old_gen_for_maj_pauses; - - // change old generation for throughput - int _change_old_gen_for_throughput; - - // change young generation for throughput - int _change_young_gen_for_throughput; - - // Flag indicating that the policy would - // increase the tenuring threshold because of the total major GC cost - // is greater than the total minor GC cost - bool _increment_tenuring_threshold_for_gc_cost; - // decrease the tenuring threshold because of the total minor GC - // cost is greater than the total major GC cost - bool _decrement_tenuring_threshold_for_gc_cost; - // decrease due to survivor size limit - bool _decrement_tenuring_threshold_for_survivor_limit; - - // decrease generation sizes for footprint - int _decrease_for_footprint; - - // Set if the ergonomic decisions were made at a full GC. - int _decide_at_full_gc; - - // Changing the generation sizing depends on the data that is - // gathered about the effects of changes on the pause times and - // throughput. These variable count the number of data points - // gathered. The policy may use these counters as a threshold - // for reliable data. - julong _young_gen_change_for_minor_throughput; - julong _old_gen_change_for_major_throughput; - // Accessors - double gc_pause_goal_sec() const { return _gc_pause_goal_sec; } - // The value returned is unitless: it's the proportion of time - // spent in a particular collection type. - // An interval time will be 0.0 if a collection type hasn't occurred yet. - // The 1.4.2 implementation put a floor on the values of major_gc_cost - // and minor_gc_cost. This was useful because of the way major_gc_cost - // and minor_gc_cost was used in calculating the sizes of the generations. - // Do not use a floor in this implementation because any finite value - // will put a limit on the throughput that can be achieved and any - // throughput goal above that limit will drive the generations sizes - // to extremes. - double major_gc_cost() const { - return MAX2(0.0F, _avg_major_gc_cost->average()); + + double minor_gc_time_sum() const { + return _trimmed_minor_gc_time_seconds.sum(); + } + double major_gc_time_sum() const { + return _trimmed_major_gc_time_seconds.sum(); } - // The value returned is unitless: it's the proportion of time - // spent in a particular collection type. - // An interval time will be 0.0 if a collection type hasn't occurred yet. - // The 1.4.2 implementation put a floor on the values of major_gc_cost - // and minor_gc_cost. This was useful because of the way major_gc_cost - // and minor_gc_cost was used in calculating the sizes of the generations. - // Do not use a floor in this implementation because any finite value - // will put a limit on the throughput that can be achieved and any - // throughput goal above that limit will drive the generations sizes - // to extremes. - - double minor_gc_cost() const { - return MAX2(0.0F, _avg_minor_gc_cost->average()); + void record_gc_duration(double gc_duration) { + _gc_samples.record_sample(gc_duration); } - // Because we're dealing with averages, gc_cost() can be - // larger than 1.0 if just the sum of the minor cost the - // the major cost is used. Worse than that is the - // fact that the minor cost and the major cost each - // tend toward 1.0 in the extreme of high GC costs. - // Limit the value of gc_cost to 1.0 so that the mutator - // cost stays non-negative. - virtual double gc_cost() const { - double result = MIN2(1.0, minor_gc_cost() + major_gc_cost()); - assert(result >= 0.0, "Both minor and major costs are non-negative"); - return result; + // Percent of GC wall-clock time. + double gc_time_percent() const { + double total_time = _gc_samples.trimmed_window_duration(); + double gc_time = _gc_samples.duration_sum(); + double gc_percent = gc_time / total_time; + assert(gc_percent <= 1.0, "inv"); + assert(gc_percent >= 0, "inv"); + return gc_percent; } - // Elapsed time since the last major collection. - virtual double time_since_major_gc() const; - - // Average interval between major collections to be used - // in calculating the decaying major GC cost. An overestimate - // of this time would be a conservative estimate because - // this time is used to decide if the major GC cost - // should be decayed (i.e., if the time since the last - // major GC is long compared to the time returned here, - // then the major GC cost will be decayed). See the - // implementations for the specifics. - virtual double major_gc_interval_average_for_decay() const { - return _avg_major_interval->average(); - } - - // Return the cost of the GC where the major GC cost - // has been decayed based on the time since the last - // major collection. - double decaying_gc_cost() const; - - // Decay the major GC cost. Use this only for decisions on - // whether to adjust, not to determine by how much to adjust. - // This approximation is crude and may not be good enough for the - // latter. - double decaying_major_gc_cost() const; - - // Return the mutator cost using the decayed - // GC cost. - double adjusted_mutator_cost() const { - double result = 1.0 - decaying_gc_cost(); - assert(result >= 0.0, "adjusted mutator cost calculation is incorrect"); - return result; - } - - virtual double mutator_cost() const { - double result = 1.0 - gc_cost(); - assert(result >= 0.0, "mutator cost calculation is incorrect"); - return result; - } - - bool young_gen_policy_is_ready() { return _young_gen_policy_is_ready; } - void update_minor_pause_young_estimator(double minor_pause_in_ms); - virtual void update_minor_pause_old_estimator(double minor_pause_in_ms) { - // This is not meaningful for all policies but needs to be present - // to use minor_collection_end() in its current form. - } - size_t eden_increment(size_t cur_eden); size_t eden_increment(size_t cur_eden, uint percent_change); - size_t eden_decrement(size_t cur_eden); - size_t promo_increment(size_t cur_eden); - size_t promo_increment(size_t cur_eden, uint percent_change); - size_t promo_decrement(size_t cur_eden); - virtual void clear_generation_free_space_flags(); - - int change_old_gen_for_throughput() const { - return _change_old_gen_for_throughput; - } - void set_change_old_gen_for_throughput(int v) { - _change_old_gen_for_throughput = v; - } - int change_young_gen_for_throughput() const { - return _change_young_gen_for_throughput; - } - void set_change_young_gen_for_throughput(int v) { - _change_young_gen_for_throughput = v; - } - - int change_old_gen_for_maj_pauses() const { - return _change_old_gen_for_maj_pauses; - } - void set_change_old_gen_for_maj_pauses(int v) { - _change_old_gen_for_maj_pauses = v; - } - - bool decrement_tenuring_threshold_for_gc_cost() const { - return _decrement_tenuring_threshold_for_gc_cost; - } - void set_decrement_tenuring_threshold_for_gc_cost(bool v) { - _decrement_tenuring_threshold_for_gc_cost = v; - } - bool increment_tenuring_threshold_for_gc_cost() const { - return _increment_tenuring_threshold_for_gc_cost; - } - void set_increment_tenuring_threshold_for_gc_cost(bool v) { - _increment_tenuring_threshold_for_gc_cost = v; - } - bool decrement_tenuring_threshold_for_survivor_limit() const { - return _decrement_tenuring_threshold_for_survivor_limit; - } - void set_decrement_tenuring_threshold_for_survivor_limit(bool v) { - _decrement_tenuring_threshold_for_survivor_limit = v; - } - // Return true if the policy suggested a change. - bool tenuring_threshold_change() const; - - public: - AdaptiveSizePolicy(size_t init_eden_size, - size_t init_promo_size, - size_t init_survivor_size, - double gc_pause_goal_sec, +public: + AdaptiveSizePolicy(double gc_pause_goal_sec, uint gc_cost_ratio); - bool is_gc_ps_adaptive_size_policy() { - return kind() == _gc_ps_adaptive_size_policy; + void record_gc_pause_end_instant() { + _gc_distance_timer.reset(); + _gc_distance_timer.start(); } - AdaptivePaddedAverage* avg_minor_pause() const { return _avg_minor_pause; } - AdaptiveWeightedAverage* avg_minor_interval() const { - return _avg_minor_interval; - } - AdaptiveWeightedAverage* avg_minor_gc_cost() const { - return _avg_minor_gc_cost; + void record_gc_pause_start_instant() { + _gc_distance_timer.stop(); + _gc_distance_seconds_seq.add(_gc_distance_timer.seconds()); } - AdaptiveWeightedAverage* avg_major_gc_cost() const { - return _avg_major_gc_cost; + double minor_gc_time_estimate() const { + return _trimmed_minor_gc_time_seconds.davg() + + _trimmed_minor_gc_time_seconds.dsd(); } - AdaptiveWeightedAverage* avg_young_live() const { return _avg_young_live; } - AdaptiveWeightedAverage* avg_eden_live() const { return _avg_eden_live; } - AdaptiveWeightedAverage* avg_old_live() const { return _avg_old_live; } + double minor_gc_time_conservative_estimate() const { + double davg_plus_dsd = _trimmed_minor_gc_time_seconds.davg() + + _trimmed_minor_gc_time_seconds.dsd(); + double avg_plus_sd = _trimmed_minor_gc_time_seconds.avg() + + _trimmed_minor_gc_time_seconds.sd(); + return MAX2(davg_plus_dsd, avg_plus_sd); + } + + double major_gc_time_estimate() const { + return _trimmed_major_gc_time_seconds.davg() + + _trimmed_major_gc_time_seconds.dsd(); + } + + void sample_old_gen_used_bytes(size_t used_bytes) { + _peak_old_used_bytes_seq.add(used_bytes); + } + + double peak_old_gen_used_estimate() const { + return _peak_old_used_bytes_seq.davg() + + _peak_old_used_bytes_seq.dsd(); + } + + double promoted_bytes_estimate() const { + return _promoted_bytes.davg() + + _promoted_bytes.dsd(); + } + + double promotion_rate_bytes_per_sec_estimate() const { + return _promotion_rate_bytes_per_sec.davg() + + _promotion_rate_bytes_per_sec.dsd(); + } + + double survived_bytes_estimate() const { + // Conservative estimate to minimize promotion to old-gen + double avg_plus_sd = _survived_bytes.avg() + + _survived_bytes.sd(); + double davg_plus_dsd = _survived_bytes.davg() + + _survived_bytes.dsd(); + return MAX2(avg_plus_sd, davg_plus_dsd); + } + + // Percent of mutator wall-clock time. + double mutator_time_percent() const { + double result = 1.0 - gc_time_percent(); + return result; + } // Methods indicating events of interest to the adaptive size policy, // called by GC algorithms. It is the responsibility of users of this // policy to call these methods at the correct times! - virtual void minor_collection_begin(); - virtual void minor_collection_end(GCCause::Cause gc_cause); + void minor_collection_begin(); + void minor_collection_end(size_t eden_capacity_in_bytes); LinearLeastSquareFit* minor_pause_young_estimator() { return _minor_pause_young_estimator; } - LinearLeastSquareFit* minor_collection_estimator() { - return _minor_collection_estimator; - } - - LinearLeastSquareFit* major_collection_estimator() { - return _major_collection_estimator; - } - - double minor_pause_young_slope() { - return _minor_pause_young_estimator->slope(); - } - - double minor_collection_slope() { return _minor_collection_estimator->slope();} - double major_collection_slope() { return _major_collection_estimator->slope();} - - double minor_pause_old_slope() { - return _minor_pause_old_estimator->slope(); - } - - void set_eden_size(size_t new_size) { - _eden_size = new_size; - } - void set_survivor_size(size_t new_size) { - _survivor_size = new_size; - } - - size_t calculated_eden_size_in_bytes() const { - return _eden_size; - } - - size_t calculated_promo_size_in_bytes() const { - return _promo_size; - } - - size_t calculated_survivor_size_in_bytes() const { - return _survivor_size; - } - - bool gc_overhead_limit_exceeded() { - return _overhead_checker.gc_overhead_limit_exceeded(); - } - void set_gc_overhead_limit_exceeded(bool v) { - _overhead_checker.set_gc_overhead_limit_exceeded(v); - } - - void reset_gc_overhead_limit_count() { - _overhead_checker.reset_gc_overhead_limit_count(); - } - // accessors for flags recording the decisions to resize the - // generations to meet the pause goal. - - int change_young_gen_for_min_pauses() const { - return _change_young_gen_for_min_pauses; - } - void set_change_young_gen_for_min_pauses(int v) { - _change_young_gen_for_min_pauses = v; - } - void set_decrease_for_footprint(int v) { _decrease_for_footprint = v; } - int decrease_for_footprint() const { return _decrease_for_footprint; } - int decide_at_full_gc() { return _decide_at_full_gc; } - void set_decide_at_full_gc(int v) { _decide_at_full_gc = v; } - - // Check the conditions for an out-of-memory due to excessive GC time. - // Set _gc_overhead_limit_exceeded if all the conditions have been met. - void check_gc_overhead_limit(size_t eden_live, - size_t max_old_gen_size, - size_t max_eden_size, - bool is_full_gc, - GCCause::Cause gc_cause, - SoftRefPolicy* soft_ref_policy); - - static bool should_update_promo_stats(GCCause::Cause cause) { - return ((GCCause::is_user_requested_gc(cause) && - UseAdaptiveSizePolicyWithSystemGC) || - GCCause::is_tenured_allocation_failure_gc(cause)); - } - - static bool should_update_eden_stats(GCCause::Cause cause) { - return ((GCCause::is_user_requested_gc(cause) && - UseAdaptiveSizePolicyWithSystemGC) || - GCCause::is_allocation_failure_gc(cause)); - } - - // Printing support - virtual bool print() const; - void print_tenuring_threshold(uint new_tenuring_threshold) const; }; #endif // SHARE_GC_SHARED_ADAPTIVESIZEPOLICY_HPP diff --git a/src/hotspot/share/gc/shared/gcOverheadChecker.cpp b/src/hotspot/share/gc/shared/gcOverheadChecker.cpp deleted file mode 100644 index c6c89a51f54..00000000000 --- a/src/hotspot/share/gc/shared/gcOverheadChecker.cpp +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2019, Google and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - * - */ - -#include "gc/shared/gcOverheadChecker.hpp" -#include "gc/shared/softRefPolicy.hpp" -#include "logging/log.hpp" - -GCOverheadChecker::GCOverheadChecker() : - _gc_overhead_limit_exceeded(false), - _gc_overhead_limit_count(0) { - assert(GCOverheadLimitThreshold > 0, - "No opportunity to clear SoftReferences before GC overhead limit"); -} - -void GCOverheadChecker::check_gc_overhead_limit(GCOverheadTester* time_overhead, - GCOverheadTester* space_overhead, - bool is_full_gc, - GCCause::Cause gc_cause, - SoftRefPolicy* soft_ref_policy) { - if (is_full_gc) { - // Explicit Full GC would do the clearing of soft-refs as well - // So reset in the beginning - soft_ref_policy->set_should_clear_all_soft_refs(false); - } - // Ignore explicit GC's. Exiting here does not set the flag and - // does not reset the count. - if (GCCause::is_user_requested_gc(gc_cause) || - GCCause::is_serviceability_requested_gc(gc_cause)) { - return; - } - - bool print_gc_overhead_limit_would_be_exceeded = false; - if (is_full_gc) { - if (time_overhead->is_exceeded() && space_overhead->is_exceeded()) { - // Collections, on average, are taking too much time, and - // we have too little space available after a full gc. - // At this point the GC overhead limit is being exceeded. - _gc_overhead_limit_count++; - if (UseGCOverheadLimit) { - if (_gc_overhead_limit_count >= GCOverheadLimitThreshold){ - // All conditions have been met for throwing an out-of-memory - set_gc_overhead_limit_exceeded(true); - // Avoid consecutive OOM due to the gc time limit by resetting - // the counter. - reset_gc_overhead_limit_count(); - } else { - // The required consecutive collections which exceed the - // GC time limit may or may not have been reached. We - // are approaching that condition and so as not to - // throw an out-of-memory before all SoftRef's have been - // cleared, set _should_clear_all_soft_refs in SoftRefPolicy. - // The clearing will be done on the next GC. - bool near_limit = gc_overhead_limit_near(); - if (near_limit) { - soft_ref_policy->set_should_clear_all_soft_refs(true); - log_trace(gc, ergo)("Nearing GC overhead limit, will be clearing all SoftReference"); - } - } - } - // Set this even when the overhead limit will not - // cause an out-of-memory. Diagnostic message indicating - // that the overhead limit is being exceeded is sometimes - // printed. - print_gc_overhead_limit_would_be_exceeded = true; - - } else { - // Did not exceed overhead limits - reset_gc_overhead_limit_count(); - } - } - - if (UseGCOverheadLimit) { - if (gc_overhead_limit_exceeded()) { - log_trace(gc, ergo)("GC is exceeding overhead limit of %u%%", GCTimeLimit); - reset_gc_overhead_limit_count(); - } else if (print_gc_overhead_limit_would_be_exceeded) { - assert(_gc_overhead_limit_count > 0, "Should not be printing"); - log_trace(gc, ergo)("GC would exceed overhead limit of %u%% %d consecutive time(s)", - GCTimeLimit, _gc_overhead_limit_count); - } - } -} diff --git a/src/hotspot/share/gc/shared/gcOverheadChecker.hpp b/src/hotspot/share/gc/shared/gcOverheadChecker.hpp deleted file mode 100644 index e29ae2ab911..00000000000 --- a/src/hotspot/share/gc/shared/gcOverheadChecker.hpp +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2019, Google and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - * - */ - -#ifndef SHARE_GC_SHARED_GCOVERHEADCHECKER_HPP -#define SHARE_GC_SHARED_GCOVERHEADCHECKER_HPP - -#include "gc/shared/gc_globals.hpp" -#include "gc/shared/gcCause.hpp" -#include "memory/allocation.hpp" -#include "runtime/globals.hpp" - -class SoftRefPolicy; - -class GCOverheadTester: public StackObj { -public: - virtual bool is_exceeded() = 0; -}; - -class GCOverheadChecker: public CHeapObj { - // This is a hint for the heap: we've detected that GC times - // are taking longer than GCTimeLimit allows. - bool _gc_overhead_limit_exceeded; - // Count of consecutive GC that have exceeded the - // GC time limit criterion - uint _gc_overhead_limit_count; - // This flag signals that GCTimeLimit is being exceeded - // but may not have done so for the required number of consecutive - // collections - -public: - GCOverheadChecker(); - - // This is a hint for the heap: we've detected that gc times - // are taking longer than GCTimeLimit allows. - // Most heaps will choose to throw an OutOfMemoryError when - // this occurs but it is up to the heap to request this information - // of the policy - bool gc_overhead_limit_exceeded() { - return _gc_overhead_limit_exceeded; - } - void set_gc_overhead_limit_exceeded(bool v) { - _gc_overhead_limit_exceeded = v; - } - - // Tests conditions indicate the GC overhead limit is being approached. - bool gc_overhead_limit_near() { - return _gc_overhead_limit_count >= (GCOverheadLimitThreshold - 1); - } - void reset_gc_overhead_limit_count() { - _gc_overhead_limit_count = 0; - } - - // Check the conditions for an out-of-memory due to excessive GC time. - // Set _gc_overhead_limit_exceeded if all the conditions have been met. - void check_gc_overhead_limit(GCOverheadTester* time_overhead, - GCOverheadTester* space_overhead, - bool is_full_gc, - GCCause::Cause gc_cause, - SoftRefPolicy* soft_ref_policy); -}; - -#endif // SHARE_GC_SHARED_GCOVERHEADCHECKER_HPP diff --git a/src/hotspot/share/gc/shared/gcPolicyCounters.cpp b/src/hotspot/share/gc/shared/gcPolicyCounters.cpp index 343ad6ca41e..028a9ba145d 100644 --- a/src/hotspot/share/gc/shared/gcPolicyCounters.cpp +++ b/src/hotspot/share/gc/shared/gcPolicyCounters.cpp @@ -59,10 +59,5 @@ GCPolicyCounters::GCPolicyCounters(const char* name, int collectors, _desired_survivor_size = PerfDataManager::create_variable(SUN_GC, cname, PerfData::U_Bytes, CHECK); - - cname = PerfDataManager::counter_name(_name_space, "gcTimeLimitExceeded"); - _gc_overhead_limit_exceeded_counter = - PerfDataManager::create_variable(SUN_GC, cname, PerfData::U_Events, - CHECK); } } diff --git a/src/hotspot/share/gc/shared/gcPolicyCounters.hpp b/src/hotspot/share/gc/shared/gcPolicyCounters.hpp index 5c5ee0beec7..1f8026d21ac 100644 --- a/src/hotspot/share/gc/shared/gcPolicyCounters.hpp +++ b/src/hotspot/share/gc/shared/gcPolicyCounters.hpp @@ -41,7 +41,6 @@ class GCPolicyCounters: public CHeapObj { PerfVariable* _tenuring_threshold; PerfVariable* _desired_survivor_size; - PerfVariable* _gc_overhead_limit_exceeded_counter; const char* _name_space; @@ -56,10 +55,6 @@ public: return _desired_survivor_size; } - inline PerfVariable* gc_overhead_limit_exceeded_counter() const { - return _gc_overhead_limit_exceeded_counter; - } - const char* name_space() const { return _name_space; } }; diff --git a/src/hotspot/share/gc/shared/gc_globals.hpp b/src/hotspot/share/gc/shared/gc_globals.hpp index cb2ec87416f..7f46f449085 100644 --- a/src/hotspot/share/gc/shared/gc_globals.hpp +++ b/src/hotspot/share/gc/shared/gc_globals.hpp @@ -304,56 +304,17 @@ product(bool, UseAdaptiveSizePolicy, true, \ "Use adaptive generation sizing policies") \ \ - product(bool, UsePSAdaptiveSurvivorSizePolicy, true, \ - "Use adaptive survivor sizing policies") \ - \ - product(bool, UseAdaptiveGenerationSizePolicyAtMinorCollection, true, \ - "Use adaptive young-old sizing policies at minor collections") \ - \ - product(bool, UseAdaptiveGenerationSizePolicyAtMajorCollection, true, \ - "Use adaptive young-old sizing policies at major collections") \ - \ - product(bool, UseAdaptiveSizePolicyWithSystemGC, false, \ - "Include statistics from System.gc() for adaptive size policy") \ - \ - product(uint, AdaptiveSizeThroughPutPolicy, 0, \ - "Policy for changing generation size for throughput goals") \ - range(0, 1) \ - \ - product(uintx, AdaptiveSizePolicyInitializingSteps, 20, \ - "Number of steps where heuristics is used before data is used") \ - range(0, max_uintx) \ - \ develop(uintx, AdaptiveSizePolicyReadyThreshold, 5, \ "Number of collections before the adaptive sizing is started") \ \ - product(uintx, AdaptiveSizePolicyOutputInterval, 0, \ - "Collection interval for printing information; zero means never") \ - range(0, max_uintx) \ - \ - product(bool, UseAdaptiveSizePolicyFootprintGoal, true, \ - "Use adaptive minimum footprint as a goal") \ - \ product(uint, AdaptiveSizePolicyWeight, 10, \ "Weight given to exponential resizing, between 0 and 100") \ range(0, 100) \ \ - product(uint, AdaptiveTimeWeight, 25, \ - "Weight given to time in adaptive policy, between 0 and 100") \ - range(0, 100) \ - \ - product(uint, PausePadding, 1, \ - "How much buffer to keep for pause time") \ - range(0, UINT_MAX) \ - \ product(uint, PromotedPadding, 3, \ "How much buffer to keep for promotion failure") \ range(0, UINT_MAX) \ \ - product(uint, SurvivorPadding, 3, \ - "How much buffer to keep for survivor overflow") \ - range(0, UINT_MAX) \ - \ product(uint, ThresholdTolerance, 10, \ "Allowed collection cost difference between generations") \ range(0, 100) \ @@ -370,18 +331,6 @@ "Decay factor to YoungGenerationSizeSupplement") \ range(1, max_uintx) \ \ - product(uint, TenuredGenerationSizeIncrement, 20, \ - "Adaptive size percentage change in tenured generation") \ - range(0, 100) \ - \ - product(uint, TenuredGenerationSizeSupplement, 80, \ - "Supplement to TenuredGenerationSizeIncrement used at startup") \ - range(0, 100) \ - \ - product(uintx, TenuredGenerationSizeSupplementDecay, 2, \ - "Decay factor to TenuredGenerationSizeIncrement") \ - range(1, max_uintx) \ - \ product(uintx, MaxGCPauseMillis, max_uintx - 1, \ "Adaptive size policy maximum GC pause time goal in millisecond, "\ "or (G1 Only) the maximum GC time per MMU time slice") \ @@ -400,13 +349,6 @@ "Adaptive size scale down factor for shrinking") \ range(1, max_uintx) \ \ - product(bool, UseAdaptiveSizeDecayMajorGCCost, true, \ - "Adaptive size decays the major cost for long major intervals") \ - \ - product(uintx, AdaptiveSizeMajorGCDecayTimeScale, 10, \ - "Time scale over which major costs decay") \ - range(0, max_uintx) \ - \ product(uintx, MinSurvivorRatio, 3, \ "Minimum ratio of young generation/survivor space size") \ range(3, max_uintx) \ diff --git a/src/hotspot/share/runtime/arguments.cpp b/src/hotspot/share/runtime/arguments.cpp index d2a1c31282f..c5e2f0c467c 100644 --- a/src/hotspot/share/runtime/arguments.cpp +++ b/src/hotspot/share/runtime/arguments.cpp @@ -550,6 +550,23 @@ static SpecialFlag const special_jvm_flags[] = { { "NearCpool", JDK_Version::undefined(), JDK_Version::jdk(25), JDK_Version::undefined() }, #endif + { "AdaptiveSizeMajorGCDecayTimeScale", JDK_Version::undefined(), JDK_Version::jdk(26), JDK_Version::jdk(27) }, + { "AdaptiveSizePolicyInitializingSteps", JDK_Version::undefined(), JDK_Version::jdk(26), JDK_Version::jdk(27) }, + { "AdaptiveSizePolicyOutputInterval", JDK_Version::undefined(), JDK_Version::jdk(26), JDK_Version::jdk(27) }, + { "AdaptiveSizeThroughPutPolicy", JDK_Version::undefined(), JDK_Version::jdk(26), JDK_Version::jdk(27) }, + { "AdaptiveTimeWeight", JDK_Version::undefined(), JDK_Version::jdk(26), JDK_Version::jdk(27) }, + { "PausePadding", JDK_Version::undefined(), JDK_Version::jdk(26), JDK_Version::jdk(27) }, + { "SurvivorPadding", JDK_Version::undefined(), JDK_Version::jdk(26), JDK_Version::jdk(27) }, + { "TenuredGenerationSizeIncrement", JDK_Version::undefined(), JDK_Version::jdk(26), JDK_Version::jdk(27) }, + { "TenuredGenerationSizeSupplement", JDK_Version::undefined(), JDK_Version::jdk(26), JDK_Version::jdk(27) }, + { "TenuredGenerationSizeSupplementDecay", JDK_Version::undefined(), JDK_Version::jdk(26), JDK_Version::jdk(27) }, + { "UseAdaptiveGenerationSizePolicyAtMajorCollection", JDK_Version::undefined(), JDK_Version::jdk(26), JDK_Version::jdk(27) }, + { "UseAdaptiveGenerationSizePolicyAtMinorCollection", JDK_Version::undefined(), JDK_Version::jdk(26), JDK_Version::jdk(27) }, + { "UseAdaptiveSizeDecayMajorGCCost", JDK_Version::undefined(), JDK_Version::jdk(26), JDK_Version::jdk(27) }, + { "UseAdaptiveSizePolicyFootprintGoal", JDK_Version::undefined(), JDK_Version::jdk(26), JDK_Version::jdk(27) }, + { "UseAdaptiveSizePolicyWithSystemGC", JDK_Version::undefined(), JDK_Version::jdk(26), JDK_Version::jdk(27) }, + { "UsePSAdaptiveSurvivorSizePolicy", JDK_Version::undefined(), JDK_Version::jdk(26), JDK_Version::jdk(27) }, + #ifdef ASSERT { "DummyObsoleteTestFlag", JDK_Version::undefined(), JDK_Version::jdk(18), JDK_Version::undefined() }, #endif diff --git a/src/jdk.internal.jvmstat/share/classes/sun/jvmstat/perfdata/resources/aliasmap b/src/jdk.internal.jvmstat/share/classes/sun/jvmstat/perfdata/resources/aliasmap index e19521c83a9..7d9ba8d2251 100644 --- a/src/jdk.internal.jvmstat/share/classes/sun/jvmstat/perfdata/resources/aliasmap +++ b/src/jdk.internal.jvmstat/share/classes/sun/jvmstat/perfdata/resources/aliasmap @@ -404,115 +404,22 @@ alias sun.gc.lastCause // 1.5.0 b39 hotspot.gc.last_cause // 1.4.2_02 // sun.gc.policy -alias sun.gc.policy.avgMajorIntervalTime // 1.5.0 b39 - hotspot.gc.policy.avg_major_interval // 1.5.0 b21 -alias sun.gc.policy.avgMajorPauseTime // 1.5.0 b39 - hotspot.gc.policy.avg_major_pause // 1.5.0 b21 -alias sun.gc.policy.avgMinorIntervalTime // 1.5.0 b39 - hotspot.gc.policy.avg_minor_interval // 1.5.0 b21 -alias sun.gc.policy.avgMinorPauseTime // 1.5.0 b39 - hotspot.gc.policy.avg_minor_pause // 1.5.0 b21 -alias sun.gc.policy.avgOldLive // 1.5.0 b39 - hotspot.gc.policy.avg_old_live // 1.5.0 b21 -alias sun.gc.policy.avgPretenuredPaddedAvg // 1.5.0 b39 - hotspot.gc.policy.avg_pretenured_padded_avg // 1.5.0 b21 -alias sun.gc.policy.avgPromotedAvg // 1.5.0 b39 - hotspot.gc.policy.avg_promoted_avg // 1.5.0 b21 -alias sun.gc.policy.avgPromotedDev // 1.5.0 b39 - hotspot.gc.policy.avg_promoted_dev // 1.5.0 b21 -alias sun.gc.policy.avgPromotedPaddedAvg // 1.5.0 b39 - hotspot.gc.policy.avg_promoted_padded_avg // 1.5.0 b21 -alias sun.gc.policy.avgSurvivedAvg // 1.5.0 b39 - hotspot.gc.policy.avg_survived_avg // 1.5.0 b21 -alias sun.gc.policy.avgSurvivedDev // 1.5.0 b39 - hotspot.gc.policy.avg_survived_dev // 1.5.0 b21 -alias sun.gc.policy.avgSurvivedPaddedAvg // 1.5.0 b39 - hotspot.gc.policy.avg_survived_padded_avg // 1.5.0 b21 -alias sun.gc.policy.avgYoungLive // 1.5.0 b39 - hotspot.gc.policy.avg_young_live // 1.5.0 b21 -alias sun.gc.policy.boundaryMoved // 1.5.0 b39 - hotspot.gc.policy.boundary_moved // 1.5.0 b21 -alias sun.gc.policy.changeOldGenForMajPauses // 1.5.0 b39 - hotspot.gc.policy.change_old_gen_for_maj_pauses // 1.5.0 b21 -alias sun.gc.policy.changeOldGenForMinPauses // 1.5.0 b39 - hotspot.gc.policy.change_old_gen_for_min_pauses // 1.5.0 b21 -alias sun.gc.policy.changeYoungGenForMajPauses // 1.5.0 b39 - hotspot.gc.policy.change_young_gen_for_maj_pauses // 1.5.0 b21 -alias sun.gc.policy.changeYoungGenForMinPauses // 1.5.0 b39 - hotspot.gc.policy.change_young_gen_for_min_pauses // 1.5.0 b21 alias sun.gc.policy.collectors // 1.5.0 b39 hotspot.gc.policy.collectors // 1.4.2 -alias sun.gc.policy.decideAtFullGc // 1.5.0 b39 - hotspot.gc.policy.decide_at_full_gc // 1.5.0 b21 -alias sun.gc.policy.decreaseForFootprint // 1.5.0 b39 - hotspot.gc.policy.decrease_for_footprint // 1.5.0 b21 -alias sun.gc.policy.decrementTenuringThresholdForGcCost // 1.5.0 b39 - hotspot.gc.policy.decrement_tenuring_threshold_for_gc_cost // 1.5.0 b21 -alias sun.gc.policy.decrementTenuringThresholdForSurvivorLimit // 1.5.0 b39 - hotspot.gc.policy.decrement_tenuring_threshold_for_survivor_limit // 1.5.0 b21 alias sun.gc.policy.desiredSurvivorSize // 1.5.0 b39 hotspot.gc.policy.desired_survivor_size // 1.5.0 b21 hotspot.gc.agetable.dss // 1.4.1 -alias sun.gc.policy.edenSize // 1.5.0 b39 - hotspot.gc.policy.eden_size // 1.5.0 b21 -alias sun.gc.policy.freeSpace // 1.5.0 b39 - hotspot.gc.policy.free_space // 1.5.0 b21 alias sun.gc.policy.gcTimeLimitExceeded // 1.5.0 b39 hotspot.gc.policy.gc_time_limit_exceeded // 1.5.0 b21 alias sun.gc.policy.generations // 1.5.0 b39 hotspot.gc.policy.generations // 1.4.2 -alias sun.gc.policy.increaseOldGenForThroughput // 1.5.0 b39 - hotspot.gc.policy.increase_old_gen_for_throughput // 1.5.0 b21 -alias sun.gc.policy.increaseYoungGenForThroughput // 1.5.0 b39 - hotspot.gc.policy.increase_young_gen_for_throughput // 1.5.0 b21 -alias sun.gc.policy.incrementTenuringThresholdForGcCost // 1.5.0 b39 - hotspot.gc.policy.increment_tenuring_threshold_for_gc_cost // 1.5.0 b21 -alias sun.gc.policy.liveAtLastFullGc // 1.5.0 b39 - hotspot.gc.policy.live_at_last_full_gc // 1.5.0 b21 -alias sun.gc.policy.liveSpace // 1.5.0 b39 - hotspot.gc.policy.live_space // 1.5.0 b21 -alias sun.gc.policy.majorCollectionSlope // 1.5.0 b39 - hotspot.gc.policy.major_collection_slope // 1.5.0 b21 -alias sun.gc.policy.majorGcCost // 1.5.0 b39 - hotspot.gc.policy.major_gc_cost // 1.5.0 b21 -alias sun.gc.policy.majorPauseOldSlope // 1.5.0 b39 - hotspot.gc.policy.major_pause_old_slope // 1.5.0 b21 -alias sun.gc.policy.majorPauseYoungSlope // 1.5.0 b39 - hotspot.gc.policy.major_pause_young_slope // 1.5.0 b21 alias sun.gc.policy.maxTenuringThreshold // 1.5.0 b39 hotspot.gc.max_tenuring_threshold // 1.5.0 b21 hotspot.gc.agetable.mtt // 1.4.1 -alias sun.gc.policy.minorCollectionSlope // 1.5.0 b39 - hotspot.gc.policy.minor_collection_slope // 1.5.0 b21 -alias sun.gc.policy.minorGcCost // 1.5.0 b39 - hotspot.gc.policy.minor_gc_cost // 1.5.0 b21 -alias sun.gc.policy.minorPauseOldSlope // 1.5.0 b39 - hotspot.gc.policy.minor_pause_old_slope // 1.5.0 b21 -alias sun.gc.policy.minorPauseYoungSlope // 1.5.0 b39 - hotspot.gc.policy.minor_pause_young_slope // 1.5.0 b21 -alias sun.gc.policy.mutatorCost // 1.5.0 b39 - hotspot.gc.policy.mutator_cost // 1.5.0 b21 alias sun.gc.policy.name // 1.5.0 b39 hotspot.gc.policy.name // 1.5.0 b21 -alias sun.gc.policy.oldCapacity // 1.5.0 b39 - hotspot.gc.policy.old_capacity // 1.5.0 b21 -alias sun.gc.policy.oldEdenSize // 1.5.0 b39 - hotspot.gc.policy.old_eden_size // 1.5.0 b21 -alias sun.gc.policy.oldPromoSize // 1.5.0 b39 - hotspot.gc.policy.old_promo_size // 1.5.0 b21 -alias sun.gc.policy.promoSize // 1.5.0 b39 - hotspot.gc.policy.promo_size // 1.5.0 b21 -alias sun.gc.policy.promoted // 1.5.0 b39 - hotspot.gc.policy.promoted // 1.5.0 b21 -alias sun.gc.policy.survived // 1.5.0 b39 - hotspot.gc.policy.survived // 1.5.0 b21 -alias sun.gc.policy.survivorOverflowed // 1.5.0 b39 - hotspot.gc.policy.survivor_overflowed // 1.5.0 b21 alias sun.gc.policy.tenuringThreshold // 1.5.0 b39 hotspot.gc.policy.tenuring_threshold // 1.5.0 b21 - hotspot.gc.agetable.tt // 1.4.1 -alias sun.gc.policy.youngCapacity // 1.5.0 b39 - hotspot.gc.policy.young_capacity // 1.5.0 b21 // sun.gc.tlab alias sun.gc.tlab.alloc // 1.5.0 b39 diff --git a/test/hotspot/gtest/gc/parallel/test_psAdaptiveSizePolicy.cpp b/test/hotspot/gtest/gc/parallel/test_psAdaptiveSizePolicy.cpp deleted file mode 100644 index 5a9d6b2c762..00000000000 --- a/test/hotspot/gtest/gc/parallel/test_psAdaptiveSizePolicy.cpp +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - * - */ - -#include "gc/parallel/psAdaptiveSizePolicy.hpp" -#include "utilities/macros.hpp" -#include "unittest.hpp" - -#if INCLUDE_PARALLELGC - - TEST_VM(gc, oldFreeSpaceCalculation) { - - struct TestCase { - size_t live; - uintx ratio; - size_t expectedResult; - }; - - TestCase test_cases[] = { - {100, 20, 25}, - {100, 50, 100}, - {100, 60, 150}, - {100, 75, 300}, - {400, 20, 100}, - {400, 50, 400}, - {400, 60, 600}, - {400, 75, 1200}, - }; - - size_t array_len = sizeof(test_cases) / sizeof(TestCase); - for (size_t i = 0; i < array_len; ++i) { - ASSERT_EQ(PSAdaptiveSizePolicy::calculate_free_based_on_live( - test_cases[i].live, test_cases[i].ratio), - test_cases[i].expectedResult) - << " Calculation of free memory failed" - << " - Test case " << i << ": live = " << test_cases[i].live - << "; ratio = " << test_cases[i].ratio; - } - } -#endif diff --git a/test/hotspot/jtreg/gc/parallel/TestDynShrinkHeap.java b/test/hotspot/jtreg/gc/parallel/TestDynShrinkHeap.java index 874087ba583..08dfb46b8af 100644 --- a/test/hotspot/jtreg/gc/parallel/TestDynShrinkHeap.java +++ b/test/hotspot/jtreg/gc/parallel/TestDynShrinkHeap.java @@ -31,7 +31,7 @@ package gc.parallel; * @modules java.base/jdk.internal.misc * @modules jdk.management * @library /test/lib / - * @run main/othervm -XX:+UseAdaptiveSizePolicyWithSystemGC -XX:+UseParallelGC -XX:MinHeapFreeRatio=0 -XX:MaxHeapFreeRatio=100 -Xmx1g -verbose:gc gc.parallel.TestDynShrinkHeap + * @run main/othervm -XX:+UseParallelGC -XX:MinHeapFreeRatio=0 -XX:MaxHeapFreeRatio=100 -Xmx1g -verbose:gc gc.parallel.TestDynShrinkHeap */ import jdk.test.lib.management.DynamicVMOption; import java.lang.management.ManagementFactory; From 2da0cdadb898efb9af827374368471102bfe0ccd Mon Sep 17 00:00:00 2001 From: Ao Qi Date: Thu, 24 Jul 2025 01:33:38 +0000 Subject: [PATCH 63/94] 8363895: Minimal build fails with slowdebug builds after JDK-8354887 Reviewed-by: kvn, shade --- src/hotspot/share/code/aotCodeCache.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hotspot/share/code/aotCodeCache.hpp b/src/hotspot/share/code/aotCodeCache.hpp index 702ce2565df..69fd7549b43 100644 --- a/src/hotspot/share/code/aotCodeCache.hpp +++ b/src/hotspot/share/code/aotCodeCache.hpp @@ -379,8 +379,8 @@ public: static void init2() NOT_CDS_RETURN; static void close() NOT_CDS_RETURN; static bool is_on() CDS_ONLY({ return cache() != nullptr && !_cache->closing(); }) NOT_CDS_RETURN_(false); - static bool is_on_for_use() { return is_on() && _cache->for_use(); } - static bool is_on_for_dump() { return is_on() && _cache->for_dump(); } + static bool is_on_for_use() CDS_ONLY({ return is_on() && _cache->for_use(); }) NOT_CDS_RETURN_(false); + static bool is_on_for_dump() CDS_ONLY({ return is_on() && _cache->for_dump(); }) NOT_CDS_RETURN_(false); static bool is_dumping_stub() NOT_CDS_RETURN_(false); static bool is_dumping_adapter() NOT_CDS_RETURN_(false); static bool is_using_stub() NOT_CDS_RETURN_(false); From b746701e5769a7a5a1e7900ddfdd285706ac5fe1 Mon Sep 17 00:00:00 2001 From: Dingli Zhang Date: Thu, 24 Jul 2025 01:37:33 +0000 Subject: [PATCH 64/94] 8363898: RISC-V: TestRangeCheckHoistingScaledIV.java fails after JDK-8355293 when running without RVV Reviewed-by: fyang, mli, syan --- .../compiler/rangechecks/TestRangeCheckHoistingScaledIV.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/hotspot/jtreg/compiler/rangechecks/TestRangeCheckHoistingScaledIV.java b/test/hotspot/jtreg/compiler/rangechecks/TestRangeCheckHoistingScaledIV.java index 4b4026c452a..62fb27ecb0f 100644 --- a/test/hotspot/jtreg/compiler/rangechecks/TestRangeCheckHoistingScaledIV.java +++ b/test/hotspot/jtreg/compiler/rangechecks/TestRangeCheckHoistingScaledIV.java @@ -28,7 +28,8 @@ * @summary Test range check hoisting for some scaled iv at array index * @library /test/lib / * @requires vm.flagless - * @requires vm.debug & vm.compiler2.enabled & (os.simpleArch == "x64" | os.arch == "aarch64" | os.arch == "riscv64") + * @requires vm.debug & vm.compiler2.enabled + * @requires os.simpleArch == "x64" | os.arch == "aarch64" | (os.arch == "riscv64" & vm.cpu.features ~= ".*rvv.*") * @modules jdk.incubator.vector * @run main/othervm compiler.rangechecks.TestRangeCheckHoistingScaledIV */ From fc8038441daebc717fedaeb107e37bf216d542d3 Mon Sep 17 00:00:00 2001 From: SendaoYan Date: Thu, 24 Jul 2025 01:47:58 +0000 Subject: [PATCH 65/94] 8359827: Test runtime/Thread/ThreadCountLimit.java need loop increasing the limit Co-authored-by: David Holmes Reviewed-by: dholmes --- .../runtime/Thread/ThreadCountLimit.java | 27 ++++++++++++++----- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/test/hotspot/jtreg/runtime/Thread/ThreadCountLimit.java b/test/hotspot/jtreg/runtime/Thread/ThreadCountLimit.java index 20d93cbfdbe..6ef7977ef8c 100644 --- a/test/hotspot/jtreg/runtime/Thread/ThreadCountLimit.java +++ b/test/hotspot/jtreg/runtime/Thread/ThreadCountLimit.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2024, 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 @@ -73,14 +73,27 @@ public class ThreadCountLimit { if (Platform.isLinux()) { // On Linux this test sometimes hits the limit for the maximum number of memory mappings, // which leads to various other failure modes. Run this test with a limit on how many - // threads the process is allowed to create, so we hit that limit first. - - final String ULIMIT_CMD = "ulimit -u 4096"; + // threads the process is allowed to create, so we hit that limit first. What we want is + // for another "limit" processes to be available, but ulimit doesn't work that way and + // if there are already many running processes we could fail to even start the JVM properly. + // So we loop increasing the limit until we get a successful run. This is not foolproof. + int pLimit = 4096; + final String ULIMIT_CMD = "ulimit -u "; ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder(ThreadCountLimit.class.getName()); String javaCmd = ProcessTools.getCommandLine(pb); - // Relaunch the test with args.length > 0, and the ulimit set - ProcessTools.executeCommand("bash", "-c", ULIMIT_CMD + " && " + javaCmd + " dummy") - .shouldHaveExitValue(0); + for (int i = 1; i <= 10; i++) { + // Relaunch the test with args.length > 0, and the ulimit set + String cmd = ULIMIT_CMD + Integer.toString(pLimit * i) + " && " + javaCmd + " dummy"; + System.out.println("Trying: bash -c " + cmd); + OutputAnalyzer oa = ProcessTools.executeCommand("bash", "-c", cmd); + int exitValue = oa.getExitValue(); + switch (exitValue) { + case 0: System.out.println("Success!"); return; + case 1: System.out.println("Retry ..."); continue; + default: oa.shouldHaveExitValue(0); // generate error report + } + } + throw new Error("Failed to perform a successful run!"); } else { // Not Linux so run directly. test(); From 0ba2942c6e7aadc3d091c40f6bd8d9f7502f5f76 Mon Sep 17 00:00:00 2001 From: Feilong Jiang Date: Thu, 24 Jul 2025 02:21:53 +0000 Subject: [PATCH 66/94] 8362838: RISC-V: Incorrect matching rule leading to improper oop instruction encoding Reviewed-by: fyang, yadongwang --- src/hotspot/cpu/riscv/riscv.ad | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/src/hotspot/cpu/riscv/riscv.ad b/src/hotspot/cpu/riscv/riscv.ad index cc59af8fae1..8e7b772414a 100644 --- a/src/hotspot/cpu/riscv/riscv.ad +++ b/src/hotspot/cpu/riscv/riscv.ad @@ -2276,10 +2276,6 @@ encode %{ __ mv(dst_reg, 1); %} - enc_class riscv_enc_mov_byte_map_base(iRegP dst) %{ - __ load_byte_map_base($dst$$Register); - %} - enc_class riscv_enc_mov_n(iRegN dst, immN src) %{ Register dst_reg = as_Register($dst$$reg); address con = (address)$src$$constant; @@ -2834,21 +2830,6 @@ operand immP_1() interface(CONST_INTER); %} -// Card Table Byte Map Base -operand immByteMapBase() -%{ - // Get base of card map - predicate(BarrierSet::barrier_set()->is_a(BarrierSet::CardTableBarrierSet) && - SHENANDOAHGC_ONLY(!BarrierSet::barrier_set()->is_a(BarrierSet::ShenandoahBarrierSet) &&) - (CardTable::CardValue*)n->get_ptr() == - ((CardTableBarrierSet*)(BarrierSet::barrier_set()))->card_table()->byte_map_base()); - match(ConP); - - op_cost(0); - format %{ %} - interface(CONST_INTER); -%} - // Int Immediate: low 16-bit mask operand immI_16bits() %{ @@ -4808,18 +4789,6 @@ instruct loadConP1(iRegPNoSp dst, immP_1 con) ins_pipe(ialu_imm); %} -// Load Byte Map Base Constant -instruct loadByteMapBase(iRegPNoSp dst, immByteMapBase con) -%{ - match(Set dst con); - ins_cost(ALU_COST); - format %{ "mv $dst, $con\t# Byte Map Base, #@loadByteMapBase" %} - - ins_encode(riscv_enc_mov_byte_map_base(dst)); - - ins_pipe(ialu_imm); -%} - // Load Narrow Pointer Constant instruct loadConN(iRegNNoSp dst, immN con) %{ From 7a22b76b73e6a6906f191e59b7d2da238b401935 Mon Sep 17 00:00:00 2001 From: Thomas Stuefe Date: Thu, 24 Jul 2025 05:09:31 +0000 Subject: [PATCH 67/94] 8362591: Wrong argument warning when heap size larger than coops threshold Reviewed-by: dholmes --- src/hotspot/share/runtime/arguments.cpp | 2 +- .../jtreg/runtime/cds/appcds/sharedStrings/SysDictCrash.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hotspot/share/runtime/arguments.cpp b/src/hotspot/share/runtime/arguments.cpp index c5e2f0c467c..8726abb91fe 100644 --- a/src/hotspot/share/runtime/arguments.cpp +++ b/src/hotspot/share/runtime/arguments.cpp @@ -1593,7 +1593,7 @@ void Arguments::set_heap_size() { // was not specified. if (reasonable_max > max_coop_heap) { if (FLAG_IS_ERGO(UseCompressedOops) && override_coop_limit) { - aot_log_info(aot)("UseCompressedOops and UseCompressedClassPointers have been disabled due to" + aot_log_info(aot)("UseCompressedOops disabled due to" " max heap %zu > compressed oop heap %zu. " "Please check the setting of MaxRAMPercentage %5.2f." ,(size_t)reasonable_max, (size_t)max_coop_heap, MaxRAMPercentage); diff --git a/test/hotspot/jtreg/runtime/cds/appcds/sharedStrings/SysDictCrash.java b/test/hotspot/jtreg/runtime/cds/appcds/sharedStrings/SysDictCrash.java index 219943d28de..9462f4f9d0b 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/sharedStrings/SysDictCrash.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/sharedStrings/SysDictCrash.java @@ -58,7 +58,7 @@ public class SysDictCrash { try { TestCommon.checkDump(output); } catch (java.lang.RuntimeException re) { - if (!output.getStdout().contains("UseCompressedOops and UseCompressedClassPointers have been disabled due to")) { + if (!output.getStdout().contains("UseCompressedOops disabled due to")) { throw re; } else { System.out.println("Shared archive was not created due to UseCompressedOops and UseCompressedClassPointers have been disabled."); From ed9066bdf48c2d9925aea745951531ebf4af35a8 Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Thu, 24 Jul 2025 05:59:24 +0000 Subject: [PATCH 68/94] 8361478: GHA: Use MSYS2 from GHA runners Reviewed-by: jwaters, ihse --- .github/actions/get-msys2/action.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/actions/get-msys2/action.yml b/.github/actions/get-msys2/action.yml index 4ca5d2ab847..d93b6e3763b 100644 --- a/.github/actions/get-msys2/action.yml +++ b/.github/actions/get-msys2/action.yml @@ -30,15 +30,15 @@ runs: using: composite steps: - name: 'Install MSYS2' - uses: msys2/setup-msys2@v2.22.0 + id: msys2 + uses: msys2/setup-msys2@v2.28.0 with: install: 'autoconf tar unzip zip make' path-type: minimal - location: ${{ runner.tool_cache }}/msys2 + release: false # We can't run bash until this is completed, so stick with pwsh - name: 'Set MSYS2 path' run: | - # Prepend msys2/msys64/usr/bin to the PATH - echo "$env:RUNNER_TOOL_CACHE/msys2/msys64/usr/bin" >> $env:GITHUB_PATH + echo "${{ steps.msys2.outputs.msys2-location }}/usr/bin" >> $env:GITHUB_PATH shell: pwsh From 67e93281a4f9e76419f1d6e05099ecf2214ebbfd Mon Sep 17 00:00:00 2001 From: Marc Chevalier Date: Thu, 24 Jul 2025 09:21:57 +0000 Subject: [PATCH 69/94] 8363357: Remove unused flag VerifyAdapterCalls Reviewed-by: chagedorn, thartmann --- src/hotspot/share/runtime/globals.hpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/hotspot/share/runtime/globals.hpp b/src/hotspot/share/runtime/globals.hpp index 41520442cd4..2da22df4e4e 100644 --- a/src/hotspot/share/runtime/globals.hpp +++ b/src/hotspot/share/runtime/globals.hpp @@ -618,9 +618,6 @@ const int ObjectAlignmentInBytes = 8; product(bool, PrintAdapterHandlers, false, DIAGNOSTIC, \ "Print code generated for i2c/c2i adapters") \ \ - product(bool, VerifyAdapterCalls, trueInDebug, DIAGNOSTIC, \ - "Verify that i2c/c2i adapters are called properly") \ - \ develop(bool, VerifyAdapterSharing, false, \ "Verify that the code for shared adapters is the equivalent") \ \ From 2f1aed2a165259a873636792cff7c9de4e1f334e Mon Sep 17 00:00:00 2001 From: Ayush Rigal Date: Thu, 24 Jul 2025 14:57:33 +0000 Subject: [PATCH 70/94] 8361423: Add IPSupport::printPlatformSupport to java/net/NetworkInterface/IPv4Only.java Reviewed-by: jpai --- test/jdk/java/net/NetworkInterface/IPv4Only.java | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/test/jdk/java/net/NetworkInterface/IPv4Only.java b/test/jdk/java/net/NetworkInterface/IPv4Only.java index efa59aa7691..3d12b3282bd 100644 --- a/test/jdk/java/net/NetworkInterface/IPv4Only.java +++ b/test/jdk/java/net/NetworkInterface/IPv4Only.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,12 +29,18 @@ */ -import java.net.*; -import java.util.*; + import jdk.test.lib.net.IPSupport; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.util.Enumeration; + public class IPv4Only { public static void main(String[] args) throws Exception { + IPSupport.printPlatformSupport(System.out); if (IPSupport.hasIPv4()) { System.out.println("Testing IPv4"); Enumeration nifs = NetworkInterface.getNetworkInterfaces(); @@ -43,7 +49,7 @@ public class IPv4Only { Enumeration addrs = nif.getInetAddresses(); while (addrs.hasMoreElements()) { InetAddress hostAddr = addrs.nextElement(); - if ( hostAddr instanceof Inet6Address ){ + if (hostAddr instanceof Inet6Address){ throw new RuntimeException( "NetworkInterfaceV6List failed - found v6 address " + hostAddr.getHostAddress() ); } } From 8477630970b61e3178abd7ac812ed97e181e2684 Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Thu, 24 Jul 2025 15:53:29 +0000 Subject: [PATCH 71/94] 8360679: Shenandoah: AOT saved adapter calls into broken GC barrier stub Reviewed-by: kvn, adinn, aph --- .../gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/hotspot/cpu/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.cpp index a2b3f44c68b..ed321ca4759 100644 --- a/src/hotspot/cpu/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.cpp @@ -292,7 +292,8 @@ void ShenandoahBarrierSetAssembler::load_reference_barrier(MacroAssembler* masm, } else { assert(is_phantom, "only remaining strength"); assert(!is_narrow, "phantom access cannot be narrow"); - __ mov(lr, CAST_FROM_FN_PTR(address, ShenandoahRuntime::load_reference_barrier_phantom)); + // AOT saved adapters need relocation for this call. + __ lea(lr, RuntimeAddress(CAST_FROM_FN_PTR(address, ShenandoahRuntime::load_reference_barrier_phantom))); } __ blr(lr); __ mov(rscratch1, r0); From 4e53a9d9dfe7a1ac7c3d7402e5ca3a3d3fcbb709 Mon Sep 17 00:00:00 2001 From: Rui Li Date: Thu, 24 Jul 2025 18:34:26 +0000 Subject: [PATCH 72/94] 8357818: Shenandoah doesn't use shared API for printing heap before/after GC Reviewed-by: wkemper, kdnilsen --- src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp | 2 ++ .../share/gc/shenandoah/shenandoahGenerationalControlThread.cpp | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp index 795bcc1cf92..ff62e8aa976 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp @@ -146,6 +146,7 @@ void ShenandoahControlThread::run_service() { // If GC was requested, we better dump freeset data for performance debugging heap->free_set()->log_status_under_lock(); + heap->print_before_gc(); switch (mode) { case concurrent_normal: service_concurrent_normal_cycle(cause); @@ -159,6 +160,7 @@ void ShenandoahControlThread::run_service() { default: ShouldNotReachHere(); } + heap->print_after_gc(); // If this was the requested GC cycle, notify waiters about it if (is_gc_requested) { diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp index 54cf8b978df..42671c33525 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalControlThread.cpp @@ -240,6 +240,7 @@ void ShenandoahGenerationalControlThread::run_gc_cycle(const ShenandoahGCRequest // Cannot uncommit bitmap slices during concurrent reset ShenandoahNoUncommitMark forbid_region_uncommit(_heap); + _heap->print_before_gc(); switch (gc_mode()) { case concurrent_normal: { service_concurrent_normal_cycle(request); @@ -261,6 +262,7 @@ void ShenandoahGenerationalControlThread::run_gc_cycle(const ShenandoahGCRequest default: ShouldNotReachHere(); } + _heap->print_after_gc(); } // If this cycle completed successfully, notify threads waiting for gc From 971ea23c95764e11ed234f657eb28ba7c51862c5 Mon Sep 17 00:00:00 2001 From: Phil Race Date: Thu, 24 Jul 2025 20:53:22 +0000 Subject: [PATCH 73/94] 8362289: [macOS] Remove finalize method in JRSUIControls.java Reviewed-by: bchristi, serb --- .../classes/apple/laf/JRSUIControl.java | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/java.desktop/macosx/classes/apple/laf/JRSUIControl.java b/src/java.desktop/macosx/classes/apple/laf/JRSUIControl.java index f1f5ebe5ff1..f28e05d1877 100644 --- a/src/java.desktop/macosx/classes/apple/laf/JRSUIControl.java +++ b/src/java.desktop/macosx/classes/apple/laf/JRSUIControl.java @@ -27,6 +27,8 @@ package apple.laf; import java.nio.*; import java.util.*; +import sun.java2d.Disposer; +import sun.java2d.DisposerRecord; import apple.laf.JRSUIConstants.*; @@ -91,7 +93,8 @@ public final class JRSUIControl { private final HashMap nativeMap; private final HashMap changes; - private long cfDictionaryPtr; + private final long cfDictionaryPtr; + private final Object disposerReferent = new Object(); private long priorEncodedProperties; private long currentEncodedProperties; @@ -101,6 +104,7 @@ public final class JRSUIControl { this.flipped = flipped; cfDictionaryPtr = getCFDictionary(flipped); if (cfDictionaryPtr == 0) throw new RuntimeException("Unable to create native representation"); + Disposer.addRecord(disposerReferent, new JRSUIControlDisposerRecord(cfDictionaryPtr)); nativeMap = new HashMap(); changes = new HashMap(); } @@ -109,17 +113,25 @@ public final class JRSUIControl { flipped = other.flipped; cfDictionaryPtr = getCFDictionary(flipped); if (cfDictionaryPtr == 0) throw new RuntimeException("Unable to create native representation"); + Disposer.addRecord(disposerReferent, new JRSUIControlDisposerRecord(cfDictionaryPtr)); nativeMap = new HashMap(); changes = new HashMap(other.nativeMap); changes.putAll(other.changes); } - @Override - @SuppressWarnings("removal") - protected synchronized void finalize() throws Throwable { - if (cfDictionaryPtr == 0) return; - disposeCFDictionary(cfDictionaryPtr); - cfDictionaryPtr = 0; + private static class JRSUIControlDisposerRecord implements DisposerRecord { + + private final long cfDictionaryPtr; + JRSUIControlDisposerRecord(long ptr) { + cfDictionaryPtr = ptr; + } + + public void dispose() { + try { + disposeCFDictionary(cfDictionaryPtr); + } catch (Throwable t) { + } + } } From ac9e51023fc34a82b795950a109af2397826adaa Mon Sep 17 00:00:00 2001 From: Thomas Stuefe Date: Fri, 25 Jul 2025 06:40:37 +0000 Subject: [PATCH 74/94] 8320836: jtreg gtest runs should limit heap size Reviewed-by: dholmes, cslucas --- test/hotspot/jtreg/gtest/GTestWrapper.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/hotspot/jtreg/gtest/GTestWrapper.java b/test/hotspot/jtreg/gtest/GTestWrapper.java index 50998c2748a..1bd9734e48c 100644 --- a/test/hotspot/jtreg/gtest/GTestWrapper.java +++ b/test/hotspot/jtreg/gtest/GTestWrapper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -82,6 +82,7 @@ public class GTestWrapper { command.add(execPath.toAbsolutePath().toString()); command.add("-jdk"); command.add(Utils.TEST_JDK); + command.add("-Xmx200m"); command.add("--gtest_output=xml:" + resultFile); command.add("--gtest_catch_exceptions=0"); for (String a : args) { From 52155dbbb0107c5077a6be7edfd91d4311411fc3 Mon Sep 17 00:00:00 2001 From: Albert Mingkun Yang Date: Fri, 25 Jul 2025 07:22:34 +0000 Subject: [PATCH 75/94] 8364082: jdk/jfr/event/gc/heapsummary/TestHeapSummaryEventPSParOld.java Eden should be placed first in young Reviewed-by: dholmes --- .../gc/heapsummary/HeapSummaryEventAllGcs.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test/jdk/jdk/jfr/event/gc/heapsummary/HeapSummaryEventAllGcs.java b/test/jdk/jdk/jfr/event/gc/heapsummary/HeapSummaryEventAllGcs.java index 674270b0449..16dd6f44fed 100644 --- a/test/jdk/jdk/jfr/event/gc/heapsummary/HeapSummaryEventAllGcs.java +++ b/test/jdk/jdk/jfr/event/gc/heapsummary/HeapSummaryEventAllGcs.java @@ -161,18 +161,18 @@ public class HeapSummaryEventAllGcs { long toStart = Events.assertField(event, "toSpace.start").getValue(); long toEnd = Events.assertField(event, "toSpace.end").getValue(); Asserts.assertEquals(oldEnd, youngStart, "Young should start where old ends"); - Asserts.assertEquals(youngStart, edenStart, "Eden should be placed first in young"); if (fromStart < toStart) { - // [eden][from][to] - Asserts.assertGreaterThanOrEqual(fromStart, edenEnd, "From should start after eden"); + // [from][to][eden] + Asserts.assertEquals(youngStart, fromStart, "From should be placed first in young"); Asserts.assertLessThanOrEqual(fromEnd, toStart, "To should start after From"); - Asserts.assertLessThanOrEqual(toEnd, youngEnd, "To should start after From"); + Asserts.assertLessThanOrEqual(toEnd, edenStart, "Eden should start after To"); } else { - // [eden][to][from] - Asserts.assertGreaterThanOrEqual(toStart, edenEnd, "From should start after eden"); - Asserts.assertLessThanOrEqual(toEnd, fromStart, "To should start after From"); - Asserts.assertLessThanOrEqual(fromEnd, youngEnd, "To should start after From"); + // [to][from][eden] + Asserts.assertEquals(youngStart, toStart, "To should be placed first in young"); + Asserts.assertLessThanOrEqual(toEnd, fromStart, "From should start after to"); + Asserts.assertLessThanOrEqual(fromEnd, edenStart, "Eden should start after From"); } + Asserts.assertEquals(edenEnd, youngEnd, "Eden should be last of young"); } private static void checkVirtualSpace(RecordedEvent event, String structName) { From f79bd54bbb9f5748e437346d34702608f7b67019 Mon Sep 17 00:00:00 2001 From: Alan Bateman Date: Fri, 25 Jul 2025 08:11:55 +0000 Subject: [PATCH 76/94] 8362882: Update SubmissionPublisher() specification to reflect use of ForkJoinPool.asyncCommonPool() Reviewed-by: jpai, dl --- .../classes/java/util/concurrent/SubmissionPublisher.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/java.base/share/classes/java/util/concurrent/SubmissionPublisher.java b/src/java.base/share/classes/java/util/concurrent/SubmissionPublisher.java index f793d2bcabd..9ddf6c74ddf 100644 --- a/src/java.base/share/classes/java/util/concurrent/SubmissionPublisher.java +++ b/src/java.base/share/classes/java/util/concurrent/SubmissionPublisher.java @@ -292,9 +292,7 @@ public class SubmissionPublisher implements Publisher, /** * Creates a new SubmissionPublisher using the {@link - * ForkJoinPool#commonPool()} for async delivery to subscribers - * (unless it does not support a parallelism level of at least two, - * in which case, a new Thread is created to run each task), with + * ForkJoinPool#commonPool()} for async delivery to subscribers, with * maximum buffer capacity of {@link Flow#defaultBufferSize}, and no * handler for Subscriber exceptions in method {@link * Flow.Subscriber#onNext(Object) onNext}. From 518d5f4bbb78ae35db793d7fd15b3cd35c881664 Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Fri, 25 Jul 2025 08:26:57 +0000 Subject: [PATCH 77/94] 8361871: [GCC static analyzer] complains about use of uninitialized value ckpObject in p11_util.c Reviewed-by: lucy --- src/jdk.crypto.cryptoki/share/native/libj2pkcs11/p11_util.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/p11_util.c b/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/p11_util.c index 9c5d6414e4c..463818626dd 100644 --- a/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/p11_util.c +++ b/src/jdk.crypto.cryptoki/share/native/libj2pkcs11/p11_util.c @@ -1209,7 +1209,7 @@ CK_VOID_PTR jObjectToPrimitiveCKObjectPtr(JNIEnv *env, jobject jObject, CK_ULONG jclass jBooleanArrayClass, jIntArrayClass, jLongArrayClass; jclass jStringClass; jclass jObjectClass, jClassClass; - CK_VOID_PTR ckpObject; + CK_VOID_PTR ckpObject = NULL; jmethodID jMethod; jobject jClassObject; jstring jClassNameString; From 41c94eed37aad570229ee2c5fb51d9e5d0378a40 Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Fri, 25 Jul 2025 11:34:37 +0000 Subject: [PATCH 78/94] 8363910: Avoid tuning for Power10 CPUs on Linux ppc64le when gcc < 10 is used Reviewed-by: stuefe --- make/autoconf/flags-cflags.m4 | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/make/autoconf/flags-cflags.m4 b/make/autoconf/flags-cflags.m4 index e80d9a98957..056334e99c4 100644 --- a/make/autoconf/flags-cflags.m4 +++ b/make/autoconf/flags-cflags.m4 @@ -736,8 +736,15 @@ AC_DEFUN([FLAGS_SETUP_CFLAGS_CPU_DEP], $1_CFLAGS_CPU_JVM="${$1_CFLAGS_CPU_JVM} -mminimal-toc" elif test "x$FLAGS_CPU" = xppc64le; then # Little endian machine uses ELFv2 ABI. - # Use Power8, this is the first CPU to support PPC64 LE with ELFv2 ABI. - $1_CFLAGS_CPU="-mcpu=power8 -mtune=power10" + # Use Power8 for target cpu, this is the first CPU to support PPC64 LE with ELFv2 ABI. + # Use Power10 for tuning target, this is supported by gcc >= 10 + POWER_TUNE_VERSION="-mtune=power10" + FLAGS_COMPILER_CHECK_ARGUMENTS(ARGUMENT: [${POWER_TUNE_VERSION}], + IF_FALSE: [ + POWER_TUNE_VERSION="-mtune=power8" + ] + ) + $1_CFLAGS_CPU="-mcpu=power8 ${POWER_TUNE_VERSION}" $1_CFLAGS_CPU_JVM="${$1_CFLAGS_CPU_JVM} -DABI_ELFv2" fi elif test "x$FLAGS_CPU" = xs390x; then From 06fdb61e1cdc9abf9ac4fa62fd63992d298baffa Mon Sep 17 00:00:00 2001 From: Sean Mullan Date: Fri, 25 Jul 2025 12:55:39 +0000 Subject: [PATCH 79/94] 8361964: Remove outdated algorithms from requirements and add PBES2 algorithms Reviewed-by: hchao --- .../share/classes/java/security/AlgorithmParameters.java | 3 ++- src/java.base/share/classes/javax/crypto/Cipher.java | 7 ++----- src/java.base/share/classes/javax/crypto/KeyGenerator.java | 1 - src/java.base/share/classes/javax/crypto/Mac.java | 1 + .../share/classes/javax/crypto/SecretKeyFactory.java | 4 +++- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/java.base/share/classes/java/security/AlgorithmParameters.java b/src/java.base/share/classes/java/security/AlgorithmParameters.java index defe25571f7..2a6fe830ba9 100644 --- a/src/java.base/share/classes/java/security/AlgorithmParameters.java +++ b/src/java.base/share/classes/java/security/AlgorithmParameters.java @@ -55,10 +55,11 @@ import java.util.Objects; *
          *
        • {@code AES}
        • *
        • {@code ChaCha20-Poly1305}
        • - *
        • {@code DESede}
        • *
        • {@code DiffieHellman}
        • *
        • {@code DSA}
        • *
        • {@code EC} (secp256r1, secp384r1)
        • + *
        • {@code PBEWithHmacSHA256AndAES_128}
        • + *
        • {@code PBEWithHmacSHA256AndAES_256}
        • *
        • {@code RSASSA-PSS} (MGF1 mask generation function and SHA-256 or SHA-384 * hash algorithms)
        • *
        diff --git a/src/java.base/share/classes/javax/crypto/Cipher.java b/src/java.base/share/classes/javax/crypto/Cipher.java index 74971182039..82a607a5553 100644 --- a/src/java.base/share/classes/javax/crypto/Cipher.java +++ b/src/java.base/share/classes/javax/crypto/Cipher.java @@ -127,11 +127,8 @@ import sun.security.util.KnownOIDs; *
      • {@code AES/ECB/PKCS5Padding} (128)
      • *
      • {@code AES/GCM/NoPadding} (128, 256)
      • *
      • {@code ChaCha20-Poly1305}
      • - *
      • {@code DESede/CBC/NoPadding} (168)
      • - *
      • {@code DESede/CBC/PKCS5Padding} (168)
      • - *
      • {@code DESede/ECB/NoPadding} (168)
      • - *
      • {@code DESede/ECB/PKCS5Padding} (168)
      • - *
      • {@code RSA/ECB/PKCS1Padding} (1024, 2048)
      • + *
      • {@code PBEWithHmacSHA256AndAES_128}
      • + *
      • {@code PBEWithHmacSHA256AndAES_256}
      • *
      • {@code RSA/ECB/OAEPWithSHA-1AndMGF1Padding} (1024, 2048)
      • *
      • {@code RSA/ECB/OAEPWithSHA-256AndMGF1Padding} (1024, 2048)
      • *
      diff --git a/src/java.base/share/classes/javax/crypto/KeyGenerator.java b/src/java.base/share/classes/javax/crypto/KeyGenerator.java index 02d0bd75753..7bbfc0a5e08 100644 --- a/src/java.base/share/classes/javax/crypto/KeyGenerator.java +++ b/src/java.base/share/classes/javax/crypto/KeyGenerator.java @@ -98,7 +98,6 @@ import sun.security.util.Debug; *
        *
      • {@code AES} (128, 256)
      • *
      • {@code ChaCha20}
      • - *
      • {@code DESede} (168)
      • *
      • {@code HmacSHA1}
      • *
      • {@code HmacSHA256}
      • *
      diff --git a/src/java.base/share/classes/javax/crypto/Mac.java b/src/java.base/share/classes/javax/crypto/Mac.java index fb1eb2c310a..82874693cf2 100644 --- a/src/java.base/share/classes/javax/crypto/Mac.java +++ b/src/java.base/share/classes/javax/crypto/Mac.java @@ -58,6 +58,7 @@ import sun.security.jca.GetInstance.Instance; *
        *
      • {@code HmacSHA1}
      • *
      • {@code HmacSHA256}
      • + *
      • {@code PBEWithHmacSHA256}
      • *
      * These algorithms are described in the * diff --git a/src/java.base/share/classes/javax/crypto/SecretKeyFactory.java b/src/java.base/share/classes/javax/crypto/SecretKeyFactory.java index d7163e4d240..7ad90eaa858 100644 --- a/src/java.base/share/classes/javax/crypto/SecretKeyFactory.java +++ b/src/java.base/share/classes/javax/crypto/SecretKeyFactory.java @@ -59,7 +59,9 @@ import sun.security.jca.GetInstance.Instance; *

      Every implementation of the Java platform is required to support the * following standard {@code SecretKeyFactory} algorithms: *

        - *
      • {@code DESede}
      • + *
      • {@code PBEWithHmacSHA256AndAES_128}
      • + *
      • {@code PBEWithHmacSHA256AndAES_256}
      • + *
      • {@code PBKDF2WithHmacSHA256}
      • *
      * These algorithms are described in the
      From 75ff7e15fe0d22149e5b8c5ccf3b702d8dc9b3fa Mon Sep 17 00:00:00 2001 From: Thomas Stuefe Date: Fri, 25 Jul 2025 13:34:30 +0000 Subject: [PATCH 80/94] 8361712: Improve ShenandoahAsserts printing Reviewed-by: rkennke, asmehra --- .../share/gc/shenandoah/shenandoahAsserts.cpp | 133 +++++++++++++----- .../share/gc/shenandoah/shenandoahAsserts.hpp | 8 +- .../gc/shenandoah/shenandoahVerifier.cpp | 14 +- src/hotspot/share/oops/compressedKlass.hpp | 3 + .../share/oops/compressedKlass.inline.hpp | 5 + src/hotspot/share/oops/oop.hpp | 1 + src/hotspot/share/oops/oop.inline.hpp | 11 ++ src/hotspot/share/utilities/ostream.hpp | 2 +- .../gtest/oops/test_compressedKlass.cpp | 15 +- 9 files changed, 151 insertions(+), 41 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahAsserts.cpp b/src/hotspot/share/gc/shenandoah/shenandoahAsserts.cpp index 7c9fa759835..0ffdc400826 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahAsserts.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahAsserts.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020, Red Hat, Inc. All rights reserved. + * Copyright (c) 2018, 2025, Red Hat, 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 @@ -29,7 +29,10 @@ #include "gc/shenandoah/shenandoahHeapRegionSet.inline.hpp" #include "gc/shenandoah/shenandoahMarkingContext.inline.hpp" #include "gc/shenandoah/shenandoahUtils.hpp" +#include "oops/oop.inline.hpp" #include "memory/resourceArea.hpp" +#include "runtime/os.hpp" +#include "utilities/vmError.hpp" void print_raw_memory(ShenandoahMessageBuffer &msg, void* loc) { // Be extra safe. Only access data that is guaranteed to be safe: @@ -57,30 +60,42 @@ void ShenandoahAsserts::print_obj(ShenandoahMessageBuffer& msg, oop obj) { ResourceMark rm; stringStream ss; - r->print_on(&ss); - - stringStream mw_ss; - obj->mark().print_on(&mw_ss); + StreamIndentor si(&ss); ShenandoahMarkingContext* const ctx = heap->marking_context(); - Klass* obj_klass = ShenandoahForwarding::klass(obj); - - msg.append(" " PTR_FORMAT " - klass " PTR_FORMAT " %s\n", p2i(obj), p2i(obj_klass), obj_klass->external_name()); - msg.append(" %3s allocated after mark start\n", ctx->allocated_after_mark_start(obj) ? "" : "not"); - msg.append(" %3s after update watermark\n", cast_from_oop(obj) >= r->get_update_watermark() ? "" : "not"); - msg.append(" %3s marked strong\n", ctx->is_marked_strong(obj) ? "" : "not"); - msg.append(" %3s marked weak\n", ctx->is_marked_weak(obj) ? "" : "not"); - msg.append(" %3s in collection set\n", heap->in_collection_set(obj) ? "" : "not"); - if (heap->mode()->is_generational() && !obj->is_forwarded()) { - msg.append(" age: %d\n", obj->age()); + narrowKlass nk = 0; + const Klass* obj_klass = nullptr; + const bool klass_valid = extract_klass_safely(obj, nk, obj_klass); + const char* klass_text = "(invalid)"; + if (klass_valid && os::is_readable_pointer(obj_klass) && Metaspace::contains(obj_klass)) { + klass_text = obj_klass->external_name(); } - msg.append(" mark:%s\n", mw_ss.freeze()); - msg.append(" region: %s", ss.freeze()); - if (obj_klass == vmClasses::Class_klass()) { - msg.append(" mirrored klass: " PTR_FORMAT "\n", p2i(obj->metadata_field(java_lang_Class::klass_offset()))); - msg.append(" mirrored array klass: " PTR_FORMAT "\n", p2i(obj->metadata_field(java_lang_Class::array_klass_offset()))); + ss.print_cr(PTR_FORMAT " - nk %u klass " PTR_FORMAT " %s\n", p2i(obj), nk, p2i(obj_klass), klass_text); + { + StreamIndentor si(&ss); + ss.print_cr("%3s allocated after mark start", ctx->allocated_after_mark_start(obj) ? "" : "not"); + ss.print_cr("%3s after update watermark", cast_from_oop(obj) >= r->get_update_watermark() ? "" : "not"); + ss.print_cr("%3s marked strong", ctx->is_marked_strong(obj) ? "" : "not"); + ss.print_cr("%3s marked weak", ctx->is_marked_weak(obj) ? "" : "not"); + ss.print_cr("%3s in collection set", heap->in_collection_set(obj) ? "" : "not"); + if (heap->mode()->is_generational() && !obj->is_forwarded()) { + ss.print_cr("age: %d", obj->age()); + } + ss.print_raw("mark: "); + obj->mark().print_on(&ss); + ss.cr(); + ss.print_raw("region: "); + r->print_on(&ss); + ss.cr(); + if (obj_klass == vmClasses::Class_klass()) { + msg.append(" mirrored klass: " PTR_FORMAT "\n", p2i(obj->metadata_field(java_lang_Class::klass_offset()))); + msg.append(" mirrored array klass: " PTR_FORMAT "\n", p2i(obj->metadata_field(java_lang_Class::array_klass_offset()))); + } } + const_address loc = cast_from_oop(obj); + os::print_hex_dump(&ss, loc, loc + 64, 4, true, 32, loc); + msg.append("%s", ss.base()); } void ShenandoahAsserts::print_non_obj(ShenandoahMessageBuffer& msg, void* loc) { @@ -121,6 +136,10 @@ void ShenandoahAsserts::print_failure(SafeLevel level, oop obj, void* interior_l ShenandoahHeap* heap = ShenandoahHeap::heap(); ResourceMark rm; + if (!os::is_readable_pointer(obj)) { + level = _safe_unknown; + } + bool loc_in_heap = (loc != nullptr && heap->is_in_reserved(loc)); ShenandoahMessageBuffer msg("%s; %s\n\n", phase, label); @@ -128,7 +147,7 @@ void ShenandoahAsserts::print_failure(SafeLevel level, oop obj, void* interior_l msg.append("Referenced from:\n"); if (interior_loc != nullptr) { msg.append(" interior location: " PTR_FORMAT "\n", p2i(interior_loc)); - if (loc_in_heap) { + if (loc_in_heap && os::is_readable_pointer(loc)) { print_obj(msg, loc); } else { print_non_obj(msg, interior_loc); @@ -150,7 +169,7 @@ void ShenandoahAsserts::print_failure(SafeLevel level, oop obj, void* interior_l oop fwd = ShenandoahForwarding::get_forwardee_raw_unchecked(obj); msg.append("Forwardee:\n"); if (obj != fwd) { - if (level >= _safe_oop_fwd) { + if (level >= _safe_oop_fwd && os::is_readable_pointer(fwd)) { print_obj(msg, fwd); } else { print_obj_safe(msg, fwd); @@ -205,17 +224,10 @@ void ShenandoahAsserts::assert_correct(void* interior_loc, oop obj, const char* file, line); } - Klass* obj_klass = ShenandoahForwarding::klass(obj); - if (obj_klass == nullptr) { + if (!os::is_readable_pointer(obj)) { print_failure(_safe_unknown, obj, interior_loc, nullptr, "Shenandoah assert_correct failed", - "Object klass pointer should not be null", - file,line); - } - - if (!Metaspace::contains(obj_klass)) { - print_failure(_safe_unknown, obj, interior_loc, nullptr, "Shenandoah assert_correct failed", - "Object klass pointer must go to metaspace", - file,line); + "oop within heap bounds but at unreadable location", + file, line); } if (!heap->is_in(obj)) { @@ -243,9 +255,9 @@ void ShenandoahAsserts::assert_correct(void* interior_loc, oop obj, const char* file, line); } - if (obj_klass != ShenandoahForwarding::klass(fwd)) { + if (!os::is_readable_pointer(fwd)) { print_failure(_safe_oop, obj, interior_loc, nullptr, "Shenandoah assert_correct failed", - "Forwardee klass disagrees with object class", + "Forwardee within heap bounds but at unreadable location", file, line); } @@ -271,6 +283,32 @@ void ShenandoahAsserts::assert_correct(void* interior_loc, oop obj, const char* } } + const Klass* obj_klass = nullptr; + narrowKlass nk = 0; + if (!extract_klass_safely(obj, nk, obj_klass)) { + print_failure(_safe_oop, obj, interior_loc, nullptr, "Shenandoah assert_correct failed", + "Object klass pointer invalid", + file,line); + } + + if (obj_klass == nullptr) { + print_failure(_safe_oop, obj, interior_loc, nullptr, "Shenandoah assert_correct failed", + "Object klass pointer should not be null", + file,line); + } + + if (!Metaspace::contains(obj_klass)) { + print_failure(_safe_oop, obj, interior_loc, nullptr, "Shenandoah assert_correct failed", + "Object klass pointer must go to metaspace", + file,line); + } + + if (!UseCompactObjectHeaders && obj_klass != fwd->klass_or_null()) { + print_failure(_safe_oop, obj, interior_loc, nullptr, "Shenandoah assert_correct failed", + "Forwardee klass disagrees with object class", + file, line); + } + // Do additional checks for special objects: their fields can hold metadata as well. // We want to check class loading/unloading did not corrupt them. We can only reasonably // trust the forwarded objects, as the from-space object can have the klasses effectively @@ -519,3 +557,30 @@ void ShenandoahAsserts::assert_generations_reconciled(const char* file, int line ShenandoahMessageBuffer msg("Active(%d) & GC(%d) Generations aren't reconciled", agen->type(), ggen->type()); report_vm_error(file, line, msg.buffer()); } + +bool ShenandoahAsserts::extract_klass_safely(oop obj, narrowKlass& nk, const Klass*& k) { + nk = 0; + k = nullptr; + + if (!os::is_readable_pointer(obj)) { + return false; + } + if (UseCompressedClassPointers) { + if (UseCompactObjectHeaders) { // look in forwardee + oop fwd = ShenandoahForwarding::get_forwardee_raw_unchecked(obj); + if (!os::is_readable_pointer(fwd)) { + return false; + } + nk = fwd->mark().narrow_klass(); + } else { + nk = obj->narrow_klass(); + } + if (!CompressedKlassPointers::is_valid_narrow_klass_id(nk)) { + return false; + } + k = CompressedKlassPointers::decode_not_null_without_asserts(nk); + } else { + k = obj->klass(); + } + return k != nullptr; +} diff --git a/src/hotspot/share/gc/shenandoah/shenandoahAsserts.hpp b/src/hotspot/share/gc/shenandoah/shenandoahAsserts.hpp index 31a99bf438c..e31ef7c99aa 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahAsserts.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahAsserts.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2019, Red Hat, Inc. All rights reserved. + * Copyright (c) 2018, 2025, Red Hat, Inc. All rights reserved. * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -27,6 +27,7 @@ #define SHARE_GC_SHENANDOAH_SHENANDOAHASSERTS_HPP #include "memory/iterator.hpp" +#include "oops/compressedKlass.hpp" #include "runtime/mutex.hpp" #include "utilities/formatBuffer.hpp" @@ -77,6 +78,11 @@ public: static void assert_generational(const char* file, int line); static void assert_generations_reconciled(const char* file, int line); + // Given a possibly invalid oop, extract narrowKlass (if UCCP) and Klass* + // from it safely. + // Note: For -UCCP, returned nk is always 0. + static bool extract_klass_safely(oop obj, narrowKlass& nk, const Klass*& k); + #ifdef ASSERT #define shenandoah_assert_in_heap_bounds(interior_loc, obj) \ ShenandoahAsserts::assert_in_heap_bounds(interior_loc, obj, __FILE__, __LINE__) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahVerifier.cpp b/src/hotspot/share/gc/shenandoah/shenandoahVerifier.cpp index 727b90e82a2..33b8744be3d 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahVerifier.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahVerifier.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021, Red Hat, Inc. All rights reserved. + * Copyright (c) 2017, 2025, Red Hat, Inc. All rights reserved. * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. @@ -149,15 +149,21 @@ private: "oop must be in heap bounds"); check(ShenandoahAsserts::_safe_unknown, obj, is_object_aligned(obj), "oop must be aligned"); + check(ShenandoahAsserts::_safe_unknown, obj, os::is_readable_pointer(obj), + "oop must be accessible"); ShenandoahHeapRegion *obj_reg = _heap->heap_region_containing(obj); - Klass* obj_klass = ShenandoahForwarding::klass(obj); + + narrowKlass nk = 0; + const Klass* obj_klass = nullptr; + const bool klass_valid = ShenandoahAsserts::extract_klass_safely(obj, nk, obj_klass); + + check(ShenandoahAsserts::_safe_unknown, obj, klass_valid, + "Object klass pointer unreadable or invalid"); // Verify that obj is not in dead space: { // Do this before touching obj->size() - check(ShenandoahAsserts::_safe_unknown, obj, obj_klass != nullptr, - "Object klass pointer should not be null"); check(ShenandoahAsserts::_safe_unknown, obj, Metaspace::contains(obj_klass), "Object klass pointer must go to metaspace"); diff --git a/src/hotspot/share/oops/compressedKlass.hpp b/src/hotspot/share/oops/compressedKlass.hpp index 99befc847bc..cec86a70a90 100644 --- a/src/hotspot/share/oops/compressedKlass.hpp +++ b/src/hotspot/share/oops/compressedKlass.hpp @@ -251,6 +251,9 @@ public: inline static void check_valid_narrow_klass_id(narrowKlass nk); #endif + // Given a narrow Klass ID, returns true if it appears to be valid + inline static bool is_valid_narrow_klass_id(narrowKlass nk); + // Returns whether the pointer is in the memory region used for encoding compressed // class pointers. This includes CDS. static inline bool is_encodable(const void* addr) { diff --git a/src/hotspot/share/oops/compressedKlass.inline.hpp b/src/hotspot/share/oops/compressedKlass.inline.hpp index 5de7c1fa5d8..f96b2b9e13f 100644 --- a/src/hotspot/share/oops/compressedKlass.inline.hpp +++ b/src/hotspot/share/oops/compressedKlass.inline.hpp @@ -93,6 +93,11 @@ inline void CompressedKlassPointers::check_valid_narrow_klass_id(narrowKlass nk) } #endif // ASSERT +// Given a narrow Klass ID, returns true if it appears to be valid +inline bool CompressedKlassPointers::is_valid_narrow_klass_id(narrowKlass nk) { + return nk >= _lowest_valid_narrow_klass_id && nk < _highest_valid_narrow_klass_id; +} + inline address CompressedKlassPointers::encoding_range_end() { const int max_bits = narrow_klass_pointer_bits() + _shift; return (address)((uintptr_t)_base + nth_bit(max_bits)); diff --git a/src/hotspot/share/oops/oop.hpp b/src/hotspot/share/oops/oop.hpp index 549b5b0bff8..02f87da2937 100644 --- a/src/hotspot/share/oops/oop.hpp +++ b/src/hotspot/share/oops/oop.hpp @@ -91,6 +91,7 @@ class oopDesc { inline Klass* klass_without_asserts() const; void set_narrow_klass(narrowKlass nk) NOT_CDS_JAVA_HEAP_RETURN; + inline narrowKlass narrow_klass() const; inline void set_klass(Klass* k); static inline void release_set_klass(HeapWord* mem, Klass* k); diff --git a/src/hotspot/share/oops/oop.inline.hpp b/src/hotspot/share/oops/oop.inline.hpp index 683792e5201..4ca1bfce472 100644 --- a/src/hotspot/share/oops/oop.inline.hpp +++ b/src/hotspot/share/oops/oop.inline.hpp @@ -141,6 +141,17 @@ Klass* oopDesc::klass_without_asserts() const { } } +narrowKlass oopDesc::narrow_klass() const { + switch (ObjLayout::klass_mode()) { + case ObjLayout::Compact: + return mark().narrow_klass(); + case ObjLayout::Compressed: + return _metadata._compressed_klass; + default: + ShouldNotReachHere(); + } +} + void oopDesc::set_klass(Klass* k) { assert(Universe::is_bootstrapping() || (k != nullptr && k->is_klass()), "incorrect Klass"); assert(!UseCompactObjectHeaders, "don't set Klass* with compact headers"); diff --git a/src/hotspot/share/utilities/ostream.hpp b/src/hotspot/share/utilities/ostream.hpp index 79e95734a53..a148557fd32 100644 --- a/src/hotspot/share/utilities/ostream.hpp +++ b/src/hotspot/share/utilities/ostream.hpp @@ -182,7 +182,7 @@ class StreamIndentor { NONCOPYABLE(StreamIndentor); public: - StreamIndentor(outputStream* os, int indentation) : + StreamIndentor(outputStream* os, int indentation = 2) : _stream(os), _indentation(indentation), _old_autoindent(_stream->set_autoindent(true)) { diff --git a/test/hotspot/gtest/oops/test_compressedKlass.cpp b/test/hotspot/gtest/oops/test_compressedKlass.cpp index 56bfd29782f..c8120e3519d 100644 --- a/test/hotspot/gtest/oops/test_compressedKlass.cpp +++ b/test/hotspot/gtest/oops/test_compressedKlass.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Red Hat, Inc. All rights reserved. + * Copyright (c) 2024, 2025, Red Hat, Inc. All rights reserved. * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -22,6 +22,7 @@ * questions. */ +#include "classfile/vmClasses.hpp" #include "oops/compressedKlass.inline.hpp" #include "utilities/globalDefinitions.hpp" @@ -107,3 +108,15 @@ TEST_VM(CompressedKlass, test_good_address) { addr = CompressedKlassPointers::klass_range_end() - alignment; ASSERT_TRUE(CompressedKlassPointers::is_encodable(addr)); } + +TEST_VM(CompressedKlass, test_is_valid_narrow_klass) { + if (!UseCompressedClassPointers) { + return; + } + ASSERT_FALSE(CompressedKlassPointers::is_valid_narrow_klass_id(0)); + narrowKlass nk_jlC = CompressedKlassPointers::encode((Klass*)vmClasses::Class_klass()); + ASSERT_TRUE(CompressedKlassPointers::is_valid_narrow_klass_id(nk_jlC)); + if (CompressedClassSpaceSize < 4 * G && CompressedKlassPointers::base() != nullptr) { + ASSERT_FALSE(CompressedKlassPointers::is_valid_narrow_klass_id(0xFFFFFFFF)); + } +} From 9e209fef86fe75fb09734c9112fd1d8490c22413 Mon Sep 17 00:00:00 2001 From: Albert Mingkun Yang Date: Fri, 25 Jul 2025 14:50:55 +0000 Subject: [PATCH 81/94] 8364110: Remove unused methods in GCCause Reviewed-by: kbarrett --- src/hotspot/share/gc/shared/gcCause.hpp | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/src/hotspot/share/gc/shared/gcCause.hpp b/src/hotspot/share/gc/shared/gcCause.hpp index 56070c88143..f775d41340d 100644 --- a/src/hotspot/share/gc/shared/gcCause.hpp +++ b/src/hotspot/share/gc/shared/gcCause.hpp @@ -92,12 +92,6 @@ class GCCause : public AllStatic { cause == GCCause::_dcmd_gc_run); } - inline static bool is_explicit_full_gc(GCCause::Cause cause) { - return (is_user_requested_gc(cause) || - is_serviceability_requested_gc(cause) || - cause == GCCause::_wb_full_gc); - } - inline static bool is_serviceability_requested_gc(GCCause::Cause cause) { return (cause == GCCause::_jvmti_force_gc || cause == GCCause::_heap_inspection || @@ -109,20 +103,6 @@ class GCCause : public AllStatic { cause == _codecache_GC_aggressive); } - // Causes for collection of the tenured generation - inline static bool is_tenured_allocation_failure_gc(GCCause::Cause cause) { - // _allocation_failure is the generic cause a collection which could result - // in the collection of the tenured generation if there is not enough space - // in the tenured generation to support a young GC. - return cause == GCCause::_allocation_failure; - } - - // Causes for collection of the young generation - inline static bool is_allocation_failure_gc(GCCause::Cause cause) { - // _allocation_failure is the generic cause a collection for allocation failure - return cause == GCCause::_allocation_failure; - } - // Return a string describing the GCCause. static const char* to_string(GCCause::Cause cause); }; From 89fe586edd5044923a2ce86f8cc5bf16004ac0b5 Mon Sep 17 00:00:00 2001 From: Vladimir Kozlov Date: Fri, 25 Jul 2025 16:47:09 +0000 Subject: [PATCH 82/94] 8363837: Make StubRoutines::crc_table_adr() into platform-specific method Reviewed-by: adinn, yzheng --- .../cpu/aarch64/stubGenerator_aarch64.cpp | 2 -- src/hotspot/cpu/aarch64/stubRoutines_aarch64.cpp | 4 ++++ src/hotspot/cpu/aarch64/stubRoutines_aarch64.hpp | 1 + src/hotspot/cpu/arm/stubRoutines_arm.cpp | 3 +++ src/hotspot/cpu/ppc/stubGenerator_ppc.cpp | 2 -- src/hotspot/cpu/ppc/stubRoutines_ppc.hpp | 1 + src/hotspot/cpu/ppc/stubRoutines_ppc_64.cpp | 16 ++++++++++++++++ src/hotspot/cpu/riscv/stubGenerator_riscv.cpp | 2 -- src/hotspot/cpu/riscv/stubRoutines_riscv.cpp | 4 ++++ src/hotspot/cpu/riscv/stubRoutines_riscv.hpp | 1 + src/hotspot/cpu/s390/stubGenerator_s390.cpp | 2 -- src/hotspot/cpu/s390/stubRoutines_s390.cpp | 7 +++++-- src/hotspot/cpu/s390/stubRoutines_s390.hpp | 1 + src/hotspot/cpu/x86/stubGenerator_x86_64.cpp | 4 ---- src/hotspot/cpu/x86/stubRoutines_x86.cpp | 13 ++++++++++++- src/hotspot/cpu/x86/stubRoutines_x86.hpp | 1 + src/hotspot/cpu/zero/stubDeclarations_zero.hpp | 4 ++-- src/hotspot/cpu/zero/stubRoutines_zero.cpp | 3 ++- src/hotspot/share/jvmci/jvmciCompilerToVM.hpp | 2 ++ .../share/jvmci/jvmciCompilerToVMInit.cpp | 3 +++ src/hotspot/share/jvmci/vmStructs_jvmci.cpp | 3 +-- src/hotspot/share/runtime/stubDeclarations.hpp | 5 +---- src/hotspot/share/runtime/stubRoutines.cpp | 2 +- src/hotspot/share/runtime/stubRoutines.hpp | 6 ++++++ 24 files changed, 67 insertions(+), 25 deletions(-) diff --git a/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp b/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp index 0a7d9af9bff..b3bd0b92206 100644 --- a/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp @@ -11680,8 +11680,6 @@ class StubGenerator: public StubCodeGenerator { } if (UseCRC32Intrinsics) { - // set table address before stub generation which use it - StubRoutines::_crc_table_adr = (address)StubRoutines::aarch64::_crc_table; StubRoutines::_updateBytesCRC32 = generate_updateBytesCRC32(); } diff --git a/src/hotspot/cpu/aarch64/stubRoutines_aarch64.cpp b/src/hotspot/cpu/aarch64/stubRoutines_aarch64.cpp index fab76c41303..88993818b47 100644 --- a/src/hotspot/cpu/aarch64/stubRoutines_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/stubRoutines_aarch64.cpp @@ -71,6 +71,10 @@ ATTRIBUTE_ALIGNED(64) uint32_t StubRoutines::aarch64::_dilithiumConsts[] = /** * crc_table[] from jdk/src/share/native/java/util/zip/zlib-1.2.5/crc32.h */ + +address StubRoutines::crc_table_addr() { return (address)StubRoutines::aarch64::_crc_table; } +address StubRoutines::crc32c_table_addr() { ShouldNotCallThis(); return nullptr; } + ATTRIBUTE_ALIGNED(4096) juint StubRoutines::aarch64::_crc_table[] = { // Table 0 diff --git a/src/hotspot/cpu/aarch64/stubRoutines_aarch64.hpp b/src/hotspot/cpu/aarch64/stubRoutines_aarch64.hpp index 4c942b9f8d8..c35371e1083 100644 --- a/src/hotspot/cpu/aarch64/stubRoutines_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/stubRoutines_aarch64.hpp @@ -47,6 +47,7 @@ enum platform_dependent_constants { class aarch64 { friend class StubGenerator; + friend class StubRoutines; #if INCLUDE_JVMCI friend class JVMCIVMStructs; #endif diff --git a/src/hotspot/cpu/arm/stubRoutines_arm.cpp b/src/hotspot/cpu/arm/stubRoutines_arm.cpp index d843d89186e..a4f2b5e1bd9 100644 --- a/src/hotspot/cpu/arm/stubRoutines_arm.cpp +++ b/src/hotspot/cpu/arm/stubRoutines_arm.cpp @@ -36,3 +36,6 @@ STUBGEN_ARCH_ENTRIES_DO(DEFINE_ARCH_ENTRY, DEFINE_ARCH_ENTRY_INIT) #undef DEFINE_ARCH_ENTRY_INIT #undef DEFINE_ARCH_ENTRY + +address StubRoutines::crc_table_addr() { ShouldNotCallThis(); return nullptr; } +address StubRoutines::crc32c_table_addr() { ShouldNotCallThis(); return nullptr; } diff --git a/src/hotspot/cpu/ppc/stubGenerator_ppc.cpp b/src/hotspot/cpu/ppc/stubGenerator_ppc.cpp index c2f290212bd..9b27441c752 100644 --- a/src/hotspot/cpu/ppc/stubGenerator_ppc.cpp +++ b/src/hotspot/cpu/ppc/stubGenerator_ppc.cpp @@ -4982,13 +4982,11 @@ void generate_lookup_secondary_supers_table_stub() { // CRC32 Intrinsics. if (UseCRC32Intrinsics) { - StubRoutines::_crc_table_adr = StubRoutines::ppc::generate_crc_constants(REVERSE_CRC32_POLY); StubRoutines::_updateBytesCRC32 = generate_CRC32_updateBytes(StubId::stubgen_updateBytesCRC32_id); } // CRC32C Intrinsics. if (UseCRC32CIntrinsics) { - StubRoutines::_crc32c_table_addr = StubRoutines::ppc::generate_crc_constants(REVERSE_CRC32C_POLY); StubRoutines::_updateBytesCRC32C = generate_CRC32_updateBytes(StubId::stubgen_updateBytesCRC32C_id); } diff --git a/src/hotspot/cpu/ppc/stubRoutines_ppc.hpp b/src/hotspot/cpu/ppc/stubRoutines_ppc.hpp index a542d7947f8..f8909ad5fa1 100644 --- a/src/hotspot/cpu/ppc/stubRoutines_ppc.hpp +++ b/src/hotspot/cpu/ppc/stubRoutines_ppc.hpp @@ -54,6 +54,7 @@ enum platform_dependent_constants { class ppc { friend class StubGenerator; + friend class StubRoutines; private: public: diff --git a/src/hotspot/cpu/ppc/stubRoutines_ppc_64.cpp b/src/hotspot/cpu/ppc/stubRoutines_ppc_64.cpp index fed3f208f06..914c5a17a19 100644 --- a/src/hotspot/cpu/ppc/stubRoutines_ppc_64.cpp +++ b/src/hotspot/cpu/ppc/stubRoutines_ppc_64.cpp @@ -74,6 +74,22 @@ static julong compute_inverse_poly(julong long_poly) { return div; } +static address _crc_table_addr = nullptr; +static address _crc32c_table_addr = nullptr; + +address StubRoutines::crc_table_addr() { + if (_crc_table_addr == nullptr) { + _crc_table_addr = StubRoutines::ppc::generate_crc_constants(REVERSE_CRC32_POLY); + } + return _crc_table_addr; +} +address StubRoutines::crc32c_table_addr() { + if (_crc32c_table_addr == nullptr) { + _crc32c_table_addr = StubRoutines::ppc::generate_crc_constants(REVERSE_CRC32C_POLY); + } + return _crc32c_table_addr; +} + // Constants to fold n words as needed by macroAssembler. address StubRoutines::ppc::generate_crc_constants(juint reverse_poly) { // Layout of constant table: diff --git a/src/hotspot/cpu/riscv/stubGenerator_riscv.cpp b/src/hotspot/cpu/riscv/stubGenerator_riscv.cpp index a44fe39917c..a4f42104d35 100644 --- a/src/hotspot/cpu/riscv/stubGenerator_riscv.cpp +++ b/src/hotspot/cpu/riscv/stubGenerator_riscv.cpp @@ -6686,8 +6686,6 @@ static const int64_t right_3_bits = right_n_bits(3); StubRoutines::_catch_exception_entry = generate_catch_exception(); if (UseCRC32Intrinsics) { - // set table address before stub generation which use it - StubRoutines::_crc_table_adr = (address)StubRoutines::riscv::_crc_table; StubRoutines::_updateBytesCRC32 = generate_updateBytesCRC32(); } diff --git a/src/hotspot/cpu/riscv/stubRoutines_riscv.cpp b/src/hotspot/cpu/riscv/stubRoutines_riscv.cpp index 2a1150276c1..2aac95d71fa 100644 --- a/src/hotspot/cpu/riscv/stubRoutines_riscv.cpp +++ b/src/hotspot/cpu/riscv/stubRoutines_riscv.cpp @@ -52,6 +52,10 @@ bool StubRoutines::riscv::_completed = false; /** * crc_table[] from jdk/src/java.base/share/native/libzip/zlib/crc32.h */ + +address StubRoutines::crc_table_addr() { return (address)StubRoutines::riscv::_crc_table; } +address StubRoutines::crc32c_table_addr() { ShouldNotCallThis(); return nullptr; } + ATTRIBUTE_ALIGNED(4096) juint StubRoutines::riscv::_crc_table[] = { // Table 0 diff --git a/src/hotspot/cpu/riscv/stubRoutines_riscv.hpp b/src/hotspot/cpu/riscv/stubRoutines_riscv.hpp index 1cd10b996db..0f90777ce64 100644 --- a/src/hotspot/cpu/riscv/stubRoutines_riscv.hpp +++ b/src/hotspot/cpu/riscv/stubRoutines_riscv.hpp @@ -48,6 +48,7 @@ enum platform_dependent_constants { class riscv { friend class StubGenerator; + friend class StubRoutines; #if INCLUDE_JVMCI friend class JVMCIVMStructs; #endif diff --git a/src/hotspot/cpu/s390/stubGenerator_s390.cpp b/src/hotspot/cpu/s390/stubGenerator_s390.cpp index aaed67fd269..32c15e56baf 100644 --- a/src/hotspot/cpu/s390/stubGenerator_s390.cpp +++ b/src/hotspot/cpu/s390/stubGenerator_s390.cpp @@ -3308,12 +3308,10 @@ class StubGenerator: public StubCodeGenerator { } if (UseCRC32Intrinsics) { - StubRoutines::_crc_table_adr = (address)StubRoutines::zarch::_crc_table; StubRoutines::_updateBytesCRC32 = generate_CRC32_updateBytes(); } if (UseCRC32CIntrinsics) { - StubRoutines::_crc32c_table_addr = (address)StubRoutines::zarch::_crc32c_table; StubRoutines::_updateBytesCRC32C = generate_CRC32C_updateBytes(); } diff --git a/src/hotspot/cpu/s390/stubRoutines_s390.cpp b/src/hotspot/cpu/s390/stubRoutines_s390.cpp index e75928ad00e..6feb20f9604 100644 --- a/src/hotspot/cpu/s390/stubRoutines_s390.cpp +++ b/src/hotspot/cpu/s390/stubRoutines_s390.cpp @@ -78,14 +78,17 @@ void StubRoutines::zarch::generate_load_absolute_address(MacroAssembler* masm, R #endif } +address StubRoutines::crc_table_addr() { return (address)StubRoutines::zarch::_crc_table; } +address StubRoutines::crc32c_table_addr() { return (address)StubRoutines::zarch::_crc32c_table; } + void StubRoutines::zarch::generate_load_crc_table_addr(MacroAssembler* masm, Register table) { const uint64_t table_contents = 0x77073096UL; // required contents of table[1] - generate_load_absolute_address(masm, table, StubRoutines::_crc_table_adr, table_contents); + generate_load_absolute_address(masm, table, StubRoutines::crc_table_addr(), table_contents); } void StubRoutines::zarch::generate_load_crc32c_table_addr(MacroAssembler* masm, Register table) { const uint64_t table_contents = 0xf26b8303UL; // required contents of table[1] - generate_load_absolute_address(masm, table, StubRoutines::_crc32c_table_addr, table_contents); + generate_load_absolute_address(masm, table, StubRoutines::crc32c_table_addr(), table_contents); } diff --git a/src/hotspot/cpu/s390/stubRoutines_s390.hpp b/src/hotspot/cpu/s390/stubRoutines_s390.hpp index 7a4bc18eb7d..494d90cd85a 100644 --- a/src/hotspot/cpu/s390/stubRoutines_s390.hpp +++ b/src/hotspot/cpu/s390/stubRoutines_s390.hpp @@ -62,6 +62,7 @@ enum method_handles_platform_dependent_constants { class zarch { friend class StubGenerator; + friend class StubRoutines; public: enum { nof_instance_allocators = 10 }; diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64.cpp index e1c84d9528f..8a9bf3aa0b8 100644 --- a/src/hotspot/cpu/x86/stubGenerator_x86_64.cpp +++ b/src/hotspot/cpu/x86/stubGenerator_x86_64.cpp @@ -4095,15 +4095,11 @@ void StubGenerator::generate_initial_stubs() { StubRoutines::x86::_double_sign_flip = generate_fp_mask(StubId::stubgen_double_sign_flip_id, 0x8000000000000000); if (UseCRC32Intrinsics) { - // set table address before stub generation which use it - StubRoutines::_crc_table_adr = (address)StubRoutines::x86::_crc_table; StubRoutines::_updateBytesCRC32 = generate_updateBytesCRC32(); } if (UseCRC32CIntrinsics) { bool supports_clmul = VM_Version::supports_clmul(); - StubRoutines::x86::generate_CRC32C_table(supports_clmul); - StubRoutines::_crc32c_table_addr = (address)StubRoutines::x86::_crc32c_table; StubRoutines::_updateBytesCRC32C = generate_updateBytesCRC32C(supports_clmul); } diff --git a/src/hotspot/cpu/x86/stubRoutines_x86.cpp b/src/hotspot/cpu/x86/stubRoutines_x86.cpp index 9b524ae94cf..ee9cea08e64 100644 --- a/src/hotspot/cpu/x86/stubRoutines_x86.cpp +++ b/src/hotspot/cpu/x86/stubRoutines_x86.cpp @@ -45,6 +45,17 @@ STUBGEN_ARCH_ENTRIES_DO(DEFINE_ARCH_ENTRY, DEFINE_ARCH_ENTRY_INIT) #undef DEFINE_ARCH_ENTRY_INIT #undef DEFINE_ARCH_ENTRY +address StubRoutines::crc_table_addr() { + return (address)StubRoutines::x86::_crc_table; +} +address StubRoutines::crc32c_table_addr() { + if (StubRoutines::x86::_crc32c_table == nullptr) { + bool supports_clmul = VM_Version::supports_clmul(); + StubRoutines::x86::generate_CRC32C_table(supports_clmul); + } + return (address)StubRoutines::x86::_crc32c_table; +} + address StubRoutines::x86::_k256_adr = nullptr; address StubRoutines::x86::_k256_W_adr = nullptr; address StubRoutines::x86::_k512_W_addr = nullptr; @@ -291,7 +302,7 @@ static uint32_t crc32c_f_pow_n(uint32_t n) { return result; } -juint *StubRoutines::x86::_crc32c_table; +juint* StubRoutines::x86::_crc32c_table = nullptr; void StubRoutines::x86::generate_CRC32C_table(bool is_pclmulqdq_table_supported) { diff --git a/src/hotspot/cpu/x86/stubRoutines_x86.hpp b/src/hotspot/cpu/x86/stubRoutines_x86.hpp index c4930e1593c..7d13c4f6e7a 100644 --- a/src/hotspot/cpu/x86/stubRoutines_x86.hpp +++ b/src/hotspot/cpu/x86/stubRoutines_x86.hpp @@ -44,6 +44,7 @@ enum platform_dependent_constants { class x86 { friend class StubGenerator; + friend class StubRoutines; friend class VMStructs; // declare fields for arch-specific entries diff --git a/src/hotspot/cpu/zero/stubDeclarations_zero.hpp b/src/hotspot/cpu/zero/stubDeclarations_zero.hpp index 2357bbb5169..3126cf71460 100644 --- a/src/hotspot/cpu/zero/stubDeclarations_zero.hpp +++ b/src/hotspot/cpu/zero/stubDeclarations_zero.hpp @@ -37,7 +37,7 @@ do_arch_blob, \ do_arch_entry, \ do_arch_entry_init) \ - do_arch_blob(initial, 0) \ + do_arch_blob(initial, 32) \ #define STUBGEN_CONTINUATION_BLOBS_ARCH_DO(do_stub, \ @@ -58,7 +58,7 @@ do_arch_blob, \ do_arch_entry, \ do_arch_entry_init) \ - do_arch_blob(final, 0) \ + do_arch_blob(final, 32) \ #endif // CPU_ZERO_STUBDECLARATIONS_HPP diff --git a/src/hotspot/cpu/zero/stubRoutines_zero.cpp b/src/hotspot/cpu/zero/stubRoutines_zero.cpp index 47d2c27eefd..9b53f09be5d 100644 --- a/src/hotspot/cpu/zero/stubRoutines_zero.cpp +++ b/src/hotspot/cpu/zero/stubRoutines_zero.cpp @@ -28,4 +28,5 @@ #include "runtime/javaThread.hpp" #include "runtime/stubRoutines.hpp" -// zero has no arch-specific stubs nor any associated entries +address StubRoutines::crc_table_addr() { ShouldNotCallThis(); return nullptr; } +address StubRoutines::crc32c_table_addr() { ShouldNotCallThis(); return nullptr; } diff --git a/src/hotspot/share/jvmci/jvmciCompilerToVM.hpp b/src/hotspot/share/jvmci/jvmciCompilerToVM.hpp index 41531b083fc..71331b578a5 100644 --- a/src/hotspot/share/jvmci/jvmciCompilerToVM.hpp +++ b/src/hotspot/share/jvmci/jvmciCompilerToVM.hpp @@ -131,6 +131,8 @@ class CompilerToVM { static address dlog10; static address dpow; + static address crc_table_addr; + static address symbol_init; static address symbol_clinit; diff --git a/src/hotspot/share/jvmci/jvmciCompilerToVMInit.cpp b/src/hotspot/share/jvmci/jvmciCompilerToVMInit.cpp index b6d919fdfe9..8a1a02d62b3 100644 --- a/src/hotspot/share/jvmci/jvmciCompilerToVMInit.cpp +++ b/src/hotspot/share/jvmci/jvmciCompilerToVMInit.cpp @@ -151,6 +151,8 @@ address CompilerToVM::Data::dlog; address CompilerToVM::Data::dlog10; address CompilerToVM::Data::dpow; +address CompilerToVM::Data::crc_table_addr; + address CompilerToVM::Data::symbol_init; address CompilerToVM::Data::symbol_clinit; @@ -289,6 +291,7 @@ void CompilerToVM::Data::initialize(JVMCI_TRAPS) { SET_TRIGFUNC_OR_NULL(dtanh); SET_TRIGFUNC_OR_NULL(dcbrt); + SET_TRIGFUNC_OR_NULL(crc_table_addr); #undef SET_TRIGFUNC_OR_NULL diff --git a/src/hotspot/share/jvmci/vmStructs_jvmci.cpp b/src/hotspot/share/jvmci/vmStructs_jvmci.cpp index 32ef3eb3e14..88d098468e9 100644 --- a/src/hotspot/share/jvmci/vmStructs_jvmci.cpp +++ b/src/hotspot/share/jvmci/vmStructs_jvmci.cpp @@ -147,6 +147,7 @@ static_field(CompilerToVM::Data, dlog, address) \ static_field(CompilerToVM::Data, dlog10, address) \ static_field(CompilerToVM::Data, dpow, address) \ + static_field(CompilerToVM::Data, crc_table_addr, address) \ \ static_field(CompilerToVM::Data, symbol_init, address) \ static_field(CompilerToVM::Data, symbol_clinit, address) \ @@ -417,8 +418,6 @@ static_field(StubRoutines, _dilithiumMontMulByConstant, address) \ static_field(StubRoutines, _dilithiumDecomposePoly, address) \ static_field(StubRoutines, _updateBytesCRC32, address) \ - static_field(StubRoutines, _crc_table_adr, address) \ - static_field(StubRoutines, _crc32c_table_addr, address) \ static_field(StubRoutines, _updateBytesCRC32C, address) \ static_field(StubRoutines, _updateBytesAdler32, address) \ static_field(StubRoutines, _multiplyToLen, address) \ diff --git a/src/hotspot/share/runtime/stubDeclarations.hpp b/src/hotspot/share/runtime/stubDeclarations.hpp index 3574aeaf636..4af017687fa 100644 --- a/src/hotspot/share/runtime/stubDeclarations.hpp +++ b/src/hotspot/share/runtime/stubDeclarations.hpp @@ -631,7 +631,7 @@ do_arch_entry, do_arch_entry_init) \ end_blob(preuniverse) \ -#define STUBGEN_INITIAL_BLOBS_DO(do_blob, end_blob, \ +#define STUBGEN_INITIAL_BLOBS_DO(do_blob, end_blob, \ do_stub, \ do_entry, do_entry_init, \ do_entry_array, \ @@ -651,12 +651,9 @@ do_stub(initial, updateBytesCRC32) \ do_entry(initial, updateBytesCRC32, updateBytesCRC32, \ updateBytesCRC32) \ - do_entry(initial, updateBytesCRC32, crc_table_adr, crc_table_addr) \ do_stub(initial, updateBytesCRC32C) \ do_entry(initial, updateBytesCRC32C, updateBytesCRC32C, \ updateBytesCRC32C) \ - do_entry(initial, updateBytesCRC32C, crc32c_table_addr, \ - crc32c_table_addr) \ do_stub(initial, f2hf) \ do_entry(initial, f2hf, f2hf, f2hf_adr) \ do_stub(initial, hf2f) \ diff --git a/src/hotspot/share/runtime/stubRoutines.cpp b/src/hotspot/share/runtime/stubRoutines.cpp index 2c50fe50915..e365d744d2b 100644 --- a/src/hotspot/share/runtime/stubRoutines.cpp +++ b/src/hotspot/share/runtime/stubRoutines.cpp @@ -144,8 +144,8 @@ static BufferBlob* initialize_stubs(BlobId blob_id, if (lt.is_enabled()) { LogStream ls(lt); ls.print_cr("%s\t not generated", buffer_name); - return nullptr; } + return nullptr; } TraceTime timer(timer_msg, TRACETIME_LOG(Info, startuptime)); // Add extra space for large CodeEntryAlignment diff --git a/src/hotspot/share/runtime/stubRoutines.hpp b/src/hotspot/share/runtime/stubRoutines.hpp index b333d6d74b9..edd393549cd 100644 --- a/src/hotspot/share/runtime/stubRoutines.hpp +++ b/src/hotspot/share/runtime/stubRoutines.hpp @@ -304,6 +304,12 @@ public: return dest_uninitialized ? _arrayof_oop_disjoint_arraycopy_uninit : _arrayof_oop_disjoint_arraycopy; } + // These methods is implemented in architecture-specific code. + // Any table that is returned must be allocated once-only in + // foreign memory (or C heap) rather generated in the code cache. + static address crc_table_addr(); + static address crc32c_table_addr(); + typedef void (*DataCacheWritebackStub)(void *); static DataCacheWritebackStub DataCacheWriteback_stub() { return CAST_TO_FN_PTR(DataCacheWritebackStub, _data_cache_writeback); } typedef void (*DataCacheWritebackSyncStub)(bool); From e756c0dbbb7d99df0751d71726b173e4eabcc903 Mon Sep 17 00:00:00 2001 From: William Kemper Date: Fri, 25 Jul 2025 17:59:46 +0000 Subject: [PATCH 83/94] 8361726: Shenandoah: More detailed evacuation instrumentation Reviewed-by: ysr, kdnilsen --- .../gc/shenandoah/shenandoahControlThread.cpp | 6 ++ .../gc/shenandoah/shenandoahEvacTracker.cpp | 80 ++++++++++++------- .../gc/shenandoah/shenandoahEvacTracker.hpp | 56 ++++++++++--- .../shenandoah/shenandoahGenerationalHeap.cpp | 17 +--- .../shenandoah/shenandoahGenerationalHeap.hpp | 6 -- .../share/gc/shenandoah/shenandoahHeap.cpp | 12 ++- .../share/gc/shenandoah/shenandoahHeap.hpp | 8 ++ .../shenandoah/shenandoahThreadLocalData.cpp | 5 +- .../shenandoah/shenandoahThreadLocalData.hpp | 10 +-- 9 files changed, 130 insertions(+), 70 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp index ff62e8aa976..421997e06d2 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahControlThread.cpp @@ -210,6 +210,12 @@ void ShenandoahControlThread::run_service() { ResourceMark rm; LogStream ls(lt); heap->phase_timings()->print_cycle_on(&ls); +#ifdef NOT_PRODUCT + ShenandoahEvacuationTracker* evac_tracker = heap->evac_tracker(); + ShenandoahCycleStats evac_stats = evac_tracker->flush_cycle_to_global(); + evac_tracker->print_evacuations_on(&ls, &evac_stats.workers, + &evac_stats.mutators); +#endif } } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahEvacTracker.cpp b/src/hotspot/share/gc/shenandoah/shenandoahEvacTracker.cpp index b1d474fa78d..499e1342083 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahEvacTracker.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahEvacTracker.cpp @@ -30,10 +30,22 @@ #include "runtime/thread.hpp" #include "runtime/threadSMR.inline.hpp" +ShenandoahEvacuationStats::ShenandoahEvacuations* ShenandoahEvacuationStats::get_category( + ShenandoahAffiliation from, + ShenandoahAffiliation to) { + if (from == YOUNG_GENERATION) { + if (to == YOUNG_GENERATION) { + return &_young; + } + assert(to == OLD_GENERATION, "If not evacuating to young, must be promotion to old"); + return &_promotion; + } + assert(from == OLD_GENERATION, "If not evacuating from young, then must be from old"); + return &_old; +} + ShenandoahEvacuationStats::ShenandoahEvacuationStats() - : _evacuations_completed(0), _bytes_completed(0), - _evacuations_attempted(0), _bytes_attempted(0), - _use_age_table(ShenandoahGenerationalCensusAtEvac || !ShenandoahGenerationalAdaptiveTenuring), + : _use_age_table(ShenandoahGenerationalCensusAtEvac || !ShenandoahGenerationalAdaptiveTenuring), _age_table(nullptr) { if (_use_age_table) { _age_table = new AgeTable(false); @@ -45,14 +57,17 @@ AgeTable* ShenandoahEvacuationStats::age_table() const { return _age_table; } -void ShenandoahEvacuationStats::begin_evacuation(size_t bytes) { - ++_evacuations_attempted; - _bytes_attempted += bytes; +void ShenandoahEvacuationStats::begin_evacuation(size_t bytes, ShenandoahAffiliation from, ShenandoahAffiliation to) { + ShenandoahEvacuations* category = get_category(from, to); + category->_evacuations_attempted++; + category->_bytes_attempted += bytes; + } -void ShenandoahEvacuationStats::end_evacuation(size_t bytes) { - ++_evacuations_completed; - _bytes_completed += bytes; +void ShenandoahEvacuationStats::end_evacuation(size_t bytes, ShenandoahAffiliation from, ShenandoahAffiliation to) { + ShenandoahEvacuations* category = get_category(from, to); + category->_evacuations_completed++; + category->_bytes_completed += bytes; } void ShenandoahEvacuationStats::record_age(size_t bytes, uint age) { @@ -63,34 +78,39 @@ void ShenandoahEvacuationStats::record_age(size_t bytes, uint age) { } void ShenandoahEvacuationStats::accumulate(const ShenandoahEvacuationStats* other) { - _evacuations_completed += other->_evacuations_completed; - _bytes_completed += other->_bytes_completed; - _evacuations_attempted += other->_evacuations_attempted; - _bytes_attempted += other->_bytes_attempted; + _young.accumulate(other->_young); + _old.accumulate(other->_old); + _promotion.accumulate(other->_promotion); + if (_use_age_table) { _age_table->merge(other->age_table()); } } void ShenandoahEvacuationStats::reset() { - _evacuations_completed = _evacuations_attempted = 0; - _bytes_completed = _bytes_attempted = 0; + _young.reset(); + _old.reset(); + _promotion.reset(); + if (_use_age_table) { _age_table->clear(); } } -void ShenandoahEvacuationStats::print_on(outputStream* st) { -#ifndef PRODUCT +void ShenandoahEvacuationStats::ShenandoahEvacuations::print_on(outputStream* st) const { size_t abandoned_size = _bytes_attempted - _bytes_completed; size_t abandoned_count = _evacuations_attempted - _evacuations_completed; - st->print_cr("Evacuated %zu%s across %zu objects, " - "abandoned %zu%s across %zu objects.", - byte_size_in_proper_unit(_bytes_completed), proper_unit_for_byte_size(_bytes_completed), - _evacuations_completed, - byte_size_in_proper_unit(abandoned_size), proper_unit_for_byte_size(abandoned_size), - abandoned_count); -#endif + st->print_cr("Evacuated " PROPERFMT" across %zu objects, " + "abandoned " PROPERFMT " across %zu objects.", + PROPERFMTARGS(_bytes_completed), _evacuations_completed, + PROPERFMTARGS(abandoned_size), abandoned_count); +} + +void ShenandoahEvacuationStats::print_on(outputStream* st) const { + st->print("Young: "); _young.print_on(st); + st->print("Promotion: "); _promotion.print_on(st); + st->print("Old: "); _old.print_on(st); + if (_use_age_table) { _age_table->print_on(st); } @@ -103,10 +123,10 @@ void ShenandoahEvacuationTracker::print_global_on(outputStream* st) { void ShenandoahEvacuationTracker::print_evacuations_on(outputStream* st, ShenandoahEvacuationStats* workers, ShenandoahEvacuationStats* mutators) { - st->print("Workers: "); + st->print_cr("Workers: "); workers->print_on(st); st->cr(); - st->print("Mutators: "); + st->print_cr("Mutators: "); mutators->print_on(st); st->cr(); @@ -160,12 +180,12 @@ ShenandoahCycleStats ShenandoahEvacuationTracker::flush_cycle_to_global() { return {workers, mutators}; } -void ShenandoahEvacuationTracker::begin_evacuation(Thread* thread, size_t bytes) { - ShenandoahThreadLocalData::begin_evacuation(thread, bytes); +void ShenandoahEvacuationTracker::begin_evacuation(Thread* thread, size_t bytes, ShenandoahAffiliation from, ShenandoahAffiliation to) { + ShenandoahThreadLocalData::begin_evacuation(thread, bytes, from, to); } -void ShenandoahEvacuationTracker::end_evacuation(Thread* thread, size_t bytes) { - ShenandoahThreadLocalData::end_evacuation(thread, bytes); +void ShenandoahEvacuationTracker::end_evacuation(Thread* thread, size_t bytes, ShenandoahAffiliation from, ShenandoahAffiliation to) { + ShenandoahThreadLocalData::end_evacuation(thread, bytes, from, to); } void ShenandoahEvacuationTracker::record_age(Thread* thread, size_t bytes, uint age) { diff --git a/src/hotspot/share/gc/shenandoah/shenandoahEvacTracker.hpp b/src/hotspot/share/gc/shenandoah/shenandoahEvacTracker.hpp index 7d195656b11..e5d7a7fec94 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahEvacTracker.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahEvacTracker.hpp @@ -26,14 +26,45 @@ #define SHARE_GC_SHENANDOAH_SHENANDOAHEVACTRACKER_HPP #include "gc/shared/ageTable.hpp" +#include "gc/shenandoah/shenandoahAffiliation.hpp" #include "utilities/ostream.hpp" class ShenandoahEvacuationStats : public CHeapObj { private: - size_t _evacuations_completed; - size_t _bytes_completed; - size_t _evacuations_attempted; - size_t _bytes_attempted; + struct ShenandoahEvacuations { + size_t _evacuations_completed; + size_t _bytes_completed; + size_t _evacuations_attempted; + size_t _bytes_attempted; + ShenandoahEvacuations() + : _evacuations_completed(0) + , _bytes_completed(0) + , _evacuations_attempted(0) + , _bytes_attempted(0) { + } + + void accumulate(const ShenandoahEvacuations& other) { + _evacuations_completed += other._evacuations_completed; + _bytes_completed += other._bytes_completed; + _evacuations_attempted += other._evacuations_attempted; + _bytes_attempted += other._bytes_attempted; + } + + void reset() { + _evacuations_completed = 0; + _bytes_completed = 0; + _evacuations_attempted = 0; + _bytes_attempted = 0; + } + + void print_on(outputStream* st) const; + }; + + ShenandoahEvacuations* get_category(ShenandoahAffiliation from, ShenandoahAffiliation to); + + ShenandoahEvacuations _young; + ShenandoahEvacuations _old; + ShenandoahEvacuations _promotion; bool _use_age_table; AgeTable* _age_table; @@ -43,11 +74,14 @@ private: AgeTable* age_table() const; - void begin_evacuation(size_t bytes); - void end_evacuation(size_t bytes); + // Record that the current thread is attempting to copy this many bytes. + void begin_evacuation(size_t bytes, ShenandoahAffiliation from, ShenandoahAffiliation to); + + // Record that the current thread has completed copying this many bytes. + void end_evacuation(size_t bytes, ShenandoahAffiliation from, ShenandoahAffiliation to); void record_age(size_t bytes, uint age); - void print_on(outputStream* st); + void print_on(outputStream* st) const; void accumulate(const ShenandoahEvacuationStats* other); void reset(); }; @@ -66,8 +100,12 @@ private: public: ShenandoahEvacuationTracker() = default; - void begin_evacuation(Thread* thread, size_t bytes); - void end_evacuation(Thread* thread, size_t bytes); + // Record that the given thread has begun to evacuate an object of this size. + void begin_evacuation(Thread* thread, size_t bytes, ShenandoahAffiliation from, ShenandoahAffiliation to); + + // Multiple threads may attempt to evacuate the same object, but only the successful thread will end the evacuation. + // Evacuations that were begun, but not ended are considered 'abandoned'. + void end_evacuation(Thread* thread, size_t bytes, ShenandoahAffiliation from, ShenandoahAffiliation to); void record_age(Thread* thread, size_t bytes, uint age); void print_global_on(outputStream* st); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp index a89fa76ba0f..d05ae713645 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp @@ -80,7 +80,6 @@ size_t ShenandoahGenerationalHeap::unsafe_max_tlab_alloc(Thread *thread) const { ShenandoahGenerationalHeap::ShenandoahGenerationalHeap(ShenandoahCollectorPolicy* policy) : ShenandoahHeap(policy), _age_census(nullptr), - _evac_tracker(new ShenandoahEvacuationTracker()), _min_plab_size(calculate_min_plab()), _max_plab_size(calculate_max_plab()), _regulator_thread(nullptr), @@ -100,18 +99,6 @@ void ShenandoahGenerationalHeap::print_init_logger() const { logger.print_all(); } -void ShenandoahGenerationalHeap::print_tracing_info() const { - ShenandoahHeap::print_tracing_info(); - - LogTarget(Info, gc, stats) lt; - if (lt.is_enabled()) { - LogStream ls(lt); - ls.cr(); - ls.cr(); - evac_tracker()->print_global_on(&ls); - } -} - void ShenandoahGenerationalHeap::initialize_heuristics() { // Initialize global generation and heuristics even in generational mode. ShenandoahHeap::initialize_heuristics(); @@ -338,7 +325,7 @@ oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, Shena } // Copy the object: - NOT_PRODUCT(evac_tracker()->begin_evacuation(thread, size * HeapWordSize)); + NOT_PRODUCT(evac_tracker()->begin_evacuation(thread, size * HeapWordSize, from_region->affiliation(), target_gen)); Copy::aligned_disjoint_words(cast_from_oop(p), copy, size); oop copy_val = cast_to_oop(copy); @@ -360,7 +347,7 @@ oop ShenandoahGenerationalHeap::try_evacuate_object(oop p, Thread* thread, Shena ContinuationGCSupport::relativize_stack_chunk(copy_val); // Record that the evacuation succeeded - NOT_PRODUCT(evac_tracker()->end_evacuation(thread, size * HeapWordSize)); + NOT_PRODUCT(evac_tracker()->end_evacuation(thread, size * HeapWordSize, from_region->affiliation(), target_gen)); if (target_gen == OLD_GENERATION) { old_generation()->handle_evacuation(copy, size, from_region->is_young()); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.hpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.hpp index 930c8ef7105..f23e49735e9 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.hpp @@ -36,7 +36,6 @@ class ShenandoahGenerationalControlThread; class ShenandoahAgeCensus; class ShenandoahGenerationalHeap : public ShenandoahHeap { - void print_tracing_info() const override; void stop() override; public: @@ -66,8 +65,6 @@ private: ShenandoahSharedFlag _is_aging_cycle; // Age census used for adapting tenuring threshold ShenandoahAgeCensus* _age_census; - // Used primarily to look for failed evacuation attempts. - ShenandoahEvacuationTracker* _evac_tracker; public: void set_aging_cycle(bool cond) { @@ -83,9 +80,6 @@ public: return _age_census; } - ShenandoahEvacuationTracker* evac_tracker() const { - return _evac_tracker; - } // Ages regions that haven't been used for allocations in the current cycle. // Resets ages for regions that have been used for allocations. diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp index 2dc768363d1..72ee7a67e2a 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp @@ -567,7 +567,8 @@ ShenandoahHeap::ShenandoahHeap(ShenandoahCollectorPolicy* policy) : _bitmap_region_special(false), _aux_bitmap_region_special(false), _liveness_cache(nullptr), - _collection_set(nullptr) + _collection_set(nullptr), + _evac_tracker(new ShenandoahEvacuationTracker()) { // Initialize GC mode early, many subsequent initialization procedures depend on it initialize_mode(); @@ -1352,6 +1353,7 @@ oop ShenandoahHeap::try_evacuate_object(oop p, Thread* thread, ShenandoahHeapReg } // Copy the object: + NOT_PRODUCT(evac_tracker()->begin_evacuation(thread, size * HeapWordSize, from_region->affiliation(), target_gen)); Copy::aligned_disjoint_words(cast_from_oop(p), copy, size); // Try to install the new forwarding pointer. @@ -1361,6 +1363,7 @@ oop ShenandoahHeap::try_evacuate_object(oop p, Thread* thread, ShenandoahHeapReg // Successfully evacuated. Our copy is now the public one! ContinuationGCSupport::relativize_stack_chunk(copy_val); shenandoah_assert_correct(nullptr, copy_val); + NOT_PRODUCT(evac_tracker()->end_evacuation(thread, size * HeapWordSize, from_region->affiliation(), target_gen)); return copy_val; } else { // Failed to evacuate. We need to deal with the object that is left behind. Since this @@ -1590,6 +1593,13 @@ void ShenandoahHeap::print_tracing_info() const { ResourceMark rm; LogStream ls(lt); +#ifdef NOT_PRODUCT + evac_tracker()->print_global_on(&ls); + + ls.cr(); + ls.cr(); +#endif + phase_timings()->print_global_on(&ls); ls.cr(); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp index a32334019f1..4a9b9906863 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp @@ -557,6 +557,10 @@ public: ShenandoahEvacOOMHandler* oom_evac_handler() { return &_oom_evac_handler; } + ShenandoahEvacuationTracker* evac_tracker() const { + return _evac_tracker; + } + void on_cycle_start(GCCause::Cause cause, ShenandoahGeneration* generation); void on_cycle_end(ShenandoahGeneration* generation); @@ -789,6 +793,10 @@ private: oop try_evacuate_object(oop src, Thread* thread, ShenandoahHeapRegion* from_region, ShenandoahAffiliation target_gen); +protected: + // Used primarily to look for failed evacuation attempts. + ShenandoahEvacuationTracker* _evac_tracker; + public: static address in_cset_fast_test_addr(); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.cpp b/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.cpp index c444a0ba86a..dd500462d0f 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.cpp @@ -44,10 +44,7 @@ ShenandoahThreadLocalData::ShenandoahThreadLocalData() : _plab_promoted(0), _plab_allows_promotion(true), _plab_retries_enabled(true), - _evacuation_stats(nullptr) { - if (ShenandoahHeap::heap()->mode()->is_generational()) { - _evacuation_stats = new ShenandoahEvacuationStats(); - } + _evacuation_stats(new ShenandoahEvacuationStats()) { } ShenandoahThreadLocalData::~ShenandoahThreadLocalData() { diff --git a/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.hpp b/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.hpp index c1cebdf1dde..098e20a72ec 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahThreadLocalData.hpp @@ -30,6 +30,7 @@ #include "gc/shared/gcThreadLocalData.hpp" #include "gc/shared/plab.hpp" #include "gc/shenandoah/mode/shenandoahMode.hpp" +#include "gc/shenandoah/shenandoahAffiliation.hpp" #include "gc/shenandoah/shenandoahBarrierSet.hpp" #include "gc/shenandoah/shenandoahCardTable.hpp" #include "gc/shenandoah/shenandoahCodeRoots.hpp" @@ -159,12 +160,12 @@ public: data(thread)->_gclab_size = v; } - static void begin_evacuation(Thread* thread, size_t bytes) { - data(thread)->_evacuation_stats->begin_evacuation(bytes); + static void begin_evacuation(Thread* thread, size_t bytes, ShenandoahAffiliation from, ShenandoahAffiliation to) { + data(thread)->_evacuation_stats->begin_evacuation(bytes, from, to); } - static void end_evacuation(Thread* thread, size_t bytes) { - data(thread)->_evacuation_stats->end_evacuation(bytes); + static void end_evacuation(Thread* thread, size_t bytes, ShenandoahAffiliation from, ShenandoahAffiliation to) { + data(thread)->_evacuation_stats->end_evacuation(bytes, from, to); } static void record_age(Thread* thread, size_t bytes, uint age) { @@ -172,7 +173,6 @@ public: } static ShenandoahEvacuationStats* evacuation_stats(Thread* thread) { - shenandoah_assert_generational(); return data(thread)->_evacuation_stats; } From d288ca28be7bfba3abe9f54cefbe53e73c25707e Mon Sep 17 00:00:00 2001 From: Jaikiran Pai Date: Sat, 26 Jul 2025 02:17:13 +0000 Subject: [PATCH 84/94] 8358048: java/net/httpclient/HttpsTunnelAuthTest.java incorrectly calls Thread::stop Reviewed-by: djelinski, alanb, vyazici --- test/jdk/java/net/httpclient/HttpsTunnelAuthTest.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/jdk/java/net/httpclient/HttpsTunnelAuthTest.java b/test/jdk/java/net/httpclient/HttpsTunnelAuthTest.java index 83961453f05..c6b2c323693 100644 --- a/test/jdk/java/net/httpclient/HttpsTunnelAuthTest.java +++ b/test/jdk/java/net/httpclient/HttpsTunnelAuthTest.java @@ -37,12 +37,11 @@ import java.util.stream.Collectors; import java.util.stream.Stream; import javax.net.ssl.SSLContext; import jdk.httpclient.test.lib.common.HttpServerAdapters; -import jdk.httpclient.test.lib.http2.Http2TestServer; import jdk.test.lib.net.SimpleSSLContext; import static java.lang.System.out; -/** +/* * @test * @bug 8262027 * @summary Verify that it's possible to handle proxy authentication manually @@ -62,7 +61,7 @@ import static java.lang.System.out; //-Djdk.internal.httpclient.debug=true -Dtest.debug=true public class HttpsTunnelAuthTest implements HttpServerAdapters, AutoCloseable { - static final String data[] = { + static final String[] data = { "Lorem ipsum", "dolor sit amet", "consectetur adipiscing elit, sed do eiusmod tempor", @@ -150,7 +149,7 @@ public class HttpsTunnelAuthTest implements HttpServerAdapters, AutoCloseable { @Override public void close() throws Exception { - if (proxy != null) close(proxy::stop); + if (proxy != null) close(proxy); if (http1Server != null) close(http1Server::stop); if (https1Server != null) close(https1Server::stop); if (https2Server != null) close(https2Server::stop); @@ -160,7 +159,8 @@ public class HttpsTunnelAuthTest implements HttpServerAdapters, AutoCloseable { try { closeable.close(); } catch (Exception x) { - // OK. + // OK to ignore and just log + System.err.println("ignoring failure during close() of " + closeable + " due to: " + x); } } From de59da27a60bd0afaf8deaf6d4a3d743a4f59db8 Mon Sep 17 00:00:00 2001 From: Michael McMahon Date: Sat, 26 Jul 2025 22:22:36 +0000 Subject: [PATCH 85/94] 8362581: Timeouts in java/nio/channels/SocketChannel/OpenLeak.java on UNIX Reviewed-by: jpai, alanb, djelinski --- .../share/classes/jdk/internal/util/Exceptions.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/util/Exceptions.java b/src/java.base/share/classes/jdk/internal/util/Exceptions.java index eb4286cd1af..70ee7026a7b 100644 --- a/src/java.base/share/classes/jdk/internal/util/Exceptions.java +++ b/src/java.base/share/classes/jdk/internal/util/Exceptions.java @@ -274,12 +274,9 @@ public final class Exceptions { */ public static IOException ioException(IOException e, SocketAddress addr) { setup(); - if (addr == null) { + if (!enhancedSocketExceptionText || addr == null) { return e; } - if (!enhancedSocketExceptionText) { - return create(e, e.getMessage()); - } if (addr instanceof UnixDomainSocketAddress) { return ofUnixDomain(e, (UnixDomainSocketAddress)addr); } else if (addr instanceof InetSocketAddress) { From 8fcbb110e9941af5fe162c6affff36e0bf652bda Mon Sep 17 00:00:00 2001 From: SendaoYan Date: Sun, 27 Jul 2025 01:19:06 +0000 Subject: [PATCH 86/94] 8362855: Test java/net/ipv6tests/TcpTest.java should report SkippedException when there no ia4addr or ia6addr Reviewed-by: jpai --- test/jdk/java/net/ipv6tests/TcpTest.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/test/jdk/java/net/ipv6tests/TcpTest.java b/test/jdk/java/net/ipv6tests/TcpTest.java index 0ca35737a76..e75397b5181 100644 --- a/test/jdk/java/net/ipv6tests/TcpTest.java +++ b/test/jdk/java/net/ipv6tests/TcpTest.java @@ -31,6 +31,7 @@ * @library /test/lib * @build jdk.test.lib.NetworkConfiguration * jdk.test.lib.Platform + * jtreg.SkippedException * @run main TcpTest -d */ @@ -38,6 +39,8 @@ import java.net.*; import java.io.*; import java.util.concurrent.TimeUnit; +import jtreg.SkippedException; + public class TcpTest extends Tests { static ServerSocket server, server1, server2; static Socket c1, c2, c3, s1, s2, s3; @@ -62,12 +65,10 @@ public class TcpTest extends Tests { public static void main (String[] args) throws Exception { checkDebug(args); if (ia4addr == null) { - System.out.println ("No IPV4 addresses: exiting test"); - return; + throw new SkippedException("No IPV4 addresses: exiting test"); } if (ia6addr == null) { - System.out.println ("No IPV6 addresses: exiting test"); - return; + throw new SkippedException("No IPV6 addresses: exiting test"); } dprintln ("Local Addresses"); dprintln (ia4addr.toString()); From 3263361a28c7e8c02734cb94bc9576e9f3ba5b50 Mon Sep 17 00:00:00 2001 From: Jaikiran Pai Date: Sun, 27 Jul 2025 06:44:09 +0000 Subject: [PATCH 87/94] 8360981: Remove use of Thread.stop in test/jdk/java/net/Socket/DeadlockTest.java Reviewed-by: alanb --- test/jdk/java/net/Socket/DeadlockTest.java | 30 +++++++++++----------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/test/jdk/java/net/Socket/DeadlockTest.java b/test/jdk/java/net/Socket/DeadlockTest.java index 40b001171f1..3e2d38dc142 100644 --- a/test/jdk/java/net/Socket/DeadlockTest.java +++ b/test/jdk/java/net/Socket/DeadlockTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -31,12 +31,19 @@ * @run main/othervm -Djava.net.preferIPv4Stack=true DeadlockTest */ -import java.net.*; -import java.io.*; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutput; +import java.io.ObjectOutputStream; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; + import jdk.test.lib.net.IPSupport; public class DeadlockTest { - public static void main(String [] argv) throws Exception { + public static void main(String[] argv) throws Exception { IPSupport.throwSkippedExceptionIfNonOperational(); ServerSocket ss = new ServerSocket(0, 0, InetAddress.getLoopbackAddress()); @@ -52,16 +59,9 @@ public class DeadlockTest { Thread c1 = new Thread(ct); c1.start(); - // Wait for the client thread to finish - c1.join(20000); - - // If timeout, we assume there is a deadlock - if (c1.isAlive() == true) { - // Close the socket to force the server thread - // terminate too - s1.stop(); - throw new Exception("Takes too long. Dead lock"); - } + // Wait for the client thread to finish. + // If it doesn't finish then it's a sign of a deadlock + c1.join(); } finally { ss.close(); clientSocket.close(); @@ -73,7 +73,7 @@ class ServerThread implements Runnable { private static boolean dbg = false; - ObjectInputStream in; + ObjectInputStream in; ObjectOutputStream out; ServerSocket server; From 4189fcbac40943f3b26c3a01938837b4e4762285 Mon Sep 17 00:00:00 2001 From: Yuri Gaevsky Date: Sun, 27 Jul 2025 14:54:52 +0000 Subject: [PATCH 88/94] 8362596: RISC-V: Improve _vectorizedHashCode intrinsic Reviewed-by: fyang, fjiang --- src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp b/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp index ce13ebde74f..bf71d2c68f1 100644 --- a/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp +++ b/src/hotspot/cpu/riscv/c2_MacroAssembler_riscv.cpp @@ -1952,16 +1952,15 @@ void C2_MacroAssembler::arrays_hashcode(Register ary, Register cnt, Register res mv(pow31_3, 29791); // [31^^3] mv(pow31_2, 961); // [31^^2] - slli(chunks_end, chunks, chunks_end_shift); - add(chunks_end, ary, chunks_end); + shadd(chunks_end, chunks, ary, t0, chunks_end_shift); andi(cnt, cnt, stride - 1); // don't forget about tail! bind(WIDE_LOOP); - mulw(result, result, pow31_4); // 31^^4 * h arrays_hashcode_elload(t0, Address(ary, 0 * elsize), eltype); arrays_hashcode_elload(t1, Address(ary, 1 * elsize), eltype); arrays_hashcode_elload(tmp5, Address(ary, 2 * elsize), eltype); arrays_hashcode_elload(tmp6, Address(ary, 3 * elsize), eltype); + mulw(result, result, pow31_4); // 31^^4 * h mulw(t0, t0, pow31_3); // 31^^3 * ary[i+0] addw(result, result, t0); mulw(t1, t1, pow31_2); // 31^^2 * ary[i+1] @@ -1976,8 +1975,7 @@ void C2_MacroAssembler::arrays_hashcode(Register ary, Register cnt, Register res beqz(cnt, DONE); bind(TAIL); - slli(chunks_end, cnt, chunks_end_shift); - add(chunks_end, ary, chunks_end); + shadd(chunks_end, cnt, ary, t0, chunks_end_shift); bind(TAIL_LOOP); arrays_hashcode_elload(t0, Address(ary), eltype); From d4322d92d1fb0ef63678897d13b192446a171c94 Mon Sep 17 00:00:00 2001 From: "Archie L. Cobbs" Date: Mon, 28 Jul 2025 12:42:35 -0500 Subject: [PATCH 89/94] Address a couple of review comments. --- .../share/classes/com/sun/tools/javac/code/LintMapper.java | 2 +- .../share/classes/com/sun/tools/javac/comp/Check.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/LintMapper.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/LintMapper.java index 30464c0cba2..fba32dc4297 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/LintMapper.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/LintMapper.java @@ -52,7 +52,7 @@ import com.sun.tools.javac.util.JCDiagnostic.DiagnosticPosition; * Maps source code positions to the applicable {@link Lint} instance. * *

      - * Because {@code @SuppressWarnings} is a Java symbol, in general this mapping can't be be + * Because {@code @SuppressWarnings} is a Java symbol, in general this mapping can't be * calculated until after attribution. As each top-level declaration (class, package, or module) * is attributed, this singleton is notified and the {@link Lint}s that apply to every source * position within that top-level declaration are calculated. diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java index 5b95af79f3c..3d9eff107da 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java @@ -121,7 +121,7 @@ public class Check { // The set of lint options currently in effect. It is initialized // from the context, and then is set/reset as needed by Attr as it // visits all the various parts of the trees during attribution. - Lint lint; + private Lint lint; // The method being analyzed in Attr - it is set/reset as needed by // Attr as it visits new method declarations. From f65e1950467c7848bdd4c179fb736d918bf38f8b Mon Sep 17 00:00:00 2001 From: "Archie L. Cobbs" Date: Mon, 28 Jul 2025 15:54:53 -0500 Subject: [PATCH 90/94] Refactor LintMapper to clean up internal type hierarchy per review. --- .../com/sun/tools/javac/code/LintMapper.java | 179 ++++++++---------- 1 file changed, 82 insertions(+), 97 deletions(-) diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/LintMapper.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/LintMapper.java index fba32dc4297..019a9880185 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/LintMapper.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/LintMapper.java @@ -28,6 +28,7 @@ package com.sun.tools.javac.code; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; @@ -71,7 +72,7 @@ public class LintMapper { // The key for the context singleton private static final Context.Key CONTEXT_KEY = new Context.Key<>(); - // Per-source file lint information + // Per-source file information. Note: during the parsing of a file, an entry exists but the FileInfo value is null private final Map fileInfoMap = new HashMap<>(); // Compiler context @@ -122,7 +123,7 @@ public class LintMapper { * * @param sourceFile source file * @param pos source position - * @return the applicable {@link Lint}, if known + * @return the applicable {@link Lint}, if known, otherwise empty */ public Optional lintAt(JavaFileObject sourceFile, DiagnosticPosition pos) { initializeIfNeeded(); @@ -152,79 +153,74 @@ public class LintMapper { // Parsing Notifications /** - * Invoked when file parsing starts to create an entry for the new file. + * Invoked when file parsing starts to create an entry for the new file (but with a null value). */ public void startParsingFile(JavaFileObject sourceFile) { initializeIfNeeded(); - fileInfoMap.put(sourceFile, new FileInfo()); + fileInfoMap.put(sourceFile, null); } /** - * Invoked when file parsing completes to identify the top-level declarations. + * Invoked when file parsing completes to put in place a corresponding {@link FileInfo}. */ public void finishParsingFile(JCCompilationUnit tree) { Assert.check(rootLint != null); - fileInfoMap.get(tree.sourcefile).afterParse(tree); + fileInfoMap.put(tree.sourcefile, new FileInfo(tree)); } // FileInfo /** - * Holds {@link Lint} information for one source file. + * Holds {@link Lint} information for a fully parsed source file. * *

      - * Instances evolve through three states: - *

        - *
      • Before the file has been completely parsed, {@link #topSpans} is null. - *
      • Immediately after the file has been parsed, {@link #topSpans} contains zero or more {@link Span}s - * corresponding to the top-level declarations in the file, and {@code rootNode} has no children. - *
      • When a top-level declaration is attributed, a corresponding {@link DeclNode} child matching one - * of the {@link Span}s in {@link #topSpans} is created and added to {@link #rootNode}. - *
      + * Initially (immediately after parsing), "rootNode" will have an (unmapped) {@link DeclNode} child corresponding + * to each top-level declaration in the source file. As those top-level declarations are attributed, the corresponding + * {@link DeclNode} child is replaced with a {@link MappedDeclNode}, created via {@link MappedDeclNodeBuilder}. */ private class FileInfo { - List topSpans; // the spans of all top level declarations - final DeclNode rootNode = new DeclNode(rootLint); // tree of file's "interesting" declaration nodes + final MappedDeclNode rootNode = new MappedDeclNode(rootLint); // tree of this file's "interesting" declaration nodes - // Find the Lint that applies to the given position, if known - Optional lintAt(DiagnosticPosition pos) { - if (topSpans == null) // has the file been parsed yet? - return Optional.empty(); // -> no, we don't know yet - if (!findTopSpan(pos).isPresent()) // is the position within some top-level declaration? - return Optional.of(rootLint); // -> no, use the root lint - DeclNode topNode = findTopNode(pos); - if (topNode == null) // has that declaration been attributed yet? - return Optional.empty(); // -> no, we don't know yet - DeclNode node = topNode.find(pos); // find the best matching node (it must exist) - return Optional.of(node.lint); // use its Lint - } - - void afterParse(JCCompilationUnit tree) { - Assert.check(topSpans == null, "source already parsed"); - topSpans = tree.defs.stream() + // After parsing: Create the root node and its immediate (unmapped) children + FileInfo(JCCompilationUnit tree) { + tree.defs.stream() .filter(this::isTopLevelDecl) - .map(decl -> new Span(decl, tree.endPositions)) - .collect(Collectors.toList()); + .forEach(decl -> new DeclNode(rootNode, decl, tree.endPositions)); } + // After attribution: Replace top-level DeclNode child with a MappedDeclNode subtree void afterAttr(JCTree tree, EndPosTable endPositions) { - Assert.check(topSpans != null, "source not parsed"); - Assert.check(findTopNode(tree.pos()) == null, "duplicate call"); - new DeclNodeTreeBuilder(rootNode, endPositions).scan(tree); + Assert.check(rootNode != null, "source not parsed"); + DeclNode node = findTopNode(tree.pos(), true); + Assert.check(node != null, "unknown declaration"); + Assert.check(!(node instanceof MappedDeclNode), "duplicate call"); + new MappedDeclNodeBuilder(rootNode, endPositions).scan(tree); } - Optional findTopSpan(DiagnosticPosition pos) { - return topSpans.stream() - .filter(span -> span.contains(pos)) - .findFirst(); + // Find the Lint configuration that applies to the given position, if known + Optional lintAt(DiagnosticPosition pos) { + return switch (findTopNode(pos, false)) { // find the top-level declaration containing "pos", if any + case MappedDeclNode node // if the declaration has been attributed... + -> Optional.of(node.find(pos).lint); // -> return the most specific applicable Lint configuration + case DeclNode node // if the declaration has not been attributed... + -> Optional.empty(); // -> we don't know yet + case null // if "pos" is outside of any declaration... + -> Optional.of(rootLint); // -> use the root lint + }; } - DeclNode findTopNode(DiagnosticPosition pos) { - return rootNode.children.stream() - .filter(node -> node.contains(pos)) - .findFirst() - .orElse(null); + // Find (and optionally remove) the top-level declaration containing "pos", if any + DeclNode findTopNode(DiagnosticPosition pos, boolean remove) { + for (Iterator i = rootNode.children.iterator(); i.hasNext(); ) { + DeclNode node = i.next(); + if (node.contains(pos)) { + if (remove) + i.remove(); + return node; + } + } + return null; } boolean isTopLevelDecl(JCTree tree) { @@ -234,110 +230,99 @@ public class LintMapper { } } -// Span +// DeclNode /** - * Represents a lexical range in a file. + * Represents the lexical range corresponding to a module, package, class, method, or variable declaration, + * or a "root" representing an entire file. */ - private static class Span { + private static class DeclNode { - final int startPos; - final int endPos; + final int startPos; // declaration's starting position + final int endPos; // declaration's ending position + final MappedDeclNode parent; // the immediately containing declaration (null for root) - Span(int startPos, int endPos) { + DeclNode(int startPos, int endPos, MappedDeclNode parent) { this.startPos = startPos; this.endPos = endPos; + this.parent = parent; + if (parent != null) + parent.children.add(this); } - Span(JCTree tree, EndPosTable endPositions) { - this(TreeInfo.getStartPos(tree), TreeInfo.getEndPos(tree, endPositions)); - } - - boolean contains(int pos) { - return pos == startPos || (pos > startPos && pos < endPos); + // Create a node representing the given declaration + DeclNode(MappedDeclNode parent, JCTree tree, EndPosTable endPositions) { + this(TreeInfo.getStartPos(tree), TreeInfo.getEndPos(tree, endPositions), parent); } boolean contains(DiagnosticPosition pos) { - return contains(pos.getLintPosition()); + int offset = pos.getLintPosition(); + return offset == startPos || (offset > startPos && offset < endPos); } - boolean contains(Span that) { + boolean contains(DeclNode that) { return this.startPos <= that.startPos && this.endPos >= that.endPos; } @Override public String toString() { - return String.format("Span[%d-%d]", startPos, endPos); + return String.format("DeclNode[%d-%d]", startPos, endPos); } } -// DeclNode - /** - * Represents a declaration and the {@link Lint} configuration that applies within its lexical range. - * - *

      - * For each file, there is a root node represents the entire source file. At the next level down are - * nodes representing the top-level declarations in the file, and so on. + * A {@link DeclNode} for which the corresponding {@link Lint} configuration is known. */ - private static class DeclNode extends Span { + private static class MappedDeclNode extends DeclNode { - final Symbol symbol; // the symbol declared by this declaration (null for root) - final DeclNode parent; // the immediately containing declaration (null for root) - final List children = new ArrayList<>(); // the immediately next level down declarations under this node + final Symbol symbol; // declaration symbol (null for root; for debug purposes only) final Lint lint; // the Lint configuration that applies at this declaration + final List children = new ArrayList<>(); // the nested declarations one level below this node - // Create a root node representing the entire file - DeclNode(Lint rootLint) { - super(Integer.MIN_VALUE, Integer.MAX_VALUE); + // Create a node representing the entire file, using the root lint configuration + MappedDeclNode(Lint rootLint) { + super(Integer.MIN_VALUE, Integer.MAX_VALUE, null); this.symbol = null; - this.parent = null; this.lint = rootLint; } - // Create a normal declaration node - DeclNode(Symbol symbol, DeclNode parent, JCTree tree, EndPosTable endPositions, Lint lint) { - super(tree, endPositions); + // Create a node representing the given declaration and its corresponding Lint configuration + MappedDeclNode(Symbol symbol, MappedDeclNode parent, JCTree tree, EndPosTable endPositions, Lint lint) { + super(parent, tree, endPositions); this.symbol = symbol; - this.parent = parent; this.lint = lint; - parent.children.add(this); } - // Find the narrowest node in this tree that contains the given position, if any - DeclNode find(DiagnosticPosition pos) { + // Find the narrowest node in this tree (including me) that contains the given position, if any + MappedDeclNode find(DiagnosticPosition pos) { return children.stream() + .map(MappedDeclNode.class::cast) // this cast is ok because this method is never invoked on the root instance .map(child -> child.find(pos)) .filter(Objects::nonNull) .reduce((a, b) -> a.contains(b) ? b : a) .orElseGet(() -> contains(pos) ? this : null); } - // Stream this node and all descendents via pre-order recursive descent - Stream stream() { - return Stream.concat(Stream.of(this), children.stream().flatMap(DeclNode::stream)); - } - @Override public String toString() { String label = symbol != null ? "sym=" + symbol : "ROOT"; - return String.format("DeclNode[%s,lint=%s]", label, lint); + return String.format("MappedDeclNode[%d-%d,%s,lint=%s]", startPos, endPos, label, lint); } } -// DeclNodeTreeBuilder +// MappedDeclNodeBuilder /** - * Builds a tree of {@link DeclNode}s starting from a top-level declaration. + * Builds a tree of {@link MappedDeclNode}s starting from a top-level declaration. */ - private class DeclNodeTreeBuilder extends TreeScanner { + private class MappedDeclNodeBuilder extends TreeScanner { private final EndPosTable endPositions; - private DeclNode parent; + private MappedDeclNode parent; private Lint lint; - DeclNodeTreeBuilder(DeclNode rootNode, EndPosTable endPositions) { + MappedDeclNodeBuilder(MappedDeclNode rootNode, EndPosTable endPositions) { this.endPositions = endPositions; this.parent = rootNode; this.lint = rootNode.lint; // i.e, rootLint @@ -386,8 +371,8 @@ public class LintMapper { return; } - // Add a DeclNode here - DeclNode node = new DeclNode(symbol, parent, tree, endPositions, lint); + // Add a MappedDeclNode here + MappedDeclNode node = new MappedDeclNode(symbol, parent, tree, endPositions, lint); parent = node; try { recursion.accept(tree); From 147cb2dc30a5adfef636e4bc7246ef7fb5608c82 Mon Sep 17 00:00:00 2001 From: "Archie L. Cobbs" Date: Tue, 29 Jul 2025 16:36:16 -0500 Subject: [PATCH 91/94] More refactoring to simplify LintMapper per review. --- .../com/sun/tools/javac/code/LintMapper.java | 133 ++++++++---------- 1 file changed, 60 insertions(+), 73 deletions(-) diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/LintMapper.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/LintMapper.java index 019a9880185..c27c59ce705 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/LintMapper.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/LintMapper.java @@ -174,53 +174,40 @@ public class LintMapper { * Holds {@link Lint} information for a fully parsed source file. * *

      - * Initially (immediately after parsing), "rootNode" will have an (unmapped) {@link DeclNode} child corresponding - * to each top-level declaration in the source file. As those top-level declarations are attributed, the corresponding - * {@link DeclNode} child is replaced with a {@link MappedDeclNode}, created via {@link MappedDeclNodeBuilder}. + * Initially (immediately after parsing), "unmappedDecls" will contain a {@link Decl} corresponding + * to each top-level declaration in the source file. As those top-level declarations are attributed, + * the {@link Decl} is removed and a {@link MappedDecl} is added to "mappedDecls". */ private class FileInfo { - final MappedDeclNode rootNode = new MappedDeclNode(rootLint); // tree of this file's "interesting" declaration nodes + final List unmappedDecls = new ArrayList<>(); // unmapped (i.e., awaiting attribution) top-level declarations + final MappedDecl mappedDecls = new MappedDecl(rootLint); // root node with subtree for each mapped top-level declaration - // After parsing: Create the root node and its immediate (unmapped) children + // After parsing: Create a Decl corresponding to each top-level declaration and add to "unmappedDecls" FileInfo(JCCompilationUnit tree) { tree.defs.stream() .filter(this::isTopLevelDecl) - .forEach(decl -> new DeclNode(rootNode, decl, tree.endPositions)); + .map(decl -> new Decl(decl, tree.endPositions)) + .forEach(unmappedDecls::add); } - // After attribution: Replace top-level DeclNode child with a MappedDeclNode subtree + // After attribution: Discard the Decl from "unmappedDecls" and add a corresponding MappedDecl to "mappedDecls" void afterAttr(JCTree tree, EndPosTable endPositions) { - Assert.check(rootNode != null, "source not parsed"); - DeclNode node = findTopNode(tree.pos(), true); - Assert.check(node != null, "unknown declaration"); - Assert.check(!(node instanceof MappedDeclNode), "duplicate call"); - new MappedDeclNodeBuilder(rootNode, endPositions).scan(tree); + for (Iterator i = unmappedDecls.iterator(); i.hasNext(); ) { + if (i.next().contains(tree.pos())) { + new MappedDeclBuilder(mappedDecls, endPositions).scan(tree); + i.remove(); + return; + } + } + throw new AssertionError("top-level declaration not found"); } // Find the Lint configuration that applies to the given position, if known Optional lintAt(DiagnosticPosition pos) { - return switch (findTopNode(pos, false)) { // find the top-level declaration containing "pos", if any - case MappedDeclNode node // if the declaration has been attributed... - -> Optional.of(node.find(pos).lint); // -> return the most specific applicable Lint configuration - case DeclNode node // if the declaration has not been attributed... - -> Optional.empty(); // -> we don't know yet - case null // if "pos" is outside of any declaration... - -> Optional.of(rootLint); // -> use the root lint - }; - } - - // Find (and optionally remove) the top-level declaration containing "pos", if any - DeclNode findTopNode(DiagnosticPosition pos, boolean remove) { - for (Iterator i = rootNode.children.iterator(); i.hasNext(); ) { - DeclNode node = i.next(); - if (node.contains(pos)) { - if (remove) - i.remove(); - return node; - } - } - return null; + if (unmappedDecls.stream().anyMatch(decl -> decl.contains(pos))) // the top level declaration is not mapped yet + return Optional.empty(); + return Optional.of(mappedDecls.bestMatch(pos).lint); // return the narrowest matching declaration } boolean isTopLevelDecl(JCTree tree) { @@ -230,29 +217,23 @@ public class LintMapper { } } -// DeclNode +// Decl /** - * Represents the lexical range corresponding to a module, package, class, method, or variable declaration, - * or a "root" representing an entire file. + * Represents a lexical range corresponding to a module, package, class, method, or variable declaration. */ - private static class DeclNode { + private static class Decl { - final int startPos; // declaration's starting position - final int endPos; // declaration's ending position - final MappedDeclNode parent; // the immediately containing declaration (null for root) + final int startPos; + final int endPos; - DeclNode(int startPos, int endPos, MappedDeclNode parent) { + Decl(int startPos, int endPos) { this.startPos = startPos; this.endPos = endPos; - this.parent = parent; - if (parent != null) - parent.children.add(this); } - // Create a node representing the given declaration - DeclNode(MappedDeclNode parent, JCTree tree, EndPosTable endPositions) { - this(TreeInfo.getStartPos(tree), TreeInfo.getEndPos(tree, endPositions), parent); + Decl(JCTree tree, EndPosTable endPositions) { + this(TreeInfo.getStartPos(tree), TreeInfo.getEndPos(tree, endPositions)); } boolean contains(DiagnosticPosition pos) { @@ -260,44 +241,49 @@ public class LintMapper { return offset == startPos || (offset > startPos && offset < endPos); } - boolean contains(DeclNode that) { + boolean contains(Decl that) { return this.startPos <= that.startPos && this.endPos >= that.endPos; } @Override public String toString() { - return String.format("DeclNode[%d-%d]", startPos, endPos); + return String.format("Decl[%d-%d]", startPos, endPos); } } - /** - * A {@link DeclNode} for which the corresponding {@link Lint} configuration is known. - */ - private static class MappedDeclNode extends DeclNode { +// MappedDecl + + /** + * A declaration for which the corresponding {@link Lint} configuration is known. + */ + private static class MappedDecl extends Decl { - final Symbol symbol; // declaration symbol (null for root; for debug purposes only) final Lint lint; // the Lint configuration that applies at this declaration - final List children = new ArrayList<>(); // the nested declarations one level below this node + final Symbol symbol; // declaration symbol (for debug purposes only; null for root) + final MappedDecl parent; // the parent node of this node + final List children = new ArrayList<>(); // the nested declarations one level below this node // Create a node representing the entire file, using the root lint configuration - MappedDeclNode(Lint rootLint) { - super(Integer.MIN_VALUE, Integer.MAX_VALUE, null); - this.symbol = null; + MappedDecl(Lint rootLint) { + super(Integer.MIN_VALUE, Integer.MAX_VALUE); this.lint = rootLint; + this.symbol = null; + this.parent = null; } // Create a node representing the given declaration and its corresponding Lint configuration - MappedDeclNode(Symbol symbol, MappedDeclNode parent, JCTree tree, EndPosTable endPositions, Lint lint) { - super(parent, tree, endPositions); - this.symbol = symbol; + MappedDecl(Symbol symbol, MappedDecl parent, JCTree tree, EndPosTable endPositions, Lint lint) { + super(TreeInfo.getStartPos(tree), TreeInfo.getEndPos(tree, endPositions)); this.lint = lint; + this.symbol = symbol; + this.parent = parent; + parent.children.add(this); } // Find the narrowest node in this tree (including me) that contains the given position, if any - MappedDeclNode find(DiagnosticPosition pos) { + MappedDecl bestMatch(DiagnosticPosition pos) { return children.stream() - .map(MappedDeclNode.class::cast) // this cast is ok because this method is never invoked on the root instance - .map(child -> child.find(pos)) + .map(child -> child.bestMatch(pos)) .filter(Objects::nonNull) .reduce((a, b) -> a.contains(b) ? b : a) .orElseGet(() -> contains(pos) ? this : null); @@ -306,23 +292,24 @@ public class LintMapper { @Override public String toString() { String label = symbol != null ? "sym=" + symbol : "ROOT"; - return String.format("MappedDeclNode[%d-%d,%s,lint=%s]", startPos, endPos, label, lint); + return String.format("MappedDecl[%d-%d,%s,lint=%s]", startPos, endPos, label, lint); } } -// MappedDeclNodeBuilder +// MappedDeclBuilder /** - * Builds a tree of {@link MappedDeclNode}s starting from a top-level declaration. + * Builds a tree of {@link MappedDecl}s starting from a top-level declaration. + * The tree is sparse: only "interesting" declarations are included. */ - private class MappedDeclNodeBuilder extends TreeScanner { + private class MappedDeclBuilder extends TreeScanner { private final EndPosTable endPositions; - private MappedDeclNode parent; + private MappedDecl parent; private Lint lint; - MappedDeclNodeBuilder(MappedDeclNode rootNode, EndPosTable endPositions) { + MappedDeclBuilder(MappedDecl rootNode, EndPosTable endPositions) { this.endPositions = endPositions; this.parent = rootNode; this.lint = rootNode.lint; // i.e, rootLint @@ -365,14 +352,14 @@ public class LintMapper { Lint previousLint = lint; lint = lint.augment(symbol); - // If this declaration is not "interesting", we don't need to create a DeclNode for it + // If this declaration is not "interesting", we don't need to create a MappedDecl for it if (lint == previousLint && parent.parent != null) { recursion.accept(tree); return; } - // Add a MappedDeclNode here - MappedDeclNode node = new MappedDeclNode(symbol, parent, tree, endPositions, lint); + // Add a MappedDecl here + MappedDecl node = new MappedDecl(symbol, parent, tree, endPositions, lint); parent = node; try { recursion.accept(tree); From 6a1289e93721f92332bf8e972898f4af346d7ef6 Mon Sep 17 00:00:00 2001 From: "Archie L. Cobbs" Date: Wed, 30 Jul 2025 10:29:00 -0500 Subject: [PATCH 92/94] More simplification of LintMapper per review suggestions. --- .../com/sun/tools/javac/code/LintMapper.java | 162 +++++++++--------- .../com/sun/tools/javac/comp/Attr.java | 2 +- 2 files changed, 78 insertions(+), 86 deletions(-) diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/LintMapper.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/LintMapper.java index c27c59ce705..bc9b3cf63da 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/LintMapper.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/LintMapper.java @@ -138,9 +138,9 @@ public class LintMapper { * @param sourceFile source file * @param tree top-level declaration (class, package, or module) */ - public void calculateLints(JavaFileObject sourceFile, JCTree tree, EndPosTable endPositions) { + public void calculateLints(JavaFileObject sourceFile, JCTree tree) { Assert.check(rootLint != null); - fileInfoMap.get(sourceFile).afterAttr(tree, endPositions); + fileInfoMap.get(sourceFile).afterAttr(tree); } /** @@ -165,7 +165,7 @@ public class LintMapper { */ public void finishParsingFile(JCCompilationUnit tree) { Assert.check(rootLint != null); - fileInfoMap.put(tree.sourcefile, new FileInfo(tree)); + fileInfoMap.put(tree.sourcefile, new FileInfo(rootLint, tree)); } // FileInfo @@ -174,40 +174,49 @@ public class LintMapper { * Holds {@link Lint} information for a fully parsed source file. * *

      - * Initially (immediately after parsing), "unmappedDecls" will contain a {@link Decl} corresponding + * Initially (immediately after parsing), "unmappedDecls" will contain a {@link JCTree} corresponding * to each top-level declaration in the source file. As those top-level declarations are attributed, - * the {@link Decl} is removed and a {@link MappedDecl} is added to "mappedDecls". + * the {@link JCTree} is removed and a new {@link MappedDecl} subtree is added to the "mappedDecls" tree. */ - private class FileInfo { + private static class FileInfo { - final List unmappedDecls = new ArrayList<>(); // unmapped (i.e., awaiting attribution) top-level declarations - final MappedDecl mappedDecls = new MappedDecl(rootLint); // root node with subtree for each mapped top-level declaration + EndPosTable endPositions; // end position table for this source file (only during attribution) + final MappedDecl mappedDecls; // root node with subtree for each mapped top-level declaration + final List unmappedDecls; // unmapped (i.e., awaiting attribution) top-level declarations - // After parsing: Create a Decl corresponding to each top-level declaration and add to "unmappedDecls" - FileInfo(JCCompilationUnit tree) { - tree.defs.stream() + // After parsing: Add top-level declarations to our "unmappedDecls" list + FileInfo(Lint rootLint, JCCompilationUnit tree) { + this.endPositions = tree.endPositions; + this.mappedDecls = new MappedDecl(rootLint); + this.unmappedDecls = tree.defs.stream() .filter(this::isTopLevelDecl) - .map(decl -> new Decl(decl, tree.endPositions)) - .forEach(unmappedDecls::add); + .collect(Collectors.toCollection(ArrayList::new)); } - // After attribution: Discard the Decl from "unmappedDecls" and add a corresponding MappedDecl to "mappedDecls" - void afterAttr(JCTree tree, EndPosTable endPositions) { - for (Iterator i = unmappedDecls.iterator(); i.hasNext(); ) { - if (i.next().contains(tree.pos())) { - new MappedDeclBuilder(mappedDecls, endPositions).scan(tree); + // After attribution: Discard the tree from "unmappedDecls" and add a corresponding MappedDecl to "mappedDecls" + void afterAttr(JCTree tree) { + MappedDeclBuilder builder = null; + for (Iterator i = unmappedDecls.iterator(); i.hasNext(); ) { + if (contains(i.next(), tree.pos())) { + builder = new MappedDeclBuilder(mappedDecls, endPositions); i.remove(); - return; + break; } } - throw new AssertionError("top-level declaration not found"); + Assert.check(builder != null, "top-level declaration not found"); + builder.scan(tree); + if (unmappedDecls.isEmpty()) + endPositions = null; // gc friendly } - // Find the Lint configuration that applies to the given position, if known + // Find the (narrowest) Lint that applies to the given position, unless the position has not been mapped yet Optional lintAt(DiagnosticPosition pos) { - if (unmappedDecls.stream().anyMatch(decl -> decl.contains(pos))) // the top level declaration is not mapped yet - return Optional.empty(); - return Optional.of(mappedDecls.bestMatch(pos).lint); // return the narrowest matching declaration + boolean mapped = unmappedDecls.stream().noneMatch(tree -> contains(tree, pos)); + return mapped ? Optional.of(mappedDecls.bestMatch(pos).lint) : Optional.empty(); + } + + boolean contains(JCTree tree, DiagnosticPosition pos) { + return FileInfo.contains(TreeInfo.getStartPos(tree), TreeInfo.getEndPos(tree, endPositions), pos); } boolean isTopLevelDecl(JCTree tree) { @@ -215,69 +224,46 @@ public class LintMapper { || tree.getTag() == Tag.PACKAGEDEF || tree.getTag() == Tag.CLASSDEF; } - } -// Decl - - /** - * Represents a lexical range corresponding to a module, package, class, method, or variable declaration. - */ - private static class Decl { - - final int startPos; - final int endPos; - - Decl(int startPos, int endPos) { - this.startPos = startPos; - this.endPos = endPos; - } - - Decl(JCTree tree, EndPosTable endPositions) { - this(TreeInfo.getStartPos(tree), TreeInfo.getEndPos(tree, endPositions)); - } - - boolean contains(DiagnosticPosition pos) { + static boolean contains(int startPos, int endPos, DiagnosticPosition pos) { int offset = pos.getLintPosition(); return offset == startPos || (offset > startPos && offset < endPos); } - - boolean contains(Decl that) { - return this.startPos <= that.startPos && this.endPos >= that.endPos; - } - - @Override - public String toString() { - return String.format("Decl[%d-%d]", startPos, endPos); - } } // MappedDecl /** - * A declaration for which the corresponding {@link Lint} configuration is known. + * A module, package, class, method, or variable declaration within which all {@link Lint} configurations are known. + * There is also a root instance that represents the entire file. */ - private static class MappedDecl extends Decl { + private static class MappedDecl { + final int startPos; // declaration's lexical starting position + final int endPos; // declaration's lexical ending position final Lint lint; // the Lint configuration that applies at this declaration final Symbol symbol; // declaration symbol (for debug purposes only; null for root) final MappedDecl parent; // the parent node of this node - final List children = new ArrayList<>(); // the nested declarations one level below this node + final List children; // the nested declarations one level below this node // Create a node representing the entire file, using the root lint configuration MappedDecl(Lint rootLint) { - super(Integer.MIN_VALUE, Integer.MAX_VALUE); - this.lint = rootLint; - this.symbol = null; - this.parent = null; + this(Integer.MIN_VALUE, Integer.MAX_VALUE, rootLint, null, null); } // Create a node representing the given declaration and its corresponding Lint configuration - MappedDecl(Symbol symbol, MappedDecl parent, JCTree tree, EndPosTable endPositions, Lint lint) { - super(TreeInfo.getStartPos(tree), TreeInfo.getEndPos(tree, endPositions)); + MappedDecl(JCTree tree, EndPosTable endPositions, Lint lint, Symbol symbol, MappedDecl parent) { + this(TreeInfo.getStartPos(tree), TreeInfo.getEndPos(tree, endPositions), lint, symbol, parent); + parent.children.add(this); + } + + MappedDecl(int startPos, int endPos, Lint lint, Symbol symbol, MappedDecl parent) { + this.startPos = startPos; + this.endPos = endPos; this.lint = lint; this.symbol = symbol; this.parent = parent; - parent.children.add(this); + this.children = new ArrayList<>(); } // Find the narrowest node in this tree (including me) that contains the given position, if any @@ -289,6 +275,14 @@ public class LintMapper { .orElseGet(() -> contains(pos) ? this : null); } + boolean contains(DiagnosticPosition pos) { + return FileInfo.contains(startPos, endPos, pos); + } + + boolean contains(MappedDecl that) { + return this.startPos <= that.startPos && this.endPos >= that.endPos; + } + @Override public String toString() { String label = symbol != null ? "sym=" + symbol : "ROOT"; @@ -300,9 +294,9 @@ public class LintMapper { /** * Builds a tree of {@link MappedDecl}s starting from a top-level declaration. - * The tree is sparse: only "interesting" declarations are included. + * The tree is sparse: only declarations that differ from their parent are included. */ - private class MappedDeclBuilder extends TreeScanner { + private static class MappedDeclBuilder extends TreeScanner { private final EndPosTable endPositions; @@ -340,32 +334,30 @@ public class LintMapper { scanDecl(tree, tree.sym, super::visitVarDef); } - private void scanDecl(T tree, Symbol symbol, Consumer recursion) { + private void scanDecl(T tree, Symbol symbol, Consumer recursor) { - // "symbol" can be null if there were earlier errors; skip this declaration if so + // The "symbol" can be null if there were earlier errors; skip this declaration if so if (symbol == null) { - recursion.accept(tree); + recursor.accept(tree); return; } - // Update the current Lint in effect; note lint.augment() returns the same instance if there's no change + // Update the current Lint in effect Lint previousLint = lint; - lint = lint.augment(symbol); + lint = lint.augment(symbol); // note: lint.augment() returns the same instance if there's no change - // If this declaration is not "interesting", we don't need to create a MappedDecl for it - if (lint == previousLint && parent.parent != null) { - recursion.accept(tree); - return; - } - - // Add a MappedDecl here - MappedDecl node = new MappedDecl(symbol, parent, tree, endPositions, lint); - parent = node; - try { - recursion.accept(tree); - } finally { - parent = node.parent; - lint = previousLint; + // Add a MappedDecl node here, but only if this declaration's Lint configuration is different from its parent + if (lint != previousLint) { + MappedDecl node = new MappedDecl(tree, endPositions, lint, symbol, parent); + parent = node; + try { + recursor.accept(tree); + } finally { + parent = node.parent; + lint = previousLint; + } + } else { + recursor.accept(tree); } } } 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 45ece909ad7..ac5b4f41830 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 @@ -5296,7 +5296,7 @@ public class Attr extends JCTree.Visitor { annotate.flush(); // Now that this tree is attributed, we can calculate the Lint configuration everywhere within it - lintMapper.calculateLints(env.toplevel.sourcefile, env.tree, env.toplevel.endPositions); + lintMapper.calculateLints(env.toplevel.sourcefile, env.tree); } public void attribPackage(DiagnosticPosition pos, PackageSymbol p) { From d1adde49247218d47ba0e993ff20b22fc8edd782 Mon Sep 17 00:00:00 2001 From: "Archie L. Cobbs" Date: Wed, 30 Jul 2025 15:12:59 -0500 Subject: [PATCH 93/94] More simplification of LintMapper per review suggestions. --- .../com/sun/tools/javac/code/LintMapper.java | 268 ++++++++---------- .../com/sun/tools/javac/comp/Attr.java | 2 +- 2 files changed, 120 insertions(+), 150 deletions(-) diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/LintMapper.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/LintMapper.java index bc9b3cf63da..5ac57b6508d 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/LintMapper.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/LintMapper.java @@ -34,7 +34,6 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.function.Consumer; -import java.util.stream.Collectors; import java.util.stream.Stream; import javax.tools.DiagnosticListener; @@ -138,9 +137,9 @@ public class LintMapper { * @param sourceFile source file * @param tree top-level declaration (class, package, or module) */ - public void calculateLints(JavaFileObject sourceFile, JCTree tree) { + public void calculateLints(JavaFileObject sourceFile, JCTree tree, EndPosTable endPositions) { Assert.check(rootLint != null); - fileInfoMap.get(sourceFile).afterAttr(tree); + fileInfoMap.get(sourceFile).afterAttr(tree, endPositions); } /** @@ -174,49 +173,40 @@ public class LintMapper { * Holds {@link Lint} information for a fully parsed source file. * *

      - * Initially (immediately after parsing), "unmappedDecls" will contain a {@link JCTree} corresponding - * to each top-level declaration in the source file. As those top-level declarations are attributed, - * the {@link JCTree} is removed and a new {@link MappedDecl} subtree is added to the "mappedDecls" tree. + * Initially (immediately after parsing), "unmappedDecls" contains a {@link Span} corresponding to each + * top-level declaration in the source file. As each top-level declaration is attributed, the corresponding + * {@link Span} is removed and the corresponding {@link LintRange} subtree is populated under "rootRange". */ private static class FileInfo { - EndPosTable endPositions; // end position table for this source file (only during attribution) - final MappedDecl mappedDecls; // root node with subtree for each mapped top-level declaration - final List unmappedDecls; // unmapped (i.e., awaiting attribution) top-level declarations + final LintRange rootRange; // the root LintRange (covering the entire source file) + final List unmappedDecls = new ArrayList<>(); // unmapped top-level declarations awaiting attribution // After parsing: Add top-level declarations to our "unmappedDecls" list FileInfo(Lint rootLint, JCCompilationUnit tree) { - this.endPositions = tree.endPositions; - this.mappedDecls = new MappedDecl(rootLint); - this.unmappedDecls = tree.defs.stream() + rootRange = new LintRange(rootLint); + tree.defs.stream() .filter(this::isTopLevelDecl) - .collect(Collectors.toCollection(ArrayList::new)); + .map(decl -> new Span(decl, tree.endPositions)) + .forEach(unmappedDecls::add); } - // After attribution: Discard the tree from "unmappedDecls" and add a corresponding MappedDecl to "mappedDecls" - void afterAttr(JCTree tree) { - MappedDeclBuilder builder = null; - for (Iterator i = unmappedDecls.iterator(); i.hasNext(); ) { - if (contains(i.next(), tree.pos())) { - builder = new MappedDeclBuilder(mappedDecls, endPositions); + // After attribution: Discard the span from "unmappedDecls" and populate the declaration's subtree under "rootRange" + void afterAttr(JCTree tree, EndPosTable endPositions) { + for (Iterator i = unmappedDecls.iterator(); i.hasNext(); ) { + if (i.next().contains(tree.pos())) { + rootRange.populateSubtree(tree, endPositions); i.remove(); - break; + return; } } - Assert.check(builder != null, "top-level declaration not found"); - builder.scan(tree); - if (unmappedDecls.isEmpty()) - endPositions = null; // gc friendly + throw new AssertionError("top-level declaration not found"); } - // Find the (narrowest) Lint that applies to the given position, unless the position has not been mapped yet + // Find the most specific Lint configuration applying to the given position, unless the position has not been mapped yet Optional lintAt(DiagnosticPosition pos) { - boolean mapped = unmappedDecls.stream().noneMatch(tree -> contains(tree, pos)); - return mapped ? Optional.of(mappedDecls.bestMatch(pos).lint) : Optional.empty(); - } - - boolean contains(JCTree tree, DiagnosticPosition pos) { - return FileInfo.contains(TreeInfo.getStartPos(tree), TreeInfo.getEndPos(tree, endPositions), pos); + boolean mapped = unmappedDecls.stream().noneMatch(span -> span.contains(pos)); + return mapped ? Optional.of(rootRange.bestMatch(pos).lint) : Optional.empty(); } boolean isTopLevelDecl(JCTree tree) { @@ -224,141 +214,121 @@ public class LintMapper { || tree.getTag() == Tag.PACKAGEDEF || tree.getTag() == Tag.CLASSDEF; } - - static boolean contains(int startPos, int endPos, DiagnosticPosition pos) { - int offset = pos.getLintPosition(); - return offset == startPos || (offset > startPos && offset < endPos); - } } -// MappedDecl +// Span /** - * A module, package, class, method, or variable declaration within which all {@link Lint} configurations are known. - * There is also a root instance that represents the entire file. + * A lexical range. */ - private static class MappedDecl { + private record Span(int startPos, int endPos) { - final int startPos; // declaration's lexical starting position - final int endPos; // declaration's lexical ending position - final Lint lint; // the Lint configuration that applies at this declaration - final Symbol symbol; // declaration symbol (for debug purposes only; null for root) - final MappedDecl parent; // the parent node of this node - final List children; // the nested declarations one level below this node + static final Span MAXIMAL = new Span(Integer.MIN_VALUE, Integer.MAX_VALUE); - // Create a node representing the entire file, using the root lint configuration - MappedDecl(Lint rootLint) { - this(Integer.MIN_VALUE, Integer.MAX_VALUE, rootLint, null, null); - } - - // Create a node representing the given declaration and its corresponding Lint configuration - MappedDecl(JCTree tree, EndPosTable endPositions, Lint lint, Symbol symbol, MappedDecl parent) { - this(TreeInfo.getStartPos(tree), TreeInfo.getEndPos(tree, endPositions), lint, symbol, parent); - parent.children.add(this); - } - - MappedDecl(int startPos, int endPos, Lint lint, Symbol symbol, MappedDecl parent) { - this.startPos = startPos; - this.endPos = endPos; - this.lint = lint; - this.symbol = symbol; - this.parent = parent; - this.children = new ArrayList<>(); - } - - // Find the narrowest node in this tree (including me) that contains the given position, if any - MappedDecl bestMatch(DiagnosticPosition pos) { - return children.stream() - .map(child -> child.bestMatch(pos)) - .filter(Objects::nonNull) - .reduce((a, b) -> a.contains(b) ? b : a) - .orElseGet(() -> contains(pos) ? this : null); + Span(JCTree tree, EndPosTable endPositions) { + this(TreeInfo.getStartPos(tree), TreeInfo.getEndPos(tree, endPositions)); } boolean contains(DiagnosticPosition pos) { - return FileInfo.contains(startPos, endPos, pos); + int offset = pos.getLintPosition(); + return offset == startPos || (offset > startPos && offset < endPos); } - boolean contains(MappedDecl that) { + boolean contains(Span that) { return this.startPos <= that.startPos && this.endPos >= that.endPos; } + } + +// LintRange + + /** + * A tree of nested lexical ranges and the {@link Lint} configurations that apply therein. + */ + private record LintRange( + Span span, // declaration's lexical range + Lint lint, // the Lint configuration that applies at this declaration + Symbol symbol, // declaration symbol (for debug purposes only; null for root) + LintRange parent, // the parent node of this node + List children // the nested declarations one level below this node + ) { + + // Create a node representing the entire file, using the root lint configuration + LintRange(Lint rootLint) { + this(Span.MAXIMAL, rootLint, null, null, new ArrayList<>()); + } + + // Create a node representing the given declaration and its corresponding Lint configuration + LintRange(JCTree tree, EndPosTable endPositions, Lint lint, Symbol symbol, LintRange parent) { + this(new Span(tree, endPositions), lint, symbol, parent, new ArrayList<>()); + parent.children.add(this); + } + + // Find the most specific node in this tree (including me) that contains the given position, if any + LintRange bestMatch(DiagnosticPosition pos) { + return children.stream() + .map(child -> child.bestMatch(pos)) + .filter(Objects::nonNull) + .reduce((a, b) -> a.span.contains(b.span) ? b : a) + .orElseGet(() -> span.contains(pos) ? this : null); + } + + // Populate a sparse subtree corresponding to the given nested declaration. + // Only when the Lint configuration differs from the parent is a node added. + void populateSubtree(JCTree tree, EndPosTable endPositions) { + new TreeScanner() { + + private LintRange parent = LintRange.this; + + @Override + public void visitModuleDef(JCModuleDecl tree) { + scanDecl(tree, tree.sym, super::visitModuleDef); + } + @Override + public void visitPackageDef(JCPackageDecl tree) { + scanDecl(tree, tree.packge, super::visitPackageDef); + } + @Override + public void visitClassDef(JCClassDecl tree) { + scanDecl(tree, tree.sym, super::visitClassDef); + } + @Override + public void visitMethodDef(JCMethodDecl tree) { + scanDecl(tree, tree.sym, super::visitMethodDef); + } + @Override + public void visitVarDef(JCVariableDecl tree) { + scanDecl(tree, tree.sym, super::visitVarDef); + } + + private void scanDecl(T tree, Symbol symbol, Consumer recursor) { + + // The "symbol" can be null if there were earlier errors; skip this declaration if so + if (symbol == null) { + recursor.accept(tree); + return; + } + + // Update the Lint using the declaration; if there's no change, then we don't need a new node here + Lint newLint = parent.lint.augment(symbol); + if (newLint == parent.lint) { // note: lint.augment() returns the same instance if there's no change + recursor.accept(tree); + return; + } + + // Add a new node here + LintRange node = parent = new LintRange(tree, endPositions, newLint, symbol, parent); + try { + recursor.accept(tree); + } finally { + parent = node.parent; + } + } + }.scan(tree); + } @Override public String toString() { - String label = symbol != null ? "sym=" + symbol : "ROOT"; - return String.format("MappedDecl[%d-%d,%s,lint=%s]", startPos, endPos, label, lint); - } - } - -// MappedDeclBuilder - - /** - * Builds a tree of {@link MappedDecl}s starting from a top-level declaration. - * The tree is sparse: only declarations that differ from their parent are included. - */ - private static class MappedDeclBuilder extends TreeScanner { - - private final EndPosTable endPositions; - - private MappedDecl parent; - private Lint lint; - - MappedDeclBuilder(MappedDecl rootNode, EndPosTable endPositions) { - this.endPositions = endPositions; - this.parent = rootNode; - this.lint = rootNode.lint; // i.e, rootLint - } - - @Override - public void visitModuleDef(JCModuleDecl tree) { - scanDecl(tree, tree.sym, super::visitModuleDef); - } - - @Override - public void visitPackageDef(JCPackageDecl tree) { - scanDecl(tree, tree.packge, super::visitPackageDef); - } - - @Override - public void visitClassDef(JCClassDecl tree) { - scanDecl(tree, tree.sym, super::visitClassDef); - } - - @Override - public void visitMethodDef(JCMethodDecl tree) { - scanDecl(tree, tree.sym, super::visitMethodDef); - } - - @Override - public void visitVarDef(JCVariableDecl tree) { - scanDecl(tree, tree.sym, super::visitVarDef); - } - - private void scanDecl(T tree, Symbol symbol, Consumer recursor) { - - // The "symbol" can be null if there were earlier errors; skip this declaration if so - if (symbol == null) { - recursor.accept(tree); - return; - } - - // Update the current Lint in effect - Lint previousLint = lint; - lint = lint.augment(symbol); // note: lint.augment() returns the same instance if there's no change - - // Add a MappedDecl node here, but only if this declaration's Lint configuration is different from its parent - if (lint != previousLint) { - MappedDecl node = new MappedDecl(tree, endPositions, lint, symbol, parent); - parent = node; - try { - recursor.accept(tree); - } finally { - parent = node.parent; - lint = previousLint; - } - } else { - recursor.accept(tree); - } + return String.format("LintRange[span=%s,sym=%s,lint=%s,children=%s]", span, symbol, lint, children); } } } 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 ac5b4f41830..45ece909ad7 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 @@ -5296,7 +5296,7 @@ public class Attr extends JCTree.Visitor { annotate.flush(); // Now that this tree is attributed, we can calculate the Lint configuration everywhere within it - lintMapper.calculateLints(env.toplevel.sourcefile, env.tree); + lintMapper.calculateLints(env.toplevel.sourcefile, env.tree, env.toplevel.endPositions); } public void attribPackage(DiagnosticPosition pos, PackageSymbol p) { From 7fca4a05930835af9209ed935c33c12eff3cb069 Mon Sep 17 00:00:00 2001 From: "Archie L. Cobbs" Date: Wed, 30 Jul 2025 15:54:03 -0500 Subject: [PATCH 94/94] Remove an unnecessary field. --- .../com/sun/tools/javac/code/LintMapper.java | 27 ++++++++----------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/LintMapper.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/LintMapper.java index 5ac57b6508d..340d941a703 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/LintMapper.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/LintMapper.java @@ -248,19 +248,17 @@ public class LintMapper { Span span, // declaration's lexical range Lint lint, // the Lint configuration that applies at this declaration Symbol symbol, // declaration symbol (for debug purposes only; null for root) - LintRange parent, // the parent node of this node List children // the nested declarations one level below this node ) { // Create a node representing the entire file, using the root lint configuration LintRange(Lint rootLint) { - this(Span.MAXIMAL, rootLint, null, null, new ArrayList<>()); + this(Span.MAXIMAL, rootLint, null, new ArrayList<>()); } // Create a node representing the given declaration and its corresponding Lint configuration - LintRange(JCTree tree, EndPosTable endPositions, Lint lint, Symbol symbol, LintRange parent) { - this(new Span(tree, endPositions), lint, symbol, parent, new ArrayList<>()); - parent.children.add(this); + LintRange(JCTree tree, EndPosTable endPositions, Lint lint, Symbol symbol) { + this(new Span(tree, endPositions), lint, symbol, new ArrayList<>()); } // Find the most specific node in this tree (including me) that contains the given position, if any @@ -277,7 +275,7 @@ public class LintMapper { void populateSubtree(JCTree tree, EndPosTable endPositions) { new TreeScanner() { - private LintRange parent = LintRange.this; + private LintRange currentNode = LintRange.this; @Override public void visitModuleDef(JCModuleDecl tree) { @@ -309,26 +307,23 @@ public class LintMapper { } // Update the Lint using the declaration; if there's no change, then we don't need a new node here - Lint newLint = parent.lint.augment(symbol); - if (newLint == parent.lint) { // note: lint.augment() returns the same instance if there's no change + Lint newLint = currentNode.lint.augment(symbol); + if (newLint == currentNode.lint) { // note: lint.augment() returns the same instance if there's no change recursor.accept(tree); return; } - // Add a new node here - LintRange node = parent = new LintRange(tree, endPositions, newLint, symbol, parent); + // Add a new node here and proceed + final LintRange previousNode = currentNode; + currentNode = new LintRange(tree, endPositions, newLint, symbol); + previousNode.children.add(currentNode); try { recursor.accept(tree); } finally { - parent = node.parent; + currentNode = previousNode; } } }.scan(tree); } - - @Override - public String toString() { - return String.format("LintRange[span=%s,sym=%s,lint=%s,children=%s]", span, symbol, lint, children); - } } }