From a34314dd79db0dd8ec1e7a5267762760bf5b0711 Mon Sep 17 00:00:00 2001 From: Xiaolong Peng Date: Thu, 25 Jun 2026 21:52:19 +0000 Subject: [PATCH 001/305] 8387260: Shenandoah: ShenandoahOldGeneration::_promoted_reserve should be atomic Reviewed-by: shade, kdnilsen, ruili, wkemper --- .../share/gc/shenandoah/shenandoahOldGeneration.cpp | 10 +++++++--- .../share/gc/shenandoah/shenandoahOldGeneration.hpp | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.cpp b/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.cpp index 0a0beaaffee..c92f74364fb 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.cpp @@ -132,16 +132,18 @@ ShenandoahOldGeneration::ShenandoahOldGeneration(uint max_queues) void ShenandoahOldGeneration::set_promoted_reserve(size_t new_val) { shenandoah_assert_heaplocked_or_safepoint(); - _promoted_reserve = new_val; + _promoted_reserve.store_relaxed(new_val); } size_t ShenandoahOldGeneration::get_promoted_reserve() const { - return _promoted_reserve; + return _promoted_reserve.load_relaxed(); } void ShenandoahOldGeneration::augment_promoted_reserve(size_t increment) { shenandoah_assert_heaplocked_or_safepoint(); - _promoted_reserve += increment; + // Writers are serialized by the heap lock, so relaxed ordering is sufficient; the atomic RMW + // only guards against tearing the concurrent lock-free reader (get_promoted_reserve). + _promoted_reserve.fetch_then_add(increment, memory_order_relaxed); } void ShenandoahOldGeneration::reset_promoted_expended() { @@ -194,6 +196,8 @@ void ShenandoahOldGeneration::maybe_log_promotion_failure_stats(bool concurrent) } bool ShenandoahOldGeneration::try_expend_promoted(size_t increment) { + // The promote reserve rarely changes during evacuation(only when there is PIP region), so snapshot it once; + // only _promoted_expended is contended and re-read on CAS failure. const size_t reserve = get_promoted_reserve(); size_t cur = _promoted_expended.load_relaxed(); while (cur + increment <= reserve) { diff --git a/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.hpp b/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.hpp index 43151af4c87..61a3114f906 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahOldGeneration.hpp @@ -58,7 +58,7 @@ private: // and in addition to the evacuation reserve for intra-generation evacuations (ShenandoahGeneration::_evacuation_reserve). // If there is more data ready to be promoted than can fit within this reserve, the promotion of some objects will be // deferred until a subsequent evacuation pass. - size_t _promoted_reserve; + Atomic _promoted_reserve; // Bytes of old-gen memory expended on promotions. This may be modified concurrently // by mutators and gc workers when promotion LABs are retired during evacuation. It From 8740fbb4eeaf742e88999d4f243e29d53d17be2b Mon Sep 17 00:00:00 2001 From: Jatin Bhateja Date: Fri, 26 Jun 2026 04:02:20 +0000 Subject: [PATCH 002/305] 8386255: Float16Vector NaN canonicalization for hashCode computation Reviewed-by: psandoz, sherman --- .../jdk/incubator/vector/Float16Vector.java | 16 ++++++++++++++-- .../incubator/vector/X-Vector.java.template | 19 +++++++++++++++++++ .../vector/Float16Vector128Tests.java | 16 +++++++++++++--- .../vector/Float16Vector256Tests.java | 16 +++++++++++++--- .../vector/Float16Vector512Tests.java | 16 +++++++++++++--- .../vector/Float16Vector64Tests.java | 16 +++++++++++++--- .../vector/Float16VectorMaxTests.java | 16 +++++++++++++--- .../templates/Unit-Miscellaneous.template | 14 ++++++++++++++ .../vector/templates/Unit-header.template | 6 ++++-- 9 files changed, 116 insertions(+), 19 deletions(-) diff --git a/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/Float16Vector.java b/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/Float16Vector.java index cf7eae5dd6a..ce3a67357f9 100644 --- a/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/Float16Vector.java +++ b/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/Float16Vector.java @@ -2861,6 +2861,17 @@ public abstract sealed class Float16Vector extends AbstractVector return a; } + // Returns the lane values boxed as Float16 elements. + @ForceInline + final Float16[] toFloat16Array() { + short[] bits = vec(); + Float16[] a = new Float16[bits.length]; + for (int i = 0; i < bits.length; i++) { + a[i] = Float16.shortBitsToFloat16(bits[i]); + } + return a; + } + /** {@inheritDoc} */ @ForceInline @@ -3734,8 +3745,9 @@ public abstract sealed class Float16Vector extends AbstractVector @ForceInline public final int hashCode() { - // now that toArray is strongly typed, we can define this - return Objects.hash(species(), Arrays.hashCode(toArray())); + // Hash the lanes as Float16 values; Float16.hashCode canonicalizes NaN + // so that all NaN representations contribute the same hash code. + return Objects.hash(species(), Arrays.hashCode(toFloat16Array())); } // ================================================ diff --git a/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/X-Vector.java.template b/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/X-Vector.java.template index 7c6fb3bcfb2..f11c6283685 100644 --- a/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/X-Vector.java.template +++ b/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/X-Vector.java.template @@ -3705,6 +3705,19 @@ public abstract sealed class $abstractvectortype$ extends AbstractVector<$Boxtyp return a; } +#if[FP16] + // Returns the lane values boxed as Float16 elements. + @ForceInline + final Float16[] toFloat16Array() { + short[] bits = vec(); + Float16[] a = new Float16[bits.length]; + for (int i = 0; i < bits.length; i++) { + a[i] = Float16.shortBitsToFloat16(bits[i]); + } + return a; + } + +#end[FP16] #if[int] /** * {@inheritDoc} @@ -5749,8 +5762,14 @@ public abstract sealed class $abstractvectortype$ extends AbstractVector<$Boxtyp @ForceInline public final int hashCode() { +#if[FP16] + // Hash the lanes as Float16 values; Float16.hashCode canonicalizes NaN + // so that all NaN representations contribute the same hash code. + return Objects.hash(species(), Arrays.hashCode(toFloat16Array())); +#else[FP16] // now that toArray is strongly typed, we can define this return Objects.hash(species(), Arrays.hashCode(toArray())); +#end[FP16] } // ================================================ diff --git a/test/jdk/jdk/incubator/vector/Float16Vector128Tests.java b/test/jdk/jdk/incubator/vector/Float16Vector128Tests.java index ad971e9b9bf..a33e83d14ea 100644 --- a/test/jdk/jdk/incubator/vector/Float16Vector128Tests.java +++ b/test/jdk/jdk/incubator/vector/Float16Vector128Tests.java @@ -1561,14 +1561,16 @@ public class Float16Vector128Tests extends AbstractVectorTest { } static short cornerCaseValue(int i) { - return switch(i % 8) { + return switch(i % 10) { case 0 -> float16ToRawShortBits(Float16.MAX_VALUE); case 1 -> float16ToRawShortBits(Float16.MIN_VALUE); case 2 -> float16ToRawShortBits(Float16.NEGATIVE_INFINITY); case 3 -> float16ToRawShortBits(Float16.POSITIVE_INFINITY); case 4 -> float16ToRawShortBits(Float16.NaN); case 5 -> float16ToRawShortBits(shortBitsToFloat16((short)0x7FFA)); - case 6 -> float16ToShortBits(Float16.valueOf(0.0f)); + case 6 -> float16ToRawShortBits(shortBitsToFloat16((short)0x7c01)); // signaling NaN + case 7 -> float16ToRawShortBits(shortBitsToFloat16((short)0x7e00)); // quiet NaN + case 8 -> float16ToShortBits(Float16.valueOf(0.0f)); default -> float16ToShortBits(Float16.valueOf(-0.0f)); }; } @@ -5423,11 +5425,19 @@ public class Float16Vector128Tests extends AbstractVectorTest { int hash = av.hashCode(); short subarr[] = Arrays.copyOfRange(a, i, i + SPECIES.length()); - int expectedHash = Objects.hash(SPECIES, Arrays.hashCode(subarr)); + int expectedHash = Objects.hash(SPECIES, Arrays.hashCode(toFloat16Array(subarr))); Assert.assertTrue(hash == expectedHash, "at index " + i + ", hash should be = " + expectedHash + ", but is = " + hash); } } + static Float16[] toFloat16Array(short[] bits) { + Float16[] a = new Float16[bits.length]; + for (int j = 0; j < bits.length; j++) { + a[j] = shortBitsToFloat16(bits[j]); + } + return a; + } + static long ADDReduceLong(short[] a, int idx) { short res = 0; diff --git a/test/jdk/jdk/incubator/vector/Float16Vector256Tests.java b/test/jdk/jdk/incubator/vector/Float16Vector256Tests.java index a946e0d8585..99b167d4024 100644 --- a/test/jdk/jdk/incubator/vector/Float16Vector256Tests.java +++ b/test/jdk/jdk/incubator/vector/Float16Vector256Tests.java @@ -1561,14 +1561,16 @@ public class Float16Vector256Tests extends AbstractVectorTest { } static short cornerCaseValue(int i) { - return switch(i % 8) { + return switch(i % 10) { case 0 -> float16ToRawShortBits(Float16.MAX_VALUE); case 1 -> float16ToRawShortBits(Float16.MIN_VALUE); case 2 -> float16ToRawShortBits(Float16.NEGATIVE_INFINITY); case 3 -> float16ToRawShortBits(Float16.POSITIVE_INFINITY); case 4 -> float16ToRawShortBits(Float16.NaN); case 5 -> float16ToRawShortBits(shortBitsToFloat16((short)0x7FFA)); - case 6 -> float16ToShortBits(Float16.valueOf(0.0f)); + case 6 -> float16ToRawShortBits(shortBitsToFloat16((short)0x7c01)); // signaling NaN + case 7 -> float16ToRawShortBits(shortBitsToFloat16((short)0x7e00)); // quiet NaN + case 8 -> float16ToShortBits(Float16.valueOf(0.0f)); default -> float16ToShortBits(Float16.valueOf(-0.0f)); }; } @@ -5423,11 +5425,19 @@ public class Float16Vector256Tests extends AbstractVectorTest { int hash = av.hashCode(); short subarr[] = Arrays.copyOfRange(a, i, i + SPECIES.length()); - int expectedHash = Objects.hash(SPECIES, Arrays.hashCode(subarr)); + int expectedHash = Objects.hash(SPECIES, Arrays.hashCode(toFloat16Array(subarr))); Assert.assertTrue(hash == expectedHash, "at index " + i + ", hash should be = " + expectedHash + ", but is = " + hash); } } + static Float16[] toFloat16Array(short[] bits) { + Float16[] a = new Float16[bits.length]; + for (int j = 0; j < bits.length; j++) { + a[j] = shortBitsToFloat16(bits[j]); + } + return a; + } + static long ADDReduceLong(short[] a, int idx) { short res = 0; diff --git a/test/jdk/jdk/incubator/vector/Float16Vector512Tests.java b/test/jdk/jdk/incubator/vector/Float16Vector512Tests.java index 0e70b4c85ec..1c391497015 100644 --- a/test/jdk/jdk/incubator/vector/Float16Vector512Tests.java +++ b/test/jdk/jdk/incubator/vector/Float16Vector512Tests.java @@ -1561,14 +1561,16 @@ public class Float16Vector512Tests extends AbstractVectorTest { } static short cornerCaseValue(int i) { - return switch(i % 8) { + return switch(i % 10) { case 0 -> float16ToRawShortBits(Float16.MAX_VALUE); case 1 -> float16ToRawShortBits(Float16.MIN_VALUE); case 2 -> float16ToRawShortBits(Float16.NEGATIVE_INFINITY); case 3 -> float16ToRawShortBits(Float16.POSITIVE_INFINITY); case 4 -> float16ToRawShortBits(Float16.NaN); case 5 -> float16ToRawShortBits(shortBitsToFloat16((short)0x7FFA)); - case 6 -> float16ToShortBits(Float16.valueOf(0.0f)); + case 6 -> float16ToRawShortBits(shortBitsToFloat16((short)0x7c01)); // signaling NaN + case 7 -> float16ToRawShortBits(shortBitsToFloat16((short)0x7e00)); // quiet NaN + case 8 -> float16ToShortBits(Float16.valueOf(0.0f)); default -> float16ToShortBits(Float16.valueOf(-0.0f)); }; } @@ -5423,11 +5425,19 @@ public class Float16Vector512Tests extends AbstractVectorTest { int hash = av.hashCode(); short subarr[] = Arrays.copyOfRange(a, i, i + SPECIES.length()); - int expectedHash = Objects.hash(SPECIES, Arrays.hashCode(subarr)); + int expectedHash = Objects.hash(SPECIES, Arrays.hashCode(toFloat16Array(subarr))); Assert.assertTrue(hash == expectedHash, "at index " + i + ", hash should be = " + expectedHash + ", but is = " + hash); } } + static Float16[] toFloat16Array(short[] bits) { + Float16[] a = new Float16[bits.length]; + for (int j = 0; j < bits.length; j++) { + a[j] = shortBitsToFloat16(bits[j]); + } + return a; + } + static long ADDReduceLong(short[] a, int idx) { short res = 0; diff --git a/test/jdk/jdk/incubator/vector/Float16Vector64Tests.java b/test/jdk/jdk/incubator/vector/Float16Vector64Tests.java index 94017042b7b..6ef651860ad 100644 --- a/test/jdk/jdk/incubator/vector/Float16Vector64Tests.java +++ b/test/jdk/jdk/incubator/vector/Float16Vector64Tests.java @@ -1561,14 +1561,16 @@ public class Float16Vector64Tests extends AbstractVectorTest { } static short cornerCaseValue(int i) { - return switch(i % 8) { + return switch(i % 10) { case 0 -> float16ToRawShortBits(Float16.MAX_VALUE); case 1 -> float16ToRawShortBits(Float16.MIN_VALUE); case 2 -> float16ToRawShortBits(Float16.NEGATIVE_INFINITY); case 3 -> float16ToRawShortBits(Float16.POSITIVE_INFINITY); case 4 -> float16ToRawShortBits(Float16.NaN); case 5 -> float16ToRawShortBits(shortBitsToFloat16((short)0x7FFA)); - case 6 -> float16ToShortBits(Float16.valueOf(0.0f)); + case 6 -> float16ToRawShortBits(shortBitsToFloat16((short)0x7c01)); // signaling NaN + case 7 -> float16ToRawShortBits(shortBitsToFloat16((short)0x7e00)); // quiet NaN + case 8 -> float16ToShortBits(Float16.valueOf(0.0f)); default -> float16ToShortBits(Float16.valueOf(-0.0f)); }; } @@ -5423,11 +5425,19 @@ public class Float16Vector64Tests extends AbstractVectorTest { int hash = av.hashCode(); short subarr[] = Arrays.copyOfRange(a, i, i + SPECIES.length()); - int expectedHash = Objects.hash(SPECIES, Arrays.hashCode(subarr)); + int expectedHash = Objects.hash(SPECIES, Arrays.hashCode(toFloat16Array(subarr))); Assert.assertTrue(hash == expectedHash, "at index " + i + ", hash should be = " + expectedHash + ", but is = " + hash); } } + static Float16[] toFloat16Array(short[] bits) { + Float16[] a = new Float16[bits.length]; + for (int j = 0; j < bits.length; j++) { + a[j] = shortBitsToFloat16(bits[j]); + } + return a; + } + static long ADDReduceLong(short[] a, int idx) { short res = 0; diff --git a/test/jdk/jdk/incubator/vector/Float16VectorMaxTests.java b/test/jdk/jdk/incubator/vector/Float16VectorMaxTests.java index d8649c838ee..61efa3de9a0 100644 --- a/test/jdk/jdk/incubator/vector/Float16VectorMaxTests.java +++ b/test/jdk/jdk/incubator/vector/Float16VectorMaxTests.java @@ -1567,14 +1567,16 @@ public class Float16VectorMaxTests extends AbstractVectorTest { } static short cornerCaseValue(int i) { - return switch(i % 8) { + return switch(i % 10) { case 0 -> float16ToRawShortBits(Float16.MAX_VALUE); case 1 -> float16ToRawShortBits(Float16.MIN_VALUE); case 2 -> float16ToRawShortBits(Float16.NEGATIVE_INFINITY); case 3 -> float16ToRawShortBits(Float16.POSITIVE_INFINITY); case 4 -> float16ToRawShortBits(Float16.NaN); case 5 -> float16ToRawShortBits(shortBitsToFloat16((short)0x7FFA)); - case 6 -> float16ToShortBits(Float16.valueOf(0.0f)); + case 6 -> float16ToRawShortBits(shortBitsToFloat16((short)0x7c01)); // signaling NaN + case 7 -> float16ToRawShortBits(shortBitsToFloat16((short)0x7e00)); // quiet NaN + case 8 -> float16ToShortBits(Float16.valueOf(0.0f)); default -> float16ToShortBits(Float16.valueOf(-0.0f)); }; } @@ -5429,11 +5431,19 @@ public class Float16VectorMaxTests extends AbstractVectorTest { int hash = av.hashCode(); short subarr[] = Arrays.copyOfRange(a, i, i + SPECIES.length()); - int expectedHash = Objects.hash(SPECIES, Arrays.hashCode(subarr)); + int expectedHash = Objects.hash(SPECIES, Arrays.hashCode(toFloat16Array(subarr))); Assert.assertTrue(hash == expectedHash, "at index " + i + ", hash should be = " + expectedHash + ", but is = " + hash); } } + static Float16[] toFloat16Array(short[] bits) { + Float16[] a = new Float16[bits.length]; + for (int j = 0; j < bits.length; j++) { + a[j] = shortBitsToFloat16(bits[j]); + } + return a; + } + static long ADDReduceLong(short[] a, int idx) { short res = 0; diff --git a/test/jdk/jdk/incubator/vector/templates/Unit-Miscellaneous.template b/test/jdk/jdk/incubator/vector/templates/Unit-Miscellaneous.template index 8606b9ba598..0ae9342539f 100644 --- a/test/jdk/jdk/incubator/vector/templates/Unit-Miscellaneous.template +++ b/test/jdk/jdk/incubator/vector/templates/Unit-Miscellaneous.template @@ -100,11 +100,25 @@ int hash = av.hashCode(); $type$ subarr[] = Arrays.copyOfRange(a, i, i + SPECIES.length()); +#if[FP16] + int expectedHash = Objects.hash(SPECIES, Arrays.hashCode(toFloat16Array(subarr))); +#else[FP16] int expectedHash = Objects.hash(SPECIES, Arrays.hashCode(subarr)); +#end[FP16] Assert.assertTrue(hash == expectedHash, "at index " + i + ", hash should be = " + expectedHash + ", but is = " + hash); } } +#if[FP16] + static Float16[] toFloat16Array(short[] bits) { + Float16[] a = new Float16[bits.length]; + for (int j = 0; j < bits.length; j++) { + a[j] = shortBitsToFloat16(bits[j]); + } + return a; + } + +#end[FP16] #if[byte] @Test(dataProvider = "$type$UnaryOpProvider") static void reinterpretAsBytes$vectorteststype$SmokeTest(IntFunction<$type$[]> fa) { diff --git a/test/jdk/jdk/incubator/vector/templates/Unit-header.template b/test/jdk/jdk/incubator/vector/templates/Unit-header.template index eac7edbbb3f..7047e27b797 100644 --- a/test/jdk/jdk/incubator/vector/templates/Unit-header.template +++ b/test/jdk/jdk/incubator/vector/templates/Unit-header.template @@ -2013,14 +2013,16 @@ relativeError)); static $type$ cornerCaseValue(int i) { #if[FP] #if[FP16] - return switch(i % 8) { + return switch(i % 10) { case 0 -> float16ToRawShortBits($Wideboxtype$.MAX_VALUE); case 1 -> float16ToRawShortBits($Wideboxtype$.MIN_VALUE); case 2 -> float16ToRawShortBits($Wideboxtype$.NEGATIVE_INFINITY); case 3 -> float16ToRawShortBits($Wideboxtype$.POSITIVE_INFINITY); case 4 -> float16ToRawShortBits($Wideboxtype$.NaN); case 5 -> float16ToRawShortBits(shortBitsToFloat16((short)0x7FFA)); - case 6 -> float16ToShortBits(Float16.valueOf(0.0f)); + case 6 -> float16ToRawShortBits(shortBitsToFloat16((short)0x7c01)); // signaling NaN + case 7 -> float16ToRawShortBits(shortBitsToFloat16((short)0x7e00)); // quiet NaN + case 8 -> float16ToShortBits(Float16.valueOf(0.0f)); default -> float16ToShortBits(Float16.valueOf(-0.0f)); }; #else[FP16] From 60e4b91f61da1a168135013874617406ded36871 Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Fri, 26 Jun 2026 06:17:54 +0000 Subject: [PATCH 003/305] 8386292: Shenandoah: Simplify and strengthen C1 barriers Co-authored-by: Martin Doerr Reviewed-by: rkennke, fyang, kdnilsen --- .../shenandoahBarrierSetAssembler_aarch64.cpp | 169 +++-------- .../shenandoahBarrierSetAssembler_aarch64.hpp | 11 +- .../shenandoahBarrierSetAssembler_ppc.cpp | 274 +++++------------- .../shenandoahBarrierSetAssembler_ppc.hpp | 26 +- .../shenandoahBarrierSetAssembler_riscv.cpp | 168 +++-------- .../shenandoahBarrierSetAssembler_riscv.hpp | 11 +- .../shenandoahBarrierSetAssembler_x86.cpp | 223 +++----------- .../shenandoahBarrierSetAssembler_x86.hpp | 11 +- .../shenandoah/c1/shenandoahBarrierSetC1.cpp | 212 ++++++-------- .../shenandoah/c1/shenandoahBarrierSetC1.hpp | 117 ++------ 10 files changed, 347 insertions(+), 875 deletions(-) diff --git a/src/hotspot/cpu/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.cpp index c590b6699c0..bc8af2354c8 100644 --- a/src/hotspot/cpu/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.cpp @@ -456,79 +456,38 @@ void ShenandoahBarrierSetAssembler::gen_write_ref_array_post_barrier(MacroAssemb #define __ ce->masm()-> -void ShenandoahBarrierSetAssembler::gen_pre_barrier_stub(LIR_Assembler* ce, ShenandoahPreBarrierStub* stub) { - ShenandoahBarrierSetC1* bs = (ShenandoahBarrierSetC1*)BarrierSet::barrier_set()->barrier_set_c1(); - // At this point we know that marking is in progress. - // If do_load() is true then we have to emit the - // load of the previous value; otherwise it has already - // been loaded into _pre_val. - +void ShenandoahBarrierSetAssembler::keepalive_barrier_c1_stub(LIR_Assembler* ce, ShenandoahKeepaliveBarrierStub* stub) { __ bind(*stub->entry()); - assert(stub->pre_val()->is_register(), "Precondition."); + ShenandoahBarrierSetC1* bs = (ShenandoahBarrierSetC1*)BarrierSet::barrier_set()->barrier_set_c1(); - Register pre_val_reg = stub->pre_val()->as_register(); + Register obj = stub->obj()->as_register(); if (stub->do_load()) { - ce->mem2reg(stub->addr(), stub->pre_val(), T_OBJECT, stub->patch_code(), stub->info(), false /*wide*/); + ce->mem2reg(stub->addr(), stub->obj(), T_OBJECT, lir_patch_none, nullptr, /* wide = */ false); } - __ cbz(pre_val_reg, *stub->continuation()); - ce->store_parameter(stub->pre_val()->as_register(), 0); - __ far_call(RuntimeAddress(bs->pre_barrier_c1_runtime_code_blob()->code_begin())); + __ cbz(obj, *stub->continuation()); + ce->store_parameter(obj, 0); + __ far_call(RuntimeAddress(bs->keepalive_barrier_stub())); __ b(*stub->continuation()); } -void ShenandoahBarrierSetAssembler::gen_load_reference_barrier_stub(LIR_Assembler* ce, ShenandoahLoadReferenceBarrierStub* stub) { - ShenandoahBarrierSetC1* bs = (ShenandoahBarrierSetC1*)BarrierSet::barrier_set()->barrier_set_c1(); +void ShenandoahBarrierSetAssembler::load_reference_barrier_c1_stub(LIR_Assembler* ce, ShenandoahLoadReferenceBarrierStub* stub) { __ bind(*stub->entry()); - DecoratorSet decorators = stub->decorators(); - bool is_strong = ShenandoahBarrierSet::is_strong_access(decorators); - bool is_weak = ShenandoahBarrierSet::is_weak_access(decorators); - bool is_phantom = ShenandoahBarrierSet::is_phantom_access(decorators); - bool is_native = ShenandoahBarrierSet::is_native_access(decorators); + ShenandoahBarrierSetC1* bs = (ShenandoahBarrierSetC1*)BarrierSet::barrier_set()->barrier_set_c1(); Register obj = stub->obj()->as_register(); - Register res = stub->result()->as_register(); Register addr = stub->addr()->as_pointer_register(); - Register tmp1 = stub->tmp1()->as_register(); - Register tmp2 = stub->tmp2()->as_register(); + Register slow_result = stub->slow_result()->as_register(); + assert_different_registers(obj, addr, slow_result); + assert(slow_result == r0, "C1 must know about our slow call result register"); - assert(res == r0, "result must arrive in r0"); - - if (res != obj) { - __ mov(res, obj); - } - - if (is_strong) { - // Check for object in cset. - if (AOTCodeCache::is_on_for_dump()) { - __ lea(tmp2, ExternalAddress(AOTRuntimeConstants::cset_base_address())); - __ ldr(tmp2, Address(tmp2)); - __ lea(tmp1, ExternalAddress(AOTRuntimeConstants::grain_shift_address())); - __ ldrw(tmp1, Address(tmp1)); - __ lsrv(tmp1, res, tmp1); - } else { - __ mov(tmp2, ShenandoahHeap::in_cset_fast_test_addr()); - __ lsr(tmp1, res, ShenandoahHeapRegion::region_size_bytes_shift_jint()); - } - __ ldrb(tmp2, Address(tmp2, tmp1)); - __ cbz(tmp2, *stub->continuation()); - } - - ce->store_parameter(res, 0); + ce->store_parameter(obj, 0); ce->store_parameter(addr, 1); - if (is_strong) { - if (is_native) { - __ far_call(RuntimeAddress(bs->load_reference_barrier_strong_native_rt_code_blob()->code_begin())); - } else { - __ far_call(RuntimeAddress(bs->load_reference_barrier_strong_rt_code_blob()->code_begin())); - } - } else if (is_weak) { - __ far_call(RuntimeAddress(bs->load_reference_barrier_weak_rt_code_blob()->code_begin())); - } else { - assert(is_phantom, "only remaining strength"); - __ far_call(RuntimeAddress(bs->load_reference_barrier_phantom_rt_code_blob()->code_begin())); + __ far_call(RuntimeAddress(bs->load_reference_barrier_stub(stub->decorators()))); + if (obj != slow_result) { + __ mov(obj, slow_result); } __ b(*stub->continuation()); @@ -538,89 +497,27 @@ void ShenandoahBarrierSetAssembler::gen_load_reference_barrier_stub(LIR_Assemble #define __ sasm-> -void ShenandoahBarrierSetAssembler::generate_c1_pre_barrier_runtime_stub(StubAssembler* sasm) { - __ prologue("shenandoah_pre_barrier", false); - - // arg0 : previous value of memory - - BarrierSet* bs = BarrierSet::barrier_set(); - - const Register pre_val = r0; - const Register thread = rthread; - const Register tmp = rscratch1; - - Address queue_index(thread, in_bytes(ShenandoahThreadLocalData::satb_mark_queue_index_offset())); - Address buffer(thread, in_bytes(ShenandoahThreadLocalData::satb_mark_queue_buffer_offset())); - - Label done; - Label runtime; - - // Is marking still active? - Address gc_state(thread, in_bytes(ShenandoahThreadLocalData::gc_state_offset())); - __ ldrb(tmp, gc_state); - __ tbz(tmp, ShenandoahHeap::MARKING_BITPOS, done); - - // Can we store original value in the thread's buffer? - __ ldr(tmp, queue_index); - __ cbz(tmp, runtime); - - __ sub(tmp, tmp, wordSize); - __ str(tmp, queue_index); - __ ldr(rscratch2, buffer); - __ add(tmp, tmp, rscratch2); - __ load_parameter(0, rscratch2); - __ str(rscratch2, Address(tmp, 0)); - __ b(done); - - __ bind(runtime); - __ push_call_clobbered_registers(); - __ load_parameter(0, pre_val); - __ call_VM_leaf(CAST_FROM_FN_PTR(address, ShenandoahRuntime::write_barrier_pre), pre_val); - __ pop_call_clobbered_registers(); - __ bind(done); - +void ShenandoahBarrierSetAssembler::keepalive_barrier_c1_runtime_stub(StubAssembler* sasm) { + __ prologue("shenandoah_keepalive_barrier", false); + const Register tmp_obj = r0; + const Register tmp1 = r1; + const Register tmp2 = r2; + __ push(RegSet::of(tmp1, tmp2, tmp_obj), sp); + __ load_parameter(0, tmp_obj); + satb_barrier(sasm, noreg, tmp_obj, rthread, tmp1, tmp2); + __ pop(RegSet::of(tmp1, tmp2, tmp_obj), sp); __ epilogue(); } -void ShenandoahBarrierSetAssembler::generate_c1_load_reference_barrier_runtime_stub(StubAssembler* sasm, DecoratorSet decorators) { +void ShenandoahBarrierSetAssembler::load_reference_barrier_c1_runtime_stub(StubAssembler* sasm, DecoratorSet decorators) { __ prologue("shenandoah_load_reference_barrier", false); - // arg0 : object to be resolved - - __ push_call_clobbered_registers(); - __ load_parameter(0, r0); - __ load_parameter(1, r1); - - bool is_strong = ShenandoahBarrierSet::is_strong_access(decorators); - bool is_weak = ShenandoahBarrierSet::is_weak_access(decorators); - bool is_phantom = ShenandoahBarrierSet::is_phantom_access(decorators); - bool is_native = ShenandoahBarrierSet::is_native_access(decorators); - if (is_strong) { - if (is_native) { - __ lea(lr, RuntimeAddress(CAST_FROM_FN_PTR(address, ShenandoahRuntime::load_reference_barrier_strong))); - } else { - if (UseCompressedOops) { - __ lea(lr, RuntimeAddress(CAST_FROM_FN_PTR(address, ShenandoahRuntime::load_reference_barrier_strong_narrow))); - } else { - __ lea(lr, RuntimeAddress(CAST_FROM_FN_PTR(address, ShenandoahRuntime::load_reference_barrier_strong))); - } - } - } else if (is_weak) { - assert(!is_native, "weak must not be called off-heap"); - if (UseCompressedOops) { - __ lea(lr, RuntimeAddress(CAST_FROM_FN_PTR(address, ShenandoahRuntime::load_reference_barrier_weak_narrow))); - } else { - __ lea(lr, RuntimeAddress(CAST_FROM_FN_PTR(address, ShenandoahRuntime::load_reference_barrier_weak))); - } - } else { - assert(is_phantom, "only remaining strength"); - assert(is_native, "phantom must only be called off-heap"); - __ lea(lr, RuntimeAddress(CAST_FROM_FN_PTR(address, ShenandoahRuntime::load_reference_barrier_phantom))); - } - __ blr(lr); - __ mov(rscratch1, r0); - __ pop_call_clobbered_registers(); - __ mov(r0, rscratch1); - + const Register tmp_obj = r0; + const Register tmp_addr = r1; + __ push(RegSet::of(tmp_addr), sp); + __ load_parameter(0, tmp_obj); + __ load_parameter(1, tmp_addr); + load_reference_barrier(sasm, tmp_obj, Address(tmp_addr, 0), decorators); + __ pop(RegSet::of(tmp_addr), sp); __ epilogue(); } diff --git a/src/hotspot/cpu/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.hpp b/src/hotspot/cpu/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.hpp index bab4fb3b37a..d25dd8871f9 100644 --- a/src/hotspot/cpu/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.hpp @@ -32,7 +32,7 @@ #include "gc/shenandoah/shenandoahBarrierSet.hpp" #ifdef COMPILER1 class LIR_Assembler; -class ShenandoahPreBarrierStub; +class ShenandoahKeepaliveBarrierStub; class ShenandoahLoadReferenceBarrierStub; class StubAssembler; #endif @@ -76,10 +76,11 @@ public: Register tmp, Label& slow_path); #ifdef COMPILER1 - void gen_pre_barrier_stub(LIR_Assembler* ce, ShenandoahPreBarrierStub* stub); - void gen_load_reference_barrier_stub(LIR_Assembler* ce, ShenandoahLoadReferenceBarrierStub* stub); - void generate_c1_pre_barrier_runtime_stub(StubAssembler* sasm); - void generate_c1_load_reference_barrier_runtime_stub(StubAssembler* sasm, DecoratorSet decorators); + void keepalive_barrier_c1_stub(LIR_Assembler* ce, ShenandoahKeepaliveBarrierStub* stub); + void keepalive_barrier_c1_runtime_stub(StubAssembler* sasm); + + void load_reference_barrier_c1_stub(LIR_Assembler* ce, ShenandoahLoadReferenceBarrierStub* stub); + void load_reference_barrier_c1_runtime_stub(StubAssembler* sasm, DecoratorSet decorators); #endif #ifdef COMPILER2 diff --git a/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.cpp b/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.cpp index 582327282fd..b17f0f924ae 100644 --- a/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.cpp +++ b/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.cpp @@ -56,10 +56,11 @@ void ShenandoahBarrierSetAssembler::satb_barrier(MacroAssembler *masm, Register base, RegisterOrConstant ind_or_offs, Register tmp1, Register tmp2, Register tmp3, - MacroAssembler::PreservationLevel preservation_level) { + MacroAssembler::PreservationLevel preservation_level, + int extra_stack_space) { if (ShenandoahSATBBarrier) { __ block_comment("satb_barrier (shenandoahgc) {"); - satb_barrier_impl(masm, 0, base, ind_or_offs, tmp1, tmp2, tmp3, preservation_level); + satb_barrier_impl(masm, 0, base, ind_or_offs, tmp1, tmp2, tmp3, preservation_level, extra_stack_space); __ block_comment("} satb_barrier (shenandoahgc)"); } } @@ -68,10 +69,11 @@ void ShenandoahBarrierSetAssembler::load_reference_barrier(MacroAssembler *masm, Register base, RegisterOrConstant ind_or_offs, Register dst, Register tmp1, Register tmp2, - MacroAssembler::PreservationLevel preservation_level) { + MacroAssembler::PreservationLevel preservation_level, + int extra_stack_space) { if (ShenandoahLoadRefBarrier) { __ block_comment("load_reference_barrier (shenandoahgc) {"); - load_reference_barrier_impl(masm, decorators, base, ind_or_offs, dst, tmp1, tmp2, preservation_level); + load_reference_barrier_impl(masm, decorators, base, ind_or_offs, dst, tmp1, tmp2, preservation_level, extra_stack_space); __ block_comment("} load_reference_barrier (shenandoahgc)"); } } @@ -205,7 +207,8 @@ void ShenandoahBarrierSetAssembler::satb_barrier_impl(MacroAssembler *masm, Deco Register base, RegisterOrConstant ind_or_offs, Register pre_val, Register tmp1, Register tmp2, - MacroAssembler::PreservationLevel preservation_level) { + MacroAssembler::PreservationLevel preservation_level, + int extra_stack_space) { assert(ShenandoahSATBBarrier, "Should be checked by caller"); assert_different_registers(tmp1, tmp2, pre_val, noreg); @@ -299,7 +302,7 @@ void ShenandoahBarrierSetAssembler::satb_barrier_impl(MacroAssembler *masm, Deco if (preserve_gp_registers) { nbytes_save = (preserve_fp_registers ? MacroAssembler::num_volatile_gp_regs + MacroAssembler::num_volatile_fp_regs - : MacroAssembler::num_volatile_gp_regs) * BytesPerWord; + : MacroAssembler::num_volatile_gp_regs) * BytesPerWord + extra_stack_space; __ save_volatile_gprs(R1_SP, -nbytes_save, preserve_fp_registers); } @@ -343,7 +346,8 @@ void ShenandoahBarrierSetAssembler::load_reference_barrier_impl( Register base, RegisterOrConstant ind_or_offs, Register dst, Register tmp1, Register tmp2, - MacroAssembler::PreservationLevel preservation_level) { + MacroAssembler::PreservationLevel preservation_level, + int extra_stack_space) { if (ind_or_offs.is_register()) { assert_different_registers(tmp1, tmp2, base, ind_or_offs.as_register(), dst, noreg); } else { @@ -430,7 +434,7 @@ void ShenandoahBarrierSetAssembler::load_reference_barrier_impl( if (preserve_gp_registers) { nbytes_save = (preserve_fp_registers ? MacroAssembler::num_volatile_gp_regs + MacroAssembler::num_volatile_fp_regs - : MacroAssembler::num_volatile_gp_regs) * BytesPerWord; + : MacroAssembler::num_volatile_gp_regs) * BytesPerWord + extra_stack_space; __ save_volatile_gprs(R1_SP, -nbytes_save, preserve_fp_registers); } @@ -693,243 +697,119 @@ void ShenandoahBarrierSetAssembler::gen_write_ref_array_post_barrier(MacroAssemb #define __ ce->masm()-> -void ShenandoahBarrierSetAssembler::gen_pre_barrier_stub(LIR_Assembler *ce, ShenandoahPreBarrierStub *stub) { - __ block_comment("gen_pre_barrier_stub (shenandoahgc) {"); - - ShenandoahBarrierSetC1 *bs = (ShenandoahBarrierSetC1*) BarrierSet::barrier_set()->barrier_set_c1(); +void ShenandoahBarrierSetAssembler::keepalive_barrier_c1_stub(LIR_Assembler* ce, ShenandoahKeepaliveBarrierStub* stub) { + __ block_comment("keepalive_barrier_stub (shenandoahgc) {"); __ bind(*stub->entry()); - // GC status has already been verified by 'ShenandoahBarrierSetC1::pre_barrier'. - // This stub is the slowpath of that function. + ShenandoahBarrierSetC1* bs = (ShenandoahBarrierSetC1*) BarrierSet::barrier_set()->barrier_set_c1(); - assert(stub->pre_val()->is_register(), "pre_val must be a register"); - Register pre_val = stub->pre_val()->as_register(); + Register obj = stub->obj()->as_register(); - // If 'do_load()' returns false, the to-be-stored value is already available in 'stub->pre_val()' - // ("preloaded mode" of the store barrier). + // If 'do_load()' returns false, the to-be-stored value is already available in 'obj' if (stub->do_load()) { - ce->mem2reg(stub->addr(), stub->pre_val(), T_OBJECT, stub->patch_code(), stub->info(), false); + ce->mem2reg(stub->addr(), stub->obj(), T_OBJECT, lir_patch_none, nullptr, false); } - // Fast path: Reference is null. - __ cmpdi(CR0, pre_val, 0); + // Fast path: reference is null. + __ cmpdi(CR0, obj, 0); __ bc_far_optimized(Assembler::bcondCRbiIs1_bhintNoHint, __ bi0(CR0, Assembler::equal), *stub->continuation()); // Argument passing via the stack. - __ std(pre_val, -8, R1_SP); + __ std(obj, -8, R1_SP); - __ load_const_optimized(R0, bs->pre_barrier_c1_runtime_code_blob()->code_begin()); + address blob_addr = bs->keepalive_barrier_stub(); + __ load_const_optimized(R0, blob_addr); __ call_stub(R0); __ b(*stub->continuation()); - __ block_comment("} gen_pre_barrier_stub (shenandoahgc)"); + __ block_comment("} keepalive_barrier_stub (shenandoahgc)"); } -void ShenandoahBarrierSetAssembler::gen_load_reference_barrier_stub(LIR_Assembler *ce, - ShenandoahLoadReferenceBarrierStub *stub) { - __ block_comment("gen_load_reference_barrier_stub (shenandoahgc) {"); +void ShenandoahBarrierSetAssembler::load_reference_barrier_c1_stub(LIR_Assembler* ce, ShenandoahLoadReferenceBarrierStub* stub) { + __ block_comment("load_reference_barrier_stub (shenandoahgc) {"); - ShenandoahBarrierSetC1 *bs = (ShenandoahBarrierSetC1*) BarrierSet::barrier_set()->barrier_set_c1(); __ bind(*stub->entry()); + ShenandoahBarrierSetC1* bs = (ShenandoahBarrierSetC1*) BarrierSet::barrier_set()->barrier_set_c1(); + Register obj = stub->obj()->as_register(); - Register res = stub->result()->as_register(); Register addr = stub->addr()->as_pointer_register(); - Register tmp1 = stub->tmp1()->as_register(); - Register tmp2 = stub->tmp2()->as_register(); - assert_different_registers(addr, res, tmp1, tmp2); + Register slow_result = stub->slow_result()->as_register(); + assert_different_registers(obj, addr, slow_result); + assert(slow_result == R3_RET, "C1 must know about our slow call result register"); - assert(R3_RET == res, "res must be r3"); + // Argument passing via the stack. + __ std(obj, -8, R1_SP); + __ std(addr, -16, R1_SP); - if (res != obj) { - __ mr(res, obj); + address blob_addr = bs->load_reference_barrier_stub(stub->decorators()); + __ load_const_optimized(R0, blob_addr); + __ call_stub(R0); + if (obj != slow_result) { + __ mr(obj, slow_result); } - DecoratorSet decorators = stub->decorators(); - - /* ==== Check whether region is in collection set ==== */ - // GC status (unstable) has already been verified by 'ShenandoahBarrierSetC1::load_reference_barrier_impl'. - // This stub is the slowpath of that function. - - bool is_strong = ShenandoahBarrierSet::is_strong_access(decorators); - bool is_weak = ShenandoahBarrierSet::is_weak_access(decorators); - bool is_phantom = ShenandoahBarrierSet::is_phantom_access(decorators); - bool is_native = ShenandoahBarrierSet::is_native_access(decorators); - - if (is_strong) { - // Check whether object is in collection set. - __ load_const_optimized(tmp2, ShenandoahHeap::in_cset_fast_test_addr(), tmp1); - __ srdi(tmp1, obj, ShenandoahHeapRegion::region_size_bytes_shift_jint()); - __ lbzx(tmp2, tmp1, tmp2); - - __ andi_(tmp2, tmp2, 1); - __ bc_far_optimized(Assembler::bcondCRbiIs1_bhintNoHint, __ bi0(CR0, Assembler::equal), *stub->continuation()); - } - - address blob_addr = nullptr; - - if (is_strong) { - if (is_native) { - blob_addr = bs->load_reference_barrier_strong_native_rt_code_blob()->code_begin(); - } else { - blob_addr = bs->load_reference_barrier_strong_rt_code_blob()->code_begin(); - } - } else if (is_weak) { - blob_addr = bs->load_reference_barrier_weak_rt_code_blob()->code_begin(); - } else { - assert(is_phantom, "only remaining strength"); - blob_addr = bs->load_reference_barrier_phantom_rt_code_blob()->code_begin(); - } - - assert(blob_addr != nullptr, "code blob cannot be found"); - - // Argument passing via the stack. 'obj' is passed implicitly (as asserted above). - __ std(addr, -8, R1_SP); - - __ load_const_optimized(tmp1, blob_addr, tmp2); - __ call_stub(tmp1); - - // 'res' is 'R3_RET'. The result is thus already in the correct register. - __ b(*stub->continuation()); - __ block_comment("} gen_load_reference_barrier_stub (shenandoahgc)"); + __ block_comment("} load_reference_barrier_stub (shenandoahgc)"); } #undef __ #define __ sasm-> -void ShenandoahBarrierSetAssembler::generate_c1_pre_barrier_runtime_stub(StubAssembler *sasm) { - __ block_comment("generate_c1_pre_barrier_runtime_stub (shenandoahgc) {"); +void ShenandoahBarrierSetAssembler::keepalive_barrier_c1_runtime_stub(StubAssembler* sasm) { + __ block_comment("keepalive_barrier_runtime_stub (shenandoahgc) {"); - Label runtime, skip_barrier; - BarrierSet *bs = BarrierSet::barrier_set(); + Register obj = R3_ARG1; + Register tmp1 = R11_scratch1; + Register tmp2 = R12_scratch2; - // Argument passing via the stack. - const int caller_stack_slots = 3; + // Save registers we are about to clobber + __ std(obj, -16, R1_SP); + __ std(tmp1, -24, R1_SP); + __ std(tmp2, -32, R1_SP); - Register R0_pre_val = R0; - __ ld(R0, -8, R1_SP); - Register R11_tmp1 = R11_scratch1; - __ std(R11_tmp1, -16, R1_SP); - Register R12_tmp2 = R12_scratch2; - __ std(R12_tmp2, -24, R1_SP); + // Pull the arguments from stack + __ ld(obj, -8, R1_SP); - /* ==== Check whether marking is active ==== */ - // Even though gc status was checked in 'ShenandoahBarrierSetAssembler::gen_pre_barrier_stub', - // another check is required as a safepoint might have been reached in the meantime (JDK-8140588). - __ lbz(R12_tmp2, in_bytes(ShenandoahThreadLocalData::gc_state_offset()), R16_thread); + satb_barrier(sasm, noreg, noreg, obj, tmp1, tmp2, MacroAssembler::PRESERVATION_FRAME_LR_GP_FP_REGS, 4 * BytesPerWord); - __ andi_(R12_tmp2, R12_tmp2, ShenandoahHeap::MARKING); - __ beq(CR0, skip_barrier); - - /* ==== Add previous value directly to thread-local SATB mark queue ==== */ - // Check queue's capacity. Jump to runtime if no free slot is available. - __ ld(R12_tmp2, in_bytes(ShenandoahThreadLocalData::satb_mark_queue_index_offset()), R16_thread); - __ cmpdi(CR0, R12_tmp2, 0); - __ beq(CR0, runtime); - - // Capacity suffices. Decrement the queue's size by one slot (size of one oop). - __ addi(R12_tmp2, R12_tmp2, -wordSize); - __ std(R12_tmp2, in_bytes(ShenandoahThreadLocalData::satb_mark_queue_index_offset()), R16_thread); - - // Enqueue the previous value and skip the runtime invocation. - __ ld(R11_tmp1, in_bytes(ShenandoahThreadLocalData::satb_mark_queue_buffer_offset()), R16_thread); - __ stdx(R0_pre_val, R11_tmp1, R12_tmp2); - __ b(skip_barrier); - - __ bind(runtime); - - /* ==== Invoke runtime to commit SATB mark queue to gc and allocate a new buffer ==== */ - // Save to-be-preserved registers. - const int nbytes_save = (MacroAssembler::num_volatile_regs + caller_stack_slots) * BytesPerWord; - __ save_volatile_gprs(R1_SP, -nbytes_save); - __ save_LR(R11_tmp1); - __ push_frame_reg_args(nbytes_save, R11_tmp1); - - // Invoke runtime. - __ call_VM_leaf(CAST_FROM_FN_PTR(address, ShenandoahRuntime::write_barrier_pre), R0_pre_val); - - // Restore to-be-preserved registers. - __ pop_frame(); - __ restore_LR(R11_tmp1); - __ restore_volatile_gprs(R1_SP, -nbytes_save); - - __ bind(skip_barrier); - - // Restore spilled registers. - __ ld(R11_tmp1, -16, R1_SP); - __ ld(R12_tmp2, -24, R1_SP); + // Restore registers + __ ld(tmp2, -32, R1_SP); + __ ld(tmp1, -24, R1_SP); + __ ld(obj, -16, R1_SP); __ blr(); - __ block_comment("} generate_c1_pre_barrier_runtime_stub (shenandoahgc)"); + __ block_comment("} keepalive_barrier_runtime_stub (shenandoahgc)"); } -void ShenandoahBarrierSetAssembler::generate_c1_load_reference_barrier_runtime_stub(StubAssembler *sasm, - DecoratorSet decorators) { - __ block_comment("generate_c1_load_reference_barrier_runtime_stub (shenandoahgc) {"); +void ShenandoahBarrierSetAssembler::load_reference_barrier_c1_runtime_stub(StubAssembler* sasm, DecoratorSet decorators) { + __ block_comment("load_reference_barrier_runtime_stub (shenandoahgc) {"); - // Argument passing via the stack. - const int caller_stack_slots = 1; + Register obj = R3_ARG1; + Register addr = R4_ARG2; + Register tmp1 = R11_scratch1; + Register tmp2 = R12_scratch2; - // Save to-be-preserved registers. - const int nbytes_save = (MacroAssembler::num_volatile_regs - 1 // 'R3_ARG1' is skipped - + caller_stack_slots) * BytesPerWord; - __ save_volatile_gprs(R1_SP, -nbytes_save, true, false); + // Save registers we are about to clobber + __ std(addr, -24, R1_SP); + __ std(tmp1, -32, R1_SP); + __ std(tmp2, -40, R1_SP); - // Load arguments from stack. - // No load required, as caller has already loaded obj into R3. - Register R3_obj = R3_ARG1; - Register R4_load_addr = R4_ARG2; - __ ld(R4_load_addr, -8, R1_SP); + // Pull the arguments from the stack + __ ld(obj, -8, R1_SP); + __ ld(addr, -16, R1_SP); - Register R11_tmp = R11_scratch1; + load_reference_barrier(sasm, decorators, addr, noreg, obj, tmp1, tmp2, + MacroAssembler::PRESERVATION_FRAME_LR_GP_FP_REGS, 5 * BytesPerWord); - /* ==== Invoke runtime ==== */ - bool is_strong = ShenandoahBarrierSet::is_strong_access(decorators); - bool is_weak = ShenandoahBarrierSet::is_weak_access(decorators); - bool is_phantom = ShenandoahBarrierSet::is_phantom_access(decorators); - bool is_native = ShenandoahBarrierSet::is_native_access(decorators); - - address jrt_address = nullptr; - - if (is_strong) { - if (is_native) { - jrt_address = CAST_FROM_FN_PTR(address, ShenandoahRuntime::load_reference_barrier_strong); - } else { - if (UseCompressedOops) { - jrt_address = CAST_FROM_FN_PTR(address, ShenandoahRuntime::load_reference_barrier_strong_narrow); - } else { - jrt_address = CAST_FROM_FN_PTR(address, ShenandoahRuntime::load_reference_barrier_strong); - } - } - } else if (is_weak) { - assert(!is_native, "weak load reference barrier must not be called off-heap"); - if (UseCompressedOops) { - jrt_address = CAST_FROM_FN_PTR(address, ShenandoahRuntime::load_reference_barrier_weak_narrow); - } else { - jrt_address = CAST_FROM_FN_PTR(address, ShenandoahRuntime::load_reference_barrier_weak); - } - } else { - assert(is_phantom, "reference type must be phantom"); - assert(is_native, "phantom load reference barrier must be called off-heap"); - jrt_address = CAST_FROM_FN_PTR(address, ShenandoahRuntime::load_reference_barrier_phantom); - } - assert(jrt_address != nullptr, "load reference barrier runtime routine cannot be found"); - - __ save_LR(R11_tmp); - __ push_frame_reg_args(nbytes_save, R11_tmp); - - // Invoke runtime. Arguments are already stored in the corresponding registers. - __ call_VM_leaf(jrt_address, R3_obj, R4_load_addr); - - // Restore to-be-preserved registers. - __ pop_frame(); - __ restore_LR(R11_tmp); - __ restore_volatile_gprs(R1_SP, -nbytes_save, true, false); // Skip 'R3_RET' register. + // Restore registers + __ ld(tmp2, -40, R1_SP); + __ ld(tmp1, -32, R1_SP); + __ ld(addr, -24, R1_SP); __ blr(); - __ block_comment("} generate_c1_load_reference_barrier_runtime_stub (shenandoahgc)"); + __ block_comment("} load_reference_barrier_runtime_stub (shenandoahgc)"); } #undef __ diff --git a/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.hpp b/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.hpp index bd1043c2d76..8d741e6104b 100644 --- a/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.hpp +++ b/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.hpp @@ -34,7 +34,7 @@ #ifdef COMPILER1 class LIR_Assembler; -class ShenandoahPreBarrierStub; +class ShenandoahKeepaliveBarrierStub; class ShenandoahLoadReferenceBarrierStub; class StubAssembler; @@ -56,7 +56,8 @@ private: Register base, RegisterOrConstant ind_or_offs, Register pre_val, Register tmp1, Register tmp2, - MacroAssembler::PreservationLevel preservation_level); + MacroAssembler::PreservationLevel preservation_level, + int extra_stack_space = 0); void card_barrier(MacroAssembler* masm, Register base, RegisterOrConstant ind_or_offs, @@ -66,7 +67,8 @@ private: Register base, RegisterOrConstant ind_or_offs, Register dst, Register tmp1, Register tmp2, - MacroAssembler::PreservationLevel preservation_level); + MacroAssembler::PreservationLevel preservation_level, + int extra_stack_space = 0); /* ==== Helper methods for barrier implementations ==== */ void gen_write_ref_array_post_barrier(MacroAssembler* masm, DecoratorSet decorators, @@ -78,28 +80,26 @@ public: /* ==== C1 stubs ==== */ #ifdef COMPILER1 + void keepalive_barrier_c1_stub(LIR_Assembler* ce, ShenandoahKeepaliveBarrierStub* stub); + void keepalive_barrier_c1_runtime_stub(StubAssembler* sasm); - void gen_pre_barrier_stub(LIR_Assembler* ce, ShenandoahPreBarrierStub* stub); - - void gen_load_reference_barrier_stub(LIR_Assembler* ce, ShenandoahLoadReferenceBarrierStub* stub); - - void generate_c1_pre_barrier_runtime_stub(StubAssembler* sasm); - - void generate_c1_load_reference_barrier_runtime_stub(StubAssembler* sasm, DecoratorSet decorators); - + void load_reference_barrier_c1_stub(LIR_Assembler* ce, ShenandoahLoadReferenceBarrierStub* stub); + void load_reference_barrier_c1_runtime_stub(StubAssembler* sasm, DecoratorSet decorators); #endif /* ==== Available barriers (facades of the actual implementations) ==== */ void satb_barrier(MacroAssembler* masm, Register base, RegisterOrConstant ind_or_offs, Register tmp1, Register tmp2, Register tmp3, - MacroAssembler::PreservationLevel preservation_level); + MacroAssembler::PreservationLevel preservation_level, + int extra_stack_space = 0); void load_reference_barrier(MacroAssembler* masm, DecoratorSet decorators, Register base, RegisterOrConstant ind_or_offs, Register dst, Register tmp1, Register tmp2, - MacroAssembler::PreservationLevel preservation_level); + MacroAssembler::PreservationLevel preservation_level, + int extra_stack_space = 0); /* ==== Access api ==== */ virtual void arraycopy_prologue(MacroAssembler* masm, DecoratorSet decorators, BasicType type, diff --git a/src/hotspot/cpu/riscv/gc/shenandoah/shenandoahBarrierSetAssembler_riscv.cpp b/src/hotspot/cpu/riscv/gc/shenandoah/shenandoahBarrierSetAssembler_riscv.cpp index eec5f9a5165..574c70c8ea4 100644 --- a/src/hotspot/cpu/riscv/gc/shenandoah/shenandoahBarrierSetAssembler_riscv.cpp +++ b/src/hotspot/cpu/riscv/gc/shenandoah/shenandoahBarrierSetAssembler_riscv.cpp @@ -471,74 +471,39 @@ void ShenandoahBarrierSetAssembler::gen_write_ref_array_post_barrier(MacroAssemb #define __ ce->masm()-> -void ShenandoahBarrierSetAssembler::gen_pre_barrier_stub(LIR_Assembler* ce, ShenandoahPreBarrierStub* stub) { - ShenandoahBarrierSetC1* bs = (ShenandoahBarrierSetC1*)BarrierSet::barrier_set()->barrier_set_c1(); - // At this point we know that marking is in progress. - // If do_load() is true then we have to emit the - // load of the previous value; otherwise it has already - // been loaded into _pre_val. +void ShenandoahBarrierSetAssembler::keepalive_barrier_c1_stub(LIR_Assembler* ce, ShenandoahKeepaliveBarrierStub* stub) { __ bind(*stub->entry()); - assert(stub->pre_val()->is_register(), "Precondition."); + ShenandoahBarrierSetC1* bs = (ShenandoahBarrierSetC1*)BarrierSet::barrier_set()->barrier_set_c1(); - Register pre_val_reg = stub->pre_val()->as_register(); + Register obj = stub->obj()->as_register(); if (stub->do_load()) { - ce->mem2reg(stub->addr(), stub->pre_val(), T_OBJECT, stub->patch_code(), stub->info(), false /* wide */); + ce->mem2reg(stub->addr(), stub->obj(), T_OBJECT, lir_patch_none, nullptr, false /* wide */); } - __ beqz(pre_val_reg, *stub->continuation(), /* is_far */ true); - ce->store_parameter(stub->pre_val()->as_register(), 0); - __ far_call(RuntimeAddress(bs->pre_barrier_c1_runtime_code_blob()->code_begin())); + __ beqz(obj, *stub->continuation(), /* is_far */ true); + + ce->store_parameter(obj, 0); + __ far_call(RuntimeAddress(bs->keepalive_barrier_stub())); __ j(*stub->continuation()); } -void ShenandoahBarrierSetAssembler::gen_load_reference_barrier_stub(LIR_Assembler* ce, - ShenandoahLoadReferenceBarrierStub* stub) { - ShenandoahBarrierSetC1* bs = (ShenandoahBarrierSetC1*)BarrierSet::barrier_set()->barrier_set_c1(); +void ShenandoahBarrierSetAssembler::load_reference_barrier_c1_stub(LIR_Assembler* ce, ShenandoahLoadReferenceBarrierStub* stub) { __ bind(*stub->entry()); - DecoratorSet decorators = stub->decorators(); - bool is_strong = ShenandoahBarrierSet::is_strong_access(decorators); - bool is_weak = ShenandoahBarrierSet::is_weak_access(decorators); - bool is_phantom = ShenandoahBarrierSet::is_phantom_access(decorators); - bool is_native = ShenandoahBarrierSet::is_native_access(decorators); + ShenandoahBarrierSetC1* bs = (ShenandoahBarrierSetC1*) BarrierSet::barrier_set()->barrier_set_c1(); Register obj = stub->obj()->as_register(); - Register res = stub->result()->as_register(); Register addr = stub->addr()->as_pointer_register(); - Register tmp1 = stub->tmp1()->as_register(); - Register tmp2 = stub->tmp2()->as_register(); + Register slow_result = stub->slow_result()->as_register(); + assert_different_registers(obj, addr, slow_result); + assert(slow_result == x10, "C1 must know about our slow call result register"); - assert(res == x10, "result must arrive in x10"); - assert_different_registers(tmp1, tmp2, t0); - - if (res != obj) { - __ mv(res, obj); - } - - if (is_strong) { - // Check for object in cset. - __ mv(tmp2, ShenandoahHeap::in_cset_fast_test_addr()); - __ srli(tmp1, res, ShenandoahHeapRegion::region_size_bytes_shift_jint()); - __ add(tmp2, tmp2, tmp1); - __ lbu(tmp2, Address(tmp2)); - __ beqz(tmp2, *stub->continuation(), true /* is_far */); - } - - ce->store_parameter(res, 0); + ce->store_parameter(obj, 0); ce->store_parameter(addr, 1); - - if (is_strong) { - if (is_native) { - __ far_call(RuntimeAddress(bs->load_reference_barrier_strong_native_rt_code_blob()->code_begin())); - } else { - __ far_call(RuntimeAddress(bs->load_reference_barrier_strong_rt_code_blob()->code_begin())); - } - } else if (is_weak) { - __ far_call(RuntimeAddress(bs->load_reference_barrier_weak_rt_code_blob()->code_begin())); - } else { - assert(is_phantom, "only remaining strength"); - __ far_call(RuntimeAddress(bs->load_reference_barrier_phantom_rt_code_blob()->code_begin())); + __ far_call(RuntimeAddress(bs->load_reference_barrier_stub(stub->decorators()))); + if (obj != slow_result) { + __ mv(obj, slow_result); } __ j(*stub->continuation()); @@ -548,92 +513,27 @@ void ShenandoahBarrierSetAssembler::gen_load_reference_barrier_stub(LIR_Assemble #define __ sasm-> -void ShenandoahBarrierSetAssembler::generate_c1_pre_barrier_runtime_stub(StubAssembler* sasm) { - __ prologue("shenandoah_pre_barrier", false); - - // arg0 : previous value of memory - - BarrierSet* bs = BarrierSet::barrier_set(); - - const Register pre_val = x10; - const Register thread = xthread; - const Register tmp = t0; - - Address queue_index(thread, in_bytes(ShenandoahThreadLocalData::satb_mark_queue_index_offset())); - Address buffer(thread, in_bytes(ShenandoahThreadLocalData::satb_mark_queue_buffer_offset())); - - Label done; - Label runtime; - - // Is marking still active? - Address gc_state(thread, in_bytes(ShenandoahThreadLocalData::gc_state_offset())); - __ lb(tmp, gc_state); - __ test_bit(tmp, tmp, ShenandoahHeap::MARKING_BITPOS); - __ beqz(tmp, done); - - // Can we store original value in the thread's buffer? - __ ld(tmp, queue_index); - __ beqz(tmp, runtime); - - __ subi(tmp, tmp, wordSize); - __ sd(tmp, queue_index); - __ ld(t1, buffer); - __ add(tmp, tmp, t1); - __ load_parameter(0, t1); - __ sd(t1, Address(tmp, 0)); - __ j(done); - - __ bind(runtime); - __ push_call_clobbered_registers(); - __ load_parameter(0, pre_val); - __ call_VM_leaf(CAST_FROM_FN_PTR(address, ShenandoahRuntime::write_barrier_pre), pre_val); - __ pop_call_clobbered_registers(); - __ bind(done); - +void ShenandoahBarrierSetAssembler::keepalive_barrier_c1_runtime_stub(StubAssembler* sasm) { + __ prologue("shenandoah_keepalive_barrier", false); + const Register tmp_obj = x10; + const Register tmp1 = x11; + const Register tmp2 = x12; + __ push_reg(RegSet::of(tmp1, tmp2, tmp_obj), sp); + __ load_parameter(0, tmp_obj); + satb_barrier(sasm, noreg, tmp_obj, xthread, tmp1, tmp2); + __ pop_reg(RegSet::of(tmp1, tmp2, tmp_obj), sp); __ epilogue(); } -void ShenandoahBarrierSetAssembler::generate_c1_load_reference_barrier_runtime_stub(StubAssembler* sasm, - DecoratorSet decorators) { +void ShenandoahBarrierSetAssembler::load_reference_barrier_c1_runtime_stub(StubAssembler* sasm, DecoratorSet decorators) { __ prologue("shenandoah_load_reference_barrier", false); - // arg0 : object to be resolved - - __ push_call_clobbered_registers(); - __ load_parameter(0, x10); - __ load_parameter(1, x11); - - bool is_strong = ShenandoahBarrierSet::is_strong_access(decorators); - bool is_weak = ShenandoahBarrierSet::is_weak_access(decorators); - bool is_phantom = ShenandoahBarrierSet::is_phantom_access(decorators); - bool is_native = ShenandoahBarrierSet::is_native_access(decorators); - address target = nullptr; - if (is_strong) { - if (is_native) { - target = CAST_FROM_FN_PTR(address, ShenandoahRuntime::load_reference_barrier_strong); - } else { - if (UseCompressedOops) { - target = CAST_FROM_FN_PTR(address, ShenandoahRuntime::load_reference_barrier_strong_narrow); - } else { - target = CAST_FROM_FN_PTR(address, ShenandoahRuntime::load_reference_barrier_strong); - } - } - } else if (is_weak) { - assert(!is_native, "weak must not be called off-heap"); - if (UseCompressedOops) { - target = CAST_FROM_FN_PTR(address, ShenandoahRuntime::load_reference_barrier_weak_narrow); - } else { - target = CAST_FROM_FN_PTR(address, ShenandoahRuntime::load_reference_barrier_weak); - } - } else { - assert(is_phantom, "only remaining strength"); - assert(is_native, "phantom must only be called off-heap"); - target = CAST_FROM_FN_PTR(address, ShenandoahRuntime::load_reference_barrier_phantom); - } - __ rt_call(target); - __ mv(t0, x10); - __ pop_call_clobbered_registers(); - __ mv(x10, t0); - + const Register tmp_obj = x10; + const Register tmp_addr = x11; + __ push_reg(RegSet::of(tmp_addr), sp); + __ load_parameter(0, tmp_obj); + __ load_parameter(1, tmp_addr); + load_reference_barrier(sasm, tmp_obj, Address(tmp_addr, 0), decorators); + __ pop_reg(RegSet::of(tmp_addr), sp); __ epilogue(); } diff --git a/src/hotspot/cpu/riscv/gc/shenandoah/shenandoahBarrierSetAssembler_riscv.hpp b/src/hotspot/cpu/riscv/gc/shenandoah/shenandoahBarrierSetAssembler_riscv.hpp index d41809f1ef7..ecb63e68a01 100644 --- a/src/hotspot/cpu/riscv/gc/shenandoah/shenandoahBarrierSetAssembler_riscv.hpp +++ b/src/hotspot/cpu/riscv/gc/shenandoah/shenandoahBarrierSetAssembler_riscv.hpp @@ -33,7 +33,7 @@ #ifdef COMPILER1 class LIR_Assembler; -class ShenandoahPreBarrierStub; +class ShenandoahKeepaliveBarrierStub; class ShenandoahLoadReferenceBarrierStub; class StubAssembler; #endif @@ -81,10 +81,11 @@ public: Register tmp, Label& slow_path); #ifdef COMPILER1 - void gen_pre_barrier_stub(LIR_Assembler* ce, ShenandoahPreBarrierStub* stub); - void gen_load_reference_barrier_stub(LIR_Assembler* ce, ShenandoahLoadReferenceBarrierStub* stub); - void generate_c1_pre_barrier_runtime_stub(StubAssembler* sasm); - void generate_c1_load_reference_barrier_runtime_stub(StubAssembler* sasm, DecoratorSet decorators); + void keepalive_barrier_c1_stub(LIR_Assembler* ce, ShenandoahKeepaliveBarrierStub* stub); + void keepalive_barrier_c1_runtime_stub(StubAssembler* sasm); + + void load_reference_barrier_c1_stub(LIR_Assembler* ce, ShenandoahLoadReferenceBarrierStub* stub); + void load_reference_barrier_c1_runtime_stub(StubAssembler* sasm, DecoratorSet decorators); #endif #ifdef COMPILER2 diff --git a/src/hotspot/cpu/x86/gc/shenandoah/shenandoahBarrierSetAssembler_x86.cpp b/src/hotspot/cpu/x86/gc/shenandoah/shenandoahBarrierSetAssembler_x86.cpp index fdf10e5b5e6..bdb98d4b4c0 100644 --- a/src/hotspot/cpu/x86/gc/shenandoah/shenandoahBarrierSetAssembler_x86.cpp +++ b/src/hotspot/cpu/x86/gc/shenandoah/shenandoahBarrierSetAssembler_x86.cpp @@ -154,11 +154,7 @@ void ShenandoahBarrierSetAssembler::satb_barrier(MacroAssembler* masm, Label runtime; assert(pre_val != noreg, "check this code"); - - if (obj != noreg) { - assert_different_registers(obj, pre_val, tmp); - assert(pre_val != rax, "check this code"); - } + assert_different_registers(obj, pre_val, tmp); Address index(thread, in_bytes(ShenandoahThreadLocalData::satb_mark_queue_index_offset())); Address buffer(thread, in_bytes(ShenandoahThreadLocalData::satb_mark_queue_buffer_offset())); @@ -560,99 +556,42 @@ void ShenandoahBarrierSetAssembler::gen_write_ref_array_post_barrier(MacroAssemb #define __ ce->masm()-> -void ShenandoahBarrierSetAssembler::gen_pre_barrier_stub(LIR_Assembler* ce, ShenandoahPreBarrierStub* stub) { - ShenandoahBarrierSetC1* bs = (ShenandoahBarrierSetC1*)BarrierSet::barrier_set()->barrier_set_c1(); - // At this point we know that marking is in progress. - // If do_load() is true then we have to emit the - // load of the previous value; otherwise it has already - // been loaded into _pre_val. - - __ bind(*stub->entry()); - assert(stub->pre_val()->is_register(), "Precondition."); - - Register pre_val_reg = stub->pre_val()->as_register(); - - if (stub->do_load()) { - ce->mem2reg(stub->addr(), stub->pre_val(), T_OBJECT, stub->patch_code(), stub->info(), false /*wide*/); - } - - __ cmpptr(pre_val_reg, NULL_WORD); - __ jcc(Assembler::equal, *stub->continuation()); - ce->store_parameter(stub->pre_val()->as_register(), 0); - __ call(RuntimeAddress(bs->pre_barrier_c1_runtime_code_blob()->code_begin())); - __ jmp(*stub->continuation()); - -} - -void ShenandoahBarrierSetAssembler::gen_load_reference_barrier_stub(LIR_Assembler* ce, ShenandoahLoadReferenceBarrierStub* stub) { - ShenandoahBarrierSetC1* bs = (ShenandoahBarrierSetC1*)BarrierSet::barrier_set()->barrier_set_c1(); +void ShenandoahBarrierSetAssembler::keepalive_barrier_c1_stub(LIR_Assembler* ce, ShenandoahKeepaliveBarrierStub* stub) { __ bind(*stub->entry()); - DecoratorSet decorators = stub->decorators(); - bool is_strong = ShenandoahBarrierSet::is_strong_access(decorators); - bool is_weak = ShenandoahBarrierSet::is_weak_access(decorators); - bool is_phantom = ShenandoahBarrierSet::is_phantom_access(decorators); - bool is_native = ShenandoahBarrierSet::is_native_access(decorators); + ShenandoahBarrierSetC1* bs = (ShenandoahBarrierSetC1*)BarrierSet::barrier_set()->barrier_set_c1(); + + Register obj = stub->obj()->as_register(); + + if (stub->do_load()) { + ce->mem2reg(stub->addr(), stub->obj(), T_OBJECT, lir_patch_none, nullptr, /* wide = */ false); + } + __ cmpptr(obj, NULL_WORD); + __ jcc(Assembler::equal, *stub->continuation()); + + ce->store_parameter(obj, 0); + __ call(RuntimeAddress(bs->keepalive_barrier_stub())); + __ jmp(*stub->continuation()); +} + +void ShenandoahBarrierSetAssembler::load_reference_barrier_c1_stub(LIR_Assembler* ce, ShenandoahLoadReferenceBarrierStub* stub) { + __ bind(*stub->entry()); + + ShenandoahBarrierSetC1* bs = (ShenandoahBarrierSetC1*)BarrierSet::barrier_set()->barrier_set_c1(); Register obj = stub->obj()->as_register(); - Register res = stub->result()->as_register(); Register addr = stub->addr()->as_pointer_register(); - Register tmp1 = stub->tmp1()->as_register(); - Register tmp2 = stub->tmp2()->as_register(); - assert_different_registers(obj, res, addr, tmp1, tmp2); + Register slow_result = stub->slow_result()->as_register(); + assert_different_registers(obj, addr, slow_result); + assert(slow_result == rax, "C1 must know about our slow call result register"); - Label slow_path; - - assert(res == rax, "result must arrive in rax"); - - if (res != obj) { - __ mov(res, obj); - } - - if (is_strong) { - // Check for object being in the collection set. - __ mov(tmp1, res); - if (AOTCodeCache::is_on_for_dump()) { - __ push(rcx); - __ lea(rcx, ExternalAddress(AOTRuntimeConstants::grain_shift_address())); - __ movl(rcx, Address(rcx)); - if (tmp1 != rcx) { - __ mov(tmp1, res); - __ shrptr(tmp1); - __ pop(rcx); - } else { - assert_different_registers(tmp2, rcx); - __ mov(tmp2, res); - __ shrptr(tmp2); - __ pop(rcx); - __ movptr(tmp1, tmp2); - } - __ lea(tmp2, ExternalAddress(AOTRuntimeConstants::cset_base_address())); - __ movptr(tmp2, Address(tmp2)); - } else { - __ shrptr(tmp1, ShenandoahHeapRegion::region_size_bytes_shift_jint()); - __ movptr(tmp2, (intptr_t) ShenandoahHeap::in_cset_fast_test_addr()); - } - __ movbool(tmp2, Address(tmp2, tmp1, Address::times_1)); - __ testbool(tmp2); - __ jcc(Assembler::zero, *stub->continuation()); - } - - __ bind(slow_path); - ce->store_parameter(res, 0); + ce->store_parameter(obj, 0); ce->store_parameter(addr, 1); - if (is_strong) { - if (is_native) { - __ call(RuntimeAddress(bs->load_reference_barrier_strong_native_rt_code_blob()->code_begin())); - } else { - __ call(RuntimeAddress(bs->load_reference_barrier_strong_rt_code_blob()->code_begin())); - } - } else if (is_weak) { - __ call(RuntimeAddress(bs->load_reference_barrier_weak_rt_code_blob()->code_begin())); - } else { - assert(is_phantom, "only remaining strength"); - __ call(RuntimeAddress(bs->load_reference_barrier_phantom_rt_code_blob()->code_begin())); + __ call(RuntimeAddress(bs->load_reference_barrier_stub(stub->decorators()))); + if (obj != slow_result) { + __ mov(obj, slow_result); } + __ jmp(*stub->continuation()); } @@ -660,98 +599,28 @@ void ShenandoahBarrierSetAssembler::gen_load_reference_barrier_stub(LIR_Assemble #define __ sasm-> -void ShenandoahBarrierSetAssembler::generate_c1_pre_barrier_runtime_stub(StubAssembler* sasm) { - __ prologue("shenandoah_pre_barrier", false); - // arg0 : previous value of memory - - __ push(rax); - __ push(rdx); - - const Register pre_val = rax; - const Register thread = r15_thread; +void ShenandoahBarrierSetAssembler::keepalive_barrier_c1_runtime_stub(StubAssembler* sasm) { + __ prologue("shenandoah_keepalive_barrier", false); + const Register tmp_obj = rax; const Register tmp = rdx; - - Address queue_index(thread, in_bytes(ShenandoahThreadLocalData::satb_mark_queue_index_offset())); - Address buffer(thread, in_bytes(ShenandoahThreadLocalData::satb_mark_queue_buffer_offset())); - - Label done; - Label runtime; - - // Is SATB still active? - Address gc_state(thread, in_bytes(ShenandoahThreadLocalData::gc_state_offset())); - __ testb(gc_state, ShenandoahHeap::MARKING); - __ jcc(Assembler::zero, done); - - // Can we store original value in the thread's buffer? - - __ movptr(tmp, queue_index); - __ testptr(tmp, tmp); - __ jcc(Assembler::zero, runtime); - __ subptr(tmp, wordSize); - __ movptr(queue_index, tmp); - __ addptr(tmp, buffer); - - // prev_val (rax) - __ load_parameter(0, pre_val); - __ movptr(Address(tmp, 0), pre_val); - __ jmp(done); - - __ bind(runtime); - - __ save_live_registers_no_oop_map(true); - - // load the pre-value - __ load_parameter(0, rcx); - __ call_VM_leaf(CAST_FROM_FN_PTR(address, ShenandoahRuntime::write_barrier_pre), rcx); - - __ restore_live_registers(true); - - __ bind(done); - - __ pop(rdx); - __ pop(rax); - + __ push(tmp); + __ push(tmp_obj); + __ load_parameter(0, tmp_obj); + satb_barrier(sasm, noreg, tmp_obj, tmp); + __ pop(tmp_obj); + __ pop(tmp); __ epilogue(); } -void ShenandoahBarrierSetAssembler::generate_c1_load_reference_barrier_runtime_stub(StubAssembler* sasm, DecoratorSet decorators) { +void ShenandoahBarrierSetAssembler::load_reference_barrier_c1_runtime_stub(StubAssembler* sasm, DecoratorSet decorators) { __ prologue("shenandoah_load_reference_barrier", false); - // arg0 : object to be resolved - - __ save_live_registers_no_oop_map(true); - - bool is_strong = ShenandoahBarrierSet::is_strong_access(decorators); - bool is_weak = ShenandoahBarrierSet::is_weak_access(decorators); - bool is_phantom = ShenandoahBarrierSet::is_phantom_access(decorators); - bool is_native = ShenandoahBarrierSet::is_native_access(decorators); - - __ load_parameter(0, c_rarg0); - __ load_parameter(1, c_rarg1); - if (is_strong) { - if (is_native) { - __ call_VM_leaf(CAST_FROM_FN_PTR(address, ShenandoahRuntime::load_reference_barrier_strong), c_rarg0, c_rarg1); - } else { - if (UseCompressedOops) { - __ call_VM_leaf(CAST_FROM_FN_PTR(address, ShenandoahRuntime::load_reference_barrier_strong_narrow), c_rarg0, c_rarg1); - } else { - __ call_VM_leaf(CAST_FROM_FN_PTR(address, ShenandoahRuntime::load_reference_barrier_strong), c_rarg0, c_rarg1); - } - } - } else if (is_weak) { - assert(!is_native, "weak must not be called off-heap"); - if (UseCompressedOops) { - __ call_VM_leaf(CAST_FROM_FN_PTR(address, ShenandoahRuntime::load_reference_barrier_weak_narrow), c_rarg0, c_rarg1); - } else { - __ call_VM_leaf(CAST_FROM_FN_PTR(address, ShenandoahRuntime::load_reference_barrier_weak), c_rarg0, c_rarg1); - } - } else { - assert(is_phantom, "only remaining strength"); - assert(is_native, "phantom must only be called off-heap"); - __ call_VM_leaf(CAST_FROM_FN_PTR(address, ShenandoahRuntime::load_reference_barrier_phantom), c_rarg0, c_rarg1); - } - - __ restore_live_registers_except_rax(true); - + const Register tmp_obj = rax; + const Register tmp_addr = rdx; + __ push(tmp_addr); + __ load_parameter(0, tmp_obj); + __ load_parameter(1, tmp_addr); + load_reference_barrier(sasm, tmp_obj, Address(tmp_addr, 0), decorators); + __ pop(tmp_addr); __ epilogue(); } diff --git a/src/hotspot/cpu/x86/gc/shenandoah/shenandoahBarrierSetAssembler_x86.hpp b/src/hotspot/cpu/x86/gc/shenandoah/shenandoahBarrierSetAssembler_x86.hpp index f608760ce42..7f417d3c262 100644 --- a/src/hotspot/cpu/x86/gc/shenandoah/shenandoahBarrierSetAssembler_x86.hpp +++ b/src/hotspot/cpu/x86/gc/shenandoah/shenandoahBarrierSetAssembler_x86.hpp @@ -33,7 +33,7 @@ #ifdef COMPILER1 class LIR_Assembler; -class ShenandoahPreBarrierStub; +class ShenandoahKeepaliveBarrierStub; class ShenandoahLoadReferenceBarrierStub; class StubAssembler; #endif @@ -73,10 +73,11 @@ public: virtual void try_peek_weak_handle_in_nmethod(MacroAssembler* masm, Register weak_handle, Register obj, Label& slowpath); #ifdef COMPILER1 - void gen_pre_barrier_stub(LIR_Assembler* ce, ShenandoahPreBarrierStub* stub); - void gen_load_reference_barrier_stub(LIR_Assembler* ce, ShenandoahLoadReferenceBarrierStub* stub); - void generate_c1_pre_barrier_runtime_stub(StubAssembler* sasm); - void generate_c1_load_reference_barrier_runtime_stub(StubAssembler* sasm, DecoratorSet decorators); + void keepalive_barrier_c1_stub(LIR_Assembler* ce, ShenandoahKeepaliveBarrierStub* stub); + void keepalive_barrier_c1_runtime_stub(StubAssembler* sasm); + + void load_reference_barrier_c1_stub(LIR_Assembler* ce, ShenandoahLoadReferenceBarrierStub* stub); + void load_reference_barrier_c1_runtime_stub(StubAssembler* sasm, DecoratorSet decorators); #endif #ifdef COMPILER2 diff --git a/src/hotspot/share/gc/shenandoah/c1/shenandoahBarrierSetC1.cpp b/src/hotspot/share/gc/shenandoah/c1/shenandoahBarrierSetC1.cpp index 637ed6e6407..de0b838fe45 100644 --- a/src/hotspot/share/gc/shenandoah/c1/shenandoahBarrierSetC1.cpp +++ b/src/hotspot/share/gc/shenandoah/c1/shenandoahBarrierSetC1.cpp @@ -31,8 +31,6 @@ #include "gc/shenandoah/shenandoahBarrierSet.hpp" #include "gc/shenandoah/shenandoahBarrierSetAssembler.hpp" #include "gc/shenandoah/shenandoahHeap.inline.hpp" -#include "gc/shenandoah/shenandoahHeapRegion.hpp" -#include "gc/shenandoah/shenandoahRuntime.hpp" #include "gc/shenandoah/shenandoahThreadLocalData.hpp" #ifdef ASSERT @@ -41,43 +39,61 @@ #define __ gen->lir()-> #endif -void ShenandoahPreBarrierStub::emit_code(LIR_Assembler* ce) { +void ShenandoahKeepaliveBarrierStub::emit_code(LIR_Assembler* ce) { ShenandoahBarrierSetAssembler* bs = (ShenandoahBarrierSetAssembler*)BarrierSet::barrier_set()->barrier_set_assembler(); - bs->gen_pre_barrier_stub(ce, this); + bs->keepalive_barrier_c1_stub(ce, this); } void ShenandoahLoadReferenceBarrierStub::emit_code(LIR_Assembler* ce) { ShenandoahBarrierSetAssembler* bs = (ShenandoahBarrierSetAssembler*)BarrierSet::barrier_set()->barrier_set_assembler(); - bs->gen_load_reference_barrier_stub(ce, this); + bs->load_reference_barrier_c1_stub(ce, this); } ShenandoahBarrierSetC1::ShenandoahBarrierSetC1() : - _pre_barrier_c1_runtime_code_blob(nullptr), + _keepalive_barrier_c1_runtime_code_blob(nullptr), _load_reference_barrier_strong_rt_code_blob(nullptr), _load_reference_barrier_strong_native_rt_code_blob(nullptr), _load_reference_barrier_weak_rt_code_blob(nullptr), _load_reference_barrier_phantom_rt_code_blob(nullptr) {} -void ShenandoahBarrierSetC1::pre_barrier(LIRGenerator* gen, CodeEmitInfo* info, DecoratorSet decorators, LIR_Opr addr_opr, LIR_Opr pre_val) { - // First we test whether marking is in progress. +address ShenandoahBarrierSetC1::keepalive_barrier_stub() { + assert(_keepalive_barrier_c1_runtime_code_blob != nullptr, "Must be available"); + return _keepalive_barrier_c1_runtime_code_blob->code_begin(); +} - bool patch = (decorators & C1_NEEDS_PATCHING) != 0; - bool do_load = pre_val == LIR_OprFact::illegalOpr; +address ShenandoahBarrierSetC1::load_reference_barrier_stub(DecoratorSet decorators) { + bool is_strong = ShenandoahBarrierSet::is_strong_access(decorators); + bool is_weak = ShenandoahBarrierSet::is_weak_access(decorators); + bool is_phantom = ShenandoahBarrierSet::is_phantom_access(decorators); + bool is_native = ShenandoahBarrierSet::is_native_access(decorators); + if (is_strong) { + if (is_native) { + assert(_load_reference_barrier_strong_native_rt_code_blob != nullptr, "Must be available"); + return _load_reference_barrier_strong_native_rt_code_blob->code_begin(); + } else { + assert(_load_reference_barrier_strong_rt_code_blob != nullptr, "Must be available"); + return _load_reference_barrier_strong_rt_code_blob->code_begin(); + } + } else if (is_weak) { + assert(_load_reference_barrier_weak_rt_code_blob != nullptr, "Must be available"); + return _load_reference_barrier_weak_rt_code_blob->code_begin(); + } else if (is_phantom) { + assert(_load_reference_barrier_phantom_rt_code_blob != nullptr, "Must be available"); + return _load_reference_barrier_phantom_rt_code_blob->code_begin(); + } + ShouldNotReachHere(); + return nullptr; +} + +void ShenandoahBarrierSetC1::enter_if_gc_state(LIRGenerator* gen, int flags, CodeStub* slow_stub) { LIR_Opr thrd = gen->getThreadPointer(); - LIR_Address* gc_state_addr = - new LIR_Address(thrd, - in_bytes(ShenandoahThreadLocalData::gc_state_offset()), - T_BYTE); - // Read the gc_state flag. LIR_Opr flag_val = gen->new_register(T_INT); - __ load(gc_state_addr, flag_val); - - // Create a mask to test if the marking bit is set. - LIR_Opr mask = LIR_OprFact::intConst(ShenandoahHeap::MARKING); LIR_Opr mask_reg = gen->new_register(T_INT); - __ move(mask, mask_reg); + LIR_Address* gc_state_addr = new LIR_Address(thrd, in_bytes(ShenandoahThreadLocalData::gc_state_offset()), T_BYTE); + __ load(gc_state_addr, flag_val); + __ move(LIR_OprFact::intConst(flags), mask_reg); if (two_operand_lir_form) { __ logical_and(flag_val, mask_reg, flag_val); } else { @@ -86,91 +102,54 @@ void ShenandoahBarrierSetC1::pre_barrier(LIRGenerator* gen, CodeEmitInfo* info, flag_val = masked_flag; } __ cmp(lir_cond_notEqual, flag_val, LIR_OprFact::intConst(0)); + __ branch(lir_cond_notEqual, slow_stub); + __ branch_destination(slow_stub->continuation()); +} - LIR_PatchCode pre_val_patch_code = lir_patch_none; +void ShenandoahBarrierSetC1::keepalive_barrier(LIRGenerator* gen, LIR_Opr obj, LIR_Opr addr, DecoratorSet decorators) { + CodeStub* slow_stub; + if (obj == LIR_OprFact::illegalOpr) { + // Caller wants us to do the load. + obj = gen->new_register(T_OBJECT); - CodeStub* slow; - - if (do_load) { - assert(pre_val == LIR_OprFact::illegalOpr, "sanity"); - assert(addr_opr != LIR_OprFact::illegalOpr, "sanity"); - - if (patch) - pre_val_patch_code = lir_patch_normal; - - pre_val = gen->new_register(T_OBJECT); - - if (!addr_opr->is_address()) { - assert(addr_opr->is_register(), "must be"); - addr_opr = LIR_OprFact::address(new LIR_Address(addr_opr, T_OBJECT)); + assert(addr != LIR_OprFact::illegalOpr, "sanity"); + if (!addr->is_address()) { + assert(addr->is_register(), "must be"); + addr = LIR_OprFact::address(new LIR_Address(addr, T_OBJECT)); } - slow = new ShenandoahPreBarrierStub(addr_opr, pre_val, pre_val_patch_code, info ? new CodeEmitInfo(info) : nullptr); - } else { - assert(addr_opr == LIR_OprFact::illegalOpr, "sanity"); - assert(pre_val->is_register(), "must be"); - assert(pre_val->type() == T_OBJECT, "must be an object"); - slow = new ShenandoahPreBarrierStub(pre_val); + slow_stub = new ShenandoahKeepaliveBarrierStub(obj, addr); + } else { + // Caller gave us the obj to work with. + assert(addr == LIR_OprFact::illegalOpr, "sanity"); + assert(obj->is_register(), "must be"); + assert(obj->type() == T_OBJECT, "must be an object"); + + slow_stub = new ShenandoahKeepaliveBarrierStub(obj); } - __ branch(lir_cond_notEqual, slow); - __ branch_destination(slow->continuation()); + enter_if_gc_state(gen, ShenandoahHeap::MARKING, slow_stub); } -LIR_Opr ShenandoahBarrierSetC1::load_reference_barrier(LIRGenerator* gen, LIR_Opr obj, LIR_Opr addr, DecoratorSet decorators) { - if (ShenandoahLoadRefBarrier) { - return load_reference_barrier_impl(gen, obj, addr, decorators); - } else { - return obj; - } -} - -LIR_Opr ShenandoahBarrierSetC1::load_reference_barrier_impl(LIRGenerator* gen, LIR_Opr obj, LIR_Opr addr, DecoratorSet decorators) { +void ShenandoahBarrierSetC1::load_reference_barrier(LIRGenerator* gen, LIR_Opr obj, LIR_Opr addr, DecoratorSet decorators) { assert(ShenandoahLoadRefBarrier, "Should be enabled"); obj = ensure_in_register(gen, obj, T_OBJECT); - assert(obj->is_register(), "must be a register at this point"); addr = ensure_in_register(gen, addr, T_ADDRESS); + assert(obj->is_register(), "must be a register at this point"); assert(addr->is_register(), "must be a register at this point"); - LIR_Opr result = gen->result_register_for(obj->value_type()); - LIR_Opr tmp1 = gen->new_register(T_ADDRESS); - LIR_Opr tmp2 = gen->new_register(T_ADDRESS); - LIR_Opr thrd = gen->getThreadPointer(); - LIR_Address* active_flag_addr = - new LIR_Address(thrd, - in_bytes(ShenandoahThreadLocalData::gc_state_offset()), - T_BYTE); - // Read and check the gc-state-flag. - LIR_Opr flag_val = gen->new_register(T_INT); - __ load(active_flag_addr, flag_val); + // Barrier slowpaths return value in this register. Declare it in the stub + // as clobbered. The obj would remain as result for both fast- and slow-paths. + LIR_Opr slow_result = gen->result_register_for(obj->value_type()); + + CodeStub* slow_stub = new ShenandoahLoadReferenceBarrierStub(obj, addr, slow_result, decorators); + int flags = ShenandoahHeap::HAS_FORWARDED; if (!ShenandoahBarrierSet::is_strong_access(decorators)) { flags |= ShenandoahHeap::WEAK_ROOTS; } - LIR_Opr mask = LIR_OprFact::intConst(flags); - LIR_Opr mask_reg = gen->new_register(T_INT); - __ move(mask, mask_reg); - - if (two_operand_lir_form) { - __ logical_and(flag_val, mask_reg, flag_val); - } else { - LIR_Opr masked_flag = gen->new_register(T_INT); - __ logical_and(flag_val, mask_reg, masked_flag); - flag_val = masked_flag; - } - __ cmp(lir_cond_notEqual, flag_val, LIR_OprFact::intConst(0)); - - CodeStub* slow = new ShenandoahLoadReferenceBarrierStub(obj, addr, result, tmp1, tmp2, decorators); - __ branch(lir_cond_notEqual, slow); - - // No barrier is needed, move obj to result now. - __ move(obj, result); - - // Slow-path re-enters here with result set. - __ branch_destination(slow->continuation()); - - return result; + enter_if_gc_state(gen, flags, slow_stub); } LIR_Opr ShenandoahBarrierSetC1::ensure_in_register(LIRGenerator* gen, LIR_Opr obj, BasicType type) { @@ -189,21 +168,21 @@ LIR_Opr ShenandoahBarrierSetC1::ensure_in_register(LIRGenerator* gen, LIR_Opr ob } void ShenandoahBarrierSetC1::store_at_resolved(LIRAccess& access, LIR_Opr value) { - if (access.is_oop()) { - if (ShenandoahSATBBarrier) { - pre_barrier(access.gen(), access.access_emit_info(), access.decorators(), access.resolved_addr(), LIR_OprFact::illegalOpr /* pre_val */); - } + DecoratorSet decorators = access.decorators(); + LIRGenerator* gen = access.gen(); + + if (ShenandoahSATBBarrier && access.is_oop()) { + keepalive_barrier(gen, /* obj = */ LIR_OprFact::illegalOpr, /* addr = */ access.resolved_addr(), decorators); } BarrierSetC1::store_at_resolved(access, value); if (ShenandoahCardBarrier && access.is_oop()) { - DecoratorSet decorators = access.decorators(); bool is_array = (decorators & IS_ARRAY) != 0; bool on_anonymous = (decorators & ON_UNKNOWN_OOP_REF) != 0; bool precise = is_array || on_anonymous; LIR_Opr post_addr = precise ? access.resolved_addr() : access.base().opr(); - post_barrier(access, post_addr); + card_barrier(gen, post_addr, decorators); } } @@ -230,7 +209,7 @@ void ShenandoahBarrierSetC1::load_at_resolved(LIRAccess& access, LIR_Opr result) if (ShenandoahBarrierSet::need_load_reference_barrier(decorators, type)) { LIR_Opr tmp = gen->new_register(T_OBJECT); BarrierSetC1::load_at_resolved(access, tmp); - tmp = load_reference_barrier(gen, tmp, access.resolved_addr(), decorators); + load_reference_barrier(gen, tmp, access.resolved_addr(), decorators); __ move(tmp, result); } else { BarrierSetC1::load_at_resolved(access, result); @@ -246,18 +225,17 @@ void ShenandoahBarrierSetC1::load_at_resolved(LIRAccess& access, LIR_Opr result) Lcont_anonymous = new LabelObj(); generate_referent_check(access, Lcont_anonymous); } - pre_barrier(gen, access.access_emit_info(), decorators, LIR_OprFact::illegalOpr /* addr_opr */, - result /* pre_val */); + keepalive_barrier(gen, /* obj = */ result, /* addr = */ LIR_OprFact::illegalOpr, decorators); if (is_anonymous) { __ branch_destination(Lcont_anonymous->label()); } } } -class C1ShenandoahPreBarrierCodeGenClosure : public StubAssemblerCodeGenClosure { +class C1ShenandoahKeepaliveBarrierCodeGenClosure : public StubAssemblerCodeGenClosure { virtual OopMapSet* generate_code(StubAssembler* sasm) { ShenandoahBarrierSetAssembler* bs = (ShenandoahBarrierSetAssembler*)BarrierSet::barrier_set()->barrier_set_assembler(); - bs->generate_c1_pre_barrier_runtime_stub(sasm); + bs->keepalive_barrier_c1_runtime_stub(sasm); return nullptr; } }; @@ -271,18 +249,20 @@ public: virtual OopMapSet* generate_code(StubAssembler* sasm) { ShenandoahBarrierSetAssembler* bs = (ShenandoahBarrierSetAssembler*)BarrierSet::barrier_set()->barrier_set_assembler(); - bs->generate_c1_load_reference_barrier_runtime_stub(sasm, _decorators); + bs->load_reference_barrier_c1_runtime_stub(sasm, _decorators); return nullptr; } }; bool ShenandoahBarrierSetC1::generate_c1_runtime_stubs(BufferBlob* buffer_blob) { - C1ShenandoahPreBarrierCodeGenClosure pre_code_gen_cl; - _pre_barrier_c1_runtime_code_blob = Runtime1::generate_blob(buffer_blob, StubId::NO_STUBID, - "shenandoah_pre_barrier_slow", - false, &pre_code_gen_cl); - if (_pre_barrier_c1_runtime_code_blob == nullptr) { - return false; + if (ShenandoahSATBBarrier) { + C1ShenandoahKeepaliveBarrierCodeGenClosure keepalive_code_gen_cl; + _keepalive_barrier_c1_runtime_code_blob = Runtime1::generate_blob(buffer_blob, StubId::NO_STUBID, + "shenandoah_keepalive_barrier_slow", + false, &keepalive_code_gen_cl); + if (_keepalive_barrier_c1_runtime_code_blob == nullptr) { + return false; + } } if (ShenandoahLoadRefBarrier) { C1ShenandoahLoadReferenceBarrierCodeGenClosure lrb_strong_code_gen_cl(ON_STRONG_OOP_REF); @@ -318,11 +298,9 @@ bool ShenandoahBarrierSetC1::generate_c1_runtime_stubs(BufferBlob* buffer_blob) return true; } -void ShenandoahBarrierSetC1::post_barrier(LIRAccess& access, LIR_Opr addr) { +void ShenandoahBarrierSetC1::card_barrier(LIRGenerator* gen, LIR_Opr addr, DecoratorSet decorators) { assert(ShenandoahCardBarrier, "Should have been checked by caller"); - DecoratorSet decorators = access.decorators(); - LIRGenerator* gen = access.gen(); bool in_heap = (decorators & IN_HEAP) != 0; if (!in_heap) { return; @@ -378,6 +356,7 @@ LIR_Opr ShenandoahBarrierSetC1::atomic_cmpxchg_at_resolved(LIRAccess& access, LI return BarrierSetC1::atomic_cmpxchg_at_resolved(access, cmp_value, new_value); } + DecoratorSet decorators = access.decorators(); LIRGenerator* gen = access.gen(); LIR_Opr tmp = gen->new_register(T_OBJECT); @@ -386,22 +365,20 @@ LIR_Opr ShenandoahBarrierSetC1::atomic_cmpxchg_at_resolved(LIRAccess& access, LI // Handle the previous value through SATB, as we are about to perform the store. __ load(addr->as_address_ptr(), tmp); if (ShenandoahSATBBarrier) { - pre_barrier(gen, access.access_emit_info(), access.decorators(), - /* addr_opr (unused) = */ LIR_OprFact::illegalOpr, - /* pre_val = */ tmp); + keepalive_barrier(gen, /* obj = */ tmp, /* addr = */ LIR_OprFact::illegalOpr, decorators); } // Perform LRB on location to fix it up for this and all following accesses. // This guarantees there are no false negatives due to concurrent evacuation, // and the value loaded later by CAS is sanitized by some LRB, or is null. if (ShenandoahLoadRefBarrier) { - load_reference_barrier(gen, /* obj = */ tmp, /* addr = */ addr, access.decorators()); + load_reference_barrier(gen, /* obj = */ tmp, /* addr = */ addr, decorators); } LIR_Opr result = BarrierSetC1::atomic_cmpxchg_at_resolved(access, cmp_value, new_value); if (ShenandoahCardBarrier) { - post_barrier(access, /* addr = */ addr); + card_barrier(gen, /* addr = */ addr, decorators); } return result; @@ -412,6 +389,7 @@ LIR_Opr ShenandoahBarrierSetC1::atomic_xchg_at_resolved(LIRAccess& access, LIRIt return BarrierSetC1::atomic_xchg_at_resolved(access, value); } + DecoratorSet decorators = access.decorators(); LIRGenerator* gen = access.gen(); LIR_Opr tmp = gen->new_register(T_OBJECT); @@ -420,22 +398,20 @@ LIR_Opr ShenandoahBarrierSetC1::atomic_xchg_at_resolved(LIRAccess& access, LIRIt // Handle the previous value through SATB, as we are about to perform the store. __ load(addr->as_address_ptr(), tmp); if (ShenandoahSATBBarrier) { - pre_barrier(gen, access.access_emit_info(), access.decorators(), - /* addr_opr (unused) = */ LIR_OprFact::illegalOpr, - /* pre_val = */ tmp); + keepalive_barrier(gen, /* obj = */ tmp, /* addr = */ LIR_OprFact::illegalOpr, decorators); } // Perform LRB on location to fix it up for this and all following accesses. // This is purely opportunistic: we would not have any false negatives here. // This guarantees the value loaded later by XCHG is sanitized by some LRB, or is null. if (ShenandoahLoadRefBarrier) { - load_reference_barrier(gen, /* obj = */ tmp, /* addr = */ addr, access.decorators()); + load_reference_barrier(gen, /* obj = */ tmp, /* addr = */ addr, decorators); } LIR_Opr result = BarrierSetC1::atomic_xchg_at_resolved(access, value); if (ShenandoahCardBarrier) { - post_barrier(access, /* addr = */ addr); + card_barrier(gen, /* addr = */ addr, decorators); } return result; diff --git a/src/hotspot/share/gc/shenandoah/c1/shenandoahBarrierSetC1.hpp b/src/hotspot/share/gc/shenandoah/c1/shenandoahBarrierSetC1.hpp index 413777a61ee..3f064c3569b 100644 --- a/src/hotspot/share/gc/shenandoah/c1/shenandoahBarrierSetC1.hpp +++ b/src/hotspot/share/gc/shenandoah/c1/shenandoahBarrierSetC1.hpp @@ -29,63 +29,48 @@ #include "c1/c1_CodeStubs.hpp" #include "gc/shared/c1/barrierSetC1.hpp" -class ShenandoahPreBarrierStub: public CodeStub { +class ShenandoahKeepaliveBarrierStub: public CodeStub { friend class ShenandoahBarrierSetC1; private: - bool _do_load; + LIR_Opr _obj; LIR_Opr _addr; - LIR_Opr _pre_val; - LIR_PatchCode _patch_code; - CodeEmitInfo* _info; + bool _do_load; public: - // Version that _does_ generate a load of the previous value from addr. - // addr (the address of the field to be read) must be a LIR_Address - // pre_val (a temporary register) must be a register; - ShenandoahPreBarrierStub(LIR_Opr addr, LIR_Opr pre_val, LIR_PatchCode patch_code, CodeEmitInfo* info) : - _do_load(true), _addr(addr), _pre_val(pre_val), - _patch_code(patch_code), _info(info) + ShenandoahKeepaliveBarrierStub(LIR_Opr obj, LIR_Opr addr) : + _obj(obj), _addr(addr), _do_load(true) { - assert(_pre_val->is_register(), "should be temporary register"); + assert(_obj->is_register(), "should be temporary register"); assert(_addr->is_address(), "should be the address of the field"); FrameMap* f = Compilation::current()->frame_map(); - f->update_reserved_argument_area_size(2 * BytesPerWord); + f->update_reserved_argument_area_size(1 * BytesPerWord); } - // Version that _does not_ generate load of the previous value; the - // previous value is assumed to have already been loaded into pre_val. - ShenandoahPreBarrierStub(LIR_Opr pre_val) : - _do_load(false), _addr(LIR_OprFact::illegalOpr), _pre_val(pre_val), - _patch_code(lir_patch_none), _info(nullptr) + ShenandoahKeepaliveBarrierStub(LIR_Opr obj) : + _obj(obj), _addr(LIR_OprFact::illegalOpr), _do_load(false) { - assert(_pre_val->is_register(), "should be a register"); + assert(_obj->is_register(), "should be a register"); + FrameMap* f = Compilation::current()->frame_map(); + f->update_reserved_argument_area_size(1 * BytesPerWord); } LIR_Opr addr() const { return _addr; } - LIR_Opr pre_val() const { return _pre_val; } - LIR_PatchCode patch_code() const { return _patch_code; } - CodeEmitInfo* info() const { return _info; } + LIR_Opr obj() const { return _obj; } bool do_load() const { return _do_load; } virtual void emit_code(LIR_Assembler* e); virtual void visit(LIR_OpVisitState* visitor) { + visitor->do_slow_case(); if (_do_load) { - // don't pass in the code emit info since it's processed in the fast - // path - if (_info != nullptr) - visitor->do_slow_case(_info); - else - visitor->do_slow_case(); - visitor->do_input(_addr); - visitor->do_temp(_pre_val); + visitor->do_temp(_addr); + visitor->do_temp(_obj); } else { - visitor->do_slow_case(); - visitor->do_input(_pre_val); + visitor->do_input(_obj); } } #ifndef PRODUCT - virtual void print_name(outputStream* out) const { out->print("ShenandoahPreBarrierStub"); } + virtual void print_name(outputStream* out) const { out->print("ShenandoahKeepaliveBarrierStub"); } #endif // PRODUCT }; @@ -94,29 +79,21 @@ class ShenandoahLoadReferenceBarrierStub: public CodeStub { private: LIR_Opr _obj; LIR_Opr _addr; - LIR_Opr _result; - LIR_Opr _tmp1; - LIR_Opr _tmp2; + LIR_Opr _slow_result; DecoratorSet _decorators; public: - ShenandoahLoadReferenceBarrierStub(LIR_Opr obj, LIR_Opr addr, LIR_Opr result, LIR_Opr tmp1, LIR_Opr tmp2, DecoratorSet decorators) : - _obj(obj), _addr(addr), _result(result), _tmp1(tmp1), _tmp2(tmp2), _decorators(decorators) + ShenandoahLoadReferenceBarrierStub(LIR_Opr obj, LIR_Opr addr, LIR_Opr slow_result, DecoratorSet decorators) : + _obj(obj), _addr(addr), _slow_result(slow_result), _decorators(decorators) { assert(_obj->is_register(), "should be register"); assert(_addr->is_register(), "should be register"); - assert(_result->is_register(), "should be register"); - assert(_tmp1->is_register(), "should be register"); - assert(_tmp2->is_register(), "should be register"); - FrameMap* f = Compilation::current()->frame_map(); f->update_reserved_argument_area_size(2 * BytesPerWord); } LIR_Opr obj() const { return _obj; } LIR_Opr addr() const { return _addr; } - LIR_Opr result() const { return _result; } - LIR_Opr tmp1() const { return _tmp1; } - LIR_Opr tmp2() const { return _tmp2; } + LIR_Opr slow_result() const { return _slow_result; } DecoratorSet decorators() const { return _decorators; } virtual void emit_code(LIR_Assembler* e); @@ -124,12 +101,10 @@ public: visitor->do_slow_case(); visitor->do_input(_obj); visitor->do_temp(_obj); + visitor->do_output(_obj); visitor->do_input(_addr); visitor->do_temp(_addr); - visitor->do_temp(_result); - visitor->do_output(_result); - visitor->do_temp(_tmp1); - visitor->do_temp(_tmp2); + visitor->do_temp(_slow_result); } #ifndef PRODUCT virtual void print_name(outputStream* out) const { out->print("ShenandoahLoadReferenceBarrierStub"); } @@ -138,63 +113,35 @@ public: class ShenandoahBarrierSetC1 : public BarrierSetC1 { private: - CodeBlob* _pre_barrier_c1_runtime_code_blob; + CodeBlob* _keepalive_barrier_c1_runtime_code_blob; CodeBlob* _load_reference_barrier_strong_rt_code_blob; CodeBlob* _load_reference_barrier_strong_native_rt_code_blob; CodeBlob* _load_reference_barrier_weak_rt_code_blob; CodeBlob* _load_reference_barrier_phantom_rt_code_blob; - void pre_barrier(LIRGenerator* gen, CodeEmitInfo* info, DecoratorSet decorators, LIR_Opr addr_opr, LIR_Opr pre_val); + void enter_if_gc_state(LIRGenerator* gen, int flags, CodeStub* slow_stub); - LIR_Opr load_reference_barrier(LIRGenerator* gen, LIR_Opr obj, LIR_Opr addr, DecoratorSet decorators); - - LIR_Opr load_reference_barrier_impl(LIRGenerator* gen, LIR_Opr obj, LIR_Opr addr, DecoratorSet decorators); + void keepalive_barrier(LIRGenerator* gen, LIR_Opr obj, LIR_Opr addr, DecoratorSet decorators); + void load_reference_barrier(LIRGenerator* gen, LIR_Opr obj, LIR_Opr addr, DecoratorSet decorators); + void card_barrier(LIRGenerator* gen, LIR_Opr addr, DecoratorSet decorators); LIR_Opr ensure_in_register(LIRGenerator* gen, LIR_Opr obj, BasicType type); public: ShenandoahBarrierSetC1(); - CodeBlob* pre_barrier_c1_runtime_code_blob() { - assert(_pre_barrier_c1_runtime_code_blob != nullptr, ""); - return _pre_barrier_c1_runtime_code_blob; - } + address keepalive_barrier_stub(); + address load_reference_barrier_stub(DecoratorSet decorators); - CodeBlob* load_reference_barrier_strong_rt_code_blob() { - assert(_load_reference_barrier_strong_rt_code_blob != nullptr, ""); - return _load_reference_barrier_strong_rt_code_blob; - } - - CodeBlob* load_reference_barrier_strong_native_rt_code_blob() { - assert(_load_reference_barrier_strong_native_rt_code_blob != nullptr, ""); - return _load_reference_barrier_strong_native_rt_code_blob; - } - - CodeBlob* load_reference_barrier_weak_rt_code_blob() { - assert(_load_reference_barrier_weak_rt_code_blob != nullptr, ""); - return _load_reference_barrier_weak_rt_code_blob; - } - - CodeBlob* load_reference_barrier_phantom_rt_code_blob() { - assert(_load_reference_barrier_phantom_rt_code_blob != nullptr, ""); - return _load_reference_barrier_phantom_rt_code_blob; - } + virtual bool generate_c1_runtime_stubs(BufferBlob* buffer_blob); protected: - virtual void store_at_resolved(LIRAccess& access, LIR_Opr value); virtual LIR_Opr resolve_address(LIRAccess& access, bool resolve_in_register); virtual void load_at_resolved(LIRAccess& access, LIR_Opr result); virtual LIR_Opr atomic_cmpxchg_at_resolved(LIRAccess& access, LIRItem& cmp_value, LIRItem& new_value); - virtual LIR_Opr atomic_xchg_at_resolved(LIRAccess& access, LIRItem& value); - - void post_barrier(LIRAccess& access, LIR_Opr addr); - -public: - - virtual bool generate_c1_runtime_stubs(BufferBlob* buffer_blob); }; #endif // SHARE_GC_SHENANDOAH_C1_SHENANDOAHBARRIERSETC1_HPP From 38ee41bee390e3d4aaa57cc090f7956cf8b9fe8b Mon Sep 17 00:00:00 2001 From: April Ivy Date: Fri, 26 Jun 2026 06:32:10 +0000 Subject: [PATCH 004/305] 8365887: Outdated comments in String::decode Reviewed-by: liach, sherman --- src/java.base/share/classes/java/lang/String.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/java.base/share/classes/java/lang/String.java b/src/java.base/share/classes/java/lang/String.java index 760f3ebc255..9f56ceb445a 100644 --- a/src/java.base/share/classes/java/lang/String.java +++ b/src/java.base/share/classes/java/lang/String.java @@ -671,14 +671,6 @@ public final class String } private static String decode(Charset charset, byte[] bytes, int offset, int length) { - // (1)We never cache the "external" cs, the only benefit of creating - // an additional StringDe/Encoder object to wrap it is to share the - // de/encode() method. These SD/E objects are short-lived, the young-gen - // gc should be able to take care of them well. But the best approach - // is still not to generate them if not really necessary. - // (2)The defensive copy of the input byte/char[] has a big performance - // impact, as well as the outgoing result byte/char[]. Need to do the - // optimization check of (sm==null && classLoader0==null) for both. CharsetDecoder cd = charset.newDecoder(); // ArrayDecoder fastpaths if (cd instanceof ArrayDecoder ad) { From fea0c229130770230f97a7665dadefe42ef1f9fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Du=EF=BF=BDan=20B=EF=BF=BDlek?= Date: Fri, 26 Jun 2026 07:04:54 +0000 Subject: [PATCH 005/305] 8387215: On-demand attribution of a record constructor body causes javac to emit an invalid diagnostic Reviewed-by: jlahoda --- .../JavacProcessingEnvironment.java | 3 +- .../OnDemandAttributionRecordConstructor.java | 215 ++++++++++++++++++ 2 files changed, 217 insertions(+), 1 deletion(-) create mode 100644 test/langtools/tools/javac/processing/model/trees/OnDemandAttributionRecordConstructor.java diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/JavacProcessingEnvironment.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/JavacProcessingEnvironment.java index 11fa3a5aebf..ede75a73824 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/JavacProcessingEnvironment.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/processing/JavacProcessingEnvironment.java @@ -1547,7 +1547,8 @@ public class JavacProcessingEnvironment implements ProcessingEnvironment, Closea } public void visitMethodDef(JCMethodDecl node) { // remove super constructor call that may have been added during attribution: - if (TreeInfo.isConstructor(node) && node.sym != null && node.sym.owner.isEnum() && + if (TreeInfo.isConstructor(node) && node.sym != null && + (node.sym.owner.isEnum() || TreeInfo.isCanonicalConstructor(node)) && node.body != null && node.body.stats.nonEmpty() && TreeInfo.isSuperCall(node.body.stats.head) && node.body.stats.head.pos == node.body.pos) { node.body.stats = node.body.stats.tail; diff --git a/test/langtools/tools/javac/processing/model/trees/OnDemandAttributionRecordConstructor.java b/test/langtools/tools/javac/processing/model/trees/OnDemandAttributionRecordConstructor.java new file mode 100644 index 00000000000..37d1541f18a --- /dev/null +++ b/test/langtools/tools/javac/processing/model/trees/OnDemandAttributionRecordConstructor.java @@ -0,0 +1,215 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8387215 + * @summary Check that javac does not report invalid errors when compiling a valid + * compact record constructor when an on-demand attribution is triggered + * by an annotation processor calling Trees.getElement(...) for identifiers + * inside the constructor. + * @library /tools/lib + * @modules + * jdk.compiler/com.sun.tools.javac.api + * jdk.compiler/com.sun.tools.javac.main + * @build toolbox.ToolBox toolbox.JavacTask + * @run junit ${test.main.class} + */ + +import com.sun.source.tree.IdentifierTree; +import com.sun.source.tree.MethodTree; +import com.sun.source.tree.Tree; +import com.sun.source.util.TreePath; +import com.sun.source.util.TreePathScanner; +import com.sun.source.util.Trees; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.Set; +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.ProcessingEnvironment; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedAnnotationTypes; +import javax.annotation.processing.SupportedOptions; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; +import javax.tools.Diagnostic; +import toolbox.JavacTask; +import toolbox.ToolBox; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import toolbox.Task; + +public class OnDemandAttributionRecordConstructor { + + Path base; + ToolBox tb = new ToolBox(); + + @Test + void testCompactRecordConstructorWithGetElementCall() throws Exception { + Path classes = base.resolve("classes"); + Files.createDirectories(classes); + new JavacTask(tb) + .options("-d", classes.toString()) + .sources(""" + record Repro(String name) { + Repro { + name = name.trim(); + } + } + """) + .processors(new ProcessorImpl()) + .run() + .writeAll(); + } + + @Test + void testCompactRecordConstructorWithoutGetElementCall() throws Exception { + Path classes = base.resolve("classes"); + Files.createDirectories(classes); + new JavacTask(tb) + .options("-d", classes.toString(), "-AskipGetElement=true") + .sources(""" + record Repro(String name) { + Repro { + name = name.trim(); + } + } + """) + .processors(new ProcessorImpl()) + .run() + .writeAll(); + } + + @Test + void testCanonicalRecordConstructorWithGetElementCall() throws Exception { + Path classes = base.resolve("classes"); + Files.createDirectories(classes); + new JavacTask(tb) + .options("-d", classes.toString()) + .sources(""" + record Repro(String name) { + Repro(String name) { + this.name = name.trim(); + } + } + """) + .processors(new ProcessorImpl()) + .run() + .writeAll(); + } + + @Test + void testBrokenRecordConstructorWithGetElementCall() throws Exception { + Path classes = base.resolve("classes"); + Files.createDirectories(classes); + List out = new JavacTask(tb) + .options("-d", classes.toString(), "-XDrawDiagnostics", "-nowarn") + .sources(""" + record Repro(String name) { + Repro(String name) { + super(); //illegal + this.name = name.trim(); + } + } + """) + .processors(new ProcessorImpl()) + .run(Task.Expect.FAIL) + .writeAll() + .getOutputLines(Task.OutputKind.DIRECT); + tb.checkEqual(out, List.of( + "Repro.java:2:5: compiler.err.invalid.canonical.constructor.in.record: (compiler.misc.canonical), Repro, (compiler.misc.canonical.must.not.contain.explicit.constructor.invocation)", + "1 error")); + } + + @SupportedAnnotationTypes("*") + @SupportedOptions(ProcessorImpl.SKIP_GET_ELEMENT) + private static class ProcessorImpl extends AbstractProcessor { + + private static final String SKIP_GET_ELEMENT = "skipGetElement"; + private Trees trees; + + @Override + public synchronized void init(ProcessingEnvironment processingEnv) { + super.init(processingEnv); + trees = Trees.instance(processingEnv); + } + + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + if (roundEnv.processingOver()) { + return false; + } + for (Element rootElement : roundEnv.getRootElements()) { + TreePath rootPath = trees.getPath(rootElement); + if (rootPath == null) { + continue; + } + new TreePathScanner() { + @Override + public Void visitIdentifier(IdentifierTree node, Void unused) { + TreePath currentPath = getCurrentPath(); + if (!skipGetElement() && insideRecordConstructor(currentPath)) { + processingEnv.getMessager() + .printMessage(Diagnostic.Kind.NOTE, + "Calling Trees.getElement for identifier '" + node.getName() + + "' inside a record constructor"); + trees.getElement(currentPath); + } + return super.visitIdentifier(node, unused); + } + }.scan(rootPath, null); + } + return false; + } + + private boolean skipGetElement() { + return Boolean.parseBoolean(processingEnv.getOptions().get(SKIP_GET_ELEMENT)); + } + + private static boolean insideRecordConstructor(TreePath path) { + TreePath current = path; + while (current != null) { + if (current.getLeaf() instanceof MethodTree method + && method.getReturnType() == null + && current.getParentPath() != null + && current.getParentPath().getLeaf().getKind() == Tree.Kind.RECORD) { + return true; + } + current = current.getParentPath(); + } + return false; + } + } + + @BeforeEach + public void setUp(TestInfo info) { + base = Paths.get(".") + .resolve(info.getTestMethod() + .orElseThrow() + .getName()); + } +} From b6e7b2b29213134a1a35fe34501b2fb94c04d70a Mon Sep 17 00:00:00 2001 From: Quan Anh Mai Date: Fri, 26 Jun 2026 08:04:23 +0000 Subject: [PATCH 006/305] 8385420: C2: SIGSEGV in compiled code due to missing ctrl Reviewed-by: vlivanov, epeter, dlong --- src/hotspot/share/opto/compile.cpp | 35 ++++++++---- src/hotspot/share/opto/node.cpp | 21 ++++++++ src/hotspot/share/opto/node.hpp | 1 + .../TestRemoveCastPPWithCMoveUse.java | 53 +++++++++++++++++++ 4 files changed, 100 insertions(+), 10 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/controldependency/TestRemoveCastPPWithCMoveUse.java diff --git a/src/hotspot/share/opto/compile.cpp b/src/hotspot/share/opto/compile.cpp index e7dc57524eb..e283d9b97ad 100644 --- a/src/hotspot/share/opto/compile.cpp +++ b/src/hotspot/share/opto/compile.cpp @@ -3481,22 +3481,37 @@ void Compile::final_graph_reshaping_main_switch(Node* n, Final_Reshape_Counts& f ResourceMark rm; Unique_Node_List wq; wq.push(n); + + + // When we remove a CastPP, we need to pin all of its transitive users under the control of + // the removed node. The simplest approach is to pin all of the uses of the removed CastPP, + // but it is overly conservative, as an AddP does not really need pinning. As a result, we + // look through those nodes that do not need pinning and only pin memory access nodes under + // n->in(0). for (uint next = 0; next < wq.size(); ++next) { Node *m = wq.at(next); for (DUIterator_Fast imax, i = m->fast_outs(imax); i < imax; i++) { Node* use = m->fast_out(i); - if (use->is_Mem() || use->is_EncodeNarrowPtr()) { + int use_op = use->Opcode(); + if (use->is_CFG() || use->pinned() || // already pinned at the exact control + use->is_Cmp() || use->Opcode() == Op_CastP2X) { // pure computations + continue; + } else if (use->is_EncodeNarrowPtr() || // EncodeP remembers whether its input is nullable, so it must be pinned + use_op == Op_PartialSubtypeCheck || // This accesses its pointer inputs, so it must depend on them being not-null + use->is_Mem() || use->is_memory_access_intrinsic()) { use->ensure_control_or_add_prec(n->in(0)); + } else if (use_op == Op_AddP || + use_op == Op_CastPP || use_op == Op_CheckCastPP || + use_op == Op_CMoveP || use_op == Op_CMoveN || + use_op == Op_DecodeN || use_op == Op_DecodeNKlass) { + // Look through use to find memory accesses if use does not need pinning + wq.push(use); } else { - switch(use->Opcode()) { - case Op_AddP: - case Op_DecodeN: - case Op_DecodeNKlass: - case Op_CheckCastPP: - case Op_CastPP: - wq.push(use); - break; - } + // Should have handled all kinds of nodes, verify that we do not unexpectedly arrive + // here + assert(false, "unexpected node %s", use->Name()); + // Be conservative in product and pin the unexpected use + use->ensure_control_or_add_prec(n->in(0)); } } } diff --git a/src/hotspot/share/opto/node.cpp b/src/hotspot/share/opto/node.cpp index 997ce92fe1c..1210693f957 100644 --- a/src/hotspot/share/opto/node.cpp +++ b/src/hotspot/share/opto/node.cpp @@ -3001,6 +3001,27 @@ bool Node::is_data_proj_of_pure_function(const Node* maybe_pure_function) const return Opcode() == Op_Proj && as_Proj()->_con == TypeFunc::Parms && maybe_pure_function->is_CallLeafPure(); } +// Whether this is an intrinsic node that accesses memory and has a memory input, such as array +// equal intrinsic. Some nodes do access memory but do not have a memory input, such as +// PartialSubTypeCheck, they are not included here. +bool Node::is_memory_access_intrinsic() const { + switch (Opcode()) { + case Op_StrComp: + case Op_StrEquals: + case Op_StrIndexOf: + case Op_StrIndexOfChar: + case Op_StrCompressedCopy: + case Op_StrInflatedCopy: + case Op_AryEq: + case Op_CountPositives: + case Op_VectorizedHashCode: + case Op_EncodeISOArray: + return true; + default: + return false; + } +} + //--------------------------has_non_debug_uses------------------------------ // Checks whether the node has any non-debug uses or not. bool Node::has_non_debug_uses() const { diff --git a/src/hotspot/share/opto/node.hpp b/src/hotspot/share/opto/node.hpp index 1ef4b5a51b6..92bd03c0d63 100644 --- a/src/hotspot/share/opto/node.hpp +++ b/src/hotspot/share/opto/node.hpp @@ -1069,6 +1069,7 @@ public: uint is_Copy() const { return (_flags & Flag_is_Copy); } virtual bool is_CFG() const { return false; } + bool is_memory_access_intrinsic() const; // If this node is control-dependent on a test, can it be rerouted to a dominating equivalent // test? This means that the node can be executed safely as long as it happens after the test diff --git a/test/hotspot/jtreg/compiler/controldependency/TestRemoveCastPPWithCMoveUse.java b/test/hotspot/jtreg/compiler/controldependency/TestRemoveCastPPWithCMoveUse.java new file mode 100644 index 00000000000..3d752cc74f5 --- /dev/null +++ b/test/hotspot/jtreg/compiler/controldependency/TestRemoveCastPPWithCMoveUse.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package compiler.controldependency; + +/* + * @test + * @bug 8385420 + * @summary C2 correctly handles the case when the removed CastPPNode has a CMove use. + * @run main ${test.main.class} + * @run main/othervm -Xbatch -XX:CompileOnly=${test.main.class}::test + * -XX:+UnlockDiagnosticVMOptions -XX:+StressGCM ${test.main.class} + * + */ +public class TestRemoveCastPPWithCMoveUse { + public static void main(String[] args) { + for (int i = 0; i < 10_000; i++) { + test(null, false); + test(null, true); + test("", false); + test("", true); + } + } + + static int test(String a, boolean flag) { + StringBuilder sb = new StringBuilder(); + if (a == null) { + sb.append(""); + } else { + sb.append(flag ? a : ""); + } + return sb.length(); + } +} From c289cf502c1f946aae863e71692a2fefeb891dee Mon Sep 17 00:00:00 2001 From: Sean Mullan Date: Fri, 26 Jun 2026 15:12:03 +0000 Subject: [PATCH 007/305] 8377102: cacerts jlink plugin Reviewed-by: alanb --- .../jlink/internal/plugins/CACertsPlugin.java | 114 +++++++++++++++++ .../tools/jlink/resources/plugins.properties | 16 ++- src/jdk.jlink/share/classes/module-info.java | 5 +- src/jdk.jlink/share/man/jlink.md | 10 ++ .../jlink/plugins/CACertsPluginTest.java | 120 ++++++++++++++++++ 5 files changed, 262 insertions(+), 3 deletions(-) create mode 100644 src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/CACertsPlugin.java create mode 100644 test/jdk/tools/jlink/plugins/CACertsPluginTest.java diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/CACertsPlugin.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/CACertsPlugin.java new file mode 100644 index 00000000000..3f663983828 --- /dev/null +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/plugins/CACertsPlugin.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.tools.jlink.internal.plugins; + +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.security.KeyStore; +import java.security.cert.Certificate; +import java.util.HashMap; +import java.util.Map; + +import jdk.tools.jlink.internal.ResourcePoolEntryFactory; +import jdk.tools.jlink.plugin.PluginException; +import jdk.tools.jlink.plugin.ResourcePool; +import jdk.tools.jlink.plugin.ResourcePoolBuilder; +import jdk.tools.jlink.plugin.ResourcePoolEntry; + +/** + * Creates the cacerts keystore in the output image with the certificates of + * the specified aliases only. + */ +public class CACertsPlugin extends AbstractPlugin { + + private static final String RES = "/java.base/lib/security/cacerts"; + + // cacerts keystore aliases + private String[] aliases; + + public CACertsPlugin() { + super("cacerts"); + } + + @Override + public boolean hasArguments() { + return true; + } + + @Override + public void configure(Map config) { + String option = config.get(getName()); + if (option == null) { + throw new AssertionError(); + } + // If alias has a comma in it, this won't work, but no cacerts + // aliases have commas. + aliases = option.split(","); + } + + @Override + public ResourcePool transform(ResourcePool in, ResourcePoolBuilder out) { + in.transformAndCopy(res -> { + if (res.type() == ResourcePoolEntry.Type.NATIVE_LIB && + res.path().equals(RES)) { + byte[] cacerts = transformCACerts(res.content()); + return ResourcePoolEntryFactory.create(res, cacerts); + } + return res; + }, out); + return out.build(); + } + + /** + * Creates a keystore containing only the certificates of the specified + * aliases. + */ + private byte[] transformCACerts(InputStream content) { + try { + var ks = KeyStore.getInstance("PKCS12"); + ks.load(content, null); + Map certs = new HashMap<>(aliases.length); + for (var alias : aliases) { + var cert = ks.getCertificate(alias); + if (cert == null) { + throw new PluginException( + "alias " + alias + " does not exist"); + } + certs.put(alias, cert); + } + ks.load(null, null); + for (var entry : certs.entrySet()) { + ks.setCertificateEntry(entry.getKey(), entry.getValue()); + } + var baos = new ByteArrayOutputStream(); + ks.store(baos, null); + return baos.toByteArray(); + } catch (PluginException pe) { + throw pe; + } catch (Exception ex) { + throw new PluginException(ex); + } + } +} diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/plugins.properties b/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/plugins.properties index 7e3c26fa7b8..892ba73249e 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/plugins.properties +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/resources/plugins.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -53,6 +53,20 @@ release-info.usage=\ \ Any number of = pairs can be passed.\n\ \ del: is to delete the list of keys in release file. +cacerts.argument=[,]* + +cacerts.description=\ +Create the cacerts keystore in the output image with only the certificates\n\ +of the specified aliases. is the name of an alias in the cacerts\n\ +keystore in the java.base module. + +cacerts.usage=\ +\ --cacerts [,]*\n\ +\ Create the cacerts keystore in the output image\n\ +\ with only the certificates of the specified\n\ +\ aliases. is the name of an alias in the\n\ +\ cacerts keystore in the java.base module. + class-for-name.argument= class-for-name.description=\ diff --git a/src/jdk.jlink/share/classes/module-info.java b/src/jdk.jlink/share/classes/module-info.java index ba66da53604..0adc1ce6d37 100644 --- a/src/jdk.jlink/share/classes/module-info.java +++ b/src/jdk.jlink/share/classes/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -81,5 +81,6 @@ module jdk.jlink { jdk.tools.jlink.internal.plugins.VendorVMBugURLPlugin, jdk.tools.jlink.internal.plugins.VendorVersionPlugin, jdk.tools.jlink.internal.plugins.CDSPlugin, - jdk.tools.jlink.internal.plugins.SaveJlinkArgfilesPlugin; + jdk.tools.jlink.internal.plugins.SaveJlinkArgfilesPlugin, + jdk.tools.jlink.internal.plugins.CACertsPlugin; } diff --git a/src/jdk.jlink/share/man/jlink.md b/src/jdk.jlink/share/man/jlink.md index b95424fdde9..1ee4d08646d 100644 --- a/src/jdk.jlink/share/man/jlink.md +++ b/src/jdk.jlink/share/man/jlink.md @@ -235,6 +235,16 @@ Options Description : Generate CDS archive if the runtime image supports the CDS feature. +### Plugin `cacerts` + +Options +: `--cacerts=`*alias*\[`,`*alias*\]\* + +Description +: Create the `cacerts` keystore in the output image with only the + certificates of the specified aliases. *alias* is the name of an alias + in the `cacerts` keystore in the java.base module. + ## jlink Examples The following command creates a runtime image in the directory `greetingsapp`. diff --git a/test/jdk/tools/jlink/plugins/CACertsPluginTest.java b/test/jdk/tools/jlink/plugins/CACertsPluginTest.java new file mode 100644 index 00000000000..8ca720be639 --- /dev/null +++ b/test/jdk/tools/jlink/plugins/CACertsPluginTest.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.nio.file.Path; +import java.security.KeyStore; +import java.security.cert.Certificate; +import java.util.Enumeration; + +import jtreg.SkippedException; +import jdk.test.lib.Asserts; +import jdk.test.lib.security.SecurityUtils; +import jdk.tools.jlink.internal.LinkableRuntimeImage; +import tests.Helper; + +/* @test + * @bug 8377102 + * @summary Test the --cacerts plugin + * @library ../../lib /test/lib + * @modules java.base/jdk.internal.jimage + * jdk.jlink/jdk.tools.jimage + * jdk.jlink/jdk.tools.jlink.internal + * @build tests.* + * @run main/othervm CACertsPluginTest + */ + +public class CACertsPluginTest { + + private static Helper helper; + + private static final String CACERTS_PATH = "lib/security/cacerts"; + private static final boolean LINKABLE_RUNTIME = + LinkableRuntimeImage.isLinkableRuntime(); + + public static void main(String[] args) throws Throwable { + + helper = Helper.newHelper(LINKABLE_RUNTIME); + if (helper == null) { + throw new SkippedException("Test not run"); + } + + KeyStore jdkCacerts = SecurityUtils.getCacertsKeyStore(); + Enumeration aliases = jdkCacerts.aliases(); + String alias1 = aliases.nextElement(); + String alias2 = aliases.nextElement(); + + // test one alias + test("testOne", jdkCacerts, alias1); + + // test two aliases + test("testTwo", jdkCacerts, alias1, alias2); + + // test illegal/bad options + testBadOptions(); + } + + private static void test(String module, KeyStore jdkCacerts, + String... aliases) throws Exception { + + helper.generateDefaultJModule(module); + + String option = toOption(aliases); + Path image = helper.generateDefaultImage( + new String[] { "--cacerts", option }, module).assertSuccess(); + helper.checkImage(image, module, null, null, + new String[] { CACERTS_PATH }); + + KeyStore imageCacerts = KeyStore.getInstance( + image.resolve(CACERTS_PATH).toFile(), (char[]) null); + + Asserts.assertEquals(imageCacerts.size(), aliases.length); + for (String alias : aliases) { + Asserts.assertTrue(imageCacerts.isCertificateEntry(alias)); + Asserts.assertEquals( + jdkCacerts.getCertificate(alias), + imageCacerts.getCertificate(alias)); + } + } + + private static void testBadOptions() throws Exception { + + String module = "testBad"; + helper.generateDefaultJModule(module); + helper.generateDefaultImage(new String[] + { "--cacerts", "bogus-alias" }, module) + .assertFailure("alias bogus-alias does not exist"); + } + + private static String toOption(String... aliases) { + int max = aliases.length - 1; + + StringBuilder sb = new StringBuilder(); + for (int i = 0; ; i++) { + sb.append(aliases[i]); + if (i == max) { + return sb.toString(); + } + sb.append(","); + } + } +} From 548a95379f159a0dc369f6bb80d8167ec835c7cd Mon Sep 17 00:00:00 2001 From: Jatin Bhateja Date: Sat, 27 Jun 2026 01:46:53 +0000 Subject: [PATCH 008/305] 8386163: C2 Vector API: assert(collect_unique_inputs(n, inputs) == 1) failed: not unary Reviewed-by: vlivanov, epeter --- src/hotspot/share/opto/compile.cpp | 12 ++- .../vectorapi/TestMaskedNotAllOnes.java | 84 +++++++++++++++++++ 2 files changed, 89 insertions(+), 7 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/vectorapi/TestMaskedNotAllOnes.java diff --git a/src/hotspot/share/opto/compile.cpp b/src/hotspot/share/opto/compile.cpp index e283d9b97ad..a2e5899a1e9 100644 --- a/src/hotspot/share/opto/compile.cpp +++ b/src/hotspot/share/opto/compile.cpp @@ -2721,13 +2721,11 @@ static uint collect_unique_inputs(Node* n, Unique_Node_List& inputs) { if (is_vector_bitwise_op(n)) { uint inp_cnt = n->is_predicated_vector() ? n->req()-1 : n->req(); if (VectorNode::is_vector_bitwise_not_pattern(n)) { - for (uint i = 1; i < inp_cnt; i++) { - Node* in = n->in(i); - bool skip = VectorNode::is_all_ones_vector(in); - if (!skip && !inputs.member(in)) { - inputs.push(in); - cnt++; - } + assert(n->req() == (n->is_predicated_vector() ? 4 : 3), "must have 2 data inputs"); + Node* opnd = VectorNode::is_all_ones_vector(n->in(1)) ? n->in(2) : n->in(1); + if (!inputs.member(opnd)) { + inputs.push(opnd); + cnt++; } assert(cnt <= 1, "not unary"); } else { diff --git a/test/hotspot/jtreg/compiler/vectorapi/TestMaskedNotAllOnes.java b/test/hotspot/jtreg/compiler/vectorapi/TestMaskedNotAllOnes.java new file mode 100644 index 00000000000..6abf88ed06f --- /dev/null +++ b/test/hotspot/jtreg/compiler/vectorapi/TestMaskedNotAllOnes.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8386163 + * @summary Checks there is no assertion failure with macro logic optimization when both inputs of not patterns are all one vectors + * @modules jdk.incubator.vector + * @library /test/lib / + * @run driver ${test.main.class} + */ + +package compiler.vectorapi; + +import compiler.lib.ir_framework.*; +import compiler.lib.verify.Verify; +import jdk.incubator.vector.IntVector; +import jdk.incubator.vector.VectorMask; +import jdk.incubator.vector.VectorOperators; +import jdk.incubator.vector.VectorSpecies; + +public class TestMaskedNotAllOnes { + + private static final VectorSpecies ISP = IntVector.SPECIES_PREFERRED; + private static final int VALUE = 1234567; + + public static void main(String[] args) { + TestFramework.runWithFlags("--add-modules=jdk.incubator.vector"); + } + + @Test + @Warmup(10000) + static int[] testMaskedDivNegOne() { + IntVector v = IntVector.broadcast(ISP, VALUE); + VectorMask mask = VectorMask.fromLong(ISP, -1L); + int[] out = new int[ISP.length()]; + v.div(-1, mask).intoArray(out, 0); + return out; + } + + static final int[] GOLD_DIV = testMaskedDivNegOne(); + + @Check(test = "testMaskedDivNegOne") + static void checkMaskedDivNegOne(int[] out) { + Verify.checkEQ(GOLD_DIV, out); + } + + @Test + @Warmup(10000) + static int[] testMaskedNotAllOnesVector() { + IntVector allOnes = IntVector.broadcast(ISP, -1); + VectorMask mask = VectorMask.fromLong(ISP, -1L); + int[] out = new int[ISP.length()]; + allOnes.lanewise(VectorOperators.NOT, mask).intoArray(out, 0); + return out; + } + + static final int[] GOLD_NOT = testMaskedNotAllOnesVector(); + + @Check(test = "testMaskedNotAllOnesVector") + static void checkMaskedNotAllOnesVector(int[] out) { + Verify.checkEQ(GOLD_NOT, out); + } +} From dc4b150bcf816c65baba831bd4bc8f4d1dda468e Mon Sep 17 00:00:00 2001 From: Saint Wesonga Date: Mon, 29 Jun 2026 03:32:15 +0000 Subject: [PATCH 009/305] 8378892: TestTrampoline fails on Windows AArch64 Reviewed-by: dlong, macarte --- .../jtreg/compiler/c2/aarch64/TestTrampoline.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/test/hotspot/jtreg/compiler/c2/aarch64/TestTrampoline.java b/test/hotspot/jtreg/compiler/c2/aarch64/TestTrampoline.java index 114f7f9bfab..084f63279bf 100644 --- a/test/hotspot/jtreg/compiler/c2/aarch64/TestTrampoline.java +++ b/test/hotspot/jtreg/compiler/c2/aarch64/TestTrampoline.java @@ -89,15 +89,19 @@ public class TestTrampoline { } static class Test { - private static void test(String s, int i) { + // Use a StringBuilder to avoid issues with String.charAt() not being + // inlined on Windows because its UTF-16 path was executed at startup + // but not enough for C2 to inline it. + private static void test(StringBuilder s, int i) { if (s.charAt(i) > 128) throw new RuntimeException(); } public static void main(String[] args) { - String s = "Returns the char value at the specified index."; + var sb = new StringBuilder(); + sb.append("Returns the char value at the specified index."); for (int i = 0; i < ITERATIONS_TO_HEAT_LOOP; ++i) { - test(s, i % s.length()); + test(sb, i % sb.length()); } } } From db1482615e4c8489a8d16bc0985d6e50a88c9409 Mon Sep 17 00:00:00 2001 From: David Holmes Date: Mon, 29 Jun 2026 05:00:28 +0000 Subject: [PATCH 010/305] 8387378: [BACKOUT] C2: SIGSEGV in compiled code due to missing ctrl Reviewed-by: jpai --- src/hotspot/share/opto/compile.cpp | 35 ++++-------- src/hotspot/share/opto/node.cpp | 21 -------- src/hotspot/share/opto/node.hpp | 1 - .../TestRemoveCastPPWithCMoveUse.java | 53 ------------------- 4 files changed, 10 insertions(+), 100 deletions(-) delete mode 100644 test/hotspot/jtreg/compiler/controldependency/TestRemoveCastPPWithCMoveUse.java diff --git a/src/hotspot/share/opto/compile.cpp b/src/hotspot/share/opto/compile.cpp index a2e5899a1e9..a273bb6053e 100644 --- a/src/hotspot/share/opto/compile.cpp +++ b/src/hotspot/share/opto/compile.cpp @@ -3479,37 +3479,22 @@ void Compile::final_graph_reshaping_main_switch(Node* n, Final_Reshape_Counts& f ResourceMark rm; Unique_Node_List wq; wq.push(n); - - - // When we remove a CastPP, we need to pin all of its transitive users under the control of - // the removed node. The simplest approach is to pin all of the uses of the removed CastPP, - // but it is overly conservative, as an AddP does not really need pinning. As a result, we - // look through those nodes that do not need pinning and only pin memory access nodes under - // n->in(0). for (uint next = 0; next < wq.size(); ++next) { Node *m = wq.at(next); for (DUIterator_Fast imax, i = m->fast_outs(imax); i < imax; i++) { Node* use = m->fast_out(i); - int use_op = use->Opcode(); - if (use->is_CFG() || use->pinned() || // already pinned at the exact control - use->is_Cmp() || use->Opcode() == Op_CastP2X) { // pure computations - continue; - } else if (use->is_EncodeNarrowPtr() || // EncodeP remembers whether its input is nullable, so it must be pinned - use_op == Op_PartialSubtypeCheck || // This accesses its pointer inputs, so it must depend on them being not-null - use->is_Mem() || use->is_memory_access_intrinsic()) { + if (use->is_Mem() || use->is_EncodeNarrowPtr()) { use->ensure_control_or_add_prec(n->in(0)); - } else if (use_op == Op_AddP || - use_op == Op_CastPP || use_op == Op_CheckCastPP || - use_op == Op_CMoveP || use_op == Op_CMoveN || - use_op == Op_DecodeN || use_op == Op_DecodeNKlass) { - // Look through use to find memory accesses if use does not need pinning - wq.push(use); } else { - // Should have handled all kinds of nodes, verify that we do not unexpectedly arrive - // here - assert(false, "unexpected node %s", use->Name()); - // Be conservative in product and pin the unexpected use - use->ensure_control_or_add_prec(n->in(0)); + switch(use->Opcode()) { + case Op_AddP: + case Op_DecodeN: + case Op_DecodeNKlass: + case Op_CheckCastPP: + case Op_CastPP: + wq.push(use); + break; + } } } } diff --git a/src/hotspot/share/opto/node.cpp b/src/hotspot/share/opto/node.cpp index 1210693f957..997ce92fe1c 100644 --- a/src/hotspot/share/opto/node.cpp +++ b/src/hotspot/share/opto/node.cpp @@ -3001,27 +3001,6 @@ bool Node::is_data_proj_of_pure_function(const Node* maybe_pure_function) const return Opcode() == Op_Proj && as_Proj()->_con == TypeFunc::Parms && maybe_pure_function->is_CallLeafPure(); } -// Whether this is an intrinsic node that accesses memory and has a memory input, such as array -// equal intrinsic. Some nodes do access memory but do not have a memory input, such as -// PartialSubTypeCheck, they are not included here. -bool Node::is_memory_access_intrinsic() const { - switch (Opcode()) { - case Op_StrComp: - case Op_StrEquals: - case Op_StrIndexOf: - case Op_StrIndexOfChar: - case Op_StrCompressedCopy: - case Op_StrInflatedCopy: - case Op_AryEq: - case Op_CountPositives: - case Op_VectorizedHashCode: - case Op_EncodeISOArray: - return true; - default: - return false; - } -} - //--------------------------has_non_debug_uses------------------------------ // Checks whether the node has any non-debug uses or not. bool Node::has_non_debug_uses() const { diff --git a/src/hotspot/share/opto/node.hpp b/src/hotspot/share/opto/node.hpp index 92bd03c0d63..1ef4b5a51b6 100644 --- a/src/hotspot/share/opto/node.hpp +++ b/src/hotspot/share/opto/node.hpp @@ -1069,7 +1069,6 @@ public: uint is_Copy() const { return (_flags & Flag_is_Copy); } virtual bool is_CFG() const { return false; } - bool is_memory_access_intrinsic() const; // If this node is control-dependent on a test, can it be rerouted to a dominating equivalent // test? This means that the node can be executed safely as long as it happens after the test diff --git a/test/hotspot/jtreg/compiler/controldependency/TestRemoveCastPPWithCMoveUse.java b/test/hotspot/jtreg/compiler/controldependency/TestRemoveCastPPWithCMoveUse.java deleted file mode 100644 index 3d752cc74f5..00000000000 --- a/test/hotspot/jtreg/compiler/controldependency/TestRemoveCastPPWithCMoveUse.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package compiler.controldependency; - -/* - * @test - * @bug 8385420 - * @summary C2 correctly handles the case when the removed CastPPNode has a CMove use. - * @run main ${test.main.class} - * @run main/othervm -Xbatch -XX:CompileOnly=${test.main.class}::test - * -XX:+UnlockDiagnosticVMOptions -XX:+StressGCM ${test.main.class} - * - */ -public class TestRemoveCastPPWithCMoveUse { - public static void main(String[] args) { - for (int i = 0; i < 10_000; i++) { - test(null, false); - test(null, true); - test("", false); - test("", true); - } - } - - static int test(String a, boolean flag) { - StringBuilder sb = new StringBuilder(); - if (a == null) { - sb.append(""); - } else { - sb.append(flag ? a : ""); - } - return sb.length(); - } -} From 56b4a547d81d968b1441186e8cb28360a8ce6cfd Mon Sep 17 00:00:00 2001 From: Albert Mingkun Yang Date: Mon, 29 Jun 2026 05:35:47 +0000 Subject: [PATCH 011/305] 8386480: Parallel: Avoid Triggering GC Before VM Initialization Completes Reviewed-by: gli, tschatzl, aboldtch --- .../gc/parallel/parallelScavengeHeap.cpp | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp b/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp index b77294a2ac1..7aa88110fc8 100644 --- a/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp +++ b/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp @@ -308,11 +308,26 @@ HeapWord* ParallelScavengeHeap::mem_allocate_work(size_t size, bool is_tlab) { for (uint loop_count = 0; /* empty */; ++loop_count) { HeapWord* result; { + // This lock is needed to sync with the VM-init expansion below. ConditionalMutexLocker locker(Heap_lock, !is_init_completed()); result = mem_allocate_cas_noexpand(size, is_tlab); if (result != nullptr) { return result; } + + if (!is_init_completed()) { + // Double checked locking, this ensure that is_init_completed() does not + // transition while expanding the heap. + MonitorLocker ml(InitCompleted_lock, Monitor::_no_safepoint_check_flag); + if (!is_init_completed()) { + result = expand_heap_and_allocate(size, is_tlab); + // Return the result if it's tlab-allocation. If the result is null, callers will retry + // non-tlab allocation. + if (result != nullptr || is_tlab) { + return result; + } + } + } } // Read total_collections() under the lock so that multiple @@ -328,19 +343,6 @@ HeapWord* ParallelScavengeHeap::mem_allocate_work(size_t size, bool is_tlab) { return result; } - if (!is_init_completed()) { - // Double checked locking, this ensure that is_init_completed() does not - // transition while expanding the heap. - MonitorLocker ml(InitCompleted_lock, Monitor::_no_safepoint_check_flag); - if (!is_init_completed()) { - // Can't do GC; try heap expansion to satisfy the request. - result = expand_heap_and_allocate(size, is_tlab); - if (result != nullptr) { - return result; - } - } - } - gc_count = total_collections(); } From b735de6d7190afde0fb056d7c439938439744576 Mon Sep 17 00:00:00 2001 From: Christian Stein Date: Mon, 29 Jun 2026 06:48:27 +0000 Subject: [PATCH 012/305] 8386844: Update to use jtreg 8.3 Reviewed-by: erikj, lancea, iris, vromero --- make/autoconf/lib-tests.m4 | 2 +- make/conf/github-actions.conf | 2 +- make/conf/jib-profiles.js | 4 ++-- test/docs/TEST.ROOT | 2 +- test/hotspot/jtreg/TEST.ROOT | 2 +- test/jaxp/TEST.ROOT | 2 +- test/jdk/TEST.ROOT | 2 +- test/langtools/TEST.ROOT | 2 +- test/lib-test/TEST.ROOT | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/make/autoconf/lib-tests.m4 b/make/autoconf/lib-tests.m4 index faaf229eacd..89f9bf425e1 100644 --- a/make/autoconf/lib-tests.m4 +++ b/make/autoconf/lib-tests.m4 @@ -28,7 +28,7 @@ ################################################################################ # Minimum supported versions -JTREG_MINIMUM_VERSION=8.2.1 +JTREG_MINIMUM_VERSION=8.3 GTEST_MINIMUM_VERSION=1.14.0 ################################################################################ diff --git a/make/conf/github-actions.conf b/make/conf/github-actions.conf index 9aee8e87e3c..6c5805f0764 100644 --- a/make/conf/github-actions.conf +++ b/make/conf/github-actions.conf @@ -26,7 +26,7 @@ # Versions and download locations for dependencies used by GitHub Actions (GHA) GTEST_VERSION=1.14.0 -JTREG_VERSION=8.2.1+1 +JTREG_VERSION=8.3+1 LINUX_X64_BOOT_JDK_EXT=tar.gz LINUX_X64_BOOT_JDK_URL=https://download.java.net/java/GA/jdk26/c3cc523845074aa0af4f5e1e1ed4151d/35/GPL/openjdk-26_linux-x64_bin.tar.gz diff --git a/make/conf/jib-profiles.js b/make/conf/jib-profiles.js index 20315cda97d..b425c66a34c 100644 --- a/make/conf/jib-profiles.js +++ b/make/conf/jib-profiles.js @@ -1174,9 +1174,9 @@ var getJibProfilesDependencies = function (input, common) { jtreg: { server: "jpg", product: "jtreg", - version: "8.2.1", + version: "8.3", build_number: "1", - file: "bundles/jtreg-8.2.1+1.zip", + file: "bundles/jtreg-8.3+1.zip", environment_name: "JT_HOME", environment_path: input.get("jtreg", "home_path") + "/bin", configure_args: "--with-jtreg=" + input.get("jtreg", "home_path"), diff --git a/test/docs/TEST.ROOT b/test/docs/TEST.ROOT index 11cba9c1c88..a42f6c99aa1 100644 --- a/test/docs/TEST.ROOT +++ b/test/docs/TEST.ROOT @@ -38,7 +38,7 @@ groups=TEST.groups # Minimum jtreg version -requiredVersion=8.2.1+1 +requiredVersion=8.3+1 # Path to libraries in the topmost test directory. This is needed so @library diff --git a/test/hotspot/jtreg/TEST.ROOT b/test/hotspot/jtreg/TEST.ROOT index 964c33bc57c..77f48171522 100644 --- a/test/hotspot/jtreg/TEST.ROOT +++ b/test/hotspot/jtreg/TEST.ROOT @@ -102,7 +102,7 @@ requires.properties= \ jdk.static # Minimum jtreg version -requiredVersion=8.2.1+1 +requiredVersion=8.3+1 # Path to libraries in the topmost test directory. This is needed so @library # does not need ../../../ notation to reach them diff --git a/test/jaxp/TEST.ROOT b/test/jaxp/TEST.ROOT index ddf29839e20..695645315d8 100644 --- a/test/jaxp/TEST.ROOT +++ b/test/jaxp/TEST.ROOT @@ -23,7 +23,7 @@ modules=java.xml groups=TEST.groups # Minimum jtreg version -requiredVersion=8.2.1+1 +requiredVersion=8.3+1 # Path to libraries in the topmost test directory. This is needed so @library # does not need ../../ notation to reach them diff --git a/test/jdk/TEST.ROOT b/test/jdk/TEST.ROOT index 7048aafc638..08bc31ffdb8 100644 --- a/test/jdk/TEST.ROOT +++ b/test/jdk/TEST.ROOT @@ -120,7 +120,7 @@ requires.properties= \ jdk.static # Minimum jtreg version -requiredVersion=8.2.1+1 +requiredVersion=8.3+1 # Path to libraries in the topmost test directory. This is needed so @library # does not need ../../ notation to reach them diff --git a/test/langtools/TEST.ROOT b/test/langtools/TEST.ROOT index c76f99d1396..8319e724e89 100644 --- a/test/langtools/TEST.ROOT +++ b/test/langtools/TEST.ROOT @@ -15,7 +15,7 @@ keys=intermittent randomness needs-src needs-src-jdk_javadoc groups=TEST.groups # Minimum jtreg version -requiredVersion=8.2.1+1 +requiredVersion=8.3+1 # Path to libraries in the topmost test directory. This is needed so @library diff --git a/test/lib-test/TEST.ROOT b/test/lib-test/TEST.ROOT index 33c9a9c2a43..9c9db2998a5 100644 --- a/test/lib-test/TEST.ROOT +++ b/test/lib-test/TEST.ROOT @@ -29,7 +29,7 @@ keys=randomness # Minimum jtreg version -requiredVersion=8.2.1+1 +requiredVersion=8.3+1 # Prevent TestNG-based tests under this root, use @run junit actions instead disallowedActions=testng From 58b646545519e727525ef06a5dfbd01decbf148d Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Mon, 29 Jun 2026 06:50:38 +0000 Subject: [PATCH 013/305] 8387142: BUILD_LIBMANAGEMENT_EXT remove special warning settings Reviewed-by: lucy, kevinw --- make/modules/jdk.management/Lib.gmk | 4 +--- .../share/native/libmanagement_ext/DiagnosticCommandImpl.c | 4 ++-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/make/modules/jdk.management/Lib.gmk b/make/modules/jdk.management/Lib.gmk index 8991414b44e..f65348e9381 100644 --- a/make/modules/jdk.management/Lib.gmk +++ b/make/modules/jdk.management/Lib.gmk @@ -1,5 +1,5 @@ # -# Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -41,8 +41,6 @@ endif $(eval $(call SetupJdkLibrary, BUILD_LIBMANAGEMENT_EXT, \ NAME := management_ext, \ OPTIMIZATION := HIGH, \ - DISABLED_WARNINGS_gcc_DiagnosticCommandImpl.c := unused-variable, \ - DISABLED_WARNINGS_clang_DiagnosticCommandImpl.c := unused-variable, \ DISABLED_WARNINGS_clang_UnixOperatingSystem.c := format-nonliteral, \ CFLAGS := $(LIBMANAGEMENT_EXT_CFLAGS), \ JDK_LIBS := java.base:libjava java.base:libjvm, \ diff --git a/src/jdk.management/share/native/libmanagement_ext/DiagnosticCommandImpl.c b/src/jdk.management/share/native/libmanagement_ext/DiagnosticCommandImpl.c index 6c0554a5c32..5a01e3ad738 100644 --- a/src/jdk.management/share/native/libmanagement_ext/DiagnosticCommandImpl.c +++ b/src/jdk.management/share/native/libmanagement_ext/DiagnosticCommandImpl.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -151,7 +151,7 @@ Java_com_sun_management_internal_DiagnosticCommandImpl_getDiagnosticCommandInfo jobjectArray args; jobject obj; jmmOptionalSupport mos; - jint ret = jmm_interface_management_ext->GetOptionalSupport(env, &mos); + jmm_interface_management_ext->GetOptionalSupport(env, &mos); jsize num_commands; dcmdInfo* dcmd_info_array; jstring jname, jdesc, jimpact, cmd; From 007c7e38be8d419cd60169e8af9065e0ac2c4fcb Mon Sep 17 00:00:00 2001 From: Thomas Schatzl Date: Mon, 29 Jun 2026 07:28:54 +0000 Subject: [PATCH 014/305] 8387303: G1: Convert G1ConcurrentRefine::_num_threads_wanted to use the Atomic API Reviewed-by: iwalulya, stefank --- src/hotspot/share/gc/g1/g1ConcurrentRefine.cpp | 8 ++++---- src/hotspot/share/gc/g1/g1ConcurrentRefine.hpp | 5 +++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/hotspot/share/gc/g1/g1ConcurrentRefine.cpp b/src/hotspot/share/gc/g1/g1ConcurrentRefine.cpp index 4d4730de0b2..c1c820cb554 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentRefine.cpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentRefine.cpp @@ -575,7 +575,7 @@ bool G1ConcurrentRefine::adjust_num_threads_periodically() { if (!_needs_adjust) { Tickspan since_adjust = Ticks::now() - _last_adjust; if (since_adjust.milliseconds() < adjust_threads_period_ms()) { - _num_threads_wanted = 0; + _num_threads_wanted.store_relaxed(0); return false; } } @@ -592,7 +592,7 @@ bool G1ConcurrentRefine::adjust_num_threads_periodically() { _needs_adjust = true; } - return (_num_threads_wanted > 0) && !heap_was_locked(); + return (num_threads_wanted() > 0) && !heap_was_locked(); } void G1ConcurrentRefine::adjust_threads_wanted(size_t available_bytes) { @@ -603,7 +603,7 @@ void G1ConcurrentRefine::adjust_threads_wanted(size_t available_bytes) { size_t num_cards = policy->current_pending_cards(); - _threads_needed.update(_num_threads_wanted, + _threads_needed.update(num_threads_wanted(), available_bytes, num_cards, _pending_cards_target); @@ -613,7 +613,7 @@ void G1ConcurrentRefine::adjust_threads_wanted(size_t available_bytes) { new_wanted = _thread_control.max_num_threads(); } - _num_threads_wanted = new_wanted; + _num_threads_wanted.store_relaxed(new_wanted); log_debug(gc, refine)("Concurrent refinement: wanted %u, pending cards: %zu (pending-from-gc %zu), " "predicted: %zu, goal %zu, time-until-next-gc: %1.2fms pred-refine-rate %1.2fc/ms log-rate %1.2fc/ms", diff --git a/src/hotspot/share/gc/g1/g1ConcurrentRefine.hpp b/src/hotspot/share/gc/g1/g1ConcurrentRefine.hpp index 50fb412f3af..62e56c14c68 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentRefine.hpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentRefine.hpp @@ -28,6 +28,7 @@ #include "gc/g1/g1ConcurrentRefineStats.hpp" #include "gc/g1/g1ConcurrentRefineThreadsNeeded.hpp" #include "memory/allocation.hpp" +#include "runtime/atomic.hpp" #include "utilities/debug.hpp" #include "utilities/globalDefinitions.hpp" #include "utilities/growableArray.hpp" @@ -212,7 +213,7 @@ public: // class G1ConcurrentRefine : public CHeapObj { G1Policy* _policy; - volatile uint _num_threads_wanted; + Atomic _num_threads_wanted; size_t _pending_cards_target; Ticks _last_adjust; Ticks _last_deactivate; @@ -306,7 +307,7 @@ public: // obtaining the heap lock. bool heap_was_locked() const { return _heap_was_locked; } - uint num_threads_wanted() const { return _num_threads_wanted; } + uint num_threads_wanted() const { return _num_threads_wanted.load_relaxed(); } uint max_num_threads() const { return _thread_control.max_num_threads(); } // Iterate over all concurrent refinement threads applying the given closure. From 5f1355b0851d95a53a760fa045e16d9bde3d1a04 Mon Sep 17 00:00:00 2001 From: Thomas Schatzl Date: Mon, 29 Jun 2026 08:45:26 +0000 Subject: [PATCH 015/305] 8387322: G1: G1CSetCandidateGroupList::_num_regions should be Atomic Reviewed-by: stefank --- src/hotspot/share/gc/g1/g1CollectionSetCandidates.cpp | 10 +++++----- src/hotspot/share/gc/g1/g1CollectionSetCandidates.hpp | 5 +++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/hotspot/share/gc/g1/g1CollectionSetCandidates.cpp b/src/hotspot/share/gc/g1/g1CollectionSetCandidates.cpp index 3637d477229..ac1b29a6bd7 100644 --- a/src/hotspot/share/gc/g1/g1CollectionSetCandidates.cpp +++ b/src/hotspot/share/gc/g1/g1CollectionSetCandidates.cpp @@ -134,7 +134,7 @@ void G1CSetCandidateGroupList::append(G1CSetCandidateGroup* group) { assert(group->length() > 0, "Do not add empty groups"); assert(!_groups.contains(group), "Already added to list"); _groups.append(group); - _num_regions += group->length(); + _num_regions.store_relaxed(num_regions() + group->length()); } G1CSetCandidateGroup* G1CSetCandidateGroupList::at(uint index) { @@ -147,7 +147,7 @@ void G1CSetCandidateGroupList::clear(bool uninstall_group_cardset) { delete gr; } _groups.clear(); - _num_regions = 0; + _num_regions.store_relaxed(0); } void G1CSetCandidateGroupList::prepare_for_scan() { @@ -156,9 +156,9 @@ void G1CSetCandidateGroupList::prepare_for_scan() { } } -void G1CSetCandidateGroupList::remove_selected(uint count, uint num_regions) { +void G1CSetCandidateGroupList::remove_selected(uint count, uint num_regions_to_remove) { _groups.remove_till(count); - _num_regions -= num_regions; + _num_regions.store_relaxed(num_regions() - num_regions_to_remove); } void G1CSetCandidateGroupList::remove(G1CSetCandidateGroupList* other) { @@ -172,7 +172,7 @@ void G1CSetCandidateGroupList::remove(G1CSetCandidateGroupList* other) { // Create a list from scratch, copying over the elements from the candidate // list not in the other list. Finally deallocate and overwrite the old list. int new_length = _groups.length() - other->length(); - _num_regions = num_regions() - other->num_regions(); + _num_regions.store_relaxed(num_regions() - other->num_regions()); GrowableArray new_list(new_length, mtGC); uint other_idx = 0; diff --git a/src/hotspot/share/gc/g1/g1CollectionSetCandidates.hpp b/src/hotspot/share/gc/g1/g1CollectionSetCandidates.hpp index 8a2235cf89c..a70f9e395b6 100644 --- a/src/hotspot/share/gc/g1/g1CollectionSetCandidates.hpp +++ b/src/hotspot/share/gc/g1/g1CollectionSetCandidates.hpp @@ -29,6 +29,7 @@ #include "gc/g1/g1CollectionSetCandidates.hpp" #include "gc/shared/gc_globals.hpp" #include "memory/allocation.hpp" +#include "runtime/atomic.hpp" #include "runtime/globals.hpp" #include "utilities/growableArray.hpp" @@ -147,7 +148,7 @@ using G1CSetCandidateGroupListIterator = GrowableArrayIterator _groups; - volatile uint _num_regions; + Atomic _num_regions; public: G1CSetCandidateGroupList(); @@ -163,7 +164,7 @@ public: uint length() const { return (uint)_groups.length(); } - uint num_regions() const { return _num_regions; } + uint num_regions() const { return _num_regions.load_relaxed(); } void remove_selected(uint count, uint num_regions); From 78112cbcaafcc7de0dfbf67b3f83677abaeaee87 Mon Sep 17 00:00:00 2001 From: Thomas Schatzl Date: Mon, 29 Jun 2026 08:52:38 +0000 Subject: [PATCH 016/305] 8385903: G1: G1CollectionSet::_num_regions needs to be Atomic Reviewed-by: stefank --- src/hotspot/share/gc/g1/g1CollectionSet.cpp | 16 ++++++++-------- src/hotspot/share/gc/g1/g1CollectionSet.hpp | 9 +++++---- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/hotspot/share/gc/g1/g1CollectionSet.cpp b/src/hotspot/share/gc/g1/g1CollectionSet.cpp index 9f1bbf1b48e..3a086d8b09b 100644 --- a/src/hotspot/share/gc/g1/g1CollectionSet.cpp +++ b/src/hotspot/share/gc/g1/g1CollectionSet.cpp @@ -33,7 +33,6 @@ #include "gc/g1/g1ParScanThreadState.hpp" #include "gc/g1/g1Policy.hpp" #include "logging/logStream.hpp" -#include "runtime/orderAccess.hpp" #include "utilities/debug.hpp" #include "utilities/globalDefinitions.hpp" @@ -128,8 +127,11 @@ void G1CollectionSet::add_old_region(G1HeapRegion* hr) { _g1h->register_old_collection_set_region_with_region_attr(hr); - assert(num_regions() < _max_num_regions, "Collection set now larger than maximum size."); - _regions[_num_regions++] = hr->hrm_index(); + uint local_num_regions = num_regions(); + assert(local_num_regions < _max_num_regions, "Collection set now larger than maximum size."); + _regions[local_num_regions] = hr->hrm_index(); + _num_regions.store_relaxed(local_num_regions + 1); + _num_initial_old_regions++; _g1h->old_set_remove(hr); @@ -162,14 +164,13 @@ void G1CollectionSet::stop_incremental_building() { void G1CollectionSet::clear() { assert_at_safepoint_on_vm_thread(); - _num_regions = 0; + _num_regions.store_relaxed(0); _groups.clear(); assert(_optional_groups.length() == 0, "must be"); } void G1CollectionSet::iterate(G1HeapRegionClosure* cl) const { - uint len = _num_regions; - OrderAccess::loadload(); + uint len = _num_regions.load_acquire(); for (uint i = 0; i < len; i++) { G1HeapRegion* r = _g1h->region_at(_regions[i]); @@ -233,8 +234,7 @@ void G1CollectionSet::add_young_region_common(G1HeapRegion* hr) { _regions[index] = hr->hrm_index(); // Concurrent readers must observe the store of the value in the array before an // update to the _num_regions field. - OrderAccess::storestore(); - _num_regions++; + _num_regions.fetch_then_add(1u, memory_order_release); } void G1CollectionSet::add_survivor_regions(G1HeapRegion* hr) { diff --git a/src/hotspot/share/gc/g1/g1CollectionSet.hpp b/src/hotspot/share/gc/g1/g1CollectionSet.hpp index 5fa9868f2b2..eee985f259d 100644 --- a/src/hotspot/share/gc/g1/g1CollectionSet.hpp +++ b/src/hotspot/share/gc/g1/g1CollectionSet.hpp @@ -26,6 +26,7 @@ #define SHARE_GC_G1_G1COLLECTIONSET_HPP #include "gc/g1/g1CollectionSetCandidates.hpp" +#include "runtime/atomic.hpp" #include "utilities/debug.hpp" #include "utilities/globalDefinitions.hpp" @@ -142,14 +143,14 @@ class G1CollectionSet { // All regions in _regions below _num_regions are assumed to be part of the // collection set. // We assume that at any time there is at most only one writer and (one or more) - // concurrent readers. This means synchronization using storestore and loadload - // barriers on the writer and reader respectively only are sufficient. + // concurrent readers. This means synchronization using release and acquire + // on the writer and reader respectively only are sufficient. // // This corresponds to the regions referenced by the candidate groups further below. uint* _regions; uint _max_num_regions; - volatile uint _num_regions; + Atomic _num_regions; // Old gen groups selected for evacuation. G1CSetCandidateGroupList _groups; @@ -285,7 +286,7 @@ public: // Returns the number of regions in the current collection set increment. uint num_regions_in_increment() const { return num_regions() - _regions_inc_part_start; } // Returns the total number of regions in the current collection set. - uint num_regions() const { return _num_regions; } + uint num_regions() const { return _num_regions.load_relaxed(); } // Iterate over the entire collection set (all increments calculated so far), applying // the given G1HeapRegionClosure on all of the regions. From 22313f85bac81e68e318976ff69439658d0a602f Mon Sep 17 00:00:00 2001 From: Thomas Schatzl Date: Mon, 29 Jun 2026 08:56:19 +0000 Subject: [PATCH 017/305] 8387206: G1: Code root verification crashes because of stale table scanner Reviewed-by: iwalulya --- src/hotspot/share/gc/g1/g1HeapRegion.cpp | 2 ++ src/hotspot/share/gc/g1/g1HeapRegionRemSet.cpp | 6 +++++- src/hotspot/share/gc/g1/g1HeapRegionRemSet.hpp | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/hotspot/share/gc/g1/g1HeapRegion.cpp b/src/hotspot/share/gc/g1/g1HeapRegion.cpp index 810bd4df2ee..2c85e2fcc0d 100644 --- a/src/hotspot/share/gc/g1/g1HeapRegion.cpp +++ b/src/hotspot/share/gc/g1/g1HeapRegion.cpp @@ -413,6 +413,8 @@ bool G1HeapRegion::verify_code_roots(VerifyOption vo) const { return has_code_roots; } + rem_set()->reset_code_root_table_scanner(); + VerifyCodeRootNMethodClosure nm_cl(this); code_roots_do(&nm_cl); diff --git a/src/hotspot/share/gc/g1/g1HeapRegionRemSet.cpp b/src/hotspot/share/gc/g1/g1HeapRegionRemSet.cpp index 13c7a6a8d3e..ef42538d4d6 100644 --- a/src/hotspot/share/gc/g1/g1HeapRegionRemSet.cpp +++ b/src/hotspot/share/gc/g1/g1HeapRegionRemSet.cpp @@ -87,8 +87,12 @@ void G1HeapRegionRemSet::clear(bool only_cardset, bool keep_tracked) { } } -void G1HeapRegionRemSet::reset_table_scanner() { +void G1HeapRegionRemSet::reset_code_root_table_scanner() { _code_roots.reset_table_scanner(); +} + +void G1HeapRegionRemSet::reset_table_scanner() { + reset_code_root_table_scanner(); if (has_cset_group()) { card_set()->reset_table_scanner(); } diff --git a/src/hotspot/share/gc/g1/g1HeapRegionRemSet.hpp b/src/hotspot/share/gc/g1/g1HeapRegionRemSet.hpp index 950098c706e..2e97d6a7597 100644 --- a/src/hotspot/share/gc/g1/g1HeapRegionRemSet.hpp +++ b/src/hotspot/share/gc/g1/g1HeapRegionRemSet.hpp @@ -154,6 +154,7 @@ public: // entries for this region in other remsets. void clear(bool only_cardset = false, bool keep_tracked = false); + void reset_code_root_table_scanner(); void reset_table_scanner(); G1MonotonicArenaMemoryStats card_set_memory_stats() const; From 9ee63d6359382ea65677d547068dcabb9151dc5c Mon Sep 17 00:00:00 2001 From: Guanqiang Han Date: Mon, 29 Jun 2026 09:29:49 +0000 Subject: [PATCH 018/305] 8387197: C2: Improve klass_ptr_type in GraphKit::gen_instanceof() similarly to GraphKit::gen_checkcast() Reviewed-by: qamai, vlivanov --- src/hotspot/share/opto/graphKit.cpp | 16 ++-- .../TestInstanceOfImprovedKlassPtrType.java | 84 +++++++++++++++++++ 2 files changed, 94 insertions(+), 6 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/parsing/TestInstanceOfImprovedKlassPtrType.java diff --git a/src/hotspot/share/opto/graphKit.cpp b/src/hotspot/share/opto/graphKit.cpp index 3112bb6b169..4f5251f39e1 100644 --- a/src/hotspot/share/opto/graphKit.cpp +++ b/src/hotspot/share/opto/graphKit.cpp @@ -3240,9 +3240,10 @@ Node* GraphKit::maybe_cast_profiled_obj(Node* obj, Node* GraphKit::gen_instanceof(Node* obj, Node* superklass, bool safe_for_replace) { kill_dead_locals(); // Benefit all the uncommon traps assert( !stopped(), "dead parse path should be checked in callers" ); - assert(!TypePtr::NULL_PTR->higher_equal(_gvn.type(superklass)->is_klassptr()), + const TypeKlassPtr* klass_ptr_type = _gvn.type(superklass)->isa_klassptr(); + assert(klass_ptr_type != nullptr && !TypePtr::NULL_PTR->higher_equal(klass_ptr_type), "must check for not-null not-dead klass in callers"); - + const TypeKlassPtr* improved_klass_ptr_type = klass_ptr_type->try_improve(); // Make the merge point enum { _obj_path = 1, _fail_path, _null_path, PATH_LIMIT }; RegionNode* region = new RegionNode(PATH_LIMIT); @@ -3278,11 +3279,10 @@ Node* GraphKit::gen_instanceof(Node* obj, Node* superklass, bool safe_for_replac // Do we know the type check always succeed? bool known_statically = false; - if (_gvn.type(superklass)->singleton()) { - const TypeKlassPtr* superk = _gvn.type(superklass)->is_klassptr(); + if (improved_klass_ptr_type->singleton()) { const TypeKlassPtr* subk = _gvn.type(obj)->is_oopptr()->as_klass_type(); if (subk->is_loaded()) { - int static_res = C->static_subtype_check(superk, subk); + int static_res = C->static_subtype_check(improved_klass_ptr_type, subk); known_statically = (static_res == Compile::SSC_always_true || static_res == Compile::SSC_always_false); } } @@ -3305,7 +3305,11 @@ Node* GraphKit::gen_instanceof(Node* obj, Node* superklass, bool safe_for_replac } // Generate the subtype check - Node* not_subtype_ctrl = gen_subtype_check(not_null_obj, superklass); + Node* improved_superklass = superklass; + if (improved_klass_ptr_type != klass_ptr_type && improved_klass_ptr_type->singleton()) { + improved_superklass = makecon(improved_klass_ptr_type); + } + Node* not_subtype_ctrl = gen_subtype_check(not_null_obj, improved_superklass); // Plug in the success path to the general merge in slot 1. region->init_req(_obj_path, control()); diff --git a/test/hotspot/jtreg/compiler/parsing/TestInstanceOfImprovedKlassPtrType.java b/test/hotspot/jtreg/compiler/parsing/TestInstanceOfImprovedKlassPtrType.java new file mode 100644 index 00000000000..19214738dd6 --- /dev/null +++ b/test/hotspot/jtreg/compiler/parsing/TestInstanceOfImprovedKlassPtrType.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8387197 + * @summary Verify that improving klass_ptr_type in GraphKit::gen_instanceof() allows + * eliminating SubTypeCheckNode when the receiver implements an interface + * unrelated to the checked class. + * @library /test/lib / + * @run driver ${test.main.class} + */ + +package compiler.parsing; + +import compiler.lib.ir_framework.*; +import jdk.test.lib.Asserts; + +public class TestInstanceOfImprovedKlassPtrType { + static abstract class B {} + static final class C extends B {} + + interface I {} + static class D implements I {} + static class E implements I {} + + public static void main(String[] args) { + TestFramework.run(); + } + + @DontInline + int testHelper2(Object o) { + return 1; + } + + @Test + @IR(counts = {IRNode.SUBTYPE_CHECK, "1"}, + phase = CompilePhase.AFTER_PARSING) + int test1(Object o) { + Object o1 = (I) o; + if (o1 instanceof B) { + return testHelper2(o1); + } else { + return 2; + } + } + + @Run(test = "test1") + @Warmup(0) + void runTest() { + int sum = 0; + Object[] arr = new Object[] {new C(), new D(), new E()}; + for (int i = 0; i < 3; i++){ + Object o = arr[i]; + if (o instanceof I) { + sum += test1(o); + } else { + sum += 3; + } + } + Asserts.assertEquals(sum, 7); + return; + } +} From 027eb8b416d2f3238f4c4c65d5d485911ad79ca5 Mon Sep 17 00:00:00 2001 From: Thomas Schatzl Date: Mon, 29 Jun 2026 10:49:58 +0000 Subject: [PATCH 019/305] 8371720: G1: Move concurrent mark initialization to first concurrent start pause Reviewed-by: iwalulya --- src/hotspot/share/gc/g1/g1CollectedHeap.cpp | 5 +++-- src/hotspot/share/gc/g1/g1ConcurrentMark.hpp | 5 +++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp index b4758897dd6..7396c1ee9ce 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp @@ -2723,13 +2723,14 @@ void G1CollectedHeap::do_collection_pause_at_safepoint(size_t allocation_word_si _bytes_used_during_gc = 0; - _cm->fully_initialize(); - policy()->decide_on_concurrent_start_pause(); // Record whether this pause may need to trigger a concurrent operation. Later, // when we signal the G1ConcurrentMarkThread, the collector state has already // been reset for the next pause. bool should_start_concurrent_mark_operation = collector_state()->is_in_concurrent_start_gc(); + if (should_start_concurrent_mark_operation) { + _cm->fully_initialize(); + } // Perform the collection. G1YoungCollector collector(gc_cause(), allocation_word_size); diff --git a/src/hotspot/share/gc/g1/g1ConcurrentMark.hpp b/src/hotspot/share/gc/g1/g1ConcurrentMark.hpp index f0071286e04..21518423957 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentMark.hpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentMark.hpp @@ -569,6 +569,11 @@ public: uint worker_id_offset() const { return _worker_id_offset; } + // Fully allocates and initializes data structures for the concurrent cycle. + // Methods that use concurrent cycle state such as the concurrent mark threads, + // tasks, marking stack, statistics, TAMS or TARS require this initialization. + // Callers that run before the first concurrent start pause, which calls this, + // should guard calls with is_fully_initialized(). void fully_initialize(); bool is_fully_initialized() const { return _cm_thread != nullptr; } From 1d514a55555248c74bb7587ecf8fac44748df001 Mon Sep 17 00:00:00 2001 From: David Briemann Date: Mon, 29 Jun 2026 11:44:47 +0000 Subject: [PATCH 020/305] 8387016: PPC64: Remove postalloc_expand from float/double compare nodes Reviewed-by: mdoerr, rrich --- src/hotspot/cpu/ppc/ppc.ad | 157 ++++++------------------------------- 1 file changed, 22 insertions(+), 135 deletions(-) diff --git a/src/hotspot/cpu/ppc/ppc.ad b/src/hotspot/cpu/ppc/ppc.ad index 9bec99e90cc..3cdc820b5f9 100644 --- a/src/hotspot/cpu/ppc/ppc.ad +++ b/src/hotspot/cpu/ppc/ppc.ad @@ -3556,9 +3556,6 @@ ins_attrib ins_alignment(1); ins_attrib ins_cannot_rematerialize(false); ins_attrib ins_should_rematerialize(false); -// Instruction has variable size depending on alignment. -ins_attrib ins_variable_size_depending_on_alignment(false); - // Instruction is a nop. ins_attrib ins_is_nop(false); @@ -7015,8 +7012,6 @@ instruct cmovF_reg(cmpOp cmp, flagsRegSrc crx, regF dst, regF src) %{ match(Set dst (CMoveF (Binary cmp crx) (Binary dst src))); ins_cost(DEFAULT_COST+BRANCH_COST); - ins_variable_size_depending_on_alignment(true); - format %{ "CMOVEF $cmp, $crx, $dst, $src\n\t" %} size(8); ins_encode %{ @@ -7034,8 +7029,6 @@ instruct cmovD_reg(cmpOp cmp, flagsRegSrc crx, regD dst, regD src) %{ match(Set dst (CMoveD (Binary cmp crx) (Binary dst src))); ins_cost(DEFAULT_COST+BRANCH_COST); - ins_variable_size_depending_on_alignment(true); - format %{ "CMOVEF $cmp, $crx, $dst, $src\n\t" %} size(8); ins_encode %{ @@ -8276,8 +8269,6 @@ instruct cmovI_bne_negI_reg(iRegIdst dst, flagsRegSrc crx, iRegIsrc src1) %{ effect(USE_DEF dst, USE src1, USE crx); predicate(false); - ins_variable_size_depending_on_alignment(true); - format %{ "CMOVE $dst, neg($src1), $crx" %} size(8); ins_encode %{ @@ -8334,8 +8325,6 @@ instruct cmovL_bne_negL_reg(iRegLdst dst, flagsRegSrc crx, iRegLsrc src1) %{ effect(USE_DEF dst, USE src1, USE crx); predicate(false); - ins_variable_size_depending_on_alignment(true); - format %{ "CMOVE $dst, neg($src1), $crx" %} size(8); ins_encode %{ @@ -10044,8 +10033,6 @@ instruct cmovI_bso_stackSlotL(iRegIdst dst, flagsRegSrc crx, stackSlotL src) %{ effect(DEF dst, USE crx, USE src); predicate(false); - ins_variable_size_depending_on_alignment(true); - format %{ "CMOVI $crx, $dst, $src" %} size(8); ins_encode( enc_cmove_bso_stackSlotL(dst, crx, src) ); @@ -10057,8 +10044,6 @@ instruct cmovI_bso_reg(iRegIdst dst, flagsRegSrc crx, regD src) %{ effect(DEF dst, USE crx, USE src); predicate(false); - ins_variable_size_depending_on_alignment(true); - format %{ "CMOVI $crx, $dst, $src" %} size(8); ins_encode( enc_cmove_bso_reg(dst, crx, src) ); @@ -10219,8 +10204,6 @@ instruct cmovL_bso_stackSlotL(iRegLdst dst, flagsRegSrc crx, stackSlotL src) %{ effect(DEF dst, USE crx, USE src); predicate(false); - ins_variable_size_depending_on_alignment(true); - format %{ "CMOVL $crx, $dst, $src" %} size(8); ins_encode( enc_cmove_bso_stackSlotL(dst, crx, src) ); @@ -10232,8 +10215,6 @@ instruct cmovL_bso_reg(iRegLdst dst, flagsRegSrc crx, regD src) %{ effect(DEF dst, USE crx, USE src); predicate(false); - ins_variable_size_depending_on_alignment(true); - format %{ "CMOVL $crx, $dst, $src" %} size(8); ins_encode( enc_cmove_bso_reg(dst, crx, src) ); @@ -10853,84 +10834,22 @@ instruct cmpFUnordered_reg_reg(flagsReg crx, regF src1, regF src2) %{ ins_pipe(pipe_class_default); %} -instruct cmov_bns_less(flagsReg crx) %{ - // no match-rule, false predicate - effect(DEF crx); - predicate(false); - - ins_variable_size_depending_on_alignment(true); - - format %{ "CMOV $crx" %} - size(12); - ins_encode %{ - Label done; - __ bns($crx$$CondRegister, done); // not unordered -> keep crx - __ li(R0, 0); - __ cmpwi($crx$$CondRegister, R0, 1); // unordered -> set crx to 'less' - __ bind(done); - %} - ins_pipe(pipe_class_default); -%} - // Compare floating, generate condition code. -instruct cmpF_reg_reg_Ex(flagsReg crx, regF src1, regF src2) %{ - // FIXME: should we match 'If cmp (CmpF src1 src2))' ?? - // - // The following code sequence occurs a lot in mpegaudio: - // - // block BXX: - // 0: instruct cmpFUnordered_reg_reg (cmpF_reg_reg-0): - // cmpFUrd CR6, F11, F9 - // 4: instruct cmov_bns_less (cmpF_reg_reg-1): - // cmov CR6 - // 8: instruct branchConSched: - // B_FARle CR6, B56 P=0.500000 C=-1.000000 +instruct cmpF_reg_reg(flagsReg crx, regF src1, regF src2) %{ match(Set crx (CmpF src1 src2)); ins_cost(DEFAULT_COST+BRANCH_COST); - format %{ "CMPF $crx, $src1, $src2 \t// postalloc expanded" %} - postalloc_expand %{ - // - // replaces - // - // region src1 src2 - // \ | | - // crx=cmpF_reg_reg - // - // with - // - // region src1 src2 - // \ | | - // crx=cmpFUnordered_reg_reg - // | - // ^ region - // | \ - // crx=cmov_bns_less - // - - // Create new nodes. - MachNode *m1 = new cmpFUnordered_reg_regNode(); - MachNode *m2 = new cmov_bns_lessNode(); - - // inputs for new nodes - m1->add_req(n_region, n_src1, n_src2); - m2->add_req(n_region); - m2->add_prec(m1); - - // operands for new nodes - m1->_opnds[0] = op_crx; - m1->_opnds[1] = op_src1; - m1->_opnds[2] = op_src2; - m2->_opnds[0] = op_crx; - - // registers for new nodes - ra_->set_pair(m1->_idx, ra_->get_reg_second(this), ra_->get_reg_first(this)); // crx - ra_->set_pair(m2->_idx, ra_->get_reg_second(this), ra_->get_reg_first(this)); // crx - - // Insert new nodes. - nodes->push(m1); - nodes->push(m2); + format %{ "CMPF $crx, $src1, $src2" %} + size(16); + ins_encode %{ + Label done; + __ fcmpu($crx$$CondRegister, $src1$$FloatRegister, $src2$$FloatRegister); + __ bns($crx$$CondRegister, done); + __ li(R0, 0); + __ cmpwi($crx$$CondRegister, R0, 1); + __ bind(done); %} + ins_pipe(pipe_class_default); %} // Compare float, generate -1,0,1 @@ -10968,53 +10887,21 @@ instruct cmpDUnordered_reg_reg(flagsReg crx, regD src1, regD src2) %{ ins_pipe(pipe_class_default); %} -instruct cmpD_reg_reg_Ex(flagsReg crx, regD src1, regD src2) %{ +instruct cmpD_reg_reg(flagsReg crx, regD src1, regD src2) %{ match(Set crx (CmpD src1 src2)); ins_cost(DEFAULT_COST+BRANCH_COST); - format %{ "CmpD $crx, $src1, $src2 \t// postalloc expanded" %} - postalloc_expand %{ - // - // replaces - // - // region src1 src2 - // \ | | - // crx=cmpD_reg_reg - // - // with - // - // region src1 src2 - // \ | | - // crx=cmpDUnordered_reg_reg - // | - // ^ region - // | \ - // crx=cmov_bns_less - // - - // create new nodes - MachNode *m1 = new cmpDUnordered_reg_regNode(); - MachNode *m2 = new cmov_bns_lessNode(); - - // inputs for new nodes - m1->add_req(n_region, n_src1, n_src2); - m2->add_req(n_region); - m2->add_prec(m1); - - // operands for new nodes - m1->_opnds[0] = op_crx; - m1->_opnds[1] = op_src1; - m1->_opnds[2] = op_src2; - m2->_opnds[0] = op_crx; - - // registers for new nodes - ra_->set_pair(m1->_idx, ra_->get_reg_second(this), ra_->get_reg_first(this)); // crx - ra_->set_pair(m2->_idx, ra_->get_reg_second(this), ra_->get_reg_first(this)); // crx - - // Insert new nodes. - nodes->push(m1); - nodes->push(m2); + format %{ "CMPD $crx, $src1, $src2" %} + size(16); + ins_encode %{ + Label done; + __ fcmpu($crx$$CondRegister, $src1$$FloatRegister, $src2$$FloatRegister); + __ bns($crx$$CondRegister, done); + __ li(R0, 0); + __ cmpwi($crx$$CondRegister, R0, 1); + __ bind(done); %} + ins_pipe(pipe_class_default); %} // Compare double, generate -1,0,1 From cc83fbd132cf4b121e9554255241eb175715865b Mon Sep 17 00:00:00 2001 From: Alexey Semenyuk Date: Mon, 29 Jun 2026 12:37:38 +0000 Subject: [PATCH 021/305] 8387306: Replace InputStream#read(byte[]) with InputStream#readNBytes(int) in RtfConverter.isRtfFile() Reviewed-by: almatvee, aturbanov --- .../classes/jdk/jpackage/internal/RtfConverter.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/RtfConverter.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/RtfConverter.java index a0ff70066b9..404185ce832 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/RtfConverter.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/RtfConverter.java @@ -46,11 +46,11 @@ sealed interface RtfConverter { } try (InputStream fin = Files.newInputStream(path)) { - byte[] firstBits = new byte[7]; + byte[] firstBits = fin.readNBytes(Details.RTF_HEADER.length()); - if (fin.read(firstBits) == firstBits.length) { + if (Details.RTF_HEADER.length() == firstBits.length) { String header = new String(firstBits); - return "{\\rtf1\\".equals(header); + return Details.RTF_HEADER.equals(header); } } @@ -136,5 +136,6 @@ sealed interface RtfConverter { } } + private static final String RTF_HEADER = "{\\rtf1\\"; } } From f740f7c66bbf2dc16dbee965df0b7e5859594fc8 Mon Sep 17 00:00:00 2001 From: Daniel Fuchs Date: Mon, 29 Jun 2026 14:18:48 +0000 Subject: [PATCH 022/305] 8386985: PacketSpaceManagerTest failed with AssertionError; A race condition may cause packetSent to mistakenly skip rescheduling of the transmitter task Reviewed-by: djelinski --- .../net/http/quic/PacketSpaceManager.java | 23 ++--- .../quic/PacketSpaceManagerTest.java | 88 ++++++++++++++++--- 2 files changed, 82 insertions(+), 29 deletions(-) diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/quic/PacketSpaceManager.java b/src/java.net.http/share/classes/jdk/internal/net/http/quic/PacketSpaceManager.java index 487a8a186f6..f2991c0738e 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/quic/PacketSpaceManager.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/quic/PacketSpaceManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -645,22 +645,15 @@ public sealed class PacketSpaceManager implements PacketSpace return false; } - boolean hasNoDeadline() { - return Deadline.MAX.equals(nextDeadline); - } - // reschedule this task void reschedule() { Deadline deadline = computeNextDeadline(); - Deadline nextDeadline = this.nextDeadline; if (Deadline.MAX.equals(deadline)) { - debug.log("no deadline, don't reschedule"); - } else if (deadline.equals(nextDeadline)) { - debug.log("deadline unchanged, don't reschedule"); - } else { - packetEmitter.reschedule(this, deadline); - debug.log("retransmission task: rescheduled"); + if (debug.on()) debug.log("no deadline, don't reschedule"); + return; } + if (debug.on()) debug.log("retransmission task: rescheduled"); + packetEmitter.reschedule(this, deadline); } @Override @@ -1304,7 +1297,7 @@ public sealed class PacketSpaceManager implements PacketSpace } finally { transferLock.unlock(); } - if (found && packetTransmissionTask.hasNoDeadline()) { + if (found) { packetTransmissionTask.reschedule(); } if (!found) { @@ -1340,9 +1333,7 @@ public sealed class PacketSpaceManager implements PacketSpace return; } addAcknowledgement(pending); - if (packetTransmissionTask.hasNoDeadline()) { - packetTransmissionTask.reschedule(); - } + packetTransmissionTask.reschedule(); } finally { transferLock.unlock(); } diff --git a/test/jdk/java/net/httpclient/quic/PacketSpaceManagerTest.java b/test/jdk/java/net/httpclient/quic/PacketSpaceManagerTest.java index 0a363e104ae..3a33bbcf95d 100644 --- a/test/jdk/java/net/httpclient/quic/PacketSpaceManagerTest.java +++ b/test/jdk/java/net/httpclient/quic/PacketSpaceManagerTest.java @@ -80,16 +80,19 @@ import javax.net.ssl.SSLSession; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; /* * @test + * @bug 8349910 8386985 * @summary tests the logic to build an AckFrame * @library /test/lib * @library ../debug @@ -102,6 +105,7 @@ import org.junit.jupiter.params.provider.MethodSource; * @run junit/othervm -Dseed=-4159871071396382784 ${test.main.class} * @run junit/othervm -Dseed=2252276218459363615 ${test.main.class} * @run junit/othervm -Dseed=-5130588140709404919 ${test.main.class} + * @run junit/othervm -Dseed=4257295716830862528 ${test.main.class} */ // -Djdk.internal.httpclient.debug=true public class PacketSpaceManagerTest { @@ -766,6 +770,26 @@ public class PacketSpaceManagerTest { } } + // Sends a trivial INITIAL packet, with a CRYPTO frame containing + // a payload of length 1 for the provided offset. If ackFrameToSend + // is not null it will be included in the packet. + public List sendPacket(long offset, AckFrame ackFrameToSend, Packet packet, long largestReceivedAckedPN) { + // add a crypto frame and build the packet + CryptoFrame crypto = new CryptoFrame(offset, 1, + ByteBuffer.wrap(new byte[] {nextByte(offset)})); + List frames = ackFrameToSend == null ? + List.of(crypto) : List.of(crypto, ackFrameToSend); + QuicPacket newPacket = codingContext.encoder + .newInitialPacket(localId, peerId, + null, + packet.packetNumber, + largestReceivedAckedPN, + frames, codingContext); + // pretend that we sent a packet + manager.packetSent(newPacket, -1, packet.packetNumber); + return frames; + } + /** * Drives the test by pretending to emit each packet in order, * then pretending to receive ack frames (as soon as possible @@ -830,19 +854,8 @@ public class PacketSpaceManagerTest { debug.log("largestAckSent is: " + largestAckAcked); } - // add a crypto frame and build the packet - CryptoFrame crypto = new CryptoFrame(offset, 1, - ByteBuffer.wrap(new byte[] {nextByte(offset)})); - List frames = ackFrameToSend == null ? - List.of(crypto) : List.of(crypto, ackFrameToSend); - QuicPacket newPacket = codingContext.encoder - .newInitialPacket(localId, peerId, - null, - packet.packetNumber, - largestReceivedAckedPN, - frames, codingContext); - // pretend that we sent a packet - manager.packetSent(newPacket, -1, packet.packetNumber); + // send a packet + List frames = sendPacket(offset, ackFrameToSend, packet, largestReceivedAckedPN); // compute next deadline var nextDeadline = timerQueue.nextDeadline(); @@ -1125,4 +1138,53 @@ public class PacketSpaceManagerTest { driver.check(); } + @Test + public void testPacketSent() throws Exception { + // this test case is specifically for JDK-8386985 + System.out.printf("%n ------- testPacketSent ------- %n"); + + // create a minimal SynchronousTestDriver + TestCase testCase = new TestCase(List.of(new Acknowledged(1, 3), new Acknowledged(4,4)), + List.of(new Packet(3, 0), new Packet(4, 0))); + SynchronousTestDriver driver = new SynchronousTestDriver(testCase); + + // send a first ack-eliciting packet, and move the timeline past PTO + driver.sendPacket(0, null, new Packet(1, 0), -1); + Deadline pto = driver.manager.nextScheduledDeadline(); // should be PTO + driver.timeSource.advance(driver.timeSource.instant().until(pto, ChronoUnit.MILLIS) + 250, ChronoUnit.MILLIS); + + // start processing events, but delay the task that will run the transmitter + ArrayList tasks = new ArrayList<>(); + Executor executor = new Executor() { + @Override + public void execute(Runnable command) { + tasks.add(command); + } + }; + driver.timerQueue.processEventsAndReturnNextDeadline(driver.timeSource.instant(), executor); + + // acknowledge the first packet so that it's no longer pending retransmission + driver.manager.processAckFrame(new AckFrameBuilder().addAck(1).build()); + + // send a second packet, and examine the timerQueue next deadline + // if sending the second packet didn't cause the task to be rescheduled, we + // will observe Deadline.MAX, or a deadline before now: that's the bug. + driver.sendPacket(1, null, new Packet(2, 0), -1); + + Deadline next = driver.timerQueue.nextDeadline(); + assertNotEquals(next, Deadline.MAX); + assertTrue(next.isAfter(driver.timeSource.instant())); + + // now finish running the task and ack the second packet, + // so that we leave the packet space manager in a clean state + // for running the driver with the next two packets. + for (Runnable task : tasks) { + task.run(); + } + driver.manager.processAckFrame(new AckFrameBuilder() + .addAck(1).addAck(2).build()); + driver.run(); + driver.check(); + } + } From 17f2e11fe400ade68ee3b0ac4706209aae4e14dd Mon Sep 17 00:00:00 2001 From: Daniel Fuchs Date: Mon, 29 Jun 2026 14:26:50 +0000 Subject: [PATCH 023/305] 8386989: QuicEndpoint.ClosedConnection should not use QuicTimerQueue::offer Reviewed-by: djelinski --- .../internal/net/http/quic/QuicEndpoint.java | 11 ++--- .../net/http/quic/QuicTimerQueue.java | 45 +++++++------------ .../H3MultipleConnectionsToSameHost.java | 2 +- 3 files changed, 23 insertions(+), 35 deletions(-) diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicEndpoint.java b/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicEndpoint.java index 3dee814e1f1..18fd7717d6d 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicEndpoint.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicEndpoint.java @@ -1788,9 +1788,10 @@ public abstract sealed class QuicEndpoint implements AutoCloseable if (more > 16) { // the server doesn't seem to take into account our // connection close frame. Just stop responding - updatedDeadline = Deadline.MIN; + updated = updatedDeadline = Deadline.MIN; } else { - updatedDeadline = updated.plusMillis(maxIdleTimeMs); + updated = updatedDeadline = timeSource().instant() + .plusMillis(maxIdleTimeMs); } handleIncoming(source, destConnId, headersType, buffer); } else { @@ -1798,7 +1799,7 @@ public abstract sealed class QuicEndpoint implements AutoCloseable dropIncoming(source, destConnId, headersType, buffer); } - timer().reschedule(this, updatedDeadline); + timer().reschedule(this, updated); } protected void handleIncoming(SocketAddress source, ByteBuffer idbytes, @@ -1821,8 +1822,8 @@ public abstract sealed class QuicEndpoint implements AutoCloseable } public final void startTimer() { - deadline = updatedDeadline = timeSource().instant().plusMillis(maxIdleTimeMs); - timer().offer(this); + Deadline deadline = updatedDeadline = timeSource().instant().plusMillis(maxIdleTimeMs); + timer().reschedule(this, deadline); } @Override diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicTimerQueue.java b/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicTimerQueue.java index 830415593cb..bbb88cf1c45 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicTimerQueue.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/quic/QuicTimerQueue.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -74,6 +74,9 @@ public final class QuicTimerQueue { private volatile Deadline scheduledDeadline = Deadline.MAX; private volatile Deadline returnedDeadline = Deadline.MAX; + // Not volatile: never accessed without holding monitor + private Deadline notifiedDeadline = Deadline.MAX; + /** * Creates a new timer queue with the given notifier. * A notifier is used to notify the timer thread that @@ -113,33 +116,8 @@ public final class QuicTimerQueue { * @param event an event to be scheduled */ public void offer(QuicTimedEvent event) { - if (event instanceof Marker marker) - throw new IllegalArgumentException(marker.name()); - assert QuicTimedEvent.COMPARATOR.compare(event, FLOOR) > 0; - assert QuicTimedEvent.COMPARATOR.compare(event, CEILING) < 0; - Deadline deadline = event.deadline(); - scheduled.add(event); - scheduled(deadline); if (debug.on()) debug.log("QuicTimerQueue: event %s offered", event); - if (notify(deadline)) { - if (debug.on()) debug.log("QuicTimerQueue: event %s will be rescheduled", event); - if (Log.quicTimer()) { - var now = debugNow(); - Log.logQuic(String.format("%s: QuicTimerQueue: event %s will be scheduled" + - " at %s (returned deadline: %s, nextDeadline: %s)", - Thread.currentThread().getName(), event, d(now, deadline), - d(now, returnedDeadline), d(now, nextDeadline()))); - } - notifier.run(); - } else { - if (Log.quicTimer()) { - var now = debugNow(); - Log.logQuic(String.format("%s: QuicTimerQueue: event %s will not be scheduled" + - " at %s (returned deadline: %s, nextDeadline: %s)", - Thread.currentThread().getName(), event, d(now, deadline), - d(now, returnedDeadline), d(now, nextDeadline()))); - } - } + reschedule(event, event.deadline()); } /** @@ -181,7 +159,7 @@ public final class QuicTimerQueue { int drained = 0; int dues; synchronized (this) { - scheduledDeadline = Deadline.MAX; + scheduledDeadline = returnedDeadline = notifiedDeadline = Deadline.MAX; } // moved scheduled / rescheduled tasks to due, until // nothing else is due. Then process dues. @@ -347,7 +325,16 @@ public final class QuicTimerQueue { synchronized (this) { if (deadline.isBefore(nextDeadline()) || deadline.isBefore(returnedDeadline)) { - return true; + // notifiedDeadline will be reset to MAX first thing in + // processEventAndReturnNextDeadline; We do not want + // to call the notifier (wake the selector) again if it's + // been already called for a notifiedDeadline <= to deadline; + // On the other hand, if deadline < notifiedDeadline, we + // need to call the notifier to force an additional wakeup + if (deadline.isBefore(notifiedDeadline)) { + notifiedDeadline = deadline; + return true; + } } } return false; diff --git a/test/jdk/java/net/httpclient/http3/H3MultipleConnectionsToSameHost.java b/test/jdk/java/net/httpclient/http3/H3MultipleConnectionsToSameHost.java index c38671e65b8..ff8e3804996 100644 --- a/test/jdk/java/net/httpclient/http3/H3MultipleConnectionsToSameHost.java +++ b/test/jdk/java/net/httpclient/http3/H3MultipleConnectionsToSameHost.java @@ -77,7 +77,7 @@ */ /* * @test id=useNioSelector - * @bug 8087112 8372409 + * @bug 8087112 8372409 8386989 * @library /test/lib /test/jdk/java/net/httpclient/lib * @build jdk.test.lib.net.SimpleSSLContext * jdk.httpclient.test.lib.http2.Http2TestServer From e28a58b48606c6bbb13c8fc9ad37225fca0e5442 Mon Sep 17 00:00:00 2001 From: Ashutosh Mehra Date: Mon, 29 Jun 2026 14:45:38 +0000 Subject: [PATCH 024/305] 8386656: C2 AVX512: -XX:-UseCountTrailingZerosInstruction causes assert(UseCountTrailingZerosInstruction) failed: tzcnt instruction not supported Reviewed-by: kvn, epeter, mhaessig, adinn --- src/hotspot/cpu/x86/x86.ad | 7 +++ .../TestUseCountTrailingZerosInstruction.java | 53 +++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 test/hotspot/jtreg/compiler/cpuflags/TestUseCountTrailingZerosInstruction.java diff --git a/src/hotspot/cpu/x86/x86.ad b/src/hotspot/cpu/x86/x86.ad index 370437edee2..df035f39f58 100644 --- a/src/hotspot/cpu/x86/x86.ad +++ b/src/hotspot/cpu/x86/x86.ad @@ -3179,6 +3179,13 @@ bool Matcher::match_rule_supported(int opcode) { break; case Op_VectorCmpMasked: + if (!UseCountTrailingZerosInstruction) { + return false; + } + if (UseAVX < 3 || !VM_Version::supports_bmi2()) { + return false; + } + break; case Op_VectorMaskGen: if (UseAVX < 3 || !VM_Version::supports_bmi2()) { return false; diff --git a/test/hotspot/jtreg/compiler/cpuflags/TestUseCountTrailingZerosInstruction.java b/test/hotspot/jtreg/compiler/cpuflags/TestUseCountTrailingZerosInstruction.java new file mode 100644 index 00000000000..0c7d04486f2 --- /dev/null +++ b/test/hotspot/jtreg/compiler/cpuflags/TestUseCountTrailingZerosInstruction.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8386656 + * @summary Verify no assertions when running with -XX:-UseCountTrailingZerosInstruction + * @requires os.simpleArch == "x64" + * @run main/othervm -Xbatch -XX:-UseCountTrailingZerosInstruction ${test.main.class} + */ + +/** + * @test + * @bug 8386656 + * @summary Verify no assertions when running with -XX:+UseCountTrailingZerosInstruction + * @requires os.simpleArch == "x64" + * @run main/othervm -Xbatch -XX:+UseCountTrailingZerosInstruction ${test.main.class} + */ + +package compiler.cpuflags; + +import java.util.Arrays; + +public class TestUseCountTrailingZerosInstruction { + public static void main(String[] args) { + byte[] a = new byte[32]; + byte[] b = new byte[32]; + for (int i = 0; i < 20_000; i++) { + Arrays.mismatch(a, b); + } + } +} + From 0a5b9d7fd4d2df6a3003f0586702f6d25d5c0559 Mon Sep 17 00:00:00 2001 From: Kieran Farrell Date: Mon, 29 Jun 2026 15:28:18 +0000 Subject: [PATCH 025/305] 8387273: Enhance httpserver logging to log when maxConnections is reached Reviewed-by: jpai, dfuchs, vyazici --- .../share/classes/sun/net/httpserver/ServerImpl.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java index 94fe78b9c64..3d77a61c0be 100644 --- a/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java +++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java @@ -538,6 +538,8 @@ class ServerImpl { if (MAX_CONNECTIONS > 0 && allConnections.size() >= MAX_CONNECTIONS) { // we've hit max limit of current open connections, so we go // ahead and close this connection without processing it + logger.log(Level.DEBUG, "connection limit reached, " + + "closing accepted connection " + chan); try { chan.close(); } catch (IOException ignore) { From db24b35a30cd28fda1f37d5e5e4a7241bdeeff3c Mon Sep 17 00:00:00 2001 From: Justin Lu Date: Mon, 29 Jun 2026 16:27:54 +0000 Subject: [PATCH 026/305] 8387259: Clarify extlang in Locale composition description Reviewed-by: naoto, iris --- src/java.base/share/classes/java/util/Locale.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/java.base/share/classes/java/util/Locale.java b/src/java.base/share/classes/java/util/Locale.java index f600afbb007..462cd5755c2 100644 --- a/src/java.base/share/classes/java/util/Locale.java +++ b/src/java.base/share/classes/java/util/Locale.java @@ -120,9 +120,13 @@ import sun.util.locale.provider.TimeZoneNameUtility; * {@code Locale} always canonicalizes to lower case. * *
Syntax: Well-formed {@code language} values have the form {@code [a-zA-Z]{2,8}}.
- *
BCP 47 deviation: this is not the full BCP 47 language production, since it excludes + *
BCP 47 deviation: {@code Locale} does not retain the * extlang - * (as modern three-letter language codes are preferred).
+ * subtag. This is because three-letter language codes are preferred over extlang + * subtags. When a {@code Locale} is created from a language tag containing an + * extlang subtag, the first extlang subtag is interpreted as the language + * field. The primary language subtag and any subsequent extlang subtags + * are ignored. * *
Example: "en" (English), "ja" (Japanese), "kok" (Konkani)
* From bc2fa43a6471cd04602f380e0f7a3974d829fa51 Mon Sep 17 00:00:00 2001 From: Mikhailo Seledtsov Date: Mon, 29 Jun 2026 16:45:57 +0000 Subject: [PATCH 027/305] 8387315: Add macosx-aarch64 bootcycle build profiles Reviewed-by: mikael, erikj --- make/conf/jib-profiles.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/make/conf/jib-profiles.js b/make/conf/jib-profiles.js index b425c66a34c..32f07325c05 100644 --- a/make/conf/jib-profiles.js +++ b/make/conf/jib-profiles.js @@ -644,7 +644,7 @@ var getJibProfilesProfiles = function (input, common, data) { // Bootcycle profiles runs the build with itself as the boot jdk. This can // be done in two ways. Either using the builtin bootcycle target in the // build system. Or by supplying the main jdk build as bootjdk to configure. - [ "linux-x64", "macosx-x64", "windows-x64", "linux-aarch64" ] + [ "linux-x64", "macosx-aarch64", "macosx-x64", "windows-x64", "linux-aarch64" ] .forEach(function (name) { var bootcycleName = name + "-bootcycle"; var bootcyclePrebuiltName = name + "-bootcycle-prebuilt"; From 9d65845a6bc2183afc7f56fe9ebcdd3d2531fe6a Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Mon, 29 Jun 2026 17:00:20 +0000 Subject: [PATCH 028/305] 8387293: Shenandoah: Improve gc+stats logging for generational mode Reviewed-by: phh, xpeng --- .../gc/shenandoah/shenandoahGenerationalEvacuationTask.cpp | 2 ++ src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalEvacuationTask.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalEvacuationTask.cpp index 750022b274e..54ab4c27038 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalEvacuationTask.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalEvacuationTask.cpp @@ -62,10 +62,12 @@ ShenandoahGenerationalEvacuationTask::ShenandoahGenerationalEvacuationTask(Shena void ShenandoahGenerationalEvacuationTask::work(uint worker_id) { if (_concurrent) { + ShenandoahWorkerTimingsTracker timer(ShenandoahPhaseTimings::conc_evac, ShenandoahPhaseTimings::Work, worker_id, true); ShenandoahConcurrentWorkerSession worker_session(worker_id); SuspendibleThreadSetJoiner stsj; do_work(); } else { + ShenandoahWorkerTimingsTracker timer(ShenandoahPhaseTimings::degen_gc_evac, ShenandoahPhaseTimings::Work, worker_id, true); ShenandoahParallelWorkerSession worker_session(worker_id); do_work(); } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp index e7638ed15c7..31129182380 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahGenerationalHeap.cpp @@ -722,10 +722,12 @@ public: void work(uint worker_id) override { if (CONCURRENT) { + ShenandoahWorkerTimingsTracker timer(ShenandoahPhaseTimings::conc_update_refs, ShenandoahPhaseTimings::Work, worker_id, true); ShenandoahConcurrentWorkerSession worker_session(worker_id); SuspendibleThreadSetJoiner stsj; do_work(worker_id); } else { + ShenandoahWorkerTimingsTracker timer(ShenandoahPhaseTimings::degen_gc_update_refs, ShenandoahPhaseTimings::Work, worker_id, true); ShenandoahParallelWorkerSession worker_session(worker_id); do_work(worker_id); } From 58f118dd1c541a69b5c839609d026865a3365101 Mon Sep 17 00:00:00 2001 From: Volodymyr Paprotski Date: Mon, 29 Jun 2026 19:11:12 +0000 Subject: [PATCH 029/305] 8386911: Crypto benchmark regressions after JDK-8384353 Reviewed-by: weijun, semery --- .../com/sun/crypto/provider/ML_KEM.java | 2 +- .../classes/sun/security/provider/ML_DSA.java | 4 +-- .../sun/security/provider/SHA3Parallel.java | 27 ++++++++++++++++--- 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM.java b/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM.java index 96a1eb686cc..5335357f8b9 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM.java @@ -858,7 +858,7 @@ public final class ML_KEM { allDone = false; while (!allDone) { allDone = true; - parXof.squeezeBlock(); + parXof.squeezeBlock(parInd); for (int k = 0; k < parInd; k++) { int parsedOfs = 0; int tmp; diff --git a/src/java.base/share/classes/sun/security/provider/ML_DSA.java b/src/java.base/share/classes/sun/security/provider/ML_DSA.java index 9c4e2c898b6..e1b41817435 100644 --- a/src/java.base/share/classes/sun/security/provider/ML_DSA.java +++ b/src/java.base/share/classes/sun/security/provider/ML_DSA.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -1184,7 +1184,7 @@ public class ML_DSA { allDone = false; while (!allDone) { allDone = true; - parXof.squeezeBlock(); + parXof.squeezeBlock(parInd); for (int k = 0; k < parInd; k++) { int parsedOfs = 0; int tmp; diff --git a/src/java.base/share/classes/sun/security/provider/SHA3Parallel.java b/src/java.base/share/classes/sun/security/provider/SHA3Parallel.java index caf6a7a2899..0fcc91542fa 100644 --- a/src/java.base/share/classes/sun/security/provider/SHA3Parallel.java +++ b/src/java.base/share/classes/sun/security/provider/SHA3Parallel.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -80,9 +80,28 @@ public class SHA3Parallel { } } - public int squeezeBlock() { - int retVal = quadKeccak(lanesArr[0], lanesArr[1], lanesArr[2], lanesArr[3]); - for (int i = 0; i < NRPAR; i++) { + public int squeezeBlock(int nr) throws InvalidAlgorithmParameterException { + int retVal = 0; + switch (nr) { + case 1: + // until we enable single keccak intrinsic, use the better + // doubleKeccak + case 2: + retVal = doubleKeccak(lanesArr[0], lanesArr[1]); + break; + case 3: + // until we enable single keccak intrinsic, use the better + // doubleKeccak/quadKeccak + case 4: + retVal = quadKeccak(lanesArr[0], lanesArr[1], lanesArr[2], + lanesArr[3]); + break; + default: + throw new InvalidAlgorithmParameterException( + "Bad parallel parameter."); + } + + for (int i = 0; i < nr; i++) { l2bLittle(lanesArr[i], 0, buffers[i], 0, blockSize); } return retVal; From c2348e645201b86eccae2646ec01d668a17c5271 Mon Sep 17 00:00:00 2001 From: Srinivas Vamsi Parasa Date: Mon, 29 Jun 2026 20:15:49 +0000 Subject: [PATCH 030/305] 8386448: Enable dumping of AVX registers (YMM/ZMM and K registers) in JVM fatal error logs Reviewed-by: kvn, drwhite, sviswanathan --- src/hotspot/cpu/x86/vm_version_x86.cpp | 19 +++ src/hotspot/cpu/x86/vm_version_x86.hpp | 11 ++ src/hotspot/os_cpu/linux_x86/os_linux_x86.cpp | 132 +++++++++++++++- .../ErrorHandling/TestAVXRegisterDump.java | 142 ++++++++++++++++++ 4 files changed, 299 insertions(+), 5 deletions(-) create mode 100644 test/hotspot/jtreg/runtime/ErrorHandling/TestAVXRegisterDump.java diff --git a/src/hotspot/cpu/x86/vm_version_x86.cpp b/src/hotspot/cpu/x86/vm_version_x86.cpp index 2ca1c172542..53696ee6ef3 100644 --- a/src/hotspot/cpu/x86/vm_version_x86.cpp +++ b/src/hotspot/cpu/x86/vm_version_x86.cpp @@ -489,6 +489,25 @@ class VM_Version_StubGenerator: public StubCodeGenerator { __ jmp(wrapup); __ bind(start_simd_check); + // Query CPUID 0xD sub-leaf 5, 6, and 7 offsets for AVX-512 XSAVE components + __ movl(rax, 0xD); + __ movl(rcx, 5); + __ cpuid(); + __ lea(rsi, Address(rbp, in_bytes(VM_Version::opmask_xstate_offset_offset()))); + __ movl(Address(rsi, 0), rbx); + + __ movl(rax, 0xD); + __ movl(rcx, 6); + __ cpuid(); + __ lea(rsi, Address(rbp, in_bytes(VM_Version::zmm0to15_hi256_xstate_offset_offset()))); + __ movl(Address(rsi, 0), rbx); + + __ movl(rax, 0xD); + __ movl(rcx, 7); + __ cpuid(); + __ lea(rsi, Address(rbp, in_bytes(VM_Version::zmm16to31_xstate_offset_offset()))); + __ movl(Address(rsi, 0), rbx); + // // Some OSs have a bug when upper 128/256bits of YMM/ZMM // registers are not restored after a signal processing. diff --git a/src/hotspot/cpu/x86/vm_version_x86.hpp b/src/hotspot/cpu/x86/vm_version_x86.hpp index 2fb1af71a10..d268665d091 100644 --- a/src/hotspot/cpu/x86/vm_version_x86.hpp +++ b/src/hotspot/cpu/x86/vm_version_x86.hpp @@ -683,6 +683,11 @@ protected: uint32_t apx_xstate_size; // EAX: size of APX state (128) uint32_t apx_xstate_offset; // EBX: offset in standard XSAVE area + // cpuid function 0xD, subleaf 5, 6 and 7 (AVX-512 extended state) + uint32_t opmask_xstate_offset; // EBX: offset of Opmask component + uint32_t zmm0to15_hi256_xstate_offset; // EBX: offset of ZMM_Hi256 component + uint32_t zmm16to31_xstate_offset; // EBX: offset of Hi16_ZMM component + VM_Features feature_flags() const; // Asserts @@ -748,9 +753,15 @@ public: static ByteSize apx_save_offset() { return byte_offset_of(CpuidInfo, apx_save); } static ByteSize apx_xstate_offset_offset() { return byte_offset_of(CpuidInfo, apx_xstate_offset); } static ByteSize apx_xstate_size_offset() { return byte_offset_of(CpuidInfo, apx_xstate_size); } + static ByteSize opmask_xstate_offset_offset() { return byte_offset_of(CpuidInfo, opmask_xstate_offset); } + static ByteSize zmm0to15_hi256_xstate_offset_offset() { return byte_offset_of(CpuidInfo, zmm0to15_hi256_xstate_offset); } + static ByteSize zmm16to31_xstate_offset_offset() { return byte_offset_of(CpuidInfo, zmm16to31_xstate_offset); } static uint32_t apx_xstate_offset() { return _cpuid_info.apx_xstate_offset; } static uint32_t apx_xstate_size() { return _cpuid_info.apx_xstate_size; } + static uint32_t opmask_xstate_offset() { return _cpuid_info.opmask_xstate_offset; } + static uint32_t zmm0to15_hi256_xstate_offset() { return _cpuid_info.zmm0to15_hi256_xstate_offset; } + static uint32_t zmm16to31_xstate_offset() { return _cpuid_info.zmm16to31_xstate_offset; } // The value used to check ymm register after signal handle static int ymm_test_value() { return 0xCAFEBABE; } diff --git a/src/hotspot/os_cpu/linux_x86/os_linux_x86.cpp b/src/hotspot/os_cpu/linux_x86/os_linux_x86.cpp index 6750b71476b..25ee449d8b1 100644 --- a/src/hotspot/os_cpu/linux_x86/os_linux_x86.cpp +++ b/src/hotspot/os_cpu/linux_x86/os_linux_x86.cpp @@ -381,9 +381,22 @@ size_t os::Posix::default_stack_size(os::ThreadType thr_type) { ///////////////////////////////////////////////////////////////////////////// // helper functions for fatal error handler +// XSAVE Buffer Layout (Intel SDM Vol. 1, Section 13.4.1) +// Bytes 0-511: Legacy x87/FPU and SSE state (includes XMM0-15) +// Bytes 512-575: XSAVE Header (64 bytes) +// Bytes 576-831: YMMH state (upper 128 bits of YMM0-15) +// YMMH[i] at: buffer + 576 + (i * 16) +// Bytes 832+: Extended state components (e.g., AVX-512, APX, etc.). +// Component offsets and sizes are +// enumerated by CPUID.(EAX=0xD, ECX=n). // XSAVE constants - from Intel SDM Vol. 1, Chapter 13 #define XSAVE_HDR_OFFSET 512 +#define XSAVE_HDR_SIZE 64 #define XFEATURE_APX (1ULL << 19) +#define XFEATURE_YMM (1ULL << 2) +#define XFEATURE_OPMASK (1ULL << 5) +#define XFEATURE_ZMM_HI256 (1ULL << 6) +#define XFEATURE_HI16_ZMM (1ULL << 7) // XSAVE header structure // See: Intel SDM Vol. 1, Section 13.4.2 "XSAVE Header" @@ -417,6 +430,118 @@ static apx_state* get_apx_state(const ucontext_t* uc) { return (apx_state*)(xsave + offset); } +static void print_xmm_registers(outputStream* st, const ucontext_t* uc) { + for (int i = 0; i < 16; ++i) { + const uint64_t* xmm = (const uint64_t*)&uc->uc_mcontext.fpregs->_xmm[i]; + st->print_cr("XMM[%d]=" INTPTR_FORMAT " " INTPTR_FORMAT, i, xmm[1], xmm[0]); + } +} + +static void print_ymm_registers(outputStream* st, const ucontext_t* uc, bool has_ymm_hi128) { + const char* xsave = (const char*)uc->uc_mcontext.fpregs; + for (int i = 0; i < 16; ++i) { + const uint64_t* xmm = (const uint64_t*)&uc->uc_mcontext.fpregs->_xmm[i]; + uint64_t values[4] = {xmm[0], xmm[1], 0, 0}; + if (has_ymm_hi128) { + const uint64_t* ymmh = (const uint64_t*)(xsave + XSAVE_HDR_OFFSET + XSAVE_HDR_SIZE + (i * 16)); + values[2] = ymmh[0]; + values[3] = ymmh[1]; + } + st->print("YMM[%d]=", i); + for (int j = 3; j >= 0; --j) { + st->print("%s" INTPTR_FORMAT, (j == 3) ? "" : " ", values[j]); + } + st->cr(); + } +} + +static void print_zmm_registers(outputStream* st, const ucontext_t* uc, bool has_ymm_hi128, + bool has_zmm_hi256, bool has_hi16_zmm) { + const char* xsave = (const char*)uc->uc_mcontext.fpregs; + + for (int i = 0; i < 32; ++i) { + uint64_t values[8] = {0, 0, 0, 0, 0, 0, 0, 0}; + + if (i < 16) { + const uint64_t* xmm = (const uint64_t*)&uc->uc_mcontext.fpregs->_xmm[i]; + values[0] = xmm[0]; + values[1] = xmm[1]; + + if (has_ymm_hi128) { + const uint64_t* ymmh = (const uint64_t*)(xsave + XSAVE_HDR_OFFSET + XSAVE_HDR_SIZE + (i * 16)); + values[2] = ymmh[0]; + values[3] = ymmh[1]; + } + + if (has_zmm_hi256) { + const uint32_t zmm_hi256_offset = VM_Version::zmm0to15_hi256_xstate_offset(); + const uint64_t* zmm_hi256 = (const uint64_t*)(xsave + zmm_hi256_offset + (i * 32)); + values[4] = zmm_hi256[0]; + values[5] = zmm_hi256[1]; + values[6] = zmm_hi256[2]; + values[7] = zmm_hi256[3]; + } + } else if (has_hi16_zmm) { + const uint32_t hi16_zmm_offset = VM_Version::zmm16to31_xstate_offset(); + const uint64_t* zmm = (const uint64_t*)(xsave + hi16_zmm_offset + ((i - 16) * 64)); + values[0] = zmm[0]; + values[1] = zmm[1]; + values[2] = zmm[2]; + values[3] = zmm[3]; + values[4] = zmm[4]; + values[5] = zmm[5]; + values[6] = zmm[6]; + values[7] = zmm[7]; + } + + st->print("ZMM[%d]=", i); + for (int j = 7; j >= 0; --j) { + st->print("%s" INTPTR_FORMAT, (j == 7) ? "" : " ", values[j]); + } + st->cr(); + } +} + +static void print_kmask_registers(outputStream* st, const ucontext_t* uc, bool has_opmask) { + const uint32_t opmask_offset = VM_Version::opmask_xstate_offset(); + if (!has_opmask || opmask_offset == 0) { + return; + } + + const char* xsave = (const char*)uc->uc_mcontext.fpregs; + const uint64_t* kmask = (const uint64_t*)(xsave + opmask_offset); + + for (int i = 0; i < 8; ++i) { + st->print_cr("K[%d]=" INTPTR_FORMAT, i, kmask[i]); + } + st->cr(); +} + +static void print_vector_registers(outputStream* st, const ucontext_t* uc) { + if (uc->uc_mcontext.fpregs == nullptr) { + return; + } + + if (UseAVX < 2) { + return print_xmm_registers(st, uc); + } + + const char* xsave = (const char*)uc->uc_mcontext.fpregs; + const uint64_t* xstate_hdr_ptr = (const uint64_t*)(xsave + XSAVE_HDR_OFFSET); + const uint64_t xsave_state_bitmap = xstate_hdr_ptr[0]; + const bool has_ymm_hi128 = (xsave_state_bitmap & XFEATURE_YMM) != 0; + const bool has_opmask = (xsave_state_bitmap & XFEATURE_OPMASK) != 0; + const bool has_zmm_hi256 = (xsave_state_bitmap & XFEATURE_ZMM_HI256) != 0; + const bool has_hi16_zmm = (xsave_state_bitmap & XFEATURE_HI16_ZMM) != 0; + const bool should_print_zmm_registers = (UseAVX > 2) && (has_zmm_hi256 || has_hi16_zmm); + + if (!should_print_zmm_registers) { + return print_ymm_registers(st, uc, has_ymm_hi128); + } + + print_kmask_registers(st, uc, has_opmask); + print_zmm_registers(st, uc, has_ymm_hi128, has_zmm_hi256, has_hi16_zmm); +} void os::print_context(outputStream *st, const void *context) { if (context == nullptr) return; @@ -458,7 +583,7 @@ void os::print_context(outputStream *st, const void *context) { st->print(", ERR=" INTPTR_FORMAT, (intptr_t)uc->uc_mcontext.gregs[REG_ERR]); st->cr(); st->print(" TRAPNO=" INTPTR_FORMAT, (intptr_t)uc->uc_mcontext.gregs[REG_TRAPNO]); - // Add XMM registers + MXCSR. Note that C2 uses XMM to spill GPR values including pointers. + // Add vector registers + MXCSR. Note that C2 uses XMM to spill GPR values including pointers. st->cr(); st->cr(); // Sanity check: fpregs should point into the context. @@ -467,10 +592,7 @@ void os::print_context(outputStream *st, const void *context) { st->print_cr("bad uc->uc_mcontext.fpregs: " INTPTR_FORMAT " (uc: " INTPTR_FORMAT ")", p2i(uc->uc_mcontext.fpregs), p2i(uc)); } else { - for (int i = 0; i < 16; ++i) { - const int64_t* xmm_val_addr = (int64_t*)&(uc->uc_mcontext.fpregs->_xmm[i]); - st->print_cr("XMM[%d]=" INTPTR_FORMAT " " INTPTR_FORMAT, i, xmm_val_addr[1], xmm_val_addr[0]); - } + print_vector_registers(st, uc); st->print(" MXCSR=" UINT32_FORMAT_X_0, uc->uc_mcontext.fpregs->mxcsr); } st->cr(); diff --git a/test/hotspot/jtreg/runtime/ErrorHandling/TestAVXRegisterDump.java b/test/hotspot/jtreg/runtime/ErrorHandling/TestAVXRegisterDump.java new file mode 100644 index 00000000000..1f2fec74fee --- /dev/null +++ b/test/hotspot/jtreg/runtime/ErrorHandling/TestAVXRegisterDump.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary Test that YMM and ZMM registers are correctly dumped in hs_err for different UseAVX settings + * @library /test/lib + * @requires os.family == "linux" & os.arch == "amd64" + * @requires vm.debug == true + * @modules java.base/jdk.internal.misc + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run driver TestAVXRegisterDump + */ + +// Note: this test can only run on debug since it relies on VMError::controlled_crash() which +// only exists in debug builds. + +import java.io.File; +import java.util.regex.Pattern; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; +import jdk.test.whitebox.WhiteBox; + +public class TestAVXRegisterDump { + + public static void main(String[] args) throws Exception { + + if (args.length > 0 && args[0].equals("crash")) { + WhiteBox.getWhiteBox().controlledCrash(2); + throw new RuntimeException("Still alive?"); + } + + // Test UseAVX=1 (XMM only) + testWithUseAVX(1); + + // Test UseAVX=2 (YMM) + testWithUseAVX(2); + + // Test UseAVX=3 (ZMM + K masks if available) + testWithUseAVX(3); + } + + static void testWithUseAVX(int useAVX) throws Exception { + ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-Xbootclasspath/a:.", + "-XX:+UnlockDiagnosticVMOptions", + "-XX:+WhiteBoxAPI", + "-XX:UseAVX=" + useAVX, + "-XX:-CreateCoredumpOnCrash", + "-Xmx100M", + TestAVXRegisterDump.class.getName(), "crash"); + + OutputAnalyzer output = new OutputAnalyzer(pb.start()); + output.shouldMatch("# A fatal error has been detected by the Java Runtime Environment:.*"); + + File hsErrFile = HsErrFileUtils.openHsErrFileFromOutput(output); + validateRegisterContent(hsErrFile, useAVX); + } + + static Pattern[] createRegisterPatterns(String regType, int count) { + Pattern[] patterns = new Pattern[count]; + for (int i = 0; i < count; i++) { + // Create regex pattern to match entire register line (e.g., "XMM[0]=0xHEX 0xHEX") + // Used with Matcher.matches() which requires matching the entire line + patterns[i] = Pattern.compile(regType + "\\[" + i + "\\]=.*"); + } + return patterns; + } + + static void validateRegisterContent(File hsErrFile, int useAVX) throws Exception { + if (useAVX == 1) { + validateRegistersUseAVX1(hsErrFile); + } else if (useAVX == 2) { + validateRegistersUseAVX2(hsErrFile); + } else if (useAVX == 3) { + validateRegistersUseAVX3(hsErrFile); + } + } + + static void validateRegistersUseAVX1(File hsErrFile) throws Exception { + // UseAVX=1: XMM registers only (0-15) + Pattern[] positivePatterns = createRegisterPatterns("XMM", 16); + Pattern[] negativePatterns = new Pattern[] { + Pattern.compile("YMM\\[.*\\]=.*"), + Pattern.compile("ZMM\\[.*\\]=.*"), + }; + HsErrFileUtils.checkHsErrFileContent(hsErrFile, positivePatterns, negativePatterns, false, false); + } + + static void validateRegistersUseAVX2(File hsErrFile) throws Exception { + // UseAVX=2: YMM registers only (0-15) + Pattern[] positivePatterns = createRegisterPatterns("YMM", 16); + Pattern[] negativePatterns = new Pattern[] { + Pattern.compile("XMM\\[.*\\]=.*"), + Pattern.compile("ZMM\\[.*\\]=.*"), + }; + HsErrFileUtils.checkHsErrFileContent(hsErrFile, positivePatterns, negativePatterns, false, false); + } + + static void validateRegistersUseAVX3(File hsErrFile) throws Exception { + // UseAVX=3: ZMM + K masks (if available) or fallback to YMM + // Try ZMM first, then fallback to YMM if CPU doesn't support AVX-512 + try { + Pattern[] zmmPatterns = createRegisterPatterns("ZMM", 32); + Pattern[] zmmNegativePatterns = new Pattern[] { + Pattern.compile("XMM\\[.*\\]=.*"), + }; + HsErrFileUtils.checkHsErrFileContent(hsErrFile, zmmPatterns, zmmNegativePatterns, false, false); + + Pattern[] kPatterns = createRegisterPatterns("K", 8); + HsErrFileUtils.checkHsErrFileContent(hsErrFile, kPatterns, null, false, false); + } catch (RuntimeException e) { + // If ZMM not found, try YMM + Pattern[] ymmPatterns = createRegisterPatterns("YMM", 16); + Pattern[] ymmNegativePatterns = new Pattern[] { + Pattern.compile("XMM\\[.*\\]=.*"), + }; + HsErrFileUtils.checkHsErrFileContent(hsErrFile, ymmPatterns, ymmNegativePatterns, false, false); + } + } +} From 57f988d31ceaec97ded8de08b777daff01840e88 Mon Sep 17 00:00:00 2001 From: Kelvin Nilsen Date: Mon, 29 Jun 2026 20:32:45 +0000 Subject: [PATCH 031/305] 8386910: Shenandoah: remove redundant logging of free set status Reviewed-by: wkemper, xpeng, ruili --- src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp index 24748bdaab3..eddeca57fd1 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2021, 2022, 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. @@ -204,8 +204,6 @@ bool ShenandoahConcurrentGC::collect(GCCause::Cause cause) { // we will not age young-gen objects in the case that we skip evacuation. entry_cleanup_early(); - heap->free_set()->log_status_under_lock(); - // Processing strong roots // This may be skipped if there is nothing to update/evacuate. // If so, strong_root_in_progress would be unset. From 299a42b3ce51e093a77ab4691e125bc1a12ec474 Mon Sep 17 00:00:00 2001 From: Jorn Vernee Date: Mon, 29 Jun 2026 21:50:33 +0000 Subject: [PATCH 032/305] 8383882: javac: incremental compilation using --module misses classes Reviewed-by: jlahoda, mcimadamore, vromero --- .../com/sun/tools/javac/main/Arguments.java | 35 +-- .../tools/javac/resources/javac.properties | 2 +- .../IncrementalComp/TestIncrementalComp.java | 293 ++++++++++++++++++ .../tools/javac/modules/MOptionTest.java | 36 +-- 4 files changed, 327 insertions(+), 39 deletions(-) create mode 100644 test/langtools/tools/javac/IncrementalComp/TestIncrementalComp.java diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/main/Arguments.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/main/Arguments.java index 58beee78af2..9a0b75a3aa4 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/main/Arguments.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/main/Arguments.java @@ -32,7 +32,6 @@ import java.nio.file.Paths; import java.util.Arrays; import java.util.Collections; import java.util.EnumSet; -import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; @@ -278,12 +277,8 @@ public class Arguments { */ public Set getFileObjects() { if (fileObjects == null) { - fileObjects = new LinkedHashSet<>(); - } - if (files != null) { - JavacFileManager jfm = (JavacFileManager) getFileManager(); - for (JavaFileObject fo: jfm.getJavaFileObjectsFromPaths(files)) - fileObjects.add(fo); + // see Arguments::validate + throw new IllegalStateException("file objects have not been initialized"); } return fileObjects; } @@ -421,6 +416,9 @@ public class Arguments { */ public boolean validate() { JavaFileManager fm = getFileManager(); + if (fileObjects == null) { + fileObjects = new LinkedHashSet<>(); + } if (options.isSet(Option.MODULE)) { if (!fm.hasLocation(StandardLocation.CLASS_OUTPUT)) { log.error(Errors.OutputDirMustBeSpecifiedWithDashMOption); @@ -433,19 +431,10 @@ public class Arguments { Location sourceLoc = fm.getLocationForModule(StandardLocation.MODULE_SOURCE_PATH, module); if (sourceLoc == null) { log.error(Errors.ModuleNotFoundInModuleSourcePath(module)); - } else { - Location classLoc = fm.getLocationForModule(StandardLocation.CLASS_OUTPUT, module); - - for (JavaFileObject file : fm.list(sourceLoc, "", EnumSet.of(JavaFileObject.Kind.SOURCE), true)) { - String className = fm.inferBinaryName(sourceLoc, file); - JavaFileObject classFile = fm.getJavaFileForInput(classLoc, className, Kind.CLASS); - - if (classFile == null || classFile.getLastModified() < file.getLastModified()) { - if (fileObjects == null) - fileObjects = new HashSet<>(); - fileObjects.add(file); - } - } + return false; + } + for (JavaFileObject file : fm.list(sourceLoc, "", EnumSet.of(Kind.SOURCE), true)) { + fileObjects.add(file); } } } catch (IOException ex) { @@ -455,6 +444,12 @@ public class Arguments { } } } + if (files != null) { + JavacFileManager jfm = (JavacFileManager) getFileManager(); + for (JavaFileObject fo : jfm.getJavaFileObjectsFromPaths(files)){ + fileObjects.add(fo); + } + } if (isEmpty()) { // It is allowed to compile nothing if just asking for help or version info. diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac.properties b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac.properties index 7824772b1f3..d835c639827 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac.properties +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/resources/javac.properties @@ -48,7 +48,7 @@ javac.opt.modulepath=\ javac.opt.sourcepath=\ Specify where to find input source files javac.opt.m=\ - Compile only the specified module(s), check timestamps + Compile only the specified module(s) javac.opt.modulesourcepath=\ Specify where to find input source files for multiple modules javac.opt.bootclasspath=\ diff --git a/test/langtools/tools/javac/IncrementalComp/TestIncrementalComp.java b/test/langtools/tools/javac/IncrementalComp/TestIncrementalComp.java new file mode 100644 index 00000000000..2be04e98c2e --- /dev/null +++ b/test/langtools/tools/javac/IncrementalComp/TestIncrementalComp.java @@ -0,0 +1,293 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary Test javac incremental compilation with modules + * @run junit TestIncrementalComp + */ + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.module.Configuration; +import java.lang.module.ModuleFinder; +import java.lang.reflect.Method; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.spi.ToolProvider; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static java.nio.file.StandardOpenOption.CREATE_NEW; +import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class TestIncrementalComp { + + static final ToolProvider JAVAC = ToolProvider.findFirst("javac") + .orElseThrow(); + + record TestCase(String srcDir, Map sources, Set modules, String mainModule, Map addReadsEdges) { + TestCase(String srcDir, Map sources, Set modules, String mainModule) { + this(srcDir, sources, modules, mainModule, Map.of()); + } + } + + @ParameterizedTest + @MethodSource("cases") + public void test(TestCase testCase) throws Throwable { + Path workDir = Path.of(testCase.srcDir()); + // set up test sources + Path localTestModules = workDir.resolve("test_modules"); + Path outDir = workDir.resolve("mods"); + for (Map.Entry sourceFile : testCase.sources().entrySet()) { + Path filePath = localTestModules.resolve(sourceFile.getKey()); + Files.createDirectories(filePath.getParent()); + Files.writeString(filePath, sourceFile.getValue(), CREATE_NEW); + } + + Path libPath = localTestModules.resolve(LIB_PATH); + Files.createDirectories(libPath.getParent()); + Files.writeString(libPath, ALT_LIB_INT, CREATE_NEW); + + List javacCommand = new ArrayList<>(List.of( + "-d", outDir.toString(), + "--module-source-path=" + localTestModules, + "--module", String.join(",", testCase.modules()) + )); + for (Map.Entry addReads : testCase.addReadsEdges().entrySet()) { + String reader = addReads.getKey(); + String read = addReads.getValue(); + javacCommand.add(String.format("--add-reads=%s=%s", reader, read)); + } + // compile both modules + compile(javacCommand); + + String mainClass = testCase.mainModule() + ".app.Main"; + invokeMainMethod(outDir, testCase.mainModule(), mainClass, testCase.addReadsEdges()); + + // modify sources. Dep is not modified + Files.writeString(libPath, ALT_LIB_LONG, TRUNCATE_EXISTING); + + // recompile. Any dependency on the changed file should be recompiled as well + compile(javacCommand); + + // should work + // if this fails because of incremental compilation issues, we can expect to see a NoSuchMethodError + invokeMainMethod(outDir, testCase.mainModule(), mainClass, testCase.addReadsEdges()); + } + + private static void invokeMainMethod(Path modulePath, String moduleName, String mainClassName, + Map addReadsEdges) + throws ReflectiveOperationException { + // define module layer + // note that we need to explicitly add any read module to the set of roots + ModuleLayer boot = ModuleLayer.boot(); + Set allRoots = Stream.concat(Stream.of(moduleName), addReadsEdges.values().stream()) + .collect(Collectors.toSet()); + Configuration config = boot.configuration() + .resolve(ModuleFinder.of(modulePath), ModuleFinder.of(), allRoots); + ModuleLayer.Controller controller = ModuleLayer.defineModulesWithOneLoader( + config, List.of(boot), ClassLoader.getSystemClassLoader()); + + // add extra reads edges + for (Map.Entry addReads : addReadsEdges.entrySet()) { + Module reader = controller.layer().findModule(addReads.getKey()).orElseThrow(); + Module read = controller.layer().findModule(addReads.getValue()).orElseThrow(); + controller.addReads(reader, read); + } + + // invoke main + Class main1 = controller.layer().findLoader(moduleName).loadClass(mainClassName); + Method m = main1.getMethod("main", String[].class); + m.invoke(null, new Object[]{ new String[0] }); + } + + private static void compile(List args) { + System.err.println("compile: " + args); + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + int rc = JAVAC.run(pw, pw, args.toArray(String[]::new)); + pw.close(); + System.err.println(sw); + assertEquals(0, rc); + } + + private static final Path LIB_PATH = Path.of("org.moda/org/moda/lib/Lib.java"); + + private static final String ALT_LIB_INT = """ + package org.moda.lib; + public class Lib { + public static int getVal() { + return 42; + } + } + """; + + private static final String ALT_LIB_LONG = """ + package org.moda.lib; + public class Lib { + public static long getVal() { + return 42; + } + } + """; + + static Stream cases() { + return Stream.of( + new TestCase("single", Map.of( + Path.of("org.moda/module-info.java"), + """ + module org.moda { + // for reflective access + exports org.moda.app; + } + """, + Path.of("org.moda/org/moda/lib/Dep.java"), + """ + package org.moda.lib; + + public class Dep { + public static long getVal() { + return Lib.getVal(); + } + } + """, + Path.of("org.moda/org/moda/app/Main.java"), + """ + package org.moda.app; + + import org.moda.lib.Dep; + + public class Main { + public static void main(String[] args) { + System.out.println(Dep.getVal()); + } + } + """ + ), Set.of("org.moda"), "org.moda"), + new TestCase("multi", Map.of( + Path.of("org.moda/module-info.java"), + """ + module org.moda { + exports org.moda.lib; + } + """, + Path.of("org.modb/module-info.java"), + """ + module org.modb { + requires org.moda; + + // for reflective access + exports org.modb.app; + } + """, + Path.of("org.modb/org/modb/app/Main.java"), + """ + package org.modb.app; + + import org.moda.lib.Lib; + + public class Main { + public static void main(String[] args) { + System.out.println(Lib.getVal()); + } + } + """ + ), Set.of("org.moda", "org.modb"), "org.modb"), + new TestCase("transitive", Map.of( + Path.of("org.moda/module-info.java"), + """ + module org.moda { + exports org.moda.lib; + } + + """, + Path.of("org.modb/module-info.java"), + """ + module org.modb { + // for org.modc + requires transitive org.moda; + } + """, + Path.of("org.modc/module-info.java"), + """ + module org.modc { + requires org.modb; + + // for reflective access + exports org.modc.app; + } + """, + Path.of("org.modc/org/modc/app/Main.java"), + """ + package org.modc.app; + + import org.moda.lib.Lib; + + public class Main { + public static void main(String[] args) { + System.out.println(Lib.getVal()); + } + } + """ + ), Set.of("org.moda", "org.modb", "org.modc"), "org.modc"), + new TestCase("add_reads", Map.of( + Path.of("org.moda/module-info.java"), + """ + module org.moda { + exports org.moda.lib; + } + """, + Path.of("org.modb/module-info.java"), + """ + module org.modb { + // no explicit requires + + // for reflective access + exports org.modb.app; + } + """, + Path.of("org.modb/org/modb/app/Main.java"), + """ + package org.modb.app; + + import org.moda.lib.Lib; + + public class Main { + public static void main(String[] args) { + System.out.println(Lib.getVal()); + } + } + """ + ), Set.of("org.moda", "org.modb"), "org.modb", Map.of("org.modb", "org.moda")) + ); + } + } diff --git a/test/langtools/tools/javac/modules/MOptionTest.java b/test/langtools/tools/javac/modules/MOptionTest.java index ff08960b0bc..470815a9493 100644 --- a/test/langtools/tools/javac/modules/MOptionTest.java +++ b/test/langtools/tools/javac/modules/MOptionTest.java @@ -84,12 +84,12 @@ public class MOptionTest extends ModuleTestBase { .run(Task.Expect.SUCCESS) .writeAll(); - if (!moduleInfoTimeStamp.equals(Files.getLastModifiedTime(moduleInfoClass))) { - throw new AssertionError("Classfile update!"); + if (Files.getLastModifiedTime(moduleInfoClass).compareTo(moduleInfoTimeStamp) <= 0) { + throw new AssertionError("Classfile too old!"); } - if (!testTestTimeStamp.equals(Files.getLastModifiedTime(testTestClass))) { - throw new AssertionError("Classfile update!"); + if (Files.getLastModifiedTime(testTestClass).compareTo(Files.getLastModifiedTime(testTest)) < 0) { + throw new AssertionError("Classfiles too old!"); } // Date back the source file by one second compared to the current time. @@ -102,8 +102,8 @@ public class MOptionTest extends ModuleTestBase { .run(Task.Expect.SUCCESS) .writeAll(); - if (!moduleInfoTimeStamp.equals(Files.getLastModifiedTime(moduleInfoClass))) { - throw new AssertionError("Classfile update!"); + if (Files.getLastModifiedTime(moduleInfoClass).compareTo(moduleInfoTimeStamp) <= 0) { + throw new AssertionError("Classfile too old!"); } if (Files.getLastModifiedTime(testTestClass).compareTo(Files.getLastModifiedTime(testTest)) < 0) { @@ -219,20 +219,20 @@ public class MOptionTest extends ModuleTestBase { .run(Task.Expect.SUCCESS) .writeAll(); - if (!m1ModuleInfoTimeStamp.equals(Files.getLastModifiedTime(m1ModuleInfoClass))) { - throw new AssertionError("Classfile update!"); + if (Files.getLastModifiedTime(m1ModuleInfoClass).compareTo(m1ModuleInfoTimeStamp) <= 0) { + throw new AssertionError("Classfile too old!"); } - if (!m2ModuleInfoTimeStamp.equals(Files.getLastModifiedTime(m2ModuleInfoClass))) { - throw new AssertionError("Classfile update!"); + if (Files.getLastModifiedTime(m2ModuleInfoClass).compareTo(m2ModuleInfoTimeStamp) <= 0) { + throw new AssertionError("Classfile too old!"); } - if (!C1TimeStamp.equals(Files.getLastModifiedTime(classC1))) { - throw new AssertionError("Classfile update!"); + if (Files.getLastModifiedTime(classC1).compareTo(Files.getLastModifiedTime(C1Source)) < 0) { + throw new AssertionError("Classfiles too old!"); } - if (!C2TimeStamp.equals(Files.getLastModifiedTime(classC2))) { - throw new AssertionError("Classfile update!"); + if (Files.getLastModifiedTime(classC2).compareTo(Files.getLastModifiedTime(C2Source)) < 0) { + throw new AssertionError("Classfiles too old!"); } // Date back the source file by one second compared to the current time. @@ -246,12 +246,12 @@ public class MOptionTest extends ModuleTestBase { .run(Task.Expect.SUCCESS) .writeAll(); - if (!m1ModuleInfoTimeStamp.equals(Files.getLastModifiedTime(m1ModuleInfoClass))) { - throw new AssertionError("Classfile update!"); + if (Files.getLastModifiedTime(m1ModuleInfoClass).compareTo(m1ModuleInfoTimeStamp) <= 0) { + throw new AssertionError("Classfile too old!"); } - if (!m2ModuleInfoTimeStamp.equals(Files.getLastModifiedTime(m2ModuleInfoClass))) { - throw new AssertionError("Classfile update!"); + if (Files.getLastModifiedTime(m2ModuleInfoClass).compareTo(m2ModuleInfoTimeStamp) <= 0) { + throw new AssertionError("Classfile too old!"); } if (Files.getLastModifiedTime(classC1).compareTo(Files.getLastModifiedTime(C1Source)) < 0) { From e4cd94459082586237c998cece527163300a758c Mon Sep 17 00:00:00 2001 From: Jorn Vernee Date: Mon, 29 Jun 2026 21:52:37 +0000 Subject: [PATCH 033/305] 8387406: ProblemList java/foreign/normalize/TestNormalize.java Reviewed-by: liach, jpai --- test/jdk/ProblemList.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/jdk/ProblemList.txt b/test/jdk/ProblemList.txt index fcde1d9c01d..5e730af92b0 100644 --- a/test/jdk/ProblemList.txt +++ b/test/jdk/ProblemList.txt @@ -648,6 +648,8 @@ jdk/jfr/event/oldobject/TestZ.java 8375615 generic- # jdk_foreign +java/foreign/normalize/TestNormalize.java 8386848 generic-all + ############################################################################ # Client manual tests From f232f552af2df3ef5190438a4bb22f0d7ee42dd2 Mon Sep 17 00:00:00 2001 From: Michael Reeves Date: Tue, 30 Jun 2026 04:39:30 +0000 Subject: [PATCH 034/305] 8379327: 128-bit multiplication uses two multiply instructions on x86_64 Reviewed-by: dlong, sviswanathan --- src/hotspot/cpu/aarch64/aarch64.ad | 14 +-- src/hotspot/cpu/arm/arm.ad | 16 +-- src/hotspot/cpu/ppc/ppc.ad | 16 +-- src/hotspot/cpu/riscv/riscv.ad | 14 +-- src/hotspot/cpu/s390/s390.ad | 16 +-- src/hotspot/cpu/x86/x86.ad | 44 +++++-- src/hotspot/share/opto/classes.hpp | 2 + src/hotspot/share/opto/compile.cpp | 30 ++++- src/hotspot/share/opto/compile.hpp | 1 + src/hotspot/share/opto/divnode.cpp | 54 ++++---- src/hotspot/share/opto/divnode.hpp | 28 +---- src/hotspot/share/opto/matcher.hpp | 16 +-- src/hotspot/share/opto/mulnode.cpp | 32 +++++ src/hotspot/share/opto/mulnode.hpp | 29 ++++- src/hotspot/share/opto/multnode.hpp | 28 +++++ src/hotspot/share/opto/node.cpp | 25 +++- src/hotspot/share/opto/node.hpp | 2 +- .../c2/TestMultiplyHighLowFusion.java | 117 ++++++++++++++++++ .../library/Operations.java | 4 + .../vm/compiler/MultiplyHighLowFusion.java | 116 +++++++++++++++++ 20 files changed, 490 insertions(+), 114 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/c2/TestMultiplyHighLowFusion.java create mode 100644 test/micro/org/openjdk/bench/vm/compiler/MultiplyHighLowFusion.java diff --git a/src/hotspot/cpu/aarch64/aarch64.ad b/src/hotspot/cpu/aarch64/aarch64.ad index 49f3419dfb6..05e4321b663 100644 --- a/src/hotspot/cpu/aarch64/aarch64.ad +++ b/src/hotspot/cpu/aarch64/aarch64.ad @@ -2512,25 +2512,25 @@ uint Matcher::float_pressure_limit() return (FLOATPRESSURE == -1) ? _FLOAT_REG_mask.size() : FLOATPRESSURE; } -const RegMask& Matcher::divI_proj_mask() { +const RegMask& Matcher::firstI_proj_mask() { ShouldNotReachHere(); return RegMask::EMPTY; } -// Register for MODI projection of divmodI. -const RegMask& Matcher::modI_proj_mask() { +// Register for the second projection of an int pair +const RegMask& Matcher::secondI_proj_mask() { ShouldNotReachHere(); return RegMask::EMPTY; } -// Register for DIVL projection of divmodL. -const RegMask& Matcher::divL_proj_mask() { +// Register for the first projection of a long pair +const RegMask& Matcher::firstL_proj_mask() { ShouldNotReachHere(); return RegMask::EMPTY; } -// Register for MODL projection of divmodL. -const RegMask& Matcher::modL_proj_mask() { +// Register for the second projection of a long pair +const RegMask& Matcher::secondL_proj_mask() { ShouldNotReachHere(); return RegMask::EMPTY; } diff --git a/src/hotspot/cpu/arm/arm.ad b/src/hotspot/cpu/arm/arm.ad index 45ae283e05a..7ae3381600e 100644 --- a/src/hotspot/cpu/arm/arm.ad +++ b/src/hotspot/cpu/arm/arm.ad @@ -1112,26 +1112,26 @@ uint Matcher::float_pressure_limit() return (FLOATPRESSURE == -1) ? 30 : FLOATPRESSURE; } -// Register for DIVI projection of divmodI -const RegMask& Matcher::divI_proj_mask() { +// Register for the first projection of an int pair +const RegMask& Matcher::firstI_proj_mask() { ShouldNotReachHere(); return RegMask::EMPTY; } -// Register for MODI projection of divmodI -const RegMask& Matcher::modI_proj_mask() { +// Register for the second projection of an int pair +const RegMask& Matcher::secondI_proj_mask() { ShouldNotReachHere(); return RegMask::EMPTY; } -// Register for DIVL projection of divmodL -const RegMask& Matcher::divL_proj_mask() { +// Register for the first projection of a long pair +const RegMask& Matcher::firstL_proj_mask() { ShouldNotReachHere(); return RegMask::EMPTY; } -// Register for MODL projection of divmodL -const RegMask& Matcher::modL_proj_mask() { +// Register for the second projection of a long pair +const RegMask& Matcher::secondL_proj_mask() { ShouldNotReachHere(); return RegMask::EMPTY; } diff --git a/src/hotspot/cpu/ppc/ppc.ad b/src/hotspot/cpu/ppc/ppc.ad index 3cdc820b5f9..e7464feb4ab 100644 --- a/src/hotspot/cpu/ppc/ppc.ad +++ b/src/hotspot/cpu/ppc/ppc.ad @@ -2351,26 +2351,26 @@ uint Matcher::float_pressure_limit() return (FLOATPRESSURE == -1) ? 28 : FLOATPRESSURE; } -// Register for DIVI projection of divmodI. -const RegMask& Matcher::divI_proj_mask() { +// Register for the first projection of an int pair +const RegMask& Matcher::firstI_proj_mask() { ShouldNotReachHere(); return RegMask::EMPTY; } -// Register for MODI projection of divmodI. -const RegMask& Matcher::modI_proj_mask() { +// Register for the second projection of an int pair +const RegMask& Matcher::secondI_proj_mask() { ShouldNotReachHere(); return RegMask::EMPTY; } -// Register for DIVL projection of divmodL. -const RegMask& Matcher::divL_proj_mask() { +// Register for the first projection of a long pair +const RegMask& Matcher::firstL_proj_mask() { ShouldNotReachHere(); return RegMask::EMPTY; } -// Register for MODL projection of divmodL. -const RegMask& Matcher::modL_proj_mask() { +// Register for the second projection of a long pair +const RegMask& Matcher::secondL_proj_mask() { ShouldNotReachHere(); return RegMask::EMPTY; } diff --git a/src/hotspot/cpu/riscv/riscv.ad b/src/hotspot/cpu/riscv/riscv.ad index 0c077dc84a3..7bfff4b2086 100644 --- a/src/hotspot/cpu/riscv/riscv.ad +++ b/src/hotspot/cpu/riscv/riscv.ad @@ -2100,25 +2100,25 @@ uint Matcher::float_pressure_limit() return (FLOATPRESSURE == -1) ? _FLOAT_REG_mask.size() : FLOATPRESSURE; } -const RegMask& Matcher::divI_proj_mask() { +const RegMask& Matcher::firstI_proj_mask() { ShouldNotReachHere(); return RegMask::EMPTY; } -// Register for MODI projection of divmodI. -const RegMask& Matcher::modI_proj_mask() { +// Register for the second projection of an int pair +const RegMask& Matcher::secondI_proj_mask() { ShouldNotReachHere(); return RegMask::EMPTY; } -// Register for DIVL projection of divmodL. -const RegMask& Matcher::divL_proj_mask() { +// Register for the first projection of a long pair +const RegMask& Matcher::firstL_proj_mask() { ShouldNotReachHere(); return RegMask::EMPTY; } -// Register for MODL projection of divmodL. -const RegMask& Matcher::modL_proj_mask() { +// Register for the second projection of a long pair +const RegMask& Matcher::secondL_proj_mask() { ShouldNotReachHere(); return RegMask::EMPTY; } diff --git a/src/hotspot/cpu/s390/s390.ad b/src/hotspot/cpu/s390/s390.ad index 2208a197ac9..c0e51bd2bfd 100644 --- a/src/hotspot/cpu/s390/s390.ad +++ b/src/hotspot/cpu/s390/s390.ad @@ -1929,23 +1929,23 @@ uint Matcher::float_pressure_limit() return (FLOATPRESSURE == -1) ? 15 : FLOATPRESSURE; } -// Register for DIVI projection of divmodI -const RegMask& Matcher::divI_proj_mask() { +// Register for the first projection of an int pair +const RegMask& Matcher::firstI_proj_mask() { return _Z_RARG4_INT_REG_mask; } -// Register for MODI projection of divmodI -const RegMask& Matcher::modI_proj_mask() { +// Register for the second projection of an int pair +const RegMask& Matcher::secondI_proj_mask() { return _Z_RARG3_INT_REG_mask; } -// Register for DIVL projection of divmodL -const RegMask& Matcher::divL_proj_mask() { +// Register for the first projection of a long pair +const RegMask& Matcher::firstL_proj_mask() { return _Z_RARG4_LONG_REG_mask; } -// Register for MODL projection of divmodL -const RegMask& Matcher::modL_proj_mask() { +// Register for the second projection of a long pair +const RegMask& Matcher::secondL_proj_mask() { return _Z_RARG3_LONG_REG_mask; } diff --git a/src/hotspot/cpu/x86/x86.ad b/src/hotspot/cpu/x86/x86.ad index df035f39f58..3f953dbe725 100644 --- a/src/hotspot/cpu/x86/x86.ad +++ b/src/hotspot/cpu/x86/x86.ad @@ -2764,23 +2764,23 @@ uint Matcher::float_pressure_limit() return (FLOATPRESSURE == -1) ? default_float_pressure_threshold : FLOATPRESSURE; } -// Register for DIVI projection of divmodI -const RegMask& Matcher::divI_proj_mask() { +// Register for the first projection of an int pair +const RegMask& Matcher::firstI_proj_mask() { return INT_RAX_REG_mask(); } -// Register for MODI projection of divmodI -const RegMask& Matcher::modI_proj_mask() { +// Register for the second projection of an int pair +const RegMask& Matcher::secondI_proj_mask() { return INT_RDX_REG_mask(); } -// Register for DIVL projection of divmodL -const RegMask& Matcher::divL_proj_mask() { +// Register for the first projection of a long pair +const RegMask& Matcher::firstL_proj_mask() { return LONG_RAX_REG_mask(); } -// Register for MODL projection of divmodL -const RegMask& Matcher::modL_proj_mask() { +// Register for the second projection of a long pair +const RegMask& Matcher::secondL_proj_mask() { return LONG_RDX_REG_mask(); } @@ -11379,6 +11379,34 @@ instruct mulL_mem_imm(rRegL dst, memory src, immL32 imm, rFlagsReg cr) ins_pipe(ialu_reg_mem_alu0); %} +instruct mulHiLoL_rReg(rax_RegL rax, rdx_RegL rdx, rRegL src, rFlagsReg cr) +%{ + match(MulHiLoL src rax); + match(MulHiLoL rax src); + effect(KILL cr); + + ins_cost(300); + format %{ "imulq RDX:RAX, RAX, $src\t# mulhilo" %} + ins_encode %{ + __ imulq($src$$Register); + %} + ins_pipe(ialu_reg_reg_alu0); +%} + +instruct umulHiLoL_rReg(rax_RegL rax, rdx_RegL rdx, rRegL src, rFlagsReg cr) +%{ + match(UMulHiLoL src rax); + match(UMulHiLoL rax src); + effect(KILL cr); + + ins_cost(300); + format %{ "mulq RDX:RAX, RAX, $src\t# umulhilo" %} + ins_encode %{ + __ mulq($src$$Register); + %} + ins_pipe(ialu_reg_reg_alu0); +%} + instruct mulHiL_rReg(rdx_RegL dst, rRegL src, rax_RegL rax, rFlagsReg cr) %{ match(Set dst (MulHiL src rax)); diff --git a/src/hotspot/share/opto/classes.hpp b/src/hotspot/share/opto/classes.hpp index 7033dad211c..4d06e20875a 100644 --- a/src/hotspot/share/opto/classes.hpp +++ b/src/hotspot/share/opto/classes.hpp @@ -268,6 +268,8 @@ macro(MulD) macro(MulF) macro(MulHiL) macro(UMulHiL) +macro(MulHiLoL) +macro(UMulHiLoL) macro(MulI) macro(MulL) macro(Multi) diff --git a/src/hotspot/share/opto/compile.cpp b/src/hotspot/share/opto/compile.cpp index a273bb6053e..1f51cdc1d39 100644 --- a/src/hotspot/share/opto/compile.cpp +++ b/src/hotspot/share/opto/compile.cpp @@ -3276,8 +3276,8 @@ void Compile::handle_div_mod_op(Node* n, BasicType bt, bool is_unsigned) { // DivMod node so the dependency is not lost. divmod->add_prec_from(n); divmod->add_prec_from(d); - d->subsume_by(divmod->div_proj(), this); - n->subsume_by(divmod->mod_proj(), this); + d->subsume_by(divmod->first_proj(), this); + n->subsume_by(divmod->second_proj(), this); } else { // Replace "a % b" with "a - ((a / b) * b)" Node* mult = MulNode::make(d, d->in(2), bt); @@ -3286,6 +3286,24 @@ void Compile::handle_div_mod_op(Node* n, BasicType bt, bool is_unsigned) { } } +void Compile::handle_mulhi_mul_op(Node* n, bool is_unsigned) { + const int fused_opcode = is_unsigned ? Op_UMulHiLoL : Op_MulHiLoL; + if (!Matcher::has_match_rule(fused_opcode)) { + return; + } + + Node* mul = n->find_similar(Op_MulL, true); + + if (mul == nullptr) { + return; + } + + MulHiLoLNode* mul_hi_lo = is_unsigned ? static_cast(UMulHiLoLNode::make(n)) + : MulHiLoLNode::make(n); + mul->subsume_by(mul_hi_lo->first_proj(), this); + n->subsume_by(mul_hi_lo->second_proj(), this); +} + void Compile::final_graph_reshaping_main_switch(Node* n, Final_Reshape_Counts& frc, uint nop, Unique_Node_List& dead_nodes) { switch( nop ) { case Op_Opaque1: // Remove Opaque Nodes before matching @@ -3721,6 +3739,14 @@ void Compile::final_graph_reshaping_main_switch(Node* n, Final_Reshape_Counts& f handle_div_mod_op(n, T_LONG, true); break; + case Op_MulHiL: + handle_mulhi_mul_op(n, false); + break; + + case Op_UMulHiL: + handle_mulhi_mul_op(n, true); + break; + case Op_LoadVector: case Op_StoreVector: #ifdef ASSERT diff --git a/src/hotspot/share/opto/compile.hpp b/src/hotspot/share/opto/compile.hpp index 3c2e1c64119..ab36f59a28f 100644 --- a/src/hotspot/share/opto/compile.hpp +++ b/src/hotspot/share/opto/compile.hpp @@ -1257,6 +1257,7 @@ public: void final_graph_reshaping_main_switch(Node* n, Final_Reshape_Counts& frc, uint nop, Unique_Node_List& dead_nodes); void final_graph_reshaping_walk(Node_Stack& nstack, Node* root, Final_Reshape_Counts& frc, Unique_Node_List& dead_nodes); void handle_div_mod_op(Node* n, BasicType bt, bool is_unsigned); + void handle_mulhi_mul_op(Node* n, bool is_unsigned); // Logic cone optimization. void optimize_logic_cones(PhaseIterGVN &igvn); diff --git a/src/hotspot/share/opto/divnode.cpp b/src/hotspot/share/opto/divnode.cpp index b398ec27b80..1687ff2cade 100644 --- a/src/hotspot/share/opto/divnode.cpp +++ b/src/hotspot/share/opto/divnode.cpp @@ -1614,12 +1614,6 @@ const Type* ModFloatingNode::Value(PhaseGVN* phase) const { //============================================================================= -DivModNode::DivModNode( Node *c, Node *dividend, Node *divisor ) : MultiNode(3) { - init_req(0, c); - init_req(1, dividend); - init_req(2, divisor); -} - DivModNode* DivModNode::make(Node* div_or_mod, BasicType bt, bool is_unsigned) { assert(bt == T_INT || bt == T_LONG, "only int or long input pattern accepted"); @@ -1645,8 +1639,8 @@ DivModINode* DivModINode::make(Node* div_or_mod) { "only div or mod input pattern accepted"); DivModINode* divmod = new DivModINode(n->in(0), n->in(1), n->in(2)); - Node* dproj = new ProjNode(divmod, DivModNode::div_proj_num); - Node* mproj = new ProjNode(divmod, DivModNode::mod_proj_num); + Node* dproj = new ProjNode(divmod, DivModNode::first_proj_num); + Node* mproj = new ProjNode(divmod, DivModNode::second_proj_num); return divmod; } @@ -1657,8 +1651,8 @@ DivModLNode* DivModLNode::make(Node* div_or_mod) { "only div or mod input pattern accepted"); DivModLNode* divmod = new DivModLNode(n->in(0), n->in(1), n->in(2)); - Node* dproj = new ProjNode(divmod, DivModNode::div_proj_num); - Node* mproj = new ProjNode(divmod, DivModNode::mod_proj_num); + Node* dproj = new ProjNode(divmod, DivModNode::first_proj_num); + Node* mproj = new ProjNode(divmod, DivModNode::second_proj_num); return divmod; } @@ -1667,11 +1661,11 @@ DivModLNode* DivModLNode::make(Node* div_or_mod) { Node *DivModINode::match( const ProjNode *proj, const Matcher *match ) { uint ideal_reg = proj->ideal_reg(); RegMask rm; - if (proj->_con == div_proj_num) { - rm.assignFrom(match->divI_proj_mask()); + if (proj->_con == first_proj_num) { + rm.assignFrom(match->firstI_proj_mask()); } else { - assert(proj->_con == mod_proj_num, "must be div or mod projection"); - rm.assignFrom(match->modI_proj_mask()); + assert(proj->_con == second_proj_num, "must be div or mod projection"); + rm.assignFrom(match->secondI_proj_mask()); } return new MachProjNode(this, proj->_con, rm, ideal_reg); } @@ -1682,11 +1676,11 @@ Node *DivModINode::match( const ProjNode *proj, const Matcher *match ) { Node *DivModLNode::match( const ProjNode *proj, const Matcher *match ) { uint ideal_reg = proj->ideal_reg(); RegMask rm; - if (proj->_con == div_proj_num) { - rm.assignFrom(match->divL_proj_mask()); + if (proj->_con == first_proj_num) { + rm.assignFrom(match->firstL_proj_mask()); } else { - assert(proj->_con == mod_proj_num, "must be div or mod projection"); - rm.assignFrom(match->modL_proj_mask()); + assert(proj->_con == second_proj_num, "must be div or mod projection"); + rm.assignFrom(match->secondL_proj_mask()); } return new MachProjNode(this, proj->_con, rm, ideal_reg); } @@ -1698,8 +1692,8 @@ UDivModINode* UDivModINode::make(Node* div_or_mod) { "only div or mod input pattern accepted"); UDivModINode* divmod = new UDivModINode(n->in(0), n->in(1), n->in(2)); - Node* dproj = new ProjNode(divmod, DivModNode::div_proj_num); - Node* mproj = new ProjNode(divmod, DivModNode::mod_proj_num); + Node* dproj = new ProjNode(divmod, DivModNode::first_proj_num); + Node* mproj = new ProjNode(divmod, DivModNode::second_proj_num); return divmod; } @@ -1710,8 +1704,8 @@ UDivModLNode* UDivModLNode::make(Node* div_or_mod) { "only div or mod input pattern accepted"); UDivModLNode* divmod = new UDivModLNode(n->in(0), n->in(1), n->in(2)); - Node* dproj = new ProjNode(divmod, DivModNode::div_proj_num); - Node* mproj = new ProjNode(divmod, DivModNode::mod_proj_num); + Node* dproj = new ProjNode(divmod, DivModNode::first_proj_num); + Node* mproj = new ProjNode(divmod, DivModNode::second_proj_num); return divmod; } @@ -1720,11 +1714,11 @@ UDivModLNode* UDivModLNode::make(Node* div_or_mod) { Node* UDivModINode::match( const ProjNode *proj, const Matcher *match ) { uint ideal_reg = proj->ideal_reg(); RegMask rm; - if (proj->_con == div_proj_num) { - rm.assignFrom(match->divI_proj_mask()); + if (proj->_con == first_proj_num) { + rm.assignFrom(match->firstI_proj_mask()); } else { - assert(proj->_con == mod_proj_num, "must be div or mod projection"); - rm.assignFrom(match->modI_proj_mask()); + assert(proj->_con == second_proj_num, "must be div or mod projection"); + rm.assignFrom(match->secondI_proj_mask()); } return new MachProjNode(this, proj->_con, rm, ideal_reg); } @@ -1735,11 +1729,11 @@ Node* UDivModINode::match( const ProjNode *proj, const Matcher *match ) { Node* UDivModLNode::match( const ProjNode *proj, const Matcher *match ) { uint ideal_reg = proj->ideal_reg(); RegMask rm; - if (proj->_con == div_proj_num) { - rm.assignFrom(match->divL_proj_mask()); + if (proj->_con == first_proj_num) { + rm.assignFrom(match->firstL_proj_mask()); } else { - assert(proj->_con == mod_proj_num, "must be div or mod projection"); - rm.assignFrom(match->modL_proj_mask()); + assert(proj->_con == second_proj_num, "must be div or mod projection"); + rm.assignFrom(match->secondL_proj_mask()); } return new MachProjNode(this, proj->_con, rm, ideal_reg); } diff --git a/src/hotspot/share/opto/divnode.hpp b/src/hotspot/share/opto/divnode.hpp index 2598429716f..366e3fb882d 100644 --- a/src/hotspot/share/opto/divnode.hpp +++ b/src/hotspot/share/opto/divnode.hpp @@ -239,36 +239,20 @@ public: //------------------------------DivModNode--------------------------------------- // Division with remainder result. -class DivModNode : public MultiNode { +class DivModNode : public BinaryMultiNode { protected: - DivModNode( Node *c, Node *dividend, Node *divisor ); + DivModNode(Node* ctrl, Node* dividend, Node* divisor) : BinaryMultiNode(ctrl, dividend, divisor) {} public: - enum { - div_proj_num = 0, // quotient - mod_proj_num = 1 // remainder - }; virtual int Opcode() const; - virtual Node* Identity(PhaseGVN* phase) { return this; } - virtual Node *Ideal(PhaseGVN *phase, bool can_reshape) { return nullptr; } - virtual const Type* Value(PhaseGVN* phase) const { return bottom_type(); } - virtual uint hash() const { return Node::hash(); } - virtual bool is_CFG() const { return false; } - virtual uint ideal_reg() const { return NotAMachineReg; } static DivModNode* make(Node* div_or_mod, BasicType bt, bool is_unsigned); - - ProjNode* div_proj() { return proj_out_or_null(div_proj_num); } - ProjNode* mod_proj() { return proj_out_or_null(mod_proj_num); } - -private: - virtual bool depends_only_on_test() const { return false; } }; //------------------------------DivModINode--------------------------------------- // Integer division with remainder result. class DivModINode : public DivModNode { public: - DivModINode( Node *c, Node *dividend, Node *divisor ) : DivModNode(c, dividend, divisor) {} + DivModINode(Node* ctrl, Node* dividend, Node* divisor) : DivModNode(ctrl, dividend, divisor) {} virtual int Opcode() const; virtual const Type *bottom_type() const { return TypeTuple::INT_PAIR; } virtual Node *match( const ProjNode *proj, const Matcher *m ); @@ -281,7 +265,7 @@ public: // Long division with remainder result. class DivModLNode : public DivModNode { public: - DivModLNode( Node *c, Node *dividend, Node *divisor ) : DivModNode(c, dividend, divisor) {} + DivModLNode(Node* ctrl, Node* dividend, Node* divisor) : DivModNode(ctrl, dividend, divisor) {} virtual int Opcode() const; virtual const Type *bottom_type() const { return TypeTuple::LONG_PAIR; } virtual Node *match( const ProjNode *proj, const Matcher *m ); @@ -295,7 +279,7 @@ public: // Unsigend integer division with remainder result. class UDivModINode : public DivModNode { public: - UDivModINode( Node *c, Node *dividend, Node *divisor ) : DivModNode(c, dividend, divisor) {} + UDivModINode(Node* ctrl, Node* dividend, Node* divisor) : DivModNode(ctrl, dividend, divisor) {} virtual int Opcode() const; virtual const Type *bottom_type() const { return TypeTuple::INT_PAIR; } virtual Node *match( const ProjNode *proj, const Matcher *m ); @@ -308,7 +292,7 @@ public: // Unsigned long division with remainder result. class UDivModLNode : public DivModNode { public: - UDivModLNode( Node *c, Node *dividend, Node *divisor ) : DivModNode(c, dividend, divisor) {} + UDivModLNode(Node* ctrl, Node* dividend, Node* divisor) : DivModNode(ctrl, dividend, divisor) {} virtual int Opcode() const; virtual const Type *bottom_type() const { return TypeTuple::LONG_PAIR; } virtual Node *match( const ProjNode *proj, const Matcher *m ); diff --git a/src/hotspot/share/opto/matcher.hpp b/src/hotspot/share/opto/matcher.hpp index 31f4a782247..2453a7ece4e 100644 --- a/src/hotspot/share/opto/matcher.hpp +++ b/src/hotspot/share/opto/matcher.hpp @@ -418,15 +418,15 @@ public: static OptoReg::Name inline_cache_reg(); static int inline_cache_reg_encode(); - // Register for DIVI projection of divmodI - static const RegMask& divI_proj_mask(); - // Register for MODI projection of divmodI - static const RegMask& modI_proj_mask(); + // Register for the first projection of an int pair + static const RegMask& firstI_proj_mask(); + // Register for the second projection of an int pair + static const RegMask& secondI_proj_mask(); - // Register for DIVL projection of divmodL - static const RegMask& divL_proj_mask(); - // Register for MODL projection of divmodL - static const RegMask& modL_proj_mask(); + // Register for the first projection of a long pair + static const RegMask& firstL_proj_mask(); + // Register for the second projection of a long pair + static const RegMask& secondL_proj_mask(); // Java-Interpreter calling convention // (what you use when calling between compiled-Java and Interpreted-Java diff --git a/src/hotspot/share/opto/mulnode.cpp b/src/hotspot/share/opto/mulnode.cpp index e48acd23b87..eb24e31eee2 100644 --- a/src/hotspot/share/opto/mulnode.cpp +++ b/src/hotspot/share/opto/mulnode.cpp @@ -26,6 +26,8 @@ #include "opto/addnode.hpp" #include "opto/connode.hpp" #include "opto/convertnode.hpp" +#include "opto/machnode.hpp" +#include "opto/matcher.hpp" #include "opto/memnode.hpp" #include "opto/mulnode.hpp" #include "opto/phaseX.hpp" @@ -606,6 +608,36 @@ const Type* UMulHiLNode::Value(PhaseGVN* phase) const { return MulHiValue(t1, t2, bot); } +MulHiLoLNode* MulHiLoLNode::make(Node* mul_hi) { + assert(mul_hi->Opcode() == Op_MulHiL, "expected MulHiL"); + + MulHiLoLNode* mul_hi_lo = new MulHiLoLNode(mul_hi->in(0), mul_hi->in(1), mul_hi->in(2)); + [[maybe_unused]] Node* lo_proj = new ProjNode(mul_hi_lo, MulHiLoLNode::first_proj_num); + [[maybe_unused]] Node* hi_proj = new ProjNode(mul_hi_lo, MulHiLoLNode::second_proj_num); + return mul_hi_lo; +} + +UMulHiLoLNode* UMulHiLoLNode::make(Node* umul_hi) { + assert(umul_hi->Opcode() == Op_UMulHiL, "expected UMulHiL"); + + UMulHiLoLNode* umul_hi_lo = new UMulHiLoLNode(umul_hi->in(0), umul_hi->in(1), umul_hi->in(2)); + [[maybe_unused]] Node* lo_proj = new ProjNode(umul_hi_lo, MulHiLoLNode::first_proj_num); + [[maybe_unused]] Node* hi_proj = new ProjNode(umul_hi_lo, MulHiLoLNode::second_proj_num); + return umul_hi_lo; +} + +Node* MulHiLoLNode::match(const ProjNode* proj, const Matcher* match) { + uint ideal_reg = proj->ideal_reg(); + RegMask rm; + if (proj->_con == first_proj_num) { + rm.assignFrom(match->firstL_proj_mask()); + } else { + assert(proj->_con == second_proj_num, "must be lo or hi projection"); + rm.assignFrom(match->secondL_proj_mask()); + } + return new MachProjNode(this, proj->_con, rm, ideal_reg); +} + // A common routine used by UMulHiLNode and MulHiLNode const Type* MulHiValue(const Type *t1, const Type *t2, const Type *bot) { // Either input is TOP ==> the result is TOP diff --git a/src/hotspot/share/opto/mulnode.hpp b/src/hotspot/share/opto/mulnode.hpp index 1e19e8ec5cd..f26137dfe49 100644 --- a/src/hotspot/share/opto/mulnode.hpp +++ b/src/hotspot/share/opto/mulnode.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,6 +25,7 @@ #ifndef SHARE_OPTO_MULNODE_HPP #define SHARE_OPTO_MULNODE_HPP +#include "opto/multnode.hpp" #include "opto/node.hpp" #include "opto/opcodes.hpp" #include "opto/type.hpp" @@ -32,6 +33,7 @@ // Portions of code courtesy of Clifford Click class PhaseTransform; +class Matcher; //------------------------------MulNode---------------------------------------- // Classic MULTIPLY functionality. This covers all the usual 'multiply' @@ -205,6 +207,31 @@ public: friend const Type* MulHiValue(const Type *t1, const Type *t2, const Type *bot); }; +//------------------------------MulHiLoLNode----------------------------------- +// Lower and upper 64-bit results of a signed 64x64->128 multiply. +class MulHiLoLNode : public BinaryMultiNode { +protected: + MulHiLoLNode(Node* ctrl, Node* in1, Node* in2) : BinaryMultiNode(ctrl, in1, in2) {} + +public: + virtual int Opcode() const; + virtual const Type* bottom_type() const { return TypeTuple::LONG_PAIR; } + + virtual Node* match(const ProjNode* proj, const Matcher* m); + + static MulHiLoLNode* make(Node* mul_hi); +}; + +//------------------------------UMulHiLoLNode---------------------------------- +// Lower and upper 64-bit results of an unsigned 64x64->128 multiply. +class UMulHiLoLNode : public MulHiLoLNode { +public: + UMulHiLoLNode(Node* ctrl, Node* in1, Node* in2) : MulHiLoLNode(ctrl, in1, in2) {} + virtual int Opcode() const; + + static UMulHiLoLNode* make(Node* umul_hi); +}; + //------------------------------AndINode--------------------------------------- // Logically AND 2 integers. Included with the MUL nodes because it inherits // all the behavior of multiplication on a ring. diff --git a/src/hotspot/share/opto/multnode.hpp b/src/hotspot/share/opto/multnode.hpp index b63d418b742..6a69eafb7ed 100644 --- a/src/hotspot/share/opto/multnode.hpp +++ b/src/hotspot/share/opto/multnode.hpp @@ -149,6 +149,34 @@ public: ProjNode* find_first(uint which_proj, bool is_io_use) const; }; +class BinaryMultiNode : public MultiNode { +protected: + BinaryMultiNode(Node* ctrl, Node* in1, Node* in2) : MultiNode(3) { + init_req(0, ctrl); + init_req(1, in1); + init_req(2, in2); + } + +public: + enum { + first_proj_num = 0, + second_proj_num = 1 + }; + + virtual Node* Identity(PhaseGVN* phase) { return this; } + virtual Node* Ideal(PhaseGVN* phase, bool can_reshape) { return nullptr; } + virtual const Type* Value(PhaseGVN* phase) const { return bottom_type(); } + virtual uint hash() const { return Node::hash(); } + virtual bool is_CFG() const { return false; } + virtual uint ideal_reg() const { return NotAMachineReg; } + + ProjNode* first_proj() const { return proj_out_or_null(first_proj_num); } + ProjNode* second_proj() const { return proj_out_or_null(second_proj_num); } + +private: + virtual bool depends_only_on_test() const { return false; } +}; + //------------------------------ProjNode--------------------------------------- // This class defines a Projection node. Projections project a single element // out of a tuple (or Signature) type. Only MultiNodes produce TypeTuple diff --git a/src/hotspot/share/opto/node.cpp b/src/hotspot/share/opto/node.cpp index 997ce92fe1c..2f7cc6d1c1d 100644 --- a/src/hotspot/share/opto/node.cpp +++ b/src/hotspot/share/opto/node.cpp @@ -2882,7 +2882,7 @@ bool Node::is_iteratively_computed() { //--------------------------find_similar------------------------------ // Return a node with opcode "opc" and same inputs as "this" if one can // be found; Otherwise return null; -Node* Node::find_similar(int opc) { +Node* Node::find_similar(int opc, bool is_commutative) { if (req() >= 2) { Node* def = in(1); if (def && def->outcnt() >= 2) { @@ -2890,9 +2890,26 @@ Node* Node::find_similar(int opc) { Node* use = def->fast_out(i); if (use != this && use->Opcode() == opc && - use->req() == req() && - has_same_inputs_as(use)) { - return use; + use->req() == req()) { + bool same = false; + if (!is_commutative || req() < 3) { + same = use->has_same_inputs_as(this); + } else { + if (use->in(0) == in(0) && + ((use->in(1) == in(1) && use->in(2) == in(2)) || + (use->in(1) == in(2) && use->in(2) == in(1)))) { + same = true; + for (uint j = 3; j < req(); j++) { + if (use->in(j) != in(j)) { + same = false; + break; + } + } + } + } + if (same) { + return use; + } } } } diff --git a/src/hotspot/share/opto/node.hpp b/src/hotspot/share/opto/node.hpp index 1ef4b5a51b6..443f4bfbe8a 100644 --- a/src/hotspot/share/opto/node.hpp +++ b/src/hotspot/share/opto/node.hpp @@ -1315,7 +1315,7 @@ public: // Return a node with opcode "opc" and same inputs as "this" if one can // be found; Otherwise return null; - Node* find_similar(int opc); + Node* find_similar(int opc, bool is_commutative = false); bool has_same_inputs_as(const Node* other) const; // Return the unique control out if only one. Null if none or more than one. diff --git a/test/hotspot/jtreg/compiler/c2/TestMultiplyHighLowFusion.java b/test/hotspot/jtreg/compiler/c2/TestMultiplyHighLowFusion.java new file mode 100644 index 00000000000..31dd52bd3f6 --- /dev/null +++ b/test/hotspot/jtreg/compiler/c2/TestMultiplyHighLowFusion.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8379327 + * @summary Verify correctness for combined low/high 64-bit multiplication patterns. + * @library /test/lib / + * @run driver ${test.main.class} + */ + +package compiler.c2; + +import compiler.lib.generators.Generator; +import compiler.lib.generators.Generators; +import compiler.lib.ir_framework.*; +import java.math.BigInteger; + +public class TestMultiplyHighLowFusion { + private static final BigInteger MASK_64 = BigInteger.ONE.shiftLeft(64).subtract(BigInteger.ONE); + private static final Generator LONG_GEN = Generators.G.longs(); + + public static void main(String[] args) { + TestFramework.run(); + } + + @Test + @IR(applyIfPlatform = {"x64", "true"}, phase = CompilePhase.PRINT_IDEAL, counts = {"\\bMulHiLoL\\b", "1"}) + public static long doMath(long a, long b) { + long low = a * b; + long high = Math.multiplyHigh(a, b); + return low + high; + } + + @Test + @IR(applyIfPlatform = {"x64", "true"}, phase = CompilePhase.PRINT_IDEAL, counts = {"\\bMulHiLoL\\b", "1"}) + public static long doMathSwapped(long a, long b) { + long low = b * a; + long high = Math.multiplyHigh(b, a); + return low + high; + } + + @Test + @IR(applyIfPlatform = {"x64", "true"}, phase = CompilePhase.PRINT_IDEAL, counts = {"\\bUMulHiLoL\\b", "1"}) + public static long doUnsignedMath(long a, long b) { + long low = a * b; + long high = Math.unsignedMultiplyHigh(a, b); + return low + high; + } + + @Test + @IR(applyIfPlatform = {"x64", "true"}, phase = CompilePhase.PRINT_IDEAL, counts = {"\\bUMulHiLoL\\b", "1"}) + public static long doUnsignedMathSwapped(long a, long b) { + long low = b * a; + long high = Math.unsignedMultiplyHigh(b, a); + return low + high; + } + + @Run(test = {"doMath", "doMathSwapped", "doUnsignedMath", "doUnsignedMathSwapped"}) + public void runTests() { + verifyPair(LONG_GEN.next(), LONG_GEN.next()); + } + + private void verifyPair(long a, long b) { + long expectedSigned = expectedSigned(a, b); + long expectedUnsigned = expectedUnsigned(a, b); + + if (doMath(a, b) != expectedSigned) { + throw new RuntimeException("Signed mismatch for a=" + a + ", b=" + b); + } + if (doMathSwapped(a, b) != expectedSigned) { + throw new RuntimeException("Signed swapped mismatch for a=" + a + ", b=" + b); + } + if (doUnsignedMath(a, b) != expectedUnsigned) { + throw new RuntimeException("Unsigned mismatch for a=" + a + ", b=" + b); + } + if (doUnsignedMathSwapped(a, b) != expectedUnsigned) { + throw new RuntimeException("Unsigned swapped mismatch for a=" + a + ", b=" + b); + } + } + + private static long expectedSigned(long a, long b) { + BigInteger product = BigInteger.valueOf(a).multiply(BigInteger.valueOf(b)); + long low = product.longValue(); + long high = product.shiftRight(64).longValue(); + return low + high; + } + + private static long expectedUnsigned(long a, long b) { + BigInteger ua = BigInteger.valueOf(a).and(MASK_64); + BigInteger ub = BigInteger.valueOf(b).and(MASK_64); + BigInteger product = ua.multiply(ub); + long low = product.longValue(); + long high = product.shiftRight(64).longValue(); + return low + high; + } +} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/library/Operations.java b/test/hotspot/jtreg/compiler/lib/template_framework/library/Operations.java index becda83a029..3dffa096525 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/library/Operations.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/library/Operations.java @@ -264,6 +264,10 @@ public final class Operations { ops.add(Expression.make(BOOLEANS, "Boolean.logicalOr(", BOOLEANS, ", ", BOOLEANS, ")")); ops.add(Expression.make(BOOLEANS, "Boolean.logicalXor(", BOOLEANS, ", ", BOOLEANS, ")")); + // ------------ Math ------------- + ops.add(Expression.make(LONGS, "Math.multiplyHigh(", LONGS, ", ", LONGS, ")")); + ops.add(Expression.make(LONGS, "Math.unsignedMultiplyHigh(", LONGS, ", ", LONGS, ")")); + // TODO: Math and other classes. // Note: Math.copySign is non-deterministic because of NaN having encoding with sign bit set and unset. diff --git a/test/micro/org/openjdk/bench/vm/compiler/MultiplyHighLowFusion.java b/test/micro/org/openjdk/bench/vm/compiler/MultiplyHighLowFusion.java new file mode 100644 index 00000000000..c4f4e522409 --- /dev/null +++ b/test/micro/org/openjdk/bench/vm/compiler/MultiplyHighLowFusion.java @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.vm.compiler; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +import java.util.Random; +import java.util.concurrent.TimeUnit; + +/** + * Benchmarks patterns that may fuse low/high 64-bit multiply operations. + */ +@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 MultiplyHighLowFusion { + + @Param("1024") + private int arraySize; + + private long[] lhs; + private long[] rhs; + + @Setup + public void setup() { + Random random = new Random(0x5EED); + lhs = new long[arraySize]; + rhs = new long[arraySize]; + for (int i = 0; i < arraySize; i++) { + lhs[i] = random.nextLong(); + rhs[i] = random.nextLong(); + } + } + + @Benchmark + public long signedLowOnly() { + long sum = 0; + for (int i = 0; i < arraySize; i++) { + sum += lhs[i] * rhs[i]; + } + return sum; + } + + @Benchmark + public long signedHighOnly() { + long sum = 0; + for (int i = 0; i < arraySize; i++) { + sum += Math.multiplyHigh(lhs[i], rhs[i]); + } + return sum; + } + + @Benchmark + public long signedLowPlusHigh() { + long sum = 0; + for (int i = 0; i < arraySize; i++) { + long a = lhs[i]; + long b = rhs[i]; + sum += (a * b) + Math.multiplyHigh(a, b); + } + return sum; + } + + @Benchmark + public long unsignedHighOnly() { + long sum = 0; + for (int i = 0; i < arraySize; i++) { + sum += Math.unsignedMultiplyHigh(lhs[i], rhs[i]); + } + return sum; + } + + @Benchmark + public long unsignedLowPlusHigh() { + long sum = 0; + for (int i = 0; i < arraySize; i++) { + long a = lhs[i]; + long b = rhs[i]; + sum += (a * b) + Math.unsignedMultiplyHigh(a, b); + } + return sum; + } +} From 08435e4861a348fade00673a91e61b83bacccbbf Mon Sep 17 00:00:00 2001 From: Serguei Spitsyn Date: Tue, 30 Jun 2026 05:05:22 +0000 Subject: [PATCH 035/305] 8379144: serviceability/jvmti/vthread/VThreadTest/VThreadTest.java timed out with --enable-preview Reviewed-by: lmesnik --- .../vthread/VThreadTest/VThreadTest.java | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/test/hotspot/jtreg/serviceability/jvmti/vthread/VThreadTest/VThreadTest.java b/test/hotspot/jtreg/serviceability/jvmti/vthread/VThreadTest/VThreadTest.java index 7330f4c061d..115326567d3 100644 --- a/test/hotspot/jtreg/serviceability/jvmti/vthread/VThreadTest/VThreadTest.java +++ b/test/hotspot/jtreg/serviceability/jvmti/vthread/VThreadTest/VThreadTest.java @@ -33,13 +33,13 @@ import java.util.concurrent.*; public class VThreadTest { - private static final String agentLib = "VThreadTest"; - static final int MSG_COUNT = 10*1000; static final SynchronousQueue QUEUE = new SynchronousQueue<>(); static native boolean check(); + static void log(String msg) { System.out.println(msg); } + static void producer(String msg) throws InterruptedException { int ii = 1; long ll = 2*(long)ii; @@ -54,7 +54,11 @@ public class VThreadTest { for (int i = 0; i < MSG_COUNT; i++) { producer("msg: "); } - } catch (InterruptedException e) { } + } catch (Throwable t) { + t.printStackTrace(System.out); + log("VThreadTest failed: PRODUCER caught a throwable: " + t); + System.exit(1); + } }; static final Runnable CONSUMER = () -> { @@ -62,7 +66,11 @@ public class VThreadTest { for (int i = 0; i < MSG_COUNT; i++) { String s = QUEUE.take(); } - } catch (InterruptedException e) { } + } catch (Throwable t) { + t.printStackTrace(System.out); + log("VThreadTest failed: CONSUMER caught a throwable: " + t); + System.exit(1); + } }; public static void test1() throws Exception { @@ -80,14 +88,6 @@ public class VThreadTest { } public static void main(String[] args) throws Exception { - try { - System.loadLibrary(agentLib); - } catch (UnsatisfiedLinkError ex) { - System.err.println("Failed to load " + agentLib + " lib"); - System.err.println("java.library.path: " + System.getProperty("java.library.path")); - throw ex; - } - VThreadTest obj = new VThreadTest(); obj.runTest(); } From ce87f11a1d2295fceb04c4b94b9a175d7807973a Mon Sep 17 00:00:00 2001 From: Ashay Rane Date: Tue, 30 Jun 2026 05:23:33 +0000 Subject: [PATCH 036/305] 8386685: CDS load on Windows/ARM64 using base address set to 0x5_0000_0000 causes a JVM crash Reviewed-by: iklam, stuefe --- src/hotspot/share/cds/aotMetaspace.cpp | 29 ++++++++++++++------------ src/hotspot/share/cds/aotMetaspace.hpp | 3 +++ src/hotspot/share/cds/cdsConfig.cpp | 7 +++++++ src/hotspot/share/cds/cds_globals.hpp | 6 ++++-- 4 files changed, 30 insertions(+), 15 deletions(-) diff --git a/src/hotspot/share/cds/aotMetaspace.cpp b/src/hotspot/share/cds/aotMetaspace.cpp index fac320c3ed7..fbd12038c94 100644 --- a/src/hotspot/share/cds/aotMetaspace.cpp +++ b/src/hotspot/share/cds/aotMetaspace.cpp @@ -164,7 +164,7 @@ size_t AOTMetaspace::protection_zone_size() { return os::cds_core_region_alignment(); } -static bool shared_base_valid(char* shared_base) { +bool AOTMetaspace::shared_base_valid(char* shared_base) { // We check user input for SharedBaseAddress at dump time. // At CDS runtime, "shared_base" will be the (attempted) mapping start. It will also @@ -172,10 +172,15 @@ static bool shared_base_valid(char* shared_base) { // the prototype mark words) carry pre-computed narrow Klass IDs that refer to the mapping // start as base. // - // On AARCH64, The "shared_base" may not be later usable as encoding base, depending on the + // The "shared_base" may not be later usable as encoding base, depending on the // total size of the reserved area and the precomputed_narrow_klass_shift. This is checked // before reserving memory. Here we weed out values already known to be invalid later. - return AARCH64_ONLY(is_aligned(shared_base, 4 * G)) NOT_AARCH64(true); + // Since we cannot predict the range, we use the full maximum encoding range + // (4G). + constexpr size_t range = 4 * G; + address addr = (address)shared_base; + const int shift = ArchiveBuilder::precomputed_narrow_klass_shift(); + return CompressedKlassPointers::check_klass_decode_mode(addr, shift, range); } class DumpClassListCLDClosure : public CLDClosure { @@ -273,7 +278,7 @@ static char* compute_shared_base(size_t cds_max) { err = "too high"; } else if (shared_base_too_high(specified_base, aligned_base, cds_max)) { err = "too high"; - } else if (!shared_base_valid(aligned_base)) { + } else if (!AOTMetaspace::shared_base_valid(aligned_base)) { err = "invalid for this platform"; } else { return aligned_base; @@ -291,7 +296,7 @@ static char* compute_shared_base(size_t cds_max) { // Make sure the default value of SharedBaseAddress specified in globals.hpp is sane. assert(!shared_base_too_high(specified_base, aligned_base, cds_max), "Sanity"); - assert(shared_base_valid(aligned_base), "Sanity"); + assert(AOTMetaspace::shared_base_valid(aligned_base), "Sanity"); return aligned_base; } @@ -1971,14 +1976,12 @@ char* AOTMetaspace::reserve_address_space_for_archives(FileMapInfo* static_mapin const size_t total_range_size = archive_space_size + gap_size + class_space_size; - // Test that class space base address plus shift can be decoded by aarch64, when restored. - const int precomputed_narrow_klass_shift = ArchiveBuilder::precomputed_narrow_klass_shift(); - if (!CompressedKlassPointers::check_klass_decode_mode(base_address, precomputed_narrow_klass_shift, - total_range_size)) { - aot_log_info(aot)("CDS initialization: Cannot use SharedBaseAddress " PTR_FORMAT " with precomputed shift %d.", - p2i(base_address), precomputed_narrow_klass_shift); - use_archive_base_addr = false; - } + // The code for dumping the archive ensures that the base address is valid. + // Here we validate that the base address plus shift can be decoded when + // restored. + assert(shared_base_valid((char*)base_address), + "Cannot use SharedBaseAddress " PTR_FORMAT " with precomputed shift %d.", + p2i(base_address), ArchiveBuilder::precomputed_narrow_klass_shift()); assert(total_range_size > ccs_begin_offset, "must be"); if (use_windows_memory_mapping() && use_archive_base_addr) { diff --git a/src/hotspot/share/cds/aotMetaspace.hpp b/src/hotspot/share/cds/aotMetaspace.hpp index 975b6be76d7..cc90c9da3b0 100644 --- a/src/hotspot/share/cds/aotMetaspace.hpp +++ b/src/hotspot/share/cds/aotMetaspace.hpp @@ -188,6 +188,9 @@ public: static bool use_optimized_module_handling() { return NOT_CDS(false) CDS_ONLY(_use_optimized_module_handling); } static void disable_optimized_module_handling() { _use_optimized_module_handling = false; } + // Check if the supplied shared base address can be used as the encoding base. + static bool shared_base_valid(char* shared_base); + private: static void read_extra_data(JavaThread* current, const char* filename) NOT_CDS_RETURN; static void fork_and_dump_final_static_archive(TRAPS); diff --git a/src/hotspot/share/cds/cdsConfig.cpp b/src/hotspot/share/cds/cdsConfig.cpp index 2dd1d9d0824..63d1f4af4ae 100644 --- a/src/hotspot/share/cds/cdsConfig.cpp +++ b/src/hotspot/share/cds/cdsConfig.cpp @@ -41,6 +41,7 @@ #include "runtime/vmThread.hpp" #include "utilities/defaultStream.hpp" #include "utilities/formatBuffer.hpp" +#include "utilities/globalDefinitions.hpp" bool CDSConfig::_is_dumping_static_archive = false; bool CDSConfig::_is_dumping_preimage_static_archive = false; @@ -123,6 +124,12 @@ void CDSConfig::ergo_initialize() { // etc), there is usually no need to attach to this JVM. FLAG_SET_ERGO(DisableAttachMechanism, true); } + + if (!AOTMetaspace::shared_base_valid((char*)SharedBaseAddress)) { + log_warning(cds)("SharedBaseAddress " PTR_FORMAT " is invalid. Reverting to " PTR_FORMAT, + p2i((void*)SharedBaseAddress), p2i((void*)DEFAULT_SHARED_BASE_ADDRESS)); + FLAG_SET_ERGO(SharedBaseAddress, DEFAULT_SHARED_BASE_ADDRESS); + } } const char* CDSConfig::default_archive_path() { diff --git a/src/hotspot/share/cds/cds_globals.hpp b/src/hotspot/share/cds/cds_globals.hpp index 7df498ca5b9..640cde848b8 100644 --- a/src/hotspot/share/cds/cds_globals.hpp +++ b/src/hotspot/share/cds/cds_globals.hpp @@ -27,6 +27,9 @@ #include "runtime/globals_shared.hpp" +#define DEFAULT_SHARED_BASE_ADDRESS (LP64_ONLY(32*G) \ + NOT_LP64(LINUX_ONLY(2*G) NOT_LINUX(0))) + // // Defines all globals flags used by CDS. // @@ -51,8 +54,7 @@ product(bool, PrintSharedArchiveAndExit, false, \ "Print shared archive file contents") \ \ - product(size_t, SharedBaseAddress, LP64_ONLY(32*G) \ - NOT_LP64(LINUX_ONLY(2*G) NOT_LINUX(0)), \ + product(size_t, SharedBaseAddress, DEFAULT_SHARED_BASE_ADDRESS, \ "Address to allocate shared memory region for class data") \ range(0, SIZE_MAX) \ \ From c0df91c40aa15937e85825d5636c11cc9b4a52e8 Mon Sep 17 00:00:00 2001 From: Benjamin Peterson Date: Tue, 30 Jun 2026 07:39:57 +0000 Subject: [PATCH 037/305] 8387262: Enum constant frame::pc_return_offset is always zero Reviewed-by: dholmes, coleenp --- src/hotspot/cpu/aarch64/frame_aarch64.hpp | 3 +-- src/hotspot/cpu/arm/frame_arm.hpp | 3 +-- src/hotspot/cpu/ppc/frame_ppc.hpp | 4 +--- src/hotspot/cpu/riscv/frame_riscv.hpp | 4 +--- src/hotspot/cpu/s390/frame_s390.hpp | 8 -------- src/hotspot/cpu/x86/frame_x86.hpp | 3 +-- src/hotspot/cpu/zero/frame_zero.hpp | 3 +-- src/hotspot/share/code/aotCodeCache.cpp | 3 --- src/hotspot/share/code/nmethod.cpp | 3 --- src/hotspot/share/code/oopRecorder.cpp | 5 +---- src/hotspot/share/code/relocInfo.cpp | 5 +---- src/hotspot/share/compiler/disassembler.cpp | 5 +---- src/hotspot/share/runtime/deoptimization.cpp | 2 +- src/hotspot/share/runtime/frame.cpp | 4 ++-- src/hotspot/share/runtime/sharedRuntime.cpp | 2 +- src/hotspot/share/runtime/vmStructs.cpp | 2 -- .../share/classes/sun/jvm/hotspot/runtime/Frame.java | 8 -------- 17 files changed, 13 insertions(+), 54 deletions(-) diff --git a/src/hotspot/cpu/aarch64/frame_aarch64.hpp b/src/hotspot/cpu/aarch64/frame_aarch64.hpp index 231710df7d7..ac4740645b8 100644 --- a/src/hotspot/cpu/aarch64/frame_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/frame_aarch64.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2014, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -66,7 +66,6 @@ public: enum { - pc_return_offset = 0, // All frames link_offset = 0, return_addr_offset = 1, diff --git a/src/hotspot/cpu/arm/frame_arm.hpp b/src/hotspot/cpu/arm/frame_arm.hpp index 026bd993981..2ef44414e1c 100644 --- a/src/hotspot/cpu/arm/frame_arm.hpp +++ b/src/hotspot/cpu/arm/frame_arm.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,7 +27,6 @@ public: enum { - pc_return_offset = 0, // All frames link_offset = 0, return_addr_offset = 1, diff --git a/src/hotspot/cpu/ppc/frame_ppc.hpp b/src/hotspot/cpu/ppc/frame_ppc.hpp index 14743c7d75a..bf49bbb7e01 100644 --- a/src/hotspot/cpu/ppc/frame_ppc.hpp +++ b/src/hotspot/cpu/ppc/frame_ppc.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2012, 2025 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -391,8 +391,6 @@ } enum { - // normal return address is 1 bundle past PC - pc_return_offset = 0, // size, in words, of frame metadata (e.g. pc and link) metadata_words = sizeof(java_abi) >> LogBytesPerWord, // size, in words, of metadata at frame bottom, i.e. it is not part of the diff --git a/src/hotspot/cpu/riscv/frame_riscv.hpp b/src/hotspot/cpu/riscv/frame_riscv.hpp index ce5a8dde230..d5f04ee3ff7 100644 --- a/src/hotspot/cpu/riscv/frame_riscv.hpp +++ b/src/hotspot/cpu/riscv/frame_riscv.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2020, 2022, Huawei Technologies Co., Ltd. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -103,8 +103,6 @@ public: enum { - pc_return_offset = 0, - // All frames link_offset = -2, return_addr_offset = -1, diff --git a/src/hotspot/cpu/s390/frame_s390.hpp b/src/hotspot/cpu/s390/frame_s390.hpp index bcdeec43e1a..664a49fdd21 100644 --- a/src/hotspot/cpu/s390/frame_s390.hpp +++ b/src/hotspot/cpu/s390/frame_s390.hpp @@ -542,14 +542,6 @@ unsigned long flags, int max_frames = 0); enum { - // This enum value specifies the offset from the pc remembered by - // call instructions to the location where control returns to - // after a normal return. Most architectures remember the return - // location directly, i.e. the offset is zero. This is the case - // for z/Architecture, too. - // - // Normal return address is the instruction following the branch. - pc_return_offset = 0, metadata_words = 0, metadata_words_at_bottom = 0, metadata_words_at_top = 0, diff --git a/src/hotspot/cpu/x86/frame_x86.hpp b/src/hotspot/cpu/x86/frame_x86.hpp index 546c40fffe4..d97e6b847b4 100644 --- a/src/hotspot/cpu/x86/frame_x86.hpp +++ b/src/hotspot/cpu/x86/frame_x86.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -54,7 +54,6 @@ public: enum { - pc_return_offset = 0, // All frames link_offset = 0, return_addr_offset = 1, diff --git a/src/hotspot/cpu/zero/frame_zero.hpp b/src/hotspot/cpu/zero/frame_zero.hpp index 19096615594..45d1cb82e82 100644 --- a/src/hotspot/cpu/zero/frame_zero.hpp +++ b/src/hotspot/cpu/zero/frame_zero.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright 2007, 2008, 2009, 2010 Red Hat, Inc. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -30,7 +30,6 @@ public: enum { - pc_return_offset = 0, metadata_words = 0, // size, in words, of metadata at frame bottom, i.e. it is not part of the // caller/callee overlap diff --git a/src/hotspot/share/code/aotCodeCache.cpp b/src/hotspot/share/code/aotCodeCache.cpp index 7e1391ed0f0..b70f89b2645 100644 --- a/src/hotspot/share/code/aotCodeCache.cpp +++ b/src/hotspot/share/code/aotCodeCache.cpp @@ -2424,9 +2424,6 @@ int AOTCodeAddressTable::id_for_address(address addr, RelocIterator reloc, CodeB id = search_address(addr, _stubs_addr, _stubs_max); if (id == BAD_ADDRESS_ID) { StubCodeDesc* desc = StubCodeDesc::desc_for(addr); - if (desc == nullptr) { - desc = StubCodeDesc::desc_for(addr + frame::pc_return_offset); - } const char* sub_name = (desc != nullptr) ? desc->name() : ""; assert(false, "Address " INTPTR_FORMAT " for Stub:%s is missing in AOT Code Cache addresses table", p2i(addr), sub_name); } else { diff --git a/src/hotspot/share/code/nmethod.cpp b/src/hotspot/share/code/nmethod.cpp index 27f01797d39..5d7df498102 100644 --- a/src/hotspot/share/code/nmethod.cpp +++ b/src/hotspot/share/code/nmethod.cpp @@ -3783,9 +3783,6 @@ const char* nmethod::reloc_string_for(u_char* begin, u_char* end) { address dest = r->destination(); if (StubRoutines::contains(dest)) { StubCodeDesc* desc = StubCodeDesc::desc_for(dest); - if (desc == nullptr) { - desc = StubCodeDesc::desc_for(dest + frame::pc_return_offset); - } if (desc != nullptr) { st.print(" Stub::%s", desc->name()); return st.as_string(); diff --git a/src/hotspot/share/code/oopRecorder.cpp b/src/hotspot/share/code/oopRecorder.cpp index c37651892cc..93c74be27b9 100644 --- a/src/hotspot/share/code/oopRecorder.cpp +++ b/src/hotspot/share/code/oopRecorder.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -302,9 +302,6 @@ void ExternalsRecorder::print_statistics() { if (addr != nullptr) { if (StubRoutines::contains(addr)) { StubCodeDesc* desc = StubCodeDesc::desc_for(addr); - if (desc == nullptr) { - desc = StubCodeDesc::desc_for(addr + frame::pc_return_offset); - } const char* stub_name = (desc != nullptr) ? desc->name() : ""; tty->print(" stub: %s", stub_name); } else { diff --git a/src/hotspot/share/code/relocInfo.cpp b/src/hotspot/share/code/relocInfo.cpp index 73e4b6de7b4..5295dc0f287 100644 --- a/src/hotspot/share/code/relocInfo.cpp +++ b/src/hotspot/share/code/relocInfo.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -922,9 +922,6 @@ void RelocIterator::print_current_on(outputStream* st) { st->print(" | [destination=" INTPTR_FORMAT "]", p2i(dest)); if (StubRoutines::contains(dest)) { StubCodeDesc* desc = StubCodeDesc::desc_for(dest); - if (desc == nullptr) { - desc = StubCodeDesc::desc_for(dest + frame::pc_return_offset); - } if (desc != nullptr) { st->print(" Stub::%s", desc->name()); } diff --git a/src/hotspot/share/compiler/disassembler.cpp b/src/hotspot/share/compiler/disassembler.cpp index 2c1ef235e07..9dc8956d98d 100644 --- a/src/hotspot/share/compiler/disassembler.cpp +++ b/src/hotspot/share/compiler/disassembler.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -591,9 +591,6 @@ void decode_env::print_address(address adr) { if (Universe::is_fully_initialized()) { if (StubRoutines::contains(adr)) { StubCodeDesc* desc = StubCodeDesc::desc_for(adr); - if (desc == nullptr) { - desc = StubCodeDesc::desc_for(adr + frame::pc_return_offset); - } if (desc != nullptr) { st->print("Stub::%s", desc->name()); if (desc->begin() != adr) { diff --git a/src/hotspot/share/runtime/deoptimization.cpp b/src/hotspot/share/runtime/deoptimization.cpp index d5dccc820f3..e9143a3c4e3 100644 --- a/src/hotspot/share/runtime/deoptimization.cpp +++ b/src/hotspot/share/runtime/deoptimization.cpp @@ -673,7 +673,7 @@ Deoptimization::UnrollBlock* Deoptimization::fetch_unroll_info_helper(JavaThread // as interpreted so the skeleton frame will be walkable // The correct pc will be set when the skeleton frame is completely filled out // The final pc we store in the loop is wrong and will be overwritten below - frame_pcs[number_of_frames - 1 - index ] = Interpreter::deopt_entry(vtos, 0) - frame::pc_return_offset; + frame_pcs[number_of_frames - 1 - index ] = Interpreter::deopt_entry(vtos, 0); callee_parameters = array->element(index)->method()->size_of_parameters(); callee_locals = array->element(index)->method()->max_locals(); diff --git a/src/hotspot/share/runtime/frame.cpp b/src/hotspot/share/runtime/frame.cpp index d99d36571ad..ae04d398043 100644 --- a/src/hotspot/share/runtime/frame.cpp +++ b/src/hotspot/share/runtime/frame.cpp @@ -206,9 +206,9 @@ address frame::raw_pc() const { if (is_deoptimized_frame()) { nmethod* nm = cb()->as_nmethod_or_null(); assert(nm != nullptr, "only nmethod is expected here"); - return nm->deopt_handler_entry() - pc_return_offset; + return nm->deopt_handler_entry(); } else { - return (pc() - pc_return_offset); + return pc(); } } diff --git a/src/hotspot/share/runtime/sharedRuntime.cpp b/src/hotspot/share/runtime/sharedRuntime.cpp index b799063d58e..5489735da39 100644 --- a/src/hotspot/share/runtime/sharedRuntime.cpp +++ b/src/hotspot/share/runtime/sharedRuntime.cpp @@ -1810,7 +1810,7 @@ JRT_LEAF(void, SharedRuntime::fixup_callers_callsite(Method* method, address cal nmethod* caller = cb->as_nmethod(); // Get the return PC for the passed caller PC. - address return_pc = caller_pc + frame::pc_return_offset; + address return_pc = caller_pc; if (!caller->is_in_use() || !NativeCall::is_call_before(return_pc)) { return; diff --git a/src/hotspot/share/runtime/vmStructs.cpp b/src/hotspot/share/runtime/vmStructs.cpp index 856ff947dc4..3868510691a 100644 --- a/src/hotspot/share/runtime/vmStructs.cpp +++ b/src/hotspot/share/runtime/vmStructs.cpp @@ -1709,8 +1709,6 @@ /**********************/ \ NOT_ZERO(PPC64_ONLY(declare_constant(frame::entry_frame_locals_size))) \ \ - declare_constant(frame::pc_return_offset) \ - \ /*************/ \ /* vmSymbols */ \ /*************/ \ diff --git a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/Frame.java b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/Frame.java index 978fb39ad1c..0258fea6808 100644 --- a/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/Frame.java +++ b/src/jdk.hotspot.agent/share/classes/sun/jvm/hotspot/runtime/Frame.java @@ -75,12 +75,6 @@ public abstract class Frame implements Cloneable { /** Size of ConstMethod for computing BCI from BCP (FIXME: hack) */ private static long ConstMethodSize; - private static int pcReturnOffset; - - public static int pcReturnOffset() { - return pcReturnOffset; - } - protected void adjustForDeopt() { if (pc != null) { // Look for a deopt pc and if it is deopted convert to original pc @@ -104,8 +98,6 @@ public abstract class Frame implements Cloneable { // FIXME: not sure whether alignment here is correct or how to // force it (round up to address size?) ConstMethodSize = ConstMethodType.getSize(); - - pcReturnOffset = db.lookupIntConstant("frame::pc_return_offset").intValue(); } protected int bcpToBci(Address bcp, ConstMethod cm) { From e5c0e0f4db80e689feb110d1b1d577adbd232a4d Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Tue, 30 Jun 2026 09:01:13 +0000 Subject: [PATCH 038/305] 8387391: hotspot_gc_shenandoah should include gtests Reviewed-by: xpeng, kdnilsen, wkemper --- test/hotspot/jtreg/TEST.groups | 3 +- .../hotspot/jtreg/gtest/ShenandoahGtests.java | 31 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 test/hotspot/jtreg/gtest/ShenandoahGtests.java diff --git a/test/hotspot/jtreg/TEST.groups b/test/hotspot/jtreg/TEST.groups index e09235f6a39..f400aa22f0b 100644 --- a/test/hotspot/jtreg/TEST.groups +++ b/test/hotspot/jtreg/TEST.groups @@ -314,7 +314,8 @@ tier1_gc_shenandoah = \ gc/shenandoah/compiler/ \ gc/shenandoah/mxbeans/ \ gc/shenandoah/TestSmallHeap.java \ - gc/shenandoah/oom/ + gc/shenandoah/oom/ \ + gtest/ShenandoahGtests.java tier2_gc_shenandoah = \ runtime/MemberName/MemberNameLeak.java \ diff --git a/test/hotspot/jtreg/gtest/ShenandoahGtests.java b/test/hotspot/jtreg/gtest/ShenandoahGtests.java new file mode 100644 index 00000000000..1e8c404fc12 --- /dev/null +++ b/test/hotspot/jtreg/gtest/ShenandoahGtests.java @@ -0,0 +1,31 @@ +/* + * 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 + * @summary Run Shenandoah gtests + * @library /test/lib + * @requires vm.gc.Shenandoah + * @requires vm.debug + * @run main/native GTestWrapper --gtest_filter=Shenandoah* + */ From 6432f5bec4014f9222d141a0af7043170e2f80ed Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Tue, 30 Jun 2026 09:01:38 +0000 Subject: [PATCH 039/305] 8387393: Problemlist compiler/escapeAnalysis/TestBCEscapeAnalyzerOverflow.java on Windows AArch64 Reviewed-by: ayang --- test/hotspot/jtreg/ProblemList.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/hotspot/jtreg/ProblemList.txt b/test/hotspot/jtreg/ProblemList.txt index 8d9de094323..4ac2843190c 100644 --- a/test/hotspot/jtreg/ProblemList.txt +++ b/test/hotspot/jtreg/ProblemList.txt @@ -70,6 +70,8 @@ compiler/c2/aarch64/TestStaticCallStub.java 8359963 generic-aarch64 compiler/unsafe/AlignmentGapAccess.java 8373487 generic-all +compiler/escapeAnalysis/TestBCEscapeAnalyzerOverflow.java 8387392 windows-aarch64 + ############################################################################# # :hotspot_gc From 9333d300aa02831ab78178449f04a4703a0b2082 Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Tue, 30 Jun 2026 09:34:15 +0000 Subject: [PATCH 040/305] 8382213: Shenandoah: Drop weak root processing flags earlier Reviewed-by: kdnilsen, wkemper --- .../shenandoah/shenandoahClosures.inline.hpp | 2 +- .../gc/shenandoah/shenandoahCodeRoots.cpp | 2 +- .../gc/shenandoah/shenandoahConcurrentGC.cpp | 74 ++++++++----------- .../gc/shenandoah/shenandoahConcurrentGC.hpp | 10 +-- .../share/gc/shenandoah/shenandoahHeap.cpp | 34 +++------ .../share/gc/shenandoah/shenandoahHeap.hpp | 4 +- .../share/gc/shenandoah/shenandoahNMethod.cpp | 2 +- .../share/gc/shenandoah/shenandoahOldGC.cpp | 2 +- .../gc/shenandoah/shenandoahPhaseTimings.hpp | 4 +- .../shenandoah/shenandoahStackWatermark.cpp | 16 ++-- 10 files changed, 60 insertions(+), 90 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahClosures.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahClosures.inline.hpp index 0f2a5b48d84..f57a9b20957 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahClosures.inline.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahClosures.inline.hpp @@ -144,7 +144,7 @@ void ShenandoahEvacuateUpdateRootClosureBase::do_oop( template template void ShenandoahEvacuateUpdateRootClosureBase::do_oop_work(T* p) { - assert(_heap->is_concurrent_weak_root_in_progress() || + assert((_heap->is_concurrent_weak_root_in_progress() && _heap->is_evacuation_in_progress()) || _heap->is_concurrent_strong_root_in_progress(), "Only do this in root processing phase"); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahCodeRoots.cpp b/src/hotspot/share/gc/shenandoah/shenandoahCodeRoots.cpp index 3116ec30665..d1c25eb49b4 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahCodeRoots.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahCodeRoots.cpp @@ -111,7 +111,7 @@ public: return; } - { + if (_heap->is_evacuation_in_progress()) { ShenandoahNMethodLocker locker(nm_data->lock()); // Heal oops diff --git a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp index eddeca57fd1..28f04de2f86 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp @@ -183,7 +183,7 @@ bool ShenandoahConcurrentGC::collect(GCCause::Cause cause) { assert(heap->is_concurrent_weak_root_in_progress(), "Must be doing weak roots now"); - // Concurrent stack processing + // Finish all thread/stack roots if needed. This completes stack watermark processing. if (heap->is_evacuation_in_progress()) { entry_thread_roots(); } @@ -211,6 +211,9 @@ bool ShenandoahConcurrentGC::collect(GCCause::Cause cause) { entry_strong_roots(); } + // Roots processing is complete, put the weak roots flag down. + entry_final_roots(); + // Continue the cycle with evacuation and optional update-refs. // This may be skipped if there is nothing to evacuate. // If so, evac_in_progress would be unset by collection set preparation code. @@ -249,9 +252,18 @@ bool ShenandoahConcurrentGC::collect(GCCause::Cause cause) { entry_cleanup_complete(); } else { _abbreviated = true; - if (!entry_final_roots()) { - assert(_degen_point != _degenerated_unset, "Need to know where to start degenerated cycle"); - return false; + + if (heap->mode()->is_generational()) { + entry_complete_abbreviated_cycle(); + + // If the promote-in-place operation was cancelled, we can have the degenerated + // cycle complete the operation. It will see that no evacuations are in progress, + // and that there are regions wanting promotion. The risk with not handling the + // cancellation would be failing to restore top for these regions and leaving + // them unable to serve allocations for the old generation. + if (check_cancellation_and_abort(ShenandoahDegenPoint::_degenerated_evac)) { + return false; + } } // In normal cycle, final-update-refs would verify at the end of the cycle. @@ -275,34 +287,34 @@ bool ShenandoahConcurrentGC::collect(GCCause::Cause cause) { return true; } -bool ShenandoahConcurrentGC::complete_abbreviated_cycle() { +void ShenandoahConcurrentGC::entry_complete_abbreviated_cycle() { shenandoah_assert_generational(); ShenandoahGenerationalHeap* const heap = ShenandoahGenerationalHeap::heap(); + TraceCollectorStats tcs(heap->monitoring_support()->concurrent_collection_counters()); + static const char* msg = "Concurrent complete abbreviated cycle"; + ShenandoahConcurrentPhase gc_phase(msg, ShenandoahPhaseTimings::complete_abbreviated); + EventMark em("%s", msg); + + ShenandoahWorkerScope scope(heap->workers(), + ShenandoahWorkerPolicy::calc_workers_for_conc_evac(), + msg); + // We chose not to evacuate because we found sufficient immediate garbage. // However, there may still be regions to promote in place, so do that now. if (heap->old_generation()->has_in_place_promotions()) { - entry_promote_in_place(); - - // If the promote-in-place operation was cancelled, we can have the degenerated - // cycle complete the operation. It will see that no evacuations are in progress, - // and that there are regions wanting promotion. The risk with not handling the - // cancellation would be failing to restore top for these regions and leaving - // them unable to serve allocations for the old generation.This will leave the weak - // roots flag set (the degenerated cycle will unset it). - if (check_cancellation_and_abort(ShenandoahDegenPoint::_degenerated_evac)) { - return false; - } + ShenandoahTimingsTracker timing(ShenandoahPhaseTimings::complete_abbreviated_promote_in_place); + ShenandoahGCWorkerPhase worker_phase(ShenandoahPhaseTimings::complete_abbreviated_promote_in_place); + heap->promote_regions_in_place(_generation, true); } // At this point, the cycle is effectively complete. If the cycle has been cancelled here, // the control thread will detect it on its next iteration and run a degenerated young cycle. - if (!_generation->is_old()) { + if (!heap->cancelled_gc() && !_generation->is_old()) { + ShenandoahTimingsTracker tracker(ShenandoahPhaseTimings::complete_abbreviated_update_region_ages); heap->update_region_ages(_generation->complete_marking_context()); } - - return true; } void ShenandoahConcurrentGC::vmop_entry_init_mark() { @@ -582,16 +594,6 @@ void ShenandoahConcurrentGC::entry_evacuate() { op_evacuate(); } -void ShenandoahConcurrentGC::entry_promote_in_place() const { - shenandoah_assert_generational(); - - ShenandoahTimingsTracker timing(ShenandoahPhaseTimings::promote_in_place); - ShenandoahGCWorkerPhase worker_phase(ShenandoahPhaseTimings::promote_in_place); - EventMark em("%s", "Promote in place"); - - ShenandoahGenerationalHeap::heap()->promote_regions_in_place(_generation, true); -} - void ShenandoahConcurrentGC::entry_update_thread_roots() { ShenandoahHeap* const heap = ShenandoahHeap::heap(); TraceCollectorStats tcs(heap->monitoring_support()->concurrent_collection_counters()); @@ -1227,26 +1229,14 @@ void ShenandoahConcurrentGC::op_final_update_refs() { } } -bool ShenandoahConcurrentGC::entry_final_roots() { +void ShenandoahConcurrentGC::entry_final_roots() { ShenandoahHeap* const heap = ShenandoahHeap::heap(); TraceCollectorStats tcs(heap->monitoring_support()->concurrent_collection_counters()); - - const char* msg = conc_final_roots_event_message(); ShenandoahConcurrentPhase gc_phase(msg, ShenandoahPhaseTimings::conc_final_roots); EventMark em("%s", msg); - ShenandoahWorkerScope scope(heap->workers(), - ShenandoahWorkerPolicy::calc_workers_for_conc_evac(), - msg); - - if (heap->mode()->is_generational()) { - if (!complete_abbreviated_cycle()) { - return false; - } - } heap->concurrent_final_roots(); - return true; } void ShenandoahConcurrentGC::op_verify_final() { diff --git a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.hpp b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.hpp index fde585b4aa9..e763d1853e3 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.hpp @@ -91,6 +91,8 @@ protected: void entry_class_unloading(); void entry_strong_roots(); void entry_cleanup_early(); + void entry_complete_abbreviated_cycle(); + void entry_final_roots(); void entry_evacuate(); void entry_update_thread_roots(); void entry_update_card_table(); @@ -98,12 +100,6 @@ protected: void entry_update_refs(); void entry_cleanup_complete(); - // This is the last phase of a cycle which performs no evacuations - bool entry_final_roots(); - - // Called when the collection set is empty, but the generational mode has regions to promote in place - void entry_promote_in_place() const; - // Actual work for the phases void op_reset(); void op_init_mark(); @@ -135,8 +131,6 @@ protected: private: void start_mark(); - bool complete_abbreviated_cycle(); - static bool has_in_place_promotions(ShenandoahHeap* heap); // Messages for GC trace events, they have to be immortal for diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp index e60db88974a..ae0c873fa58 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp @@ -1236,7 +1236,6 @@ void ShenandoahHeap::concurrent_prepare_for_update_refs() { // A cancellation at this point means the degenerated cycle must resume from update-refs. set_gc_state_concurrent(EVACUATION, false); - set_gc_state_concurrent(WEAK_ROOTS, false); set_gc_state_concurrent(UPDATE_REFS, true); } @@ -1252,35 +1251,24 @@ void ShenandoahHeap::concurrent_prepare_for_update_refs() { _update_refs_iterator.reset(); } -class ShenandoahCompositeHandshakeClosure : public HandshakeClosure { - HandshakeClosure* _handshake_1; - HandshakeClosure* _handshake_2; - public: - ShenandoahCompositeHandshakeClosure(HandshakeClosure* handshake_1, HandshakeClosure* handshake_2) : - HandshakeClosure(handshake_2->name()), - _handshake_1(handshake_1), _handshake_2(handshake_2) {} - - void do_thread(Thread* thread) override { - _handshake_1->do_thread(thread); - _handshake_2->do_thread(thread); - } -}; - -void ShenandoahHeap::concurrent_final_roots(HandshakeClosure* handshake_closure) { +void ShenandoahHeap::concurrent_final_roots() { { - assert(!is_evacuation_in_progress(), "Should not evacuate for abbreviated or old cycles"); MutexLocker lock(Threads_lock); + +#ifdef ASSERT + for (JavaThreadIteratorWithHandle jtiwh; JavaThread* jt = jtiwh.next();) { + StackWatermark* sw = StackWatermarkSet::get(jt, StackWatermarkKind::gc); + assert(sw == nullptr || sw->processing_completed(), + "Cannot turn off weak roots before stack watermark processing is complete"); + } +#endif + set_gc_state_concurrent(WEAK_ROOTS, false); } ShenandoahGCStatePropagatorHandshakeClosure propagator(_gc_state.raw_value()); Threads::non_java_threads_do(&propagator); - if (handshake_closure == nullptr) { - Handshake::execute(&propagator); - } else { - ShenandoahCompositeHandshakeClosure composite(&propagator, handshake_closure); - Handshake::execute(&composite); - } + Handshake::execute(&propagator); } oop ShenandoahHeap::evacuate_object(oop p, Thread* thread) { diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp index 86707c7e831..9810b316c21 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp @@ -493,8 +493,8 @@ private: // Retires LABs used for evacuation void concurrent_prepare_for_update_refs(); - // Turn off weak roots flag, purge old satb buffers in generational mode - void concurrent_final_roots(HandshakeClosure* handshake_closure = nullptr); + // Turn off weak roots flag + void concurrent_final_roots(); virtual void update_heap_references(ShenandoahGeneration* generation, bool concurrent); // Final update region states diff --git a/src/hotspot/share/gc/shenandoah/shenandoahNMethod.cpp b/src/hotspot/share/gc/shenandoah/shenandoahNMethod.cpp index 5b24cfc979a..b0573c3f677 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahNMethod.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahNMethod.cpp @@ -124,7 +124,7 @@ void ShenandoahNMethod::heal_nmethod(nmethod* nm) { assert(data->lock()->owned_by_self(), "Must hold the lock"); ShenandoahHeap* const heap = ShenandoahHeap::heap(); - if (heap->is_concurrent_weak_root_in_progress() || + if ((heap->is_concurrent_weak_root_in_progress() && heap->is_evacuation_in_progress()) || heap->is_concurrent_strong_root_in_progress()) { heal_nmethod_metadata(data); } else if (heap->is_concurrent_mark_in_progress()) { diff --git a/src/hotspot/share/gc/shenandoah/shenandoahOldGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahOldGC.cpp index ff441a0c868..df41069d922 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahOldGC.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahOldGC.cpp @@ -134,7 +134,7 @@ bool ShenandoahOldGC::collect(GCCause::Cause cause) { // return from here with weak roots in progress. This is not a valid gc state // for any young collections (or allocation failures) that interrupt the old // collection. - heap->concurrent_final_roots(); + entry_final_roots(); // After concurrent old marking finishes, we reclaim immediate garbage. Further, we may also want to expand OLD in order // to make room for anticipated promotions and/or for mixed evacuations. Mixed evacuations are especially likely to diff --git a/src/hotspot/share/gc/shenandoah/shenandoahPhaseTimings.hpp b/src/hotspot/share/gc/shenandoah/shenandoahPhaseTimings.hpp index bc52d755139..dfb42e0b76f 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahPhaseTimings.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahPhaseTimings.hpp @@ -106,8 +106,10 @@ class outputStream; " CE: ") \ SHENANDOAH_SIMPLE_PHASE_DEF(f, conc_update_card_table, "Concurrent Update Cards") \ SHENANDOAH_SIMPLE_PHASE_DEF(f, conc_final_roots, "Concurrent Final Roots") \ - SHENANDOAH_WORKER_PHASE_DEF(f, promote_in_place, " Promote Regions", \ + SHENANDOAH_SIMPLE_PHASE_DEF(f, complete_abbreviated, "Complete Abbreviated Cycle") \ + SHENANDOAH_WORKER_PHASE_DEF(f, complete_abbreviated_promote_in_place, " Promote Regions", \ " PIP: ") \ + SHENANDOAH_SIMPLE_PHASE_DEF(f, complete_abbreviated_update_region_ages, " Update Region Ages") \ SHENANDOAH_SIMPLE_PHASE_DEF(f, final_verify_gross, "Pause Final Verify (G)") \ SHENANDOAH_SIMPLE_PHASE_DEF(f, final_verify, "Pause Final Verify (N)") \ SHENANDOAH_SIMPLE_PHASE_DEF(f, init_update_refs_gross, "Pause Init Update Refs (G)") \ diff --git a/src/hotspot/share/gc/shenandoah/shenandoahStackWatermark.cpp b/src/hotspot/share/gc/shenandoah/shenandoahStackWatermark.cpp index 81c584dfa37..8df2449f8b6 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahStackWatermark.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahStackWatermark.cpp @@ -67,14 +67,13 @@ ShenandoahStackWatermark::ShenandoahStackWatermark(JavaThread* jt) : OopClosure* ShenandoahStackWatermark::closure_from_context(void* context) { if (context != nullptr) { - assert(_heap->is_concurrent_weak_root_in_progress() || + assert((_heap->is_concurrent_weak_root_in_progress() && _heap->is_evacuation_in_progress()) || _heap->is_concurrent_mark_in_progress(), "Only these two phases"); assert(Thread::current()->is_Worker_thread(), "Unexpected thread passing in context: " PTR_FORMAT, p2i(context)); return reinterpret_cast(context); } else { - if (_heap->is_concurrent_weak_root_in_progress()) { - assert(_heap->is_evacuation_in_progress(), "Nothing to evacuate"); + if (_heap->is_concurrent_weak_root_in_progress() && _heap->is_evacuation_in_progress()) { return &_evac_update_oop_cl; } else if (_heap->is_concurrent_mark_in_progress()) { return &_keep_alive_cl; @@ -87,11 +86,9 @@ OopClosure* ShenandoahStackWatermark::closure_from_context(void* context) { void ShenandoahStackWatermark::start_processing_impl(void* context) { NoSafepointVerifier nsv; - ShenandoahHeap* const heap = ShenandoahHeap::heap(); // Process the non-frame part of the thread - if (heap->is_concurrent_weak_root_in_progress()) { - assert(heap->is_evacuation_in_progress(), "Should not be armed"); + if (_heap->is_concurrent_weak_root_in_progress() && _heap->is_evacuation_in_progress()) { // Retire the TLABs, which will force threads to reacquire their TLABs. // This is needed for two reasons. Strong one: new allocations would be with new freeset, // which would be outside the collection set, so no cset writes would happen there. @@ -100,7 +97,7 @@ void ShenandoahStackWatermark::start_processing_impl(void* context) { retire_tlab(); _jt->oops_do_no_frames(closure_from_context(context), &_nm_cl); - } else if (heap->is_concurrent_mark_in_progress()) { + } else if (_heap->is_concurrent_mark_in_progress()) { // We need to reset all TLABs because they might be below the TAMS, and we need to mark // the objects in them. Do not let mutators allocate any new objects in their current TLABs. // It is also a good place to resize the TLAB sizes for future allocations. @@ -129,9 +126,8 @@ void ShenandoahStackWatermark::retire_tlab() { void ShenandoahStackWatermark::process(const frame& fr, RegisterMap& register_map, void* context) { OopClosure* oops = closure_from_context(context); assert(oops != nullptr, "Should not get to here"); - ShenandoahHeap* const heap = ShenandoahHeap::heap(); - assert((heap->is_concurrent_weak_root_in_progress() && heap->is_evacuation_in_progress()) || - heap->is_concurrent_mark_in_progress(), + assert((_heap->is_concurrent_weak_root_in_progress() && _heap->is_evacuation_in_progress()) || + _heap->is_concurrent_mark_in_progress(), "Only these two phases"); fr.oops_do(oops, &_nm_cl, ®ister_map, DerivedPointerIterationMode::_directly); } From 88d111e24ec11910b19cc482b466df01bc03c30e Mon Sep 17 00:00:00 2001 From: Thomas Schatzl Date: Tue, 30 Jun 2026 12:21:51 +0000 Subject: [PATCH 041/305] 8387258: Test jdk/jfr/event/runtime/TestResidentSetSizeEvent.java failed on Windows: The size should be less than or equal to peak Reviewed-by: dholmes, mgronlun --- src/hotspot/os/windows/os_windows.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hotspot/os/windows/os_windows.cpp b/src/hotspot/os/windows/os_windows.cpp index 2b74cccb072..0fc636483f5 100644 --- a/src/hotspot/os/windows/os_windows.cpp +++ b/src/hotspot/os/windows/os_windows.cpp @@ -6387,7 +6387,7 @@ void os::jfr_report_memory_info() { // Send the RSS JFR event EventResidentSetSize event; event.set_size(pmex.WorkingSetSize); - event.set_peak(pmex.PeakWorkingSetSize); + event.set_peak(MAX2(pmex.PeakWorkingSetSize, pmex.WorkingSetSize)); event.commit(); } else { // Log a warning From fa2ca3d087adaeb1bd5f449edf916887d817fb6d Mon Sep 17 00:00:00 2001 From: Martin Doerr Date: Tue, 30 Jun 2026 14:21:06 +0000 Subject: [PATCH 042/305] 8387184: [PPC64] C1 logic operations should support generic constants Reviewed-by: rrich, dbriemann --- src/hotspot/cpu/ppc/assembler_ppc.cpp | 47 ++++++++--- src/hotspot/cpu/ppc/assembler_ppc.hpp | 10 ++- src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp | 36 +++++--- src/hotspot/cpu/ppc/c1_LIRGenerator_ppc.cpp | 15 ++-- src/hotspot/cpu/ppc/ppc.ad | 92 +++------------------ 5 files changed, 85 insertions(+), 115 deletions(-) diff --git a/src/hotspot/cpu/ppc/assembler_ppc.cpp b/src/hotspot/cpu/ppc/assembler_ppc.cpp index ab16fc437e9..406d0b446a4 100644 --- a/src/hotspot/cpu/ppc/assembler_ppc.cpp +++ b/src/hotspot/cpu/ppc/assembler_ppc.cpp @@ -75,23 +75,46 @@ int Assembler::branch_destination(int inst, int pos) { return r; } -// Low-level andi-one-instruction-macro. -void Assembler::andi(Register a, Register s, const long ui16) { - if (is_power_of_2(((unsigned long) ui16)+1)) { +// Low-level andi-one-instruction-macro. May clobber CR0. +void Assembler::andi(Register a, Register s, julong int_or_long_const) { + // Instructions which don't set CR0 are preferred. + if (int_or_long_const == 0) { + // should not be handled as pow2minus1 + li(a, 0); + } else if (is_power_of_2(int_or_long_const + 1)) { // pow2minus1 - clrldi(a, s, 64 - log2i_exact((((unsigned long) ui16)+1))); - } else if (is_power_of_2((jlong) ui16)) { - // pow2 - rlwinm(a, s, 0, 31 - log2i_exact((jlong) ui16), 31 - log2i_exact((jlong) ui16)); - } else if (is_power_of_2((jlong)-ui16)) { - // negpow2 - clrrdi(a, s, log2i_exact((jlong)-ui16)); + clrldi(a, s, 64 - log2i_exact(int_or_long_const + 1)); + } else if (is_power_of_2(-int_or_long_const)) { + // negpow2 (includes (julong)min_jlong) + clrrdi(a, s, log2i_exact(-int_or_long_const)); + } else if (is_uimm((jlong)int_or_long_const, 32) && has_consecutive_ones(int_or_long_const)) { + // consecutive ones + rlwinm(a, s, 0, count_leading_zeros((uint32_t)int_or_long_const), + 31 - count_trailing_zeros((uint32_t)int_or_long_const)); + } else if (is_uimm((jlong)int_or_long_const, 16)) { + // side effect: clobbers CR0 + andi_(a, s, int_or_long_const); } else { - assert(is_uimm(ui16, 16), "must be 16-bit unsigned immediate"); - andi_(a, s, ui16); + assert(is_uimm((jlong)int_or_long_const, 32) && (int_or_long_const & 0xFFFF) == 0, + "not encodable: " UINT64_FORMAT_X, int_or_long_const); + // side effect: clobbers CR0 + andis_(a, s, int_or_long_const >> 16); } } +// Check if int_or_long_const is supported by Assembler::andi. +bool Assembler::andi_supports(julong int_or_long_const) { + // 16 bit always possible by andi_ (but other instructions are preferred) + if (is_uimm((jlong)int_or_long_const, 16)) return true; + + // special cases 32 bit: higher 16 bit and consecutive ones are supported + if (is_uimm((jlong)int_or_long_const, 32) && + ((int_or_long_const & 0xFFFF) == 0 || has_consecutive_ones(int_or_long_const))) return true; + + // special cases 64 bit: clrldi, clrrdi + return is_power_of_2(int_or_long_const + 1) || is_power_of_2(-int_or_long_const); +} + // RegisterOrConstant version. void Assembler::ld(Register d, RegisterOrConstant roc, Register s1) { if (roc.is_constant()) { diff --git a/src/hotspot/cpu/ppc/assembler_ppc.hpp b/src/hotspot/cpu/ppc/assembler_ppc.hpp index f62c93e466c..77c7f63cd06 100644 --- a/src/hotspot/cpu/ppc/assembler_ppc.hpp +++ b/src/hotspot/cpu/ppc/assembler_ppc.hpp @@ -1048,6 +1048,13 @@ class Assembler : public AbstractAssembler { return (julong)x < maxplus1; } + // Test if x has exactly one consecutive range of one bits (e.g. 00111000) + static bool has_consecutive_ones(julong x) { + if (x == max_julong) return true; + if (x == 0) return false; + return is_power_of_2((x >> count_trailing_zeros(x)) + 1); + } + protected: // helpers @@ -1606,7 +1613,8 @@ class Assembler : public AbstractAssembler { inline void isel_0( Register d, ConditionRegister cr, Condition cc, Register b = noreg); // PPC 1, section 3.3.11, Fixed-Point Logical Instructions - void andi( Register a, Register s, long ui16); // optimized version + void andi( Register a, Register s, julong int_or_long_const); // optimized version, may clobber CR0 + static bool andi_supports(julong int_or_long_const); inline void andi_( Register a, Register s, int ui16); inline void andis_( Register a, Register s, int ui16); inline void ori( Register a, Register s, int ui16); diff --git a/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp b/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp index 78fae5c2677..1ec710aad29 100644 --- a/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp +++ b/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp @@ -1669,26 +1669,40 @@ void LIR_Assembler::logic_op(LIR_Code code, LIR_Opr left, LIR_Opr right, LIR_Opr d = dest->as_register_lo(); l = left->as_register_lo(); } - long uimms = (unsigned long)uimm >> 16, - uimmss = (unsigned long)uimm >> 32; + long uimms = (unsigned long)uimm >> 16; switch (code) { case lir_logic_and: - if (uimmss != 0 || (uimms != 0 && (uimm & 0xFFFF) != 0) || is_power_of_2(uimm)) { - __ andi(d, l, uimm); // special cases - } else if (uimms != 0) { __ andis_(d, l, uimms); } - else { __ andi_(d, l, uimm); } + if (Assembler::andi_supports(uimm)) { + __ andi(d, l, uimm); // includes andis_ and special cases + } else { // for operands which are not generated by LIRGenerator::do_LogicOp + __ load_const_optimized(R0, uimm); + __ andr(d, l, R0); + } break; case lir_logic_or: - if (uimms != 0) { assert((uimm & 0xFFFF) == 0, "sanity"); __ oris(d, l, uimms); } - else { __ ori(d, l, uimm); } + if (Assembler::is_uimm(uimm, 16)) { + __ ori(d, l, uimm); + } else if ((uimm & 0xFFFF) == 0 && Assembler::is_uimm(uimms, 16)) { + __ oris(d, l, uimms); + } else { // for operands which are not generated by LIRGenerator::do_LogicOp + __ load_const_optimized(R0, uimm); + __ orr(d, l, R0); + } break; case lir_logic_xor: - if (uimm == -1) { __ nand(d, l, l); } // special case - else if (uimms != 0) { assert((uimm & 0xFFFF) == 0, "sanity"); __ xoris(d, l, uimms); } - else { __ xori(d, l, uimm); } + if (Assembler::is_uimm(uimm, 16)) { + __ xori(d, l, uimm); + } else if ((uimm & 0xFFFF) == 0 && Assembler::is_uimm(uimms, 16)) { + __ xoris(d, l, uimms); + } else if (uimm == -1) { + __ nand(d, l, l); // special case + } else { // for operands which are not generated by LIRGenerator::do_LogicOp + __ load_const_optimized(R0, uimm); + __ xorr(d, l, R0); + } break; default: ShouldNotReachHere(); diff --git a/src/hotspot/cpu/ppc/c1_LIRGenerator_ppc.cpp b/src/hotspot/cpu/ppc/c1_LIRGenerator_ppc.cpp index a652a155f62..56c069053c6 100644 --- a/src/hotspot/cpu/ppc/c1_LIRGenerator_ppc.cpp +++ b/src/hotspot/cpu/ppc/c1_LIRGenerator_ppc.cpp @@ -578,18 +578,13 @@ inline bool can_handle_logic_op_as_uimm(ValueType *type, Bytecodes::Code bc) { Assembler::is_uimm((jlong)((julong)int_or_long_const >> 16), 16)) return true; // see Assembler::andi - if (bc == Bytecodes::_iand && - (is_power_of_2(int_or_long_const+1) || - is_power_of_2(int_or_long_const) || - is_power_of_2(-int_or_long_const))) return true; - if (bc == Bytecodes::_land && - (is_power_of_2((unsigned long)int_or_long_const+1) || - (Assembler::is_uimm(int_or_long_const, 32) && is_power_of_2(int_or_long_const)) || - (int_or_long_const != min_jlong && is_power_of_2(-int_or_long_const)))) return true; + if ((bc == Bytecodes::_iand || bc == Bytecodes::_land)) + return Assembler::andi_supports(int_or_long_const); // special case: xor -1 - if ((bc == Bytecodes::_ixor || bc == Bytecodes::_lxor) && - int_or_long_const == -1) return true; + if ((bc == Bytecodes::_ixor || bc == Bytecodes::_lxor)) + return (int_or_long_const == -1); + return false; } diff --git a/src/hotspot/cpu/ppc/ppc.ad b/src/hotspot/cpu/ppc/ppc.ad index e7464feb4ab..896128f99cc 100644 --- a/src/hotspot/cpu/ppc/ppc.ad +++ b/src/hotspot/cpu/ppc/ppc.ad @@ -9155,61 +9155,14 @@ instruct andI_reg_reg(iRegIdst dst, iRegIsrc src1, iRegIsrc src2) %{ ins_pipe(pipe_class_default); %} -// Left shifted Immediate And -instruct andI_reg_immIhi16(iRegIdst dst, iRegIsrc src1, immIhi16 src2, flagsRegCR0 cr0) %{ +instruct andI_reg_immI(iRegIdst dst, iRegIsrc src1, immI src2, flagsRegCR0 cr0) %{ match(Set dst (AndI src1 src2)); + predicate(Assembler::andi_supports((juint)(n->in(2)->get_int()))); effect(KILL cr0); - format %{ "ANDIS $dst, $src1, $src2.hi" %} - size(4); - ins_encode %{ - __ andis_($dst$$Register, $src1$$Register, (int)((unsigned short)(($src2$$constant & 0xFFFF0000) >> 16))); - %} - ins_pipe(pipe_class_default); -%} - -// Immediate And -instruct andI_reg_uimm16(iRegIdst dst, iRegIsrc src1, uimmI16 src2, flagsRegCR0 cr0) %{ - match(Set dst (AndI src1 src2)); - effect(KILL cr0); - format %{ "ANDI $dst, $src1, $src2" %} size(4); ins_encode %{ - // FIXME: avoid andi_ ? - __ andi_($dst$$Register, $src1$$Register, $src2$$constant); - %} - ins_pipe(pipe_class_default); -%} - -// Immediate And where the immediate is a negative power of 2. -instruct andI_reg_immInegpow2(iRegIdst dst, iRegIsrc src1, immInegpow2 src2) %{ - match(Set dst (AndI src1 src2)); - format %{ "ANDWI $dst, $src1, $src2" %} - size(4); - ins_encode %{ - __ clrrdi($dst$$Register, $src1$$Register, log2i_exact(-(juint)$src2$$constant)); - %} - ins_pipe(pipe_class_default); -%} - -instruct andI_reg_immIpow2minus1(iRegIdst dst, iRegIsrc src1, immIpow2minus1 src2) %{ - match(Set dst (AndI src1 src2)); - format %{ "ANDWI $dst, $src1, $src2" %} - size(4); - ins_encode %{ - __ clrldi($dst$$Register, $src1$$Register, 64 - log2i_exact((juint)$src2$$constant + 1u)); - %} - ins_pipe(pipe_class_default); -%} - -instruct andI_reg_immIpowerOf2(iRegIdst dst, iRegIsrc src1, immIpowerOf2 src2) %{ - match(Set dst (AndI src1 src2)); - predicate(UseRotateAndMaskInstructionsPPC64); - format %{ "ANDWI $dst, $src1, $src2" %} - size(4); - ins_encode %{ - int bitpos = 31 - log2i_exact((juint)$src2$$constant); - __ rlwinm($dst$$Register, $src1$$Register, 0, bitpos, bitpos); + __ andi($dst$$Register, $src1$$Register, (juint)$src2$$constant); // optimized version %} ins_pipe(pipe_class_default); %} @@ -9227,50 +9180,27 @@ instruct andL_reg_reg(iRegLdst dst, iRegLsrc src1, iRegLsrc src2) %{ ins_pipe(pipe_class_default); %} -// Immediate And long -instruct andL_reg_uimm16(iRegLdst dst, iRegLsrc src1, uimmL16 src2, flagsRegCR0 cr0) %{ +instruct andL_reg_immL(iRegLdst dst, iRegLsrc src1, immL src2, flagsRegCR0 cr0) %{ match(Set dst (AndL src1 src2)); + predicate(Assembler::andi_supports(n->in(2)->get_long())); effect(KILL cr0); - format %{ "ANDI $dst, $src1, $src2 \t// long" %} size(4); ins_encode %{ - // FIXME: avoid andi_ ? - __ andi_($dst$$Register, $src1$$Register, $src2$$constant); - %} - ins_pipe(pipe_class_default); -%} - -// Immediate And Long where the immediate is a negative power of 2. -instruct andL_reg_immLnegpow2(iRegLdst dst, iRegLsrc src1, immLnegpow2 src2) %{ - match(Set dst (AndL src1 src2)); - format %{ "ANDDI $dst, $src1, $src2" %} - size(4); - ins_encode %{ - __ clrrdi($dst$$Register, $src1$$Register, log2i_exact(-(julong)$src2$$constant)); - %} - ins_pipe(pipe_class_default); -%} - -instruct andL_reg_immLpow2minus1(iRegLdst dst, iRegLsrc src1, immLpow2minus1 src2) %{ - match(Set dst (AndL src1 src2)); - format %{ "ANDDI $dst, $src1, $src2" %} - size(4); - ins_encode %{ - __ clrldi($dst$$Register, $src1$$Register, 64 - log2i_exact((julong)$src2$$constant + 1ull)); + __ andi($dst$$Register, $src1$$Register, $src2$$constant); // optimized version %} ins_pipe(pipe_class_default); %} // AndL + ConvL2I. -instruct convL2I_andL_reg_immLpow2minus1(iRegIdst dst, iRegLsrc src1, immLpow2minus1 src2) %{ +instruct convL2I_andL_reg_immL(iRegIdst dst, iRegLsrc src1, immL src2, flagsRegCR0 cr0) %{ match(Set dst (ConvL2I (AndL src1 src2))); - ins_cost(DEFAULT_COST); - - format %{ "ANDDI $dst, $src1, $src2 \t// long + l2i" %} + predicate(Assembler::andi_supports(n->in(1)->in(2)->get_long())); + effect(KILL cr0); + format %{ "ANDI $dst, $src1, $src2 \t// long + l2i" %} size(4); ins_encode %{ - __ clrldi($dst$$Register, $src1$$Register, 64 - log2i_exact((julong)$src2$$constant + 1ull)); + __ andi($dst$$Register, $src1$$Register, $src2$$constant); // optimized version %} ins_pipe(pipe_class_default); %} From 45d3532a2ce7e2d0cc1c6c65b0cf7301569af1b6 Mon Sep 17 00:00:00 2001 From: Matias Saavedra Silva Date: Tue, 30 Jun 2026 15:28:53 +0000 Subject: [PATCH 043/305] 8380750: Test runtime/cds/appcds/TestSerialGCWithCDS.java#id1 failed: StringIndexOutOfBoundsException Reviewed-by: coleenp, iklam --- test/hotspot/jtreg/runtime/cds/TestCDSVMCrash.java | 2 +- test/lib/jdk/test/lib/cds/CDSTestUtils.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/hotspot/jtreg/runtime/cds/TestCDSVMCrash.java b/test/hotspot/jtreg/runtime/cds/TestCDSVMCrash.java index 8241b0f9a2e..2c796243cbf 100644 --- a/test/hotspot/jtreg/runtime/cds/TestCDSVMCrash.java +++ b/test/hotspot/jtreg/runtime/cds/TestCDSVMCrash.java @@ -62,7 +62,7 @@ public class TestCDSVMCrash { throw new Error("Expected VM to crash"); } catch(RuntimeException e) { if (!e.getMessage().contains("A fatal error has been detected")) { - throw new Error("Expected message: A fatal error has been detected"); + throw new Error("Expected message: A fatal error has been detected. Instead message is: " + e.getMessage()); } } System.out.println("PASSED"); diff --git a/test/lib/jdk/test/lib/cds/CDSTestUtils.java b/test/lib/jdk/test/lib/cds/CDSTestUtils.java index 59e4a1bbbde..8060eb92a87 100644 --- a/test/lib/jdk/test/lib/cds/CDSTestUtils.java +++ b/test/lib/jdk/test/lib/cds/CDSTestUtils.java @@ -703,7 +703,7 @@ public class CDSTestUtils { static String getCrashMessage(String stdOut) { int start = stdOut.indexOf("# A fatal error has been detected by the Java Runtime Environment:"); - int end = stdOut.indexOf(".log", start) + 4; + int end = stdOut.indexOf("# JRE version", start); return stdOut.substring(start, end); } From 4bf4a60f76a9980fdce54fa85cec223ebd5e025e Mon Sep 17 00:00:00 2001 From: Mark Powers Date: Tue, 30 Jun 2026 18:07:11 +0000 Subject: [PATCH 044/305] 8387123: Remove LuxTrust Global Root CA Reviewed-by: mullan, rhalade --- .../share/data/cacerts/luxtrustglobalrootca | 28 ------------------- .../security/lib/cacerts/VerifyCACerts.java | 10 ++----- 2 files changed, 3 insertions(+), 35 deletions(-) delete mode 100644 src/java.base/share/data/cacerts/luxtrustglobalrootca diff --git a/src/java.base/share/data/cacerts/luxtrustglobalrootca b/src/java.base/share/data/cacerts/luxtrustglobalrootca deleted file mode 100644 index 7fb3d818f80..00000000000 --- a/src/java.base/share/data/cacerts/luxtrustglobalrootca +++ /dev/null @@ -1,28 +0,0 @@ -Owner: CN=LuxTrust Global Root, O=LuxTrust s.a., C=LU -Issuer: CN=LuxTrust Global Root, O=LuxTrust s.a., C=LU -Serial number: bb8 -Valid from: Thu Mar 17 09:51:37 GMT 2011 until: Wed Mar 17 09:51:37 GMT 2021 -Signature algorithm name: SHA256withRSA -Subject Public Key Algorithm: 2048-bit RSA key -Version: 3 ------BEGIN CERTIFICATE----- -MIIDZDCCAkygAwIBAgICC7gwDQYJKoZIhvcNAQELBQAwRDELMAkGA1UEBhMCTFUx -FjAUBgNVBAoTDUx1eFRydXN0IHMuYS4xHTAbBgNVBAMTFEx1eFRydXN0IEdsb2Jh -bCBSb290MB4XDTExMDMxNzA5NTEzN1oXDTIxMDMxNzA5NTEzN1owRDELMAkGA1UE -BhMCTFUxFjAUBgNVBAoTDUx1eFRydXN0IHMuYS4xHTAbBgNVBAMTFEx1eFRydXN0 -IEdsb2JhbCBSb290MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsn+n -QPAiygz267Hxyw6VV0B1r6A/Ps7sqjJX5hmxZ0OYWmt8s7j6eJyqpoSyYBuAQc5j -zR8XCJmk9e8+EsdMsFeaXHhAePxFjdqRZ9w6Ubltc+a3OY52OrQfBfVpVfmTz3iI -Sr6qm9d7R1tGBEyCFqY19vx039a0r9jitScRdFmiwmYsaArhmIiIPIoFdRTjuK7z -CISbasE/MRivJ6VLm6T9eTHemD0OYcqHmMH4ijCc+j4z1aXEAwfh95Z0GAAnOCfR -K6qq4UFFi2/xJcLcopeVx0IUM115hCNq52XAV6DYXaljAeew5Ivo+MVjuOVsdJA9 -x3f8K7p56aTGEnin/wIDAQABo2AwXjAMBgNVHRMEBTADAQH/MA4GA1UdDwEB/wQE -AwIBBjAfBgNVHSMEGDAWgBQXFYWJCS8kh28/HRvk8pZ5g0gTzjAdBgNVHQ4EFgQU -FxWFiQkvJIdvPx0b5PKWeYNIE84wDQYJKoZIhvcNAQELBQADggEBAFrwHNDUUM9B -fua4nX3DcNBeNv9ujnov3kgR1TQuPLdFwlQlp+HBHjeDtpSutkVIA+qVvuucarQ3 -XB8u02uCgUNbCj8RVWOs+nwIAjegPDkEM/6XMshS5dklTbDG7mgfcKpzzlcD3H0K -DTPy0lrfCmw7zBFRlxqkIaKFNQLXgCLShLL4wKpov9XrqsMLq6F8K/f1O4fhVFfs -BSTveUJO84ton+Ruy4KZycwq3FPCH3CDqyEPVrRI/98HIrOM+R2mBN8tAza53W/+ -MYhm/2xtRDSvCHc+JtJy9LtHVpM8mGPhM7uZI5K1g3noHZ9nrWLWidb2/CfeMifL -hNp3hSGhEiE= ------END CERTIFICATE----- diff --git a/test/jdk/sun/security/lib/cacerts/VerifyCACerts.java b/test/jdk/sun/security/lib/cacerts/VerifyCACerts.java index c2c58b36c38..82b6a6c257e 100644 --- a/test/jdk/sun/security/lib/cacerts/VerifyCACerts.java +++ b/test/jdk/sun/security/lib/cacerts/VerifyCACerts.java @@ -28,7 +28,7 @@ * 8223499 8225392 8232019 8234245 8233223 8225068 8225069 8243321 8243320 * 8243559 8225072 8258630 8259312 8256421 8225081 8225082 8225083 8245654 * 8305975 8304760 8307134 8295894 8314960 8317373 8317374 8318759 8319187 - * 8321408 8316138 8341057 8303770 8350498 8359170 8361212 8372351 + * 8321408 8316138 8341057 8303770 8350498 8359170 8361212 8372351 8387123 * @summary Check root CA entries in cacerts file */ import java.io.ByteArrayInputStream; @@ -47,12 +47,12 @@ public class VerifyCACerts { + File.separator + "security" + File.separator + "cacerts"; // The numbers of certs now. - private static final int COUNT = 111; + private static final int COUNT = 110; // SHA-256 of cacerts, can be generated with // shasum -a 256 cacerts | sed -e 's/../&:/g' | tr '[:lower:]' '[:upper:]' | cut -c1-95 private static final String CHECKSUM - = "26:75:A0:AA:6E:7C:15:8B:BC:CF:11:81:38:3E:E7:94:31:9E:36:2D:F9:A6:BC:88:E1:A5:F8:46:9A:4C:1D:D7"; + = "AA:C2:64:41:28:06:1F:83:92:54:7C:DD:95:82:61:4C:8F:FA:09:7B:17:64:A7:A8:7C:A9:F6:27:25:95:2D:BB"; // Hex formatter to upper case with ":" delimiter private static final HexFormat HEX = HexFormat.ofDelimiter(":").withUpperCase(); @@ -143,8 +143,6 @@ public class VerifyCACerts { "96:BC:EC:06:26:49:76:F3:74:60:77:9A:CF:28:C5:A7:CF:E8:A3:C0:AA:E1:1A:8F:FC:EE:05:C0:BD:DF:08:C6"); put("letsencryptisrgx2 [jdk]", "69:72:9B:8E:15:A8:6E:FC:17:7A:57:AF:B7:17:1D:FC:64:AD:D2:8C:2F:CA:8C:F1:50:7E:34:45:3C:CB:14:70"); - put("luxtrustglobalrootca [jdk]", - "A1:B2:DB:EB:64:E7:06:C6:16:9E:3C:41:18:B2:3B:AA:09:01:8A:84:27:66:6D:8B:F0:E2:88:91:EC:05:19:50"); put("quovadisrootca [jdk]", "A4:5E:DE:3B:BB:F0:9C:8A:E1:5C:72:EF:C0:72:68:D6:93:A2:1C:99:6F:D5:1E:67:CA:07:94:60:FD:6D:88:73"); put("quovadisrootca1g3 [jdk]", @@ -296,8 +294,6 @@ public class VerifyCACerts { add("addtrustexternalca [jdk]"); // Valid until: Sat May 30 10:44:50 GMT 2020 add("addtrustqualifiedca [jdk]"); - // Valid until: Wed Mar 17 02:51:37 PDT 2021 - add("luxtrustglobalrootca [jdk]"); // Valid until: Wed Mar 17 11:33:33 PDT 2021 add("quovadisrootca [jdk]"); // Valid until: Sat May 21 04:00:00 GMT 2022 From c7816b0b444019aef047b7f0d8281cbf3b8d17fb Mon Sep 17 00:00:00 2001 From: Chad Rakoczy Date: Tue, 30 Jun 2026 19:44:04 +0000 Subject: [PATCH 045/305] 8382135: AArch64: HotCodeCollectorMoveFunction.java fails intermittently Reviewed-by: eastigeevich, aph --- src/hotspot/share/runtime/hotCodeSampler.cpp | 16 +++++++--------- .../hotcode/HotCodeCollectorMoveFunction.java | 12 +++++++++++- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/hotspot/share/runtime/hotCodeSampler.cpp b/src/hotspot/share/runtime/hotCodeSampler.cpp index 94242b718a5..e033765c1f2 100644 --- a/src/hotspot/share/runtime/hotCodeSampler.cpp +++ b/src/hotspot/share/runtime/hotCodeSampler.cpp @@ -61,15 +61,13 @@ bool ThreadSampler::sample_all_java_threads() { continue; } - if (CodeCache::contains(pc)) { - nmethod* nm = CodeCache::find_blob(pc)->as_nmethod_or_null(); - if (nm != nullptr) { - bool created = false; - int *count = _samples.put_if_absent(nm, 0, &created); - (*count)++; - if (created) { - _samples.maybe_grow(); - } + CodeBlob* cb = CodeCache::find_blob(pc); + if (cb != nullptr && cb->is_nmethod()) { + bool created = false; + int *count = _samples.put_if_absent(cb->as_nmethod(), 0, &created); + (*count)++; + if (created) { + _samples.maybe_grow(); } } } diff --git a/test/hotspot/jtreg/compiler/hotcode/HotCodeCollectorMoveFunction.java b/test/hotspot/jtreg/compiler/hotcode/HotCodeCollectorMoveFunction.java index 5677ca88eb2..2b93c24e255 100644 --- a/test/hotspot/jtreg/compiler/hotcode/HotCodeCollectorMoveFunction.java +++ b/test/hotspot/jtreg/compiler/hotcode/HotCodeCollectorMoveFunction.java @@ -79,6 +79,8 @@ public class HotCodeCollectorMoveFunction { private static final int C2_LEVEL = 4; private static final int FUNC_RUN_MILLIS = 60_000; + private static volatile int blackholeCount = 0; + static { try { method = Runner.class.getMethod("func"); @@ -111,7 +113,15 @@ public class HotCodeCollectorMoveFunction { public static void func() { long start = System.currentTimeMillis(); - while (System.currentTimeMillis() - start < FUNC_RUN_MILLIS) {} + while (System.currentTimeMillis() - start < FUNC_RUN_MILLIS) { + // Perform multiplicative LCG to ensure the compiler does not optimize away the code. + // Integer overflow is used for the modulus so the loop terminates after (2^32)/4 iterations + int num = 1; + do { + blackholeCount++; + num *= 69069; + } while (num != 1); + } } } } From db357f7e089127d550e6ea872d533b8ce22e7992 Mon Sep 17 00:00:00 2001 From: "Daniel D. Daugherty" Date: Tue, 30 Jun 2026 23:25:45 +0000 Subject: [PATCH 046/305] 8387554: ProblemList vmTestbase/nsk/jvmti/unit/functions/Dispose/JvmtiTest/TestDescription.java in virtual thread configs 8387557: ProblemList vmTestbase/nsk/jvmti/scenarios/capability/CM02/cm02t001/TestDescription.java in virtual thread configs 8387558: ProblemList vmTestbase/nsk/jvmti/unit/timers/JvmtiTest/TestDescription.java on windows 8387560: ProblemList vmTestbase/nsk/jdi/EventRequestManager/threadStartRequests/thrstartreq002/TestDescription.java in virtual thread configs Reviewed-by: sspitsyn --- test/hotspot/jtreg/ProblemList-Virtual.txt | 5 +++++ test/hotspot/jtreg/ProblemList.txt | 1 + 2 files changed, 6 insertions(+) diff --git a/test/hotspot/jtreg/ProblemList-Virtual.txt b/test/hotspot/jtreg/ProblemList-Virtual.txt index 705cded007a..b30a09a7710 100644 --- a/test/hotspot/jtreg/ProblemList-Virtual.txt +++ b/test/hotspot/jtreg/ProblemList-Virtual.txt @@ -29,6 +29,11 @@ serviceability/AsyncGetCallTrace/MyPackage/ASGCTBaseTest.java 8308026 generic-al serviceability/jvmti/Heap/IterateHeapWithEscapeAnalysisEnabled.java 8264699 generic-all vmTestbase/vm/mlvm/indy/func/jvmti/mergeCP_indy2manyDiff_a/TestDescription.java 8308367 generic-all +vmTestbase/nsk/jvmti/unit/functions/Dispose/JvmtiTest/TestDescription.java 8387429 generic-all +vmTestbase/nsk/jvmti/scenarios/capability/CM02/cm02t001/TestDescription.java 8299217 generic-all +vmTestbase/nsk/jdi/EventRequestManager/threadStartRequests/thrstartreq002/TestDescription.java 8327967 generic-all + + #### ## Classes not unloaded as expected (TODO, need to check if FJ keeps a reference) diff --git a/test/hotspot/jtreg/ProblemList.txt b/test/hotspot/jtreg/ProblemList.txt index 4ac2843190c..a9f70fc97a4 100644 --- a/test/hotspot/jtreg/ProblemList.txt +++ b/test/hotspot/jtreg/ProblemList.txt @@ -162,6 +162,7 @@ vmTestbase/metaspace/gc/firstGC_default/TestDescription.java 8208250 generic-all vmTestbase/nsk/jvmti/scenarios/capability/CM03/cm03t001/TestDescription.java 8073470 linux-all vmTestbase/nsk/jvmti/scenarios/events/EM02/em02t006/TestDescription.java 8372206 generic-all vmTestbase/nsk/jvmti/InterruptThread/intrpthrd003/TestDescription.java 8288911 macosx-all +vmTestbase/nsk/jvmti/unit/timers/JvmtiTest/TestDescription.java 8235348 windows-x64 vmTestbase/jit/escape/LockCoarsening/LockCoarsening001.java 8148743 generic-all vmTestbase/jit/escape/LockCoarsening/LockCoarsening002.java 8208259 generic-all From aa17cf560835706351f2ce69886b3e23049f6bb1 Mon Sep 17 00:00:00 2001 From: Gui Cao Date: Wed, 1 Jul 2026 02:34:42 +0000 Subject: [PATCH 047/305] 8387381: RISC-V: assert failed with fastdebug build on systems with different core types Reviewed-by: dzhang, fyang --- src/hotspot/os_cpu/linux_riscv/riscv_hwprobe.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/hotspot/os_cpu/linux_riscv/riscv_hwprobe.cpp b/src/hotspot/os_cpu/linux_riscv/riscv_hwprobe.cpp index f48df178ce6..3ede62e14cd 100644 --- a/src/hotspot/os_cpu/linux_riscv/riscv_hwprobe.cpp +++ b/src/hotspot/os_cpu/linux_riscv/riscv_hwprobe.cpp @@ -261,13 +261,16 @@ void RiscvHwprobe::add_features_from_query_result() { // ====== non-extensions ====== // - if (is_valid(RISCV_HWPROBE_KEY_MARCHID)) { + // For value-type keys, the kernel returns (uint64_t)-1 when CPUs in the + // query set disagree (different core types). Skip these as the value is + // not meaningful for the system as a whole. + if (is_valid(RISCV_HWPROBE_KEY_MARCHID) && query[RISCV_HWPROBE_KEY_MARCHID].value != (uint64_t)-1) { VM_Version::marchid.enable_feature(query[RISCV_HWPROBE_KEY_MARCHID].value); } - if (is_valid(RISCV_HWPROBE_KEY_MIMPID)) { + if (is_valid(RISCV_HWPROBE_KEY_MIMPID) && query[RISCV_HWPROBE_KEY_MIMPID].value != (uint64_t)-1) { VM_Version::mimpid.enable_feature(query[RISCV_HWPROBE_KEY_MIMPID].value); } - if (is_valid(RISCV_HWPROBE_KEY_MVENDORID)) { + if (is_valid(RISCV_HWPROBE_KEY_MVENDORID) && query[RISCV_HWPROBE_KEY_MVENDORID].value != (uint64_t)-1) { VM_Version::mvendorid.enable_feature(query[RISCV_HWPROBE_KEY_MVENDORID].value); } // RISCV_HWPROBE_KEY_CPUPERF_0 is deprecated and returns similar values From 64ae319b5cd457aeb23d910d5ce09541028593fb Mon Sep 17 00:00:00 2001 From: Tobias Hartmann Date: Wed, 1 Jul 2026 05:55:55 +0000 Subject: [PATCH 048/305] 8387334: IR Framework tests should run in jtreg driver mode Reviewed-by: epeter, shade, chagedorn, mchevalier --- .../compiler/c2/ReachabilityFenceTest.java | 2 +- .../jtreg/compiler/c2/TestMergeStores.java | 8 +- .../c2/irTests/ConstructorBarriers.java | 2 +- .../TestVectorizationMismatchedAccess.java | 8 +- .../c2/riscv64/TestIntegerReverse.java | 4 +- .../compiler/c2/riscv64/TestLongReverse.java | 4 +- .../TestDebugDuringExceptionCatching.java | 2 +- .../CmpDisjointButNonOrderedRangesLong.java | 4 +- .../TestShortRunningLongCountedLoop.java | 10 +-- .../loopopts/TestHasTruncationWrap.java | 4 +- .../TestRedundantSafepointElimination.java | 2 +- .../rangechecks/TestFoldCompares.java | 6 +- .../compiler/stable/LazyConstantsIrTest.java | 2 +- .../TestRotateByteAndShortVector.java | 2 +- .../TestRoundVectorDoubleRandom.java | 2 +- .../TestRoundVectorFloatRandom.java | 2 +- .../vectorization/runner/ArrayCopyTest.java | 12 +-- .../runner/ArrayIndexFillTest.java | 14 +--- .../runner/ArrayInvariantFillTest.java | 37 +++++---- .../runner/ArrayShiftOpTest.java | 14 +--- .../runner/ArrayTypeConvertTest.java | 29 +------ .../runner/ArrayUnsafeOpTest.java | 12 +-- .../runner/BasicBooleanOpTest.java | 14 +--- .../vectorization/runner/BasicByteOpTest.java | 22 +++--- .../vectorization/runner/BasicCharOpTest.java | 12 +-- .../runner/BasicDoubleOpTest.java | 14 +--- .../runner/BasicFloatOpTest.java | 12 +-- .../vectorization/runner/BasicIntOpTest.java | 14 +--- .../vectorization/runner/BasicLongOpTest.java | 14 +--- .../runner/BasicShortOpTest.java | 12 +-- .../runner/LoopArrayIndexComputeTest.java | 33 +++----- .../runner/LoopCombinedOpTest.java | 31 ++------ .../runner/LoopControlFlowTest.java | 12 +-- .../runner/LoopLiveOutNodesTest.java | 14 +--- .../runner/LoopRangeStrideTest.java | 14 +--- .../runner/LoopReductionOpTest.java | 11 +-- .../runner/MultipleLoopsTest.java | 14 +--- .../runner/StripMinedLoopTest.java | 20 +++-- .../runner/VectorizationTestRunner.java | 75 ++++++++++++++----- 39 files changed, 180 insertions(+), 340 deletions(-) diff --git a/test/hotspot/jtreg/compiler/c2/ReachabilityFenceTest.java b/test/hotspot/jtreg/compiler/c2/ReachabilityFenceTest.java index d0bce024696..14c4f7b5a48 100644 --- a/test/hotspot/jtreg/compiler/c2/ReachabilityFenceTest.java +++ b/test/hotspot/jtreg/compiler/c2/ReachabilityFenceTest.java @@ -38,7 +38,7 @@ import compiler.lib.ir_framework.*; * @summary Tests to ensure that reachabilityFence() correctly keeps objects from being collected prematurely. * @modules java.base/jdk.internal.misc * @library /test/lib / - * @run main/othervm -Xbatch compiler.c2.ReachabilityFenceTest + * @run driver ${test.main.class} */ public class ReachabilityFenceTest { private static final int SIZE = 100; diff --git a/test/hotspot/jtreg/compiler/c2/TestMergeStores.java b/test/hotspot/jtreg/compiler/c2/TestMergeStores.java index 5e6a757dd5f..99143f04dcd 100644 --- a/test/hotspot/jtreg/compiler/c2/TestMergeStores.java +++ b/test/hotspot/jtreg/compiler/c2/TestMergeStores.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -38,7 +38,7 @@ import java.util.Random; * @summary Test merging of consecutive stores * @modules java.base/jdk.internal.misc * @library /test/lib / - * @run main/timeout=480 compiler.c2.TestMergeStores aligned + * @run driver/timeout=480 ${test.main.class} aligned */ /* @@ -48,7 +48,7 @@ import java.util.Random; * @summary Test merging of consecutive stores * @modules java.base/jdk.internal.misc * @library /test/lib / - * @run main/timeout=480 compiler.c2.TestMergeStores unaligned + * @run driver/timeout=480 ${test.main.class} unaligned */ /* @@ -58,7 +58,7 @@ import java.util.Random; * @summary Test merging of consecutive stores * @modules java.base/jdk.internal.misc * @library /test/lib / - * @run main/timeout=480 compiler.c2.TestMergeStores StressIGVN + * @run driver/timeout=480 ${test.main.class} StressIGVN */ public class TestMergeStores { diff --git a/test/hotspot/jtreg/compiler/c2/irTests/ConstructorBarriers.java b/test/hotspot/jtreg/compiler/c2/irTests/ConstructorBarriers.java index ba7e7d851b0..66dabcebf80 100644 --- a/test/hotspot/jtreg/compiler/c2/irTests/ConstructorBarriers.java +++ b/test/hotspot/jtreg/compiler/c2/irTests/ConstructorBarriers.java @@ -31,7 +31,7 @@ import compiler.lib.ir_framework.*; * @summary Test barriers emitted in constructors * @library /test/lib / * @requires os.arch=="aarch64" | os.arch=="riscv64" | os.arch=="x86_64" | os.arch=="amd64" - * @run main compiler.c2.irTests.ConstructorBarriers + * @run driver ${test.main.class} */ public class ConstructorBarriers { public static void main(String[] args) { diff --git a/test/hotspot/jtreg/compiler/c2/irTests/TestVectorizationMismatchedAccess.java b/test/hotspot/jtreg/compiler/c2/irTests/TestVectorizationMismatchedAccess.java index 5524b5d7b6c..9556fce988d 100644 --- a/test/hotspot/jtreg/compiler/c2/irTests/TestVectorizationMismatchedAccess.java +++ b/test/hotspot/jtreg/compiler/c2/irTests/TestVectorizationMismatchedAccess.java @@ -1,6 +1,6 @@ /* * Copyright (c) 2023, Red Hat, Inc. All rights reserved. - * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,7 +26,6 @@ package compiler.c2.irTests; import compiler.lib.ir_framework.*; import jdk.test.lib.Utils; -import jdk.test.whitebox.WhiteBox; import jdk.internal.misc.Unsafe; import java.util.Random; import java.util.Arrays; @@ -40,15 +39,12 @@ import java.util.List; * @summary C2: vectorization fails on simple ByteBuffer loop * @modules java.base/jdk.internal.misc * @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.c2.irTests.TestVectorizationMismatchedAccess + * @run driver ${test.main.class} */ public class TestVectorizationMismatchedAccess { private static final Unsafe UNSAFE = Unsafe.getUnsafe(); private static final Random RANDOM = Utils.getRandomInstance(); - private final static WhiteBox wb = WhiteBox.getWhiteBox(); public static void main(String[] args) { TestFramework framework = new TestFramework(); diff --git a/test/hotspot/jtreg/compiler/c2/riscv64/TestIntegerReverse.java b/test/hotspot/jtreg/compiler/c2/riscv64/TestIntegerReverse.java index 8b3abbb0300..82bb79d3c1f 100644 --- a/test/hotspot/jtreg/compiler/c2/riscv64/TestIntegerReverse.java +++ b/test/hotspot/jtreg/compiler/c2/riscv64/TestIntegerReverse.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2025, Rivos Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -30,7 +30,7 @@ * * @library /test/lib / * @requires os.arch == "riscv64" & vm.cpu.features ~= ".*zbkb.*" - * @run main/othervm compiler.c2.riscv64.TestIntegerReverse + * @run driver ${test.main.class} */ package compiler.c2.riscv64; diff --git a/test/hotspot/jtreg/compiler/c2/riscv64/TestLongReverse.java b/test/hotspot/jtreg/compiler/c2/riscv64/TestLongReverse.java index 01c3b871ffa..807a58a18f3 100644 --- a/test/hotspot/jtreg/compiler/c2/riscv64/TestLongReverse.java +++ b/test/hotspot/jtreg/compiler/c2/riscv64/TestLongReverse.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2025, Rivos Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -30,7 +30,7 @@ * * @library /test/lib / * @requires os.arch == "riscv64" & vm.cpu.features ~= ".*zbkb.*" - * @run main/othervm compiler.c2.riscv64.TestLongReverse + * @run driver ${test.main.class} */ package compiler.c2.riscv64; diff --git a/test/hotspot/jtreg/compiler/exceptions/TestDebugDuringExceptionCatching.java b/test/hotspot/jtreg/compiler/exceptions/TestDebugDuringExceptionCatching.java index 9be192d1f55..026b2d15b77 100644 --- a/test/hotspot/jtreg/compiler/exceptions/TestDebugDuringExceptionCatching.java +++ b/test/hotspot/jtreg/compiler/exceptions/TestDebugDuringExceptionCatching.java @@ -43,7 +43,7 @@ import test.java.lang.invoke.lib.InstructionHelper; * @library /test/lib /test/jdk/java/lang/invoke/common / * @build test.java.lang.invoke.lib.InstructionHelper * - * @run main/othervm ${test.main.class} + * @run driver ${test.main.class} */ public class TestDebugDuringExceptionCatching { diff --git a/test/hotspot/jtreg/compiler/igvn/CmpDisjointButNonOrderedRangesLong.java b/test/hotspot/jtreg/compiler/igvn/CmpDisjointButNonOrderedRangesLong.java index c5ef1640721..ab40a2ea234 100644 --- a/test/hotspot/jtreg/compiler/igvn/CmpDisjointButNonOrderedRangesLong.java +++ b/test/hotspot/jtreg/compiler/igvn/CmpDisjointButNonOrderedRangesLong.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,7 +27,7 @@ * @summary Ranges can be proven to be disjoint but not orderable (thanks to unsigned range) * Comparing such values in such range with != should always be true. * @library /test/lib / - * @run main compiler.igvn.CmpDisjointButNonOrderedRangesLong + * @run driver ${test.main.class} */ package compiler.igvn; diff --git a/test/hotspot/jtreg/compiler/longcountedloops/TestShortRunningLongCountedLoop.java b/test/hotspot/jtreg/compiler/longcountedloops/TestShortRunningLongCountedLoop.java index 7e55353e0f7..ed65deb6c85 100644 --- a/test/hotspot/jtreg/compiler/longcountedloops/TestShortRunningLongCountedLoop.java +++ b/test/hotspot/jtreg/compiler/longcountedloops/TestShortRunningLongCountedLoop.java @@ -32,14 +32,11 @@ import java.util.Objects; * @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 + * @run driver ${test.main.class} */ 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 @@ -351,8 +348,9 @@ public class TestShortRunningLongCountedLoop { throw new RuntimeException("incorrect result: " + res); } } - wb.enqueueMethodForCompilation(info.getTest(), CompilerWhiteBoxTest.COMP_LEVEL_FULL_OPTIMIZATION); - if (!wb.isMethodCompiled(info.getTest())) { + WhiteBox whitebox = WhiteBox.getWhiteBox(); + whitebox.enqueueMethodForCompilation(info.getTest(), CompilerWhiteBoxTest.COMP_LEVEL_FULL_OPTIMIZATION); + if (!whitebox.isMethodCompiled(info.getTest())) { throw new RuntimeException("Should be compiled now"); } for (int i = 0; i < 10; i++) { diff --git a/test/hotspot/jtreg/compiler/loopopts/TestHasTruncationWrap.java b/test/hotspot/jtreg/compiler/loopopts/TestHasTruncationWrap.java index 9a68a2fcb77..143933ed6ea 100644 --- a/test/hotspot/jtreg/compiler/loopopts/TestHasTruncationWrap.java +++ b/test/hotspot/jtreg/compiler/loopopts/TestHasTruncationWrap.java @@ -27,14 +27,14 @@ * @summary Test CountedLoopConverter::has_truncation_wrap logic that checks if * a truncated iv (e.g. byte or char iv) is still a valid counted loop. * @library /test/lib / - * @run main ${test.main.class} + * @run driver ${test.main.class} */ /* * @test id=Xcomp * @bug 8385855 * @library /test/lib / - * @run main ${test.main.class} -Xcomp -XX:-TieredCompilation -XX:CompileCommand=compileonly,${test.main.class}::test* + * @run driver ${test.main.class} -Xcomp -XX:-TieredCompilation -XX:CompileCommand=compileonly,${test.main.class}::test* */ package compiler.loopopts; diff --git a/test/hotspot/jtreg/compiler/loopopts/TestRedundantSafepointElimination.java b/test/hotspot/jtreg/compiler/loopopts/TestRedundantSafepointElimination.java index 69f86a2bf1d..f557a491160 100644 --- a/test/hotspot/jtreg/compiler/loopopts/TestRedundantSafepointElimination.java +++ b/test/hotspot/jtreg/compiler/loopopts/TestRedundantSafepointElimination.java @@ -30,7 +30,7 @@ import compiler.lib.ir_framework.*; * @bug 8347499 * @summary Tests that redundant safepoints can be eliminated in loops. * @library /test/lib / - * @run main compiler.loopopts.TestRedundantSafepointElimination + * @run driver ${test.main.class} */ public class TestRedundantSafepointElimination { public static void main(String[] args) { diff --git a/test/hotspot/jtreg/compiler/rangechecks/TestFoldCompares.java b/test/hotspot/jtreg/compiler/rangechecks/TestFoldCompares.java index bec3e442403..b0df68b209a 100644 --- a/test/hotspot/jtreg/compiler/rangechecks/TestFoldCompares.java +++ b/test/hotspot/jtreg/compiler/rangechecks/TestFoldCompares.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,14 +27,14 @@ * @summary Test logic in IfNode::fold_compares, which folds 2 signed comparisons * into a single comparison. * @library /test/lib / - * @run main ${test.main.class} + * @run driver ${test.main.class} */ /* * @test id=Xcomp * @bug 8346420 * @library /test/lib / - * @run main ${test.main.class} -Xcomp -XX:-TieredCompilation -XX:CompileCommand=compileonly,${test.main.class}::test* + * @run driver ${test.main.class} -Xcomp -XX:-TieredCompilation -XX:CompileCommand=compileonly,${test.main.class}::test* */ package compiler.rangechecks; diff --git a/test/hotspot/jtreg/compiler/stable/LazyConstantsIrTest.java b/test/hotspot/jtreg/compiler/stable/LazyConstantsIrTest.java index b9f9343dd39..8f967fae560 100644 --- a/test/hotspot/jtreg/compiler/stable/LazyConstantsIrTest.java +++ b/test/hotspot/jtreg/compiler/stable/LazyConstantsIrTest.java @@ -27,7 +27,7 @@ * @modules java.base/jdk.internal.lang * @library /test/lib / * @enablePreview - * @run main ${test.main.class} + * @run driver ${test.main.class} */ package compiler.stable; diff --git a/test/hotspot/jtreg/compiler/vectorization/TestRotateByteAndShortVector.java b/test/hotspot/jtreg/compiler/vectorization/TestRotateByteAndShortVector.java index 79cde2f0d26..4c448564a87 100644 --- a/test/hotspot/jtreg/compiler/vectorization/TestRotateByteAndShortVector.java +++ b/test/hotspot/jtreg/compiler/vectorization/TestRotateByteAndShortVector.java @@ -29,7 +29,7 @@ * @key randomness * @summary Test vectorization of rotate byte and short * @library /test/lib / - * @run main/othervm TestRotateByteAndShortVector + * @run driver ${test.main.class} */ import java.util.Random; diff --git a/test/hotspot/jtreg/compiler/vectorization/TestRoundVectorDoubleRandom.java b/test/hotspot/jtreg/compiler/vectorization/TestRoundVectorDoubleRandom.java index 78dd4f50a06..e5a6966cdcf 100644 --- a/test/hotspot/jtreg/compiler/vectorization/TestRoundVectorDoubleRandom.java +++ b/test/hotspot/jtreg/compiler/vectorization/TestRoundVectorDoubleRandom.java @@ -31,7 +31,7 @@ * @library /test/lib / * @modules java.base/jdk.internal.math * @requires os.arch == "riscv64" & vm.cpu.features ~= ".*rvv.*" - * @run main compiler.vectorization.TestRoundVectorDoubleRandom + * @run driver ${test.main.class} */ package compiler.vectorization; diff --git a/test/hotspot/jtreg/compiler/vectorization/TestRoundVectorFloatRandom.java b/test/hotspot/jtreg/compiler/vectorization/TestRoundVectorFloatRandom.java index 474601346e8..92b6d3b9840 100644 --- a/test/hotspot/jtreg/compiler/vectorization/TestRoundVectorFloatRandom.java +++ b/test/hotspot/jtreg/compiler/vectorization/TestRoundVectorFloatRandom.java @@ -31,7 +31,7 @@ * @library /test/lib / * @modules java.base/jdk.internal.math * @requires os.arch == "riscv64" & vm.cpu.features ~= ".*rvv.*" - * @run main compiler.vectorization.TestRoundVectorFloatRandom + * @run driver ${test.main.class} */ package compiler.vectorization; diff --git a/test/hotspot/jtreg/compiler/vectorization/runner/ArrayCopyTest.java b/test/hotspot/jtreg/compiler/vectorization/runner/ArrayCopyTest.java index 48b2ff754ad..f1140533d25 100644 --- a/test/hotspot/jtreg/compiler/vectorization/runner/ArrayCopyTest.java +++ b/test/hotspot/jtreg/compiler/vectorization/runner/ArrayCopyTest.java @@ -24,18 +24,10 @@ /* * @test * @summary Vectorization test on array copy + * @requires vm.compiler2.enabled * @library /test/lib / * - * @build jdk.test.whitebox.WhiteBox - * compiler.vectorization.runner.VectorizationTestRunner - * - * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox - * @run main/othervm -Xbootclasspath/a:. - * -XX:+UnlockDiagnosticVMOptions - * -XX:+WhiteBoxAPI - * compiler.vectorization.runner.ArrayCopyTest - * - * @requires vm.compiler2.enabled + * @run driver ${test.main.class} */ package compiler.vectorization.runner; diff --git a/test/hotspot/jtreg/compiler/vectorization/runner/ArrayIndexFillTest.java b/test/hotspot/jtreg/compiler/vectorization/runner/ArrayIndexFillTest.java index 8d0ba2be589..3708fc87f29 100644 --- a/test/hotspot/jtreg/compiler/vectorization/runner/ArrayIndexFillTest.java +++ b/test/hotspot/jtreg/compiler/vectorization/runner/ArrayIndexFillTest.java @@ -1,6 +1,6 @@ /* * Copyright (c) 2022, 2023, Arm Limited. All rights reserved. - * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,18 +26,10 @@ * @test * @summary Vectorization test on array index fill * @library /test/lib / - * - * @build jdk.test.whitebox.WhiteBox - * compiler.vectorization.runner.VectorizationTestRunner - * - * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox - * @run main/othervm -Xbootclasspath/a:. - * -XX:+UnlockDiagnosticVMOptions - * -XX:+WhiteBoxAPI - * compiler.vectorization.runner.ArrayIndexFillTest - * * @requires (os.simpleArch == "x64") | (os.simpleArch == "aarch64") | (os.simpleArch == "riscv64") * @requires vm.compiler2.enabled + * + * @run driver ${test.main.class} */ package compiler.vectorization.runner; diff --git a/test/hotspot/jtreg/compiler/vectorization/runner/ArrayInvariantFillTest.java b/test/hotspot/jtreg/compiler/vectorization/runner/ArrayInvariantFillTest.java index b7044b1c79d..90e4955bee3 100644 --- a/test/hotspot/jtreg/compiler/vectorization/runner/ArrayInvariantFillTest.java +++ b/test/hotspot/jtreg/compiler/vectorization/runner/ArrayInvariantFillTest.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2022, 2023, Arm Limited. All rights reserved. + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,24 +26,11 @@ * @test * @summary Vectorization test on array invariant fill * @library /test/lib / - * - * @build jdk.test.whitebox.WhiteBox - * compiler.vectorization.runner.VectorizationTestRunner - * - * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox - * @run main/othervm -Xbootclasspath/a:. - * -XX:+UnlockDiagnosticVMOptions - * -XX:+WhiteBoxAPI - * -XX:-OptimizeFill - * compiler.vectorization.runner.ArrayInvariantFillTest - * @run main/othervm -Xbootclasspath/a:. - * -XX:+UnlockDiagnosticVMOptions - * -XX:+WhiteBoxAPI - * -XX:+OptimizeFill - * compiler.vectorization.runner.ArrayInvariantFillTest - * * @requires (os.simpleArch == "x64") | (os.simpleArch == "aarch64") | (os.simpleArch == "riscv64") * @requires vm.compiler2.enabled + * + * @run driver ${test.main.class} NoOptimizeFill + * @run driver ${test.main.class} OptimizeFill */ package compiler.vectorization.runner; @@ -68,11 +56,22 @@ public class ArrayInvariantFillTest extends VectorizationTestRunner { doubleInv = ran.nextDouble(); } + // We must pass the flags directly to the Test VM, and not the Driver VM in the @run above. + @Override + protected String[] testVMFlags(String[] args) { + return switch (args[0]) { + case "NoOptimizeFill" -> new String[]{"-XX:-OptimizeFill"}; + case "OptimizeFill" -> new String[]{"-XX:+OptimizeFill"}; + default -> throw new RuntimeException("Test argument not recognized: " + args[0]); + }; + } + // ---------------- Simple Fill ---------------- @Test - @IR(applyIfCPUFeatureOr = {"asimd", "true", "sse2", "true", "rvv", "true"}, - applyIf = {"OptimizeFill", "false"}, - counts = {IRNode.REPLICATE_B, ">0"}) + // TODO 8387402 + //@IR(applyIfCPUFeatureOr = {"asimd", "true", "sse2", "true", "rvv", "true"}, + // applyIf = {"OptimizeFill", "false"}, + // counts = {IRNode.REPLICATE_B, ">0"}) @IR(applyIfCPUFeatureOr = {"asimd", "true", "sse2", "true", "rvv", "true"}, applyIf = {"OptimizeFill", "true"}, counts = {IRNode.REPLICATE_B, "0"}) diff --git a/test/hotspot/jtreg/compiler/vectorization/runner/ArrayShiftOpTest.java b/test/hotspot/jtreg/compiler/vectorization/runner/ArrayShiftOpTest.java index e2d28cbf083..2699afda5cc 100644 --- a/test/hotspot/jtreg/compiler/vectorization/runner/ArrayShiftOpTest.java +++ b/test/hotspot/jtreg/compiler/vectorization/runner/ArrayShiftOpTest.java @@ -1,6 +1,6 @@ /* * Copyright (c) 2022, 2023, Arm Limited. All rights reserved. - * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2025, Rivos Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -28,18 +28,10 @@ * @bug 8183390 8332905 * @summary Vectorization test on bug-prone shift operation * @library /test/lib / - * - * @build jdk.test.whitebox.WhiteBox - * compiler.vectorization.runner.VectorizationTestRunner - * - * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox - * @run main/othervm -Xbootclasspath/a:. - * -XX:+UnlockDiagnosticVMOptions - * -XX:+WhiteBoxAPI - * compiler.vectorization.runner.ArrayShiftOpTest - * * @requires (os.simpleArch == "x64") | (os.simpleArch == "aarch64") | (os.simpleArch == "riscv64") * @requires vm.compiler2.enabled + * + * @run driver ${test.main.class} */ package compiler.vectorization.runner; diff --git a/test/hotspot/jtreg/compiler/vectorization/runner/ArrayTypeConvertTest.java b/test/hotspot/jtreg/compiler/vectorization/runner/ArrayTypeConvertTest.java index f9c5f6199f1..d6f2febb06f 100644 --- a/test/hotspot/jtreg/compiler/vectorization/runner/ArrayTypeConvertTest.java +++ b/test/hotspot/jtreg/compiler/vectorization/runner/ArrayTypeConvertTest.java @@ -27,33 +27,12 @@ * @bug 8183390 8340010 8342095 * @summary Vectorization test on array type conversions * @library /test/lib / - * - * @build jdk.test.whitebox.WhiteBox - * compiler.vectorization.runner.VectorizationTestRunner - * * @requires vm.compiler2.enabled * - * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox - * - * @run main/othervm -Xbootclasspath/a:. - * -XX:+UnlockDiagnosticVMOptions - * -XX:+WhiteBoxAPI - * compiler.vectorization.runner.ArrayTypeConvertTest nCOH_nAV - * - * @run main/othervm -Xbootclasspath/a:. - * -XX:+UnlockDiagnosticVMOptions - * -XX:+WhiteBoxAPI - * compiler.vectorization.runner.ArrayTypeConvertTest nCOH_yAV - * - * @run main/othervm -Xbootclasspath/a:. - * -XX:+UnlockDiagnosticVMOptions - * -XX:+WhiteBoxAPI - * compiler.vectorization.runner.ArrayTypeConvertTest yCOH_nAV - * - * @run main/othervm -Xbootclasspath/a:. - * -XX:+UnlockDiagnosticVMOptions - * -XX:+WhiteBoxAPI - * compiler.vectorization.runner.ArrayTypeConvertTest yCOH_yAV + * @run driver ${test.main.class} nCOH_nAV + * @run driver ${test.main.class} nCOH_yAV + * @run driver ${test.main.class} yCOH_nAV + * @run driver ${test.main.class} yCOH_yAV */ package compiler.vectorization.runner; diff --git a/test/hotspot/jtreg/compiler/vectorization/runner/ArrayUnsafeOpTest.java b/test/hotspot/jtreg/compiler/vectorization/runner/ArrayUnsafeOpTest.java index 8b4513b8490..f6874a03ffb 100644 --- a/test/hotspot/jtreg/compiler/vectorization/runner/ArrayUnsafeOpTest.java +++ b/test/hotspot/jtreg/compiler/vectorization/runner/ArrayUnsafeOpTest.java @@ -25,17 +25,9 @@ * @test * @summary Vectorization test on array unsafe operations * @library /test/lib / - * - * @build jdk.test.whitebox.WhiteBox - * compiler.vectorization.runner.VectorizationTestRunner - * - * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox - * @run main/othervm -Xbootclasspath/a:. - * -XX:+UnlockDiagnosticVMOptions - * -XX:+WhiteBoxAPI - * compiler.vectorization.runner.ArrayUnsafeOpTest - * * @requires vm.compiler2.enabled + * + * @run driver ${test.main.class} */ package compiler.vectorization.runner; diff --git a/test/hotspot/jtreg/compiler/vectorization/runner/BasicBooleanOpTest.java b/test/hotspot/jtreg/compiler/vectorization/runner/BasicBooleanOpTest.java index ba82013e182..3a61b365800 100644 --- a/test/hotspot/jtreg/compiler/vectorization/runner/BasicBooleanOpTest.java +++ b/test/hotspot/jtreg/compiler/vectorization/runner/BasicBooleanOpTest.java @@ -1,6 +1,6 @@ /* * Copyright (c) 2022, 2023, Arm Limited. All rights reserved. - * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,17 +27,9 @@ * @summary Vectorization test on basic boolean operations * @requires vm.opt.StressUnstableIfTraps == null | !vm.opt.StressUnstableIfTraps * @library /test/lib / - * - * @build jdk.test.whitebox.WhiteBox - * compiler.vectorization.runner.VectorizationTestRunner - * - * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox - * @run main/othervm -Xbootclasspath/a:. - * -XX:+UnlockDiagnosticVMOptions - * -XX:+WhiteBoxAPI - * compiler.vectorization.runner.BasicBooleanOpTest - * * @requires vm.compiler2.enabled + * + * @run driver ${test.main.class} */ package compiler.vectorization.runner; diff --git a/test/hotspot/jtreg/compiler/vectorization/runner/BasicByteOpTest.java b/test/hotspot/jtreg/compiler/vectorization/runner/BasicByteOpTest.java index a336b32f7b9..acbf44c471c 100644 --- a/test/hotspot/jtreg/compiler/vectorization/runner/BasicByteOpTest.java +++ b/test/hotspot/jtreg/compiler/vectorization/runner/BasicByteOpTest.java @@ -1,6 +1,6 @@ /* * Copyright (c) 2022, 2023, Arm Limited. All rights reserved. - * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,19 +26,9 @@ * @test * @summary Vectorization test on basic byte operations * @library /test/lib / - * - * @build jdk.test.whitebox.WhiteBox - * compiler.vectorization.runner.VectorizationTestRunner - * - * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox - * @run main/othervm -Xbootclasspath/a:. - * -XX:+UnlockDiagnosticVMOptions - * -XX:+WhiteBoxAPI - * -XX:CompileCommand=CompileOnly,compiler.vectorization.runner.BasicByteOpTest::* - * -XX:LoopUnrollLimit=1000 - * compiler.vectorization.runner.BasicByteOpTest - * * @requires vm.compiler2.enabled + * + * @run driver ${test.main.class} */ package compiler.vectorization.runner; @@ -64,6 +54,12 @@ public class BasicByteOpTest extends VectorizationTestRunner { } } + // We must pass the flags directly to the test-VM, and not the driver vm in the @run above. + @Override + protected String[] testVMFlags(String[] args) { + return new String[]{"-XX:CompileCommand=CompileOnly,compiler.vectorization.runner.BasicByteOpTest::*", "-XX:LoopUnrollLimit=1000"}; + } + // ---------------- Arithmetic ---------------- @Test @IR(applyIfCPUFeatureOr = {"asimd", "true", "sse2", "true", "rvv", "true"}, diff --git a/test/hotspot/jtreg/compiler/vectorization/runner/BasicCharOpTest.java b/test/hotspot/jtreg/compiler/vectorization/runner/BasicCharOpTest.java index 4211d5eec5e..be462f0be16 100644 --- a/test/hotspot/jtreg/compiler/vectorization/runner/BasicCharOpTest.java +++ b/test/hotspot/jtreg/compiler/vectorization/runner/BasicCharOpTest.java @@ -25,17 +25,9 @@ * @test * @summary Vectorization test on basic char operations * @library /test/lib / - * - * @build jdk.test.whitebox.WhiteBox - * compiler.vectorization.runner.VectorizationTestRunner - * - * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox - * @run main/othervm -Xbootclasspath/a:. - * -XX:+UnlockDiagnosticVMOptions - * -XX:+WhiteBoxAPI - * compiler.vectorization.runner.BasicCharOpTest - * * @requires vm.compiler2.enabled + * + * @run driver ${test.main.class} */ package compiler.vectorization.runner; diff --git a/test/hotspot/jtreg/compiler/vectorization/runner/BasicDoubleOpTest.java b/test/hotspot/jtreg/compiler/vectorization/runner/BasicDoubleOpTest.java index 8d5925ec8c6..1adb89591a5 100644 --- a/test/hotspot/jtreg/compiler/vectorization/runner/BasicDoubleOpTest.java +++ b/test/hotspot/jtreg/compiler/vectorization/runner/BasicDoubleOpTest.java @@ -1,6 +1,6 @@ /* * Copyright (c) 2022, 2023, Arm Limited. All rights reserved. - * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2025, Rivos Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -27,18 +27,10 @@ * @test * @summary Vectorization test on basic double operations * @library /test/lib / - * - * @build jdk.test.whitebox.WhiteBox - * compiler.vectorization.runner.VectorizationTestRunner - * - * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox - * @run main/othervm -Xbootclasspath/a:. - * -XX:+UnlockDiagnosticVMOptions - * -XX:+WhiteBoxAPI - * compiler.vectorization.runner.BasicDoubleOpTest - * * @requires (os.simpleArch == "x64") | (os.simpleArch == "aarch64") | (os.simpleArch == "riscv64") * @requires vm.compiler2.enabled + * + * @run driver ${test.main.class} */ package compiler.vectorization.runner; diff --git a/test/hotspot/jtreg/compiler/vectorization/runner/BasicFloatOpTest.java b/test/hotspot/jtreg/compiler/vectorization/runner/BasicFloatOpTest.java index b89d068d8af..870b8746baf 100644 --- a/test/hotspot/jtreg/compiler/vectorization/runner/BasicFloatOpTest.java +++ b/test/hotspot/jtreg/compiler/vectorization/runner/BasicFloatOpTest.java @@ -25,18 +25,10 @@ * @test * @summary Vectorization test on basic float operations * @library /test/lib / - * - * @build jdk.test.whitebox.WhiteBox - * compiler.vectorization.runner.VectorizationTestRunner - * - * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox - * @run main/othervm -Xbootclasspath/a:. - * -XX:+UnlockDiagnosticVMOptions - * -XX:+WhiteBoxAPI - * compiler.vectorization.runner.BasicFloatOpTest - * * @requires (os.simpleArch == "x64") | (os.simpleArch == "aarch64") | (os.simpleArch == "riscv64") * @requires vm.compiler2.enabled + * + * @run driver ${test.main.class} */ package compiler.vectorization.runner; diff --git a/test/hotspot/jtreg/compiler/vectorization/runner/BasicIntOpTest.java b/test/hotspot/jtreg/compiler/vectorization/runner/BasicIntOpTest.java index e096f7878ab..8849418e609 100644 --- a/test/hotspot/jtreg/compiler/vectorization/runner/BasicIntOpTest.java +++ b/test/hotspot/jtreg/compiler/vectorization/runner/BasicIntOpTest.java @@ -1,6 +1,6 @@ /* * Copyright (c) 2022, 2023, Arm Limited. All rights reserved. - * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,17 +26,9 @@ * @test * @summary Vectorization test on basic int operations * @library /test/lib / - * - * @build jdk.test.whitebox.WhiteBox - * compiler.vectorization.runner.VectorizationTestRunner - * - * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox - * @run main/othervm -Xbootclasspath/a:. - * -XX:+UnlockDiagnosticVMOptions - * -XX:+WhiteBoxAPI - * compiler.vectorization.runner.BasicIntOpTest - * * @requires vm.compiler2.enabled + * + * @run driver ${test.main.class} */ package compiler.vectorization.runner; diff --git a/test/hotspot/jtreg/compiler/vectorization/runner/BasicLongOpTest.java b/test/hotspot/jtreg/compiler/vectorization/runner/BasicLongOpTest.java index a6767054958..5404d943bbc 100644 --- a/test/hotspot/jtreg/compiler/vectorization/runner/BasicLongOpTest.java +++ b/test/hotspot/jtreg/compiler/vectorization/runner/BasicLongOpTest.java @@ -1,6 +1,6 @@ /* * Copyright (c) 2022, 2023, Arm Limited. All rights reserved. - * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,18 +26,10 @@ * @test * @summary Vectorization test on basic long operations * @library /test/lib / - * - * @build jdk.test.whitebox.WhiteBox - * compiler.vectorization.runner.VectorizationTestRunner - * - * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox - * @run main/othervm -Xbootclasspath/a:. - * -XX:+UnlockDiagnosticVMOptions - * -XX:+WhiteBoxAPI - * compiler.vectorization.runner.BasicLongOpTest - * * @requires (os.simpleArch == "x64") | (os.simpleArch == "aarch64") | (os.simpleArch == "riscv64") * @requires vm.compiler2.enabled + * + * @run driver ${test.main.class} */ package compiler.vectorization.runner; diff --git a/test/hotspot/jtreg/compiler/vectorization/runner/BasicShortOpTest.java b/test/hotspot/jtreg/compiler/vectorization/runner/BasicShortOpTest.java index b957a00278a..4c7221dea52 100644 --- a/test/hotspot/jtreg/compiler/vectorization/runner/BasicShortOpTest.java +++ b/test/hotspot/jtreg/compiler/vectorization/runner/BasicShortOpTest.java @@ -27,17 +27,9 @@ * @bug 8183390 8342095 * @summary Vectorization test on basic short operations * @library /test/lib / - * - * @build jdk.test.whitebox.WhiteBox - * compiler.vectorization.runner.VectorizationTestRunner - * - * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox - * @run main/othervm -Xbootclasspath/a:. - * -XX:+UnlockDiagnosticVMOptions - * -XX:+WhiteBoxAPI - * compiler.vectorization.runner.BasicShortOpTest - * * @requires vm.compiler2.enabled + * + * @run driver ${test.main.class} */ package compiler.vectorization.runner; diff --git a/test/hotspot/jtreg/compiler/vectorization/runner/LoopArrayIndexComputeTest.java b/test/hotspot/jtreg/compiler/vectorization/runner/LoopArrayIndexComputeTest.java index c8a3c71bdee..27058012f36 100644 --- a/test/hotspot/jtreg/compiler/vectorization/runner/LoopArrayIndexComputeTest.java +++ b/test/hotspot/jtreg/compiler/vectorization/runner/LoopArrayIndexComputeTest.java @@ -1,6 +1,6 @@ /* * Copyright (c) 2022, 2023, Arm Limited. All rights reserved. - * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,26 +26,13 @@ * @test * @summary Vectorization test on loop array index computation * @library /test/lib / - * - * @build jdk.test.whitebox.WhiteBox - * compiler.vectorization.runner.VectorizationTestRunner - * - * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox - * - * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI - * compiler.vectorization.runner.LoopArrayIndexComputeTest nAV_ySAC - * - * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI - * compiler.vectorization.runner.LoopArrayIndexComputeTest yAV_ySAC - * - * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI - * compiler.vectorization.runner.LoopArrayIndexComputeTest nAV_nSAC - * - * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI - * compiler.vectorization.runner.LoopArrayIndexComputeTest yAV_nSAC - * * @requires (os.simpleArch == "x64") | (os.simpleArch == "aarch64") * @requires vm.compiler2.enabled + * + * @run driver ${test.main.class} nAV_ySAC + * @run driver ${test.main.class} yAV_ySAC + * @run driver ${test.main.class} nAV_nSAC + * @run driver ${test.main.class} yAV_nSAC */ package compiler.vectorization.runner; @@ -60,10 +47,10 @@ public class LoopArrayIndexComputeTest extends VectorizationTestRunner { @Override protected String[] testVMFlags(String[] args) { return switch (args[0]) { - case "nAV_ySAC" -> new String[]{"-XX:-AlignVector", "-XX:+UseAutoVectorizationSpeculativeAliasingChecks"}; - case "yAV_ySAC" -> new String[]{"-XX:+AlignVector", "-XX:+UseAutoVectorizationSpeculativeAliasingChecks"}; - case "nAV_nSAC" -> new String[]{"-XX:-AlignVector", "-XX:-UseAutoVectorizationSpeculativeAliasingChecks"}; - case "yAV_nSAC" -> new String[]{"-XX:+AlignVector", "-XX:-UseAutoVectorizationSpeculativeAliasingChecks"}; + case "nAV_ySAC" -> new String[]{"-XX:+UnlockDiagnosticVMOptions", "-XX:-AlignVector", "-XX:+UseAutoVectorizationSpeculativeAliasingChecks"}; + case "yAV_ySAC" -> new String[]{"-XX:+UnlockDiagnosticVMOptions", "-XX:+AlignVector", "-XX:+UseAutoVectorizationSpeculativeAliasingChecks"}; + case "nAV_nSAC" -> new String[]{"-XX:+UnlockDiagnosticVMOptions", "-XX:-AlignVector", "-XX:-UseAutoVectorizationSpeculativeAliasingChecks"}; + case "yAV_nSAC" -> new String[]{"-XX:+UnlockDiagnosticVMOptions", "-XX:+AlignVector", "-XX:-UseAutoVectorizationSpeculativeAliasingChecks"}; default -> { throw new RuntimeException("Test argument not recognized: " + args[0]); } }; } diff --git a/test/hotspot/jtreg/compiler/vectorization/runner/LoopCombinedOpTest.java b/test/hotspot/jtreg/compiler/vectorization/runner/LoopCombinedOpTest.java index c46b2e11612..714de5b3c6b 100644 --- a/test/hotspot/jtreg/compiler/vectorization/runner/LoopCombinedOpTest.java +++ b/test/hotspot/jtreg/compiler/vectorization/runner/LoopCombinedOpTest.java @@ -1,6 +1,6 @@ /* * Copyright (c) 2022, 2023, Arm Limited. All rights reserved. - * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,33 +26,12 @@ * @test * @summary Vectorization test on combined operations * @library /test/lib / - * - * @build jdk.test.whitebox.WhiteBox - * compiler.vectorization.runner.VectorizationTestRunner - * * @requires vm.compiler2.enabled * - * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox - * - * @run main/othervm -Xbootclasspath/a:. - * -XX:+UnlockDiagnosticVMOptions - * -XX:+WhiteBoxAPI - * compiler.vectorization.runner.LoopCombinedOpTest nCOH_nAV - * - * @run main/othervm -Xbootclasspath/a:. - * -XX:+UnlockDiagnosticVMOptions - * -XX:+WhiteBoxAPI - * compiler.vectorization.runner.LoopCombinedOpTest nCOH_yAV - * - * @run main/othervm -Xbootclasspath/a:. - * -XX:+UnlockDiagnosticVMOptions - * -XX:+WhiteBoxAPI - * compiler.vectorization.runner.LoopCombinedOpTest yCOH_nAV - * - * @run main/othervm -Xbootclasspath/a:. - * -XX:+UnlockDiagnosticVMOptions - * -XX:+WhiteBoxAPI - * compiler.vectorization.runner.LoopCombinedOpTest yCOH_yAV + * @run driver ${test.main.class} nCOH_nAV + * @run driver ${test.main.class} nCOH_yAV + * @run driver ${test.main.class} yCOH_nAV + * @run driver ${test.main.class} yCOH_yAV */ package compiler.vectorization.runner; diff --git a/test/hotspot/jtreg/compiler/vectorization/runner/LoopControlFlowTest.java b/test/hotspot/jtreg/compiler/vectorization/runner/LoopControlFlowTest.java index e36e4097813..51326956983 100644 --- a/test/hotspot/jtreg/compiler/vectorization/runner/LoopControlFlowTest.java +++ b/test/hotspot/jtreg/compiler/vectorization/runner/LoopControlFlowTest.java @@ -25,17 +25,9 @@ * @test * @summary Vectorization test on simple control flow in loop * @library /test/lib / - * - * @build jdk.test.whitebox.WhiteBox - * compiler.vectorization.runner.VectorizationTestRunner - * - * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox - * @run main/othervm -Xbootclasspath/a:. - * -XX:+UnlockDiagnosticVMOptions - * -XX:+WhiteBoxAPI - * compiler.vectorization.runner.LoopControlFlowTest - * * @requires vm.compiler2.enabled + * + * @run driver ${test.main.class} */ package compiler.vectorization.runner; diff --git a/test/hotspot/jtreg/compiler/vectorization/runner/LoopLiveOutNodesTest.java b/test/hotspot/jtreg/compiler/vectorization/runner/LoopLiveOutNodesTest.java index 06a3eb33bc3..cad2af04a9b 100644 --- a/test/hotspot/jtreg/compiler/vectorization/runner/LoopLiveOutNodesTest.java +++ b/test/hotspot/jtreg/compiler/vectorization/runner/LoopLiveOutNodesTest.java @@ -1,6 +1,6 @@ /* * Copyright (c) 2022, 2023, Arm Limited. All rights reserved. - * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,17 +26,9 @@ * @test * @summary Vectorization test on loops with live out nodes * @library /test/lib / - * - * @build jdk.test.whitebox.WhiteBox - * compiler.vectorization.runner.VectorizationTestRunner - * - * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox - * @run main/othervm -Xbootclasspath/a:. - * -XX:+UnlockDiagnosticVMOptions - * -XX:+WhiteBoxAPI - * compiler.vectorization.runner.LoopLiveOutNodesTest - * * @requires vm.compiler2.enabled + * + * @run driver ${test.main.class} */ package compiler.vectorization.runner; diff --git a/test/hotspot/jtreg/compiler/vectorization/runner/LoopRangeStrideTest.java b/test/hotspot/jtreg/compiler/vectorization/runner/LoopRangeStrideTest.java index 2db565461ac..a36d11198e7 100644 --- a/test/hotspot/jtreg/compiler/vectorization/runner/LoopRangeStrideTest.java +++ b/test/hotspot/jtreg/compiler/vectorization/runner/LoopRangeStrideTest.java @@ -1,6 +1,6 @@ /* * Copyright (c) 2022, 2023, Arm Limited. All rights reserved. - * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,18 +26,10 @@ * @test * @summary Vectorization test on different loop ranges and strides * @library /test/lib / - * - * @build jdk.test.whitebox.WhiteBox - * compiler.vectorization.runner.VectorizationTestRunner - * - * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox - * @run main/othervm -Xbootclasspath/a:. - * -XX:+UnlockDiagnosticVMOptions - * -XX:+WhiteBoxAPI - * compiler.vectorization.runner.LoopRangeStrideTest - * * @requires (os.simpleArch == "x64") | (os.simpleArch == "aarch64") | (os.simpleArch == "riscv64") * @requires vm.compiler2.enabled + * + * @run driver ${test.main.class} */ package compiler.vectorization.runner; diff --git a/test/hotspot/jtreg/compiler/vectorization/runner/LoopReductionOpTest.java b/test/hotspot/jtreg/compiler/vectorization/runner/LoopReductionOpTest.java index 546d99f5cce..9b9dcb03f6e 100644 --- a/test/hotspot/jtreg/compiler/vectorization/runner/LoopReductionOpTest.java +++ b/test/hotspot/jtreg/compiler/vectorization/runner/LoopReductionOpTest.java @@ -25,19 +25,10 @@ * @test * @summary Vectorization test on reduction operations * @library /test/lib / - * - * @build jdk.test.whitebox.WhiteBox - * compiler.vectorization.runner.VectorizationTestRunner - * - * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox - * @run main/othervm -Xbootclasspath/a:. - * -XX:+UnlockDiagnosticVMOptions - * -XX:+WhiteBoxAPI - * compiler.vectorization.runner.LoopReductionOpTest - * * @requires (os.simpleArch == "x64") | (os.simpleArch == "aarch64") | (os.simpleArch == "riscv64") * @requires vm.compiler2.enabled * + * @run driver ${test.main.class} */ package compiler.vectorization.runner; diff --git a/test/hotspot/jtreg/compiler/vectorization/runner/MultipleLoopsTest.java b/test/hotspot/jtreg/compiler/vectorization/runner/MultipleLoopsTest.java index 4dbfba02a43..4be74d20733 100644 --- a/test/hotspot/jtreg/compiler/vectorization/runner/MultipleLoopsTest.java +++ b/test/hotspot/jtreg/compiler/vectorization/runner/MultipleLoopsTest.java @@ -1,6 +1,6 @@ /* * Copyright (c) 2022, 2023, Arm Limited. All rights reserved. - * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,17 +26,9 @@ * @test * @summary Vectorization test on multiple loops in a method * @library /test/lib / - * - * @build jdk.test.whitebox.WhiteBox - * compiler.vectorization.runner.VectorizationTestRunner - * - * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox - * @run main/othervm -Xbootclasspath/a:. - * -XX:+UnlockDiagnosticVMOptions - * -XX:+WhiteBoxAPI - * compiler.vectorization.runner.MultipleLoopsTest - * * @requires vm.compiler2.enabled + * + * @run driver ${test.main.class} */ package compiler.vectorization.runner; diff --git a/test/hotspot/jtreg/compiler/vectorization/runner/StripMinedLoopTest.java b/test/hotspot/jtreg/compiler/vectorization/runner/StripMinedLoopTest.java index dbc999647ad..347571fc95b 100644 --- a/test/hotspot/jtreg/compiler/vectorization/runner/StripMinedLoopTest.java +++ b/test/hotspot/jtreg/compiler/vectorization/runner/StripMinedLoopTest.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2022, 2023, Arm Limited. All rights reserved. + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,18 +26,9 @@ * @test * @summary Vectorization test with small strip mining iterations * @library /test/lib / - * - * @build jdk.test.whitebox.WhiteBox - * compiler.vectorization.runner.VectorizationTestRunner - * - * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox - * @run main/othervm -Xbootclasspath/a:. - * -XX:+UnlockDiagnosticVMOptions - * -XX:+WhiteBoxAPI - * -XX:LoopStripMiningIter=10 - * compiler.vectorization.runner.StripMinedLoopTest - * * @requires vm.compiler2.enabled + * + * @run driver ${test.main.class} */ package compiler.vectorization.runner; @@ -59,6 +51,12 @@ public class StripMinedLoopTest extends VectorizationTestRunner { } } + // We must pass the flags directly to the Test VM, and not the Driver VM in the @run above. + @Override + protected String[] testVMFlags(String[] args) { + return new String[]{"-XX:LoopStripMiningIter=10"}; + } + @Test @IR(applyIfCPUFeatureOr = {"asimd", "true", "sse2", "true", "rvv", "true"}, counts = {IRNode.STORE_VECTOR, ">0"}) diff --git a/test/hotspot/jtreg/compiler/vectorization/runner/VectorizationTestRunner.java b/test/hotspot/jtreg/compiler/vectorization/runner/VectorizationTestRunner.java index 7f8e4ec3b39..9adebf30d31 100644 --- a/test/hotspot/jtreg/compiler/vectorization/runner/VectorizationTestRunner.java +++ b/test/hotspot/jtreg/compiler/vectorization/runner/VectorizationTestRunner.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2022, 2023, Arm Limited. All rights reserved. + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,14 +30,23 @@ import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; import jdk.test.lib.Utils; +import jdk.test.lib.helpers.ClassFileInstaller; +import jdk.test.lib.process.ProcessTools; import jdk.test.whitebox.WhiteBox; public class VectorizationTestRunner { - private static final WhiteBox WB = WhiteBox.getWhiteBox(); + private static final String VERIFY_CORRECTNESS_ARG = "--verify-vectorization-correctness"; + + private static class Flags { + private static final WhiteBox WHITEBOX = WhiteBox.getWhiteBox(); + } private static final int COMP_LEVEL_INTP = 0; private static final int COMP_LEVEL_C2 = 4; @@ -52,6 +62,35 @@ public class VectorizationTestRunner { // invokes it twice - first time in the interpreter and second time compiled // by C2. Then this runner compares the two return values. Hence we require // each test method returning a primitive value or an array of primitive type. + runCorrectnessTestsInTestVM(args); + + // 2) Vectorization ability test + // To test vectorizability, invoke the IR test framework to check existence of + // expected C2 IR node. + TestFramework irTest = new TestFramework(klass); + irTest.addFlags(testVMFlags(args)); + irTest.start(); + } + + private void runCorrectnessTestsInTestVM(String[] args) { + List command = new ArrayList<>(); + command.addAll(Arrays.asList(testVMFlags(args))); + command.add("-Xbootclasspath/a:."); + command.add("-XX:+UnlockDiagnosticVMOptions"); + command.add("-XX:+WhiteBoxAPI"); + command.add(getClass().getName()); + command.add(VERIFY_CORRECTNESS_ARG); + command.add(getClass().getName()); + try { + ClassFileInstaller.main("jdk.test.whitebox.WhiteBox"); + ProcessTools.executeTestJava(command).shouldHaveExitValue(0); + } catch (Exception e) { + throw new RuntimeException("Vectorization correctness test failed", e); + } + } + + private void runCorrectnessTests() { + Class klass = getClass(); for (Method method : klass.getDeclaredMethods()) { try { if (method.isAnnotationPresent(Test.class)) { @@ -63,13 +102,6 @@ public class VectorizationTestRunner { "." + method.getName() + ": " + e.getMessage()); } } - - // 2) Vectorization ability test - // To test vectorizability, invoke the IR test framework to check existence of - // expected C2 IR node. - TestFramework irTest = new TestFramework(klass); - irTest.addFlags(testVMFlags(args)); - irTest.start(); } // Override this to add extra flags. @@ -111,20 +143,20 @@ public class VectorizationTestRunner { // Temporarily disable the compiler and invoke the method to get reference // result from the interpreter - WB.setBooleanVMFlag("UseCompiler", false); + Flags.WHITEBOX.setBooleanVMFlag("UseCompiler", false); try { expected = method.invoke(this); } catch (Exception e) { e.printStackTrace(); fail("Exception is thrown in test method invocation (interpreter)."); } - assert(WB.getMethodCompilationLevel(method) == COMP_LEVEL_INTP); - WB.setBooleanVMFlag("UseCompiler", true); + assert(Flags.WHITEBOX.getMethodCompilationLevel(method) == COMP_LEVEL_INTP); + Flags.WHITEBOX.setBooleanVMFlag("UseCompiler", true); // Compile the method and invoke it again long enqueueTime = System.currentTimeMillis(); - WB.enqueueMethodForCompilation(method, COMP_LEVEL_C2); - while (WB.getMethodCompilationLevel(method) != COMP_LEVEL_C2) { + Flags.WHITEBOX.enqueueMethodForCompilation(method, COMP_LEVEL_C2); + while (Flags.WHITEBOX.getMethodCompilationLevel(method) != COMP_LEVEL_C2) { Thread.sleep(100 /*ms*/); } try { @@ -133,7 +165,7 @@ public class VectorizationTestRunner { e.printStackTrace(); fail("Exception is thrown in test method invocation (C2)."); } - assert(WB.getMethodCompilationLevel(method) == COMP_LEVEL_C2); + assert(Flags.WHITEBOX.getMethodCompilationLevel(method) == COMP_LEVEL_C2); // Check if two invocations return the same Class retType = method.getReturnType(); @@ -172,11 +204,10 @@ public class VectorizationTestRunner { } private static VectorizationTestRunner createTestInstance(String testName) { - if (!testName.toLowerCase().endsWith(".java")) { - fail("Invalid test file name " + testName); + if (testName.toLowerCase().endsWith(".java")) { + testName = testName.substring(0, testName.length() - 5); + testName = testName.replace('/', '.'); } - testName = testName.substring(0, testName.length() - 5); - testName = testName.replace('/', '.'); VectorizationTestRunner instance = null; try { @@ -196,7 +227,13 @@ public class VectorizationTestRunner { } public static void main(String[] args) { - VectorizationTestRunner testObj = createTestInstance(Utils.TEST_NAME); + VectorizationTestRunner testObj; + if (args.length > 0 && args[0].equals(VERIFY_CORRECTNESS_ARG)) { + testObj = createTestInstance(args[1]); + testObj.runCorrectnessTests(); + return; + } + testObj = createTestInstance(Utils.TEST_NAME); testObj.run(args); } } From b186074751bbd5b34dc92a1119e7b93e91cd8c7c Mon Sep 17 00:00:00 2001 From: Albert Mingkun Yang Date: Wed, 1 Jul 2026 06:02:23 +0000 Subject: [PATCH 049/305] 8387387: Parallel: Clean up startup allocation locking Co-authored-by: Axel Boldt-Christmas Reviewed-by: tschatzl, aboldtch --- .../gc/parallel/parallelScavengeHeap.cpp | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp b/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp index 7aa88110fc8..ea3a85861b8 100644 --- a/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp +++ b/src/hotspot/share/gc/parallel/parallelScavengeHeap.cpp @@ -315,17 +315,16 @@ HeapWord* ParallelScavengeHeap::mem_allocate_work(size_t size, bool is_tlab) { return result; } + // Ensure that is_init_completed() does not transition while expanding the heap. + ConditionalMutexLocker ml_init(InitCompleted_lock, !is_init_completed(), Mutex::_no_safepoint_check_flag); if (!is_init_completed()) { - // Double checked locking, this ensure that is_init_completed() does not - // transition while expanding the heap. - MonitorLocker ml(InitCompleted_lock, Monitor::_no_safepoint_check_flag); - if (!is_init_completed()) { - result = expand_heap_and_allocate(size, is_tlab); - // Return the result if it's tlab-allocation. If the result is null, callers will retry - // non-tlab allocation. - if (result != nullptr || is_tlab) { - return result; - } + // Rechecked !is_init_completed() implies we have mutual exclusion via + // `Heap_lock` and `InitCompleted_lock` + result = expand_heap_and_allocate(size, is_tlab); + // Return the result if it's tlab-allocation. If the result is null, + // callers will retry non-tlab allocation. + if (result != nullptr || is_tlab) { + return result; } } } From 28c79eb79222f304a4bc7233f90a255bf66d4d91 Mon Sep 17 00:00:00 2001 From: Quan Anh Mai Date: Wed, 1 Jul 2026 07:17:48 +0000 Subject: [PATCH 050/305] 8387395: [REDO] C2: SIGSEGV in compiled code due to missing ctrl Reviewed-by: dlong, kvn, vlivanov --- src/hotspot/share/opto/compile.cpp | 36 +++++++++---- src/hotspot/share/opto/node.cpp | 21 ++++++++ src/hotspot/share/opto/node.hpp | 1 + .../TestRemoveCastPPWithCMoveUse.java | 53 +++++++++++++++++++ 4 files changed, 101 insertions(+), 10 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/controldependency/TestRemoveCastPPWithCMoveUse.java diff --git a/src/hotspot/share/opto/compile.cpp b/src/hotspot/share/opto/compile.cpp index 1f51cdc1d39..e5f91875516 100644 --- a/src/hotspot/share/opto/compile.cpp +++ b/src/hotspot/share/opto/compile.cpp @@ -3497,22 +3497,38 @@ void Compile::final_graph_reshaping_main_switch(Node* n, Final_Reshape_Counts& f ResourceMark rm; Unique_Node_List wq; wq.push(n); + + + // When we remove a CastPP, we need to pin all of its transitive users under the control of + // the removed node. The simplest approach is to pin all of the uses of the removed CastPP, + // but it is overly conservative, as an AddP does not really need pinning. As a result, we + // look through those nodes that do not need pinning and only pin memory access nodes under + // n->in(0). for (uint next = 0; next < wq.size(); ++next) { Node *m = wq.at(next); for (DUIterator_Fast imax, i = m->fast_outs(imax); i < imax; i++) { Node* use = m->fast_out(i); - if (use->is_Mem() || use->is_EncodeNarrowPtr()) { + int use_op = use->Opcode(); + if (use->is_CFG() || use->pinned() || // already pinned at the exact control + use->is_Cmp() || use_op == Op_CastP2X || use_op == Op_Conv2B) { // pure computations + continue; + } else if (use->is_EncodeNarrowPtr() || // EncodeP remembers whether its input is nullable, so it must be pinned + use_op == Op_PartialSubtypeCheck || // This accesses its pointer inputs, so it must depend on them being not-null + use->is_Mem() || use->is_memory_access_intrinsic()) { use->ensure_control_or_add_prec(n->in(0)); + } else if (use_op == Op_AddP || + use_op == Op_CastPP || use_op == Op_CheckCastPP || + use_op == Op_CMoveP || use_op == Op_CMoveN || + use_op == Op_DecodeN || use_op == Op_DecodeNKlass || + use_op == Op_VerifyVectorAlignment) { + // Look through use to find memory accesses if use does not need pinning + wq.push(use); } else { - switch(use->Opcode()) { - case Op_AddP: - case Op_DecodeN: - case Op_DecodeNKlass: - case Op_CheckCastPP: - case Op_CastPP: - wq.push(use); - break; - } + // Should have handled all kinds of nodes, verify that we do not unexpectedly arrive + // here + assert(false, "unexpected node %s", use->Name()); + // Be conservative in product and pin the unexpected use + use->ensure_control_or_add_prec(n->in(0)); } } } diff --git a/src/hotspot/share/opto/node.cpp b/src/hotspot/share/opto/node.cpp index 2f7cc6d1c1d..726a3ea1b55 100644 --- a/src/hotspot/share/opto/node.cpp +++ b/src/hotspot/share/opto/node.cpp @@ -3018,6 +3018,27 @@ bool Node::is_data_proj_of_pure_function(const Node* maybe_pure_function) const return Opcode() == Op_Proj && as_Proj()->_con == TypeFunc::Parms && maybe_pure_function->is_CallLeafPure(); } +// Whether this is an intrinsic node that accesses memory and has a memory input, such as array +// equal intrinsic. Some nodes do access memory but do not have a memory input, such as +// PartialSubTypeCheck, they are not included here. +bool Node::is_memory_access_intrinsic() const { + switch (Opcode()) { + case Op_StrComp: + case Op_StrEquals: + case Op_StrIndexOf: + case Op_StrIndexOfChar: + case Op_StrCompressedCopy: + case Op_StrInflatedCopy: + case Op_AryEq: + case Op_CountPositives: + case Op_VectorizedHashCode: + case Op_EncodeISOArray: + return true; + default: + return false; + } +} + //--------------------------has_non_debug_uses------------------------------ // Checks whether the node has any non-debug uses or not. bool Node::has_non_debug_uses() const { diff --git a/src/hotspot/share/opto/node.hpp b/src/hotspot/share/opto/node.hpp index 443f4bfbe8a..b3de7498e50 100644 --- a/src/hotspot/share/opto/node.hpp +++ b/src/hotspot/share/opto/node.hpp @@ -1069,6 +1069,7 @@ public: uint is_Copy() const { return (_flags & Flag_is_Copy); } virtual bool is_CFG() const { return false; } + bool is_memory_access_intrinsic() const; // If this node is control-dependent on a test, can it be rerouted to a dominating equivalent // test? This means that the node can be executed safely as long as it happens after the test diff --git a/test/hotspot/jtreg/compiler/controldependency/TestRemoveCastPPWithCMoveUse.java b/test/hotspot/jtreg/compiler/controldependency/TestRemoveCastPPWithCMoveUse.java new file mode 100644 index 00000000000..3d752cc74f5 --- /dev/null +++ b/test/hotspot/jtreg/compiler/controldependency/TestRemoveCastPPWithCMoveUse.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package compiler.controldependency; + +/* + * @test + * @bug 8385420 + * @summary C2 correctly handles the case when the removed CastPPNode has a CMove use. + * @run main ${test.main.class} + * @run main/othervm -Xbatch -XX:CompileOnly=${test.main.class}::test + * -XX:+UnlockDiagnosticVMOptions -XX:+StressGCM ${test.main.class} + * + */ +public class TestRemoveCastPPWithCMoveUse { + public static void main(String[] args) { + for (int i = 0; i < 10_000; i++) { + test(null, false); + test(null, true); + test("", false); + test("", true); + } + } + + static int test(String a, boolean flag) { + StringBuilder sb = new StringBuilder(); + if (a == null) { + sb.append(""); + } else { + sb.append(flag ? a : ""); + } + return sb.length(); + } +} From fcfd6ad141e27e77beba7a6c3b82f9c5ac113550 Mon Sep 17 00:00:00 2001 From: Thomas Schatzl Date: Wed, 1 Jul 2026 07:57:41 +0000 Subject: [PATCH 051/305] 8386846: G1: Crash in ~ThreadTotalCPUTimeClosure inside G1ServiceThread during CDS abort Reviewed-by: shade, dholmes --- src/hotspot/share/runtime/cpuTimeCounters.cpp | 11 ++++++++++- src/hotspot/share/runtime/cpuTimeCounters.hpp | 5 +++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/hotspot/share/runtime/cpuTimeCounters.cpp b/src/hotspot/share/runtime/cpuTimeCounters.cpp index e174407089c..3374a1c5db3 100644 --- a/src/hotspot/share/runtime/cpuTimeCounters.cpp +++ b/src/hotspot/share/runtime/cpuTimeCounters.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2023 Google LLC. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -25,6 +25,7 @@ #include "runtime/atomicAccess.hpp" #include "runtime/cpuTimeCounters.hpp" +#include "utilities/globalCounter.inline.hpp" const char* CPUTimeGroups::to_string(CPUTimeType val) { switch (val) { @@ -77,6 +78,10 @@ void CPUTimeCounters::inc_gc_total_cpu_time(jlong diff) { } void CPUTimeCounters::publish_gc_total_cpu_time() { + GlobalCounter::CriticalSection cs(Thread::current()); + if (!UsePerfData || !PerfDataManager::has_PerfData()) { + return; + } CPUTimeCounters* instance = CPUTimeCounters::get_instance(); // Atomically fetch the current _gc_total_cpu_time_diff and reset it to zero. jlong new_value = 0; @@ -103,6 +108,10 @@ PerfCounter* CPUTimeCounters::get_counter(CPUTimeGroups::CPUTimeType name) { } void CPUTimeCounters::update_counter(CPUTimeGroups::CPUTimeType name, jlong total) { + GlobalCounter::CriticalSection cs(Thread::current()); + if (!UsePerfData || !PerfDataManager::has_PerfData()) { + return; + } CPUTimeCounters* instance = CPUTimeCounters::get_instance(); PerfCounter* counter = instance->get_counter(name); jlong prev_value = counter->get_value(); diff --git a/src/hotspot/share/runtime/cpuTimeCounters.hpp b/src/hotspot/share/runtime/cpuTimeCounters.hpp index c2e636bdb1d..15f680c06e1 100644 --- a/src/hotspot/share/runtime/cpuTimeCounters.hpp +++ b/src/hotspot/share/runtime/cpuTimeCounters.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2023 Google LLC. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -79,6 +79,8 @@ private: static void inc_gc_total_cpu_time(jlong diff); + static PerfCounter* get_counter(CPUTimeGroups::CPUTimeType name); + public: static void initialize() { assert(_instance == nullptr, "we can only allocate one CPUTimeCounters object"); @@ -91,7 +93,6 @@ public: } static void create_counter(CPUTimeGroups::CPUTimeType name); - static PerfCounter* get_counter(CPUTimeGroups::CPUTimeType name); static void update_counter(CPUTimeGroups::CPUTimeType name, jlong total); static void publish_gc_total_cpu_time(); From 867b4f42c0eacb7758a7615a1f56fc7c7dc56371 Mon Sep 17 00:00:00 2001 From: Ruben Ayrapetyan Date: Wed, 1 Jul 2026 08:36:46 +0000 Subject: [PATCH 052/305] 8387081: AArch64: Refactor MacroAssembler::cmpxchg Reviewed-by: qamai, aph --- src/hotspot/cpu/aarch64/aarch64_atomic.ad | 132 +++++------------- src/hotspot/cpu/aarch64/aarch64_atomic_ad.m4 | 14 +- .../cpu/aarch64/c1_LIRAssembler_aarch64.cpp | 4 +- .../cpu/aarch64/c2_MacroAssembler_aarch64.cpp | 9 +- src/hotspot/cpu/aarch64/gc/g1/g1_aarch64.ad | 20 ++- src/hotspot/cpu/aarch64/gc/g1/g1_aarch64.m4 | 26 ++-- .../shenandoahBarrierSetAssembler_aarch64.cpp | 8 +- .../gc/z/zBarrierSetAssembler_aarch64.cpp | 5 +- src/hotspot/cpu/aarch64/gc/z/z_aarch64.ad | 10 +- .../cpu/aarch64/macroAssembler_aarch64.cpp | 35 ++++- .../cpu/aarch64/macroAssembler_aarch64.hpp | 20 ++- 11 files changed, 122 insertions(+), 161 deletions(-) diff --git a/src/hotspot/cpu/aarch64/aarch64_atomic.ad b/src/hotspot/cpu/aarch64/aarch64_atomic.ad index 3b05a637215..13fbe781518 100644 --- a/src/hotspot/cpu/aarch64/aarch64_atomic.ad +++ b/src/hotspot/cpu/aarch64/aarch64_atomic.ad @@ -43,8 +43,7 @@ instruct compareAndExchangeB(iRegINoSp res, indirect mem, iRegI oldval, iRegI ne %} ins_encode %{ __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, - Assembler::byte, /*acquire*/ false, /*release*/ true, - /*weak*/ false, $res$$Register); + Assembler::byte, memory_order_release, $res$$Register); __ sxtbw($res$$Register, $res$$Register); %} ins_pipe(pipe_slow); @@ -59,8 +58,7 @@ instruct compareAndExchangeS(iRegINoSp res, indirect mem, iRegI oldval, iRegI ne %} ins_encode %{ __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, - Assembler::halfword, /*acquire*/ false, /*release*/ true, - /*weak*/ false, $res$$Register); + Assembler::halfword, memory_order_release, $res$$Register); __ sxthw($res$$Register, $res$$Register); %} ins_pipe(pipe_slow); @@ -75,8 +73,7 @@ instruct compareAndExchangeI(iRegINoSp res, indirect mem, iRegI oldval, iRegI ne %} ins_encode %{ __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, - Assembler::word, /*acquire*/ false, /*release*/ true, - /*weak*/ false, $res$$Register); + Assembler::word, memory_order_release, $res$$Register); %} ins_pipe(pipe_slow); %} @@ -90,8 +87,7 @@ instruct compareAndExchangeL(iRegLNoSp res, indirect mem, iRegL oldval, iRegL ne %} ins_encode %{ __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, - Assembler::xword, /*acquire*/ false, /*release*/ true, - /*weak*/ false, $res$$Register); + Assembler::xword, memory_order_release, $res$$Register); %} ins_pipe(pipe_slow); %} @@ -106,8 +102,7 @@ instruct compareAndExchangeN(iRegNNoSp res, indirect mem, iRegN oldval, iRegN ne %} ins_encode %{ __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, - Assembler::word, /*acquire*/ false, /*release*/ true, - /*weak*/ false, $res$$Register); + Assembler::word, memory_order_release, $res$$Register); %} ins_pipe(pipe_slow); %} @@ -122,8 +117,7 @@ instruct compareAndExchangeP(iRegPNoSp res, indirect mem, iRegP oldval, iRegP ne %} ins_encode %{ __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, - Assembler::xword, /*acquire*/ false, /*release*/ true, - /*weak*/ false, $res$$Register); + Assembler::xword, memory_order_release, $res$$Register); %} ins_pipe(pipe_slow); %} @@ -138,8 +132,7 @@ instruct compareAndExchangeBAcq(iRegINoSp res, indirect mem, iRegI oldval, iRegI %} ins_encode %{ __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, - Assembler::byte, /*acquire*/ true, /*release*/ true, - /*weak*/ false, $res$$Register); + Assembler::byte, memory_order_seq_cst, $res$$Register); __ sxtbw($res$$Register, $res$$Register); %} ins_pipe(pipe_slow); @@ -155,8 +148,7 @@ instruct compareAndExchangeSAcq(iRegINoSp res, indirect mem, iRegI oldval, iRegI %} ins_encode %{ __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, - Assembler::halfword, /*acquire*/ true, /*release*/ true, - /*weak*/ false, $res$$Register); + Assembler::halfword, memory_order_seq_cst, $res$$Register); __ sxthw($res$$Register, $res$$Register); %} ins_pipe(pipe_slow); @@ -172,8 +164,7 @@ instruct compareAndExchangeIAcq(iRegINoSp res, indirect mem, iRegI oldval, iRegI %} ins_encode %{ __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, - Assembler::word, /*acquire*/ true, /*release*/ true, - /*weak*/ false, $res$$Register); + Assembler::word, memory_order_seq_cst, $res$$Register); %} ins_pipe(pipe_slow); %} @@ -188,8 +179,7 @@ instruct compareAndExchangeLAcq(iRegLNoSp res, indirect mem, iRegL oldval, iRegL %} ins_encode %{ __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, - Assembler::xword, /*acquire*/ true, /*release*/ true, - /*weak*/ false, $res$$Register); + Assembler::xword, memory_order_seq_cst, $res$$Register); %} ins_pipe(pipe_slow); %} @@ -204,8 +194,7 @@ instruct compareAndExchangeNAcq(iRegNNoSp res, indirect mem, iRegN oldval, iRegN %} ins_encode %{ __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, - Assembler::word, /*acquire*/ true, /*release*/ true, - /*weak*/ false, $res$$Register); + Assembler::word, memory_order_seq_cst, $res$$Register); %} ins_pipe(pipe_slow); %} @@ -220,8 +209,7 @@ instruct compareAndExchangePAcq(iRegPNoSp res, indirect mem, iRegP oldval, iRegP %} ins_encode %{ __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, - Assembler::xword, /*acquire*/ true, /*release*/ true, - /*weak*/ false, $res$$Register); + Assembler::xword, memory_order_seq_cst, $res$$Register); %} ins_pipe(pipe_slow); %} @@ -235,9 +223,7 @@ instruct compareAndSwapB(iRegINoSp res, indirect mem, iRegI oldval, iRegI newval "csetw $res, EQ\t# $res <-- (EQ ? 1 : 0)" %} ins_encode %{ - __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, - Assembler::byte, /*acquire*/ false, /*release*/ true, - /*weak*/ false, noreg); + __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, Assembler::byte, memory_order_release); __ csetw($res$$Register, Assembler::EQ); %} ins_pipe(pipe_slow); @@ -252,9 +238,7 @@ instruct compareAndSwapS(iRegINoSp res, indirect mem, iRegI oldval, iRegI newval "csetw $res, EQ\t# $res <-- (EQ ? 1 : 0)" %} ins_encode %{ - __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, - Assembler::halfword, /*acquire*/ false, /*release*/ true, - /*weak*/ false, noreg); + __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, Assembler::halfword, memory_order_release); __ csetw($res$$Register, Assembler::EQ); %} ins_pipe(pipe_slow); @@ -269,9 +253,7 @@ instruct compareAndSwapI(iRegINoSp res, indirect mem, iRegI oldval, iRegI newval "csetw $res, EQ\t# $res <-- (EQ ? 1 : 0)" %} ins_encode %{ - __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, - Assembler::word, /*acquire*/ false, /*release*/ true, - /*weak*/ false, noreg); + __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, Assembler::word, memory_order_release); __ csetw($res$$Register, Assembler::EQ); %} ins_pipe(pipe_slow); @@ -286,9 +268,7 @@ instruct compareAndSwapL(iRegINoSp res, indirect mem, iRegL oldval, iRegL newval "csetw $res, EQ\t# $res <-- (EQ ? 1 : 0)" %} ins_encode %{ - __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, - Assembler::xword, /*acquire*/ false, /*release*/ true, - /*weak*/ false, noreg); + __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, Assembler::xword, memory_order_release); __ csetw($res$$Register, Assembler::EQ); %} ins_pipe(pipe_slow); @@ -304,9 +284,7 @@ instruct compareAndSwapN(iRegINoSp res, indirect mem, iRegN oldval, iRegN newval "csetw $res, EQ\t# $res <-- (EQ ? 1 : 0)" %} ins_encode %{ - __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, - Assembler::word, /*acquire*/ false, /*release*/ true, - /*weak*/ false, noreg); + __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, Assembler::word, memory_order_release); __ csetw($res$$Register, Assembler::EQ); %} ins_pipe(pipe_slow); @@ -322,9 +300,7 @@ instruct compareAndSwapP(iRegINoSp res, indirect mem, iRegP oldval, iRegP newval "csetw $res, EQ\t# $res <-- (EQ ? 1 : 0)" %} ins_encode %{ - __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, - Assembler::xword, /*acquire*/ false, /*release*/ true, - /*weak*/ false, noreg); + __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, Assembler::xword, memory_order_release); __ csetw($res$$Register, Assembler::EQ); %} ins_pipe(pipe_slow); @@ -340,9 +316,7 @@ instruct compareAndSwapBAcq(iRegINoSp res, indirect mem, iRegI oldval, iRegI new "csetw $res, EQ\t# $res <-- (EQ ? 1 : 0)" %} ins_encode %{ - __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, - Assembler::byte, /*acquire*/ true, /*release*/ true, - /*weak*/ false, noreg); + __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, Assembler::byte, memory_order_seq_cst); __ csetw($res$$Register, Assembler::EQ); %} ins_pipe(pipe_slow); @@ -358,9 +332,7 @@ instruct compareAndSwapSAcq(iRegINoSp res, indirect mem, iRegI oldval, iRegI new "csetw $res, EQ\t# $res <-- (EQ ? 1 : 0)" %} ins_encode %{ - __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, - Assembler::halfword, /*acquire*/ true, /*release*/ true, - /*weak*/ false, noreg); + __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, Assembler::halfword, memory_order_seq_cst); __ csetw($res$$Register, Assembler::EQ); %} ins_pipe(pipe_slow); @@ -376,9 +348,7 @@ instruct compareAndSwapIAcq(iRegINoSp res, indirect mem, iRegI oldval, iRegI new "csetw $res, EQ\t# $res <-- (EQ ? 1 : 0)" %} ins_encode %{ - __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, - Assembler::word, /*acquire*/ true, /*release*/ true, - /*weak*/ false, noreg); + __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, Assembler::word, memory_order_seq_cst); __ csetw($res$$Register, Assembler::EQ); %} ins_pipe(pipe_slow); @@ -394,9 +364,7 @@ instruct compareAndSwapLAcq(iRegINoSp res, indirect mem, iRegL oldval, iRegL new "csetw $res, EQ\t# $res <-- (EQ ? 1 : 0)" %} ins_encode %{ - __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, - Assembler::xword, /*acquire*/ true, /*release*/ true, - /*weak*/ false, noreg); + __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, Assembler::xword, memory_order_seq_cst); __ csetw($res$$Register, Assembler::EQ); %} ins_pipe(pipe_slow); @@ -412,9 +380,7 @@ instruct compareAndSwapNAcq(iRegINoSp res, indirect mem, iRegN oldval, iRegN new "csetw $res, EQ\t# $res <-- (EQ ? 1 : 0)" %} ins_encode %{ - __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, - Assembler::word, /*acquire*/ true, /*release*/ true, - /*weak*/ false, noreg); + __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, Assembler::word, memory_order_seq_cst); __ csetw($res$$Register, Assembler::EQ); %} ins_pipe(pipe_slow); @@ -430,9 +396,7 @@ instruct compareAndSwapPAcq(iRegINoSp res, indirect mem, iRegP oldval, iRegP new "csetw $res, EQ\t# $res <-- (EQ ? 1 : 0)" %} ins_encode %{ - __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, - Assembler::xword, /*acquire*/ true, /*release*/ true, - /*weak*/ false, noreg); + __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, Assembler::xword, memory_order_seq_cst); __ csetw($res$$Register, Assembler::EQ); %} ins_pipe(pipe_slow); @@ -447,9 +411,7 @@ instruct weakCompareAndSwapB(iRegINoSp res, indirect mem, iRegI oldval, iRegI ne "csetw $res, EQ\t# $res <-- (EQ ? 1 : 0)" %} ins_encode %{ - __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, - Assembler::byte, /*acquire*/ false, /*release*/ true, - /*weak*/ true, noreg); + __ cmpxchg_weak($mem$$Register, $oldval$$Register, $newval$$Register, Assembler::byte, memory_order_release); __ csetw($res$$Register, Assembler::EQ); %} ins_pipe(pipe_slow); @@ -464,9 +426,7 @@ instruct weakCompareAndSwapS(iRegINoSp res, indirect mem, iRegI oldval, iRegI ne "csetw $res, EQ\t# $res <-- (EQ ? 1 : 0)" %} ins_encode %{ - __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, - Assembler::halfword, /*acquire*/ false, /*release*/ true, - /*weak*/ true, noreg); + __ cmpxchg_weak($mem$$Register, $oldval$$Register, $newval$$Register, Assembler::halfword, memory_order_release); __ csetw($res$$Register, Assembler::EQ); %} ins_pipe(pipe_slow); @@ -481,9 +441,7 @@ instruct weakCompareAndSwapI(iRegINoSp res, indirect mem, iRegI oldval, iRegI ne "csetw $res, EQ\t# $res <-- (EQ ? 1 : 0)" %} ins_encode %{ - __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, - Assembler::word, /*acquire*/ false, /*release*/ true, - /*weak*/ true, noreg); + __ cmpxchg_weak($mem$$Register, $oldval$$Register, $newval$$Register, Assembler::word, memory_order_release); __ csetw($res$$Register, Assembler::EQ); %} ins_pipe(pipe_slow); @@ -498,9 +456,7 @@ instruct weakCompareAndSwapL(iRegINoSp res, indirect mem, iRegL oldval, iRegL ne "csetw $res, EQ\t# $res <-- (EQ ? 1 : 0)" %} ins_encode %{ - __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, - Assembler::xword, /*acquire*/ false, /*release*/ true, - /*weak*/ true, noreg); + __ cmpxchg_weak($mem$$Register, $oldval$$Register, $newval$$Register, Assembler::xword, memory_order_release); __ csetw($res$$Register, Assembler::EQ); %} ins_pipe(pipe_slow); @@ -516,9 +472,7 @@ instruct weakCompareAndSwapN(iRegINoSp res, indirect mem, iRegN oldval, iRegN ne "csetw $res, EQ\t# $res <-- (EQ ? 1 : 0)" %} ins_encode %{ - __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, - Assembler::word, /*acquire*/ false, /*release*/ true, - /*weak*/ true, noreg); + __ cmpxchg_weak($mem$$Register, $oldval$$Register, $newval$$Register, Assembler::word, memory_order_release); __ csetw($res$$Register, Assembler::EQ); %} ins_pipe(pipe_slow); @@ -534,9 +488,7 @@ instruct weakCompareAndSwapP(iRegINoSp res, indirect mem, iRegP oldval, iRegP ne "csetw $res, EQ\t# $res <-- (EQ ? 1 : 0)" %} ins_encode %{ - __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, - Assembler::xword, /*acquire*/ false, /*release*/ true, - /*weak*/ true, noreg); + __ cmpxchg_weak($mem$$Register, $oldval$$Register, $newval$$Register, Assembler::xword, memory_order_release); __ csetw($res$$Register, Assembler::EQ); %} ins_pipe(pipe_slow); @@ -552,9 +504,7 @@ instruct weakCompareAndSwapBAcq(iRegINoSp res, indirect mem, iRegI oldval, iRegI "csetw $res, EQ\t# $res <-- (EQ ? 1 : 0)" %} ins_encode %{ - __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, - Assembler::byte, /*acquire*/ true, /*release*/ true, - /*weak*/ true, noreg); + __ cmpxchg_weak($mem$$Register, $oldval$$Register, $newval$$Register, Assembler::byte, memory_order_seq_cst); __ csetw($res$$Register, Assembler::EQ); %} ins_pipe(pipe_slow); @@ -570,9 +520,7 @@ instruct weakCompareAndSwapSAcq(iRegINoSp res, indirect mem, iRegI oldval, iRegI "csetw $res, EQ\t# $res <-- (EQ ? 1 : 0)" %} ins_encode %{ - __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, - Assembler::halfword, /*acquire*/ true, /*release*/ true, - /*weak*/ true, noreg); + __ cmpxchg_weak($mem$$Register, $oldval$$Register, $newval$$Register, Assembler::halfword, memory_order_seq_cst); __ csetw($res$$Register, Assembler::EQ); %} ins_pipe(pipe_slow); @@ -588,9 +536,7 @@ instruct weakCompareAndSwapIAcq(iRegINoSp res, indirect mem, iRegI oldval, iRegI "csetw $res, EQ\t# $res <-- (EQ ? 1 : 0)" %} ins_encode %{ - __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, - Assembler::word, /*acquire*/ true, /*release*/ true, - /*weak*/ true, noreg); + __ cmpxchg_weak($mem$$Register, $oldval$$Register, $newval$$Register, Assembler::word, memory_order_seq_cst); __ csetw($res$$Register, Assembler::EQ); %} ins_pipe(pipe_slow); @@ -606,9 +552,7 @@ instruct weakCompareAndSwapLAcq(iRegINoSp res, indirect mem, iRegL oldval, iRegL "csetw $res, EQ\t# $res <-- (EQ ? 1 : 0)" %} ins_encode %{ - __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, - Assembler::xword, /*acquire*/ true, /*release*/ true, - /*weak*/ true, noreg); + __ cmpxchg_weak($mem$$Register, $oldval$$Register, $newval$$Register, Assembler::xword, memory_order_seq_cst); __ csetw($res$$Register, Assembler::EQ); %} ins_pipe(pipe_slow); @@ -624,9 +568,7 @@ instruct weakCompareAndSwapNAcq(iRegINoSp res, indirect mem, iRegN oldval, iRegN "csetw $res, EQ\t# $res <-- (EQ ? 1 : 0)" %} ins_encode %{ - __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, - Assembler::word, /*acquire*/ true, /*release*/ true, - /*weak*/ true, noreg); + __ cmpxchg_weak($mem$$Register, $oldval$$Register, $newval$$Register, Assembler::word, memory_order_seq_cst); __ csetw($res$$Register, Assembler::EQ); %} ins_pipe(pipe_slow); @@ -642,9 +584,7 @@ instruct weakCompareAndSwapPAcq(iRegINoSp res, indirect mem, iRegP oldval, iRegP "csetw $res, EQ\t# $res <-- (EQ ? 1 : 0)" %} ins_encode %{ - __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, - Assembler::xword, /*acquire*/ true, /*release*/ true, - /*weak*/ true, noreg); + __ cmpxchg_weak($mem$$Register, $oldval$$Register, $newval$$Register, Assembler::xword, memory_order_seq_cst); __ csetw($res$$Register, Assembler::EQ); %} ins_pipe(pipe_slow); diff --git a/src/hotspot/cpu/aarch64/aarch64_atomic_ad.m4 b/src/hotspot/cpu/aarch64/aarch64_atomic_ad.m4 index dc51754e7f9..d6b3abd1e6f 100644 --- a/src/hotspot/cpu/aarch64/aarch64_atomic_ad.m4 +++ b/src/hotspot/cpu/aarch64/aarch64_atomic_ad.m4 @@ -53,8 +53,7 @@ ifelse($7,Acq,INDENT(predicate(needs_acquiring_load_exclusive(n));),`dnl') %} ins_encode %{ __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, - Assembler::$4, /*acquire*/ ifelse($7,Acq,true,false), /*release*/ true, - /*weak*/ false, $res$$Register); + Assembler::$4, ifelse($7,Acq,memory_order_seq_cst,memory_order_release), $res$$Register); __ $6($res$$Register, $res$$Register); %} ins_pipe(pipe_slow); @@ -76,8 +75,7 @@ ifelse($1$6,PAcq,INDENT(predicate(needs_acquiring_load_exclusive(n) && (n->as_Lo %} ins_encode %{ __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, - Assembler::$4, /*acquire*/ ifelse($6,Acq,true,false), /*release*/ true, - /*weak*/ false, $res$$Register); + Assembler::$4, ifelse($6,Acq,memory_order_seq_cst,memory_order_release), $res$$Register); %} ins_pipe(pipe_slow); %}')dnl @@ -112,9 +110,7 @@ ifelse($6,Acq,INDENT(predicate(needs_acquiring_load_exclusive(n));),`dnl') "csetw $res, EQ\t# $res <-- (EQ ? 1 : 0)" %} ins_encode %{ - __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, - Assembler::$4, /*acquire*/ ifelse($6,Acq,true,false), /*release*/ true, - /*weak*/ ifelse($7,Weak,true,false), noreg); + __ ifelse($7,Weak,cmpxchg_weak,cmpxchg)($mem$$Register, $oldval$$Register, $newval$$Register, Assembler::$4, ifelse($6,Acq,memory_order_seq_cst,memory_order_release)); __ csetw($res$$Register, Assembler::EQ); %} ins_pipe(pipe_slow); @@ -137,9 +133,7 @@ ifelse($1$6,PAcq,INDENT(predicate(needs_acquiring_load_exclusive(n) && (n->as_Lo "csetw $res, EQ\t# $res <-- (EQ ? 1 : 0)" %} ins_encode %{ - __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, - Assembler::$4, /*acquire*/ ifelse($6,Acq,true,false), /*release*/ true, - /*weak*/ ifelse($7,Weak,true,false), noreg); + __ ifelse($7,Weak,cmpxchg_weak,cmpxchg)($mem$$Register, $oldval$$Register, $newval$$Register, Assembler::$4, ifelse($6,Acq,memory_order_seq_cst,memory_order_release)); __ csetw($res$$Register, Assembler::EQ); %} ins_pipe(pipe_slow); diff --git a/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp index 87451b5a07a..202f3227e2d 100644 --- a/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp @@ -1492,12 +1492,12 @@ void LIR_Assembler::emit_opTypeCheck(LIR_OpTypeCheck* op) { } void LIR_Assembler::casw(Register addr, Register newval, Register cmpval) { - __ cmpxchg(addr, cmpval, newval, Assembler::word, /* acquire*/ true, /* release*/ true, /* weak*/ false, rscratch1); + __ cmpxchg(addr, cmpval, newval, Assembler::word, memory_order_seq_cst, rscratch1); __ cset(rscratch1, Assembler::NE); } void LIR_Assembler::casl(Register addr, Register newval, Register cmpval) { - __ cmpxchg(addr, cmpval, newval, Assembler::xword, /* acquire*/ true, /* release*/ true, /* weak*/ false, rscratch1); + __ cmpxchg(addr, cmpval, newval, Assembler::xword, memory_order_seq_cst, rscratch1); __ cset(rscratch1, Assembler::NE); } diff --git a/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp index cb9e308197e..e46a338e649 100644 --- a/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp @@ -204,8 +204,7 @@ void C2_MacroAssembler::fast_lock(Register obj, Register box, Register t1, // Try to lock. Transition lock-bits 0b01 => 0b00 orr(t1_mark, t1_mark, markWord::unlocked_value); eor(t3_t, t1_mark, markWord::unlocked_value); - cmpxchg(/*addr*/ obj, /*expected*/ t1_mark, /*new*/ t3_t, Assembler::xword, - /*acquire*/ true, /*release*/ false, /*weak*/ false, noreg); + cmpxchg(/*addr*/ obj, /*expected*/ t1_mark, /*new*/ t3_t, Assembler::xword, memory_order_acquire); br(Assembler::NE, slow_path); bind(push); @@ -285,8 +284,7 @@ void C2_MacroAssembler::fast_lock(Register obj, Register box, Register t1, // Try to CAS owner (no owner => current thread's _monitor_owner_id). ldr(rscratch2, Address(rthread, JavaThread::monitor_owner_id_offset())); - cmpxchg(t2_owner_addr, zr, rscratch2, Assembler::xword, /*acquire*/ true, - /*release*/ false, /*weak*/ false, t3_owner); + cmpxchg(t2_owner_addr, zr, rscratch2, Assembler::xword, memory_order_acquire, t3_owner); br(Assembler::EQ, monitor_locked); // Check if recursive. @@ -371,8 +369,7 @@ void C2_MacroAssembler::fast_unlock(Register obj, Register box, Register t1, // Try to unlock. Transition lock bits 0b00 => 0b01 assert(oopDesc::mark_offset_in_bytes() == 0, "required to avoid lea"); orr(t3_t, t1_mark, markWord::unlocked_value); - cmpxchg(/*addr*/ obj, /*expected*/ t1_mark, /*new*/ t3_t, Assembler::xword, - /*acquire*/ false, /*release*/ true, /*weak*/ false, noreg); + cmpxchg(/*addr*/ obj, /*expected*/ t1_mark, /*new*/ t3_t, Assembler::xword, memory_order_release); br(Assembler::EQ, unlocked); bind(push_and_slow_path); diff --git a/src/hotspot/cpu/aarch64/gc/g1/g1_aarch64.ad b/src/hotspot/cpu/aarch64/gc/g1/g1_aarch64.ad index 18fc27a4af4..375a0a89760 100644 --- a/src/hotspot/cpu/aarch64/gc/g1/g1_aarch64.ad +++ b/src/hotspot/cpu/aarch64/gc/g1/g1_aarch64.ad @@ -283,7 +283,7 @@ instruct g1CompareAndExchangeP(iRegPNoSp res, indirect mem, iRegP oldval, iRegP RegSet::of($mem$$Register, $oldval$$Register, $newval$$Register) /* preserve */, RegSet::of($res$$Register) /* no_preserve */); __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, Assembler::xword, - false /* acquire */, true /* release */, false /* weak */, $res$$Register); + memory_order_release, $res$$Register); write_barrier_post(masm, this, $mem$$Register /* store_addr */, $newval$$Register /* new_val */, @@ -316,7 +316,7 @@ instruct g1CompareAndExchangePAcq(iRegPNoSp res, indirect mem, iRegP oldval, iRe RegSet::of($mem$$Register, $oldval$$Register, $newval$$Register) /* preserve */, RegSet::of($res$$Register) /* no_preserve */); __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, Assembler::xword, - true /* acquire */, true /* release */, false /* weak */, $res$$Register); + memory_order_seq_cst, $res$$Register); write_barrier_post(masm, this, $mem$$Register /* store_addr */, $newval$$Register /* new_val */, @@ -346,7 +346,7 @@ instruct g1CompareAndExchangeN(iRegNNoSp res, indirect mem, iRegN oldval, iRegN RegSet::of($mem$$Register, $oldval$$Register, $newval$$Register) /* preserve */, RegSet::of($res$$Register) /* no_preserve */); __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, Assembler::word, - false /* acquire */, true /* release */, false /* weak */, $res$$Register); + memory_order_release, $res$$Register); __ decode_heap_oop($tmp1$$Register, $newval$$Register); write_barrier_post(masm, this, $mem$$Register /* store_addr */, @@ -377,7 +377,7 @@ instruct g1CompareAndExchangeNAcq(iRegNNoSp res, indirect mem, iRegN oldval, iRe RegSet::of($mem$$Register, $oldval$$Register, $newval$$Register) /* preserve */, RegSet::of($res$$Register) /* no_preserve */); __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, Assembler::word, - true /* acquire */, true /* release */, false /* weak */, $res$$Register); + memory_order_seq_cst, $res$$Register); __ decode_heap_oop($tmp1$$Register, $newval$$Register); write_barrier_post(masm, this, $mem$$Register /* store_addr */, @@ -409,8 +409,7 @@ instruct g1CompareAndSwapP(iRegINoSp res, indirect mem, iRegP newval, iRegPNoSp $tmp2$$Register /* tmp2 */, RegSet::of($mem$$Register, $oldval$$Register, $newval$$Register) /* preserve */, RegSet::of($res$$Register) /* no_preserve */); - __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, Assembler::xword, - false /* acquire */, true /* release */, false /* weak */, noreg); + __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, Assembler::xword, memory_order_release); __ cset($res$$Register, Assembler::EQ); write_barrier_post(masm, this, $mem$$Register /* store_addr */, @@ -442,8 +441,7 @@ instruct g1CompareAndSwapPAcq(iRegINoSp res, indirect mem, iRegP newval, iRegPNo $tmp2$$Register /* tmp2 */, RegSet::of($mem$$Register, $oldval$$Register, $newval$$Register) /* preserve */, RegSet::of($res$$Register) /* no_preserve */); - __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, Assembler::xword, - true /* acquire */, true /* release */, false /* weak */, noreg); + __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, Assembler::xword, memory_order_seq_cst); __ cset($res$$Register, Assembler::EQ); write_barrier_post(masm, this, $mem$$Register /* store_addr */, @@ -475,8 +473,7 @@ instruct g1CompareAndSwapN(iRegINoSp res, indirect mem, iRegN newval, iRegPNoSp $tmp3$$Register /* tmp2 */, RegSet::of($mem$$Register, $oldval$$Register, $newval$$Register) /* preserve */, RegSet::of($res$$Register) /* no_preserve */); - __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, Assembler::word, - false /* acquire */, true /* release */, false /* weak */, noreg); + __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, Assembler::word, memory_order_release); __ cset($res$$Register, Assembler::EQ); __ decode_heap_oop($tmp1$$Register, $newval$$Register); write_barrier_post(masm, this, @@ -509,8 +506,7 @@ instruct g1CompareAndSwapNAcq(iRegINoSp res, indirect mem, iRegN newval, iRegPNo $tmp3$$Register /* tmp2 */, RegSet::of($mem$$Register, $oldval$$Register, $newval$$Register) /* preserve */, RegSet::of($res$$Register) /* no_preserve */); - __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, Assembler::word, - true /* acquire */, true /* release */, false /* weak */, noreg); + __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, Assembler::word, memory_order_seq_cst); __ cset($res$$Register, Assembler::EQ); __ decode_heap_oop($tmp1$$Register, $newval$$Register); write_barrier_post(masm, this, diff --git a/src/hotspot/cpu/aarch64/gc/g1/g1_aarch64.m4 b/src/hotspot/cpu/aarch64/gc/g1/g1_aarch64.m4 index 8fb1f7e8e42..63b464ceb8c 100644 --- a/src/hotspot/cpu/aarch64/gc/g1/g1_aarch64.m4 +++ b/src/hotspot/cpu/aarch64/gc/g1/g1_aarch64.m4 @@ -151,7 +151,7 @@ instruct g1CompareAndExchangeP$1(iRegPNoSp res, indirect mem, iRegP oldval, iReg RegSet::of($mem$$Register, $oldval$$Register, $newval$$Register) /* preserve */, RegSet::of($res$$Register) /* no_preserve */); __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, Assembler::xword, - $3 /* acquire */, true /* release */, false /* weak */, $res$$Register); + ifelse($1,Acq,memory_order_seq_cst,memory_order_release), $res$$Register); write_barrier_post(masm, this, $mem$$Register /* store_addr */, $newval$$Register /* new_val */, @@ -160,8 +160,8 @@ instruct g1CompareAndExchangeP$1(iRegPNoSp res, indirect mem, iRegP oldval, iReg %} ins_pipe(pipe_slow); %}')dnl -CAEP_INSN(,,false) -CAEP_INSN(Acq,_acq,true) +CAEP_INSN(,) +CAEP_INSN(Acq,_acq) dnl define(`CAEN_INSN', ` @@ -185,7 +185,7 @@ instruct g1CompareAndExchangeN$1(iRegNNoSp res, indirect mem, iRegN oldval, iReg RegSet::of($mem$$Register, $oldval$$Register, $newval$$Register) /* preserve */, RegSet::of($res$$Register) /* no_preserve */); __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, Assembler::word, - $3 /* acquire */, true /* release */, false /* weak */, $res$$Register); + ifelse($1,Acq,memory_order_seq_cst,memory_order_release), $res$$Register); __ decode_heap_oop($tmp1$$Register, $newval$$Register); write_barrier_post(masm, this, $mem$$Register /* store_addr */, @@ -195,8 +195,8 @@ instruct g1CompareAndExchangeN$1(iRegNNoSp res, indirect mem, iRegN oldval, iReg %} ins_pipe(pipe_slow); %}')dnl -CAEN_INSN(,,false) -CAEN_INSN(Acq,_acq,true) +CAEN_INSN(,) +CAEN_INSN(Acq,_acq) dnl define(`CASP_INSN', ` @@ -221,8 +221,7 @@ instruct g1CompareAndSwapP$1(iRegINoSp res, indirect mem, iRegP newval, iRegPNoS $tmp2$$Register /* tmp2 */, RegSet::of($mem$$Register, $oldval$$Register, $newval$$Register) /* preserve */, RegSet::of($res$$Register) /* no_preserve */); - __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, Assembler::xword, - $3 /* acquire */, true /* release */, false /* weak */, noreg); + __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, Assembler::xword, ifelse($1,Acq,memory_order_seq_cst,memory_order_release)); __ cset($res$$Register, Assembler::EQ); write_barrier_post(masm, this, $mem$$Register /* store_addr */, @@ -232,8 +231,8 @@ instruct g1CompareAndSwapP$1(iRegINoSp res, indirect mem, iRegP newval, iRegPNoS %} ins_pipe(pipe_slow); %}')dnl -CASP_INSN(,,false) -CASP_INSN(Acq,_acq,true) +CASP_INSN(,) +CASP_INSN(Acq,_acq) dnl define(`CASN_INSN', ` @@ -258,8 +257,7 @@ instruct g1CompareAndSwapN$1(iRegINoSp res, indirect mem, iRegN newval, iRegPNoS $tmp3$$Register /* tmp2 */, RegSet::of($mem$$Register, $oldval$$Register, $newval$$Register) /* preserve */, RegSet::of($res$$Register) /* no_preserve */); - __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, Assembler::word, - $3 /* acquire */, true /* release */, false /* weak */, noreg); + __ cmpxchg($mem$$Register, $oldval$$Register, $newval$$Register, Assembler::word, ifelse($1,Acq,memory_order_seq_cst,memory_order_release)); __ cset($res$$Register, Assembler::EQ); __ decode_heap_oop($tmp1$$Register, $newval$$Register); write_barrier_post(masm, this, @@ -270,8 +268,8 @@ instruct g1CompareAndSwapN$1(iRegINoSp res, indirect mem, iRegN newval, iRegPNoS %} ins_pipe(pipe_slow); %}')dnl -CASN_INSN(,,false) -CASN_INSN(Acq,_acq,true) +CASN_INSN(,) +CASN_INSN(Acq,_acq) dnl define(`XCHGP_INSN', ` diff --git a/src/hotspot/cpu/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.cpp index bc8af2354c8..7406aa0c1c4 100644 --- a/src/hotspot/cpu/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.cpp @@ -597,8 +597,14 @@ void ShenandoahBarrierSetAssembler::compare_and_set_c2(const MachNode* node, Mac ShenandoahBarrierStubC2::load_store_pre(masm, node, addr, tmp1, tmp2, tmp3, narrow); + atomic_memory_order order = acquire ? memory_order_seq_cst : memory_order_release; + // CAS! - __ cmpxchg(addr, oldval, newval, op_size, acquire, /* release */ true, weak, exchange ? res : noreg); + if (weak) { + __ cmpxchg_weak(addr, oldval, newval, op_size, order, exchange ? res : noreg); + } else { + __ cmpxchg(addr, oldval, newval, op_size, order, exchange ? res : noreg); + } // If we need a boolean result out of CAS, set the flag appropriately and promote the result. if (!exchange) { diff --git a/src/hotspot/cpu/aarch64/gc/z/zBarrierSetAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/gc/z/zBarrierSetAssembler_aarch64.cpp index 1eb96cdb6e7..7c320d835e7 100644 --- a/src/hotspot/cpu/aarch64/gc/z/zBarrierSetAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/gc/z/zBarrierSetAssembler_aarch64.cpp @@ -283,10 +283,7 @@ void ZBarrierSetAssembler::store_barrier_medium(MacroAssembler* masm, // If we get this far, we know there is a young raw null value in the field. __ relocate(barrier_Relocation::spec(), ZBarrierRelocationFormatStoreGoodBeforeMov); __ movzw(rtmp1, barrier_Relocation::unpatched); - __ cmpxchg(rtmp2, zr, rtmp1, - Assembler::xword, - false /* acquire */, false /* release */, true /* weak */, - rtmp3); + __ cmpxchg_weak(rtmp2, zr, rtmp1, Assembler::xword, memory_order_relaxed, rtmp3); __ br(Assembler::NE, slow_path); __ bind(slow_path_continuation); diff --git a/src/hotspot/cpu/aarch64/gc/z/z_aarch64.ad b/src/hotspot/cpu/aarch64/gc/z/z_aarch64.ad index ad2e9243823..74e0395c81e 100644 --- a/src/hotspot/cpu/aarch64/gc/z/z_aarch64.ad +++ b/src/hotspot/cpu/aarch64/gc/z/z_aarch64.ad @@ -207,8 +207,7 @@ instruct zCompareAndSwapP(iRegINoSp res, indirect mem, iRegP oldval, iRegP newva Address ref_addr($mem$$Register); z_store_barrier(masm, this, ref_addr, $newval$$Register, $newval_tmp$$Register, rscratch2, true /* is_atomic */); z_color(masm, this, $oldval_tmp$$Register, $oldval$$Register); - __ cmpxchg($mem$$Register, $oldval_tmp$$Register, $newval_tmp$$Register, Assembler::xword, - false /* acquire */, true /* release */, false /* weak */, noreg); + __ cmpxchg($mem$$Register, $oldval_tmp$$Register, $newval_tmp$$Register, Assembler::xword, memory_order_release); __ cset($res$$Register, Assembler::EQ); %} @@ -231,8 +230,7 @@ instruct zCompareAndSwapPAcq(iRegINoSp res, indirect mem, iRegP oldval, iRegP ne Address ref_addr($mem$$Register); z_store_barrier(masm, this, ref_addr, $newval$$Register, $newval_tmp$$Register, rscratch2, true /* is_atomic */); z_color(masm, this, $oldval_tmp$$Register, $oldval$$Register); - __ cmpxchg($mem$$Register, $oldval_tmp$$Register, $newval_tmp$$Register, Assembler::xword, - true /* acquire */, true /* release */, false /* weak */, noreg); + __ cmpxchg($mem$$Register, $oldval_tmp$$Register, $newval_tmp$$Register, Assembler::xword, memory_order_seq_cst); __ cset($res$$Register, Assembler::EQ); %} @@ -255,7 +253,7 @@ instruct zCompareAndExchangeP(iRegPNoSp res, indirect mem, iRegP oldval, iRegP n z_store_barrier(masm, this, ref_addr, $newval$$Register, $newval_tmp$$Register, rscratch2, true /* is_atomic */); z_color(masm, this, $oldval_tmp$$Register, $oldval$$Register); __ cmpxchg($mem$$Register, $oldval_tmp$$Register, $newval_tmp$$Register, Assembler::xword, - false /* acquire */, true /* release */, false /* weak */, $res$$Register); + memory_order_release, $res$$Register); z_uncolor(masm, this, $res$$Register); %} @@ -278,7 +276,7 @@ instruct zCompareAndExchangePAcq(iRegPNoSp res, indirect mem, iRegP oldval, iReg z_store_barrier(masm, this, ref_addr, $newval$$Register, $newval_tmp$$Register, rscratch2, true /* is_atomic */); z_color(masm, this, $oldval_tmp$$Register, $oldval$$Register); __ cmpxchg($mem$$Register, $oldval_tmp$$Register, $newval_tmp$$Register, Assembler::xword, - true /* acquire */, true /* release */, false /* weak */, $res$$Register); + memory_order_seq_cst, $res$$Register); z_uncolor(masm, this, $res$$Register); %} diff --git a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp index 1c052b67503..d5e220fd4a3 100644 --- a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp @@ -2231,8 +2231,7 @@ void MacroAssembler::profile_receiver_type(Register recv, Register mdp, int mdp_ // offset is no longer needed after the address is computed. lea(rscratch2, Address(mdp, offset)); - cmpxchg(/*addr*/ rscratch2, /*expected*/ zr, /*new*/ recv, Assembler::xword, - /*acquire*/ false, /*release*/ false, /*weak*/ true, noreg); + cmpxchg_weak(/*addr*/ rscratch2, /*expected*/ zr, /*new*/ recv, Assembler::xword, memory_order_relaxed); // CAS success means the slot now has the receiver we want. CAS failure means // something had claimed the slot concurrently: it can be the same receiver we want, @@ -3494,9 +3493,33 @@ void MacroAssembler::reinit_heapbase() void MacroAssembler::cmpxchg(Register addr, Register expected, Register new_val, enum operand_size size, - bool acquire, bool release, + enum atomic_memory_order order, bool weak, Register result) { + bool acquire, release; + + switch (order) { + case memory_order_relaxed: + acquire = false; + release = false; + break; + case memory_order_acquire: + acquire = true; + release = false; + break; + case memory_order_release: + acquire = false; + release = true; + break; + case memory_order_acq_rel: + case memory_order_seq_cst: + acquire = true; + release = true; + break; + default: + ShouldNotReachHere(); + } + if (result == noreg) result = rscratch1; BLOCK_COMMENT("cmpxchg {"); if (UseLSE) { @@ -7180,8 +7203,7 @@ void MacroAssembler::fast_lock(Register basic_lock, Register obj, Register t1, R assert(oopDesc::mark_offset_in_bytes() == 0, "required to avoid lea"); orr(mark, mark, markWord::unlocked_value); eor(t, mark, markWord::unlocked_value); - cmpxchg(/*addr*/ obj, /*expected*/ mark, /*new*/ t, Assembler::xword, - /*acquire*/ true, /*release*/ false, /*weak*/ false, noreg); + cmpxchg(/*addr*/ obj, /*expected*/ mark, /*new*/ t, Assembler::xword, memory_order_acquire); br(Assembler::NE, slow); bind(push); @@ -7249,8 +7271,7 @@ void MacroAssembler::fast_unlock(Register obj, Register t1, Register t2, Registe // Try to unlock. Transition lock bits 0b00 => 0b01 assert(oopDesc::mark_offset_in_bytes() == 0, "required to avoid lea"); orr(t, mark, markWord::unlocked_value); - cmpxchg(obj, mark, t, Assembler::xword, - /*acquire*/ false, /*release*/ true, /*weak*/ false, noreg); + cmpxchg(obj, mark, t, Assembler::xword, memory_order_release); br(Assembler::EQ, unlocked); bind(push_and_slow); diff --git a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp index 8f1e662765e..9c722cd297e 100644 --- a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp @@ -32,6 +32,7 @@ #include "metaprogramming/enableIf.hpp" #include "oops/compressedOops.hpp" #include "oops/compressedKlass.hpp" +#include "runtime/atomicAccess.hpp" #include "runtime/vm_version.hpp" #include "utilities/globalDefinitions.hpp" #include "utilities/powerOfTwo.hpp" @@ -1239,12 +1240,25 @@ public: str(rscratch1, adr); } +private: // A generic CAS; success or failure is in the EQ flag. // Clobbers rscratch1 void cmpxchg(Register addr, Register expected, Register new_val, - enum operand_size size, - bool acquire, bool release, bool weak, - Register result); + enum operand_size size, enum atomic_memory_order order, + bool weak, Register result); + +public: + void cmpxchg(Register addr, Register expected, Register new_val, + enum operand_size size, enum atomic_memory_order order, + Register result = noreg) { + cmpxchg(addr, expected, new_val, size, order, /* weak */ false, result); + } + + void cmpxchg_weak(Register addr, Register expected, Register new_val, + enum operand_size size, enum atomic_memory_order order, + Register result = noreg) { + cmpxchg(addr, expected, new_val, size, order, /* weak */ true, result); + } #ifdef ASSERT // Template short-hand support to clean-up after a failed call to trampoline From 0c209afd4f9f669490ef6e07ac582fbc0a6cb649 Mon Sep 17 00:00:00 2001 From: Thomas Schatzl Date: Wed, 1 Jul 2026 12:44:51 +0000 Subject: [PATCH 053/305] 8387265: G1: Shutdown during concurrent cycle leaves SATB queues in inconsistent state Reviewed-by: aboldtch, ayang --- src/hotspot/share/gc/g1/g1CollectedHeap.cpp | 3 ++ src/hotspot/share/gc/g1/g1ConcurrentMark.cpp | 20 +++++++++++++ src/hotspot/share/gc/g1/g1ConcurrentMark.hpp | 2 ++ .../share/gc/g1/g1ConcurrentMarkThread.hpp | 3 +- .../gc/g1/g1ConcurrentMarkThread.inline.hpp | 10 +++---- .../share/gc/g1/g1SATBMarkQueueSet.cpp | 9 +----- src/hotspot/share/gc/g1/g1VMOperations.cpp | 30 +++++++++++++++---- src/hotspot/share/gc/g1/g1VMOperations.hpp | 14 ++++++++- src/hotspot/share/runtime/vmOperation.hpp | 1 + 9 files changed, 71 insertions(+), 21 deletions(-) diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp index 7396c1ee9ce..eaa6afb5efa 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp @@ -1654,6 +1654,9 @@ void G1CollectedHeap::stop() { // that are destroyed during shutdown. _cr->stop(); _service_thread->stop(); + VM_G1StopMarking op; + VMThread::execute(&op); + _cm->stop(); } diff --git a/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp b/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp index 11c93b092b1..6f9e4e2e9cf 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp @@ -2036,6 +2036,26 @@ void G1ConcurrentMark::print_stats() { } } +bool G1ConcurrentMark::shutdown_cleanup_needed() const { + // Cleanup (aborting threads, setting abort flags) is needed throughout the whole cycle before + // stopping the CM thread. + return is_fully_initialized() && is_in_concurrent_cycle(); +} + +void G1ConcurrentMark::shutdown_concurrent_cycle() { + assert_at_safepoint_on_vm_thread(); + + abort_root_region_scan_at_safepoint(); + abort_marking_threads(); + + SATBMarkQueueSet& satb_mq_set = G1BarrierSet::satb_mark_queue_set(); + satb_mq_set.abandon_partial_marking(); + // This can be called either during or outside marking, we'll read + // the expected_active value from the SATB queue set. + satb_mq_set.set_active_all_threads(false, /* new active value */ + satb_mq_set.is_active() /* expected_active */); +} + bool G1ConcurrentMark::concurrent_cycle_abort() { assert_at_safepoint_on_vm_thread(); assert(_g1h->collector_state()->is_in_full_gc(), "must be"); diff --git a/src/hotspot/share/gc/g1/g1ConcurrentMark.hpp b/src/hotspot/share/gc/g1/g1ConcurrentMark.hpp index 21518423957..73dabc12863 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentMark.hpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentMark.hpp @@ -608,6 +608,8 @@ public: bool mark_stack_empty() const { return _global_mark_stack.is_empty(); } void concurrent_cycle_start(); + bool shutdown_cleanup_needed() const; + void shutdown_concurrent_cycle(); // Abandon current marking iteration due to a Full GC. bool concurrent_cycle_abort(); void concurrent_cycle_end(bool mark_cycle_completed); diff --git a/src/hotspot/share/gc/g1/g1ConcurrentMarkThread.hpp b/src/hotspot/share/gc/g1/g1ConcurrentMarkThread.hpp index a22442c2b7f..a1c684ecf59 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentMarkThread.hpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentMarkThread.hpp @@ -50,7 +50,8 @@ class G1ConcurrentMarkThread: public ConcurrentGCThread { Atomic _state; - ServiceState state() const { return _state.load_relaxed(); } + ServiceState state() const { return _state.load_acquire(); } + void set_state(ServiceState new_state) { _state.release_store(new_state); } // Returns whether we are in a "Full" cycle. bool is_in_full_concurrent_cycle() const; diff --git a/src/hotspot/share/gc/g1/g1ConcurrentMarkThread.inline.hpp b/src/hotspot/share/gc/g1/g1ConcurrentMarkThread.inline.hpp index bea6fe4e451..3225c253dbb 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentMarkThread.inline.hpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentMarkThread.inline.hpp @@ -48,31 +48,31 @@ inline bool G1ConcurrentMarkThread::is_in_full_concurrent_cycle() const { inline void G1ConcurrentMarkThread::set_idle() { // Concurrent cycle may be aborted any time. assert(!is_idle(), "must not be idle"); - _state.store_relaxed(Idle); + set_state(Idle); } inline void G1ConcurrentMarkThread::start_full_cycle() { assert(SafepointSynchronize::is_at_safepoint(), "must be"); assert(is_idle(), "cycle in progress"); - _state.store_relaxed(FullCycleMarking); + set_state(FullCycleMarking); } inline void G1ConcurrentMarkThread::start_undo_cycle() { assert(SafepointSynchronize::is_at_safepoint(), "must be"); assert(is_idle(), "cycle in progress"); - _state.store_relaxed(UndoCycleResetForNextCycle); + set_state(UndoCycleResetForNextCycle); } inline void G1ConcurrentMarkThread::set_full_cycle_rebuild_and_scrub() { assert(SafepointSynchronize::is_at_safepoint(), "must be"); assert(state() == FullCycleMarking, "must be"); - _state.store_relaxed(FullCycleRebuildOrScrub); + set_state(FullCycleRebuildOrScrub); } inline void G1ConcurrentMarkThread::set_full_cycle_reset_for_next_cycle() { assert(SafepointSynchronize::is_at_safepoint(), "must be"); assert(state() == FullCycleRebuildOrScrub, "must be"); - _state.store_relaxed(FullCycleResetForNextCycle); + set_state(FullCycleResetForNextCycle); } inline bool G1ConcurrentMarkThread::is_in_marking() const { diff --git a/src/hotspot/share/gc/g1/g1SATBMarkQueueSet.cpp b/src/hotspot/share/gc/g1/g1SATBMarkQueueSet.cpp index 7553936bb26..b913bdc2525 100644 --- a/src/hotspot/share/gc/g1/g1SATBMarkQueueSet.cpp +++ b/src/hotspot/share/gc/g1/g1SATBMarkQueueSet.cpp @@ -114,12 +114,5 @@ public: }; void G1SATBMarkQueueSet::filter(SATBMarkQueue& queue) { - G1CollectedHeap* g1h = G1CollectedHeap::heap(); - if (g1h->collector_state()->is_in_marking()) { - apply_filter(G1SATBMarkQueueFilterFn(), queue); - } else { - // is_in_marking() covers both the concurrent marking and the Remark pause. Outside - // of that, there can be no entry that requires SATB marking. - queue.set_empty(); - } + apply_filter(G1SATBMarkQueueFilterFn(), queue); } diff --git a/src/hotspot/share/gc/g1/g1VMOperations.cpp b/src/hotspot/share/gc/g1/g1VMOperations.cpp index 86e55e8ac4f..373ec9660da 100644 --- a/src/hotspot/share/gc/g1/g1VMOperations.cpp +++ b/src/hotspot/share/gc/g1/g1VMOperations.cpp @@ -130,8 +130,13 @@ void VM_G1CollectForAllocation::doit() { } void VM_G1PauseConcurrent::doit() { - GCIdMark gc_id_mark(_gc_id); G1CollectedHeap* g1h = G1CollectedHeap::heap(); + if (_is_shutting_down) { + g1h->concurrent_mark()->shutdown_concurrent_cycle(); + return; + } + + GCIdMark gc_id_mark(_gc_id); GCTraceCPUTime tcpu(g1h->concurrent_mark()->gc_tracer_cm()); // GCTraceTime(...) only supports sub-phases, so a more verbose version @@ -150,12 +155,9 @@ void VM_G1PauseConcurrent::doit() { bool VM_G1PauseConcurrent::doit_prologue() { Heap_lock->lock(); G1CollectedHeap* g1h = G1CollectedHeap::heap(); - if (g1h->is_shutting_down()) { + _is_shutting_down = g1h->is_shutting_down(); + if (_is_shutting_down && !g1h->concurrent_mark()->shutdown_cleanup_needed()) { Heap_lock->unlock(); - // JVM shutdown has started. Abort concurrent marking to ensure that any further - // concurrent VM operations will not try to start and interfere with the shutdown - // process. - g1h->concurrent_mark()->abort_marking_threads(); return false; } return true; @@ -177,3 +179,19 @@ void VM_G1PauseCleanup::work() { G1ConcurrentMark* cm = G1CollectedHeap::heap()->concurrent_mark(); cm->cleanup(); } + +bool VM_G1StopMarking::doit_prologue() { + G1CollectedHeap* g1h = G1CollectedHeap::heap(); +#ifdef ASSERT + { + MutexLocker ml(Heap_lock); + assert(g1h->is_shutting_down(), "must be"); + } +#endif + return g1h->concurrent_mark()->shutdown_cleanup_needed(); +} + +void VM_G1StopMarking::doit() { + G1ConcurrentMark* cm = G1CollectedHeap::heap()->concurrent_mark(); + cm->shutdown_concurrent_cycle(); +} diff --git a/src/hotspot/share/gc/g1/g1VMOperations.hpp b/src/hotspot/share/gc/g1/g1VMOperations.hpp index 458d638b04e..7d56ea1916f 100644 --- a/src/hotspot/share/gc/g1/g1VMOperations.hpp +++ b/src/hotspot/share/gc/g1/g1VMOperations.hpp @@ -80,11 +80,12 @@ public: // Concurrent G1 stop-the-world operations such as remark and cleanup. class VM_G1PauseConcurrent : public VM_Operation { uint _gc_id; + bool _is_shutting_down; const char* _message; protected: VM_G1PauseConcurrent(const char* message) : - _gc_id(GCId::current()), _message(message) { } + _gc_id(GCId::current()), _is_shutting_down(false), _message(message) { } virtual void work() = 0; // Does this concurrent pause affect the memory pools? If so, update the collectionUsage() @@ -116,4 +117,15 @@ public: void work() override; }; +class VM_G1StopMarking : public VM_Operation { +public: + VM_G1StopMarking() : VM_Operation() { } + VMOp_Type type() const override { return VMOp_G1StopMarking; } + + bool doit_prologue() override; + void doit() override; + + bool is_gc_operation() const override { return true; } +}; + #endif // SHARE_GC_G1_G1VMOPERATIONS_HPP diff --git a/src/hotspot/share/runtime/vmOperation.hpp b/src/hotspot/share/runtime/vmOperation.hpp index e22d11cf1a8..af9aa68c7ec 100644 --- a/src/hotspot/share/runtime/vmOperation.hpp +++ b/src/hotspot/share/runtime/vmOperation.hpp @@ -59,6 +59,7 @@ template(G1PauseCleanup) \ template(G1TryInitiateConcMark) \ template(G1RendezvousGCThreads) \ + template(G1StopMarking) \ template(ZMarkEndOld) \ template(ZMarkEndYoung) \ template(ZMarkFlushOperation) \ From 3f52251c9d82991b14f0bbf34c81627911b0fdbf Mon Sep 17 00:00:00 2001 From: Andreas Chmielewski Date: Wed, 1 Jul 2026 20:32:46 +0000 Subject: [PATCH 054/305] 8387124: Incomplete algorithm decomposition for TLS 1.3 cipher suites in SSLAlgorithmDecomposer Reviewed-by: abarashev, mullan --- .../security/ssl/SSLAlgorithmDecomposer.java | 10 + .../BulkCipherDisabledAlgorithms.java | 218 ++++++++++++++++++ .../TLS13BulkCipherDisabledCipherSuite.java | 79 +++++++ 3 files changed, 307 insertions(+) create mode 100644 test/jdk/javax/net/ssl/ciphersuites/BulkCipherDisabledAlgorithms.java create mode 100644 test/jdk/sun/security/ssl/CipherSuite/TLS13BulkCipherDisabledCipherSuite.java diff --git a/src/java.base/share/classes/sun/security/ssl/SSLAlgorithmDecomposer.java b/src/java.base/share/classes/sun/security/ssl/SSLAlgorithmDecomposer.java index 565ed8f6128..61b1236e9bc 100644 --- a/src/java.base/share/classes/sun/security/ssl/SSLAlgorithmDecomposer.java +++ b/src/java.base/share/classes/sun/security/ssl/SSLAlgorithmDecomposer.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2015, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2026, IBM Corporation. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -172,9 +173,18 @@ class SSLAlgorithmDecomposer extends AlgorithmDecomposer { case B_AES_128_GCM: components.add("AES_128_GCM"); break; + case B_AES_128_GCM_IV: + components.add("AES_128_GCM"); + break; case B_AES_256_GCM: components.add("AES_256_GCM"); break; + case B_AES_256_GCM_IV: + components.add("AES_256_GCM"); + break; + case B_CC20_P1305: + components.add("CHACHA20_POLY1305"); + break; } return components; diff --git a/test/jdk/javax/net/ssl/ciphersuites/BulkCipherDisabledAlgorithms.java b/test/jdk/javax/net/ssl/ciphersuites/BulkCipherDisabledAlgorithms.java new file mode 100644 index 00000000000..11f3efb1518 --- /dev/null +++ b/test/jdk/javax/net/ssl/ciphersuites/BulkCipherDisabledAlgorithms.java @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2026, IBM Corporation. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * 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 8387124 + * @summary Test TLS cipher suite disabling via jdk.tls.disabledAlgorithms, + * including matching on bulk cipher components, covering both + * visibility and handshake behavior. + * @library /test/lib + * /javax/net/ssl/TLSCommon + * /javax/net/ssl/templates + * @run main/othervm BulkCipherDisabledAlgorithms visibility + * @run main/othervm BulkCipherDisabledAlgorithms handshake + */ + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.net.ssl.*; + +import jdk.test.lib.process.Proc; + +import java.security.NoSuchAlgorithmException; +import java.security.Security; + +public class BulkCipherDisabledAlgorithms { + + public static void main(String[] args) throws Exception { + if (args.length == 0) { + throw new RuntimeException("Missing mode argument"); + } + + String mode = args[0]; + boolean isVisibilityTest = "visibility".equals(mode); + boolean isHandshakeTest = "handshake".equals(mode); + + if (args.length == 1) { + List tests = buildTests(isVisibilityTest); + + for (String[] test : tests) { + String suite = test[0]; + String disabled = test[1]; + String expected = test[2]; + + System.out.println("================================================="); + System.out.println("Testing: " + mode + + ", suite=" + suite + + ", disabled=" + disabled + + ", expected=" + expected); + + Proc p = Proc.create( + BulkCipherDisabledAlgorithms.class.getName()) + .args(mode, suite, expected) + .secprop("jdk.tls.disabledAlgorithms", disabled) + .inheritIO(); + + p.start().waitFor(0); + } + + System.out.println("TEST PASS - OK"); + return; + } + + String suite = args[1]; + String expected = args[2]; + boolean expectedDisabled = "disabled".equals(expected); + + if (isVisibilityTest) { + testCipherSuiteVisibility(suite, expectedDisabled); + } + + if (isHandshakeTest) { + testHandshake(suite, expectedDisabled); + } + } + + // Returns cipher suites for testing. + // - true: use all supported suites (independent of disabledAlgorithms) + // - false: use default enabled suites (candidates for handshake) + private static CipherSuite[] getCipherSuites(boolean useSupportedSuites) + throws NoSuchAlgorithmException { + SSLEngine engine = SSLContext.getDefault().createSSLEngine(); + String[] suites = useSupportedSuites + ? engine.getSupportedCipherSuites() + : engine.getEnabledCipherSuites(); + + return Arrays.stream(suites) + .map(CipherSuite::cipherSuite) + .filter(cs -> cs != CipherSuite.TLS_EMPTY_RENEGOTIATION_INFO_SCSV) + .toArray(CipherSuite[]::new); + } + + private static List buildTests(boolean useSupportedSuites) + throws NoSuchAlgorithmException { + if (useSupportedSuites) { + // disabledAlgorithms limits supported suites; clear to list all + Security.setProperty("jdk.tls.disabledAlgorithms", ""); + } + + List tests = new ArrayList<>(); + CipherSuite[] suites = getCipherSuites(useSupportedSuites); + + for (CipherSuite suite : suites) { + String suiteName = suite.name(); + String bulk = extractBulkCipher(suiteName); + + tests.add(new String[] { suiteName, suiteName, "disabled" }); + tests.add(new String[] { suiteName, bulk, "disabled" }); + + for (CipherSuite other : suites) { + // Negative test case: disable a different bulk cipher than the one + // used by the current suite. This ensures that the suite remains + // enabled and a successful TLS handshake can still be negotiated. + if (other == suite) { + continue; + } + + String otherBulk = extractBulkCipher(other.name()); + + if (!bulk.equals(otherBulk) + && !suiteName.contains(otherBulk)) { + tests.add(new String[] { suiteName, otherBulk, "enabled" }); + break; + } + } + } + + return tests; + } + + /** + * Separator used in TLS cipher suite names to mark the start of + * the bulk cipher component (e.g. TLS_RSA_WITH_AES_128_CBC_SHA). + */ + private static final String WITH = "_WITH_"; + + private static String extractBulkCipher(String suite) { + if (suite.contains(WITH)) { + String after = suite.substring(suite.indexOf(WITH) + WITH.length()); + int last = after.lastIndexOf('_'); + return after.substring(0, last); + } else { + int first = suite.indexOf('_'); + int last = suite.lastIndexOf('_'); + return suite.substring(first + 1, last); + } + } + + private static void testCipherSuiteVisibility(String suite, boolean expectedDisabled) + throws NoSuchAlgorithmException { + boolean visible = Arrays.asList(getCipherSuites(true)) + .contains(CipherSuite.cipherSuite(suite)); + + if (!expectedDisabled && !visible) { + throw new RuntimeException( + "Cipher suite '" + suite + "' not visible but expected to be enabled"); + } else if (expectedDisabled && visible) { + throw new RuntimeException( + "Cipher suite '" + suite + "' visible but expected to be disabled"); + } + } + + private static void testHandshake(String suite, boolean expectedDisabled) throws Exception { + try { + new TLSHandshakeTest(suite).run(); + + if (expectedDisabled) { + throw new RuntimeException( + "Handshake succeeded but should fail: " + suite); + } + } catch (SSLHandshakeException e) { + if (!expectedDisabled) { + throw new RuntimeException( + "Handshake failed unexpectedly: " + suite, e); + } + } + } + + private static class TLSHandshakeTest extends SSLSocketTemplate { + private final String suite; + + TLSHandshakeTest(String suite) { + this.suite = suite; + } + + @Override + protected void configureClientSocket(SSLSocket socket) { + socket.setEnabledCipherSuites(new String[] { suite }); + } + + @Override + protected void configureServerSocket(SSLServerSocket socket) { + socket.setEnabledCipherSuites(new String[] { suite }); + } + } +} diff --git a/test/jdk/sun/security/ssl/CipherSuite/TLS13BulkCipherDisabledCipherSuite.java b/test/jdk/sun/security/ssl/CipherSuite/TLS13BulkCipherDisabledCipherSuite.java new file mode 100644 index 00000000000..87a6f156152 --- /dev/null +++ b/test/jdk/sun/security/ssl/CipherSuite/TLS13BulkCipherDisabledCipherSuite.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2026, IBM Corporation. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * 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 8387124 + * @summary Test disabling TLS 1.3 cipher suites with bulk ciphers names + * @run testng/othervm TLS13BulkCipherDisabledCipherSuite + */ + +import static org.testng.AssertJUnit.assertTrue; + +import org.testng.annotations.BeforeTest; +import org.testng.annotations.Test; + +import java.security.Security; +import java.util.List; + +public class TLS13BulkCipherDisabledCipherSuite extends AbstractDisableCipherSuites { + + private static final String SECURITY_PROPERTY = "jdk.tls.disabledAlgorithms"; + private static final String TEST_ALGORITHMS = "AES_256_GCM," + + " AES_128_GCM," + + " CHACHA20_POLY1305"; + private static final String[] CIPHER_SUITES = new String[] { + "TLS_AES_256_GCM_SHA384", + "TLS_AES_128_GCM_SHA256", + "TLS_CHACHA20_POLY1305_SHA256" + }; + static final List CIPHER_SUITES_IDS = List.of( + 0x1301, + 0x1302, + 0x1303); + + @Override + protected String getProtocol() { + return "TLSv1.3"; + } + + @BeforeTest + void setUp() throws Exception { + Security.setProperty(SECURITY_PROPERTY, TEST_ALGORITHMS); + } + + @Test + public void testDefault() throws Exception { + assertTrue(testDefaultCase(CIPHER_SUITES_IDS)); + } + + @Test + public void testAddDisabled() throws Exception { + assertTrue(testEngAddDisabled(CIPHER_SUITES, CIPHER_SUITES_IDS)); + } + + @Test + public void testOnlyDisabled() throws Exception { + assertTrue(testEngOnlyDisabled(CIPHER_SUITES)); + } +} From 568bb44750e5a967c233277525c975aec2e88bd3 Mon Sep 17 00:00:00 2001 From: Ashay Rane Date: Thu, 2 Jul 2026 02:21:09 +0000 Subject: [PATCH 055/305] 8386807: com/sun/jndi/ldap/Connection.java references the wrong exception Reviewed-by: dfuchs --- .../share/classes/com/sun/jndi/ldap/Connection.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/java.naming/share/classes/com/sun/jndi/ldap/Connection.java b/src/java.naming/share/classes/com/sun/jndi/ldap/Connection.java index 1e0a924f12c..3bbebf5f9d7 100644 --- a/src/java.naming/share/classes/com/sun/jndi/ldap/Connection.java +++ b/src/java.naming/share/classes/com/sun/jndi/ldap/Connection.java @@ -1173,8 +1173,14 @@ public final class Connection implements Runnable { tlsHandshakeCompleted.complete(tlsServerCert); } catch (SSLPeerUnverifiedException ex) { CommunicationException ce = new CommunicationException(); - ce.setRootCause(closureReason); - tlsHandshakeCompleted.completeExceptionally(ex); + IOException priorFailure = closureReason; + if (priorFailure != null) { + ce.setRootCause(priorFailure); + ce.addSuppressed(ex); + } else { + ce.setRootCause(ex); + } + tlsHandshakeCompleted.completeExceptionally(ce); } } } From a301709aba74d2a7ca744b38b45232fd88cbfbc7 Mon Sep 17 00:00:00 2001 From: Jaikiran Pai Date: Thu, 2 Jul 2026 04:32:10 +0000 Subject: [PATCH 056/305] 8385924: GZIPInputStream.read() behaves differently on some Java versions Reviewed-by: lancea, alanb, simonis --- .../java/util/zip/GZIPInputStream.java | 172 +++++++++++++----- .../GZIP/GZIPInputStreamCallsAvailable.java | 124 +++++++++++++ .../zip/GZIP/GZIPOverBlockingStreams.java | 3 + 3 files changed, 254 insertions(+), 45 deletions(-) create mode 100644 test/jdk/java/util/zip/GZIP/GZIPInputStreamCallsAvailable.java diff --git a/src/java.base/share/classes/java/util/zip/GZIPInputStream.java b/src/java.base/share/classes/java/util/zip/GZIPInputStream.java index 88d08386e8c..8586dc7f63b 100644 --- a/src/java.base/share/classes/java/util/zip/GZIPInputStream.java +++ b/src/java.base/share/classes/java/util/zip/GZIPInputStream.java @@ -58,6 +58,19 @@ import java.util.Objects; * The {@link #close} method should be called to release resources used by this * stream, either directly, or with the {@code try}-with-resources statement. * + * @implNote + * After reading a member trailer, the {@linkplain #read(byte[], int, int) read} method calls + * {@link InputStream#available()} on the underlying stream to determine whether additional + * bytes are available that may represent a subsequent member. If the + * {@systemProperty jdk.util.gzip.tryReadAheadAfterTrailer} system property is set + * to {@code true}, then the call to {@code InputStream.available()} is skipped and the + * implementation instead attempts to read a subsequent member in the stream. + * {@code GZIPInputStream} depends on the return value of {@code InputStream.available()} + * to reliably process a stream with a series of members. Consequently, it may be necessary + * to set this property in environments that process streams with a series of members. By default, + * the {@code jdk.util.gzip.tryReadAheadAfterTrailer} system property is not set, and + * {@code InputStream.available()} gets called. + * * @spec https://www.rfc-editor.org/info/rfc1952 * RFC 1952: GZIP file format specification version 4.3 * @@ -66,6 +79,12 @@ import java.util.Objects; * @since 1.1 */ public class GZIPInputStream extends InflaterInputStream { + + // system property which configures whether we skip the call to InputStream.available() + // when checking for additional GZIP members in a stream + private static final boolean alwaysReadNextMember = + Boolean.getBoolean("jdk.util.gzip.tryReadAheadAfterTrailer"); + /** * GZIP header magic number. */ @@ -119,7 +138,11 @@ public class GZIPInputStream extends InflaterInputStream { super(in, createInflater(in, size), size); usesDefaultInflater = true; try { - readHeader(in); + // we don't expect the stream to be at EOF + // and if it is, then we want readHeader to + // raise an exception, so we pass "true" for + // the "failOnEOF" param. + readHeader(in, true); } catch (IOException ioe) { this.inf.end(); throw ioe; @@ -194,10 +217,15 @@ public class GZIPInputStream extends InflaterInputStream { } int n = super.read(buf, off, len); if (n == -1) { - if (readTrailer()) + if (hasNoMoreMembers()) { eos = true; - else + } else { + // When a next member is available, hasNoMoreMembers() will read + // its header and will position the stream at the next member's + // deflated data. We now decompress and return that member's + // decompressed data. return this.read(buf, off, len); + } } else { crc.update(buf, off, n); } @@ -221,12 +249,40 @@ public class GZIPInputStream extends InflaterInputStream { /* * Reads GZIP member header and returns the total byte number * of this member header. + * If failOnEOF is false and if the given InputStream has already + * reached EOF when this method was invoked, then this method returns + * -1 (indicating that there's no GZIP member header). + * In all other cases of malformed header or EOF being detected + * when reading the header, this method will throw an IOException. */ - private int readHeader(InputStream this_in) throws IOException { - CheckedInputStream in = new CheckedInputStream(this_in, crc); + private int readHeader(InputStream stream, boolean failOnEOF) throws IOException { + CheckedInputStream in = new CheckedInputStream(stream, crc); crc.reset(); + + int magic; + if (!failOnEOF) { + // read an unsigned short value representing the GZIP magic header. + // this is the same as calling readUShort(in), except that here, + // when reading the first byte, we don't raise an EOFException + // if the stream has already reached EOF. + + // read unsigned byte + int b = in.read(); + if (b == -1) { // EOF + crc.reset(); + return -1; // represents no header bytes available + } + checkUnexpectedByte(b); + // read the next unsigned byte to form the unsigned + // short. we throw the usual EOFException/ZipException + // from this point on if there is no more data or + // the data doesn't represent a header. + magic = (readUByte(in) << 8) | b; + } else { + magic = readUShort(in); + } // Check header magic - if (readUShort(in) != GZIP_MAGIC) { + if (magic != GZIP_MAGIC) { throw new ZipException("Not in GZIP format"); } // Check compression method @@ -268,44 +324,66 @@ public class GZIPInputStream extends InflaterInputStream { return n; } - /* - * Reads GZIP member trailer and returns true if the eos - * reached, false if there are more (concatenated gzip - * data set) + /** + * Reads the current GZIP member's trailer and returns true if the end-of-stream is + * reached. After reading the current member's trailer, if the stream has a subsequent + * GZIP member, then this method reads that member's header and returns false indicating + * that there is another member in the stream. */ - private boolean readTrailer() throws IOException { - InputStream in = this.in; - int n = inf.getRemaining(); - if (n > 0) { - in = new SequenceInputStream( - new ByteArrayInputStream(buf, len - n, n), - new FilterInputStream(in) { - public void close() throws IOException {} - }); + private boolean hasNoMoreMembers() throws IOException { + final int numRemainingInInflater = inf.getRemaining(); + InputStream stream = this.in; + if (numRemainingInInflater > 0) { + stream = new SequenceInputStream( + new ByteArrayInputStream(buf, len - numRemainingInInflater, numRemainingInInflater), + new FilterInputStream(stream) { + public void close() {} + }); } - // Uses left-to-right evaluation order - if ((readUInt(in) != crc.getValue()) || - // rfc1952; ISIZE is the input size modulo 2^32 - (readUInt(in) != (inf.getBytesWritten() & 0xffffffffL))) - throw new ZipException("Corrupt GZIP trailer"); - - // If there are more bytes available in "in" or - // the leftover in the "inf" is > 26 bytes: - // this.trailer(8) + next.header.min(10) + next.trailer(8) - // try concatenated case - if (this.in.available() > 0 || n > 26) { - int m = 8; // this.trailer - try { - m += readHeader(in); // next.header - } catch (IOException ze) { - return true; // ignore any malformed, do nothing + // first read the current member's trailer + readTrailer(stream); + // decide whether to read next member's header + final boolean readNextMember = alwaysReadNextMember + || this.in.available() > 0 + || numRemainingInInflater > 26; // current member's trailer == 8 bytes + // + minimum of 10 bytes header for next member + // + mandatory 8 bytes from next member's trailer + // == at least 26 bytes needed for next member to + // be present + if (!readNextMember) { + return true; // no need to read next member + } + // read next member's header + int m = 8; // this.trailer + try { + int numNextHeaderBytes = readHeader(stream, false); // next.header (if available) + if (numNextHeaderBytes == -1) { + return true; // end of stream reached, no more members } - inf.reset(); - if (n > m) - inf.setInput(buf, len - n + m, n - m); - return false; + m += numNextHeaderBytes; + } catch (IOException ze) { + return true; // ignore any malformed, consider it as no more members in the stream + } + inf.reset(); // reset the inflater for fresh input data from the next member + if (numRemainingInInflater > m) { + // position the inflater's input buffer to the start of next member's deflated data + inf.setInput(buf, len - numRemainingInInflater + m, numRemainingInInflater - m); + } + return false; // next member exists + } + + /** + * Reads the current member's trailer + * + * @param stream the InputStream containing the trailer + */ + private void readTrailer(final InputStream stream) throws IOException { + // Uses left-to-right evaluation order + if ((readUInt(stream) != crc.getValue()) || + // rfc1952; ISIZE is the input size modulo 2^32 + (readUInt(stream) != (inf.getBytesWritten() & 0xffffffffL))) { + throw new ZipException("Corrupt GZIP trailer"); } - return true; } /* @@ -332,14 +410,18 @@ public class GZIPInputStream extends InflaterInputStream { if (b == -1) { throw new EOFException(); } - if (b < -1 || b > 255) { - // Report on this.in, not argument in; see read{Header, Trailer}. - throw new IOException(this.in.getClass().getName() - + ".read() returned value out of range -1..255: " + b); - } + checkUnexpectedByte(b); return b; } + private void checkUnexpectedByte(final int b) throws IOException { + if (b < -1 || b > 255) { + // report the InputStream type which returned this unexpected byte + throw new IOException(this.in.getClass().getName() + + ".read() returned value out of range -1..255: " + b); + } + } + /* * Skips bytes of input data blocking until all bytes are skipped. * Does not assume that the input stream is capable of seeking. diff --git a/test/jdk/java/util/zip/GZIP/GZIPInputStreamCallsAvailable.java b/test/jdk/java/util/zip/GZIP/GZIPInputStreamCallsAvailable.java new file mode 100644 index 00000000000..e39b47dfc8e --- /dev/null +++ b/test/jdk/java/util/zip/GZIP/GZIPInputStreamCallsAvailable.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FilterInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.List; +import java.util.Random; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; + +import jdk.test.lib.RandomFactory; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; +import static java.nio.charset.StandardCharsets.US_ASCII; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +/* + * @test + * @summary Verify the behaviour of GZIPInputStream when dealing with InputStream.available() + * on the underlying stream and the jdk.util.gzip.tryReadAheadAfterTrailer + * system property being enabled/disabled + * @key randomness + * @library /test/lib + * @build jdk.test.lib.RandomFactory + * @run junit/othervm -Djdk.util.gzip.tryReadAheadAfterTrailer=true GZIPInputStreamCallsAvailable + * @run junit/othervm -Djdk.util.gzip.tryReadAheadAfterTrailer=false GZIPInputStreamCallsAvailable + * @run junit GZIPInputStreamCallsAvailable + */ +class GZIPInputStreamCallsAvailable { + + private static final boolean AVAILABLE_METHOD_INVOCATION_SKIPPED = + Boolean.getBoolean("jdk.util.gzip.tryReadAheadAfterTrailer"); + private static final Random random = RandomFactory.getRandom(); + + private record TestData(byte[] uncompressed, byte[] compressed) { + } + + static List numGZIPMembers() { + return List.of(1, + 33, + random.nextInt(2, 1001) // a reasonably large number of members + ); + } + + /* + * Verify that GZIPInputStream reads and returns the correct decompressed data when: + * - the underlying InputStream.available() returns an accurate value + * - and when the GZIPInputStream isn't expected to call the underlying InputStream.available() + * method + */ + @ParameterizedTest + @MethodSource("numGZIPMembers") + void testMultipleMembers(final int numMembers) throws IOException { + final TestData testData = createGZIPStream(numMembers); + final InputStream underlyingStream = AVAILABLE_METHOD_INVOCATION_SKIPPED + // stream whose available() method isn't expected to be invoked + ? new AlwaysThrowFromAvailable(new ByteArrayInputStream(testData.compressed)) + // stream whose available() will be invoked and returns an accurate value + : new ByteArrayInputStream(testData.compressed); + try (GZIPInputStream gzip = new GZIPInputStream(underlyingStream)) { + final byte[] decompressed = gzip.readAllBytes(); + assertArrayEquals(testData.uncompressed, decompressed, "unexpected decompressed data"); + } + } + + /* + * Creates and returns bytes representing a GZIP stream consisting of the given number of + * members. + */ + private static TestData createGZIPStream(final int numMembers) throws IOException { + final String content = "foo bar hello world from " + GZIPInputStreamCallsAvailable.class; + final ByteArrayOutputStream uncompressed = new ByteArrayOutputStream(); + final ByteArrayOutputStream gzipped = new ByteArrayOutputStream(); + for (int i = 1; i <= numMembers; i++) { + final ByteArrayOutputStream member = new ByteArrayOutputStream(); + try (final OutputStream gzip = new GZIPOutputStream(member)) { + final byte[] memberRawBytes = ("member-" + i + " " + content).getBytes(US_ASCII); + gzip.write(memberRawBytes); + // keep track of the uncompressed content too so that it can be compared for + // equality with the decompressed content + uncompressed.write(memberRawBytes); + } + // write out the GZIP member to the stream which accumulates all the members + gzipped.write(member.toByteArray()); + } + return new TestData(uncompressed.toByteArray(), gzipped.toByteArray()); + } + + private static class AlwaysThrowFromAvailable extends FilterInputStream { + public AlwaysThrowFromAvailable(InputStream in) { + super(in); + } + + @Override + public int available() { + throw new AssertionError(this.getClass().getName() + + ".available() wasn't expected to be invoked"); + } + } +} diff --git a/test/jdk/java/util/zip/GZIP/GZIPOverBlockingStreams.java b/test/jdk/java/util/zip/GZIP/GZIPOverBlockingStreams.java index 81f55f2f0dd..b6dea98c28d 100644 --- a/test/jdk/java/util/zip/GZIP/GZIPOverBlockingStreams.java +++ b/test/jdk/java/util/zip/GZIP/GZIPOverBlockingStreams.java @@ -63,6 +63,9 @@ import static org.junit.jupiter.api.Assertions.fail; * @library /test/lib * @build jdk.test.lib.net.URIBuilder jdk.test.lib.RandomFactory * @run junit GZIPOverBlockingStreams + * @comment verify it behaves the same when jdk.util.gzip.tryReadAheadAfterTrailer system property + * is set to false + * @run junit/othervm -Djdk.util.gzip.tryReadAheadAfterTrailer=false GZIPOverBlockingStreams */ class GZIPOverBlockingStreams { From e1a3967870c0fc5171e41ebe686eb94c8158dfb2 Mon Sep 17 00:00:00 2001 From: Benjamin Peterson Date: Thu, 2 Jul 2026 05:35:46 +0000 Subject: [PATCH 057/305] 8364322: (fs) fchmodat support for AT_SYMLINK_NOFOLLOW flag too pessimistic on Linux Reviewed-by: alanb --- .../sun/nio/fs/UnixNativeDispatcher.java | 10 ++++---- .../native/libnio/fs/UnixNativeDispatcher.c | 17 +++++++------ .../nio/file/DirectoryStream/SecureDS.java | 24 ++++++++++++++----- 3 files changed, 31 insertions(+), 20 deletions(-) diff --git a/src/java.base/unix/classes/sun/nio/fs/UnixNativeDispatcher.java b/src/java.base/unix/classes/sun/nio/fs/UnixNativeDispatcher.java index 2d72aeb2ee9..ed28e1a1fe7 100644 --- a/src/java.base/unix/classes/sun/nio/fs/UnixNativeDispatcher.java +++ b/src/java.base/unix/classes/sun/nio/fs/UnixNativeDispatcher.java @@ -555,9 +555,10 @@ class UnixNativeDispatcher { /** * Capabilities */ - private static final int SUPPORTS_OPENAT = 1 << 1; // syscalls - private static final int SUPPORTS_XATTR = 1 << 3; - private static final int SUPPORTS_BIRTHTIME = 1 << 16; // other features + private static final int SUPPORTS_OPENAT = 1 << 1; // syscalls + private static final int SUPPORTS_FCHMODAT_NOFOLLOW = 1 << 2; + private static final int SUPPORTS_XATTR = 1 << 3; + private static final int SUPPORTS_BIRTHTIME = 1 << 16; // other features private static final int capabilities; /** @@ -585,9 +586,8 @@ class UnixNativeDispatcher { * Supports fchmodat with AT_SYMLINK_NOFOLLOW flag */ static boolean fchmodatNoFollowSupported() { - return fchmodatNoFollowSupported0(); + return (capabilities & SUPPORTS_FCHMODAT_NOFOLLOW) != 0; } - private static native boolean fchmodatNoFollowSupported0(); private static native int init(); static { diff --git a/src/java.base/unix/native/libnio/fs/UnixNativeDispatcher.c b/src/java.base/unix/native/libnio/fs/UnixNativeDispatcher.c index 4b5cfabebfb..aba16118988 100644 --- a/src/java.base/unix/native/libnio/fs/UnixNativeDispatcher.c +++ b/src/java.base/unix/native/libnio/fs/UnixNativeDispatcher.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -388,17 +388,16 @@ Java_sun_nio_fs_UnixNativeDispatcher_init(JNIEnv* env, jclass this) capabilities |= sun_nio_fs_UnixNativeDispatcher_SUPPORTS_XATTR; #endif - return capabilities; -} - -JNIEXPORT jboolean JNICALL -Java_sun_nio_fs_UnixNativeDispatcher_fchmodatNoFollowSupported0(JNIEnv* env, jclass this) { #if defined(__linux__) - // Linux recognizes but does not support the AT_SYMLINK_NOFOLLOW flag - return JNI_FALSE; + // Linux 6.6+ supports AT_SYMLINK_NOFOLLOW. glibc 2.32+ also provides emulation for older kernels. + if (fchmodat(AT_FDCWD, "", 0, AT_SYMLINK_NOFOLLOW) == 0 || errno != ENOTSUP) { + capabilities |= sun_nio_fs_UnixNativeDispatcher_SUPPORTS_FCHMODAT_NOFOLLOW; + } #else - return JNI_TRUE; + capabilities |= sun_nio_fs_UnixNativeDispatcher_SUPPORTS_FCHMODAT_NOFOLLOW; #endif + + return capabilities; } JNIEXPORT jbyteArray JNICALL diff --git a/test/jdk/java/nio/file/DirectoryStream/SecureDS.java b/test/jdk/java/nio/file/DirectoryStream/SecureDS.java index 870a84a8927..f3321a8c04d 100644 --- a/test/jdk/java/nio/file/DirectoryStream/SecureDS.java +++ b/test/jdk/java/nio/file/DirectoryStream/SecureDS.java @@ -212,6 +212,13 @@ public class SecureDS { Path link = createSymbolicLink(aDir.resolve(linkEntry), fileEntry); Set permsLink = getPosixFilePermissions(link, NOFOLLOW_LINKS); + // Test setting permissions on a regular file through the no-follow view + view = stream.getFileAttributeView(fileEntry, PosixFileAttributeView.class, NOFOLLOW_LINKS); + view.setPermissions(noperms); + assertEquals(noperms, getPosixFilePermissions(file)); + view.setPermissions(permsFile); + assertEquals(permsFile, getPosixFilePermissions(file)); + // Test following link to file view = stream.getFileAttributeView(link, PosixFileAttributeView.class); view.setPermissions(noperms); @@ -220,14 +227,19 @@ public class SecureDS { view.setPermissions(permsFile); assertEquals(permsFile, getPosixFilePermissions(file)); assertEquals(permsLink, getPosixFilePermissions(link, NOFOLLOW_LINKS)); - // Symbolic link permissions do not apply on Linux - if (!Platform.isLinux()) { - // Test not following link to file - view = stream.getFileAttributeView(link, PosixFileAttributeView.class, NOFOLLOW_LINKS); - view.setPermissions(noperms); + + // Test not following link to file + var linkView = stream.getFileAttributeView(link, PosixFileAttributeView.class, NOFOLLOW_LINKS); + if (Platform.isLinux()) { + // Symbolic link permissions do not apply on Linux + assertThrows(IOException.class, () -> linkView.setPermissions(noperms)); + assertEquals(permsFile, getPosixFilePermissions(file)); + assertThrows(IOException.class, () -> linkView.setPermissions(permsLink)); + } else { + linkView.setPermissions(noperms); assertEquals(permsFile, getPosixFilePermissions(file)); assertEquals(noperms, getPosixFilePermissions(link, NOFOLLOW_LINKS)); - view.setPermissions(permsLink); + linkView.setPermissions(permsLink); assertEquals(permsFile, getPosixFilePermissions(file)); assertEquals(permsLink, getPosixFilePermissions(link, NOFOLLOW_LINKS)); } From db987b1e378cb337a5043a8c051f724461405296 Mon Sep 17 00:00:00 2001 From: Ivan Bereziuk Date: Thu, 2 Jul 2026 08:11:09 +0000 Subject: [PATCH 058/305] 8386474: Aarch64: Correct static_assert((N & (N - 1)) == 0 Co-authored-by: Ferenc Rakoczi Reviewed-by: adinn, semery --- src/hotspot/cpu/aarch64/register_aarch64.hpp | 9 +++++---- src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp | 12 ++++++++---- src/hotspot/share/utilities/globalDefinitions.hpp | 4 ++-- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/hotspot/cpu/aarch64/register_aarch64.hpp b/src/hotspot/cpu/aarch64/register_aarch64.hpp index ab83307d526..8d8856d3cf9 100644 --- a/src/hotspot/cpu/aarch64/register_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/register_aarch64.hpp @@ -28,6 +28,7 @@ #include "asm/register.hpp" #include "utilities/checkedCast.hpp" +#include "utilities/globalDefinitions.hpp" #include "utilities/powerOfTwo.hpp" class VMRegImpl; @@ -513,25 +514,25 @@ template bool vs_write_before_read(const VSeq& vout, const VSeq& vi template VSeq vs_front(const VSeq& v) { - static_assert(N > 0 && ((N & 1) == 0), "sequence length must be even"); + static_assert(N > 0 && is_even(N), "sequence length must be even"); return VSeq(v.base(), v.delta()); } template VSeq vs_back(const VSeq& v) { - static_assert(N > 0 && ((N & 1) == 0), "sequence length must be even"); + static_assert(N > 0 && is_even(N), "sequence length must be even"); return VSeq(v.base() + N / 2 * v.delta(), v.delta()); } template VSeq vs_even(const VSeq& v) { - static_assert(N > 0 && ((N & 1) == 0), "sequence length must be even"); + static_assert(N > 0 && is_even(N), "sequence length must be even"); return VSeq(v.base(), v.delta() * 2); } template VSeq vs_odd(const VSeq& v) { - static_assert(N > 0 && ((N & 1) == 0), "sequence length must be even"); + static_assert(N > 0 && is_even(N), "sequence length must be even"); return VSeq(v.base() + v.delta(), v.delta() * 2); } diff --git a/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp b/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp index cae69ac4621..2ad7e00817c 100644 --- a/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp @@ -5442,6 +5442,7 @@ class StubGenerator: public StubCodeGenerator { // address supplied in base. template void vs_ldpq(const VSeq& v, Register base) { + static_assert(N > 0 && is_even(N), "sequence length must be even"); for (int i = 0; i < N; i += 2) { __ ldpq(v[i], v[i+1], Address(base, 16 * i)); } @@ -5452,7 +5453,7 @@ class StubGenerator: public StubCodeGenerator { // in base using post-increment addressing template void vs_ldpq_post(const VSeq& v, Register base) { - static_assert((N & (N - 1)) == 0, "sequence length must be even"); + static_assert(N > 0 && is_even(N), "sequence length must be even"); for (int i = 0; i < N; i += 2) { __ ldpq(v[i], v[i+1], __ post(base, 32)); } @@ -5463,7 +5464,7 @@ class StubGenerator: public StubCodeGenerator { // supplied in base using post-increment addressing template void vs_stpq_post(const VSeq& v, Register base) { - static_assert((N & (N - 1)) == 0, "sequence length must be even"); + static_assert(N > 0 && is_even(N), "sequence length must be even"); for (int i = 0; i < N; i += 2) { __ stpq(v[i], v[i+1], __ post(base, 32)); } @@ -5474,7 +5475,7 @@ class StubGenerator: public StubCodeGenerator { // using post-increment addressing. template void vs_ld2_post(const VSeq& v, Assembler::SIMD_Arrangement T, Register base) { - static_assert((N & (N - 1)) == 0, "sequence length must be even"); + static_assert(N > 0 && is_even(N), "sequence length must be even"); for (int i = 0; i < N; i += 2) { __ ld2(v[i], v[i+1], T, __ post(base, 32)); } @@ -5485,7 +5486,7 @@ class StubGenerator: public StubCodeGenerator { // post-increment addressing. template void vs_st2_post(const VSeq& v, Assembler::SIMD_Arrangement T, Register base) { - static_assert((N & (N - 1)) == 0, "sequence length must be even"); + static_assert(N > 0 && is_even(N), "sequence length must be even"); for (int i = 0; i < N; i += 2) { __ st2(v[i], v[i+1], T, __ post(base, 32)); } @@ -5530,6 +5531,7 @@ class StubGenerator: public StubCodeGenerator { // offsets array template void vs_ldpq_indexed(const VSeq& v, Register base, int start, int (&offsets)[N/2]) { + static_assert(N > 0 && is_even(N), "sequence length must be even"); for (int i = 0; i < N/2; i++) { __ ldpq(v[2*i], v[2*i+1], Address(base, start + offsets[i])); } @@ -5577,6 +5579,7 @@ class StubGenerator: public StubCodeGenerator { template void vs_ld2_indexed(const VSeq& v, Assembler::SIMD_Arrangement T, Register base, Register tmp, int start, int (&offsets)[N/2]) { + static_assert(N > 0 && is_even(N), "sequence length must be even"); for (int i = 0; i < N/2; i++) { __ add(tmp, base, start + offsets[i]); __ ld2(v[2*i], v[2*i+1], T, tmp); @@ -5590,6 +5593,7 @@ class StubGenerator: public StubCodeGenerator { template void vs_st2_indexed(const VSeq& v, Assembler::SIMD_Arrangement T, Register base, Register tmp, int start, int (&offsets)[N/2]) { + static_assert(N > 0 && is_even(N), "sequence length must be even"); for (int i = 0; i < N/2; i++) { __ add(tmp, base, start + offsets[i]); __ st2(v[2*i], v[2*i+1], T, tmp); diff --git a/src/hotspot/share/utilities/globalDefinitions.hpp b/src/hotspot/share/utilities/globalDefinitions.hpp index 40691de518e..5e5a57c3780 100644 --- a/src/hotspot/share/utilities/globalDefinitions.hpp +++ b/src/hotspot/share/utilities/globalDefinitions.hpp @@ -1157,8 +1157,8 @@ inline T clamp(T value, T min, T max) { return MIN2(MAX2(value, min), max); } -inline bool is_odd (intx x) { return x & 1; } -inline bool is_even(intx x) { return !is_odd(x); } +constexpr bool is_odd (intx x) { return x & 1; } +constexpr bool is_even(intx x) { return !is_odd(x); } // abs methods which cannot overflow and so are well-defined across // the entire domain of integer types. From 157cca780278fd276f807ca1c5cf88e73894b936 Mon Sep 17 00:00:00 2001 From: Jaikiran Pai Date: Thu, 2 Jul 2026 09:13:16 +0000 Subject: [PATCH 059/305] 8355412: com/sun/net/httpserver/Test9a.java failed on windows trying to delete file: java.nio.file.FileSystemException: The process cannot access the file because it is being used by another process Reviewed-by: dfuchs --- test/jdk/com/sun/net/httpserver/Test9a.java | 204 -------------------- 1 file changed, 204 deletions(-) delete mode 100644 test/jdk/com/sun/net/httpserver/Test9a.java diff --git a/test/jdk/com/sun/net/httpserver/Test9a.java b/test/jdk/com/sun/net/httpserver/Test9a.java deleted file mode 100644 index 56fbf9953a3..00000000000 --- a/test/jdk/com/sun/net/httpserver/Test9a.java +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright (c) 2005, 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 6270015 - * @library /test/lib - * @build jdk.test.lib.Utils - * jdk.test.lib.net.SimpleSSLContext - * jdk.test.lib.net.URIBuilder - * @run main/othervm Test9a - * @run main/othervm -Djava.net.preferIPv6Addresses=true Test9a - * @summary Light weight HTTP server - */ - -import com.sun.net.httpserver.*; - -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.concurrent.*; -import java.io.*; -import java.net.*; -import javax.net.ssl.*; -import jdk.test.lib.net.SimpleSSLContext; -import jdk.test.lib.net.URIBuilder; - -import static jdk.test.lib.Asserts.assertEquals; -import static jdk.test.lib.Asserts.assertFileContentsEqual; -import static jdk.test.lib.Utils.createTempFileOfSize; - -/* Same as Test1 but requests run in parallel. - */ - -public class Test9a extends Test { - - private static final String TEMP_FILE_PREFIX = - HttpServer.class.getPackageName() + '-' + Test9a.class.getSimpleName() + '-'; - - private static final SSLContext serverCtx = SimpleSSLContext.findSSLContext(); - private static final SSLContext clientCtx = SimpleSSLContext.findSSLContext(); - static volatile boolean error = false; - - public static void main (String[] args) throws Exception { - HttpsServer server = null; - ExecutorService executor=null; - Path smallFilePath = createTempFileOfSize(TEMP_FILE_PREFIX, null, 23); - Path largeFilePath = createTempFileOfSize(TEMP_FILE_PREFIX, null, 2730088); - try { - System.out.print ("Test9a: "); - InetAddress loopback = InetAddress.getLoopbackAddress(); - InetSocketAddress addr = new InetSocketAddress(loopback, 0); - server = HttpsServer.create (addr, 0); - // Assert that both files share the same parent and can be served from the same `FileServerHandler` - assertEquals(smallFilePath.getParent(), largeFilePath.getParent()); - HttpHandler h = new FileServerHandler (smallFilePath.getParent().toString()); - HttpContext c1 = server.createContext ("/", h); - executor = Executors.newCachedThreadPool(); - server.setExecutor (executor); - server.setHttpsConfigurator(new HttpsConfigurator (serverCtx)); - server.start(); - - int port = server.getAddress().getPort(); - error = false; - Thread[] t = new Thread[100]; - - t[0] = test (true, "https", port, smallFilePath); - t[1] = test (true, "https", port, largeFilePath); - t[2] = test (true, "https", port, smallFilePath); - t[3] = test (true, "https", port, largeFilePath); - t[4] = test (true, "https", port, smallFilePath); - t[5] = test (true, "https", port, largeFilePath); - t[6] = test (true, "https", port, smallFilePath); - t[7] = test (true, "https", port, largeFilePath); - t[8] = test (true, "https", port, smallFilePath); - t[9] = test (true, "https", port, largeFilePath); - t[10] = test (true, "https", port, smallFilePath); - t[11] = test (true, "https", port, largeFilePath); - t[12] = test (true, "https", port, smallFilePath); - t[13] = test (true, "https", port, largeFilePath); - t[14] = test (true, "https", port, smallFilePath); - t[15] = test (true, "https", port, largeFilePath); - for (int i=0; i<16; i++) { - t[i].join(); - } - if (error) { - throw new RuntimeException ("error"); - } - - System.out.println ("OK"); - } finally { - if (server != null) - server.stop(0); - if (executor != null) - executor.shutdown(); - Files.delete(smallFilePath); - Files.delete(largeFilePath); - } - } - - static int foo = 1; - - static ClientThread test (boolean fixedLen, String protocol, int port, Path filePath) throws Exception { - ClientThread t = new ClientThread (fixedLen, protocol, port, filePath); - t.start(); - return t; - } - - static Object fileLock = new Object(); - - static class ClientThread extends Thread { - - boolean fixedLen; - String protocol; - int port; - private final Path filePath; - - ClientThread (boolean fixedLen, String protocol, int port, Path filePath) { - this.fixedLen = fixedLen; - this.protocol = protocol; - this.port = port; - this.filePath = filePath; - } - - public void run () { - try { - URL url = URIBuilder.newBuilder() - .scheme(protocol) - .loopback() - .port(port) - .path("/" + filePath.getFileName()) - .toURL(); - - HttpURLConnection urlc = (HttpURLConnection) url.openConnection(Proxy.NO_PROXY); - if (urlc instanceof HttpsURLConnection) { - HttpsURLConnection urlcs = (HttpsURLConnection) urlc; - urlcs.setHostnameVerifier (new HostnameVerifier () { - public boolean verify (String s, SSLSession s1) { - return true; - } - }); - urlcs.setSSLSocketFactory (clientCtx.getSocketFactory()); - } - byte [] buf = new byte [4096]; - - String s = "chunk"; - if (fixedLen) { - urlc.setRequestProperty ("XFixed", "yes"); - s = "fixed"; - } - InputStream is = urlc.getInputStream(); - File temp; - synchronized (fileLock) { - temp = File.createTempFile (s, null); - temp.deleteOnExit(); - } - OutputStream fout = new BufferedOutputStream (new FileOutputStream(temp)); - int c, count = 0; - while ((c=is.read(buf)) != -1) { - count += c; - fout.write (buf, 0, c); - } - is.close(); - fout.close(); - - if (count != filePath.toFile().length()) { - System.out.println ("wrong amount of data returned"); - System.out.println ("fixedLen = "+fixedLen); - System.out.println ("protocol = "+protocol); - System.out.println ("port = "+port); - System.out.println ("file = " + filePath); - System.out.println ("temp = "+temp); - System.out.println ("count = "+count); - error = true; - } - assertFileContentsEqual(filePath, temp.toPath()); - temp.delete(); - } catch (Exception e) { - e.printStackTrace(); - error = true; - } - } - } - -} From c042ad289d10e4c2dba4371630ff82318b0aa23b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Du=EF=BF=BDan=20B=EF=BF=BDlek?= Date: Thu, 2 Jul 2026 09:32:44 +0000 Subject: [PATCH 060/305] 8386842: Preview files in root directory not recognized in system image Reviewed-by: alanb, sherman, liach --- .../jdk/internal/jimage/ImageReader.java | 48 +++++++++++++++-- .../jdk/internal/jimage/ImageReaderTest.java | 52 ++++++++++++++++++- 2 files changed, 95 insertions(+), 5 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/jimage/ImageReader.java b/src/java.base/share/classes/jdk/internal/jimage/ImageReader.java index 2cf28b835ce..59c2392dea9 100644 --- a/src/java.base/share/classes/jdk/internal/jimage/ImageReader.java +++ b/src/java.base/share/classes/jdk/internal/jimage/ImageReader.java @@ -486,7 +486,7 @@ public final class ImageReader implements AutoCloseable { ImageLocation loc = null; if (isPreviewEnabled) { // We must test preview location first (if in preview mode). - loc = findLocation(moduleName, PREVIEW_RESOURCE_PREFIX + resourcePath); + loc = findLocation(moduleName, PREVIEW_RESOURCE_PREFIX + "/" + resourcePath); } if (loc == null) { loc = findLocation(moduleName, resourcePath); @@ -531,7 +531,7 @@ public final class ImageReader implements AutoCloseable { return node.isResource(); } } - loc = findLocation(moduleName, PREVIEW_RESOURCE_PREFIX + resourcePath); + loc = findLocation(moduleName, PREVIEW_RESOURCE_PREFIX + "/" + resourcePath); } if (loc == null) { loc = findLocation(moduleName, resourcePath); @@ -561,7 +561,19 @@ public final class ImageReader implements AutoCloseable { // Now try the non-prefixed resource name, but be careful to avoid false // positives for names like "/modules/modules/xxx" which could return a // location of a directory entry. - loc = findLocation(name.substring(MODULES_PREFIX.length())); + String resourceName = name.substring(MODULES_PREFIX.length()); + if (isPreviewEnabled) { + // Root-level preview resources are not pre-cached when an image + // is opened, so check for them first. + int pathStart = resourceName.indexOf('/', 1); + if (pathStart > 1 && resourceName.indexOf('/', pathStart + 1) < 0) { + loc = findLocation(resourceName.substring(0, pathStart) + + PREVIEW_INFIX + "/" + resourceName.substring(pathStart + 1)); + } + } + if (loc == null) { + loc = findLocation(resourceName); + } return loc != null && loc.getType() == RESOURCE ? ensureCached(newResource(name, loc)) : null; @@ -649,6 +661,36 @@ public final class ImageReader implements AutoCloseable { private Directory completeModuleDirectory(Directory dir, ImageLocation loc) { assert dir.getName().equals(loc.getFullName()) : "Mismatched location for directory: " + dir; List previewOnlyNodes = getPreviewNodesToMerge(dir); + if (isPreviewEnabled && previewOnlyNodes.isEmpty()) { + // When opening an image in preview mode, packages that have preview + // content are eagerly processed, caching preview resources and + // preview-only directories for direct lookup. Root-level preview + // resources are omitted during this process, since they have no + // package path and the empty package is not represented under + // "/packages", and must be processed separately. + int moduleStart = MODULES_PREFIX.length() + 1; + if (dir.getName().indexOf('/', moduleStart) < 0) { + ImageLocation previewLoc = findLocation(dir.getName() + PREVIEW_INFIX); + if (previewLoc != null) { + previewOnlyNodes = createChildNodes(previewLoc, 0, childLoc -> { + String baseName = getBaseName(childLoc); + String nonPreviewChildName = dir.getName() + "/" + baseName; + boolean isPreviewOnly = ImageLocation.isPreviewOnly(childLoc.getFlags()); + LocationType type = childLoc.getType(); + if (type == RESOURCE) { + Node childNode = nodes.computeIfAbsent(nonPreviewChildName, n -> newResource(n, childLoc)); + return isPreviewOnly ? childNode : null; + } else { + assert type == MODULES_DIR : "Invalid location type: " + childLoc; + Node childNode = nodes.get(nonPreviewChildName); + assert !(isPreviewOnly && childNode == null) : + "Inconsistent child node: " + nonPreviewChildName; + return isPreviewOnly ? childNode : null; + } + }); + } + } + } // We hide preview names from direct lookup, but must also prevent // the preview directory from appearing in any META-INF directories. boolean parentIsMetaInfDir = isMetaInf(dir); diff --git a/test/jdk/jdk/internal/jimage/ImageReaderTest.java b/test/jdk/jdk/internal/jimage/ImageReaderTest.java index 5104bb97f95..0fadc6eec12 100644 --- a/test/jdk/jdk/internal/jimage/ImageReaderTest.java +++ b/test/jdk/jdk/internal/jimage/ImageReaderTest.java @@ -46,6 +46,7 @@ import java.util.Set; import java.util.stream.Collectors; import static java.util.stream.Collectors.toSet; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -59,7 +60,7 @@ import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; /* * @test - * @bug 8385355 + * @bug 8385355 8386842 * @summary Tests for ImageReader. * @modules java.base/jdk.internal.jimage * jdk.jlink/jdk.tools.jlink.internal @@ -86,6 +87,11 @@ public class ImageReaderTest { "!META-INF/z", "!META-INF/collision/child.properties", "!META-INF/collision", + // Non-class resource in top level directory + "!fileA.txt", + "!fileB.txt", + "!META-INF/preview/fileA.txt", + "!META-INF/preview/fileB.txt", // Replaces original class in preview mode. "@com.foo.HasPreviewVersion", // New class in existing package in preview mode. @@ -96,6 +102,11 @@ public class ImageReaderTest { // Two new packages in preview mode (new symbolic links). "@com.bar.preview.stuff.Foo", "@com.bar.preview.stuff.Bar"), + "modbaz", Arrays.asList( + "!file.txt", + "!normal.txt", + "!META-INF/preview/file.txt", + "!META-INF/preview/previewOnly.txt"), "modgus", Arrays.asList( // A second module with a preview-only empty package (preview). "@com.bar.preview.other.Gus")); @@ -270,6 +281,18 @@ public class ImageReaderTest { assertAbsent(reader, "/modules/modfoo/com/foo/bar/IsPreviewOnly.class"); assertDirContents(reader, "/modules/modfoo/com/foo", "HasPreviewVersion.class", "NormalFoo.class", "bar"); assertDirContents(reader, "/modules/modfoo/com/foo/bar", "NormalBar.class"); + + // Non-class resource in top level directory + assertResource(reader, "modfoo", "fileA.txt"); + assertNonPreviewResourceVersion(reader, "modfoo", "fileA.txt"); + assertNode(reader, "/modules/modfoo/fileB.txt"); + assertNonPreviewResourceVersion(reader, "modfoo", "fileB.txt"); + assertDirContents(reader, "/modules/modfoo", "META-INF", "module-info.class", "fileA.txt", "fileB.txt", "com"); + + assertAbsent(reader, "/modules/modbaz/previewOnly.txt"); + assertDirContents(reader, "/modules/modbaz", "META-INF", "module-info.class", "file.txt", "normal.txt"); + assertNonPreviewResourceVersion(reader, "modbaz", "file.txt"); + assertNonPreviewResourceVersion(reader, "modbaz", "normal.txt"); } } @@ -289,6 +312,20 @@ public class ImageReaderTest { assertResource(reader, "modfoo", "com/foo/bar/IsPreviewOnly.class"); assertDirContents(reader, "/modules/modfoo/com/foo", "HasPreviewVersion.class", "NormalFoo.class", "bar"); assertDirContents(reader, "/modules/modfoo/com/foo/bar", "NormalBar.class", "IsPreviewOnly.class"); + + // Non-class resource in top level directory + assertResource(reader, "modfoo", "fileA.txt"); + assertPreviewResourceVersion(reader, "modfoo", "fileA.txt"); + assertNode(reader, "/modules/modfoo/fileB.txt"); + assertPreviewResourceVersion(reader, "modfoo", "fileB.txt"); + assertDirContents(reader, "/modules/modfoo/com", "foo"); + assertDirContents(reader, "/modules/modfoo", "META-INF", "module-info.class", "fileA.txt", "fileB.txt", "com"); + + assertNode(reader, "/modules/modbaz/previewOnly.txt"); + assertDirContents(reader, "/modules/modbaz", "META-INF", "module-info.class", "file.txt", "normal.txt", "previewOnly.txt"); + assertPreviewResourceVersion(reader, "modbaz", "file.txt"); + assertNonPreviewResourceVersion(reader, "modbaz", "normal.txt"); + assertPreviewResourceVersion(reader, "modbaz", "previewOnly.txt"); } } @@ -405,6 +442,17 @@ public class ImageReaderTest { assertSame(resNode, reader.findNode(nodeName)); } + private static void assertNonPreviewResourceVersion(ImageReader reader, String modName, String resPath) throws IOException { + Node resNode = reader.findResourceNode(modName, resPath); + assertArrayEquals(resPath.getBytes(StandardCharsets.UTF_8), reader.getResource(resNode)); + } + + private static void assertPreviewResourceVersion(ImageReader reader, String modName, String resPath) throws IOException { + Node resNode = reader.findResourceNode(modName, resPath); + String name = "META-INF/preview/" + resPath; + assertArrayEquals(name.getBytes(StandardCharsets.UTF_8), reader.getResource(resNode)); + } + private static void assertNonPreviewVersion(ImageClassLoader loader, String module, String fqn) throws IOException { assertEquals("Class: " + fqn, loader.loadAndGetToString(module, fqn)); } @@ -441,7 +489,7 @@ public class ImageReaderTest { classes.forEach(fqn -> { if (fqn.startsWith("!")) { - jar.addEntry(fqn.substring(1), "resource".getBytes(StandardCharsets.UTF_8)); + jar.addEntry(fqn.substring(1), fqn.substring(1).getBytes(StandardCharsets.UTF_8)); return; } boolean isPreviewEntry = fqn.startsWith("@"); From 36ca5bbc82f3fe3855016bc6e74e169cb3f2857a Mon Sep 17 00:00:00 2001 From: Albert Mingkun Yang Date: Thu, 2 Jul 2026 11:28:39 +0000 Subject: [PATCH 061/305] 8387129: Parallel: Wrong TaskTerminator in ParallelScavengeRefProcProxyTask Reviewed-by: jsikstro, tschatzl --- src/hotspot/share/gc/parallel/psCompactionManager.hpp | 1 - src/hotspot/share/gc/parallel/psPromotionManager.hpp | 1 + src/hotspot/share/gc/parallel/psScavenge.cpp | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hotspot/share/gc/parallel/psCompactionManager.hpp b/src/hotspot/share/gc/parallel/psCompactionManager.hpp index ee8ab3f7df0..cd56bf1c91e 100644 --- a/src/hotspot/share/gc/parallel/psCompactionManager.hpp +++ b/src/hotspot/share/gc/parallel/psCompactionManager.hpp @@ -60,7 +60,6 @@ public: class ParCompactionManager : public CHeapObj { friend class MarkFromRootsTask; friend class ParallelCompactRefProcProxyTask; - friend class ParallelScavengeRefProcProxyTask; friend class ParMarkBitMap; friend class PSParallelCompact; friend class FillDensePrefixAndCompactionTask; diff --git a/src/hotspot/share/gc/parallel/psPromotionManager.hpp b/src/hotspot/share/gc/parallel/psPromotionManager.hpp index edce4861d4d..287808429d3 100644 --- a/src/hotspot/share/gc/parallel/psPromotionManager.hpp +++ b/src/hotspot/share/gc/parallel/psPromotionManager.hpp @@ -55,6 +55,7 @@ class ParCompactionManager; class PSPromotionManager { friend class PSScavenge; + friend class ParallelScavengeRefProcProxyTask; friend class ScavengeRootsTask; private: diff --git a/src/hotspot/share/gc/parallel/psScavenge.cpp b/src/hotspot/share/gc/parallel/psScavenge.cpp index 8dbd2485e76..883bcb81a50 100644 --- a/src/hotspot/share/gc/parallel/psScavenge.cpp +++ b/src/hotspot/share/gc/parallel/psScavenge.cpp @@ -193,7 +193,7 @@ class ParallelScavengeRefProcProxyTask : public RefProcProxyTask { public: ParallelScavengeRefProcProxyTask(uint max_workers) : RefProcProxyTask("ParallelScavengeRefProcProxyTask", max_workers), - _terminator(max_workers, ParCompactionManager::marking_stacks()) {} + _terminator(max_workers, PSPromotionManager::vm_thread_promotion_manager()->stack_array_depth()) {} void work(uint worker_id) override { assert(worker_id < _max_workers, "sanity"); From e68f5ec8352b226de0c59c30e19ee9a762444048 Mon Sep 17 00:00:00 2001 From: Coleen Phillimore Date: Thu, 2 Jul 2026 11:37:20 +0000 Subject: [PATCH 062/305] 8387633: Remove UnlockExperimentalVMOptions for COH in CDS build Reviewed-by: iklam, erikj, shade --- make/Images.gmk | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/make/Images.gmk b/make/Images.gmk index 8008cfa6779..a09ac7e3bc6 100644 --- a/make/Images.gmk +++ b/make/Images.gmk @@ -142,8 +142,7 @@ define CreateCDSArchive $1_$2_COOPS_OPTION := $(if $(findstring _nocoops, $2),-XX:-UseCompressedOops) # enable and also explicitly disable coh as needed. ifeq ($(call isTargetCpuBits, 64), true) - $1_$2_NOCOH_OPTION := -XX:+UnlockExperimentalVMOptions \ - $(if $(findstring _nocoh, $2),-XX:-UseCompactObjectHeaders,-XX:+UseCompactObjectHeaders) + $1_$2_NOCOH_OPTION := $(if $(findstring _nocoh, $2),-XX:-UseCompactObjectHeaders,-XX:+UseCompactObjectHeaders) endif $1_$2_DUMP_EXTRA_ARG := $$($1_$2_COOPS_OPTION) $$($1_$2_NOCOH_OPTION) $1_$2_DUMP_TYPE := $(if $(findstring _nocoops, $2),-NOCOOPS,)$(if $(findstring _nocoh, $2),-NOCOH,) From afe05fc47e3b77964c1ab6a2a8621ac9184e9e5a Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Thu, 2 Jul 2026 11:51:42 +0000 Subject: [PATCH 063/305] 8387596: DEVKIT_LIB_DIR is unused Reviewed-by: erikj --- make/autoconf/basic.m4 | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/make/autoconf/basic.m4 b/make/autoconf/basic.m4 index bb6908d9194..1591df46a91 100644 --- a/make/autoconf/basic.m4 +++ b/make/autoconf/basic.m4 @@ -1,5 +1,5 @@ # -# Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2011, 2026, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -327,14 +327,6 @@ AC_DEFUN_ONCE([BASIC_SETUP_DEVKIT], elif test -d "$DEVKIT_ROOT/$host/sys-root"; then SYSROOT="$DEVKIT_ROOT/$host/sys-root" fi - - if test "x$DEVKIT_ROOT" != x; then - DEVKIT_LIB_DIR="$DEVKIT_ROOT/lib" - if test "x$OPENJDK_TARGET_CPU_BITS" = x64; then - DEVKIT_LIB_DIR="$DEVKIT_ROOT/lib64" - fi - AC_SUBST(DEVKIT_LIB_DIR) - fi fi # You can force the sysroot if the sysroot encoded into the compiler tools From 052bd362d9538a18a48338ee5d63db457be9b1d2 Mon Sep 17 00:00:00 2001 From: Alexey Ivanov Date: Thu, 2 Jul 2026 12:44:18 +0000 Subject: [PATCH 064/305] 8386056: Test JFileChooser/HTMLFileName.java doesn't run in Nimbus and Motif Reviewed-by: psadhukhan, azvegint --- .../swing/JFileChooser/HTMLFileName.java | 67 +++++++++++++++---- 1 file changed, 53 insertions(+), 14 deletions(-) diff --git a/test/jdk/javax/swing/JFileChooser/HTMLFileName.java b/test/jdk/javax/swing/JFileChooser/HTMLFileName.java index a8bc9525cca..d22d8e207dd 100644 --- a/test/jdk/javax/swing/JFileChooser/HTMLFileName.java +++ b/test/jdk/javax/swing/JFileChooser/HTMLFileName.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -33,7 +33,7 @@ import javax.swing.filechooser.FileSystemView; /* * @test id=metal * @bug 8139228 - * @summary JFileChooser should not render Directory names in HTML format + * @summary JFileChooser should not render directory names in HTML format * @library /java/awt/regtesthelpers * @build PassFailJFrame * @run main/manual HTMLFileName metal @@ -42,15 +42,32 @@ import javax.swing.filechooser.FileSystemView; /* * @test id=system * @bug 8139228 8358532 - * @summary JFileChooser should not render Directory names in HTML format + * @summary JFileChooser should not render directory names in HTML format * @library /java/awt/regtesthelpers * @build PassFailJFrame * @run main/manual HTMLFileName system */ +/* + * @test id=nimbus + * @bug 8139228 + * @summary JFileChooser should not render directory names in HTML format + * @library /java/awt/regtesthelpers + * @build PassFailJFrame + * @run main/manual HTMLFileName nimbus + */ + +/* + * @test id=motif + * @bug 8139228 + * @summary JFileChooser should not render directory names in HTML format + * @library /java/awt/regtesthelpers + * @build PassFailJFrame + * @run main/manual HTMLFileName motif + */ + public class HTMLFileName { private static final String INSTRUCTIONS = """ -
  1. JFileChooser shows a virtual directory. The first file in the list has the following name: @@ -86,31 +103,52 @@ public class HTMLFileName { """; + private static final String MOTIF_INSTRUCTIONS = + "

    Note: there's no navigation combo box in Motif. " + + "Ignore it in the instructions.

    \n"; + + + private static volatile String lafName; + + private static String getLafClassName(String lafKey) { + final String lafClassName; + switch (lafKey) { + case "metal" -> lafClassName = UIManager.getCrossPlatformLookAndFeelClassName(); + case "system" -> lafClassName = UIManager.getSystemLookAndFeelClassName(); + case "nimbus" -> lafClassName = "javax.swing.plaf.nimbus.NimbusLookAndFeel"; + case "motif" -> lafClassName = "com.sun.java.swing.plaf.motif.MotifLookAndFeel"; + default -> throw new IllegalArgumentException("Unsupported Look-and-Feel keyword: " + lafKey); + } + return lafClassName; + } + public static void main(String[] args) throws Exception { if (args.length < 1) { throw new IllegalArgumentException("Look-and-Feel keyword is required"); } - final String lafClassName; - switch (args[0]) { - case "metal" -> lafClassName = UIManager.getCrossPlatformLookAndFeelClassName(); - case "system" -> lafClassName = UIManager.getSystemLookAndFeelClassName(); - default -> throw new IllegalArgumentException("Unsupported Look-and-Feel keyword: " + args[0]); - } + final String lafClassName = getLafClassName(args[0]); SwingUtilities.invokeAndWait(() -> { try { UIManager.setLookAndFeel(lafClassName); + lafName = UIManager.getLookAndFeel().getName(); } catch (Exception e) { throw new RuntimeException(e); } }); + final boolean motif = "CDE/Motif".equals(lafName); + System.out.println("Test for LookAndFeel " + lafClassName); PassFailJFrame.builder() - .instructions(INSTRUCTIONS) - .columns(45) - .rows(20) + .instructions("\n" + + "

    Look and Feel: " + + lafName + "

    \n" + + (motif ? MOTIF_INSTRUCTIONS : "") + + INSTRUCTIONS) + .columns(motif ? 70 : 45) + .rows(25) .testUI(HTMLFileName::initialize) .positionTestUIBottomRowCentered() .build() @@ -127,7 +165,8 @@ public class HTMLFileName { jfc.putClientProperty("html.disable", htmlDisabled); jfc.setControlButtonsAreShown(false); - JFrame frame = new JFrame(htmlDisabled ? "HTML disabled" : "HTML enabled"); + JFrame frame = new JFrame((htmlDisabled ? "HTML disabled" : "HTML enabled") + + " - " + lafName); frame.add(jfc); frame.pack(); return frame; From 9b59c2dc766a71f8a042a6d5b3a8d14b2835df2f Mon Sep 17 00:00:00 2001 From: Srinivas Vamsi Parasa Date: Thu, 2 Jul 2026 12:44:36 +0000 Subject: [PATCH 065/305] 8369020: Test compiler/intrinsics/TestLongUnsignedDivMod.java completed and timed out Reviewed-by: mhaessig, thartmann --- .../jtreg/compiler/intrinsics/TestLongUnsignedDivMod.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/test/hotspot/jtreg/compiler/intrinsics/TestLongUnsignedDivMod.java b/test/hotspot/jtreg/compiler/intrinsics/TestLongUnsignedDivMod.java index ce9444823da..393d33f62a6 100644 --- a/test/hotspot/jtreg/compiler/intrinsics/TestLongUnsignedDivMod.java +++ b/test/hotspot/jtreg/compiler/intrinsics/TestLongUnsignedDivMod.java @@ -110,7 +110,6 @@ public class TestLongUnsignedDivMod { } @Test // needs to be run in (fast) debug mode - @Warmup(10000) @IR(counts = {IRNode.UDIV_L, ">= 1"}) // At least one UDivL node is generated if intrinsic is used public void testDivideUnsigned() { for (int i = 0; i < BUFFER_SIZE; i++) { @@ -124,7 +123,6 @@ public class TestLongUnsignedDivMod { } @Test // needs to be run in (fast) debug mode - @Warmup(10000) @IR(counts = {IRNode.UMOD_L, ">= 1"}) // At least one UModL node is generated if intrinsic is used public void testRemainderUnsigned() { for (int i = 0; i < BUFFER_SIZE; i++) { @@ -139,7 +137,6 @@ public class TestLongUnsignedDivMod { @Test // needs to be run in (fast) debug mode - @Warmup(10000) @IR(applyIfPlatform = {"x64", "true"}, counts = {IRNode.UDIV_MOD_L, ">= 1"}) // At least one UDivModL node is generated if intrinsic is used public void testDivModUnsigned() { From 10af769eb06427e37ae943a21964ae51de4526c6 Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Thu, 2 Jul 2026 13:47:53 +0000 Subject: [PATCH 066/305] 8387298: IS_WIN2000 and IS_WINXP macros are obsolete Reviewed-by: aivanov, dgredler --- .../classes/sun/awt/windows/WToolkit.java | 4 +- .../native/libawt/windows/ComCtl32Util.cpp | 28 ++------ .../windows/native/libawt/windows/awt.h | 6 +- .../native/libawt/windows/awt_Choice.cpp | 8 +-- .../libawt/windows/awt_DesktopProperties.cpp | 65 ++++++------------- .../native/libawt/windows/awt_MenuItem.cpp | 10 +-- .../native/libawt/windows/awt_Toolkit.cpp | 35 ---------- 7 files changed, 32 insertions(+), 124 deletions(-) diff --git a/src/java.desktop/windows/classes/sun/awt/windows/WToolkit.java b/src/java.desktop/windows/classes/sun/awt/windows/WToolkit.java index 4ed3e6b7e68..c60a2d6a362 100644 --- a/src/java.desktop/windows/classes/sun/awt/windows/WToolkit.java +++ b/src/java.desktop/windows/classes/sun/awt/windows/WToolkit.java @@ -173,15 +173,13 @@ public final class WToolkit extends SunToolkit implements Runnable { } } - private static native String getWindowsVersion(); - static { loadLibraries(); initIDs(); // Print out which version of Windows is running if (log.isLoggable(PlatformLogger.Level.FINE)) { - log.fine("Win version: " + getWindowsVersion()); + log.fine("Win version: " + System.getProperty("os.version")); } } diff --git a/src/java.desktop/windows/native/libawt/windows/ComCtl32Util.cpp b/src/java.desktop/windows/native/libawt/windows/ComCtl32Util.cpp index acb3315d1e6..66e9d0c1a89 100644 --- a/src/java.desktop/windows/native/libawt/windows/ComCtl32Util.cpp +++ b/src/java.desktop/windows/native/libawt/windows/ComCtl32Util.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2008, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -42,32 +42,18 @@ void ComCtl32Util::InitLibraries() { } WNDPROC ComCtl32Util::SubclassHWND(HWND hwnd, WNDPROC _WindowProc) { - if (IS_WINXP) { - const SUBCLASSPROC p = SharedWindowProc; // let compiler check type of SharedWindowProc - ::SetWindowSubclass(hwnd, p, (UINT_PTR)_WindowProc, NULL); // _WindowProc is used as subclass ID - return NULL; - } else { - return (WNDPROC)::SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)_WindowProc); - } + const SUBCLASSPROC p = SharedWindowProc; // let compiler check type of SharedWindowProc + ::SetWindowSubclass(hwnd, p, (UINT_PTR)_WindowProc, NULL); // _WindowProc is used as subclass ID + return NULL; } void ComCtl32Util::UnsubclassHWND(HWND hwnd, WNDPROC _WindowProc, WNDPROC _DefWindowProc) { - if (IS_WINXP) { - const SUBCLASSPROC p = SharedWindowProc; // let compiler check type of SharedWindowProc - ::RemoveWindowSubclass(hwnd, p, (UINT_PTR)_WindowProc); // _WindowProc is used as subclass ID - } else { - ::SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)_DefWindowProc); - } + const SUBCLASSPROC p = SharedWindowProc; // let compiler check type of SharedWindowProc + ::RemoveWindowSubclass(hwnd, p, (UINT_PTR)_WindowProc); // _WindowProc is used as subclass ID } LRESULT ComCtl32Util::DefWindowProc(WNDPROC _DefWindowProc, HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { - if (IS_WINXP) { - return ::DefSubclassProc(hwnd, msg, wParam, lParam); - } else if (_DefWindowProc != NULL) { - return ::CallWindowProc(_DefWindowProc, hwnd, msg, wParam, lParam); - } else { - return ::DefWindowProc(hwnd, msg, wParam, lParam); - } + return ::DefSubclassProc(hwnd, msg, wParam, lParam); } LRESULT ComCtl32Util::SharedWindowProc(HWND hwnd, UINT msg, diff --git a/src/java.desktop/windows/native/libawt/windows/awt.h b/src/java.desktop/windows/native/libawt/windows/awt.h index b6289dcae68..c367471afa9 100644 --- a/src/java.desktop/windows/native/libawt/windows/awt.h +++ b/src/java.desktop/windows/native/libawt/windows/awt.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -155,12 +155,8 @@ typedef AwtObject* PDATA; /* /NEW JNI */ /* - * IS_WIN2000 returns TRUE on 2000, XP and Vista - * IS_WINXP returns TRUE on XP and Vista * IS_WINVISTA returns TRUE on Vista */ -#define IS_WIN2000 (LOBYTE(LOWORD(::GetVersion())) >= 5) -#define IS_WINXP ((IS_WIN2000 && HIBYTE(LOWORD(::GetVersion())) >= 1) || LOBYTE(LOWORD(::GetVersion())) > 5) #define IS_WINVISTA (LOBYTE(LOWORD(::GetVersion())) >= 6) #define IS_WIN8 ( \ (IS_WINVISTA && (HIBYTE(LOWORD(::GetVersion())) >= 2)) || \ diff --git a/src/java.desktop/windows/native/libawt/windows/awt_Choice.cpp b/src/java.desktop/windows/native/libawt/windows/awt_Choice.cpp index 125065c92fe..b7c45463c5a 100644 --- a/src/java.desktop/windows/native/libawt/windows/awt_Choice.cpp +++ b/src/java.desktop/windows/native/libawt/windows/awt_Choice.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -179,11 +179,7 @@ AwtChoice* AwtChoice::Create(jobject peer, jobject parent) { ::GetClientRect(c->GetHWnd(), &rc); env->SetIntField(target, AwtComponent::widthID, c->ScaleDownX(rc.right)); env->SetIntField(target, AwtComponent::heightID, c->ScaleDownY(rc.bottom)); - - if (IS_WINXP) { - ::SendMessage(c->GetHWnd(), CB_SETMINVISIBLE, (WPARAM) MINIMUM_NUMBER_OF_VISIBLE_ITEMS, 0); - } - + ::SendMessage(c->GetHWnd(), CB_SETMINVISIBLE, (WPARAM) MINIMUM_NUMBER_OF_VISIBLE_ITEMS, 0); env->DeleteLocalRef(dimension); } } catch (...) { diff --git a/src/java.desktop/windows/native/libawt/windows/awt_DesktopProperties.cpp b/src/java.desktop/windows/native/libawt/windows/awt_DesktopProperties.cpp index 502433a13aa..d5ad022c1e0 100644 --- a/src/java.desktop/windows/native/libawt/windows/awt_DesktopProperties.cpp +++ b/src/java.desktop/windows/native/libawt/windows/awt_DesktopProperties.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -76,9 +76,7 @@ void AwtDesktopProperties::GetWindowsParameters() { GetOtherParameters(); GetSoundEvents(); GetSystemProperties(); - if (IS_WINXP) { - GetXPStyleProperties(); - } + GetXPStyleProperties(); } void getInvScale(float &invScaleX, float &invScaleY) { @@ -423,12 +421,8 @@ void CheckFontSmoothingSettings(HWND hWnd) { if (firstTime) { SystemParametersInfo(SPI_GETFONTSMOOTHING, 0, &fontSmoothing, 0); - if (IS_WINXP) { - SystemParametersInfo(SPI_GETFONTSMOOTHINGTYPE, 0, - &fontSmoothingType, 0); - SystemParametersInfo(SPI_GETFONTSMOOTHINGCONTRAST, 0, - &fontSmoothingContrast, 0); - } + SystemParametersInfo(SPI_GETFONTSMOOTHINGTYPE, 0, &fontSmoothingType, 0); + SystemParametersInfo(SPI_GETFONTSMOOTHINGCONTRAST, 0, &fontSmoothingContrast, 0); lastFontSmoothing = fontSmoothing; lastFontSmoothingType = fontSmoothingType; lastFontSmoothingContrast = fontSmoothingContrast; @@ -441,28 +435,18 @@ void CheckFontSmoothingSettings(HWND hWnd) { /* no need to check the other settings in this case. */ return; } - if (IS_WINXP) { - SystemParametersInfo(SPI_GETFONTSMOOTHINGTYPE, 0, - &fontSmoothingType, 0); - settingsChanged |= fontSmoothingType != lastFontSmoothingType; - if (!settingsChanged && - fontSmoothingType == FONTSMOOTHING_STANDARD) { - /* No need to check any LCD specific settings */ - return; - } else { - SystemParametersInfo(SPI_GETFONTSMOOTHINGCONTRAST, 0, - &fontSmoothingContrast, 0); - settingsChanged |= - fontSmoothingContrast != lastFontSmoothingContrast; - if (fontSmoothingType == FONTSMOOTHING_LCD) { - // Order is a registry entry so more expensive to check.x - subPixelOrder = GetLCDSubPixelOrder(); - settingsChanged |= subPixelOrder != lastSubpixelOrder; - } - } + SystemParametersInfo(SPI_GETFONTSMOOTHINGTYPE, 0, &fontSmoothingType, 0); + settingsChanged |= fontSmoothingType != lastFontSmoothingType; + if (!settingsChanged && fontSmoothingType == FONTSMOOTHING_STANDARD) { + /* No need to check any LCD specific settings */ + return; } else { - if (settingsChanged && fontSmoothing == FONTSMOOTHING_ON) { - fontSmoothingType = FONTSMOOTHING_STANDARD; + SystemParametersInfo(SPI_GETFONTSMOOTHINGCONTRAST, 0, &fontSmoothingContrast, 0); + settingsChanged |= fontSmoothingContrast != lastFontSmoothingContrast; + if (fontSmoothingType == FONTSMOOTHING_LCD) { + // Order is a registry entry so more expensive to check.x + subPixelOrder = GetLCDSubPixelOrder(); + settingsChanged |= subPixelOrder != lastSubpixelOrder; } } } @@ -519,13 +503,7 @@ void AwtDesktopProperties::GetColorParameters() { SetColorProperty(TEXT("win.mdi.backgroundColor"), GetSysColor(COLOR_APPWORKSPACE)); SetColorProperty(TEXT("win.menu.backgroundColor"), GetSysColor(COLOR_MENU)); SetColorProperty(TEXT("win.menu.textColor"), GetSysColor(COLOR_MENUTEXT)); - // COLOR_MENUBAR is only defined on WindowsXP. Our binaries are - // built on NT, hence the below ifdef. -#ifndef COLOR_MENUBAR -#define COLOR_MENUBAR 30 -#endif - SetColorProperty(TEXT("win.menubar.backgroundColor"), - GetSysColor(IS_WINXP ? COLOR_MENUBAR : COLOR_MENU)); + SetColorProperty(TEXT("win.menubar.backgroundColor"), GetSysColor(COLOR_MENUBAR)); SetColorProperty(TEXT("win.scrollbar.backgroundColor"), GetSysColor(COLOR_SCROLLBAR)); SetColorProperty(TEXT("win.text.grayedTextColor"), GetSysColor(COLOR_GRAYTEXT)); SetColorProperty(TEXT("win.tooltip.backgroundColor"), GetSysColor(COLOR_INFOBK)); @@ -540,14 +518,9 @@ void AwtDesktopProperties::GetOtherParameters() { SetBooleanProperty(TEXT("win.text.fontSmoothingOn"), GetBooleanParameter(SPI_GETFONTSMOOTHING)); // TODO END - if (IS_WINXP) { - SetIntegerProperty(TEXT("win.text.fontSmoothingType"), - GetIntegerParameter(SPI_GETFONTSMOOTHINGTYPE)); - SetIntegerProperty(TEXT("win.text.fontSmoothingContrast"), - GetIntegerParameter(SPI_GETFONTSMOOTHINGCONTRAST)); - SetIntegerProperty(TEXT("win.text.fontSmoothingOrientation"), - GetLCDSubPixelOrder()); - } + SetIntegerProperty(TEXT("win.text.fontSmoothingType"), GetIntegerParameter(SPI_GETFONTSMOOTHINGTYPE)); + SetIntegerProperty(TEXT("win.text.fontSmoothingContrast"), GetIntegerParameter(SPI_GETFONTSMOOTHINGCONTRAST)); + SetIntegerProperty(TEXT("win.text.fontSmoothingOrientation"), GetLCDSubPixelOrder()); int cxdrag = GetSystemMetrics(SM_CXDRAG); int cydrag = GetSystemMetrics(SM_CYDRAG); diff --git a/src/java.desktop/windows/native/libawt/windows/awt_MenuItem.cpp b/src/java.desktop/windows/native/libawt/windows/awt_MenuItem.cpp index ace140593f6..ff7e01df3e8 100644 --- a/src/java.desktop/windows/native/libawt/windows/awt_MenuItem.cpp +++ b/src/java.desktop/windows/native/libawt/windows/awt_MenuItem.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -369,14 +369,8 @@ AwtMenuItem::DrawSelf(DRAWITEMSTRUCT& drawInfo) // Disabled text must be drawn in gray. crText = ::GetSysColor(bEnabled? COLOR_HIGHLIGHTTEXT : COLOR_GRAYTEXT); } else { - // COLOR_MENUBAR is only defined on WindowsXP. Our binaries are - // built on NT, hence the below ifdef. - -#ifndef COLOR_MENUBAR -#define COLOR_MENUBAR 30 -#endif // Set background and text colors for unselected item - if (IS_WINXP && IsTopMenu() && AwtDesktopProperties::IsXPStyle()) { + if (IsTopMenu() && AwtDesktopProperties::IsXPStyle()) { crBack = ::GetSysColor (COLOR_MENUBAR); } else { crBack = ::GetSysColor (COLOR_MENU); diff --git a/src/java.desktop/windows/native/libawt/windows/awt_Toolkit.cpp b/src/java.desktop/windows/native/libawt/windows/awt_Toolkit.cpp index a94c96c58c5..c91ff821cba 100644 --- a/src/java.desktop/windows/native/libawt/windows/awt_Toolkit.cpp +++ b/src/java.desktop/windows/native/libawt/windows/awt_Toolkit.cpp @@ -2848,41 +2848,6 @@ Java_sun_awt_windows_WToolkit_isDynamicLayoutSupportedNative(JNIEnv *env, CATCH_BAD_ALLOC_RET(FALSE); } -/* - * Class: sun_awt_windows_WToolkit - * Method: printWindowsVersion - * Signature: ()Ljava/lang/String; - */ -JNIEXPORT jstring JNICALL -Java_sun_awt_windows_WToolkit_getWindowsVersion(JNIEnv *env, jclass cls) -{ - TRY; - - WCHAR szVer[128]; - - DWORD version = ::GetVersion(); - swprintf(szVer, 128, L"0x%x = %ld", version, version); - int l = lstrlen(szVer); - - if (IS_WIN2000) { - if (IS_WINXP) { - if (IS_WINVISTA) { - swprintf(szVer + l, 128, L" (Windows Vista)"); - } else { - swprintf(szVer + l, 128, L" (Windows XP)"); - } - } else { - swprintf(szVer + l, 128, L" (Windows 2000)"); - } - } else { - swprintf(szVer + l, 128, L" (Unknown)"); - } - - return JNU_NewStringPlatform(env, szVer); - - CATCH_BAD_ALLOC_RET(NULL); -} - JNIEXPORT void JNICALL Java_sun_awt_windows_WToolkit_showTouchKeyboard(JNIEnv *env, jobject self, jboolean causedByTouchEvent) From 43838f04c35cc3f2ca1131f236d6a3af5a76ff2f Mon Sep 17 00:00:00 2001 From: Saint Wesonga Date: Thu, 2 Jul 2026 14:41:17 +0000 Subject: [PATCH 067/305] 8387302: Disable reserved stack areas for critical sections on Windows AArch64 Reviewed-by: dlong, shade --- src/hotspot/cpu/aarch64/globalDefinitions_aarch64.hpp | 2 ++ src/hotspot/cpu/aarch64/globals_aarch64.hpp | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/hotspot/cpu/aarch64/globalDefinitions_aarch64.hpp b/src/hotspot/cpu/aarch64/globalDefinitions_aarch64.hpp index 1e788590b64..30aa30aede9 100644 --- a/src/hotspot/cpu/aarch64/globalDefinitions_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/globalDefinitions_aarch64.hpp @@ -61,7 +61,9 @@ const bool CCallingConventionRequiresIntsAsLongs = false; // evidence that it's worth doing. #define DEOPTIMIZE_WHEN_PATCHING +#if !defined(_WINDOWS) #define SUPPORT_RESERVED_STACK_AREA +#endif #if defined(__APPLE__) || defined(_WIN64) #define R18_RESERVED diff --git a/src/hotspot/cpu/aarch64/globals_aarch64.hpp b/src/hotspot/cpu/aarch64/globals_aarch64.hpp index 59c7e44b0e5..1db73ff0306 100644 --- a/src/hotspot/cpu/aarch64/globals_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/globals_aarch64.hpp @@ -48,7 +48,7 @@ define_pd_global(intx, OptoLoopAlignment, 16); // stack if compiled for unix and LP64. To pass stack overflow tests we need // 20 shadow pages. #define DEFAULT_STACK_SHADOW_PAGES (20 DEBUG_ONLY(+5)) -#define DEFAULT_STACK_RESERVED_PAGES (1) +#define DEFAULT_STACK_RESERVED_PAGES (NOT_WINDOWS(1) WINDOWS_ONLY(0)) #define MIN_STACK_YELLOW_PAGES DEFAULT_STACK_YELLOW_PAGES #define MIN_STACK_RED_PAGES DEFAULT_STACK_RED_PAGES From 5a781620b81ecbd699e3a32f95ef417d49d4deec Mon Sep 17 00:00:00 2001 From: Chris Plummer Date: Thu, 2 Jul 2026 19:56:42 +0000 Subject: [PATCH 068/305] 8327967: vmTestbase/nsk/jdi/EventRequestManager/threadStartRequests/thrstartreq002/TestDescription.java fails intermittently Reviewed-by: sspitsyn, dholmes --- test/hotspot/jtreg/ProblemList-Virtual.txt | 1 - test/hotspot/jtreg/vmTestbase/nsk/share/jdi/JDIBase.java | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/hotspot/jtreg/ProblemList-Virtual.txt b/test/hotspot/jtreg/ProblemList-Virtual.txt index b30a09a7710..14601f2ba6f 100644 --- a/test/hotspot/jtreg/ProblemList-Virtual.txt +++ b/test/hotspot/jtreg/ProblemList-Virtual.txt @@ -31,7 +31,6 @@ vmTestbase/vm/mlvm/indy/func/jvmti/mergeCP_indy2manyDiff_a/TestDescription.java vmTestbase/nsk/jvmti/unit/functions/Dispose/JvmtiTest/TestDescription.java 8387429 generic-all vmTestbase/nsk/jvmti/scenarios/capability/CM02/cm02t001/TestDescription.java 8299217 generic-all -vmTestbase/nsk/jdi/EventRequestManager/threadStartRequests/thrstartreq002/TestDescription.java 8327967 generic-all #### diff --git a/test/hotspot/jtreg/vmTestbase/nsk/share/jdi/JDIBase.java b/test/hotspot/jtreg/vmTestbase/nsk/share/jdi/JDIBase.java index 88b1de413dd..91a06a426c0 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/share/jdi/JDIBase.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/share/jdi/JDIBase.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -242,6 +242,7 @@ public class JDIBase { ThreadStartEvent tse = (ThreadStartEvent) event; log2("ThreadStartEvent is received while waiting for a breakpoint" + " event, thread: : " + tse.thread().name()); + eventSet.resume(); continue; } From 41a6eee8756ccd2ae8c511f1aacf7454aa5731db Mon Sep 17 00:00:00 2001 From: Ashay Rane Date: Fri, 3 Jul 2026 04:33:51 +0000 Subject: [PATCH 069/305] 8387625: Add "dt_socket" to `CheckedFeatures.notImplemented` for Windows/ARM64 Reviewed-by: cjplummer, shade --- .../jtreg/vmTestbase/nsk/share/jdi/ArgumentHandler.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/hotspot/jtreg/vmTestbase/nsk/share/jdi/ArgumentHandler.java b/test/hotspot/jtreg/vmTestbase/nsk/share/jdi/ArgumentHandler.java index 08bfad05a04..11ccfc9447c 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/share/jdi/ArgumentHandler.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/share/jdi/ArgumentHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -597,6 +597,9 @@ class CheckedFeatures { {"windows-x64", "com.sun.jdi.CommandLineLaunch", "dt_socket"}, {"windows-x64", "com.sun.jdi.RawCommandLineLaunch", "dt_socket"}, + {"windows-aarch64", "com.sun.jdi.CommandLineLaunch", "dt_socket"}, + {"windows-aarch64", "com.sun.jdi.RawCommandLineLaunch", "dt_socket"}, + {"macosx-amd64", "com.sun.jdi.CommandLineLaunch", "dt_shmem"}, {"macosx-amd64", "com.sun.jdi.RawCommandLineLaunch", "dt_shmem"}, From 23d1e859c04e71a42d93862e5fab5d223608fb2b Mon Sep 17 00:00:00 2001 From: Tobias Hartmann Date: Fri, 3 Jul 2026 06:06:00 +0000 Subject: [PATCH 070/305] 8387622: Tests producing many small output writes are extremely slow on Windows Reviewed-by: chagedorn, mchevalier, kvn --- .../jtreg/compiler/arguments/TestC1Globals.java | 5 +++-- .../compiler/arguments/TestTraceTypeProfile.java | 3 ++- .../jtreg/compiler/c1/TestCITimeCountLinearScan.java | 4 +++- .../compiler/c1/TestPrintIRDuringConstruction.java | 6 ++++-- .../jtreg/compiler/c1/TestTraceLinearScanLevel.java | 5 +++-- test/hotspot/jtreg/compiler/c2/TestFindNode.java | 5 +++-- .../jtreg/compiler/c2/TestPrintIdealNodeCount.java | 5 +++-- .../c2/TestReduceAllocationAndNonExactAllocate.java | 3 +-- .../scalarReplacement/AllocationMergesTests.java | 3 --- .../jtreg/compiler/debug/TestCountCompiledCalls.java | 2 +- .../jtreg/compiler/debug/TestLogStackAssert.java | 6 ++++-- .../jtreg/compiler/debug/TestTracePhaseCCP.java | 9 +++++---- .../jtreg/compiler/debug/TraceIterativeGVN.java | 4 ++-- .../loopopts/TestBadlyFormedCountedLoop.java | 5 +++-- .../jtreg/compiler/loopopts/TestCMoveLimitType.java | 4 ++-- .../jtreg/compiler/print/PrintCompileQueue.java | 4 +++- test/hotspot/jtreg/compiler/print/PrintInlining.java | 12 ++++++------ .../compiler/print/TestPrintAssemblyDeoptRace.java | 5 +++-- .../compiler/print/TestPrintInliningLateMHCall.java | 5 ++++- .../print/TestPrintInliningLateVirtualCall.java | 5 ++++- .../print/TestProfileReturnTypePrinting.java | 5 +++-- .../jtreg/compiler/print/TestTraceOptoParse.java | 4 +++- .../compiler/relocations/TestPrintRelocations.java | 6 ++++-- .../uncommontrap/TestDeoptDetailsLockRank.java | 6 ++++-- .../jtreg/compiler/uncommontrap/TestDeoptOOM.java | 3 ++- .../TestPrintDiagnosticsWithoutProfileTraps.java | 4 ++-- .../uncommontrap/TraceDeoptimizationNoRealloc.java | 5 +++-- 27 files changed, 80 insertions(+), 53 deletions(-) diff --git a/test/hotspot/jtreg/compiler/arguments/TestC1Globals.java b/test/hotspot/jtreg/compiler/arguments/TestC1Globals.java index ba3d8aef191..b41b99b391b 100644 --- a/test/hotspot/jtreg/compiler/arguments/TestC1Globals.java +++ b/test/hotspot/jtreg/compiler/arguments/TestC1Globals.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,7 +27,8 @@ * @requires vm.debug * @summary Test flag with c1 value numbering * - * @run main/othervm -XX:+PrintValueNumbering -XX:+Verbose -XX:-UseLocalValueNumbering + * @run main/othervm -XX:-DisplayVMOutput + * -XX:+PrintValueNumbering -XX:+Verbose -XX:-UseLocalValueNumbering * -Xcomp -XX:TieredStopAtLevel=1 * compiler.arguments.TestC1Globals */ diff --git a/test/hotspot/jtreg/compiler/arguments/TestTraceTypeProfile.java b/test/hotspot/jtreg/compiler/arguments/TestTraceTypeProfile.java index df1c253b689..f61018738ae 100644 --- a/test/hotspot/jtreg/compiler/arguments/TestTraceTypeProfile.java +++ b/test/hotspot/jtreg/compiler/arguments/TestTraceTypeProfile.java @@ -25,7 +25,8 @@ * @test * @summary Test running TraceTypeProfile enabled. * @run main/othervm -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions - * -XX:+TraceTypeProfile compiler.arguments.TestTraceTypeProfile + * -XX:-DisplayVMOutput -XX:+TraceTypeProfile + * compiler.arguments.TestTraceTypeProfile */ package compiler.arguments; diff --git a/test/hotspot/jtreg/compiler/c1/TestCITimeCountLinearScan.java b/test/hotspot/jtreg/compiler/c1/TestCITimeCountLinearScan.java index e67a3679758..81069210ee1 100644 --- a/test/hotspot/jtreg/compiler/c1/TestCITimeCountLinearScan.java +++ b/test/hotspot/jtreg/compiler/c1/TestCITimeCountLinearScan.java @@ -25,7 +25,9 @@ * @test * @bug 8374518 * @summary Sanity check the flag -XX:+CITime and -XX:+CountLinearScan - * @run main/othervm -Xbatch -XX:+IgnoreUnrecognizedVMOptions -XX:+CITime -XX:+CountLinearScan ${test.main.class} + * @run main/othervm -Xbatch -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions + * -XX:-DisplayVMOutput -XX:+CITime -XX:+CountLinearScan + * ${test.main.class} */ package compiler.c1; diff --git a/test/hotspot/jtreg/compiler/c1/TestPrintIRDuringConstruction.java b/test/hotspot/jtreg/compiler/c1/TestPrintIRDuringConstruction.java index d406438d39f..ba2e1b081f5 100644 --- a/test/hotspot/jtreg/compiler/c1/TestPrintIRDuringConstruction.java +++ b/test/hotspot/jtreg/compiler/c1/TestPrintIRDuringConstruction.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,7 +26,9 @@ * @summary load/store elimination will print out instructions without bcis. * @bug 8235383 * @requires vm.debug == true & vm.compiler1.enabled - * @run main/othervm -XX:+TieredCompilation -XX:TieredStopAtLevel=1 -Xcomp -XX:+PrintIRDuringConstruction -XX:+Verbose compiler.c1.TestPrintIRDuringConstruction + * @run main/othervm -XX:+TieredCompilation -XX:TieredStopAtLevel=1 -Xcomp + * -XX:-DisplayVMOutput -XX:+PrintIRDuringConstruction -XX:+Verbose + * compiler.c1.TestPrintIRDuringConstruction */ package compiler.c1; diff --git a/test/hotspot/jtreg/compiler/c1/TestTraceLinearScanLevel.java b/test/hotspot/jtreg/compiler/c1/TestTraceLinearScanLevel.java index 233498d3c04..7eafb5b1c50 100644 --- a/test/hotspot/jtreg/compiler/c1/TestTraceLinearScanLevel.java +++ b/test/hotspot/jtreg/compiler/c1/TestTraceLinearScanLevel.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,7 +27,8 @@ * @summary Sanity check the flag TraceLinearScanLevel with the highest level in a silent HelloWorld program. * * @requires vm.debug == true & vm.compiler1.enabled & vm.compMode != "Xcomp" - * @run main/othervm -Xbatch -XX:TraceLinearScanLevel=4 compiler.c1.TestTraceLinearScanLevel + * @run main/othervm -Xbatch -XX:-DisplayVMOutput -XX:TraceLinearScanLevel=4 + * compiler.c1.TestTraceLinearScanLevel */ package compiler.c1; diff --git a/test/hotspot/jtreg/compiler/c2/TestFindNode.java b/test/hotspot/jtreg/compiler/c2/TestFindNode.java index fa545da7e58..09f94a18f69 100644 --- a/test/hotspot/jtreg/compiler/c2/TestFindNode.java +++ b/test/hotspot/jtreg/compiler/c2/TestFindNode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,7 +27,8 @@ * @requires vm.debug == true & vm.flavor == "server" * @summary Test which uses some special flags in order to test Node::find() in debug builds which could result in an endless loop or a stack overflow crash. * - * @run main/othervm -Xbatch -XX:CompileCommand=option,*::*,bool,Vectorize,true + * @run main/othervm -Xbatch -XX:-DisplayVMOutput + * -XX:CompileCommand=option,*::*,bool,Vectorize,true * -XX:+PrintOpto -XX:+TraceLoopOpts compiler.c2.TestFindNode */ package compiler.c2; diff --git a/test/hotspot/jtreg/compiler/c2/TestPrintIdealNodeCount.java b/test/hotspot/jtreg/compiler/c2/TestPrintIdealNodeCount.java index af252265b76..aeb391ea15f 100644 --- a/test/hotspot/jtreg/compiler/c2/TestPrintIdealNodeCount.java +++ b/test/hotspot/jtreg/compiler/c2/TestPrintIdealNodeCount.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,7 +27,8 @@ * @requires vm.debug == true & vm.compiler2.enabled * @summary Run with -Xcomp -XX:-TieredCompilation to force C2 compilations to test -XX:+PrintIdealNodeCount in debug builds. * - * @run main/othervm -Xcomp -XX:-TieredCompilation -XX:+PrintIdealNodeCount compiler.c2.TestPrintIdealNodeCount + * @run main/othervm -Xcomp -XX:-TieredCompilation -XX:-DisplayVMOutput + * -XX:+PrintIdealNodeCount compiler.c2.TestPrintIdealNodeCount */ package compiler.c2; diff --git a/test/hotspot/jtreg/compiler/c2/TestReduceAllocationAndNonExactAllocate.java b/test/hotspot/jtreg/compiler/c2/TestReduceAllocationAndNonExactAllocate.java index 1146d189ce2..ccb00c635c5 100644 --- a/test/hotspot/jtreg/compiler/c2/TestReduceAllocationAndNonExactAllocate.java +++ b/test/hotspot/jtreg/compiler/c2/TestReduceAllocationAndNonExactAllocate.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -32,7 +32,6 @@ * -XX:CompileCommand=compileonly,*::allocateInstance * -XX:CompileCommand=dontinline,*TestReduceAllocationAndNonExactAllocate*::* * -XX:+UnlockDiagnosticVMOptions - * -XX:+TraceReduceAllocationMerges * -XX:-TieredCompilation * -Xbatch * -Xcomp diff --git a/test/hotspot/jtreg/compiler/c2/irTests/scalarReplacement/AllocationMergesTests.java b/test/hotspot/jtreg/compiler/c2/irTests/scalarReplacement/AllocationMergesTests.java index 2c84ad2676e..8f24cb46e20 100644 --- a/test/hotspot/jtreg/compiler/c2/irTests/scalarReplacement/AllocationMergesTests.java +++ b/test/hotspot/jtreg/compiler/c2/irTests/scalarReplacement/AllocationMergesTests.java @@ -43,7 +43,6 @@ public class AllocationMergesTests { Scenario scenario0 = new Scenario(0, "-XX:+UnlockDiagnosticVMOptions", "-XX:+ReduceAllocationMerges", - "-XX:+TraceReduceAllocationMerges", "-XX:+DeoptimizeALot", "-XX:+UseCompressedOops", "-XX:CompileCommand=inline,*::charAt*", @@ -54,7 +53,6 @@ public class AllocationMergesTests { Scenario scenario1 = new Scenario(2, "-XX:+UnlockDiagnosticVMOptions", "-XX:+ReduceAllocationMerges", - "-XX:+TraceReduceAllocationMerges", "-XX:+DeoptimizeALot", "-XX:-UseCompressedOops", "-XX:CompileCommand=inline,*::charAt*", @@ -65,7 +63,6 @@ public class AllocationMergesTests { Scenario scenario2 = new Scenario(3, "-XX:+UnlockDiagnosticVMOptions", "-XX:+ReduceAllocationMerges", - "-XX:+TraceReduceAllocationMerges", "-XX:+DeoptimizeALot", "-XX:+UseCompressedOops", "-XX:-OptimizePtrCompare", diff --git a/test/hotspot/jtreg/compiler/debug/TestCountCompiledCalls.java b/test/hotspot/jtreg/compiler/debug/TestCountCompiledCalls.java index 1a3fdf6e9d6..551c22c1377 100644 --- a/test/hotspot/jtreg/compiler/debug/TestCountCompiledCalls.java +++ b/test/hotspot/jtreg/compiler/debug/TestCountCompiledCalls.java @@ -26,7 +26,7 @@ * @bug 8382057 * @requires vm.debug == true * - * @run main/othervm -Xbatch -XX:+CountCompiledCalls ${test.main.class} + * @run main/othervm -Xbatch -XX:-DisplayVMOutput -XX:+CountCompiledCalls ${test.main.class} */ package compiler.debug; diff --git a/test/hotspot/jtreg/compiler/debug/TestLogStackAssert.java b/test/hotspot/jtreg/compiler/debug/TestLogStackAssert.java index 042abc23fcc..8ad971bc68d 100644 --- a/test/hotspot/jtreg/compiler/debug/TestLogStackAssert.java +++ b/test/hotspot/jtreg/compiler/debug/TestLogStackAssert.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -28,7 +28,9 @@ package compiler.debug; * @bug 8344013 * @requires vm.debug == true & vm.compiler2.enabled * @summary Verify the xmlStream log stack is not left in a bad state - * @run main/othervm -XX:+LogCompilation -XX:CompileCommand=log,*.* -XX:+CITimeVerbose -Xcomp compiler.debug.TestLogStackAssert + * @run main/othervm -XX:-DisplayVMOutput -XX:+LogCompilation + * -XX:CompileCommand=log,*.* -XX:+CITimeVerbose -Xcomp + * compiler.debug.TestLogStackAssert */ public class TestLogStackAssert { diff --git a/test/hotspot/jtreg/compiler/debug/TestTracePhaseCCP.java b/test/hotspot/jtreg/compiler/debug/TestTracePhaseCCP.java index b46aac9a824..6de08c97329 100644 --- a/test/hotspot/jtreg/compiler/debug/TestTracePhaseCCP.java +++ b/test/hotspot/jtreg/compiler/debug/TestTracePhaseCCP.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,9 +27,10 @@ * @requires vm.debug == true & vm.compiler2.enabled * @modules java.base/jdk.internal.misc * - * @run main/othervm -Xbatch -XX:CompileCommand=dontinline,compiler.debug.TestTracePhaseCCP::test - * -XX:CompileCommand=compileonly,compiler.debug.TestTracePhaseCCP::test -XX:+TracePhaseCCP - * compiler.debug.TestTracePhaseCCP + * @run main/othervm -Xbatch -XX:-DisplayVMOutput + * -XX:CompileCommand=dontinline,compiler.debug.TestTracePhaseCCP::test + * -XX:CompileCommand=compileonly,compiler.debug.TestTracePhaseCCP::test + * -XX:+TracePhaseCCP compiler.debug.TestTracePhaseCCP */ package compiler.debug; diff --git a/test/hotspot/jtreg/compiler/debug/TraceIterativeGVN.java b/test/hotspot/jtreg/compiler/debug/TraceIterativeGVN.java index 8e6169f07dc..9d31cabd825 100644 --- a/test/hotspot/jtreg/compiler/debug/TraceIterativeGVN.java +++ b/test/hotspot/jtreg/compiler/debug/TraceIterativeGVN.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (C) 2021, Tencent. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -25,7 +25,7 @@ /* * @test * @requires vm.debug == true & vm.compiler2.enabled - * @run main/othervm -Xbatch -XX:-TieredCompilation + * @run main/othervm -Xbatch -XX:-TieredCompilation -XX:-DisplayVMOutput * -XX:+IgnoreUnrecognizedVMOptions -XX:+TraceIterativeGVN * compiler.debug.TraceIterativeGVN */ diff --git a/test/hotspot/jtreg/compiler/loopopts/TestBadlyFormedCountedLoop.java b/test/hotspot/jtreg/compiler/loopopts/TestBadlyFormedCountedLoop.java index d8b6fc3fdfb..9375e7b7f40 100644 --- a/test/hotspot/jtreg/compiler/loopopts/TestBadlyFormedCountedLoop.java +++ b/test/hotspot/jtreg/compiler/loopopts/TestBadlyFormedCountedLoop.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2022, Red Hat, Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -26,7 +26,8 @@ * @test * @bug 8273115 * @summary CountedLoopEndNode::stride_con crash in debug build with -XX:+TraceLoopOpts - * @run main/othervm -XX:+IgnoreUnrecognizedVMOptions -XX:+TraceLoopOpts -Xcomp -XX:-TieredCompilation + * @run main/othervm -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions + * -XX:-DisplayVMOutput -XX:+TraceLoopOpts -Xcomp -XX:-TieredCompilation * -XX:CompileOnly=TestBadlyFormedCountedLoop::main TestBadlyFormedCountedLoop */ diff --git a/test/hotspot/jtreg/compiler/loopopts/TestCMoveLimitType.java b/test/hotspot/jtreg/compiler/loopopts/TestCMoveLimitType.java index 3b2c82afc46..10f2e0c113a 100644 --- a/test/hotspot/jtreg/compiler/loopopts/TestCMoveLimitType.java +++ b/test/hotspot/jtreg/compiler/loopopts/TestCMoveLimitType.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -39,7 +39,7 @@ * @key stress randomness * @bug 8299975 * @summary Limit underflow protection CMoveINode in PhaseIdealLoop::do_unroll must also protect type from underflow - * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:-TieredCompilation + * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:-DisplayVMOutput -XX:-TieredCompilation * -XX:CompileCommand=compileonly,compiler.loopopts.TestCMoveLimitType::test* * -XX:CompileCommand=dontinline,compiler.loopopts.TestCMoveLimitType::dontInline * -XX:RepeatCompilation=50 -XX:+StressIGVN diff --git a/test/hotspot/jtreg/compiler/print/PrintCompileQueue.java b/test/hotspot/jtreg/compiler/print/PrintCompileQueue.java index ee368b54bea..b633cfa46ca 100644 --- a/test/hotspot/jtreg/compiler/print/PrintCompileQueue.java +++ b/test/hotspot/jtreg/compiler/print/PrintCompileQueue.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2019, Loongson Technology Co. Ltd. All rights reserved. + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,7 +26,8 @@ * @test * @bug 8230943 * @summary possible deadlock was detected when ran with -XX:+CIPrintCompileQueue - * @run main/othervm -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:+CIPrintCompileQueue + * @run main/othervm -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:-DisplayVMOutput + * -XX:+CIPrintCompileQueue * compiler.print.PrintCompileQueue * */ diff --git a/test/hotspot/jtreg/compiler/print/PrintInlining.java b/test/hotspot/jtreg/compiler/print/PrintInlining.java index 4b45a32949f..486231cc50f 100644 --- a/test/hotspot/jtreg/compiler/print/PrintInlining.java +++ b/test/hotspot/jtreg/compiler/print/PrintInlining.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2013, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2013, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,12 +25,12 @@ * @test * @bug 8022585 8277055 * @summary VM crashes when ran with -XX:+PrintInlining - * @run main/othervm -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining - * compiler.print.PrintInlining - * @run main/othervm -Xcomp -XX:-TieredCompilation -XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining - * compiler.print.PrintInlining - * @run main/othervm -Xcomp -XX:-TieredCompilation -XX:+UnlockDiagnosticVMOptions -XX:+PrintIntrinsics + * @run main/othervm -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:-DisplayVMOutput -XX:+PrintInlining * compiler.print.PrintInlining + * @run main/othervm -Xcomp -XX:-TieredCompilation -XX:+UnlockDiagnosticVMOptions + * -XX:-DisplayVMOutput -XX:+PrintInlining compiler.print.PrintInlining + * @run main/othervm -Xcomp -XX:-TieredCompilation -XX:+UnlockDiagnosticVMOptions + * -XX:-DisplayVMOutput -XX:+PrintIntrinsics compiler.print.PrintInlining */ package compiler.print; diff --git a/test/hotspot/jtreg/compiler/print/TestPrintAssemblyDeoptRace.java b/test/hotspot/jtreg/compiler/print/TestPrintAssemblyDeoptRace.java index 22ce12f9641..726f7820797 100644 --- a/test/hotspot/jtreg/compiler/print/TestPrintAssemblyDeoptRace.java +++ b/test/hotspot/jtreg/compiler/print/TestPrintAssemblyDeoptRace.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,7 +25,8 @@ * @test * @bug 8258229 * @summary If a method is made not entrant while printing the assembly, hotspot crashes due to mismatched relocation information. - * @run main/othervm -XX:+IgnoreUnrecognizedVMOptions -XX:-TieredCompilation -XX:+DeoptimizeALot + * @run main/othervm -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions + * -XX:-DisplayVMOutput -XX:-TieredCompilation -XX:+DeoptimizeALot * -XX:CompileCommand=print,java/math/BitSieve.bit compiler.print.TestPrintAssemblyDeoptRace */ diff --git a/test/hotspot/jtreg/compiler/print/TestPrintInliningLateMHCall.java b/test/hotspot/jtreg/compiler/print/TestPrintInliningLateMHCall.java index 529469e3a95..85d3e19504c 100644 --- a/test/hotspot/jtreg/compiler/print/TestPrintInliningLateMHCall.java +++ b/test/hotspot/jtreg/compiler/print/TestPrintInliningLateMHCall.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2024, Red Hat and/or its affiliates. All rights reserved. + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,7 +26,9 @@ * @test * @bug 8335843 * @summary C2 hits assert(_print_inlining_stream->size() > 0) failed: missing inlining msg - * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:-BackgroundCompilation -XX:+PrintCompilation -XX:+PrintInlining TestPrintInliningLateMHCall + * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:-DisplayVMOutput + * -XX:-BackgroundCompilation -XX:+PrintCompilation -XX:+PrintInlining + * TestPrintInliningLateMHCall */ import java.lang.invoke.MethodHandle; diff --git a/test/hotspot/jtreg/compiler/print/TestPrintInliningLateVirtualCall.java b/test/hotspot/jtreg/compiler/print/TestPrintInliningLateVirtualCall.java index f73e30badcb..63a7215bf5b 100644 --- a/test/hotspot/jtreg/compiler/print/TestPrintInliningLateVirtualCall.java +++ b/test/hotspot/jtreg/compiler/print/TestPrintInliningLateVirtualCall.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2024, Red Hat and/or its affiliates. All rights reserved. + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,7 +26,9 @@ * @test * @bug 8327741 * @summary JVM crash in hotspot/share/opto/compile.cpp - failed: missing inlining msg - * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:-BackgroundCompilation -XX:+PrintCompilation -XX:+PrintInlining TestPrintInliningLateVirtualCall + * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:-DisplayVMOutput + * -XX:-BackgroundCompilation -XX:+PrintCompilation -XX:+PrintInlining + * TestPrintInliningLateVirtualCall */ public class TestPrintInliningLateVirtualCall { diff --git a/test/hotspot/jtreg/compiler/print/TestProfileReturnTypePrinting.java b/test/hotspot/jtreg/compiler/print/TestProfileReturnTypePrinting.java index 15f60ac3e77..cb5f67bc61c 100644 --- a/test/hotspot/jtreg/compiler/print/TestProfileReturnTypePrinting.java +++ b/test/hotspot/jtreg/compiler/print/TestProfileReturnTypePrinting.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * 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,8 @@ * @bug 8073154 * @run main/othervm -XX:TypeProfileLevel=020 * -XX:CompileCommand=compileonly,compiler.print.TestProfileReturnTypePrinting::testMethod - * -XX:+IgnoreUnrecognizedVMOptions -XX:+PrintLIR + * -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions + * -XX:-DisplayVMOutput -XX:+PrintLIR * compiler.print.TestProfileReturnTypePrinting * @summary Verify that c1's LIR that contains ProfileType node could be dumped * without a crash disregard to an exact class knowledge. diff --git a/test/hotspot/jtreg/compiler/print/TestTraceOptoParse.java b/test/hotspot/jtreg/compiler/print/TestTraceOptoParse.java index 52a7aba1a7e..3d3b242b4ce 100644 --- a/test/hotspot/jtreg/compiler/print/TestTraceOptoParse.java +++ b/test/hotspot/jtreg/compiler/print/TestTraceOptoParse.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2022, Tencent. All rights reserved. + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,7 +27,8 @@ * @bug 8293785 * @summary test for -XX:+TraceOptoParse * @requires vm.debug & vm.compiler2.enabled - * @run main/othervm -XX:+TraceOptoParse compiler.print.TestTraceOptoParse + * @run main/othervm -XX:-DisplayVMOutput -XX:+TraceOptoParse + * compiler.print.TestTraceOptoParse * */ diff --git a/test/hotspot/jtreg/compiler/relocations/TestPrintRelocations.java b/test/hotspot/jtreg/compiler/relocations/TestPrintRelocations.java index 7c84450c778..29508cf9092 100644 --- a/test/hotspot/jtreg/compiler/relocations/TestPrintRelocations.java +++ b/test/hotspot/jtreg/compiler/relocations/TestPrintRelocations.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,7 +26,9 @@ * @bug 8044538 * @summary assert hit while printing relocations for jump table entries * - * @run main/othervm -XX:+IgnoreUnrecognizedVMOptions -Xcomp -XX:CompileCommand=compileonly,java.lang.String*::* -XX:+PrintRelocations + * @run main/othervm -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions + * -XX:-DisplayVMOutput -Xcomp + * -XX:CompileCommand=compileonly,java.lang.String*::* -XX:+PrintRelocations * compiler.relocations.TestPrintRelocations */ /** diff --git a/test/hotspot/jtreg/compiler/uncommontrap/TestDeoptDetailsLockRank.java b/test/hotspot/jtreg/compiler/uncommontrap/TestDeoptDetailsLockRank.java index 2866a84ba46..7bbe82c7311 100644 --- a/test/hotspot/jtreg/compiler/uncommontrap/TestDeoptDetailsLockRank.java +++ b/test/hotspot/jtreg/compiler/uncommontrap/TestDeoptDetailsLockRank.java @@ -26,7 +26,9 @@ * @bug 8374862 * @summary Regression test for -XX:+Verbose -XX:+WizardMode -XX:+PrintDeoptimizationDetails crash * @requires vm.debug - * @run main/othervm -XX:+Verbose -XX:+WizardMode -XX:+PrintDeoptimizationDetails compiler.uncommontrap.TestDeoptDetailsLockRank + * @run main/othervm -XX:-DisplayVMOutput + * -XX:+Verbose -XX:+WizardMode -XX:+PrintDeoptimizationDetails + * compiler.uncommontrap.TestDeoptDetailsLockRank */ package compiler.uncommontrap; @@ -36,4 +38,4 @@ public class TestDeoptDetailsLockRank { public static void main(String[] args) { System.out.println("passed"); } -} \ No newline at end of file +} diff --git a/test/hotspot/jtreg/compiler/uncommontrap/TestDeoptOOM.java b/test/hotspot/jtreg/compiler/uncommontrap/TestDeoptOOM.java index 7a4f15d6461..21a3c08b665 100644 --- a/test/hotspot/jtreg/compiler/uncommontrap/TestDeoptOOM.java +++ b/test/hotspot/jtreg/compiler/uncommontrap/TestDeoptOOM.java @@ -41,7 +41,8 @@ * -XX:CompileCommand=exclude,compiler.uncommontrap.TestDeoptOOM::main * -XX:CompileCommand=exclude,compiler.uncommontrap.TestDeoptOOM::m9_1 * -XX:+UnlockDiagnosticVMOptions - * -XX:+UseZGC -XX:+LogCompilation -XX:+PrintDeoptimizationDetails -XX:+TraceDeoptimization -XX:+Verbose + * -XX:-DisplayVMOutput -XX:+UseZGC -XX:+LogCompilation + * -XX:+PrintDeoptimizationDetails -XX:+TraceDeoptimization -XX:+Verbose * compiler.uncommontrap.TestDeoptOOM */ diff --git a/test/hotspot/jtreg/compiler/uncommontrap/TestPrintDiagnosticsWithoutProfileTraps.java b/test/hotspot/jtreg/compiler/uncommontrap/TestPrintDiagnosticsWithoutProfileTraps.java index 51b30219aca..6fb22ec0759 100644 --- a/test/hotspot/jtreg/compiler/uncommontrap/TestPrintDiagnosticsWithoutProfileTraps.java +++ b/test/hotspot/jtreg/compiler/uncommontrap/TestPrintDiagnosticsWithoutProfileTraps.java @@ -28,7 +28,7 @@ * -XX:-TieredCompilation -Xcomp crash * @modules java.base/jdk.internal.misc * @requires vm.debug - * @run main/othervm -XX:+TraceDeoptimization -XX:-ProfileTraps + * @run main/othervm -XX:-DisplayVMOutput -XX:+TraceDeoptimization -XX:-ProfileTraps * -XX:-TieredCompilation -Xcomp -Xbatch * -XX:CompileCommand=compileonly,compiler.uncommontrap.TestPrintDiagnosticsWithoutProfileTraps::test * compiler.uncommontrap.TestPrintDiagnosticsWithoutProfileTraps @@ -55,4 +55,4 @@ public class TestPrintDiagnosticsWithoutProfileTraps { test(); System.out.println("passed"); } -} \ No newline at end of file +} diff --git a/test/hotspot/jtreg/compiler/uncommontrap/TraceDeoptimizationNoRealloc.java b/test/hotspot/jtreg/compiler/uncommontrap/TraceDeoptimizationNoRealloc.java index 4cd10a1a63e..abac84cdf23 100644 --- a/test/hotspot/jtreg/compiler/uncommontrap/TraceDeoptimizationNoRealloc.java +++ b/test/hotspot/jtreg/compiler/uncommontrap/TraceDeoptimizationNoRealloc.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,7 +27,8 @@ * @summary -XX:+TraceDeoptimization tries to print realloc'ed objects even when there are none * * @run main/othervm -XX:-BackgroundCompilation -XX:-UseOnStackReplacement - * -XX:+UnlockDiagnosticVMOptions -XX:+TraceDeoptimization + * -XX:+UnlockDiagnosticVMOptions -XX:-DisplayVMOutput + * -XX:+TraceDeoptimization * compiler.uncommontrap.TraceDeoptimizationNoRealloc */ From 298965828c2ba45e7264c8560f36a3edb7449331 Mon Sep 17 00:00:00 2001 From: zifeihan Date: Fri, 3 Jul 2026 06:37:54 +0000 Subject: [PATCH 071/305] 8387078: RISC-V: x27 can be allocated in CompressedOops mode Reviewed-by: dzhang, fyang --- src/hotspot/cpu/riscv/riscv.ad | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/hotspot/cpu/riscv/riscv.ad b/src/hotspot/cpu/riscv/riscv.ad index 7bfff4b2086..e022dcb4262 100644 --- a/src/hotspot/cpu/riscv/riscv.ad +++ b/src/hotspot/cpu/riscv/riscv.ad @@ -1105,8 +1105,9 @@ void reg_mask_init() { _NO_SPECIAL_PTR_REG_mask.assignFrom(_ALL_REG_mask); _NO_SPECIAL_PTR_REG_mask.subtract(_NON_ALLOCATABLE_REG_mask); - // x27 is not allocatable when compressed oops is on - if (UseCompressedOops) { + // x27 is not allocatable when compressed oops is on and heapbase is not zero, + // compressed klass pointers doesn't use x27 when heapbase is zero. + if (UseCompressedOops && (CompressedOops::base() != nullptr)) { _NO_SPECIAL_REG32_mask.remove(OptoReg::as_OptoReg(x27->as_VMReg())); _NO_SPECIAL_REG_mask.remove(OptoReg::as_OptoReg(x27->as_VMReg())); _NO_SPECIAL_PTR_REG_mask.remove(OptoReg::as_OptoReg(x27->as_VMReg())); From 16da9173a7a0fb0511a6924e407e7125a9725f93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20H=C3=A4ssig?= Date: Fri, 3 Jul 2026 06:38:10 +0000 Subject: [PATCH 072/305] 8381880: Test compiler/c1/TestTooManyVirtualRegistersMain.java uses wrong class in CompileCommand Reviewed-by: chagedorn, shade --- .../jtreg/compiler/c1/TestTooManyVirtualRegistersMain.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/hotspot/jtreg/compiler/c1/TestTooManyVirtualRegistersMain.java b/test/hotspot/jtreg/compiler/c1/TestTooManyVirtualRegistersMain.java index eff52cce7bb..b712fd2bdf3 100644 --- a/test/hotspot/jtreg/compiler/c1/TestTooManyVirtualRegistersMain.java +++ b/test/hotspot/jtreg/compiler/c1/TestTooManyVirtualRegistersMain.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,8 +29,8 @@ * The test should bail out in C1. * * @compile TestTooManyVirtualRegisters.jasm - * @run main/othervm -Xbatch -XX:CompileCommand=dontinline,compiler.c1.TestExceptionBlockWithPredecessors::* - * compiler.c1.TestTooManyVirtualRegistersMain + * @run main/othervm -Xbatch -XX:CompileCommand=dontinline,compiler.c1.TestTooManyVirtualRegisters::* + * ${test.main.class} */ package compiler.c1; From 79be204abffe59bcd76e8d998cbc08c02a6dd6fa Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Fri, 3 Jul 2026 12:35:21 +0000 Subject: [PATCH 073/305] 8387465: Remove isXP() function from WPathGraphics.java Reviewed-by: aivanov, mdoerr --- .../classes/sun/awt/windows/WPathGraphics.java | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/src/java.desktop/windows/classes/sun/awt/windows/WPathGraphics.java b/src/java.desktop/windows/classes/sun/awt/windows/WPathGraphics.java index 87b1591c0eb..6be79ccaadf 100644 --- a/src/java.desktop/windows/classes/sun/awt/windows/WPathGraphics.java +++ b/src/java.desktop/windows/classes/sun/awt/windows/WPathGraphics.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -312,16 +312,6 @@ final class WPathGraphics extends PathGraphics { } } - private static boolean isXP() { - String osVersion = System.getProperty("os.version"); - if (osVersion != null) { - float version = Float.parseFloat(osVersion); - return version >= 5.1f; - } else { - return false; - } - } - /* In case GDI doesn't handle shaping or BIDI consistently with * 2D's TextLayout, we can detect these cases and redelegate up to * be drawn via TextLayout, which in is rendered as runs of @@ -335,8 +325,7 @@ final class WPathGraphics extends PathGraphics { } else if (!useGDITextLayout) { return true; } else { - if (preferGDITextLayout || - (isXP() && FontUtilities.textLayoutIsCompatible(font))) { + if (preferGDITextLayout || FontUtilities.textLayoutIsCompatible(font)) { return false; } else { return true; From 0d84d84ad29824147ab000c9284928980473fcb7 Mon Sep 17 00:00:00 2001 From: Thomas Stuefe Date: Sat, 4 Jul 2026 04:53:08 +0000 Subject: [PATCH 074/305] 8387660: Oop verification is sometimes wrong Reviewed-by: shade, kvn --- .../gc/shared/barrierSetAssembler_aarch64.cpp | 4 ++-- .../gc/z/zBarrierSetAssembler_aarch64.cpp | 5 ++--- .../cpu/aarch64/macroAssembler_aarch64.cpp | 9 +++++++-- .../cpu/aarch64/macroAssembler_aarch64.hpp | 1 + .../cpu/aarch64/stubGenerator_aarch64.cpp | 2 +- .../gc/shared/barrierSetAssembler_riscv.cpp | 4 ++-- .../riscv/gc/z/zBarrierSetAssembler_riscv.cpp | 2 +- src/hotspot/cpu/riscv/macroAssembler_riscv.cpp | 17 ++++++++++------- src/hotspot/cpu/riscv/macroAssembler_riscv.hpp | 1 + src/hotspot/cpu/riscv/stubGenerator_riscv.cpp | 2 +- .../x86/gc/shared/barrierSetAssembler_x86.cpp | 4 ++-- .../cpu/x86/gc/z/zBarrierSetAssembler_x86.cpp | 4 ++-- src/hotspot/cpu/x86/macroAssembler_x86.cpp | 18 ++++++++++-------- src/hotspot/cpu/x86/macroAssembler_x86.hpp | 1 + .../cpu/x86/stubGenerator_x86_64_arraycopy.cpp | 4 ++-- 15 files changed, 45 insertions(+), 33 deletions(-) diff --git a/src/hotspot/cpu/aarch64/gc/shared/barrierSetAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/gc/shared/barrierSetAssembler_aarch64.cpp index 38efcf80650..93781bb14bf 100644 --- a/src/hotspot/cpu/aarch64/gc/shared/barrierSetAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/gc/shared/barrierSetAssembler_aarch64.cpp @@ -389,8 +389,8 @@ void BarrierSetAssembler::check_oop(MacroAssembler* masm, Register obj, Register __ cbnz(tmp1, error); // make sure klass is 'reasonable', which is not zero. - __ load_klass(obj, obj); // get klass - __ cbz(obj, error); // if klass is null it is broken + __ load_narrow_klass(tmp1, obj); // get klass + __ cbz(tmp1, error); // if klass is null it is broken } void BarrierSetAssembler::try_peek_weak_handle_in_nmethod(MacroAssembler* masm, Register weak_handle, Register obj, Register tmp, Label& slow_path) { diff --git a/src/hotspot/cpu/aarch64/gc/z/zBarrierSetAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/gc/z/zBarrierSetAssembler_aarch64.cpp index 7c320d835e7..f07f899e869 100644 --- a/src/hotspot/cpu/aarch64/gc/z/zBarrierSetAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/gc/z/zBarrierSetAssembler_aarch64.cpp @@ -1385,9 +1385,8 @@ void ZBarrierSetAssembler::check_oop(MacroAssembler* masm, Register obj, Registe __ bind(check_oop); // make sure klass is 'reasonable', which is not zero. - __ load_klass(tmp1, obj); // get klass - __ tst(tmp1, tmp1); - __ br(Assembler::EQ, error); // if klass is null it is broken + __ load_narrow_klass(tmp1, obj); // get narrow klass + __ cbz(tmp1, error); // if klass is null it is broken __ bind(check_zaddress); // Check if the oop is in the right area of memory diff --git a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp index d5e220fd4a3..62a6f61599c 100644 --- a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp @@ -5097,7 +5097,7 @@ void MacroAssembler::load_method_holder(Register holder, Register method) { ldr(holder, Address(holder, ConstantPool::pool_holder_offset())); // InstanceKlass* } -// Loads the obj's Klass* into dst. +// Loads the obj's narrow Klass from a compact object header (+COH) into dst. // Preserves all registers (incl src, rscratch1 and rscratch2). // Input: // src - the oop we want to load the klass from. @@ -5108,12 +5108,17 @@ void MacroAssembler::load_narrow_klass_compact(Register dst, Register src) { lsr(dst, dst, markWord::klass_shift); } -void MacroAssembler::load_klass(Register dst, Register src) { +// Loads the obj's narrow Klass from any header (compact or not) into dst. +void MacroAssembler::load_narrow_klass(Register dst, Register src) { if (UseCompactObjectHeaders) { load_narrow_klass_compact(dst, src); } else { ldrw(dst, Address(src, oopDesc::klass_offset_in_bytes())); } +} + +void MacroAssembler::load_klass(Register dst, Register src) { + load_narrow_klass(dst, src); decode_klass_not_null(dst); } diff --git a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp index 9c722cd297e..740b783cbd4 100644 --- a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp @@ -924,6 +924,7 @@ public: // oop manipulations void load_narrow_klass_compact(Register dst, Register src); + void load_narrow_klass(Register dst, Register src); void load_klass(Register dst, Register src); void store_klass(Register dst, Register src); void cmp_klass(Register obj, Register klass, Register tmp); diff --git a/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp b/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp index 2ad7e00817c..f6ed5c2862a 100644 --- a/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp @@ -2578,7 +2578,7 @@ class StubGenerator: public StubCodeGenerator { __ bind(L1); __ stop("broken null klass"); __ bind(L2); - __ load_klass(rscratch1, dst); + __ load_narrow_klass(rscratch1, dst); __ cbz(rscratch1, L1); // this would be broken also BLOCK_COMMENT("} assert klasses not null done"); } diff --git a/src/hotspot/cpu/riscv/gc/shared/barrierSetAssembler_riscv.cpp b/src/hotspot/cpu/riscv/gc/shared/barrierSetAssembler_riscv.cpp index fd78b429ee4..f16b22e5575 100644 --- a/src/hotspot/cpu/riscv/gc/shared/barrierSetAssembler_riscv.cpp +++ b/src/hotspot/cpu/riscv/gc/shared/barrierSetAssembler_riscv.cpp @@ -352,8 +352,8 @@ void BarrierSetAssembler::check_oop(MacroAssembler* masm, Register obj, Register __ bne(tmp1, tmp2, error); // Make sure klass is 'reasonable', which is not zero. - __ load_klass(obj, obj, tmp1); // get klass - __ beqz(obj, error); // if klass is null it is broken + __ load_narrow_klass(tmp1, obj); // get klass + __ beqz(tmp1, error); // if klass is null it is broken } void BarrierSetAssembler::try_peek_weak_handle_in_nmethod(MacroAssembler* masm, Register weak_handle, Register obj, diff --git a/src/hotspot/cpu/riscv/gc/z/zBarrierSetAssembler_riscv.cpp b/src/hotspot/cpu/riscv/gc/z/zBarrierSetAssembler_riscv.cpp index bf37ccf64e2..2f8491dd592 100644 --- a/src/hotspot/cpu/riscv/gc/z/zBarrierSetAssembler_riscv.cpp +++ b/src/hotspot/cpu/riscv/gc/z/zBarrierSetAssembler_riscv.cpp @@ -1039,7 +1039,7 @@ void ZBarrierSetAssembler::check_oop(MacroAssembler* masm, Register obj, Registe __ bind(check_oop); // Make sure klass is 'reasonable', which is not zero - __ load_klass(tmp1, obj, tmp2); + __ load_narrow_klass(tmp1, obj); __ beqz(tmp1, error); __ bind(check_zaddress); diff --git a/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp b/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp index d93329544a7..7a339d83d25 100644 --- a/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp +++ b/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp @@ -3767,16 +3767,19 @@ void MacroAssembler::load_narrow_klass_compact(Register dst, Register src) { srli(dst, dst, markWord::klass_shift); } +void MacroAssembler::load_narrow_klass(Register dst, Register src) { + if (UseCompactObjectHeaders) { + load_narrow_klass_compact(dst, src); + } else { + lwu(dst, Address(src, oopDesc::klass_offset_in_bytes())); + } +} + void MacroAssembler::load_klass(Register dst, Register src, Register tmp) { assert_different_registers(dst, tmp); assert_different_registers(src, tmp); - if (UseCompactObjectHeaders) { - load_narrow_klass_compact(dst, src); - decode_klass_not_null(dst, tmp); - } else { - lwu(dst, Address(src, oopDesc::klass_offset_in_bytes())); - decode_klass_not_null(dst, tmp); - } + load_narrow_klass(dst, src); + decode_klass_not_null(dst, tmp); } void MacroAssembler::store_klass(Register dst, Register src, Register tmp) { diff --git a/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp b/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp index a5ad7eeaa5f..f28e828fb65 100644 --- a/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp +++ b/src/hotspot/cpu/riscv/macroAssembler_riscv.hpp @@ -197,6 +197,7 @@ class MacroAssembler: public Assembler { Register val, Register tmp1, Register tmp2, Register tmp3); void load_klass(Register dst, Register src, Register tmp = t0); void load_narrow_klass_compact(Register dst, Register src); + void load_narrow_klass(Register dst, Register src); void store_klass(Register dst, Register src, Register tmp = t0); void cmp_klass_beq(Register obj, Register klass, Register tmp1, Register tmp2, diff --git a/src/hotspot/cpu/riscv/stubGenerator_riscv.cpp b/src/hotspot/cpu/riscv/stubGenerator_riscv.cpp index 13f3ed4de89..82e5a49faf0 100644 --- a/src/hotspot/cpu/riscv/stubGenerator_riscv.cpp +++ b/src/hotspot/cpu/riscv/stubGenerator_riscv.cpp @@ -1898,7 +1898,7 @@ class StubGenerator: public StubCodeGenerator { __ bind(L1); __ stop("broken null klass"); __ bind(L2); - __ load_klass(t0, dst, t1); + __ load_narrow_klass(t0, dst); __ beqz(t0, L1); // this would be broken also BLOCK_COMMENT("} assert klasses not null done"); } diff --git a/src/hotspot/cpu/x86/gc/shared/barrierSetAssembler_x86.cpp b/src/hotspot/cpu/x86/gc/shared/barrierSetAssembler_x86.cpp index 731eef09c37..265d9b16397 100644 --- a/src/hotspot/cpu/x86/gc/shared/barrierSetAssembler_x86.cpp +++ b/src/hotspot/cpu/x86/gc/shared/barrierSetAssembler_x86.cpp @@ -357,8 +357,8 @@ void BarrierSetAssembler::check_oop(MacroAssembler* masm, Register obj, Register __ jcc(Assembler::notZero, error); // make sure klass is 'reasonable', which is not zero. - __ load_klass(obj, obj, tmp1); // get klass - __ testptr(obj, obj); + __ load_narrow_klass(tmp1, obj); // get narrow Klass + __ testl(tmp1, tmp1); __ jcc(Assembler::zero, error); // if klass is null it is broken } diff --git a/src/hotspot/cpu/x86/gc/z/zBarrierSetAssembler_x86.cpp b/src/hotspot/cpu/x86/gc/z/zBarrierSetAssembler_x86.cpp index 3301d6ace49..12e9cfa4573 100644 --- a/src/hotspot/cpu/x86/gc/z/zBarrierSetAssembler_x86.cpp +++ b/src/hotspot/cpu/x86/gc/z/zBarrierSetAssembler_x86.cpp @@ -1551,8 +1551,8 @@ void ZBarrierSetAssembler::check_oop(MacroAssembler* masm, Register obj, Registe __ bind(check_oop); // make sure klass is 'reasonable', which is not zero. - __ load_klass(tmp1, obj, tmp2); // get klass - __ testptr(tmp1, tmp1); + __ load_narrow_klass(tmp1, obj); // get narrow klass + __ testl(tmp1, tmp1); __ jcc(Assembler::zero, error); // if klass is null it is broken __ bind(check_zaddress); diff --git a/src/hotspot/cpu/x86/macroAssembler_x86.cpp b/src/hotspot/cpu/x86/macroAssembler_x86.cpp index 80dd7ccfbca..d1250f0820f 100644 --- a/src/hotspot/cpu/x86/macroAssembler_x86.cpp +++ b/src/hotspot/cpu/x86/macroAssembler_x86.cpp @@ -5434,17 +5434,19 @@ void MacroAssembler::load_narrow_klass_compact(Register dst, Register src) { shrq(dst, markWord::klass_shift); } +void MacroAssembler::load_narrow_klass(Register dst, Register src) { + if (UseCompactObjectHeaders) { + load_narrow_klass_compact(dst, src); + } else { + movl(dst, Address(src, oopDesc::klass_offset_in_bytes())); + } +} + void MacroAssembler::load_klass(Register dst, Register src, Register tmp) { assert_different_registers(src, tmp); assert_different_registers(dst, tmp); - - if (UseCompactObjectHeaders) { - load_narrow_klass_compact(dst, src); - decode_klass_not_null(dst, tmp); - } else { - movl(dst, Address(src, oopDesc::klass_offset_in_bytes())); - decode_klass_not_null(dst, tmp); - } + load_narrow_klass(dst, src); + decode_klass_not_null(dst, tmp); } void MacroAssembler::store_klass(Register dst, Register src, Register tmp) { diff --git a/src/hotspot/cpu/x86/macroAssembler_x86.hpp b/src/hotspot/cpu/x86/macroAssembler_x86.hpp index de5ec02fe43..a74c7b16f3e 100644 --- a/src/hotspot/cpu/x86/macroAssembler_x86.hpp +++ b/src/hotspot/cpu/x86/macroAssembler_x86.hpp @@ -350,6 +350,7 @@ class MacroAssembler: public Assembler { // oop manipulations void load_narrow_klass_compact(Register dst, Register src); + void load_narrow_klass(Register dst, Register src); void load_klass(Register dst, Register src, Register tmp); void store_klass(Register dst, Register src, Register tmp); diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64_arraycopy.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64_arraycopy.cpp index e7dc416a961..a45340b8800 100644 --- a/src/hotspot/cpu/x86/stubGenerator_x86_64_arraycopy.cpp +++ b/src/hotspot/cpu/x86/stubGenerator_x86_64_arraycopy.cpp @@ -3571,8 +3571,8 @@ address StubGenerator::generate_generic_copy(address byte_copy_entry, address sh __ bind(L1); __ stop("broken null klass"); __ bind(L2); - __ load_klass(rax, dst, rklass_tmp); - __ cmpq(rax, 0); + __ load_narrow_klass(rax, dst); + __ testl(rax, rax); __ jcc(Assembler::equal, L1); // this would be broken also BLOCK_COMMENT("} assert klasses not null done"); } From cb511b64e981a0fb32b777d9ac8bf5a08cdc694f Mon Sep 17 00:00:00 2001 From: Benjamin Peterson Date: Sat, 4 Jul 2026 18:10:55 +0000 Subject: [PATCH 075/305] 8387704: java/nio/file/DirectoryStream/SecureDS.java failing with AccessDeniedException Reviewed-by: alanb --- test/jdk/java/nio/file/DirectoryStream/SecureDS.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/test/jdk/java/nio/file/DirectoryStream/SecureDS.java b/test/jdk/java/nio/file/DirectoryStream/SecureDS.java index f3321a8c04d..e481f6f1594 100644 --- a/test/jdk/java/nio/file/DirectoryStream/SecureDS.java +++ b/test/jdk/java/nio/file/DirectoryStream/SecureDS.java @@ -216,8 +216,13 @@ public class SecureDS { view = stream.getFileAttributeView(fileEntry, PosixFileAttributeView.class, NOFOLLOW_LINKS); view.setPermissions(noperms); assertEquals(noperms, getPosixFilePermissions(file)); - view.setPermissions(permsFile); - assertEquals(permsFile, getPosixFilePermissions(file)); + try { + view.setPermissions(permsFile); + assertEquals(permsFile, getPosixFilePermissions(file)); + } catch (AccessDeniedException e) { + // Fails on older Linux systems without fchmodat AT_SYMLINK_NOFOLLOW support + setPosixFilePermissions(file, permsFile); + } // Test following link to file view = stream.getFileAttributeView(link, PosixFileAttributeView.class); From 9a8592117745193ff90ccf510ad0344a18b2d3d5 Mon Sep 17 00:00:00 2001 From: Prasanta Sadhukhan Date: Sun, 5 Jul 2026 06:09:54 +0000 Subject: [PATCH 076/305] 8387693: Remove unused method Reviewed-by: azvegint --- .../swing/plaf/basic/BasicProgressBarUI.java | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/java.desktop/share/classes/javax/swing/plaf/basic/BasicProgressBarUI.java b/src/java.desktop/share/classes/javax/swing/plaf/basic/BasicProgressBarUI.java index d249bf0bc9d..6f58fd9cdb6 100644 --- a/src/java.desktop/share/classes/javax/swing/plaf/basic/BasicProgressBarUI.java +++ b/src/java.desktop/share/classes/javax/swing/plaf/basic/BasicProgressBarUI.java @@ -1165,24 +1165,6 @@ public class BasicProgressBarUI extends ProgressBarUI { return repaintInterval; } - /** - * Returns the number of milliseconds per animation cycle. - * This value is meaningful - * only if the progress bar is in indeterminate mode. - * The cycle time is used by the default indeterminate progress bar - * painting code when determining - * how far to move the bouncing box per frame. - * The cycle time is specified by - * the "ProgressBar.cycleTime" UI default - * and adjusted, if necessary, - * by the initIndeterminateDefaults method. - * - * @return the cycle time, in milliseconds - */ - private int getCycleTime() { - return cycleTime; - } - private int initCycleTime() { cycleTime = DefaultLookup.getInt(progressBar, this, "ProgressBar.cycleTime", 3000); From 92b0565b00fcdae354bd101c762974b47e075f40 Mon Sep 17 00:00:00 2001 From: April Ivy Date: Sun, 5 Jul 2026 07:22:35 +0000 Subject: [PATCH 077/305] 8386802: ClassFile Util.entryList should consider non-RandomAccess lists Reviewed-by: liach --- .../classes/jdk/internal/classfile/impl/Util.java | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/java.base/share/classes/jdk/internal/classfile/impl/Util.java b/src/java.base/share/classes/jdk/internal/classfile/impl/Util.java index 6411c939549..d19dd202432 100644 --- a/src/java.base/share/classes/jdk/internal/classfile/impl/Util.java +++ b/src/java.base/share/classes/jdk/internal/classfile/impl/Util.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -189,16 +189,18 @@ public final class Util { public static List entryList(List list) { var result = new Object[list.size()]; // null check - for (int i = 0; i < result.length; i++) { - result[i] = TemporaryConstantPool.INSTANCE.classEntry(list.get(i)); + int i = 0; + for (var entry : list) { + result[i++] = TemporaryConstantPool.INSTANCE.classEntry(entry); } return SharedSecrets.getJavaUtilCollectionAccess().listFromTrustedArray(result); } public static List moduleEntryList(List list) { var result = new Object[list.size()]; // null check - for (int i = 0; i < result.length; i++) { - result[i] = TemporaryConstantPool.INSTANCE.moduleEntry(TemporaryConstantPool.INSTANCE.utf8Entry(list.get(i).name())); + int i = 0; + for (var entry : list) { + result[i++] = TemporaryConstantPool.INSTANCE.moduleEntry(entry); } return SharedSecrets.getJavaUtilCollectionAccess().listFromTrustedArray(result); } From 6e7e6f0bbf60b67c2e4f533a0edd8e867d954d31 Mon Sep 17 00:00:00 2001 From: Eric Fang Date: Mon, 6 Jul 2026 05:41:02 +0000 Subject: [PATCH 078/305] 8383905: AArch64: Improve code generation for long vector multiply Reviewed-by: aph, xgong --- src/hotspot/cpu/aarch64/aarch64_vector.ad | 92 ++++++++++- src/hotspot/cpu/aarch64/aarch64_vector_ad.m4 | 59 ++++++- src/hotspot/cpu/aarch64/assembler_aarch64.hpp | 16 ++ test/hotspot/gtest/aarch64/aarch64-asmtest.py | 4 + test/hotspot/gtest/aarch64/asmtest.out.h | 49 +++--- .../compiler/lib/ir_framework/IRNode.java | 20 +++ .../TestVectorMulLongToSignedUnsignedInt.java | 153 +++++++++++++++--- .../compiler/vectorapi/VectorMultiplyOpt.java | 107 ++++++++++-- 8 files changed, 441 insertions(+), 59 deletions(-) diff --git a/src/hotspot/cpu/aarch64/aarch64_vector.ad b/src/hotspot/cpu/aarch64/aarch64_vector.ad index 12f98bb8549..c06c8b856b7 100644 --- a/src/hotspot/cpu/aarch64/aarch64_vector.ad +++ b/src/hotspot/cpu/aarch64/aarch64_vector.ad @@ -1157,7 +1157,8 @@ instruct vmulI_sve(vReg dst_src1, vReg src2) %{ // vector mul - LONG instruct vmulL_neon(vReg dst, vReg src1, vReg src2) %{ - predicate(UseSVE == 0); + predicate(UseSVE == 0 && !n->as_MulVL()->has_int_inputs() && + !n->as_MulVL()->has_uint_inputs()); match(Set dst (MulVL src1 src2)); format %{ "vmulL_neon $dst, $src1, $src2\t# 2L" %} ins_encode %{ @@ -1175,8 +1176,75 @@ instruct vmulL_neon(vReg dst, vReg src1, vReg src2) %{ ins_pipe(pipe_slow); %} +// Specialization of vmulL_int_neon when both inputs are the same IR node +// (e.g. v * v). Avoids one redundant xtn and saves one temporary register. +instruct vmulL_int_neon_same(vReg dst, vReg src, vReg tmp) %{ + predicate(UseSVE == 0 && n->as_MulVL()->has_int_inputs() && + n->in(1) == n->in(2)); + match(Set dst (MulVL src src)); + effect(TEMP tmp); + format %{ "vmulL_int_neon_same $dst, $src, $src\t# 2L. KILL $tmp" %} + ins_encode %{ + uint length_in_bytes = Matcher::vector_length_in_bytes(this); + assert(length_in_bytes == 16, "must be"); + __ xtn($tmp$$FloatRegister, __ T2S, $src$$FloatRegister, __ T2D); + __ smullv($dst$$FloatRegister, __ T2S, $tmp$$FloatRegister, $tmp$$FloatRegister); + %} + ins_pipe(pipe_slow); +%} + +instruct vmulL_int_neon(vReg dst, vReg src1, vReg src2, vReg tmp1, vReg tmp2) %{ + predicate(UseSVE == 0 && n->as_MulVL()->has_int_inputs() && + n->in(1) != n->in(2)); + match(Set dst (MulVL src1 src2)); + effect(TEMP tmp1, TEMP tmp2); + format %{ "vmulL_int_neon $dst, $src1, $src2\t# 2L. KILL $tmp1, $tmp2" %} + ins_encode %{ + uint length_in_bytes = Matcher::vector_length_in_bytes(this); + assert(length_in_bytes == 16, "must be"); + __ xtn($tmp1$$FloatRegister, __ T2S, $src1$$FloatRegister, __ T2D); + __ xtn($tmp2$$FloatRegister, __ T2S, $src2$$FloatRegister, __ T2D); + __ smullv($dst$$FloatRegister, __ T2S, $tmp1$$FloatRegister, $tmp2$$FloatRegister); + %} + ins_pipe(pipe_slow); +%} + +// Specialization of vmulL_uint_neon when both inputs are the same IR node +// (e.g. v * v). Avoids one redundant xtn and saves one temporary register. +instruct vmulL_uint_neon_same(vReg dst, vReg src, vReg tmp) %{ + predicate(UseSVE == 0 && n->as_MulVL()->has_uint_inputs() && + n->in(1) == n->in(2)); + match(Set dst (MulVL src src)); + effect(TEMP tmp); + format %{ "vmulL_uint_neon_same $dst, $src, $src\t# 2L. KILL $tmp" %} + ins_encode %{ + uint length_in_bytes = Matcher::vector_length_in_bytes(this); + assert(length_in_bytes == 16, "must be"); + __ xtn($tmp$$FloatRegister, __ T2S, $src$$FloatRegister, __ T2D); + __ umullv($dst$$FloatRegister, __ T2S, $tmp$$FloatRegister, $tmp$$FloatRegister); + %} + ins_pipe(pipe_slow); +%} + +instruct vmulL_uint_neon(vReg dst, vReg src1, vReg src2, vReg tmp1, vReg tmp2) %{ + predicate(UseSVE == 0 && n->as_MulVL()->has_uint_inputs() && + n->in(1) != n->in(2)); + match(Set dst (MulVL src1 src2)); + effect(TEMP tmp1, TEMP tmp2); + format %{ "vmulL_uint_neon $dst, $src1, $src2\t# 2L. KILL $tmp1, $tmp2" %} + ins_encode %{ + uint length_in_bytes = Matcher::vector_length_in_bytes(this); + assert(length_in_bytes == 16, "must be"); + __ xtn($tmp1$$FloatRegister, __ T2S, $src1$$FloatRegister, __ T2D); + __ xtn($tmp2$$FloatRegister, __ T2S, $src2$$FloatRegister, __ T2D); + __ umullv($dst$$FloatRegister, __ T2S, $tmp1$$FloatRegister, $tmp2$$FloatRegister); + %} + ins_pipe(pipe_slow); +%} + instruct vmulL_sve(vReg dst_src1, vReg src2) %{ - predicate(UseSVE > 0); + predicate(UseSVE == 1 || (UseSVE == 2 && !n->as_MulVL()->has_int_inputs() && + !n->as_MulVL()->has_uint_inputs())); match(Set dst_src1 (MulVL dst_src1 src2)); format %{ "vmulL_sve $dst_src1, $dst_src1, $src2" %} ins_encode %{ @@ -1185,6 +1253,26 @@ instruct vmulL_sve(vReg dst_src1, vReg src2) %{ ins_pipe(pipe_slow); %} +instruct vmulL_int_sve2(vReg dst, vReg src1, vReg src2) %{ + predicate(UseSVE == 2 && n->as_MulVL()->has_int_inputs()); + match(Set dst (MulVL src1 src2)); + format %{ "vmulL_int_sve2 $dst, $src1, $src2" %} + ins_encode %{ + __ sve_smullb($dst$$FloatRegister, __ D, $src1$$FloatRegister, $src2$$FloatRegister); + %} + ins_pipe(pipe_slow); +%} + +instruct vmulL_uint_sve2(vReg dst, vReg src1, vReg src2) %{ + predicate(UseSVE == 2 && n->as_MulVL()->has_uint_inputs()); + match(Set dst (MulVL src1 src2)); + format %{ "vmulL_uint_sve2 $dst, $src1, $src2" %} + ins_encode %{ + __ sve_umullb($dst$$FloatRegister, __ D, $src1$$FloatRegister, $src2$$FloatRegister); + %} + ins_pipe(pipe_slow); +%} + // vector mul - floating-point instruct vmulHF(vReg dst, vReg src1, vReg src2) %{ diff --git a/src/hotspot/cpu/aarch64/aarch64_vector_ad.m4 b/src/hotspot/cpu/aarch64/aarch64_vector_ad.m4 index 68c407bc9af..b749647ae1e 100644 --- a/src/hotspot/cpu/aarch64/aarch64_vector_ad.m4 +++ b/src/hotspot/cpu/aarch64/aarch64_vector_ad.m4 @@ -736,7 +736,8 @@ BINARY_OP_NEON_SVE_PAIRWISE(vmulI, MulVI, mulv, sve_mul, S) // vector mul - LONG instruct vmulL_neon(vReg dst, vReg src1, vReg src2) %{ - predicate(UseSVE == 0); + predicate(UseSVE == 0 && !n->as_MulVL()->has_int_inputs() && + !n->as_MulVL()->has_uint_inputs()); match(Set dst (MulVL src1 src2)); format %{ "vmulL_neon $dst, $src1, $src2\t# 2L" %} ins_encode %{ @@ -754,8 +755,47 @@ instruct vmulL_neon(vReg dst, vReg src1, vReg src2) %{ ins_pipe(pipe_slow); %} +dnl VMUL_L_NEON($1, $2 ) +dnl VMUL_L_NEON(kind, insn ) +define(`VMUL_L_NEON', `dnl +// Specialization of vmulL_$1_neon when both inputs are the same IR node +// (e.g. v * v). Avoids one redundant xtn and saves one temporary register. +instruct vmulL_$1_neon_same(vReg dst, vReg src, vReg tmp) %{ + predicate(UseSVE == 0 && n->as_MulVL()->has_$1_inputs() && + n->in(1) == n->in(2)); + match(Set dst (MulVL src src)); + effect(TEMP tmp); + format %{ "vmulL_$1_neon_same $dst, $src, $src\t# 2L. KILL $tmp" %} + ins_encode %{ + uint length_in_bytes = Matcher::vector_length_in_bytes(this); + assert(length_in_bytes == 16, "must be"); + __ xtn($tmp$$FloatRegister, __ T2S, $src$$FloatRegister, __ T2D); + __ $2($dst$$FloatRegister, __ T2S, $tmp$$FloatRegister, $tmp$$FloatRegister); + %} + ins_pipe(pipe_slow); +%} + +instruct vmulL_$1_neon(vReg dst, vReg src1, vReg src2, vReg tmp1, vReg tmp2) %{ + predicate(UseSVE == 0 && n->as_MulVL()->has_$1_inputs() && + n->in(1) != n->in(2)); + match(Set dst (MulVL src1 src2)); + effect(TEMP tmp1, TEMP tmp2); + format %{ "vmulL_$1_neon $dst, $src1, $src2\t# 2L. KILL $tmp1, $tmp2" %} + ins_encode %{ + uint length_in_bytes = Matcher::vector_length_in_bytes(this); + assert(length_in_bytes == 16, "must be"); + __ xtn($tmp1$$FloatRegister, __ T2S, $src1$$FloatRegister, __ T2D); + __ xtn($tmp2$$FloatRegister, __ T2S, $src2$$FloatRegister, __ T2D); + __ $2($dst$$FloatRegister, __ T2S, $tmp1$$FloatRegister, $tmp2$$FloatRegister); + %} + ins_pipe(pipe_slow); +%} +')dnl +VMUL_L_NEON(int, smullv) +VMUL_L_NEON(uint, umullv) instruct vmulL_sve(vReg dst_src1, vReg src2) %{ - predicate(UseSVE > 0); + predicate(UseSVE == 1 || (UseSVE == 2 && !n->as_MulVL()->has_int_inputs() && + !n->as_MulVL()->has_uint_inputs())); match(Set dst_src1 (MulVL dst_src1 src2)); format %{ "vmulL_sve $dst_src1, $dst_src1, $src2" %} ins_encode %{ @@ -764,6 +804,21 @@ instruct vmulL_sve(vReg dst_src1, vReg src2) %{ ins_pipe(pipe_slow); %} +dnl VMUL_L_SVE2($1, $2 ) +dnl VMUL_L_SVE2(kind, sve2_insn ) +define(`VMUL_L_SVE2', `dnl +instruct vmulL_$1_sve2(vReg dst, vReg src1, vReg src2) %{ + predicate(UseSVE == 2 && n->as_MulVL()->has_$1_inputs()); + match(Set dst (MulVL src1 src2)); + format %{ "vmulL_$1_sve2 $dst, $src1, $src2" %} + ins_encode %{ + __ $2($dst$$FloatRegister, __ D, $src1$$FloatRegister, $src2$$FloatRegister); + %} + ins_pipe(pipe_slow); +%} +')dnl +VMUL_L_SVE2(int, sve_smullb) +VMUL_L_SVE2(uint, sve_umullb) // vector mul - floating-point BINARY_OP(vmulHF, MulVHF, fmul, sve_fmul, H) BINARY_OP(vmulF, MulVF, fmul, sve_fmul, S) diff --git a/src/hotspot/cpu/aarch64/assembler_aarch64.hpp b/src/hotspot/cpu/aarch64/assembler_aarch64.hpp index ae2b9ac9bf7..a81213c5ae4 100644 --- a/src/hotspot/cpu/aarch64/assembler_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/assembler_aarch64.hpp @@ -4331,6 +4331,22 @@ public: INSN(sve_bsl, 0b001, 0b1); // Bitwise select #undef INSN +// SVE2 widening integer multiply - vector +#define INSN(NAME, is_unsigned, is_top) \ + void NAME(FloatRegister Zd, SIMD_RegVariant T, FloatRegister Zn, FloatRegister Zm) { \ + starti; \ + assert(T != B && T != Q, "invalid size"); \ + int op = 0b011100 | (is_unsigned ? 0b10 : 0) | (is_top ? 0b1 : 0); \ + f(0b01000101, 31, 24), f(T, 23, 22), f(0, 21), rf(Zm, 16); \ + f(op, 15, 10), rf(Zn, 5), rf(Zd, 0); \ + } + + INSN(sve_umullb, /* is_unsigned */ true, /* is_top */ false); // Unsigned widening multiply of bottom elements + INSN(sve_umullt, /* is_unsigned */ true, /* is_top */ true ); // Unsigned widening multiply of top elements + INSN(sve_smullb, /* is_unsigned */ false, /* is_top */ false); // Signed widening multiply of bottom elements + INSN(sve_smullt, /* is_unsigned */ false, /* is_top */ true ); // Signed widening multiply of top elements +#undef INSN + // SVE2 saturating operations - predicate #define INSN(NAME, op1, op2) \ void NAME(FloatRegister Zdn, SIMD_RegVariant T, PRegister Pg, FloatRegister Znm) { \ diff --git a/test/hotspot/gtest/aarch64/aarch64-asmtest.py b/test/hotspot/gtest/aarch64/aarch64-asmtest.py index 04088bb0dc8..b5386d47a73 100644 --- a/test/hotspot/gtest/aarch64/aarch64-asmtest.py +++ b/test/hotspot/gtest/aarch64/aarch64-asmtest.py @@ -2163,6 +2163,10 @@ generate(SpecialCases, [["ccmn", "__ ccmn(zr, zr, 3u, Assembler::LE);", # SVE2 instructions ["histcnt", "__ sve_histcnt(z16, __ S, p0, z16, z16);", "histcnt\tz16.s, p0/z, z16.s, z16.s"], ["histcnt", "__ sve_histcnt(z17, __ D, p0, z17, z17);", "histcnt\tz17.d, p0/z, z17.d, z17.d"], + ["umullb", "__ sve_umullb(z16, __ H, z17, z18);", "umullb\tz16.h, z17.b, z18.b"], + ["umullt", "__ sve_umullt(z19, __ S, z20, z21);", "umullt\tz19.s, z20.h, z21.h"], + ["smullb", "__ sve_smullb(z22, __ D, z23, z24);", "smullb\tz22.d, z23.s, z24.s"], + ["smullt", "__ sve_smullt(z25, __ H, z26, z27);", "smullt\tz25.h, z26.b, z27.b"], ]) print "\n// FloatImmediateOp" diff --git a/test/hotspot/gtest/aarch64/asmtest.out.h b/test/hotspot/gtest/aarch64/asmtest.out.h index bad9825af9b..95832a1faf6 100644 --- a/test/hotspot/gtest/aarch64/asmtest.out.h +++ b/test/hotspot/gtest/aarch64/asmtest.out.h @@ -1180,6 +1180,10 @@ __ sve_splice(z0, __ D, p0, z1); // splice z0.d, p0, z0.d, z1.d __ sve_histcnt(z16, __ S, p0, z16, z16); // histcnt z16.s, p0/z, z16.s, z16.s __ sve_histcnt(z17, __ D, p0, z17, z17); // histcnt z17.d, p0/z, z17.d, z17.d + __ sve_umullb(z16, __ H, z17, z18); // umullb z16.h, z17.b, z18.b + __ sve_umullt(z19, __ S, z20, z21); // umullt z19.s, z20.h, z21.h + __ sve_smullb(z22, __ D, z23, z24); // smullb z22.d, z23.s, z24.s + __ sve_smullt(z25, __ H, z26, z27); // smullt z25.h, z26.b, z27.b // FloatImmediateOp __ fmovd(v0, 2.0); // fmov d0, #2.0 @@ -1470,30 +1474,30 @@ 0x9101a1a0, 0xb10a5cc8, 0xd10810aa, 0xf10fd061, 0x120cb166, 0x321764bc, 0x52174681, 0x720c0227, 0x9241018e, 0xb25a2969, 0xd278b411, 0xf26aad01, - 0x14000000, 0x17ffffd7, 0x140004cc, 0x94000000, - 0x97ffffd4, 0x940004c9, 0x3400000a, 0x34fffa2a, - 0x340098ca, 0x35000008, 0x35fff9c8, 0x35009868, - 0xb400000b, 0xb4fff96b, 0xb400980b, 0xb500001d, - 0xb5fff91d, 0xb50097bd, 0x10000013, 0x10fff8b3, - 0x10009753, 0x90000013, 0x36300016, 0x3637f836, - 0x363096d6, 0x3758000c, 0x375ff7cc, 0x3758966c, + 0x14000000, 0x17ffffd7, 0x140004d0, 0x94000000, + 0x97ffffd4, 0x940004cd, 0x3400000a, 0x34fffa2a, + 0x3400994a, 0x35000008, 0x35fff9c8, 0x350098e8, + 0xb400000b, 0xb4fff96b, 0xb400988b, 0xb500001d, + 0xb5fff91d, 0xb500983d, 0x10000013, 0x10fff8b3, + 0x100097d3, 0x90000013, 0x36300016, 0x3637f836, + 0x36309756, 0x3758000c, 0x375ff7cc, 0x375896ec, 0x128313a0, 0x528a32c7, 0x7289173b, 0x92ab3acc, 0xd2a0bf94, 0xf2c285e8, 0x9358722f, 0x330e652f, 0x53067f3b, 0x93577c53, 0xb34a1aac, 0xd35a4016, 0x13946c63, 0x93c3dbc8, 0x54000000, 0x54fff5a0, - 0x54009440, 0x54000001, 0x54fff541, 0x540093e1, - 0x54000002, 0x54fff4e2, 0x54009382, 0x54000002, - 0x54fff482, 0x54009322, 0x54000003, 0x54fff423, - 0x540092c3, 0x54000003, 0x54fff3c3, 0x54009263, - 0x54000004, 0x54fff364, 0x54009204, 0x54000005, - 0x54fff305, 0x540091a5, 0x54000006, 0x54fff2a6, - 0x54009146, 0x54000007, 0x54fff247, 0x540090e7, - 0x54000008, 0x54fff1e8, 0x54009088, 0x54000009, - 0x54fff189, 0x54009029, 0x5400000a, 0x54fff12a, - 0x54008fca, 0x5400000b, 0x54fff0cb, 0x54008f6b, - 0x5400000c, 0x54fff06c, 0x54008f0c, 0x5400000d, - 0x54fff00d, 0x54008ead, 0x5400000e, 0x54ffefae, - 0x54008e4e, 0x5400000f, 0x54ffef4f, 0x54008def, + 0x540094c0, 0x54000001, 0x54fff541, 0x54009461, + 0x54000002, 0x54fff4e2, 0x54009402, 0x54000002, + 0x54fff482, 0x540093a2, 0x54000003, 0x54fff423, + 0x54009343, 0x54000003, 0x54fff3c3, 0x540092e3, + 0x54000004, 0x54fff364, 0x54009284, 0x54000005, + 0x54fff305, 0x54009225, 0x54000006, 0x54fff2a6, + 0x540091c6, 0x54000007, 0x54fff247, 0x54009167, + 0x54000008, 0x54fff1e8, 0x54009108, 0x54000009, + 0x54fff189, 0x540090a9, 0x5400000a, 0x54fff12a, + 0x5400904a, 0x5400000b, 0x54fff0cb, 0x54008feb, + 0x5400000c, 0x54fff06c, 0x54008f8c, 0x5400000d, + 0x54fff00d, 0x54008f2d, 0x5400000e, 0x54ffefae, + 0x54008ece, 0x5400000f, 0x54ffef4f, 0x54008e6f, 0xd40658e1, 0xd4014d22, 0xd4046543, 0xd4273f60, 0xd44cad80, 0xd503201f, 0xd503203f, 0xd503205f, 0xd503209f, 0xd50320bf, 0xd503219f, 0xd50323bf, @@ -1536,7 +1540,7 @@ 0x39598921, 0x795d3077, 0x399d0675, 0x7998d8f3, 0x79dbd02a, 0xb99d068a, 0xfd5d11a0, 0xbd58d76b, 0xfd1ac72d, 0xbd1d9c14, 0x5800001a, 0x18ffda33, - 0xf8991100, 0xd80078a0, 0xf8a758e0, 0xf9989d80, + 0xf8991100, 0xd8007920, 0xf8a758e0, 0xf9989d80, 0x1a0b0298, 0x3a1c01a0, 0x5a0400ea, 0x7a02020f, 0x9a1d028c, 0xba0e01ad, 0xda140186, 0xfa19022c, 0x0b2b877e, 0x2b21c8ee, 0xcb3ba47d, 0x6b3ae9a0, @@ -1719,7 +1723,8 @@ 0x0420bc31, 0x05271e11, 0x6545e891, 0x6585e891, 0x65c5e891, 0x6545c891, 0x6585c891, 0x65c5c891, 0x052c8020, 0x056c8020, 0x05ac8020, 0x05ec8020, - 0x45b0c210, 0x45f1c231, 0x1e601000, 0x1e603000, + 0x45b0c210, 0x45f1c231, 0x45527a30, 0x45957e93, + 0x45d872f6, 0x455b7759, 0x1e601000, 0x1e603000, 0x1e621000, 0x1e623000, 0x1e641000, 0x1e643000, 0x1e661000, 0x1e663000, 0x1e681000, 0x1e683000, 0x1e6a1000, 0x1e6a3000, 0x1e6c1000, 0x1e6c3000, diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java index a76853016d9..249e73fa54b 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java @@ -2863,6 +2863,26 @@ public class IRNode { machOnlyNameRegex(X86_VMULDQ_REG, "vmuldq_reg"); } + public static final String AARCH64_VMULL_UINT_SVE2 = PREFIX + "AARCH64_VMULL_UINT_SVE2" + POSTFIX; + static { + machOnlyNameRegex(AARCH64_VMULL_UINT_SVE2, "vmulL_uint_sve2"); + } + + public static final String AARCH64_VMULL_INT_SVE2 = PREFIX + "AARCH64_VMULL_INT_SVE2" + POSTFIX; + static { + machOnlyNameRegex(AARCH64_VMULL_INT_SVE2, "vmulL_int_sve2"); + } + + public static final String AARCH64_VMULL_UINT_NEON = PREFIX + "AARCH64_VMULL_UINT_NEON" + POSTFIX; + static { + machOnlyNameRegex(AARCH64_VMULL_UINT_NEON, "vmulL_uint_neon"); + } + + public static final String AARCH64_VMULL_INT_NEON = PREFIX + "AARCH64_VMULL_INT_NEON" + POSTFIX; + static { + machOnlyNameRegex(AARCH64_VMULL_INT_NEON, "vmulL_int_neon"); + } + public static final String X86_SCONV_D2I = PREFIX + "X86_SCONV_D2I" + POSTFIX; static { machOnlyNameRegex(X86_SCONV_D2I, "convD2I_reg_reg"); diff --git a/test/hotspot/jtreg/compiler/vectorapi/TestVectorMulLongToSignedUnsignedInt.java b/test/hotspot/jtreg/compiler/vectorapi/TestVectorMulLongToSignedUnsignedInt.java index d5b4771d3e1..e7745b5e88c 100644 --- a/test/hotspot/jtreg/compiler/vectorapi/TestVectorMulLongToSignedUnsignedInt.java +++ b/test/hotspot/jtreg/compiler/vectorapi/TestVectorMulLongToSignedUnsignedInt.java @@ -34,7 +34,7 @@ import compiler.lib.verify.*; /* * @test - * @bug 8384963 + * @bug 8384963 8383905 * @key randomness * @summary C2: Incorrect uint constant match mishandles negative values in vectors * @modules jdk.incubator.vector @@ -87,8 +87,18 @@ public class TestVectorMulLongToSignedUnsignedInt { // Case 1: Negative mask (-2L = 0xFFFF_FFFF_FFFF_FFFE). @Test - @IR(counts = {IRNode.AND_VL, " >0 ", IRNode.MUL_VL, " >0 "}, applyIfCPUFeature = {"avx", "true"}) - @IR(failOn = {IRNode.X86_VMULUDQ_REG}, phase = CompilePhase.MATCHING, applyIfCPUFeature = {"avx", "true"}) + @IR(counts = {IRNode.AND_VL, " >0 ", + IRNode.MUL_VL, " >0 "}, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}) + @IR(failOn = {IRNode.X86_VMULUDQ_REG}, + phase = CompilePhase.MATCHING, + applyIfCPUFeature = {"avx", "true"}) + @IR(failOn = {IRNode.AARCH64_VMULL_UINT_SVE2}, + phase = CompilePhase.MATCHING, + applyIfCPUFeature = {"sve2", "true"}) + @IR(failOn = {IRNode.AARCH64_VMULL_UINT_NEON}, + phase = CompilePhase.MATCHING, + applyIfCPUFeatureAnd = {"asimd", "true", "sve", "false"}) public static void testNegativeMask() { LongVector v1 = LongVector.fromArray(SPECIES, src1, 0); LongVector v2 = LongVector.fromArray(SPECIES, src2, 0); @@ -107,8 +117,16 @@ public class TestVectorMulLongToSignedUnsignedInt { // Case 3: Mask = 0x1_0000_0000L (bit 32 set, exceeds uint range). @Test - @IR(counts = {IRNode.AND_VL, " >0 ", IRNode.MUL_VL, " >0 "}, applyIfCPUFeature = {"avx", "true"}) - @IR(failOn = {IRNode.X86_VMULUDQ_REG}, phase = CompilePhase.MATCHING, applyIfCPUFeature = {"avx", "true"}) + @IR(counts = {IRNode.AND_VL, " >0 ", + IRNode.MUL_VL, " >0 "}, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}) + @IR(failOn = {IRNode.X86_VMULUDQ_REG}, + phase = CompilePhase.MATCHING, + applyIfCPUFeature = {"avx", "true"}) + @IR(failOn = {IRNode.AARCH64_VMULL_UINT_SVE2}, + phase = CompilePhase.MATCHING, + applyIfCPUFeature = {"sve2", "true"}) + @IR(failOn = {IRNode.AARCH64_VMULL_UINT_NEON}, phase = CompilePhase.MATCHING, applyIfCPUFeatureAnd = {"asimd", "true", "sve", "false"}) public static void testBit32SetMask() { LongVector v1 = LongVector.fromArray(SPECIES, src1, 0); LongVector v2 = LongVector.fromArray(SPECIES, src2, 0); @@ -127,8 +145,18 @@ public class TestVectorMulLongToSignedUnsignedInt { // Case 4: Mask = Long.MIN_VALUE (0x8000_0000_0000_0000). @Test - @IR(counts = {IRNode.AND_VL, " >0 ", IRNode.MUL_VL, " >0 "}, applyIfCPUFeature = {"avx", "true"}) - @IR(failOn = {IRNode.X86_VMULUDQ_REG}, phase = CompilePhase.MATCHING, applyIfCPUFeature = {"avx", "true"}) + @IR(counts = {IRNode.AND_VL, " >0 ", + IRNode.MUL_VL, " >0 "}, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}) + @IR(failOn = {IRNode.X86_VMULUDQ_REG}, + phase = CompilePhase.MATCHING, + applyIfCPUFeature = {"avx", "true"}) + @IR(failOn = {IRNode.AARCH64_VMULL_UINT_SVE2}, + phase = CompilePhase.MATCHING, + applyIfCPUFeature = {"sve2", "true"}) + @IR(failOn = {IRNode.AARCH64_VMULL_UINT_NEON}, + phase = CompilePhase.MATCHING, + applyIfCPUFeatureAnd = {"asimd", "true", "sve", "false"}) public static void testMinValueMask() { LongVector v1 = LongVector.fromArray(SPECIES, src1, 0); LongVector v2 = LongVector.fromArray(SPECIES, src2, 0); @@ -147,8 +175,17 @@ public class TestVectorMulLongToSignedUnsignedInt { // Case 5: Mask = 0xFFFF_FFFFL (exactly uint max, boundary valid case). @Test - @IR(counts = {IRNode.MUL_VL, " >0 "}, applyIfCPUFeature = {"avx", "true"}) - @IR(counts = {IRNode.X86_VMULUDQ_REG, " >0 "}, phase = CompilePhase.MATCHING, applyIfCPUFeature = {"avx", "true"}) + @IR(counts = {IRNode.MUL_VL, " >0 "}, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}) + @IR(counts = {IRNode.X86_VMULUDQ_REG, " >0 "}, + phase = CompilePhase.MATCHING, + applyIfCPUFeature = {"avx", "true"}) + @IR(counts = {IRNode.AARCH64_VMULL_UINT_SVE2, " >0 "}, + phase = CompilePhase.MATCHING, + applyIfCPUFeature = {"sve2", "true"}) + @IR(counts = {IRNode.AARCH64_VMULL_UINT_NEON, " >0 "}, + phase = CompilePhase.MATCHING, + applyIfCPUFeatureAnd = {"asimd", "true", "sve", "false"}) public static void testUintMaxMask() { LongVector v1 = LongVector.fromArray(SPECIES, src1, 0); LongVector v2 = LongVector.fromArray(SPECIES, src2, 0); @@ -167,8 +204,17 @@ public class TestVectorMulLongToSignedUnsignedInt { // Case 6: Small mask (0xFFFFL), clearly fits in uint. @Test - @IR(counts = {IRNode.MUL_VL, " >0 "}, applyIfCPUFeature = {"avx", "true"}) - @IR(counts = {IRNode.X86_VMULUDQ_REG, " >0 "}, phase = CompilePhase.MATCHING, applyIfCPUFeature = {"avx", "true"}) + @IR(counts = {IRNode.MUL_VL, " >0 "}, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}) + @IR(counts = {IRNode.X86_VMULUDQ_REG, " >0 "}, + phase = CompilePhase.MATCHING, + applyIfCPUFeature = {"avx", "true"}) + @IR(counts = {IRNode.AARCH64_VMULL_UINT_SVE2, " >0 "}, + phase = CompilePhase.MATCHING, + applyIfCPUFeature = {"sve2", "true"}) + @IR(counts = {IRNode.AARCH64_VMULL_UINT_NEON, " >0 "}, + phase = CompilePhase.MATCHING, + applyIfCPUFeatureAnd = {"asimd", "true", "sve", "false"}) public static void testSmallMask() { LongVector v1 = LongVector.fromArray(SPECIES, src1, 0); LongVector v2 = LongVector.fromArray(SPECIES, src2, 0); @@ -187,8 +233,18 @@ public class TestVectorMulLongToSignedUnsignedInt { // Case 7: URShift by 32 clears upper doubleword. @Test - @IR(counts = {IRNode.MUL_VL, " >0 ", IRNode.URSHIFT_VL, " >0 "}, applyIfCPUFeature = {"avx", "true"}) - @IR(counts = {IRNode.X86_VMULUDQ_REG, " >0 "}, phase = CompilePhase.MATCHING, applyIfCPUFeature = {"avx", "true"}) + @IR(counts = {IRNode.MUL_VL, " >0 ", + IRNode.URSHIFT_VL, " >0 "}, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}) + @IR(counts = {IRNode.X86_VMULUDQ_REG, " >0 "}, + phase = CompilePhase.MATCHING, + applyIfCPUFeature = {"avx", "true"}) + @IR(counts = {IRNode.AARCH64_VMULL_UINT_SVE2, " >0 "}, + phase = CompilePhase.MATCHING, + applyIfCPUFeature = {"sve2", "true"}) + @IR(counts = {IRNode.AARCH64_VMULL_UINT_NEON, " >0 "}, + phase = CompilePhase.MATCHING, + applyIfCPUFeatureAnd = {"asimd", "true", "sve", "false"}) public static void testURShift32() { LongVector v1 = LongVector.fromArray(SPECIES, src1, 0); LongVector v2 = LongVector.fromArray(SPECIES, src2, 0); @@ -207,8 +263,18 @@ public class TestVectorMulLongToSignedUnsignedInt { // Case 8: Asymmetric — one input valid uint mask, other negative mask. @Test - @IR(counts = {IRNode.AND_VL, " >0 ", IRNode.MUL_VL, " >0 "}, applyIfCPUFeature = {"avx", "true"}) - @IR(failOn = {IRNode.X86_VMULUDQ_REG}, phase = CompilePhase.MATCHING, applyIfCPUFeature = {"avx", "true"}) + @IR(counts = {IRNode.AND_VL, " >0 ", + IRNode.MUL_VL, " >0 "}, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}) + @IR(failOn = {IRNode.X86_VMULUDQ_REG}, + phase = CompilePhase.MATCHING, + applyIfCPUFeature = {"avx", "true"}) + @IR(failOn = {IRNode.AARCH64_VMULL_UINT_SVE2}, + phase = CompilePhase.MATCHING, + applyIfCPUFeature = {"sve2", "true"}) + @IR(failOn = {IRNode.AARCH64_VMULL_UINT_NEON}, + phase = CompilePhase.MATCHING, + applyIfCPUFeatureAnd = {"asimd", "true", "sve", "false"}) public static void testAsymmetricMask() { LongVector v1 = LongVector.fromArray(SPECIES, src1, 0); LongVector v2 = LongVector.fromArray(SPECIES, src2, 0); @@ -228,8 +294,19 @@ public class TestVectorMulLongToSignedUnsignedInt { // Case 9: Mixed — one input URShift (valid), other negative mask (invalid). // Note: -2L is used (not -1L) since AND with -1L is identity and gets folded. @Test - @IR(counts = {IRNode.URSHIFT_VL, " >0 ", IRNode.AND_VL, " >0 ", IRNode.MUL_VL, " >0 "}, applyIfCPUFeature = {"avx", "true"}) - @IR(failOn = {IRNode.X86_VMULUDQ_REG}, phase = CompilePhase.MATCHING, applyIfCPUFeature = {"avx", "true"}) + @IR(counts = {IRNode.URSHIFT_VL, " >0 ", + IRNode.AND_VL, " >0 ", + IRNode.MUL_VL, " >0 "}, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}) + @IR(failOn = {IRNode.X86_VMULUDQ_REG}, + phase = CompilePhase.MATCHING, + applyIfCPUFeature = {"avx", "true"}) + @IR(failOn = {IRNode.AARCH64_VMULL_UINT_SVE2}, + phase = CompilePhase.MATCHING, + applyIfCPUFeature = {"sve2", "true"}) + @IR(failOn = {IRNode.AARCH64_VMULL_UINT_NEON}, + phase = CompilePhase.MATCHING, + applyIfCPUFeatureAnd = {"asimd", "true", "sve", "false"}) public static void testMixedURShiftAndNegMask() { LongVector v1 = LongVector.fromArray(SPECIES, src1, 0); LongVector v2 = LongVector.fromArray(SPECIES, src2, 0); @@ -248,8 +325,18 @@ public class TestVectorMulLongToSignedUnsignedInt { // Case 10: Predicated AndV (uint path). Inactive lanes preserves destination with non-zero upper 32 bits. @Test - @IR(counts = {IRNode.AND_VL, " >0 ", IRNode.MUL_VL, " >0 "}, applyIfCPUFeature = {"avx512f", "true"}) - @IR(failOn = {IRNode.X86_VMULUDQ_REG}, phase = CompilePhase.MATCHING, applyIfCPUFeature = {"avx512f", "true"}) + @IR(counts = {IRNode.AND_VL, " >0 ", + IRNode.MUL_VL, " >0 "}, + applyIfCPUFeatureOr = {"avx512f", "true", "sve", "true"}) + @IR(failOn = {IRNode.X86_VMULUDQ_REG}, + phase = CompilePhase.MATCHING, + applyIfCPUFeature = {"avx512f", "true"}) + @IR(failOn = {IRNode.AARCH64_VMULL_UINT_SVE2}, + phase = CompilePhase.MATCHING, + applyIfCPUFeature = {"sve2", "true"}) + @IR(failOn = {IRNode.AARCH64_VMULL_UINT_NEON}, + phase = CompilePhase.MATCHING, + applyIfCPUFeatureAnd = {"asimd", "true", "sve", "false"}) public static void testPredicatedAndMask() { LongVector v1 = LongVector.fromArray(SPECIES, src1, 0); LongVector v2 = LongVector.fromArray(SPECIES, src2, 0); @@ -270,8 +357,18 @@ public class TestVectorMulLongToSignedUnsignedInt { // Case 11: Predicated URShiftVL by 32 (uint path). Inactive lanes preserves destination with non-zero upper 32 bits. @Test - @IR(counts = {IRNode.URSHIFT_VL, " >0 ", IRNode.MUL_VL, " >0 "}, applyIfCPUFeature = {"avx512f", "true"}) - @IR(failOn = {IRNode.X86_VMULUDQ_REG}, phase = CompilePhase.MATCHING, applyIfCPUFeature = {"avx512f", "true"}) + @IR(counts = {IRNode.URSHIFT_VL, " >0 ", + IRNode.MUL_VL, " >0 "}, + applyIfCPUFeatureOr = {"avx512f", "true", "sve", "true"}) + @IR(failOn = {IRNode.X86_VMULUDQ_REG}, + phase = CompilePhase.MATCHING, + applyIfCPUFeature = {"avx512f", "true"}) + @IR(failOn = {IRNode.AARCH64_VMULL_UINT_SVE2}, + phase = CompilePhase.MATCHING, + applyIfCPUFeature = {"sve2", "true"}) + @IR(failOn = {IRNode.AARCH64_VMULL_UINT_NEON}, + phase = CompilePhase.MATCHING, + applyIfCPUFeatureAnd = {"asimd", "true", "sve", "false"}) public static void testPredicatedURShift32() { LongVector v1 = LongVector.fromArray(SPECIES, src1, 0); LongVector v2 = LongVector.fromArray(SPECIES, src2, 0); @@ -292,8 +389,18 @@ public class TestVectorMulLongToSignedUnsignedInt { // Case 12: Predicated RShiftVL (arithmetic) by 32. @Test - @IR(counts = {IRNode.RSHIFT_VL, " >0 ", IRNode.MUL_VL, " >0 "}, applyIfCPUFeature = {"avx512f", "true"}) - @IR(failOn = {IRNode.X86_VMULDQ_REG}, phase = CompilePhase.MATCHING, applyIfCPUFeature = {"avx512f", "true"}) + @IR(counts = {IRNode.RSHIFT_VL, " >0 ", + IRNode.MUL_VL, " >0 "}, + applyIfCPUFeatureOr = {"avx512f", "true", "sve", "true"}) + @IR(failOn = {IRNode.X86_VMULDQ_REG}, + phase = CompilePhase.MATCHING, + applyIfCPUFeature = {"avx512f", "true"}) + @IR(failOn = {IRNode.AARCH64_VMULL_INT_SVE2}, + phase = CompilePhase.MATCHING, + applyIfCPUFeature = {"sve2", "true"}) + @IR(failOn = {IRNode.AARCH64_VMULL_INT_NEON}, + phase = CompilePhase.MATCHING, + applyIfCPUFeatureAnd = {"asimd", "true", "sve", "false"}) public static void testPredicatedRShift32() { LongVector v1 = LongVector.fromArray(SPECIES, src1, 0); LongVector v2 = LongVector.fromArray(SPECIES, src2, 0); diff --git a/test/hotspot/jtreg/compiler/vectorapi/VectorMultiplyOpt.java b/test/hotspot/jtreg/compiler/vectorapi/VectorMultiplyOpt.java index a8394f41f8a..68ac9249ebf 100644 --- a/test/hotspot/jtreg/compiler/vectorapi/VectorMultiplyOpt.java +++ b/test/hotspot/jtreg/compiler/vectorapi/VectorMultiplyOpt.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -31,9 +31,9 @@ import java.lang.reflect.Array; /** * @test - * @bug 8341137 + * @bug 8341137 8383905 * @key randomness - * @summary Optimize long vector multiplication using x86 VPMUL[U]DQ instruction. + * @summary Optimize long vector multiplication. * @modules jdk.incubator.vector * @library /test/lib / * @run driver compiler.vectorapi.VectorMultiplyOpt @@ -80,7 +80,7 @@ public class VectorMultiplyOpt { public static void main(String[] args) { TestFramework testFramework = new TestFramework(); - testFramework.setDefaultWarmup(5000) + testFramework.setDefaultWarmup(10000) .addFlags("--add-modules=jdk.incubator.vector") .start(); System.out.println("PASSED"); @@ -109,7 +109,12 @@ public class VectorMultiplyOpt { @Test @IR(counts = {IRNode.MUL_VL, " >0 ", IRNode.AND_VL, " >0 "}, applyIfCPUFeature = {"avx", "true"}) @IR(counts = {"vmuludq", " >0 "}, phase = CompilePhase.FINAL_CODE, applyIfCPUFeature = {"avx", "true"}) - @Warmup(value = 10000) + @IR(counts = {"vmulL_uint_sve2", " >0 "}, phase = CompilePhase.FINAL_CODE, + applyIfCPUFeature = {"sve2", "true"}) + @IR(counts = {"vmulL_sve", " >0 "}, phase = CompilePhase.FINAL_CODE, + applyIfCPUFeatureAnd = {"sve", "true", "sve2", "false"}) + @IR(counts = {"vmulL_uint_neon", " >0 "}, phase = CompilePhase.FINAL_CODE, + applyIfCPUFeatureAnd = {"asimd", "true", "sve", "false"}) public static void test_pattern1() { int i = 0; for (; i < LSP.loopBound(res.length); i += LSP.length()) { @@ -132,7 +137,12 @@ public class VectorMultiplyOpt { @Test @IR(counts = {IRNode.MUL_VL, " >0 ", IRNode.AND_VL, " >0 ", IRNode.URSHIFT_VL, " >0 "}, applyIfCPUFeature = {"avx", "true"}) @IR(counts = {"vmuludq", " >0 "}, phase = CompilePhase.FINAL_CODE, applyIfCPUFeature = {"avx", "true"}) - @Warmup(value = 10000) + @IR(counts = {"vmulL_uint_sve2", " >0 "}, phase = CompilePhase.FINAL_CODE, + applyIfCPUFeature = {"sve2", "true"}) + @IR(counts = {"vmulL_sve", " >0 "}, phase = CompilePhase.FINAL_CODE, + applyIfCPUFeatureAnd = {"sve", "true", "sve2", "false"}) + @IR(counts = {"vmulL_uint_neon", " >0 "}, phase = CompilePhase.FINAL_CODE, + applyIfCPUFeatureAnd = {"asimd", "true", "sve", "false"}) public static void test_pattern2() { int i = 0; for (; i < LSP.loopBound(res.length); i += LSP.length()) { @@ -155,7 +165,12 @@ public class VectorMultiplyOpt { @Test @IR(counts = {IRNode.MUL_VL, " >0 ", IRNode.URSHIFT_VL, " >0 "}, applyIfCPUFeature = {"avx", "true"}) @IR(counts = {"vmuludq", " >0 "}, phase = CompilePhase.FINAL_CODE, applyIfCPUFeature = {"avx", "true"}) - @Warmup(value = 10000) + @IR(counts = {"vmulL_uint_sve2", " >0 "}, phase = CompilePhase.FINAL_CODE, + applyIfCPUFeature = {"sve2", "true"}) + @IR(counts = {"vmulL_sve", " >0 "}, phase = CompilePhase.FINAL_CODE, + applyIfCPUFeatureAnd = {"sve", "true", "sve2", "false"}) + @IR(counts = {"vmulL_uint_neon", " >0 "}, phase = CompilePhase.FINAL_CODE, + applyIfCPUFeatureAnd = {"asimd", "true", "sve", "false"}) public static void test_pattern3() { int i = 0; for (; i < LSP.loopBound(res.length); i += LSP.length()) { @@ -178,7 +193,12 @@ public class VectorMultiplyOpt { @Test @IR(counts = {IRNode.MUL_VL, " >0 ", IRNode.URSHIFT_VL, " >0 "}, applyIfCPUFeature = {"avx", "true"}) @IR(counts = {"vmuludq", " >0 "}, applyIfCPUFeature = {"avx", "true"}, phase = CompilePhase.FINAL_CODE) - @Warmup(value = 10000) + @IR(counts = {"vmulL_uint_sve2", " >0 "}, phase = CompilePhase.FINAL_CODE, + applyIfCPUFeature = {"sve2", "true"}) + @IR(counts = {"vmulL_sve", " >0 "}, phase = CompilePhase.FINAL_CODE, + applyIfCPUFeatureAnd = {"sve", "true", "sve2", "false"}) + @IR(counts = {"vmulL_uint_neon", " >0 "}, phase = CompilePhase.FINAL_CODE, + applyIfCPUFeatureAnd = {"asimd", "true", "sve", "false"}) public static void test_pattern4() { int i = 0; for (; i < LSP.loopBound(res.length); i += LSP.length()) { @@ -201,7 +221,12 @@ public class VectorMultiplyOpt { @Test @IR(counts = {IRNode.MUL_VL, " >0 ", IRNode.VECTOR_CAST_I2L, " >0 "}, applyIfCPUFeature = {"avx", "true"}) @IR(counts = {"vmuldq", " >0 "}, applyIfCPUFeature = {"avx", "true"}, phase = CompilePhase.FINAL_CODE) - @Warmup(value = 10000) + @IR(counts = {"vmulL_int_sve2", " >0 "}, phase = CompilePhase.FINAL_CODE, + applyIfCPUFeature = {"sve2", "true"}) + @IR(counts = {"vmulL_sve", " >0 "}, phase = CompilePhase.FINAL_CODE, + applyIfCPUFeatureAnd = {"sve", "true", "sve2", "false"}) + @IR(counts = {"vmulL_int_neon", " >0 "}, phase = CompilePhase.FINAL_CODE, + applyIfCPUFeatureAnd = {"asimd", "true", "sve", "false"}) public static void test_pattern5() { int i = 0; for (; i < LSP.loopBound(res.length); i += LSP.length()) { @@ -227,7 +252,12 @@ public class VectorMultiplyOpt { @Test @IR(counts = {IRNode.MUL_VL, " >0 ", IRNode.RSHIFT_VL, " >0 "}, applyIfCPUFeature = {"avx", "true"}) @IR(counts = {"vmuldq", " >0 "}, applyIfCPUFeature = {"avx", "true"}, phase = CompilePhase.FINAL_CODE) - @Warmup(value = 10000) + @IR(counts = {"vmulL_int_sve2", " >0 "}, phase = CompilePhase.FINAL_CODE, + applyIfCPUFeature = {"sve2", "true"}) + @IR(counts = {"vmulL_sve", " >0 "}, phase = CompilePhase.FINAL_CODE, + applyIfCPUFeatureAnd = {"sve", "true", "sve2", "false"}) + @IR(counts = {"vmulL_int_neon", " >0 "}, phase = CompilePhase.FINAL_CODE, + applyIfCPUFeatureAnd = {"asimd", "true", "sve", "false"}) public static void test_pattern6() { int i = 0; for (; i < LSP.loopBound(res.length); i += LSP.length()) { @@ -247,4 +277,61 @@ public class VectorMultiplyOpt { validate("pattern6 ", res, lsrc1, lsrc2, (l1, l2) -> (l1 >> shift5) * (l2 >> shift5)); } + // Same-operand multiplication (v * v) where v has zero-extended high bits. + // On NEON this should map to the dedicated rule that emits a single xtn. + @Test + @IR(counts = {IRNode.MUL_VL, " >0 ", IRNode.AND_VL, " >0 "}, applyIfCPUFeature = {"avx", "true"}) + @IR(counts = {"vmuludq", " >0 "}, phase = CompilePhase.FINAL_CODE, applyIfCPUFeature = {"avx", "true"}) + @IR(counts = {"vmulL_uint_sve2", " >0 "}, phase = CompilePhase.FINAL_CODE, + applyIfCPUFeature = {"sve2", "true"}) + @IR(counts = {"vmulL_sve", " >0 "}, phase = CompilePhase.FINAL_CODE, + applyIfCPUFeatureAnd = {"sve", "true", "sve2", "false"}) + @IR(counts = {"vmulL_uint_neon_same", " >0 "}, phase = CompilePhase.FINAL_CODE, + applyIfCPUFeatureAnd = {"asimd", "true", "sve", "false"}) + public static void test_pattern7() { + int i = 0; + for (; i < LSP.loopBound(res.length); i += LSP.length()) { + LongVector vsrc = LongVector.fromArray(LSP, lsrc1, i) + .lanewise(VectorOperators.AND, mask1); + vsrc.lanewise(VectorOperators.MUL, vsrc).intoArray(res, i); + } + for (; i < res.length; i++) { + long x = lsrc1[i] & mask1; + res[i] = x * x; + } + } + + @Check(test = "test_pattern7") + public void test_pattern7_validate() { + validate("pattern7 ", res, lsrc1, lsrc1, (l1, l2) -> { long x = l1 & mask1; return x * x; }); + } + + // Same-operand multiplication (v * v) where v has sign-extended high bits. + // On NEON this should map to the dedicated rule that emits a single xtn. + @Test + @IR(counts = {IRNode.MUL_VL, " >0 ", IRNode.VECTOR_CAST_I2L, " >0 "}, applyIfCPUFeature = {"avx", "true"}) + @IR(counts = {"vmuldq", " >0 "}, applyIfCPUFeature = {"avx", "true"}, phase = CompilePhase.FINAL_CODE) + @IR(counts = {"vmulL_int_sve2", " >0 "}, phase = CompilePhase.FINAL_CODE, + applyIfCPUFeature = {"sve2", "true"}) + @IR(counts = {"vmulL_sve", " >0 "}, phase = CompilePhase.FINAL_CODE, + applyIfCPUFeatureAnd = {"sve", "true", "sve2", "false"}) + @IR(counts = {"vmulL_int_neon_same", " >0 "}, phase = CompilePhase.FINAL_CODE, + applyIfCPUFeatureAnd = {"asimd", "true", "sve", "false"}) + public static void test_pattern8() { + int i = 0; + for (; i < LSP.loopBound(res.length); i += LSP.length()) { + LongVector vsrc = IntVector.fromArray(ISP, isrc1, i) + .convert(VectorOperators.I2L, 0) + .reinterpretAsLongs(); + vsrc.lanewise(VectorOperators.MUL, vsrc).intoArray(res, i); + } + for (; i < res.length; i++) { + res[i] = Math.multiplyFull(isrc1[i], isrc1[i]); + } + } + + @Check(test = "test_pattern8") + public void test_pattern8_validate() { + validate("pattern8 ", res, isrc1, isrc1, (i1, i2) -> Math.multiplyFull((int)i1, (int)i1)); + } } From 631b675d7949a0e6312d8d6f45e2515d53b12f05 Mon Sep 17 00:00:00 2001 From: Eric Fang Date: Mon, 6 Jul 2026 05:45:30 +0000 Subject: [PATCH 079/305] 8387388: AArch64: Optimize reduceLanes MUL op with ext instruction Reviewed-by: aph, xgong --- .../cpu/aarch64/c2_MacroAssembler_aarch64.cpp | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp index e46a338e649..eacfef9618a 100644 --- a/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp @@ -1806,19 +1806,19 @@ void C2_MacroAssembler::neon_reduce_mul_integral(Register dst, BasicType bt, if (isQ) { // Multiply the lower half and higher half of vector iteratively. // vtmp1 = vsrc[8:15] - ins(vtmp1, D, vsrc, 0, 1); + ext(vtmp1, T16B, vsrc, vsrc, 8); // vtmp1[n] = vsrc[n] * vsrc[n + 8], where n=[0, 7] mulv(vtmp1, T8B, vtmp1, vsrc); // vtmp2 = vtmp1[4:7] - ins(vtmp2, S, vtmp1, 0, 1); + ext(vtmp2, T8B, vtmp1, vtmp1, 4); // vtmp1[n] = vtmp1[n] * vtmp1[n + 4], where n=[0, 3] mulv(vtmp1, T8B, vtmp2, vtmp1); } else { - ins(vtmp1, S, vsrc, 0, 1); + ext(vtmp1, T8B, vsrc, vsrc, 4); mulv(vtmp1, T8B, vtmp1, vsrc); } // vtmp2 = vtmp1[2:3] - ins(vtmp2, H, vtmp1, 0, 1); + ext(vtmp2, T8B, vtmp1, vtmp1, 2); // vtmp2[n] = vtmp1[n] * vtmp1[n + 2], where n=[0, 1] mulv(vtmp2, T8B, vtmp2, vtmp1); // dst = vtmp2[0] * isrc * vtmp2[1] @@ -1831,12 +1831,12 @@ void C2_MacroAssembler::neon_reduce_mul_integral(Register dst, BasicType bt, break; case T_SHORT: if (isQ) { - ins(vtmp2, D, vsrc, 0, 1); + ext(vtmp2, T16B, vsrc, vsrc, 8); mulv(vtmp2, T4H, vtmp2, vsrc); - ins(vtmp1, S, vtmp2, 0, 1); + ext(vtmp1, T8B, vtmp2, vtmp2, 4); mulv(vtmp1, T4H, vtmp1, vtmp2); } else { - ins(vtmp1, S, vsrc, 0, 1); + ext(vtmp1, T8B, vsrc, vsrc, 4); mulv(vtmp1, T4H, vtmp1, vsrc); } umov(rscratch1, vtmp1, H, 0); @@ -1848,7 +1848,7 @@ void C2_MacroAssembler::neon_reduce_mul_integral(Register dst, BasicType bt, break; case T_INT: if (isQ) { - ins(vtmp1, D, vsrc, 0, 1); + ext(vtmp1, T16B, vsrc, vsrc, 8); mulv(vtmp1, T2S, vtmp1, vsrc); } else { vtmp1 = vsrc; @@ -1904,19 +1904,19 @@ void C2_MacroAssembler::neon_reduce_mul_fp(FloatRegister dst, BasicType bt, break; case T_FLOAT: fmuls(dst, fsrc, vsrc); - ins(vtmp, S, vsrc, 0, 1); + ext(vtmp, T8B, vsrc, vsrc, 4); fmuls(dst, dst, vtmp); if (isQ) { - ins(vtmp, S, vsrc, 0, 2); + ext(vtmp, T16B, vsrc, vsrc, 8); fmuls(dst, dst, vtmp); - ins(vtmp, S, vsrc, 0, 3); + ext(vtmp, T16B, vsrc, vsrc, 12); fmuls(dst, dst, vtmp); } break; case T_DOUBLE: assert(isQ, "unsupported"); fmuld(dst, fsrc, vsrc); - ins(vtmp, D, vsrc, 0, 1); + ext(vtmp, T16B, vsrc, vsrc, 8); fmuld(dst, dst, vtmp); break; default: From b3100b4173184a8c9d9c9ef0975c795bd4d64b7f Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Mon, 6 Jul 2026 07:58:46 +0000 Subject: [PATCH 080/305] 8387400: Force-inline Devirtualizer methods Reviewed-by: kvn, aboldtch --- src/hotspot/share/utilities/devirtualizer.hpp | 25 +++++++++++++------ .../share/utilities/devirtualizer.inline.hpp | 12 ++++++++- 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/src/hotspot/share/utilities/devirtualizer.hpp b/src/hotspot/share/utilities/devirtualizer.hpp index b4d444dc5a8..39e1ba89239 100644 --- a/src/hotspot/share/utilities/devirtualizer.hpp +++ b/src/hotspot/share/utilities/devirtualizer.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -34,12 +34,23 @@ class ClassLoaderData; // a concrete implementation, otherwise a virtual call is taken. class Devirtualizer { public: - template static void do_oop(OopClosureType* closure, T* p); - template static void do_klass(OopClosureType* closure, Klass* k); - template static void do_cld(OopClosureType* closure, ClassLoaderData* cld); - template static bool do_metadata(OopClosureType* closure); - template static void do_derived_oop(DerivedOopClosureType* closure, derived_base* base, derived_pointer* derived); - template static bool do_bit(BitMapClosureType* closure, BitMap::idx_t index); + template + static ALWAYSINLINE void do_oop(OopClosureType* closure, T* p); + + template + static ALWAYSINLINE void do_klass(OopClosureType* closure, Klass* k); + + template + static ALWAYSINLINE void do_cld(OopClosureType* closure, ClassLoaderData* cld); + + template + static ALWAYSINLINE bool do_metadata(OopClosureType* closure); + + template + static ALWAYSINLINE void do_derived_oop(DerivedOopClosureType* closure, derived_base* base, derived_pointer* derived); + + template + static ALWAYSINLINE bool do_bit(BitMapClosureType* closure, BitMap::idx_t index); }; #endif // SHARE_UTILITIES_DEVIRTUALIZER_HPP diff --git a/src/hotspot/share/utilities/devirtualizer.inline.hpp b/src/hotspot/share/utilities/devirtualizer.inline.hpp index 7f49524e0fb..8cc6f931908 100644 --- a/src/hotspot/share/utilities/devirtualizer.inline.hpp +++ b/src/hotspot/share/utilities/devirtualizer.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -74,12 +74,14 @@ // p - The oop (or narrowOop) field to pass to the closure template +ALWAYSINLINE static typename EnableIf::value, void>::type call_do_oop(void (Receiver::*)(T*), void (Base::*)(T*), OopClosureType* closure, T* p) { closure->do_oop(p); } template +ALWAYSINLINE static typename EnableIf::value, void>::type call_do_oop(void (Receiver::*)(T*), void (Base::*)(T*), OopClosureType* closure, T* p) { // Sanity check @@ -95,12 +97,14 @@ inline void Devirtualizer::do_oop(OopClosureType* closure, T* p) { // Implementation of the non-virtual do_metadata dispatch. template +ALWAYSINLINE static typename EnableIf::value, bool>::type call_do_metadata(bool (Receiver::*)(), bool (Base::*)(), OopClosureType* closure) { return closure->do_metadata(); } template +ALWAYSINLINE static typename EnableIf::value, bool>::type call_do_metadata(bool (Receiver::*)(), bool (Base::*)(), OopClosureType* closure) { return closure->OopClosureType::do_metadata(); @@ -114,12 +118,14 @@ inline bool Devirtualizer::do_metadata(OopClosureType* closure) { // Implementation of the non-virtual do_klass dispatch. template +ALWAYSINLINE static typename EnableIf::value, void>::type call_do_klass(void (Receiver::*)(Klass*), void (Base::*)(Klass*), OopClosureType* closure, Klass* k) { closure->do_klass(k); } template +ALWAYSINLINE static typename EnableIf::value, void>::type call_do_klass(void (Receiver::*)(Klass*), void (Base::*)(Klass*), OopClosureType* closure, Klass* k) { closure->OopClosureType::do_klass(k); @@ -133,12 +139,14 @@ inline void Devirtualizer::do_klass(OopClosureType* closure, Klass* k) { // Implementation of the non-virtual do_cld dispatch. template +ALWAYSINLINE static typename EnableIf::value, void>::type call_do_cld(void (Receiver::*)(ClassLoaderData*), void (Base::*)(ClassLoaderData*), OopClosureType* closure, ClassLoaderData* cld) { closure->do_cld(cld); } template +ALWAYSINLINE static typename EnableIf::value, void>::type call_do_cld(void (Receiver::*)(ClassLoaderData*), void (Base::*)(ClassLoaderData*), OopClosureType* closure, ClassLoaderData* cld) { closure->OopClosureType::do_cld(cld); @@ -152,12 +160,14 @@ void Devirtualizer::do_cld(OopClosureType* closure, ClassLoaderData* cld) { // Implementation of the non-virtual do_derived_oop dispatch. template +ALWAYSINLINE static typename EnableIf::value, void>::type call_do_derived_oop(void (Receiver::*)(derived_base*, derived_pointer*), void (Base::*)(derived_base*, derived_pointer*), DerivedOopClosureType* closure, derived_base* base, derived_pointer* derived) { closure->do_derived_oop(base, derived); } template +ALWAYSINLINE static typename EnableIf::value, void>::type call_do_derived_oop(void (Receiver::*)(derived_base*, derived_pointer*), void (Base::*)(derived_base*, derived_pointer*), DerivedOopClosureType* closure, derived_base* base, derived_pointer* derived) { closure->DerivedOopClosureType::do_derived_oop(base, derived); From a96895c580c790e5ab0d4e88365d0e4f2ee9c568 Mon Sep 17 00:00:00 2001 From: David Briemann Date: Mon, 6 Jul 2026 08:15:19 +0000 Subject: [PATCH 081/305] 8387019: PPC64: Remove postalloc_expand from cmovI/cmovL bso_reg_conLvalue0 nodes Reviewed-by: mdoerr, rrich --- src/hotspot/cpu/ppc/ppc.ad | 225 ++++++++++--------------------------- 1 file changed, 60 insertions(+), 165 deletions(-) diff --git a/src/hotspot/cpu/ppc/ppc.ad b/src/hotspot/cpu/ppc/ppc.ad index 896128f99cc..d3e08a21640 100644 --- a/src/hotspot/cpu/ppc/ppc.ad +++ b/src/hotspot/cpu/ppc/ppc.ad @@ -3078,13 +3078,6 @@ encode %{ __ bind(done); %} - enc_class enc_cmove_bso_reg(iRegLdst dst, flagsRegSrc crx, regD src) %{ - Label done; - __ bso($crx$$CondRegister, done); - __ mffprd($dst$$Register, $src$$FloatRegister); - __ bind(done); - %} - enc_class enc_bc(flagsRegSrc crx, cmpOp cmp, Label lbl) %{ Label d; // dummy __ bind(d); @@ -9945,6 +9938,34 @@ instruct convL2I_reg(iRegIdst dst, iRegLsrc src) %{ ins_pipe(pipe_class_default); %} +instruct cmovI_bso_stackSlotL(iRegIdst dst, flagsRegSrc crx, stackSlotL src) %{ + // no match-rule, false predicate + effect(DEF dst, USE crx, USE src); + predicate(false); + + format %{ "CMOVI $crx, $dst, $src" %} + size(8); + ins_encode( enc_cmove_bso_stackSlotL(dst, crx, src) ); + ins_pipe(pipe_class_default); +%} + +instruct cmovI_bso_reg_con0(iRegIdst dst, flagsRegSrc crx, regD src) %{ + // no match-rule, false predicate + effect(DEF dst, USE crx, USE src); + predicate(false); + + format %{ "CMOVI $dst, $crx, $src, 0 \t// set to 0 if unordered" %} + size(12); + ins_encode %{ + Label done; + __ li($dst$$Register, 0); + __ bso($crx$$CondRegister, done); + __ mffprd($dst$$Register, $src$$FloatRegister); + __ bind(done); + %} + ins_pipe(pipe_class_default); +%} + instruct convD2IRaw_regD(regD dst, regD src) %{ // no match-rule, false predicate effect(DEF dst, USE src); @@ -9958,84 +9979,6 @@ instruct convD2IRaw_regD(regD dst, regD src) %{ ins_pipe(pipe_class_default); %} -instruct cmovI_bso_stackSlotL(iRegIdst dst, flagsRegSrc crx, stackSlotL src) %{ - // no match-rule, false predicate - effect(DEF dst, USE crx, USE src); - predicate(false); - - format %{ "CMOVI $crx, $dst, $src" %} - size(8); - ins_encode( enc_cmove_bso_stackSlotL(dst, crx, src) ); - ins_pipe(pipe_class_default); -%} - -instruct cmovI_bso_reg(iRegIdst dst, flagsRegSrc crx, regD src) %{ - // no match-rule, false predicate - effect(DEF dst, USE crx, USE src); - predicate(false); - - format %{ "CMOVI $crx, $dst, $src" %} - size(8); - ins_encode( enc_cmove_bso_reg(dst, crx, src) ); - ins_pipe(pipe_class_default); -%} - - -instruct cmovI_bso_reg_conLvalue0_Ex(iRegIdst dst, flagsRegSrc crx, regD src) %{ - // no match-rule, false predicate - effect(DEF dst, USE crx, USE src); - predicate(false); - - format %{ "CMOVI $dst, $crx, $src \t// postalloc expanded" %} - postalloc_expand %{ - // - // replaces - // - // region dst crx src - // \ | | / - // dst=cmovI_bso_reg_conLvalue0 - // - // with - // - // region dst - // \ / - // dst=loadConI16(0) - // | - // ^ region dst crx src - // | \ | | / - // dst=cmovI_bso_reg - // - - // Create new nodes. - MachNode *m1 = new loadConI16Node(); - MachNode *m2 = new cmovI_bso_regNode(); - - // inputs for new nodes - m1->add_req(n_region); - m2->add_req(n_region, n_crx, n_src); - - // precedences for new nodes - m2->add_prec(m1); - - // operands for new nodes - m1->_opnds[0] = op_dst; - m1->_opnds[1] = new immI16Oper(0); - - m2->_opnds[0] = op_dst; - m2->_opnds[1] = op_crx; - m2->_opnds[2] = op_src; - - // registers for new nodes - ra_->set_pair(m1->_idx, ra_->get_reg_second(this), ra_->get_reg_first(this)); // dst - ra_->set_pair(m2->_idx, ra_->get_reg_second(this), ra_->get_reg_first(this)); // dst - - // Insert new nodes. - nodes->push(m1); - nodes->push(m2); - %} -%} - - // Double to Int conversion, NaN is mapped to 0. Special version for Power8. instruct convD2I_reg_mffprd_ExEx(iRegIdst dst, regD src) %{ match(Set dst (ConvD2I src)); @@ -10046,7 +9989,7 @@ instruct convD2I_reg_mffprd_ExEx(iRegIdst dst, regD src) %{ flagsReg crx; cmpDUnordered_reg_reg(crx, src, src); // Check whether src is NaN. convD2IRaw_regD(tmpD, src); // Convert float to int (speculated). - cmovI_bso_reg_conLvalue0_Ex(dst, crx, tmpD); // Cmove based on NaN check. + cmovI_bso_reg_con0(dst, crx, tmpD); // Cmove based on NaN check. %} %} @@ -10074,7 +10017,7 @@ instruct convF2I_regF_mffprd_ExEx(iRegIdst dst, regF src) %{ flagsReg crx; cmpFUnordered_reg_reg(crx, src, src); // Check whether src is NaN. convF2IRaw_regF(tmpF, src); // Convert float to int (speculated). - cmovI_bso_reg_conLvalue0_Ex(dst, crx, tmpF); // Cmove based on NaN check. + cmovI_bso_reg_con0(dst, crx, tmpF); // Cmove based on NaN check. %} %} @@ -10116,6 +10059,34 @@ instruct zeroExtendL_regL(iRegLdst dst, iRegLsrc src, immL_32bits mask) %{ ins_pipe(pipe_class_default); %} +instruct cmovL_bso_stackSlotL(iRegLdst dst, flagsRegSrc crx, stackSlotL src) %{ + // no match-rule, false predicate + effect(DEF dst, USE crx, USE src); + predicate(false); + + format %{ "CMOVL $crx, $dst, $src" %} + size(8); + ins_encode( enc_cmove_bso_stackSlotL(dst, crx, src) ); + ins_pipe(pipe_class_default); +%} + +instruct cmovL_bso_reg_con0(iRegLdst dst, flagsRegSrc crx, regD src) %{ + // no match-rule, false predicate + effect(DEF dst, USE crx, USE src); + predicate(false); + + format %{ "CMOVL $dst, $crx, $src, 0 \t// set to 0 if unordered" %} + size(12); + ins_encode %{ + Label done; + __ li($dst$$Register, 0); + __ bso($crx$$CondRegister, done); + __ mffprd($dst$$Register, $src$$FloatRegister); + __ bind(done); + %} + ins_pipe(pipe_class_default); +%} + instruct convF2LRaw_regF(regF dst, regF src) %{ // no match-rule, false predicate effect(DEF dst, USE src); @@ -10129,81 +10100,6 @@ instruct convF2LRaw_regF(regF dst, regF src) %{ ins_pipe(pipe_class_default); %} -instruct cmovL_bso_stackSlotL(iRegLdst dst, flagsRegSrc crx, stackSlotL src) %{ - // no match-rule, false predicate - effect(DEF dst, USE crx, USE src); - predicate(false); - - format %{ "CMOVL $crx, $dst, $src" %} - size(8); - ins_encode( enc_cmove_bso_stackSlotL(dst, crx, src) ); - ins_pipe(pipe_class_default); -%} - -instruct cmovL_bso_reg(iRegLdst dst, flagsRegSrc crx, regD src) %{ - // no match-rule, false predicate - effect(DEF dst, USE crx, USE src); - predicate(false); - - format %{ "CMOVL $crx, $dst, $src" %} - size(8); - ins_encode( enc_cmove_bso_reg(dst, crx, src) ); - ins_pipe(pipe_class_default); -%} - - -instruct cmovL_bso_reg_conLvalue0_Ex(iRegLdst dst, flagsRegSrc crx, regD src) %{ - // no match-rule, false predicate - effect(DEF dst, USE crx, USE src); - predicate(false); - - format %{ "CMOVL $dst, $crx, $src \t// postalloc expanded" %} - postalloc_expand %{ - // - // replaces - // - // region dst crx src - // \ | | / - // dst=cmovL_bso_reg_conLvalue0 - // - // with - // - // region dst - // \ / - // dst=loadConL16(0) - // | - // ^ region dst crx src - // | \ | | / - // dst=cmovL_bso_reg - // - - // Create new nodes. - MachNode *m1 = new loadConL16Node(); - MachNode *m2 = new cmovL_bso_regNode(); - - // inputs for new nodes - m1->add_req(n_region); - m2->add_req(n_region, n_crx, n_src); - m2->add_prec(m1); - - // operands for new nodes - m1->_opnds[0] = op_dst; - m1->_opnds[1] = new immL16Oper(0); - m2->_opnds[0] = op_dst; - m2->_opnds[1] = op_crx; - m2->_opnds[2] = op_src; - - // registers for new nodes - ra_->set_pair(m1->_idx, ra_->get_reg_second(this), ra_->get_reg_first(this)); // dst - ra_->set_pair(m2->_idx, ra_->get_reg_second(this), ra_->get_reg_first(this)); // dst - - // Insert new nodes. - nodes->push(m1); - nodes->push(m2); - %} -%} - - // Float to Long conversion, NaN is mapped to 0. Special version for Power8. instruct convF2L_reg_mffprd_ExEx(iRegLdst dst, regF src) %{ match(Set dst (ConvF2L src)); @@ -10214,7 +10110,7 @@ instruct convF2L_reg_mffprd_ExEx(iRegLdst dst, regF src) %{ flagsReg crx; cmpFUnordered_reg_reg(crx, src, src); // Check whether src is NaN. convF2LRaw_regF(tmpF, src); // Convert float to long (speculated). - cmovL_bso_reg_conLvalue0_Ex(dst, crx, tmpF); // Cmove based on NaN check. + cmovL_bso_reg_con0(dst, crx, tmpF); // Cmove based on NaN check. %} %} @@ -10231,7 +10127,6 @@ instruct convD2LRaw_regD(regD dst, regD src) %{ ins_pipe(pipe_class_default); %} - // Double to Long conversion, NaN is mapped to 0. Special version for Power8. instruct convD2L_reg_mffprd_ExEx(iRegLdst dst, regD src) %{ match(Set dst (ConvD2L src)); @@ -10242,7 +10137,7 @@ instruct convD2L_reg_mffprd_ExEx(iRegLdst dst, regD src) %{ flagsReg crx; cmpDUnordered_reg_reg(crx, src, src); // Check whether src is NaN. convD2LRaw_regD(tmpD, src); // Convert float to long (speculated). - cmovL_bso_reg_conLvalue0_Ex(dst, crx, tmpD); // Cmove based on NaN check. + cmovL_bso_reg_con0(dst, crx, tmpD); // Cmove based on NaN check. %} %} From 6383ad150cf024ed0492526ed5dd042856b9cfee Mon Sep 17 00:00:00 2001 From: Albert Mingkun Yang Date: Mon, 6 Jul 2026 08:31:54 +0000 Subject: [PATCH 082/305] 8387581: Serial: Clean up startup allocation locking Reviewed-by: tschatzl, aboldtch --- src/hotspot/share/gc/serial/serialHeap.cpp | 27 +++++++++++----------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/hotspot/share/gc/serial/serialHeap.cpp b/src/hotspot/share/gc/serial/serialHeap.cpp index 5d068ff67e0..3de562e886d 100644 --- a/src/hotspot/share/gc/serial/serialHeap.cpp +++ b/src/hotspot/share/gc/serial/serialHeap.cpp @@ -306,11 +306,25 @@ HeapWord* SerialHeap::mem_allocate_work(size_t size, bool is_tlab) { for (uint try_count = 1; /* break */; try_count++) { { + // This lock is needed to sync with the VM-init expansion below. ConditionalMutexLocker locker(Heap_lock, !is_init_completed()); result = mem_allocate_cas_noexpand(size, is_tlab); if (result != nullptr) { break; } + + // Ensure that is_init_completed() does not transition while expanding the heap. + ConditionalMutexLocker ml_init(InitCompleted_lock, !is_init_completed(), Mutex::_no_safepoint_check_flag); + if (!is_init_completed()) { + // Rechecked !is_init_completed() implies we have mutual exclusion via + // `Heap_lock` and `InitCompleted_lock` + result = expand_heap_and_allocate(size, is_tlab); + // Return the result if it's tlab-allocation. If the result is null, + // callers will retry non-tlab allocation. + if (result != nullptr || is_tlab) { + return result; + } + } } uint gc_count_before; // Read inside the Heap_lock locked region. { @@ -323,19 +337,6 @@ HeapWord* SerialHeap::mem_allocate_work(size_t size, bool is_tlab) { break; } - if (!is_init_completed()) { - // Double checked locking, this ensure that is_init_completed() does not - // transition while expanding the heap. - MonitorLocker ml(InitCompleted_lock, Monitor::_no_safepoint_check_flag); - if (!is_init_completed()) { - // Can't do GC; try heap expansion to satisfy the request. - result = expand_heap_and_allocate(size, is_tlab); - if (result != nullptr) { - return result; - } - } - } - gc_count_before = total_collections(); } From 7ac72d18c2ad9839c4ebcda346cb604135aece49 Mon Sep 17 00:00:00 2001 From: Thomas Schatzl Date: Mon, 6 Jul 2026 08:58:39 +0000 Subject: [PATCH 083/305] 8225186: G1: Compiler code cache requested GC deadlocks while WhiteBox has control Reviewed-by: ayang, iwalulya --- src/hotspot/share/code/codeCache.cpp | 27 ++- src/hotspot/share/code/codeCache.hpp | 10 +- src/hotspot/share/gc/g1/g1CollectedHeap.cpp | 24 +- src/hotspot/share/gc/g1/g1Policy.cpp | 6 +- src/hotspot/share/gc/g1/g1VMOperations.cpp | 28 ++- src/hotspot/share/gc/g1/g1VMOperations.hpp | 4 +- .../gc/shared/concurrentGCBreakpoints.cpp | 4 +- .../jtreg/gc/g1/TestCodeCacheWhiteBox.java | 216 ++++++++++++++++++ 8 files changed, 291 insertions(+), 28 deletions(-) create mode 100644 test/hotspot/jtreg/gc/g1/TestCodeCacheWhiteBox.java diff --git a/src/hotspot/share/code/codeCache.cpp b/src/hotspot/share/code/codeCache.cpp index f1ca317d36f..94cf8ebdec1 100644 --- a/src/hotspot/share/code/codeCache.cpp +++ b/src/hotspot/share/code/codeCache.cpp @@ -38,6 +38,7 @@ #include "gc/shared/barrierSetNMethod.hpp" #include "gc/shared/classUnloadingContext.hpp" #include "gc/shared/collectedHeap.hpp" +#include "gc/shared/gcCause.hpp" #include "jfr/jfrEvents.hpp" #include "jvm_io.h" #include "logging/log.hpp" @@ -814,7 +815,8 @@ void CodeCache::update_cold_gc_count() { size_t used = max - free; double gc_interval = time - last_time; - _unloading_threshold_gc_requested = false; + AtomicAccess::store(&_unloading_threshold_gc_state, UnloadingRequestState::Idle); + _last_unloading_time = time; _last_unloading_used = used; @@ -889,7 +891,7 @@ void CodeCache::gc_on_allocation() { double free_ratio = double(free) / double(max); if (free_ratio <= StartAggressiveSweepingAt / 100.0) { // In case the GC is concurrent, we make sure only one thread requests the GC. - if (AtomicAccess::cmpxchg(&_unloading_threshold_gc_requested, false, true) == false) { + if (AtomicAccess::cmpxchg(&_unloading_threshold_gc_state, UnloadingRequestState::Idle, UnloadingRequestState::Active) == UnloadingRequestState::Idle) { log_info(codecache)("Triggering aggressive GC due to having only %.3f%% free memory", free_ratio * 100.0); Universe::heap()->collect(GCCause::_codecache_GC_aggressive); } @@ -915,7 +917,7 @@ void CodeCache::gc_on_allocation() { // it is eventually invoked to avoid trouble. if (allocated_since_last_ratio > threshold) { // In case the GC is concurrent, we make sure only one thread requests the GC. - if (AtomicAccess::cmpxchg(&_unloading_threshold_gc_requested, false, true) == false) { + if (AtomicAccess::cmpxchg(&_unloading_threshold_gc_state, UnloadingRequestState::Idle, UnloadingRequestState::Active) == UnloadingRequestState::Idle) { log_info(codecache)("Triggering threshold (%.3f%%) GC due to allocating %.3f%% since last unloading (%.3f%% used -> %.3f%% used)", threshold * 100.0, allocated_since_last_ratio * 100.0, last_used_ratio * 100.0, used_ratio * 100.0); Universe::heap()->collect(GCCause::_codecache_GC_threshold); @@ -935,7 +937,7 @@ uint64_t CodeCache::_cold_gc_count = INT_MAX; double CodeCache::_last_unloading_time = 0.0; size_t CodeCache::_last_unloading_used = 0; -volatile bool CodeCache::_unloading_threshold_gc_requested = false; +volatile CodeCache::UnloadingRequestState CodeCache::_unloading_threshold_gc_state = UnloadingRequestState::Idle; TruncatedSeq CodeCache::_unloading_gc_intervals(10 /* samples */); TruncatedSeq CodeCache::_unloading_allocation_rates(10 /* samples */); @@ -970,6 +972,23 @@ void CodeCache::on_gc_marking_cycle_finish() { update_cold_gc_count(); } +void CodeCache::defer_unloading_gc_request() { + assert_at_safepoint(); + assert(_unloading_threshold_gc_state == UnloadingRequestState::Active, "only defer active requests"); + AtomicAccess::store(&_unloading_threshold_gc_state, UnloadingRequestState::Deferred); +} + +void CodeCache::clear_deferred_unloading_gc_request() { + // Codecache marking may still be active after aborting gc marking, so we can not + // use is_marking_active() to check whether we are in the correct state to clear + // the deferred state. + // Requests are only deferred outside GC marking, and only cleared after + // at the end of whitebox, we can just clear it if it was Deferred. + AtomicAccess::cmpxchg(&_unloading_threshold_gc_state, + UnloadingRequestState::Deferred, + UnloadingRequestState::Idle); +} + void CodeCache::arm_all_nmethods() { BarrierSet::barrier_set()->barrier_set_nmethod()->arm_all_nmethods(); } diff --git a/src/hotspot/share/code/codeCache.hpp b/src/hotspot/share/code/codeCache.hpp index 3b8aa5b2e58..bef114e5e19 100644 --- a/src/hotspot/share/code/codeCache.hpp +++ b/src/hotspot/share/code/codeCache.hpp @@ -107,7 +107,12 @@ class CodeCache : AllStatic { static double _last_unloading_time; static TruncatedSeq _unloading_gc_intervals; static TruncatedSeq _unloading_allocation_rates; - static volatile bool _unloading_threshold_gc_requested; + enum UnloadingRequestState : uint { + Idle, + Active, + Deferred + }; + static volatile UnloadingRequestState _unloading_threshold_gc_state; static ExceptionCache* volatile _exception_cache_purge_list; @@ -198,6 +203,9 @@ class CodeCache : AllStatic { static uint64_t previous_completed_gc_marking_cycle(); static void on_gc_marking_cycle_start(); static void on_gc_marking_cycle_finish(); + + static void defer_unloading_gc_request(); + static void clear_deferred_unloading_gc_request(); // Arm nmethods so that special actions are taken (nmethod_entry_barrier) for // on-stack nmethods. It's used in two places: // 1. Used before the start of concurrent marking so that oops inside diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp index eaa6afb5efa..9dfdb376905 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp @@ -1932,7 +1932,7 @@ static bool should_retry_vm_op(GCCause::Cause cause, // GC, so try again. LOG_COLLECT_CONCURRENTLY(cause, "retry after in-progress"); return true; - } else if (op->whitebox_attached()) { + } else if (op->whitebox_controlled()) { // 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 @@ -2000,7 +2000,7 @@ bool G1CollectedHeap::try_collect_concurrently(size_t allocation_word_size, } // When _wb_breakpoint there can't be another cycle or deferred. assert(!op.cycle_already_in_progress(), "invariant"); - assert(!op.whitebox_attached(), "invariant"); + assert(!op.whitebox_controlled(), "invariant"); // Concurrent cycle attempt might have been cancelled by some other // collection, so retry. Unlike other cases below, we want to retry // even if cancelled by a STW full collection, because we really want @@ -2025,15 +2025,21 @@ bool G1CollectedHeap::try_collect_concurrently(size_t allocation_word_size, // 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. + // Compared to other "automatic" GCs (see below), being in WhiteBox is not + // addressed here because we need to handle it specially. if (op.mark_in_progress() || (old_marking_started_before != old_marking_started_after)) { LOG_COLLECT_CONCURRENTLY_COMPLETE(cause, true); return true; } + if (op.whitebox_controlled()) { + LOG_COLLECT_CONCURRENTLY(cause, "Suppressed CodeCache GC because of WhiteBox in control."); + // The caller in this case does not check the return value, so it does not + // really matter what we return. However we did not finish the request. + return false; + } + if (wait_full_mark_finished(cause, old_marking_started_before, old_marking_started_after, @@ -2041,7 +2047,11 @@ bool G1CollectedHeap::try_collect_concurrently(size_t allocation_word_size, return true; } - if (should_retry_vm_op(cause, &op)) { + if (op.cycle_already_in_progress()) { + // If VMOp failed because a cycle was already in progress, it + // is now complete (we just waited). But it didn't finish this + // request, so try again. + LOG_COLLECT_CONCURRENTLY(cause, "retry after in-progress"); continue; } } else if (!GCCause::is_user_requested_gc(cause)) { @@ -2062,7 +2072,7 @@ bool G1CollectedHeap::try_collect_concurrently(size_t allocation_word_size, // _old_marking_cycles_started. if (op.gc_succeeded() || op.cycle_already_in_progress() || - op.whitebox_attached() || + op.whitebox_controlled() || (old_marking_started_before != old_marking_started_after)) { LOG_COLLECT_CONCURRENTLY_COMPLETE(cause, true); return true; diff --git a/src/hotspot/share/gc/g1/g1Policy.cpp b/src/hotspot/share/gc/g1/g1Policy.cpp index e2c01f9a13e..2414fdd7840 100644 --- a/src/hotspot/share/gc/g1/g1Policy.cpp +++ b/src/hotspot/share/gc/g1/g1Policy.cpp @@ -1253,9 +1253,9 @@ void G1Policy::update_survivors_policy() { } bool G1Policy::force_concurrent_start_if_outside_cycle(GCCause::Cause gc_cause) { - // We actually check whether we are marking here and not if we are in a - // reclamation phase. This means that we will schedule a concurrent mark - // even while we are still in the process of reclaiming memory. + // Check whether a concurrent cycle is active, do not include the + // reclamation/mixed phase. This means that we can schedule a concurrent cycle + // even while in the mixed phase. bool during_cycle = collector_state()->is_in_concurrent_cycle(); if (!during_cycle) { log_debug(gc, ergo)("Request concurrent cycle initiation (requested by GC cause). " diff --git a/src/hotspot/share/gc/g1/g1VMOperations.cpp b/src/hotspot/share/gc/g1/g1VMOperations.cpp index 373ec9660da..577f3b5491d 100644 --- a/src/hotspot/share/gc/g1/g1VMOperations.cpp +++ b/src/hotspot/share/gc/g1/g1VMOperations.cpp @@ -22,6 +22,7 @@ * */ +#include "code/codeCache.hpp" #include "gc/g1/g1CollectedHeap.inline.hpp" #include "gc/g1/g1CollectorState.inline.hpp" #include "gc/g1/g1ConcurrentMarkThread.inline.hpp" @@ -66,7 +67,7 @@ VM_G1TryInitiateConcMark::VM_G1TryInitiateConcMark(size_t allocation_word_size, _transient_failure(false), _mark_in_progress(false), _cycle_already_in_progress(false), - _whitebox_attached(false), + _whitebox_controlled(false), _gc_succeeded(false) {} @@ -88,19 +89,26 @@ void VM_G1TryInitiateConcMark::doit() { G1CollectorState* state = g1h->collector_state(); _mark_in_progress = state->is_in_marking(); _cycle_already_in_progress = state->is_in_concurrent_cycle(); + _whitebox_controlled = (_gc_cause != GCCause::_wb_breakpoint) && ConcurrentGCBreakpoints::is_controlled(); - if (!g1h->policy()->force_concurrent_start_if_outside_cycle(_gc_cause)) { + // Notify the code cache that we deferred clearing the unloading GC request if we are WhiteBox controlled + // and we are going to suppress it. If marking is active, we do not need to suppress because that will satisfy the + // request already. + // This needs to be atomic wrt. to all code-cache allocation threads to allow setting the request + // after WhiteBox releases control again. + bool defer_codecache_request = whitebox_controlled() && + GCCause::is_codecache_requested_gc(_gc_cause) && + !mark_in_progress(); + if (defer_codecache_request) { + CodeCache::defer_unloading_gc_request(); + return; + } 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. 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 - // start one. This check is after the force_concurrent_start_xxx so that a - // request will be remembered for a later partial collection, even though - // we've rejected this request. - _whitebox_attached = true; - } else { + return; + } else if (!whitebox_controlled()) { + // Only run a concurrent marking if not controlled by WhiteBox. g1h->do_collection_pause_at_safepoint(_word_size); _gc_succeeded = true; } diff --git a/src/hotspot/share/gc/g1/g1VMOperations.hpp b/src/hotspot/share/gc/g1/g1VMOperations.hpp index 7d56ea1916f..0c12e75eef0 100644 --- a/src/hotspot/share/gc/g1/g1VMOperations.hpp +++ b/src/hotspot/share/gc/g1/g1VMOperations.hpp @@ -48,7 +48,7 @@ class VM_G1TryInitiateConcMark : public VM_GC_Collect_Operation { bool _transient_failure; bool _mark_in_progress; bool _cycle_already_in_progress; - bool _whitebox_attached; + bool _whitebox_controlled; // The concurrent start pause may be cancelled for some reasons. Keep track of // this. bool _gc_succeeded; @@ -63,7 +63,7 @@ public: 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 whitebox_controlled() const { return _whitebox_controlled; } bool gc_succeeded() const { return _gc_succeeded && VM_GC_Operation::gc_succeeded(); } }; diff --git a/src/hotspot/share/gc/shared/concurrentGCBreakpoints.cpp b/src/hotspot/share/gc/shared/concurrentGCBreakpoints.cpp index 3a974952fea..b0a784e9282 100644 --- a/src/hotspot/share/gc/shared/concurrentGCBreakpoints.cpp +++ b/src/hotspot/share/gc/shared/concurrentGCBreakpoints.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -22,6 +22,7 @@ * */ +#include "code/codeCache.hpp" #include "gc/shared/collectedHeap.hpp" #include "gc/shared/concurrentGCBreakpoints.hpp" #include "logging/log.hpp" @@ -89,6 +90,7 @@ void ConcurrentGCBreakpoints::release_control() { MonitorLocker ml(monitor()); log_trace(gc, breakpoint)("release_control"); reset_request_state(); + CodeCache::clear_deferred_unloading_gc_request(); ml.notify_all(); } diff --git a/test/hotspot/jtreg/gc/g1/TestCodeCacheWhiteBox.java b/test/hotspot/jtreg/gc/g1/TestCodeCacheWhiteBox.java new file mode 100644 index 00000000000..c47dafc3203 --- /dev/null +++ b/test/hotspot/jtreg/gc/g1/TestCodeCacheWhiteBox.java @@ -0,0 +1,216 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package gc.g1; + +/* + * @test TestCodeCacheWhiteBox.java + * @bug 8225186 + * @summary Test to make sure that code cache unloading does not make the VM hang when receiving + * a request while WhiteBox is holding control. + * We do that by triggering a code cache gc request (by triggering compilations) during a + * synchronous compilation while whitebox is holding control, and additionally verify that + * after the concurrent cycle additional code cache gc requests start more concurrent cycles. + * @requires vm.gc.G1 + * @requires vm.flagless + * @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.TestCodeCacheWhiteBox + */ + + +import java.lang.reflect.Field; + +import java.net.URL; +import java.net.URLClassLoader; + +import jdk.test.lib.Asserts; +import jdk.test.lib.Platform; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; +import jdk.test.whitebox.WhiteBox; + +public class TestCodeCacheWhiteBox { + 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:.", + "-Xbatch", // Needed to make compilation synchronous + "-Xlog:gc=trace,codecache", + "-XX:+WhiteBoxAPI", + "-XX:ReservedCodeCacheSize=" + (Platform.is32bit() ? "4M" : "8M"), + "-XX:StartAggressiveSweepingAt=50", + "-XX:CompileCommand=compileonly,gc.g1.SomeClass::*", + "-XX:CompileCommand=compileonly,gc.g1.Foo*::*", + TestCodeCacheWhiteBoxRunner.class.getName(), + concPhase); + return output; + } + + private static void runAndCheckTest(String test) throws Exception { + OutputAnalyzer output; + + output = runTest(test); + output.shouldHaveExitValue(0); + output.shouldNotContain("ERROR"); + 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"); + } + + public static void main(String[] args) throws Exception { + runAndCheckTest(WB.BEFORE_MARKING_COMPLETED); // This one should always complete. Just for sanity checking. + runAndCheckTest(WB.G1_BEFORE_REBUILD_COMPLETED); + runAndCheckTest(WB.G1_BEFORE_CLEANUP_COMPLETED); + } +} + +class TestCodeCacheWhiteBoxRunner { + 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, TestCodeCacheWhiteBoxRunner.class.getClassLoader()); + } + } + } + + private static void triggerCodeCacheGC() { + URL url = TestCodeCacheWhiteBoxRunner.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) { + System.out.println("ERROR: threw exception " + t); + } + } + + public static void main(String[] args) throws Exception { + System.out.println("Running to breakpoint: " + args[0]); + try { + WB.concurrentGCAcquireControl(); + WB.concurrentGCRunTo(args[0]); + + System.out.println("Try to trigger code cache GC"); + + Thread toRun = new Thread(() -> + { + System.out.println("Thread is running"); + triggerCodeCacheGC(); + System.out.println("Thread completed"); + }); + toRun.setDaemon(true); // non-daemon thread could prevent VM shutdown after the main thread times out + toRun.start(); + toRun.join(60_000); + + if (toRun.isAlive()) { + toRun.interrupt(); + throw new RuntimeException("ERROR: thread took too long, deadlocked?"); + } + + WB.concurrentGCRunToIdle(); + } catch (InterruptedException e) { + System.out.println("ERROR: starting helper thread"); + throw e; + } finally { + // Make sure that the marker we use to find the expected log message is printed + // before we release whitebox control, i.e. before the expected garbage collection + // can start. + System.out.println(TestCodeCacheWhiteBox.AFTER_FIRST_CYCLE_MARKER); + WB.concurrentGCReleaseControl(); + } + 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 b9f36a121a16ccea1877d0db8b49998d2df0cb17 Mon Sep 17 00:00:00 2001 From: Tobias Hartmann Date: Mon, 6 Jul 2026 14:09:26 +0000 Subject: [PATCH 084/305] 8387638: Some compiler/vectorization/runner/* tests timed out in Driver mode Reviewed-by: chagedorn, mhaessig --- .../runner/VectorizationTestRunner.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/test/hotspot/jtreg/compiler/vectorization/runner/VectorizationTestRunner.java b/test/hotspot/jtreg/compiler/vectorization/runner/VectorizationTestRunner.java index 9adebf30d31..7e836d78849 100644 --- a/test/hotspot/jtreg/compiler/vectorization/runner/VectorizationTestRunner.java +++ b/test/hotspot/jtreg/compiler/vectorization/runner/VectorizationTestRunner.java @@ -141,17 +141,20 @@ public class VectorizationTestRunner { Object expected = null; Object actual = null; - // Temporarily disable the compiler and invoke the method to get reference - // result from the interpreter - Flags.WHITEBOX.setBooleanVMFlag("UseCompiler", false); + // Temporarily make the test method not compilable and invoke it to get the + // reference result from the interpreter. + Flags.WHITEBOX.makeMethodNotCompilable(method, CompLevel.ANY.getValue(), true); + Flags.WHITEBOX.makeMethodNotCompilable(method, CompLevel.ANY.getValue(), false); try { expected = method.invoke(this); + assert(Flags.WHITEBOX.getMethodCompilationLevel(method) == COMP_LEVEL_INTP); } catch (Exception e) { e.printStackTrace(); fail("Exception is thrown in test method invocation (interpreter)."); + } finally { + // Make the test method compilable again + Flags.WHITEBOX.clearMethodState(method); } - assert(Flags.WHITEBOX.getMethodCompilationLevel(method) == COMP_LEVEL_INTP); - Flags.WHITEBOX.setBooleanVMFlag("UseCompiler", true); // Compile the method and invoke it again long enqueueTime = System.currentTimeMillis(); From 01a9a4021848aabc4bb68600a89a380a76a33ff1 Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Mon, 6 Jul 2026 15:44:00 +0000 Subject: [PATCH 085/305] 8387721: C2: Print Node barrier data Reviewed-by: amitkumar, chagedorn --- src/hotspot/share/opto/memnode.cpp | 30 ++++++++++++++++++++++++++---- src/hotspot/share/opto/memnode.hpp | 4 ++++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/src/hotspot/share/opto/memnode.cpp b/src/hotspot/share/opto/memnode.cpp index 4f68ff281a0..00ccb3e3dbc 100644 --- a/src/hotspot/share/opto/memnode.cpp +++ b/src/hotspot/share/opto/memnode.cpp @@ -86,12 +86,13 @@ bool MemNode::check_if_adr_maybe_raw(Node* adr) { #ifndef PRODUCT void MemNode::dump_spec(outputStream *st) const { - if (in(Address) == nullptr) return; // node is dead + if (in(Address) == nullptr) { + // node is dead + return; + } #ifndef ASSERT // fake the missing field - const TypePtr* _adr_type = nullptr; - if (in(Address) != nullptr) - _adr_type = in(Address)->bottom_type()->isa_ptr(); + const TypePtr* _adr_type = in(Address)->bottom_type()->isa_ptr(); #endif dump_adr_type(_adr_type, st); @@ -108,6 +109,7 @@ void MemNode::dump_spec(outputStream *st) const { if (_unsafe_access) { st->print(" unsafe"); } + st->print(" barrier(0x%x)", _barrier_data); } void MemNode::dump_adr_type(const TypePtr* adr_type, outputStream* st) { @@ -4145,6 +4147,26 @@ MemBarNode* LoadStoreNode::trailing_membar() const { uint LoadStoreNode::size_of() const { return sizeof(*this); } +#ifndef PRODUCT +void LoadStoreNode::dump_spec(outputStream* st) const { + if (in(MemNode::Address) == nullptr) { + // node is dead + return; + } +#ifndef ASSERT + // fake the missing field + const TypePtr* _adr_type = in(MemNode::Address)->bottom_type()->isa_ptr(); +#endif + MemNode::dump_adr_type(_adr_type, st); + + Compile* C = Compile::current(); + if (C->alias_type(_adr_type)->is_volatile()) { + st->print(" Volatile!"); + } + st->print(" barrier(0x%x)", _barrier_data); +} +#endif + //============================================================================= //----------------------------------LoadStoreConditionalNode-------------------- LoadStoreConditionalNode::LoadStoreConditionalNode( Node *c, Node *mem, Node *adr, Node *val, Node *ex ) : LoadStoreNode(c, mem, adr, val, nullptr, TypeInt::BOOL, 5) { diff --git a/src/hotspot/share/opto/memnode.hpp b/src/hotspot/share/opto/memnode.hpp index f3f65608972..77252520324 100644 --- a/src/hotspot/share/opto/memnode.hpp +++ b/src/hotspot/share/opto/memnode.hpp @@ -880,6 +880,10 @@ public: uint8_t barrier_data() { return _barrier_data; } void set_barrier_data(uint8_t barrier_data) { _barrier_data = barrier_data; } +#ifndef PRODUCT + virtual void dump_spec(outputStream *st) const; +#endif + private: virtual bool depends_only_on_test_impl() const { return false; } }; From 223b80b6d6566c9a543c8cb1752393addb589923 Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Mon, 6 Jul 2026 20:38:46 +0000 Subject: [PATCH 086/305] 8387713: Shenandoah: Rework native card table barrier Reviewed-by: wkemper, kdnilsen --- .../gc/shenandoah/shenandoahBarrierSet.hpp | 2 +- .../shenandoahBarrierSet.inline.hpp | 31 +++++++++++-------- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.hpp b/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.hpp index 8989c5f2028..51b355e7042 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.hpp @@ -119,7 +119,7 @@ public: inline oop oop_xchg(DecoratorSet decorators, T* addr, oop new_value); template - void write_ref_field_post(T* field); + void write_ref_field_post(T* field, oop new_value); void write_ref_array(HeapWord* start, size_t count); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.inline.hpp index e8eb4ee4180..f4b859afc44 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.inline.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.inline.hpp @@ -208,23 +208,28 @@ inline void ShenandoahBarrierSet::keep_alive_if_weak(DecoratorSet decorators, oo } template -inline void ShenandoahBarrierSet::write_ref_field_post(T* field) { +inline void ShenandoahBarrierSet::write_ref_field_post(T* field, oop new_value) { assert(ShenandoahCardBarrier, "Should have been checked by caller"); + + if (new_value == nullptr) { + // Null reference stores do not require card mark. + return; + } + if (_heap->is_in_young(field)) { // Young field stores do not require card mark. return; } - T heap_oop = RawAccess<>::oop_load(field); - if (CompressedOops::is_null(heap_oop)) { - // Null reference store do not require card mark. - return; - } - oop obj = CompressedOops::decode_not_null(heap_oop); - if (!_heap->is_in_young(obj)) { + + if (!_heap->is_in_young(new_value)) { // Not an old->young reference store. return; } + volatile CardTable::CardValue* byte = card_table()->byte_for(field); + if (UseCondCardMark && (*byte == CardTable::dirty_card_val())) { + return; + } *byte = CardTable::dirty_card_val(); } @@ -321,7 +326,7 @@ inline void ShenandoahBarrierSet::AccessBarrier::oop_st oop_store_common(addr, value); if (ShenandoahCardBarrier) { ShenandoahBarrierSet* bs = ShenandoahBarrierSet::barrier_set(); - bs->write_ref_field_post(addr); + bs->write_ref_field_post(addr, value); } } @@ -347,7 +352,7 @@ inline oop ShenandoahBarrierSet::AccessBarrier::oop_ato ShenandoahBarrierSet* bs = ShenandoahBarrierSet::barrier_set(); oop result = bs->oop_cmpxchg(decorators, addr, compare_value, new_value); if (ShenandoahCardBarrier) { - bs->write_ref_field_post(addr); + bs->write_ref_field_post(addr, new_value); } return result; } @@ -371,7 +376,7 @@ inline oop ShenandoahBarrierSet::AccessBarrier::oop_ato auto addr = AccessInternal::oop_field_addr(base, offset); oop result = bs->oop_cmpxchg(resolved_decorators, addr, compare_value, new_value); if (ShenandoahCardBarrier) { - bs->write_ref_field_post(addr); + bs->write_ref_field_post(addr, new_value); } return result; } @@ -393,7 +398,7 @@ inline oop ShenandoahBarrierSet::AccessBarrier::oop_ato ShenandoahBarrierSet* bs = ShenandoahBarrierSet::barrier_set(); oop result = bs->oop_xchg(decorators, addr, new_value); if (ShenandoahCardBarrier) { - bs->write_ref_field_post(addr); + bs->write_ref_field_post(addr, new_value); } return result; } @@ -417,7 +422,7 @@ inline oop ShenandoahBarrierSet::AccessBarrier::oop_ato auto addr = AccessInternal::oop_field_addr(base, offset); oop result = bs->oop_xchg(resolved_decorators, addr, new_value); if (ShenandoahCardBarrier) { - bs->write_ref_field_post(addr); + bs->write_ref_field_post(addr, new_value); } return result; } From 7be9cd96741084c983eacaff555bc554affb913a Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Tue, 7 Jul 2026 07:22:41 +0000 Subject: [PATCH 087/305] 8387403: BUILD_LIBJAVA remove special warning settings Reviewed-by: djelinski, naoto, jlu, lucy --- make/modules/java.base/lib/CoreLibraries.gmk | 2 -- .../unix/native/libjava/TimeZone_md.c | 19 ++++--------------- 2 files changed, 4 insertions(+), 17 deletions(-) diff --git a/make/modules/java.base/lib/CoreLibraries.gmk b/make/modules/java.base/lib/CoreLibraries.gmk index 8e3891a344c..87a4460a972 100644 --- a/make/modules/java.base/lib/CoreLibraries.gmk +++ b/make/modules/java.base/lib/CoreLibraries.gmk @@ -57,8 +57,6 @@ $(eval $(call SetupJdkLibrary, BUILD_LIBJAVA, \ ProcessImpl_md.c_CFLAGS := $(VERSION_CFLAGS), \ java_props_md.c_CFLAGS := \ -DARCHPROPNAME='"$(OPENJDK_TARGET_CPU_OSARCH)"', \ - DISABLED_WARNINGS_gcc_ProcessImpl_md.c := unused-result, \ - DISABLED_WARNINGS_clang_TimeZone_md.c := unused-variable, \ JDK_LIBS := libjvm, \ LIBS_linux := $(LIBDL), \ LIBS_aix := $(LIBDL) $(LIBM), \ diff --git a/src/java.base/unix/native/libjava/TimeZone_md.c b/src/java.base/unix/native/libjava/TimeZone_md.c index bc2ed500d60..709617333d9 100644 --- a/src/java.base/unix/native/libjava/TimeZone_md.c +++ b/src/java.base/unix/native/libjava/TimeZone_md.c @@ -41,22 +41,11 @@ #include "TimeZone_md.h" #include "path_util.h" -#define fileopen fopen -#define filegets fgets -#define fileclose fclose - -#if defined(__linux__) || defined(_ALLBSD_SOURCE) +#if defined(__linux__) || defined(MACOSX) static const char *ZONEINFO_DIR = "/usr/share/zoneinfo"; static const char *DEFAULT_ZONEINFO_FILE = "/etc/localtime"; -#else -static const char *SYS_INIT_FILE = "/etc/default/init"; -static const char *ZONEINFO_DIR = "/usr/share/lib/zoneinfo"; -static const char *DEFAULT_ZONEINFO_FILE = "/usr/share/lib/zoneinfo/localtime"; -#endif /* defined(__linux__) || defined(_ALLBSD_SOURCE) */ - static const char popularZones[][4] = {"UTC", "GMT"}; -#if defined(__linux__) || defined(MACOSX) static char *isFileIdentical(char* buf, size_t size, char *pathname); /* @@ -121,7 +110,7 @@ getPathName(const char *dir, const char *name) { /* * Scans the specified directory and its subdirectories to find a * zoneinfo file which has the same content as /etc/localtime on Linux - * or /usr/share/lib/zoneinfo/localtime on Solaris given in 'buf'. + * given in 'buf'. * If file is symbolic link, then the contents it points to are in buf. * Returns a zone ID if found, otherwise, NULL is returned. */ @@ -475,7 +464,7 @@ tzerr: return javatz; } -#endif /* defined(_AIX) */ +#endif /* defined(__linux__) || defined(MACOSX) || defined(_AIX) */ /* * findJavaTZ_md() maps platform time zone ID to Java time zone ID @@ -542,7 +531,6 @@ char * getGMTOffsetID() { char buf[32]; - char offset[6]; struct tm localtm; time_t clock = time(NULL); if (localtime_r(&clock, &localtm) == NULL) { @@ -576,6 +564,7 @@ getGMTOffsetID() snprintf(buf, sizeof(buf), (const char *)"GMT%c%02.2d:%02.2d", gmt_off < 0 ? '-' : '+' , abs(gmt_off / 60), gmt_off % 60); #else + char offset[6]; if (strftime(offset, 6, "%z", &localtm) != 5) { return strdup("GMT"); } From 5a7905643b7e61bcc4c99341a31a141638160117 Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Tue, 7 Jul 2026 07:27:18 +0000 Subject: [PATCH 088/305] 8386592: Gtest os.trim_native_heap_vm sometimes fails in subtest os.trim_native_heap_vm Reviewed-by: clanger, stuefe --- test/hotspot/gtest/runtime/test_os.cpp | 31 +++++++++++++++++--------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/test/hotspot/gtest/runtime/test_os.cpp b/test/hotspot/gtest/runtime/test_os.cpp index cd0f0b9aa53..72e1080f099 100644 --- a/test/hotspot/gtest/runtime/test_os.cpp +++ b/test/hotspot/gtest/runtime/test_os.cpp @@ -1062,17 +1062,26 @@ TEST_VM(os, is_first_C_frame) { TEST_VM(os, trim_native_heap) { EXPECT_TRUE(os::can_trim_native_heap()); os::size_change_t sc; - sc.before = sc.after = (size_t)-1; - EXPECT_TRUE(os::trim_native_heap(&sc)); - tty->print_cr("%zu->%zu", sc.before, sc.after); - // Regardless of whether we freed memory, both before and after - // should be somewhat believable numbers (RSS). - const size_t min = 5 * M; - const size_t max = LP64_ONLY(20 * G) NOT_LP64(3 * G); - ASSERT_LE(min, sc.before); - ASSERT_GT(max, sc.before); - ASSERT_LE(min, sc.after); - ASSERT_GT(max, sc.after); + os::Linux::accurate_meminfo_t info1; + os::Linux::accurate_meminfo_t info2; + bool have_info1 = os::Linux::query_accurate_process_memory_info(&info1); + EXPECT_TRUE(os::trim_native_heap(nullptr)); + bool have_info2 = os::Linux::query_accurate_process_memory_info(&info2); + + if (have_info1 && have_info2) { + sc.before = (info1.rss + info1.swap) * K; + sc.after = (info2.rss + info2.swap) * K; + tty->print_cr("%zu->%zu", sc.before, sc.after); + + // Regardless of whether we freed memory, both before and after + // should be somewhat believable numbers (RSS). + const size_t min = 5 * M; + const size_t max = LP64_ONLY(20 * G) NOT_LP64(3 * G); + ASSERT_LE(min, sc.before); + ASSERT_GT(max, sc.before); + ASSERT_LE(min, sc.after); + ASSERT_GT(max, sc.after); + } // Should also work EXPECT_TRUE(os::trim_native_heap()); } From cb45fb887af0e6116db91bd2a5725efb02ae4780 Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Tue, 7 Jul 2026 07:29:39 +0000 Subject: [PATCH 089/305] 8387697: Avoid using os::Linux::query_accurate_process_memory_info in JFR Reviewed-by: mgronlun, stuefe --- src/hotspot/os/linux/os_linux.cpp | 35 +++++-------------------------- 1 file changed, 5 insertions(+), 30 deletions(-) diff --git a/src/hotspot/os/linux/os_linux.cpp b/src/hotspot/os/linux/os_linux.cpp index aa5a9b9d937..ad1f384fa32 100644 --- a/src/hotspot/os/linux/os_linux.cpp +++ b/src/hotspot/os/linux/os_linux.cpp @@ -2860,39 +2860,14 @@ void os::pd_print_cpu_info(outputStream* st, char* buf, size_t buflen) { #if INCLUDE_JFR -// hwm (high water mark) in K for the VM RSS -static long jfr_rss_hwm_k = -1; - -static void send_resident_set_size_event(ssize_t size, ssize_t peak) { - EventResidentSetSize event; - event.set_size(size * K); - event.set_peak(peak * K); - event.commit(); -} - void os::jfr_report_memory_info() { - os::Linux::accurate_meminfo_t accurate_info; - if (os::Linux::query_accurate_process_memory_info(&accurate_info) && accurate_info.rss != -1) { - // unfortunately the smaps_rollup/accurate_info contains no hwm (high water mark) for RSS - struct rusage ru; - if (getrusage(RUSAGE_SELF, &ru) == 0) { - if (ru.ru_maxrss > jfr_rss_hwm_k) { - jfr_rss_hwm_k = ru.ru_maxrss; - } - } - - // do not allow larger current RSS than hwm - if (accurate_info.rss > jfr_rss_hwm_k) { - jfr_rss_hwm_k = accurate_info.rss; - } - - send_resident_set_size_event(accurate_info.rss, jfr_rss_hwm_k); - return; - } - os::Linux::meminfo_t info; if (os::Linux::query_process_memory_info(&info)) { - send_resident_set_size_event(info.vmrss, info.vmhwm); + // Send the RSS JFR event + EventResidentSetSize event; + event.set_size(info.vmrss * K); + event.set_peak(info.vmhwm * K); + event.commit(); } else { // Log a warning static bool first_warning = true; From 63294ee8ba61fb58e8bf4be1eb0d46631d9ff270 Mon Sep 17 00:00:00 2001 From: Thomas Stuefe Date: Tue, 7 Jul 2026 09:39:32 +0000 Subject: [PATCH 090/305] 8387758: Oop verification wrong in StubGenerator::generate_generic_copy Reviewed-by: shade, galder --- src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp | 3 ++- src/hotspot/cpu/riscv/stubGenerator_riscv.cpp | 3 ++- src/hotspot/cpu/x86/stubGenerator_x86_64_arraycopy.cpp | 5 +++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp b/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp index f6ed5c2862a..5dfd41293fd 100644 --- a/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp @@ -2568,7 +2568,7 @@ class StubGenerator: public StubCodeGenerator { __ movw(scratch_length, length); // length (elements count, 32-bits value) __ tbnz(scratch_length, 31, L_failed); // i.e. sign bit set - __ load_klass(scratch_src_klass, src); + __ load_narrow_klass(scratch_src_klass, src); #ifdef ASSERT // assert(src->klass() != nullptr); { @@ -2583,6 +2583,7 @@ class StubGenerator: public StubCodeGenerator { BLOCK_COMMENT("} assert klasses not null done"); } #endif + __ decode_klass_not_null(scratch_src_klass, scratch_src_klass); // Load layout helper (32-bits) // diff --git a/src/hotspot/cpu/riscv/stubGenerator_riscv.cpp b/src/hotspot/cpu/riscv/stubGenerator_riscv.cpp index 82e5a49faf0..06cf67e2486 100644 --- a/src/hotspot/cpu/riscv/stubGenerator_riscv.cpp +++ b/src/hotspot/cpu/riscv/stubGenerator_riscv.cpp @@ -1889,7 +1889,7 @@ class StubGenerator: public StubCodeGenerator { __ sext(scratch_length, length, 32); // length (elements count, 32-bits value) __ bltz(scratch_length, L_failed); - __ load_klass(scratch_src_klass, src); + __ load_narrow_klass(scratch_src_klass, src); #ifdef ASSERT { BLOCK_COMMENT("assert klasses not null {"); @@ -1903,6 +1903,7 @@ class StubGenerator: public StubCodeGenerator { BLOCK_COMMENT("} assert klasses not null done"); } #endif + __ decode_klass_not_null(scratch_src_klass, t0); // Load layout helper (32-bits) // diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64_arraycopy.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64_arraycopy.cpp index a45340b8800..cececa7b3ad 100644 --- a/src/hotspot/cpu/x86/stubGenerator_x86_64_arraycopy.cpp +++ b/src/hotspot/cpu/x86/stubGenerator_x86_64_arraycopy.cpp @@ -3560,13 +3560,13 @@ address StubGenerator::generate_generic_copy(address byte_copy_entry, address sh __ testl(r11_length, r11_length); __ jccb(Assembler::negative, L_failed_0); - __ load_klass(r10_src_klass, src, rklass_tmp); + __ load_narrow_klass(r10_src_klass, src); #ifdef ASSERT // assert(src->klass() != nullptr); { BLOCK_COMMENT("assert klasses not null {"); Label L1, L2; - __ testptr(r10_src_klass, r10_src_klass); + __ testl(r10_src_klass, r10_src_klass); __ jcc(Assembler::notZero, L2); // it is broken if klass is null __ bind(L1); __ stop("broken null klass"); @@ -3577,6 +3577,7 @@ address StubGenerator::generate_generic_copy(address byte_copy_entry, address sh BLOCK_COMMENT("} assert klasses not null done"); } #endif + __ decode_klass_not_null(r10_src_klass, rklass_tmp); // Load layout helper (32-bits) // From ed81db1fe22c8eed5c46cd03dec4aa802f880fcf Mon Sep 17 00:00:00 2001 From: Dingli Zhang Date: Tue, 7 Jul 2026 09:42:08 +0000 Subject: [PATCH 091/305] 8387747: Enable long vector multiply IR tests for RISC-V Reviewed-by: fyang, gcao --- .../TestVectorMulLongToSignedUnsignedInt.java | 22 ++++++++--------- .../compiler/vectorapi/VectorMultiplyOpt.java | 24 ++++++++++++------- 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/test/hotspot/jtreg/compiler/vectorapi/TestVectorMulLongToSignedUnsignedInt.java b/test/hotspot/jtreg/compiler/vectorapi/TestVectorMulLongToSignedUnsignedInt.java index e7745b5e88c..cb58cfca652 100644 --- a/test/hotspot/jtreg/compiler/vectorapi/TestVectorMulLongToSignedUnsignedInt.java +++ b/test/hotspot/jtreg/compiler/vectorapi/TestVectorMulLongToSignedUnsignedInt.java @@ -89,7 +89,7 @@ public class TestVectorMulLongToSignedUnsignedInt { @Test @IR(counts = {IRNode.AND_VL, " >0 ", IRNode.MUL_VL, " >0 "}, - applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}) + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true", "rvv", "true"}) @IR(failOn = {IRNode.X86_VMULUDQ_REG}, phase = CompilePhase.MATCHING, applyIfCPUFeature = {"avx", "true"}) @@ -119,7 +119,7 @@ public class TestVectorMulLongToSignedUnsignedInt { @Test @IR(counts = {IRNode.AND_VL, " >0 ", IRNode.MUL_VL, " >0 "}, - applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}) + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true", "rvv", "true"}) @IR(failOn = {IRNode.X86_VMULUDQ_REG}, phase = CompilePhase.MATCHING, applyIfCPUFeature = {"avx", "true"}) @@ -147,7 +147,7 @@ public class TestVectorMulLongToSignedUnsignedInt { @Test @IR(counts = {IRNode.AND_VL, " >0 ", IRNode.MUL_VL, " >0 "}, - applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}) + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true", "rvv", "true"}) @IR(failOn = {IRNode.X86_VMULUDQ_REG}, phase = CompilePhase.MATCHING, applyIfCPUFeature = {"avx", "true"}) @@ -176,7 +176,7 @@ public class TestVectorMulLongToSignedUnsignedInt { // Case 5: Mask = 0xFFFF_FFFFL (exactly uint max, boundary valid case). @Test @IR(counts = {IRNode.MUL_VL, " >0 "}, - applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}) + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true", "rvv", "true"}) @IR(counts = {IRNode.X86_VMULUDQ_REG, " >0 "}, phase = CompilePhase.MATCHING, applyIfCPUFeature = {"avx", "true"}) @@ -205,7 +205,7 @@ public class TestVectorMulLongToSignedUnsignedInt { // Case 6: Small mask (0xFFFFL), clearly fits in uint. @Test @IR(counts = {IRNode.MUL_VL, " >0 "}, - applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}) + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true", "rvv", "true"}) @IR(counts = {IRNode.X86_VMULUDQ_REG, " >0 "}, phase = CompilePhase.MATCHING, applyIfCPUFeature = {"avx", "true"}) @@ -235,7 +235,7 @@ public class TestVectorMulLongToSignedUnsignedInt { @Test @IR(counts = {IRNode.MUL_VL, " >0 ", IRNode.URSHIFT_VL, " >0 "}, - applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}) + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true", "rvv", "true"}) @IR(counts = {IRNode.X86_VMULUDQ_REG, " >0 "}, phase = CompilePhase.MATCHING, applyIfCPUFeature = {"avx", "true"}) @@ -265,7 +265,7 @@ public class TestVectorMulLongToSignedUnsignedInt { @Test @IR(counts = {IRNode.AND_VL, " >0 ", IRNode.MUL_VL, " >0 "}, - applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}) + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true", "rvv", "true"}) @IR(failOn = {IRNode.X86_VMULUDQ_REG}, phase = CompilePhase.MATCHING, applyIfCPUFeature = {"avx", "true"}) @@ -297,7 +297,7 @@ public class TestVectorMulLongToSignedUnsignedInt { @IR(counts = {IRNode.URSHIFT_VL, " >0 ", IRNode.AND_VL, " >0 ", IRNode.MUL_VL, " >0 "}, - applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}) + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true", "rvv", "true"}) @IR(failOn = {IRNode.X86_VMULUDQ_REG}, phase = CompilePhase.MATCHING, applyIfCPUFeature = {"avx", "true"}) @@ -327,7 +327,7 @@ public class TestVectorMulLongToSignedUnsignedInt { @Test @IR(counts = {IRNode.AND_VL, " >0 ", IRNode.MUL_VL, " >0 "}, - applyIfCPUFeatureOr = {"avx512f", "true", "sve", "true"}) + applyIfCPUFeatureOr = {"avx512f", "true", "sve", "true", "rvv", "true"}) @IR(failOn = {IRNode.X86_VMULUDQ_REG}, phase = CompilePhase.MATCHING, applyIfCPUFeature = {"avx512f", "true"}) @@ -359,7 +359,7 @@ public class TestVectorMulLongToSignedUnsignedInt { @Test @IR(counts = {IRNode.URSHIFT_VL, " >0 ", IRNode.MUL_VL, " >0 "}, - applyIfCPUFeatureOr = {"avx512f", "true", "sve", "true"}) + applyIfCPUFeatureOr = {"avx512f", "true", "sve", "true", "rvv", "true"}) @IR(failOn = {IRNode.X86_VMULUDQ_REG}, phase = CompilePhase.MATCHING, applyIfCPUFeature = {"avx512f", "true"}) @@ -391,7 +391,7 @@ public class TestVectorMulLongToSignedUnsignedInt { @Test @IR(counts = {IRNode.RSHIFT_VL, " >0 ", IRNode.MUL_VL, " >0 "}, - applyIfCPUFeatureOr = {"avx512f", "true", "sve", "true"}) + applyIfCPUFeatureOr = {"avx512f", "true", "sve", "true", "rvv", "true"}) @IR(failOn = {IRNode.X86_VMULDQ_REG}, phase = CompilePhase.MATCHING, applyIfCPUFeature = {"avx512f", "true"}) diff --git a/test/hotspot/jtreg/compiler/vectorapi/VectorMultiplyOpt.java b/test/hotspot/jtreg/compiler/vectorapi/VectorMultiplyOpt.java index 68ac9249ebf..4d8344e729e 100644 --- a/test/hotspot/jtreg/compiler/vectorapi/VectorMultiplyOpt.java +++ b/test/hotspot/jtreg/compiler/vectorapi/VectorMultiplyOpt.java @@ -107,7 +107,8 @@ public class VectorMultiplyOpt { } @Test - @IR(counts = {IRNode.MUL_VL, " >0 ", IRNode.AND_VL, " >0 "}, applyIfCPUFeature = {"avx", "true"}) + @IR(counts = {IRNode.MUL_VL, " >0 ", IRNode.AND_VL, " >0 "}, + applyIfCPUFeatureOr = {"avx", "true", "rvv", "true"}) @IR(counts = {"vmuludq", " >0 "}, phase = CompilePhase.FINAL_CODE, applyIfCPUFeature = {"avx", "true"}) @IR(counts = {"vmulL_uint_sve2", " >0 "}, phase = CompilePhase.FINAL_CODE, applyIfCPUFeature = {"sve2", "true"}) @@ -135,7 +136,8 @@ public class VectorMultiplyOpt { } @Test - @IR(counts = {IRNode.MUL_VL, " >0 ", IRNode.AND_VL, " >0 ", IRNode.URSHIFT_VL, " >0 "}, applyIfCPUFeature = {"avx", "true"}) + @IR(counts = {IRNode.MUL_VL, " >0 ", IRNode.AND_VL, " >0 ", IRNode.URSHIFT_VL, " >0 "}, + applyIfCPUFeatureOr = {"avx", "true", "rvv", "true"}) @IR(counts = {"vmuludq", " >0 "}, phase = CompilePhase.FINAL_CODE, applyIfCPUFeature = {"avx", "true"}) @IR(counts = {"vmulL_uint_sve2", " >0 "}, phase = CompilePhase.FINAL_CODE, applyIfCPUFeature = {"sve2", "true"}) @@ -163,7 +165,8 @@ public class VectorMultiplyOpt { } @Test - @IR(counts = {IRNode.MUL_VL, " >0 ", IRNode.URSHIFT_VL, " >0 "}, applyIfCPUFeature = {"avx", "true"}) + @IR(counts = {IRNode.MUL_VL, " >0 ", IRNode.URSHIFT_VL, " >0 "}, + applyIfCPUFeatureOr = {"avx", "true", "rvv", "true"}) @IR(counts = {"vmuludq", " >0 "}, phase = CompilePhase.FINAL_CODE, applyIfCPUFeature = {"avx", "true"}) @IR(counts = {"vmulL_uint_sve2", " >0 "}, phase = CompilePhase.FINAL_CODE, applyIfCPUFeature = {"sve2", "true"}) @@ -191,7 +194,8 @@ public class VectorMultiplyOpt { } @Test - @IR(counts = {IRNode.MUL_VL, " >0 ", IRNode.URSHIFT_VL, " >0 "}, applyIfCPUFeature = {"avx", "true"}) + @IR(counts = {IRNode.MUL_VL, " >0 ", IRNode.URSHIFT_VL, " >0 "}, + applyIfCPUFeatureOr = {"avx", "true", "rvv", "true"}) @IR(counts = {"vmuludq", " >0 "}, applyIfCPUFeature = {"avx", "true"}, phase = CompilePhase.FINAL_CODE) @IR(counts = {"vmulL_uint_sve2", " >0 "}, phase = CompilePhase.FINAL_CODE, applyIfCPUFeature = {"sve2", "true"}) @@ -219,7 +223,8 @@ public class VectorMultiplyOpt { } @Test - @IR(counts = {IRNode.MUL_VL, " >0 ", IRNode.VECTOR_CAST_I2L, " >0 "}, applyIfCPUFeature = {"avx", "true"}) + @IR(counts = {IRNode.MUL_VL, " >0 ", IRNode.VECTOR_CAST_I2L, " >0 "}, + applyIfCPUFeatureOr = {"avx", "true", "rvv", "true"}) @IR(counts = {"vmuldq", " >0 "}, applyIfCPUFeature = {"avx", "true"}, phase = CompilePhase.FINAL_CODE) @IR(counts = {"vmulL_int_sve2", " >0 "}, phase = CompilePhase.FINAL_CODE, applyIfCPUFeature = {"sve2", "true"}) @@ -250,7 +255,8 @@ public class VectorMultiplyOpt { @Test - @IR(counts = {IRNode.MUL_VL, " >0 ", IRNode.RSHIFT_VL, " >0 "}, applyIfCPUFeature = {"avx", "true"}) + @IR(counts = {IRNode.MUL_VL, " >0 ", IRNode.RSHIFT_VL, " >0 "}, + applyIfCPUFeatureOr = {"avx", "true", "rvv", "true"}) @IR(counts = {"vmuldq", " >0 "}, applyIfCPUFeature = {"avx", "true"}, phase = CompilePhase.FINAL_CODE) @IR(counts = {"vmulL_int_sve2", " >0 "}, phase = CompilePhase.FINAL_CODE, applyIfCPUFeature = {"sve2", "true"}) @@ -280,7 +286,8 @@ public class VectorMultiplyOpt { // Same-operand multiplication (v * v) where v has zero-extended high bits. // On NEON this should map to the dedicated rule that emits a single xtn. @Test - @IR(counts = {IRNode.MUL_VL, " >0 ", IRNode.AND_VL, " >0 "}, applyIfCPUFeature = {"avx", "true"}) + @IR(counts = {IRNode.MUL_VL, " >0 ", IRNode.AND_VL, " >0 "}, + applyIfCPUFeatureOr = {"avx", "true", "rvv", "true"}) @IR(counts = {"vmuludq", " >0 "}, phase = CompilePhase.FINAL_CODE, applyIfCPUFeature = {"avx", "true"}) @IR(counts = {"vmulL_uint_sve2", " >0 "}, phase = CompilePhase.FINAL_CODE, applyIfCPUFeature = {"sve2", "true"}) @@ -309,7 +316,8 @@ public class VectorMultiplyOpt { // Same-operand multiplication (v * v) where v has sign-extended high bits. // On NEON this should map to the dedicated rule that emits a single xtn. @Test - @IR(counts = {IRNode.MUL_VL, " >0 ", IRNode.VECTOR_CAST_I2L, " >0 "}, applyIfCPUFeature = {"avx", "true"}) + @IR(counts = {IRNode.MUL_VL, " >0 ", IRNode.VECTOR_CAST_I2L, " >0 "}, + applyIfCPUFeatureOr = {"avx", "true", "rvv", "true"}) @IR(counts = {"vmuldq", " >0 "}, applyIfCPUFeature = {"avx", "true"}, phase = CompilePhase.FINAL_CODE) @IR(counts = {"vmulL_int_sve2", " >0 "}, phase = CompilePhase.FINAL_CODE, applyIfCPUFeature = {"sve2", "true"}) From 432f005b87211b402bb9299fb123379543988168 Mon Sep 17 00:00:00 2001 From: Coleen Phillimore Date: Tue, 7 Jul 2026 12:22:01 +0000 Subject: [PATCH 092/305] 8387757: Man pages still say CompactObjectHeaders is not default Reviewed-by: stuefe, lfoltan, shade, rkennke, dholmes --- src/java.base/share/man/java.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/java.base/share/man/java.md b/src/java.base/share/man/java.md index 30f018314e5..89166ae39e1 100644 --- a/src/java.base/share/man/java.md +++ b/src/java.base/share/man/java.md @@ -1568,14 +1568,14 @@ These `java` options control the runtime behavior of the Java HotSpot VM. This option is similar to `-Xss`. -[`-XX:+UseCompactObjectHeaders`]{#-XX__UseCompactObjectHeaders} -: Enables compact object headers. By default, this option is disabled. - Enabling this option reduces memory footprint in the Java heap by - 4 bytes per object (on average) and often improves performance. +[`-XX:-UseCompactObjectHeaders`]{#-XX__UseCompactObjectHeaders} +: Disables compact object headers. By default, this option is enabled and + compact object headers are used. Using compact object headers reduces + memory footprint in the Java heap by 4 bytes per object (on average) and + often improves performance. - The feature remains disabled by default while it continues to be evaluated. - In a future release it is expected to be enabled by default, and - eventually will be the only mode of operation. + This option can be used if performance regressions are suspected. In a future + release compact object headers is expected to become the only mode of operation. [`-XX:-UseCompressedOops`]{#-XX__UseCompressedOops} : Disables the use of compressed pointers. By default, this option is From c8d4c7d1815fe44cecaceeec497ecb758aabebf7 Mon Sep 17 00:00:00 2001 From: Thomas Schatzl Date: Tue, 7 Jul 2026 12:53:43 +0000 Subject: [PATCH 093/305] 8387760: G1: Let G1ConcurrentMark::_chunks_in_chunk_list use the Atomic API Reviewed-by: shade, aboldtch --- src/hotspot/share/gc/g1/g1ConcurrentMark.cpp | 6 +++--- src/hotspot/share/gc/g1/g1ConcurrentMark.hpp | 6 +++--- src/hotspot/share/gc/g1/g1ConcurrentMark.inline.hpp | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp b/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp index 6f9e4e2e9cf..233901c30f8 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp @@ -299,7 +299,7 @@ void G1CMMarkStack::add_chunk_to_list(Atomic* list, TaskQu void G1CMMarkStack::add_chunk_to_chunk_list(TaskQueueEntryChunk* elem) { MutexLocker x(G1MarkStackChunkList_lock, Mutex::_no_safepoint_check_flag); add_chunk_to_list(&_chunk_list, elem); - _chunks_in_chunk_list++; + _chunks_in_chunk_list.add_then_fetch(1u, memory_order_relaxed); } void G1CMMarkStack::add_chunk_to_free_list(TaskQueueEntryChunk* elem) { @@ -319,7 +319,7 @@ G1CMMarkStack::TaskQueueEntryChunk* G1CMMarkStack::remove_chunk_from_chunk_list( MutexLocker x(G1MarkStackChunkList_lock, Mutex::_no_safepoint_check_flag); TaskQueueEntryChunk* result = remove_chunk_from_list(&_chunk_list); if (result != nullptr) { - _chunks_in_chunk_list--; + _chunks_in_chunk_list.sub_then_fetch(1u, memory_order_relaxed); } return result; } @@ -363,7 +363,7 @@ bool G1CMMarkStack::par_pop_chunk(G1TaskQueueEntry* ptr_arr) { } void G1CMMarkStack::set_empty() { - _chunks_in_chunk_list = 0; + _chunks_in_chunk_list.store_relaxed(0); _chunk_list.store_relaxed(nullptr); _free_list.store_relaxed(nullptr); _chunk_allocator.reset(); diff --git a/src/hotspot/share/gc/g1/g1ConcurrentMark.hpp b/src/hotspot/share/gc/g1/g1ConcurrentMark.hpp index 73dabc12863..f1f84bf246e 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentMark.hpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentMark.hpp @@ -213,8 +213,8 @@ private: Atomic _free_list; // Linked list of free chunks that can be allocated by users. char _pad1[DEFAULT_PADDING_SIZE - sizeof(TaskQueueEntryChunk*)]; Atomic _chunk_list; // List of chunks currently containing data. - volatile size_t _chunks_in_chunk_list; - char _pad2[DEFAULT_PADDING_SIZE - sizeof(TaskQueueEntryChunk*) - sizeof(size_t)]; + Atomic _chunks_in_chunk_list; + char _pad2[DEFAULT_PADDING_SIZE - sizeof(TaskQueueEntryChunk*) - sizeof(_chunks_in_chunk_list)]; // Atomically add the given chunk to the list. void add_chunk_to_list(Atomic* list, TaskQueueEntryChunk* elem); @@ -265,7 +265,7 @@ private: // Return the approximate number of oops on this mark stack. Racy due to // unsynchronized access to _chunks_in_chunk_list. - size_t size() const { return _chunks_in_chunk_list * EntriesPerChunk; } + size_t size() const { return _chunks_in_chunk_list.load_relaxed() * EntriesPerChunk; } void set_empty(); diff --git a/src/hotspot/share/gc/g1/g1ConcurrentMark.inline.hpp b/src/hotspot/share/gc/g1/g1ConcurrentMark.inline.hpp index ec6a486dc02..76fdcd218ae 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentMark.inline.hpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentMark.inline.hpp @@ -93,7 +93,7 @@ inline void G1CMMarkStack::iterate(Fn fn) const { TaskQueueEntryChunk* cur = _chunk_list.load_relaxed(); while (cur != nullptr) { - guarantee(num_chunks <= _chunks_in_chunk_list, "Found %zu oop chunks which is more than there should be", num_chunks); + guarantee(num_chunks <= _chunks_in_chunk_list.load_relaxed(), "Found %zu oop chunks which is more than there should be", num_chunks); for (size_t i = 0; i < EntriesPerChunk; ++i) { if (cur->data[i].is_null()) { From fc73ca7f38be7e82902f162bccc6009779223950 Mon Sep 17 00:00:00 2001 From: Thomas Schatzl Date: Tue, 7 Jul 2026 12:54:01 +0000 Subject: [PATCH 094/305] 8387751: G1: Remove volatile from G1CollectorState::_initiate_conc_mark_if_possible Reviewed-by: aboldtch, ayang --- src/hotspot/share/gc/g1/g1CollectorState.hpp | 2 +- src/hotspot/share/gc/g1/g1CollectorState.inline.hpp | 2 +- src/hotspot/share/gc/g1/g1Policy.cpp | 1 + src/hotspot/share/gc/g1/g1Policy.hpp | 5 ++--- src/hotspot/share/gc/g1/g1YoungCollector.hpp | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/hotspot/share/gc/g1/g1CollectorState.hpp b/src/hotspot/share/gc/g1/g1CollectorState.hpp index 762ddb1fc8f..002b7894030 100644 --- a/src/hotspot/share/gc/g1/g1CollectorState.hpp +++ b/src/hotspot/share/gc/g1/g1CollectorState.hpp @@ -59,7 +59,7 @@ class G1CollectorState { // has been in progress when the request came in. // // This flag remembers that there is an unfullfilled request. - volatile bool _initiate_conc_mark_if_possible; + bool _initiate_conc_mark_if_possible; public: G1CollectorState() : diff --git a/src/hotspot/share/gc/g1/g1CollectorState.inline.hpp b/src/hotspot/share/gc/g1/g1CollectorState.inline.hpp index b63d683bb63..1a0e91f1adb 100644 --- a/src/hotspot/share/gc/g1/g1CollectorState.inline.hpp +++ b/src/hotspot/share/gc/g1/g1CollectorState.inline.hpp @@ -42,7 +42,7 @@ inline void G1CollectorState::set_in_full_gc() { inline void G1CollectorState::set_in_concurrent_start_gc() { _phase = Phase::YoungConcurrentStart; - _initiate_conc_mark_if_possible = false; + set_initiate_conc_mark_if_possible(false); } inline void G1CollectorState::set_in_prepare_mixed_gc() { _phase = Phase::YoungPrepareMixed; diff --git a/src/hotspot/share/gc/g1/g1Policy.cpp b/src/hotspot/share/gc/g1/g1Policy.cpp index 2414fdd7840..d271a8a610a 100644 --- a/src/hotspot/share/gc/g1/g1Policy.cpp +++ b/src/hotspot/share/gc/g1/g1Policy.cpp @@ -1253,6 +1253,7 @@ void G1Policy::update_survivors_policy() { } bool G1Policy::force_concurrent_start_if_outside_cycle(GCCause::Cause gc_cause) { + assert_at_safepoint_on_vm_thread(); // Check whether a concurrent cycle is active, do not include the // reclamation/mixed phase. This means that we can schedule a concurrent cycle // even while in the mixed phase. diff --git a/src/hotspot/share/gc/g1/g1Policy.hpp b/src/hotspot/share/gc/g1/g1Policy.hpp index 1fa81fe60b6..e09a76397cc 100644 --- a/src/hotspot/share/gc/g1/g1Policy.hpp +++ b/src/hotspot/share/gc/g1/g1Policy.hpp @@ -335,9 +335,8 @@ private: public: // This sets the initiate_conc_mark_if_possible() flag to start a - // new cycle, as long as we are not already in one. It's best if it - // is called during a safepoint when the test whether a cycle is in - // progress or not is stable. + // new cycle, as long as we are not already in one. It is called + // at a safepoint. bool force_concurrent_start_if_outside_cycle(GCCause::Cause gc_cause); // Decide whether this garbage collection pause should be a concurrent start diff --git a/src/hotspot/share/gc/g1/g1YoungCollector.hpp b/src/hotspot/share/gc/g1/g1YoungCollector.hpp index 7415bc83827..e9f2477ea76 100644 --- a/src/hotspot/share/gc/g1/g1YoungCollector.hpp +++ b/src/hotspot/share/gc/g1/g1YoungCollector.hpp @@ -144,7 +144,7 @@ public: size_t allocation_word_size); void collect(); - G1CollectorState next_state() const { return _next_state; } + const G1CollectorState next_state() const { return _next_state; } bool concurrent_operation_is_full_mark() const { return _concurrent_operation_is_full_mark; } }; From f264341bacd7f8aae41772cc993f9741d68d59a1 Mon Sep 17 00:00:00 2001 From: Thomas Schatzl Date: Tue, 7 Jul 2026 12:54:23 +0000 Subject: [PATCH 095/305] 8387490: G1: Dynamically creating worker threads exposes memory visibility race Reviewed-by: aboldtch, ayang --- src/hotspot/share/gc/shared/workerThread.cpp | 20 ++++++++++++-------- src/hotspot/share/gc/shared/workerThread.hpp | 8 ++++++-- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/hotspot/share/gc/shared/workerThread.cpp b/src/hotspot/share/gc/shared/workerThread.cpp index 35749452c85..94d0d904340 100644 --- a/src/hotspot/share/gc/shared/workerThread.cpp +++ b/src/hotspot/share/gc/shared/workerThread.cpp @@ -103,7 +103,7 @@ bool WorkerThreads::allow_inject_creation_failure() const { return false; } - if (_created_workers == 0) { + if (_created_workers.load_relaxed() == 0) { // Never allow creation failures of the first worker, it will cause the VM to exit return false; } @@ -135,18 +135,20 @@ uint WorkerThreads::set_active_workers(uint num_workers) { "Invalid number of active workers %u (should be 1-%u)", num_workers, _max_workers); - while (_created_workers < num_workers) { - WorkerThread* const worker = create_worker(_created_workers); + uint local_created_workers = created_workers(); + while (local_created_workers < num_workers) { + WorkerThread* const worker = create_worker(local_created_workers); if (worker == nullptr) { log_error(gc, task)("Failed to create worker thread"); break; } - _workers[_created_workers] = worker; - _created_workers++; + _workers[local_created_workers] = worker; + local_created_workers++; + _created_workers.release_store(local_created_workers); } - _active_workers = MIN2(_created_workers, num_workers); + _active_workers = MIN2(local_created_workers, num_workers); log_trace(gc, task)("%s: using %d out of %d workers", _name, _active_workers, _max_workers); @@ -154,14 +156,16 @@ uint WorkerThreads::set_active_workers(uint num_workers) { } void WorkerThreads::threads_do(ThreadClosure* tc) const { - for (uint i = 0; i < _created_workers; i++) { + uint local_created_workers = created_workers(); + for (uint i = 0; i < local_created_workers; i++) { tc->do_thread(_workers[i]); } } template void WorkerThreads::threads_do_f(Function function) const { - for (uint i = 0; i < _created_workers; i++) { + uint local_created_workers = created_workers(); + for (uint i = 0; i < local_created_workers; i++) { function(_workers[i]); } } diff --git a/src/hotspot/share/gc/shared/workerThread.hpp b/src/hotspot/share/gc/shared/workerThread.hpp index 003ce8a2959..6ed28e5b9b7 100644 --- a/src/hotspot/share/gc/shared/workerThread.hpp +++ b/src/hotspot/share/gc/shared/workerThread.hpp @@ -88,7 +88,11 @@ private: const char* const _name; WorkerThread** _workers; const uint _max_workers; - uint _created_workers; + // _created_workers publishes the initialized prefix of _workers. + // Writers release-store to it after initializing an entry. Readers + // load-acquire before accessing _workers to not access uninitalized + // data. + Atomic _created_workers; uint _active_workers; WorkerTaskDispatcher _dispatcher; @@ -107,7 +111,7 @@ public: bool allow_inject_creation_failure() const; uint max_workers() const { return _max_workers; } - uint created_workers() const { return _created_workers; } + uint created_workers() const { return _created_workers.load_acquire(); } uint active_workers() const { return _active_workers; } uint set_active_workers(uint num_workers); From 6e5bfcc6450512b4b974ed4afa067547552a0847 Mon Sep 17 00:00:00 2001 From: Thomas Schatzl Date: Tue, 7 Jul 2026 12:54:47 +0000 Subject: [PATCH 096/305] 8387764: G1: Let G1CollectedHeap::_summary_bytes_used use the Atomic API Reviewed-by: aboldtch, shade --- src/hotspot/share/gc/g1/g1CollectedHeap.cpp | 15 +++++++-------- src/hotspot/share/gc/g1/g1CollectedHeap.hpp | 2 +- src/hotspot/share/gc/g1/vmStructs_g1.hpp | 4 ++-- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp index 9dfdb376905..3c41133e572 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp @@ -1761,12 +1761,11 @@ size_t G1CollectedHeap::unused_committed_regions_in_bytes() const { // Computes the sum of the storage used by the various regions. size_t G1CollectedHeap::used() const { - size_t result = _summary_bytes_used + _allocator->used_in_alloc_regions(); - return result; + return used_unlocked() + _allocator->used_in_alloc_regions(); } size_t G1CollectedHeap::used_unlocked() const { - return _summary_bytes_used; + return _summary_bytes_used.load_relaxed(); } class SumUsedClosure: public G1HeapRegionClosure { @@ -3034,18 +3033,18 @@ void G1CollectedHeap::prepare_region_for_full_compaction(G1HeapRegion* hr) { } void G1CollectedHeap::increase_used(size_t bytes) { - _summary_bytes_used += bytes; + _summary_bytes_used.add_then_fetch(bytes, memory_order_relaxed); } void G1CollectedHeap::decrease_used(size_t bytes) { - assert(_summary_bytes_used >= bytes, + assert(used_unlocked() >= bytes, "invariant: _summary_bytes_used: %zu should be >= bytes: %zu", - _summary_bytes_used, bytes); - _summary_bytes_used -= bytes; + used_unlocked(), bytes); + _summary_bytes_used.sub_then_fetch(bytes, memory_order_relaxed); } void G1CollectedHeap::set_used(size_t bytes) { - _summary_bytes_used = bytes; + _summary_bytes_used.store_relaxed(bytes); } class RebuildRegionSetsClosure : public G1HeapRegionClosure { diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.hpp b/src/hotspot/share/gc/g1/g1CollectedHeap.hpp index 718c230851f..cb466a5e120 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.hpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.hpp @@ -243,7 +243,7 @@ private: // Outside of GC pauses, the number of bytes used in all regions other // than the current allocation region(s). - volatile size_t _summary_bytes_used; + Atomic _summary_bytes_used; void increase_used(size_t bytes); void decrease_used(size_t bytes); diff --git a/src/hotspot/share/gc/g1/vmStructs_g1.hpp b/src/hotspot/share/gc/g1/vmStructs_g1.hpp index af236ec8581..e0179b69646 100644 --- a/src/hotspot/share/gc/g1/vmStructs_g1.hpp +++ b/src/hotspot/share/gc/g1/vmStructs_g1.hpp @@ -42,7 +42,7 @@ nonstatic_field(G1HeapRegion, _bottom, HeapWord* const) \ nonstatic_field(G1HeapRegion, _top, Atomic) \ nonstatic_field(G1HeapRegion, _end, HeapWord* const) \ - volatile_nonstatic_field(G1HeapRegion, _pinned_object_count, Atomic)\ + nonstatic_field(G1HeapRegion, _pinned_object_count, Atomic) \ \ nonstatic_field(G1HeapRegionType, _tag, G1HeapRegionType::Tag volatile) \ \ @@ -55,7 +55,7 @@ \ nonstatic_field(G1HeapRegionManager, _regions, G1HeapRegionTable) \ \ - volatile_nonstatic_field(G1CollectedHeap, _summary_bytes_used, size_t) \ + nonstatic_field(G1CollectedHeap, _summary_bytes_used, Atomic) \ nonstatic_field(G1CollectedHeap, _hrm, G1HeapRegionManager) \ nonstatic_field(G1CollectedHeap, _monitoring_support, G1MonitoringSupport*) \ nonstatic_field(G1CollectedHeap, _old_set, G1HeapRegionSetBase) \ From 74f9b51f3436018f5f0987cee253d01f2eb27541 Mon Sep 17 00:00:00 2001 From: Jatin Bhateja Date: Tue, 7 Jul 2026 14:52:07 +0000 Subject: [PATCH 097/305] 8381809: Template Framework Library: add Float16Vector type Reviewed-by: epeter, galder --- .../library/CodeGenerationDataNameType.java | 101 +++++++- .../library/Operations.java | 224 +++++++++++------- .../library/PrimitiveType.java | 52 +++- .../library/ShortCarriesFloat16Type.java | 112 +++++++++ .../library/VectorElementType.java | 103 ++++++++ .../library/VectorType.java | 23 +- .../jtreg/compiler/lib/verify/Verify.java | 54 +++++ .../vectorapi/VectorExpressionFuzzer.java | 67 ++++-- .../verify/tests/TestVerifyFloat16.java | 84 ++++++- 9 files changed, 695 insertions(+), 125 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/library/ShortCarriesFloat16Type.java create mode 100644 test/hotspot/jtreg/compiler/lib/template_framework/library/VectorElementType.java diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/library/CodeGenerationDataNameType.java b/test/hotspot/jtreg/compiler/lib/template_framework/library/CodeGenerationDataNameType.java index 33eba66cd8c..5bfa217a1bb 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/library/CodeGenerationDataNameType.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/library/CodeGenerationDataNameType.java @@ -33,6 +33,27 @@ import compiler.lib.template_framework.Template; * additional functionality for code generation. These types with their extended * functionality can be used with many other code generation facilities in the * library, such as generating random {@code Expression}s. + * + *

    This module distinguishes scalar Java types and + * Vector API lane-element types: + *

      + *
    • Scalar {@code PRIMITIVE_TYPES}/{@code FLOATING_TYPES}/etc. enumerate + * only Java primitive types ({@code byte}, {@code short}, ...). + * These lists are typed as {@code List} and are consumed + * by scalar fuzzers / scalar code generation. {@link Float16Type} (the + * scalar {@code Float16} logical type) is included in + * {@link #SCALAR_NUMERIC_TYPES}.
    • + *
    • Vector-lane lists ({@code VECTOR_ELEMENT_TYPES}, + * {@code FLOATING_VECTOR_ELEMENT_TYPES}, ...) enumerate the lane types + * valid for {@code VectorType.Vector}. These are typed as + * {@code List} and additionally include + * {@link ShortCarriesFloat16Type#SHORT_CARRIES_FLOAT16} since {@code Float16Vector} is a real + * Vector API type whose lanes happen to have no Java primitive + * keyword.
    • + *
    + * Vector generators (e.g. {@code Operations.VECTOR_OPERATIONS}) consume the + * vector-lane lists; scalar generators (e.g. + * {@code Operations.PRIMITIVE_OPERATIONS}) consume the scalar lists. */ public interface CodeGenerationDataNameType extends DataName.Type { @@ -101,9 +122,22 @@ public interface CodeGenerationDataNameType extends DataName.Type { static PrimitiveType booleans() { return PrimitiveType.BOOLEANS; } /** - * The Float16 type. + * The {@code short}-carried {@code Float16} lane-element type used by + * {@code Float16Vector}. This is a {@link VectorElementType}, + * not a Java {@link PrimitiveType}; it appears in + * vector-lane lists but never in the scalar + * {@code PRIMITIVE_TYPES}/{@code FLOATING_TYPES} lists. Its lanes carry the + * raw bits of the {@code Float16} value in a {@code short}, hence the + * explicit {@code shortCarriesFloat16} naming. * - * @return The Float16 type. + * @return The {@code Float16Vector} {@link VectorElementType}. + */ + static ShortCarriesFloat16Type shortCarriesFloat16() { return ShortCarriesFloat16Type.SHORT_CARRIES_FLOAT16; } + + /** + * The {@code Float16} scalar (boxed) type. + * + * @return The scalar {@code Float16} type. */ static CodeGenerationDataNameType float16() { return Float16Type.FLOAT16; } @@ -185,6 +219,61 @@ public interface CodeGenerationDataNameType extends DataName.Type { float16() ); + // -------------------------------------------------------------------- + // Vector API lane-element type lists. + // + // These are typed as List and may include + // ShortCarriesFloat16Type.SHORT_CARRIES_FLOAT16 in addition to the Java + // primitive lane carriers. Vector generators (e.g. Operations.VECTOR_OPS) + // iterate over these lists to enumerate the lane types they support. + // -------------------------------------------------------------------- + + /** + * All Vector API lane-element types: every Java numeric primitive lane + * carrier plus {@link ShortCarriesFloat16Type#SHORT_CARRIES_FLOAT16}. + */ + List VECTOR_ELEMENT_TYPES = List.of( + bytes(), + shorts(), + shortCarriesFloat16(), + ints(), + longs(), + floats(), + doubles() + ); + + /** + * Integral Vector API lane-element types (byte, short, int, long). + */ + List INTEGRAL_VECTOR_ELEMENT_TYPES = List.of( + bytes(), + shorts(), + ints(), + longs() + ); + + /** + * Floating Vector API lane-element types (float16, float, double). + */ + List FLOATING_VECTOR_ELEMENT_TYPES = List.of( + shortCarriesFloat16(), + floats(), + doubles() + ); + + /** + * Vector API lane-element types whose lanes are 32/64 bits and integral + * (int, long). + */ + List INT_LONG_VECTOR_ELEMENT_TYPES = List.of( + ints(), + longs() + ); + + // -------------------------------------------------------------------- + // Concrete VectorType lists (typed as the concrete Vector subclasses). + // -------------------------------------------------------------------- + List VECTOR_BYTE_VECTOR_TYPES = List.of( VectorType.BYTE_64, VectorType.BYTE_128, @@ -199,6 +288,13 @@ public interface CodeGenerationDataNameType extends DataName.Type { VectorType.SHORT_512 ); + List VECTOR_FLOAT16_VECTOR_TYPES = List.of( + VectorType.FLOAT16_64, + VectorType.FLOAT16_128, + VectorType.FLOAT16_256, + VectorType.FLOAT16_512 + ); + List VECTOR_INT_VECTOR_TYPES = List.of( VectorType.INT_64, VectorType.INT_128, @@ -230,6 +326,7 @@ public interface CodeGenerationDataNameType extends DataName.Type { List VECTOR_VECTOR_TYPES = Utils.concat( VECTOR_BYTE_VECTOR_TYPES, VECTOR_SHORT_VECTOR_TYPES, + VECTOR_FLOAT16_VECTOR_TYPES, VECTOR_INT_VECTOR_TYPES, VECTOR_LONG_VECTOR_TYPES, VECTOR_FLOAT_VECTOR_TYPES, diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/library/Operations.java b/test/hotspot/jtreg/compiler/lib/template_framework/library/Operations.java index 3dffa096525..e9218101081 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/library/Operations.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/library/Operations.java @@ -36,10 +36,15 @@ import static compiler.lib.template_framework.library.PrimitiveType.FLOATS; import static compiler.lib.template_framework.library.PrimitiveType.DOUBLES; import static compiler.lib.template_framework.library.PrimitiveType.BOOLEANS; import static compiler.lib.template_framework.library.Float16Type.FLOAT16; +import static compiler.lib.template_framework.library.ShortCarriesFloat16Type.SHORT_CARRIES_FLOAT16; import static compiler.lib.template_framework.library.CodeGenerationDataNameType.PRIMITIVE_TYPES; import static compiler.lib.template_framework.library.CodeGenerationDataNameType.INTEGRAL_TYPES; import static compiler.lib.template_framework.library.CodeGenerationDataNameType.FLOATING_TYPES; import static compiler.lib.template_framework.library.CodeGenerationDataNameType.INT_LONG_TYPES; +import static compiler.lib.template_framework.library.CodeGenerationDataNameType.VECTOR_ELEMENT_TYPES; +import static compiler.lib.template_framework.library.CodeGenerationDataNameType.INTEGRAL_VECTOR_ELEMENT_TYPES; +import static compiler.lib.template_framework.library.CodeGenerationDataNameType.FLOATING_VECTOR_ELEMENT_TYPES; +import static compiler.lib.template_framework.library.CodeGenerationDataNameType.INT_LONG_VECTOR_ELEMENT_TYPES; /** * This class provides various lists of {@link Expression}s, that represent Java operators or library @@ -326,8 +331,11 @@ public final class Operations { INTEGRAL_ASSOCIATIVE, // Binary - but only safe for integral reductions TERNARY } - private record VOP(String name, VOPType type, List elementTypes, boolean isDeterministic) { - VOP(String name, VOPType type, List elementTypes) { + // VOP element type pools are typed as VectorElementType so they can include + // ShortCarriesFloat16Type.SHORT_CARRIES_FLOAT16 (the Float16Vector lane type) alongside the + // primitive lane types. + private record VOP(String name, VOPType type, List elementTypes, boolean isDeterministic) { + VOP(String name, VOPType type, List elementTypes) { this(name, type, elementTypes, true); } } @@ -337,81 +345,81 @@ public final class Operations { // But if a test is just interested in determinism, they are still // non-deterministic. private static final List VECTOR_OPS = List.of( - new VOP("ABS", VOPType.UNARY, PRIMITIVE_TYPES), - new VOP("ACOS", VOPType.UNARY, FLOATING_TYPES, false), // 1 ulp - new VOP("ADD", VOPType.INTEGRAL_ASSOCIATIVE, PRIMITIVE_TYPES), - new VOP("AND", VOPType.ASSOCIATIVE, INTEGRAL_TYPES), - new VOP("AND_NOT", VOPType.BINARY, INTEGRAL_TYPES), - new VOP("ASHR", VOPType.BINARY, INTEGRAL_TYPES), - new VOP("ASIN", VOPType.UNARY, FLOATING_TYPES, false), // 1 ulp - new VOP("ATAN", VOPType.UNARY, FLOATING_TYPES, false), // 1 ulp - new VOP("ATAN2", VOPType.BINARY, FLOATING_TYPES, false), // 2 ulp - new VOP("BIT_COUNT", VOPType.UNARY, INTEGRAL_TYPES), - new VOP("BITWISE_BLEND", VOPType.TERNARY, INTEGRAL_TYPES), - new VOP("CBRT", VOPType.UNARY, FLOATING_TYPES, false), // 1 ulp - new VOP("COMPRESS_BITS", VOPType.BINARY, INT_LONG_TYPES), - new VOP("COS", VOPType.UNARY, FLOATING_TYPES, false), // 1 ulp - new VOP("COSH", VOPType.UNARY, FLOATING_TYPES, false), // 2.5 ulp - new VOP("DIV", VOPType.BINARY, FLOATING_TYPES), - new VOP("EXP", VOPType.UNARY, FLOATING_TYPES, false), // 1 ulp - new VOP("EXPAND_BITS", VOPType.BINARY, INT_LONG_TYPES), - new VOP("EXPM1", VOPType.UNARY, FLOATING_TYPES, false), // 1 ulp - new VOP("FIRST_NONZERO", VOPType.ASSOCIATIVE, PRIMITIVE_TYPES), - new VOP("FMA", VOPType.TERNARY, FLOATING_TYPES), - new VOP("HYPOT", VOPType.BINARY, FLOATING_TYPES, false), // 1.5 ulp - new VOP("LEADING_ZEROS_COUNT", VOPType.UNARY, INTEGRAL_TYPES), - new VOP("LOG", VOPType.UNARY, FLOATING_TYPES, false), // 1 ulp - new VOP("LOG10", VOPType.UNARY, FLOATING_TYPES, false), // 1 ulp - new VOP("LOG1P", VOPType.UNARY, FLOATING_TYPES, false), // 1 ulp - new VOP("LSHL", VOPType.BINARY, INTEGRAL_TYPES), - new VOP("LSHR", VOPType.BINARY, INTEGRAL_TYPES), - new VOP("MIN", VOPType.ASSOCIATIVE, PRIMITIVE_TYPES), - new VOP("MAX", VOPType.ASSOCIATIVE, PRIMITIVE_TYPES), - new VOP("MUL", VOPType.INTEGRAL_ASSOCIATIVE, PRIMITIVE_TYPES), - new VOP("NEG", VOPType.UNARY, PRIMITIVE_TYPES), - new VOP("NOT", VOPType.UNARY, INTEGRAL_TYPES), - new VOP("OR", VOPType.ASSOCIATIVE, INTEGRAL_TYPES), - new VOP("POW", VOPType.BINARY, FLOATING_TYPES, false), // 1 ulp - new VOP("REVERSE", VOPType.UNARY, INTEGRAL_TYPES), - new VOP("REVERSE_BYTES", VOPType.UNARY, INTEGRAL_TYPES), - new VOP("ROL", VOPType.BINARY, INTEGRAL_TYPES), - new VOP("ROR", VOPType.BINARY, INTEGRAL_TYPES), - new VOP("SADD", VOPType.BINARY, INTEGRAL_TYPES), - new VOP("SIN", VOPType.UNARY, FLOATING_TYPES, false), // 1 ulp - new VOP("SINH", VOPType.UNARY, FLOATING_TYPES, false), // 2.5 ulp - new VOP("SQRT", VOPType.UNARY, FLOATING_TYPES), - new VOP("SSUB", VOPType.BINARY, INTEGRAL_TYPES), - new VOP("SUADD", VOPType.BINARY, INTEGRAL_TYPES), - new VOP("SUB", VOPType.BINARY, PRIMITIVE_TYPES), - new VOP("SUSUB", VOPType.BINARY, INTEGRAL_TYPES), - new VOP("TAN", VOPType.UNARY, FLOATING_TYPES, false), // 1.25 ulp - new VOP("TANH", VOPType.UNARY, FLOATING_TYPES, false), // 2.5 ulp - new VOP("TRAILING_ZEROS_COUNT", VOPType.UNARY, INTEGRAL_TYPES), - new VOP("UMAX", VOPType.ASSOCIATIVE, INTEGRAL_TYPES), - new VOP("UMIN", VOPType.ASSOCIATIVE, INTEGRAL_TYPES), - new VOP("XOR", VOPType.ASSOCIATIVE, INTEGRAL_TYPES), - new VOP("ZOMO", VOPType.UNARY, INTEGRAL_TYPES) + new VOP("ABS", VOPType.UNARY, VECTOR_ELEMENT_TYPES), + new VOP("ACOS", VOPType.UNARY, FLOATING_VECTOR_ELEMENT_TYPES, false), // 1 ulp + new VOP("ADD", VOPType.INTEGRAL_ASSOCIATIVE, VECTOR_ELEMENT_TYPES), + new VOP("AND", VOPType.ASSOCIATIVE, INTEGRAL_VECTOR_ELEMENT_TYPES), + new VOP("AND_NOT", VOPType.BINARY, INTEGRAL_VECTOR_ELEMENT_TYPES), + new VOP("ASHR", VOPType.BINARY, INTEGRAL_VECTOR_ELEMENT_TYPES), + new VOP("ASIN", VOPType.UNARY, FLOATING_VECTOR_ELEMENT_TYPES, false), // 1 ulp + new VOP("ATAN", VOPType.UNARY, FLOATING_VECTOR_ELEMENT_TYPES, false), // 1 ulp + new VOP("ATAN2", VOPType.BINARY, FLOATING_VECTOR_ELEMENT_TYPES, false), // 2 ulp + new VOP("BIT_COUNT", VOPType.UNARY, INTEGRAL_VECTOR_ELEMENT_TYPES), + new VOP("BITWISE_BLEND", VOPType.TERNARY, INTEGRAL_VECTOR_ELEMENT_TYPES), + new VOP("CBRT", VOPType.UNARY, FLOATING_VECTOR_ELEMENT_TYPES, false), // 1 ulp + new VOP("COMPRESS_BITS", VOPType.BINARY, INT_LONG_VECTOR_ELEMENT_TYPES), + new VOP("COS", VOPType.UNARY, FLOATING_VECTOR_ELEMENT_TYPES, false), // 1 ulp + new VOP("COSH", VOPType.UNARY, FLOATING_VECTOR_ELEMENT_TYPES, false), // 2.5 ulp + new VOP("DIV", VOPType.BINARY, FLOATING_VECTOR_ELEMENT_TYPES), + new VOP("EXP", VOPType.UNARY, FLOATING_VECTOR_ELEMENT_TYPES, false), // 1 ulp + new VOP("EXPAND_BITS", VOPType.BINARY, INT_LONG_VECTOR_ELEMENT_TYPES), + new VOP("EXPM1", VOPType.UNARY, FLOATING_VECTOR_ELEMENT_TYPES, false), // 1 ulp + new VOP("FIRST_NONZERO", VOPType.ASSOCIATIVE, VECTOR_ELEMENT_TYPES), + new VOP("FMA", VOPType.TERNARY, FLOATING_VECTOR_ELEMENT_TYPES), + new VOP("HYPOT", VOPType.BINARY, FLOATING_VECTOR_ELEMENT_TYPES, false), // 1.5 ulp + new VOP("LEADING_ZEROS_COUNT", VOPType.UNARY, INTEGRAL_VECTOR_ELEMENT_TYPES), + new VOP("LOG", VOPType.UNARY, FLOATING_VECTOR_ELEMENT_TYPES, false), // 1 ulp + new VOP("LOG10", VOPType.UNARY, FLOATING_VECTOR_ELEMENT_TYPES, false), // 1 ulp + new VOP("LOG1P", VOPType.UNARY, FLOATING_VECTOR_ELEMENT_TYPES, false), // 1 ulp + new VOP("LSHL", VOPType.BINARY, INTEGRAL_VECTOR_ELEMENT_TYPES), + new VOP("LSHR", VOPType.BINARY, INTEGRAL_VECTOR_ELEMENT_TYPES), + new VOP("MIN", VOPType.ASSOCIATIVE, VECTOR_ELEMENT_TYPES), + new VOP("MAX", VOPType.ASSOCIATIVE, VECTOR_ELEMENT_TYPES), + new VOP("MUL", VOPType.INTEGRAL_ASSOCIATIVE, VECTOR_ELEMENT_TYPES), + new VOP("NEG", VOPType.UNARY, VECTOR_ELEMENT_TYPES), + new VOP("NOT", VOPType.UNARY, INTEGRAL_VECTOR_ELEMENT_TYPES), + new VOP("OR", VOPType.ASSOCIATIVE, INTEGRAL_VECTOR_ELEMENT_TYPES), + new VOP("POW", VOPType.BINARY, FLOATING_VECTOR_ELEMENT_TYPES, false), // 1 ulp + new VOP("REVERSE", VOPType.UNARY, INTEGRAL_VECTOR_ELEMENT_TYPES), + new VOP("REVERSE_BYTES", VOPType.UNARY, INTEGRAL_VECTOR_ELEMENT_TYPES), + new VOP("ROL", VOPType.BINARY, INTEGRAL_VECTOR_ELEMENT_TYPES), + new VOP("ROR", VOPType.BINARY, INTEGRAL_VECTOR_ELEMENT_TYPES), + new VOP("SADD", VOPType.BINARY, INTEGRAL_VECTOR_ELEMENT_TYPES), + new VOP("SIN", VOPType.UNARY, FLOATING_VECTOR_ELEMENT_TYPES, false), // 1 ulp + new VOP("SINH", VOPType.UNARY, FLOATING_VECTOR_ELEMENT_TYPES, false), // 2.5 ulp + new VOP("SQRT", VOPType.UNARY, FLOATING_VECTOR_ELEMENT_TYPES), + new VOP("SSUB", VOPType.BINARY, INTEGRAL_VECTOR_ELEMENT_TYPES), + new VOP("SUADD", VOPType.BINARY, INTEGRAL_VECTOR_ELEMENT_TYPES), + new VOP("SUB", VOPType.BINARY, VECTOR_ELEMENT_TYPES), + new VOP("SUSUB", VOPType.BINARY, INTEGRAL_VECTOR_ELEMENT_TYPES), + new VOP("TAN", VOPType.UNARY, FLOATING_VECTOR_ELEMENT_TYPES, false), // 1.25 ulp + new VOP("TANH", VOPType.UNARY, FLOATING_VECTOR_ELEMENT_TYPES, false), // 2.5 ulp + new VOP("TRAILING_ZEROS_COUNT", VOPType.UNARY, INTEGRAL_VECTOR_ELEMENT_TYPES), + new VOP("UMAX", VOPType.ASSOCIATIVE, INTEGRAL_VECTOR_ELEMENT_TYPES), + new VOP("UMIN", VOPType.ASSOCIATIVE, INTEGRAL_VECTOR_ELEMENT_TYPES), + new VOP("XOR", VOPType.ASSOCIATIVE, INTEGRAL_VECTOR_ELEMENT_TYPES), + new VOP("ZOMO", VOPType.UNARY, INTEGRAL_VECTOR_ELEMENT_TYPES) ); private static final List VECTOR_CMP = List.of( - new VOP("EQ", VOPType.ASSOCIATIVE, PRIMITIVE_TYPES), - new VOP("GE", VOPType.ASSOCIATIVE, PRIMITIVE_TYPES), - new VOP("GT", VOPType.ASSOCIATIVE, PRIMITIVE_TYPES), - new VOP("LE", VOPType.ASSOCIATIVE, PRIMITIVE_TYPES), - new VOP("LT", VOPType.ASSOCIATIVE, PRIMITIVE_TYPES), - new VOP("NE", VOPType.ASSOCIATIVE, PRIMITIVE_TYPES), - new VOP("UGE", VOPType.ASSOCIATIVE, INTEGRAL_TYPES), - new VOP("UGT", VOPType.ASSOCIATIVE, INTEGRAL_TYPES), - new VOP("ULE", VOPType.ASSOCIATIVE, INTEGRAL_TYPES), - new VOP("ULT", VOPType.ASSOCIATIVE, INTEGRAL_TYPES) + new VOP("EQ", VOPType.ASSOCIATIVE, VECTOR_ELEMENT_TYPES), + new VOP("GE", VOPType.ASSOCIATIVE, VECTOR_ELEMENT_TYPES), + new VOP("GT", VOPType.ASSOCIATIVE, VECTOR_ELEMENT_TYPES), + new VOP("LE", VOPType.ASSOCIATIVE, VECTOR_ELEMENT_TYPES), + new VOP("LT", VOPType.ASSOCIATIVE, VECTOR_ELEMENT_TYPES), + new VOP("NE", VOPType.ASSOCIATIVE, VECTOR_ELEMENT_TYPES), + new VOP("UGE", VOPType.ASSOCIATIVE, INTEGRAL_VECTOR_ELEMENT_TYPES), + new VOP("UGT", VOPType.ASSOCIATIVE, INTEGRAL_VECTOR_ELEMENT_TYPES), + new VOP("ULE", VOPType.ASSOCIATIVE, INTEGRAL_VECTOR_ELEMENT_TYPES), + new VOP("ULT", VOPType.ASSOCIATIVE, INTEGRAL_VECTOR_ELEMENT_TYPES) ); private static final List VECTOR_TEST = List.of( - new VOP("IS_DEFAULT", VOPType.UNARY, PRIMITIVE_TYPES), - new VOP("IS_NEGATIVE", VOPType.UNARY, PRIMITIVE_TYPES), - new VOP("IS_FINITE", VOPType.UNARY, FLOATING_TYPES), - new VOP("IS_NAN", VOPType.UNARY, FLOATING_TYPES), - new VOP("IS_INFINITE", VOPType.UNARY, FLOATING_TYPES) + new VOP("IS_DEFAULT", VOPType.UNARY, VECTOR_ELEMENT_TYPES), + new VOP("IS_NEGATIVE", VOPType.UNARY, VECTOR_ELEMENT_TYPES), + new VOP("IS_FINITE", VOPType.UNARY, FLOATING_VECTOR_ELEMENT_TYPES), + new VOP("IS_NAN", VOPType.UNARY, FLOATING_VECTOR_ELEMENT_TYPES), + new VOP("IS_INFINITE", VOPType.UNARY, FLOATING_VECTOR_ELEMENT_TYPES) ); // TODO: Conversion VectorOperators -> convertShape @@ -476,14 +484,14 @@ public final class Operations { "((" + type.name() + ")", type2, ".convert(VectorOperators.Conversion.ofCast(" - + type2.elementType.name() + ".class, " - + type.elementType.name() + ".class), 0))")); + + type2.elementType.vectorElementClass() + ".class, " + + type.elementType.vectorElementClass() + ".class), 0))")); ops.add(Expression.make(type, "((" + type.name() + ")", type2, ".convert(VectorOperators.Conversion.ofCast(" - + type2.elementType.name() + ".class, " - + type.elementType.name() + ".class),", + + type2.elementType.vectorElementClass() + ".class, " + + type.elementType.vectorElementClass() + ".class),", INTS, // part "))", WITH_OUT_OF_BOUNDS_EXCEPTION)); } @@ -498,14 +506,14 @@ public final class Operations { "((" + type.name() + ")", type2, ".convert(VectorOperators.Conversion.ofReinterpret(" - + type2.elementType.name() + ".class, " - + type.elementType.name() + ".class), 0))", reinterpretInfo)); + + type2.elementType.vectorElementClass() + ".class, " + + type.elementType.vectorElementClass() + ".class), 0))", reinterpretInfo)); ops.add(Expression.make(type, "((" + type.name() + ")", type2, ".convert(VectorOperators.Conversion.ofReinterpret(" - + type2.elementType.name() + ".class, " - + type.elementType.name() + ".class),", + + type2.elementType.vectorElementClass() + ".class, " + + type.elementType.vectorElementClass() + ".class),", INTS, // part "))", reinterpretInfo.combineWith(WITH_OUT_OF_BOUNDS_EXCEPTION))); if (type.elementType == BYTES) { @@ -523,6 +531,9 @@ public final class Operations { if (type.elementType == FLOATS) { ops.add(Expression.make(type, "", type2, ".reinterpretAsFloats()", reinterpretInfo)); } + if (type.elementType == SHORT_CARRIES_FLOAT16) { + ops.add(Expression.make(type, "", type2, ".reinterpretAsFloat16s()", reinterpretInfo)); + } if (type.elementType == DOUBLES) { ops.add(Expression.make(type, "", type2, ".reinterpretAsDoubles()", reinterpretInfo)); } @@ -558,8 +569,8 @@ public final class Operations { "((" + type.name() + ")", type2, ".convertShape(VectorOperators.Conversion.ofCast(" - + type2.elementType.name() + ".class, " - + type.elementType.name() + ".class), " + + type2.elementType.vectorElementClass() + ".class, " + + type.elementType.vectorElementClass() + ".class), " + type.speciesName + ", ", INTS, // part "))", WITH_OUT_OF_BOUNDS_EXCEPTION)); @@ -567,8 +578,8 @@ public final class Operations { "((" + type.name() + ")", type2, ".convertShape(VectorOperators.Conversion.ofReinterpret(" - + type2.elementType.name() + ".class, " - + type.elementType.name() + ".class), " + + type2.elementType.vectorElementClass() + ".class, " + + type.elementType.vectorElementClass() + ".class), " + type.speciesName + ", ", INTS, // part "))", reinterpretInfo.combineWith(WITH_OUT_OF_BOUNDS_EXCEPTION))); @@ -585,16 +596,16 @@ public final class Operations { "((" + type.name() + ")", type2, ".convertShape(VectorOperators.Conversion.ofCast(" - + type2.elementType.name() + ".class, " - + type.elementType.name() + ".class), " + + type2.elementType.vectorElementClass() + ".class, " + + type.elementType.vectorElementClass() + ".class), " + type.speciesName + ", ", INTS, " & " + partMask + "))")); ops.add(Expression.make(type, "((" + type.name() + ")", type2, ".convertShape(VectorOperators.Conversion.ofReinterpret(" - + type2.elementType.name() + ".class, " - + type.elementType.name() + ".class), " + + type2.elementType.vectorElementClass() + ".class, " + + type.elementType.vectorElementClass() + ".class), " + type.speciesName + ", ", INTS, " & " + partMask + "))", reinterpretInfo)); } else { @@ -604,16 +615,16 @@ public final class Operations { "((" + type.name() + ")", type2, ".convertShape(VectorOperators.Conversion.ofCast(" - + type2.elementType.name() + ".class, " - + type.elementType.name() + ".class), " + + type2.elementType.vectorElementClass() + ".class, " + + type.elementType.vectorElementClass() + ".class), " + type.speciesName + ", " + "-(", INTS, " & " + partMask + ")))")); ops.add(Expression.make(type, "((" + type.name() + ")", type2, ".convertShape(VectorOperators.Conversion.ofReinterpret(" - + type2.elementType.name() + ".class, " - + type.elementType.name() + ".class), " + + type2.elementType.vectorElementClass() + ".class, " + + type.elementType.vectorElementClass() + ".class), " + type.speciesName + ", " + "-(", INTS, " & " + partMask + ")))", reinterpretInfo)); } @@ -795,6 +806,27 @@ public final class Operations { // skip hashCode } + // ----------------- ShortCarriesFloat16Type lane bridges -------------------- + // ShortCarriesFloat16Type is the Float16Vector lane type; its lanes carry the raw + // bits of a Float16 in a short. We bridge it both to the boxed Float16 (rich + // float16 arithmetic) and to a plain short (raw-bit fiddling), so expression + // nesting can transition in and out of the lane type and so any IGVN + // optimizations on those transitions are exercised. + var float16Lane = ShortCarriesFloat16Type.SHORT_CARRIES_FLOAT16; + // Lane carrier -> boxed Float16: lifts a lane()/reduceLanes() result into the rich + // scalar Float16 world. The raw bits are not exposed (NaN-awareness is handled by + // Float16 verification), so deterministic. + ops.add(Expression.make(FLOAT16, "Float16.shortBitsToFloat16(", float16Lane, ")")); + // Boxed Float16 -> lane carrier: produces a ShortCarriesFloat16Type scalar to feed + // Float16Vector.broadcast/add(scalar)/withLane(...). + ops.add(Expression.make(float16Lane, "Float16.float16ToShortBits(", FLOAT16, ")")); + ops.add(Expression.make(float16Lane, "Float16.float16ToRawShortBits(", FLOAT16, ")")); + // Raw short <-> lane carrier: a Java-level no-op (both are carried in a short), but + // a type-level transition. short -> lane is deterministic; lane -> short exposes the + // raw bits, so distinct NaN encodings make it non-deterministic (preventing result verification). + ops.add(Expression.make(SHORT_CARRIES_FLOAT16, "/*cast to ShortCarriesFloat16Type*/(", SHORTS, ")")); + ops.add(Expression.make(SHORTS, "/*cast to short*/(", SHORT_CARRIES_FLOAT16, ")", WITH_NONDETERMINISTIC_RESULT)); + // TODO: VectorSpecies API methods // Make sure the list is not modifiable. @@ -838,8 +870,18 @@ public final class Operations { FLOAT16_OPERATIONS ); + /** + * Provides a list of Vector API operations. Iterates over all + * {@link CodeGenerationDataNameType#VECTOR_VECTOR_TYPES}, including + * {@code Float16Vector_*}, whose lanes are described by + * {@link ShortCarriesFloat16Type#SHORT_CARRIES_FLOAT16}. + */ public static final List VECTOR_OPERATIONS = generateVectorOperations(); + /** + * Provides a list of all operations: every scalar operation and every + * Vector API operation. + */ public static final List ALL_OPERATIONS = Utils.concat( SCALAR_NUMERIC_OPERATIONS, VECTOR_OPERATIONS diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/library/PrimitiveType.java b/test/hotspot/jtreg/compiler/lib/template_framework/library/PrimitiveType.java index cd796fd0d31..31e0eecbac3 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/library/PrimitiveType.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/library/PrimitiveType.java @@ -40,8 +40,19 @@ import static compiler.lib.template_framework.Template.let; * The {@link PrimitiveType} models Java's primitive types, and provides a set * of useful methods for code generation, such as the {@link #byteSize} and * {@link #boxedTypeName}. + * + *

    {@link PrimitiveType} is a Java scalar type and additionally + * doubles as a {@link VectorElementType} for those Vector API lane types whose + * lane carrier is itself a Java primitive (e.g. {@code IntVector}'s lane + * carrier is {@code int}). For these primitive lane types + * {@link #carrierTypeName} coincides with {@link #name}. + * + *

    Non-primitive lane types, such as the {@code Float16Vector} lane, are + * modeled by separate {@link VectorElementType} implementations (see + * {@link ShortCarriesFloat16Type}). They do not appear in any of + * the scalar {@code PRIMITIVE_TYPES}/{@code FLOATING_TYPES} lists. */ -public final class PrimitiveType implements CodeGenerationDataNameType { +public final class PrimitiveType implements VectorElementType { private static final Random RANDOM = Utils.getRandomInstance(); private static final RestrictableGenerator GEN_BYTE = Generators.G.safeRestrict(Generators.G.ints(), Byte.MIN_VALUE, Byte.MAX_VALUE); private static final RestrictableGenerator GEN_CHAR = Generators.G.safeRestrict(Generators.G.ints(), Character.MIN_VALUE, Character.MAX_VALUE); @@ -107,6 +118,23 @@ public final class PrimitiveType implements CodeGenerationDataNameType { }; } + @Override + public String carrierTypeName() { + return name(); + } + + @Override + public String vectorElementClass() { + // For primitive lanes the code-usable name and the lane element class + // token coincide (e.g. "int" -> int.class). boolean/char are not real + // Vector API lane element types, so we fail fast during code generation + // rather than emitting code that would only break at compile/runtime. + if (kind == Kind.BOOLEAN || kind == Kind.CHAR) { + throw new UnsupportedOperationException(name() + " is not a Vector API lane element type"); + } + return name(); + } + @Override public String toString() { return name(); @@ -132,6 +160,7 @@ public final class PrimitiveType implements CodeGenerationDataNameType { * @return Size of the type in bytes. * @throws UnsupportedOperationException for boolean which has no defined size. */ + @Override public int byteSize() { return switch (kind) { case BYTE -> 1; @@ -147,6 +176,7 @@ public final class PrimitiveType implements CodeGenerationDataNameType { * * @return the name of the boxed type. */ + @Override public String boxedTypeName() { return switch (kind) { case BYTE -> "Byte"; @@ -194,6 +224,7 @@ public final class PrimitiveType implements CodeGenerationDataNameType { * * @return true iff the type is a floating point type. */ + @Override public boolean isFloating() { return switch (kind) { case BYTE, SHORT, CHAR, INT, LONG, BOOLEAN -> false; @@ -213,6 +244,7 @@ public final class PrimitiveType implements CodeGenerationDataNameType { * @return the token representing the method call to obtain a * random value for the given type at runtime. */ + @Override public Object callLibraryRNG() { return switch (kind) { case BYTE -> "LibraryRNG.nextByte()"; @@ -231,6 +263,12 @@ public final class PrimitiveType implements CodeGenerationDataNameType { * random number generators available, wrapping {@link Generators}. This * is supposed to be used in tandem with {@link #callLibraryRNG}. * + *

    In addition to the Java primitive generators, this also emits + * helpers for {@code Float16Vector}'s {@code short} carrier + * ({@code nextFloat16()} / {@code fill_float16(short[])}) so that + * {@link ShortCarriesFloat16Type#callLibraryRNG()} can be used with vector + * fuzzers without depending on this class importing Float16Vector itself. + * * Note: you must ensure that all required imports are performed: * {@code java.util.Random} * {@code jdk.test.lib.Utils} @@ -250,6 +288,7 @@ public final class PrimitiveType implements CodeGenerationDataNameType { private static final RestrictableGenerator GEN_LONG = Generators.G.longs(); private static final Generator GEN_DOUBLE = Generators.G.doubles(); private static final Generator GEN_FLOAT = Generators.G.floats(); + private static final Generator GEN_FLOAT16 = Generators.G.float16s(); public static byte nextByte() { return GEN_BYTE.next().byteValue(); @@ -283,6 +322,17 @@ public final class PrimitiveType implements CodeGenerationDataNameType { return RANDOM.nextBoolean(); } + // Float16Vector lane helpers. Float16 lanes are carried in short[]. + public static short nextFloat16() { + return GEN_FLOAT16.next(); + } + + public static void fill_float16(short[] a) { + for (int i = 0; i < a.length; i++) { + a[i] = nextFloat16(); + } + } + """, CodeGenerationDataNameType.PRIMITIVE_TYPES.stream().map(type -> scope( let("type", type), diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/library/ShortCarriesFloat16Type.java b/test/hotspot/jtreg/compiler/lib/template_framework/library/ShortCarriesFloat16Type.java new file mode 100644 index 00000000000..33c5535b99b --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/library/ShortCarriesFloat16Type.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.lib.template_framework.library; + +import compiler.lib.generators.Generators; +import compiler.lib.generators.Generator; + +import compiler.lib.template_framework.DataName; + +/** + * The {@link ShortCarriesFloat16Type} is the {@link VectorElementType} that describes + * the lane type of a {@code Float16Vector}. Its name makes the semantics + * explicit: a {@code Float16} value carried in a {@code short}. + * + *

    Float16 is not a Java primitive type and therefore does + * not appear in any of the scalar {@link PrimitiveType} lists. As a + * {@link VectorElementType} it appears in vector-lane-typed lists such as + * {@link CodeGenerationDataNameType#VECTOR_ELEMENT_TYPES} and + * {@link CodeGenerationDataNameType#FLOATING_VECTOR_ELEMENT_TYPES}, which are + * consumed by vector-only generators (e.g. {@code Operations.VECTOR_OPERATIONS}). + * + *

    The carrier type for a {@code Float16Vector} lane is {@code short}, so + * {@link #name()} (the code-usable type, per the {@code name()} contract) + * returns {@code "short"}. The logical lane element type token used in + * {@code VectorOperators.Conversion.of*} expressions and + * {@code Float16Vector.SPECIES_*} is {@code Float16}, returned by + * {@link #vectorElementClass()}. + * + *

    NaN handling note: there are multiple bit representations for NaN within + * {@code short}/{@code Float16}. Consumers comparing {@code short[]} carrier + * arrays should canonicalize via {@code Float.float16ToFloat} (which returns a + * canonical NaN) before structural comparison. + */ +public final class ShortCarriesFloat16Type implements VectorElementType { + private static final Generator GEN_FLOAT16 = Generators.G.float16s(); + + /** The singleton instance. */ + public static final ShortCarriesFloat16Type SHORT_CARRIES_FLOAT16 = new ShortCarriesFloat16Type(); + + private ShortCarriesFloat16Type() {} + + @Override + public boolean isSubtypeOf(DataName.Type other) { + return other instanceof ShortCarriesFloat16Type; + } + + @Override + public String name() { + return "short"; + } + + @Override + public String carrierTypeName() { + return "short"; + } + + @Override + public String vectorElementClass() { + return "Float16"; + } + + @Override + public String boxedTypeName() { + return "Float16"; + } + + @Override + public int byteSize() { + return 2; + } + + @Override + public boolean isFloating() { + return true; + } + + @Override + public String toString() { + return name(); + } + + @Override + public Object con() { + return "(short)" + GEN_FLOAT16.next(); + } + + @Override + public Object callLibraryRNG() { + return "LibraryRNG.nextFloat16()"; + } +} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/library/VectorElementType.java b/test/hotspot/jtreg/compiler/lib/template_framework/library/VectorElementType.java new file mode 100644 index 00000000000..657dc80fd5e --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/template_framework/library/VectorElementType.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * 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.lib.template_framework.library; + +/** + * A {@link VectorElementType} describes a single lane-element of a Vector API + * vector ({@link VectorType.Vector}). It abstracts over: + *

      + *
    • {@link PrimitiveType} - the standard Java primitive lane types + * (byte, short, int, long, float, double). For these {@link #name()} is + * the primitive keyword + * (e.g. {@code "int"}) and {@link #vectorElementClass()} is the same + * token, so {@code vectorElementClass() + ".class"} yields the primitive + * {@code Class} literal ({@code int.class}).
    • + *
    • {@link ShortCarriesFloat16Type} - the {@code Float16Vector} lane type. Float16 + * has no Java primitive keyword; its lanes are stored in a {@code short[]} + * carrier, so {@link #name()} returns the code-usable carrier keyword + * {@code "short"} (consistent with {@link #toString()}), while + * {@link #vectorElementClass()} returns {@code "Float16"} so that + * {@code vectorElementClass() + ".class"} ({@code Float16.class}) is the + * token expected by + * {@code VectorOperators.Conversion.ofCast}/{@code ofReinterpret}.
    • + *
    + * + *

    This interface lives outside the scalar + * {@link PrimitiveType} type lists (e.g. {@code PRIMITIVE_TYPES}, + * {@code FLOATING_TYPES}). Those lists model Java scalar types and are consumed + * by scalar fuzzers. Vector-lane lists (e.g. {@code VECTOR_ELEMENT_TYPES}, + * {@code FLOATING_VECTOR_ELEMENT_TYPES}) are typed as {@code List} + * and may include {@link ShortCarriesFloat16Type#SHORT_CARRIES_FLOAT16}. + */ +public interface VectorElementType extends CodeGenerationDataNameType { + + /** + * The string whose {@code + ".class"} form is the lane element + * {@code Class} literal expected by the Vector API conversion factories + * ({@code VectorOperators.Conversion.ofCast}/{@code ofReinterpret}) and by + * {@code Float16Vector.SPECIES_*}/{@code IntVector.SPECIES_*} lookups. + * + *

    Unlike {@link #name()} (which must always be a Java type usable + * directly in code, e.g. for variable declarations and casts), this token + * is the logical lane element type. For Java primitive lanes the + * two coincide ({@code "int"} -> {@code int.class}); for {@code Float16} + * lanes {@link #name()} is the carrier {@code "short"} while this returns + * {@code "Float16"} ({@code Float16.class}). + * + * @return The logical lane element type token (e.g. {@code "int"}, + * {@code "float"}, {@code "Float16"}). + */ + String vectorElementClass(); + + /** + * @return The element type of the Java carrier array used to hold these + * lanes when calling {@code fromArray}/{@code intoArray}. For most + * lane types this is the same as {@link #name()}; for + * {@code Float16} it is {@code "short"}. + */ + String carrierTypeName(); + + /** + * @return The boxed type name used to parameterize generic types such as + * {@code VectorMask} and {@code VectorShuffle} + * (e.g. {@code "Integer"}, {@code "Float16"}). + */ + String boxedTypeName(); + + /** + * @return Size of the lane type in bytes. + */ + int byteSize(); + + /** + * @return {@code true} iff the lane type is a floating point type. + */ + boolean isFloating(); + + /** + * @return A token representing a call to the corresponding pseudo random + * number generator from {@link PrimitiveType#generateLibraryRNG()}. + */ + Object callLibraryRNG(); +} diff --git a/test/hotspot/jtreg/compiler/lib/template_framework/library/VectorType.java b/test/hotspot/jtreg/compiler/lib/template_framework/library/VectorType.java index 7eabd42a723..df1365a3566 100644 --- a/test/hotspot/jtreg/compiler/lib/template_framework/library/VectorType.java +++ b/test/hotspot/jtreg/compiler/lib/template_framework/library/VectorType.java @@ -39,6 +39,11 @@ import static compiler.lib.template_framework.library.PrimitiveType.BOOLEANS; /** * The {@link VectorType} models the Vector API types. + * + *

    A {@code VectorType.Vector} is parameterized by a {@link VectorElementType} + * (its lane element type) and a lane count. The lane element type may be a + * Java primitive lane ({@link PrimitiveType}) or {@link ShortCarriesFloat16Type} for + * {@code Float16Vector}. */ public abstract class VectorType implements CodeGenerationDataNameType { private static final Random RANDOM = Utils.getRandomInstance(); @@ -73,6 +78,11 @@ public abstract class VectorType implements CodeGenerationDataNameType { public static final VectorType.Vector DOUBLE_256 = new VectorType.Vector(DOUBLES, 4); public static final VectorType.Vector DOUBLE_512 = new VectorType.Vector(DOUBLES, 8); + public static final VectorType.Vector FLOAT16_64 = new VectorType.Vector(ShortCarriesFloat16Type.SHORT_CARRIES_FLOAT16, 4); + public static final VectorType.Vector FLOAT16_128 = new VectorType.Vector(ShortCarriesFloat16Type.SHORT_CARRIES_FLOAT16, 8); + public static final VectorType.Vector FLOAT16_256 = new VectorType.Vector(ShortCarriesFloat16Type.SHORT_CARRIES_FLOAT16, 16); + public static final VectorType.Vector FLOAT16_512 = new VectorType.Vector(ShortCarriesFloat16Type.SHORT_CARRIES_FLOAT16, 32); + private final String vectorTypeName; private VectorType(String vectorTypeName) { @@ -95,8 +105,8 @@ public abstract class VectorType implements CodeGenerationDataNameType { return this == other; } - private static final String vectorTypeName(PrimitiveType elementType) { - return switch(elementType.name()) { + private static final String vectorTypeName(VectorElementType elementType) { + return switch(elementType.vectorElementClass()) { case "byte" -> "ByteVector"; case "short" -> "ShortVector"; case "char" -> throw new UnsupportedOperationException("VectorAPI has no char vector type"); @@ -104,19 +114,20 @@ public abstract class VectorType implements CodeGenerationDataNameType { case "long" -> "LongVector"; case "float" -> "FloatVector"; case "double" -> "DoubleVector"; - default -> throw new UnsupportedOperationException("Not supported: " + elementType.name()); + case "Float16" -> "Float16Vector"; + default -> throw new UnsupportedOperationException("Not supported: " + elementType.vectorElementClass()); }; } public static final class Vector extends VectorType { - public final PrimitiveType elementType; + public final VectorElementType elementType; public final int length; // lane count public final String speciesName; public final Mask maskType; public final Shuffle shuffleType; - private Vector(PrimitiveType elementType, int length) { + private Vector(VectorElementType elementType, int length) { super(vectorTypeName(elementType)); this.elementType = elementType; this.length = length; @@ -132,7 +143,7 @@ public abstract class VectorType implements CodeGenerationDataNameType { return List.of(name(), ".zero(", speciesName, ")"); } else if (r <= 8) { return List.of( - name(), ".fromArray(", speciesName, ", new ", elementType.name(), "[] {", + name(), ".fromArray(", speciesName, ", new ", elementType.carrierTypeName(), "[] {", elementType.con(), Stream.generate(() -> List.of(", ", elementType.con()) diff --git a/test/hotspot/jtreg/compiler/lib/verify/Verify.java b/test/hotspot/jtreg/compiler/lib/verify/Verify.java index c79ad2c55a0..32463bf3454 100644 --- a/test/hotspot/jtreg/compiler/lib/verify/Verify.java +++ b/test/hotspot/jtreg/compiler/lib/verify/Verify.java @@ -52,6 +52,11 @@ import java.util.HashMap; * This applies to the boxed floating types, as well as arrays of floating arrays. With * {@link Verify#checkEQWithRawBits} we compare the raw bits, and so different NaN encodings are not equal. * Note: {@link MemorySegment} data is always compared with raw bits. + * + *

    + * The same NaN handling applies to {@code Float16}: both the scalar {@code Float16} box and the + * {@code Float16Vector} lanes (whose {@code short} carrier bits encode Float16 values) are compared + * with the selected NaN mode rather than as raw {@code short}s. */ public final class Verify { private final boolean isFloatCheckWithRawBits; @@ -455,9 +460,58 @@ public final class Verify { } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { throw new RuntimeException("Could not invoke toArray on " + ca.getName(), e); } + // A Float16Vector carries its lanes in a short[], but those short bits encode Float16 + // values rather than plain shorts. Comparing them as a raw short[] would treat distinct + // NaN encodings as unequal, even in the non-raw mode. Compare them with Float16 NaN + // semantics instead. + if (va instanceof short[] sa && vb instanceof short[] sb && isFloat16VectorClass(ca)) { + checkEQForFloat16Carrier(sa, sb, field + ".toArray", aParent, bParent); + return; + } checkEQdispatch(va, vb, field + ".toArray", aParent, bParent); } + private static boolean isFloat16VectorClass(Class c) { + // The concrete classes (Float16Vector64/128/256/512/Max) all extend Float16Vector. + for (Class k = c; k != null; k = k.getSuperclass()) { + if (k.getName().equals("jdk.incubator.vector.Float16Vector")) { + return true; + } + } + return false; + } + + /** + * Compare the {@code short[]} carriers of two {@code Float16Vector}s. The short bits encode + * Float16 values, so in the non-raw mode we canonicalize NaN by widening each lane to float + * via {@link Float#float16ToFloat}, and then reuse the float canonicalization. In the raw mode we + * compare the carrier bits directly, so distinct NaN encodings are not equal. See {@link #isFloatEQ}. + */ + private void checkEQForFloat16Carrier(short[] a, short[] b, String field, Object aParent, Object bParent) { + if (a.length != b.length) { + System.err.println("ERROR: Equality matching failed: length mismatch: " + a.length + " vs " + b.length); + print(a, b, field, aParent, bParent); + throw new VerifyException("Float16 array length mismatch."); + } + + for (int i = 0; i < a.length; i++) { + if (!isFloat16EQ(a[i], b[i])) { + System.err.println("ERROR: Equality matching failed: value mismatch at " + i + ": " + a[i] + " vs " + b[i] + ". check raw: " + isFloatCheckWithRawBits); + print(a, b, field, aParent, bParent); + throw new VerifyException("Float16 array value mismatch " + a[i] + " vs " + b[i]); + } + } + } + + /** + * For Float16 we widen each lane to float, which is exact and lossless and maps every NaN encoding + * to the canonical float NaN, and then reuse the float canonicalization. + */ + private boolean isFloat16EQ(short a, short b) { + return isFloatCheckWithRawBits ? a == b + : Float.floatToIntBits(Float.float16ToFloat(a)) == Float.floatToIntBits(Float.float16ToFloat(b)); + } + private static boolean isFloat16Class(Class c) { return c.getName().equals("jdk.incubator.vector.Float16"); } diff --git a/test/hotspot/jtreg/compiler/vectorapi/VectorExpressionFuzzer.java b/test/hotspot/jtreg/compiler/vectorapi/VectorExpressionFuzzer.java index cb5b95109f5..3413ece592f 100644 --- a/test/hotspot/jtreg/compiler/vectorapi/VectorExpressionFuzzer.java +++ b/test/hotspot/jtreg/compiler/vectorapi/VectorExpressionFuzzer.java @@ -66,6 +66,8 @@ import compiler.lib.template_framework.library.Expression.Nesting; import compiler.lib.template_framework.library.Operations; import compiler.lib.template_framework.library.TestFrameworkClass; import compiler.lib.template_framework.library.PrimitiveType; +import compiler.lib.template_framework.library.ShortCarriesFloat16Type; +import compiler.lib.template_framework.library.VectorElementType; import compiler.lib.template_framework.library.VectorType; /** @@ -162,23 +164,39 @@ public class VectorExpressionFuzzer { // - We check correctness with a reference method that does the same but runs in the interpreter. // - Input values are delivered via fields or array loads. // - The final vector is written into an array, and that array is returned. - var template2Body = Template.make("expression", "arguments", (Expression expression, List arguments) -> scope( - let("elementType", ((VectorType.Vector)expression.returnType).elementType), - """ - try { - #elementType[] out = new #elementType[1000]; - """, - expression.asToken(arguments), ".intoArray(out, 0);\n", - "return out;\n", - expression.info.exceptions.stream().map(exception -> - "} catch (" + exception + " e) { return e;\n" - ).toList(), - """ - } finally { - // Just javac is happy if there are no exceptions to catch. - } - """ - )); + // + // NaN canonicalization (Float16Vector only): the {@code short} carrier of Float16Vector lanes + // distinguishes multiple NaN bit patterns, so a structural comparison between two distinct NaN + // bit patterns would spuriously fail. We widen the {@code short[]} carrier to {@code float[]} + // via {@link Float#float16ToFloat}, which returns a canonical NaN for any NaN input. + var template2Body = Template.make("expression", "arguments", (Expression expression, List arguments) -> { + VectorType.Vector retType = (VectorType.Vector) expression.returnType; + boolean float16Result = retType.elementType instanceof ShortCarriesFloat16Type; + return scope( + let("carrierType", retType.elementType.carrierTypeName()), + """ + try { + #carrierType[] out = new #carrierType[1000]; + """, + expression.asToken(arguments), ".intoArray(out, 0);\n", + float16Result + ? """ + // Float16Vector NaN canonicalization: widen short carrier to float for compare. + float[] outF = new float[out.length]; + for (int i = 0; i < out.length; i++) { outF[i] = Float.float16ToFloat(out[i]); } + return outF; + """ + : "return out;\n", + expression.info.exceptions.stream().map(exception -> + "} catch (" + exception + " e) { return e;\n" + ).toList(), + """ + } finally { + // Just javac is happy if there are no exceptions to catch. + } + """ + ); + }); var template2 = Template.make("type", (VectorType.Vector type) -> { // The depth determines roughly how many operations are going to be used in the expression. @@ -210,24 +228,25 @@ public class VectorExpressionFuzzer { )); } default -> { - if (argumentType instanceof PrimitiveType t) { + if (argumentType instanceof VectorElementType vet) { // We can use the LibraryRGN to create a new value for the primitive in each // invocation. We have to make sure to call the LibraryRNG in the "defineAndFill", // so we get the same value for both test and reference. If we called LibraryRNG // for "use", we would get separate values, which is not helpful. arguments.add(new TestArgument( - List.of(t.name(), " ", name, " = ", t.callLibraryRNG(), ";\n"), + List.of(vet.carrierTypeName(), " ", name, " = ", vet.callLibraryRNG(), ";\n"), name, - List.of(t.name(), " ", name), + List.of(vet.carrierTypeName(), " ", name), name )); } else if (argumentType instanceof VectorType.Vector t) { - PrimitiveType et = t.elementType; + VectorElementType et = t.elementType; + String fillMethod = (et instanceof ShortCarriesFloat16Type) ? "fill_float16" : "fill"; arguments.add(new TestArgument( - List.of(et.name(), "[] ", name, " = new ", et.name(), "[1000];\n", - "LibraryRNG.fill(", name,");\n"), + List.of(et.carrierTypeName(), "[] ", name, " = new ", et.carrierTypeName(), "[1000];\n", + "LibraryRNG.", fillMethod, "(", name,");\n"), name, - List.of(et.name(), "[] ", name), + List.of(et.carrierTypeName(), "[] ", name), List.of(t.name(), ".fromArray(", t.speciesName, ", ", name, ", 0)") )); } else if (argumentType instanceof VectorType.Mask t) { diff --git a/test/hotspot/jtreg/testlibrary_tests/verify/tests/TestVerifyFloat16.java b/test/hotspot/jtreg/testlibrary_tests/verify/tests/TestVerifyFloat16.java index 8d1d763a250..7fc3bc11dee 100644 --- a/test/hotspot/jtreg/testlibrary_tests/verify/tests/TestVerifyFloat16.java +++ b/test/hotspot/jtreg/testlibrary_tests/verify/tests/TestVerifyFloat16.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -33,10 +33,12 @@ package verify.tests; import java.lang.foreign.*; +import java.util.Arrays; import java.util.Random; import jdk.test.lib.Utils; import jdk.incubator.vector.Float16; +import jdk.incubator.vector.Float16Vector; import compiler.lib.verify.*; @@ -47,6 +49,8 @@ public class TestVerifyFloat16 { testArrayFloat16(); testRawFloat16(); testFloat16Random(); + testFloat16VectorCarrier(); + testFloat16VectorCarrierRandom(); } public static void testArrayFloat16() { @@ -113,6 +117,84 @@ public class TestVerifyFloat16 { } } + /** + * Exercises the {@code Float16Vector} short[]-carrier path in Verify + * (checkEQForFloat16Carrier). The short carrier bits encode Float16 values, so the + * non-raw mode must canonicalize NaN (distinct NaN encodings are equal) while the raw + * mode must compare the carrier bits directly (distinct NaN encodings are not equal). + */ + public static void testFloat16VectorCarrier() { + var species = Float16Vector.SPECIES_128; + int len = species.length(); + + // Two different NaN encodings of Float16. + short nan1 = (short)0xFFFF; + short nan2 = (short)0x7FFF; + + short[] aBits = new short[len]; + short[] bBits = new short[len]; + Arrays.fill(aBits, nan1); + Arrays.fill(bBits, nan2); + Float16Vector va = Float16Vector.fromArray(species, aBits, 0); + Float16Vector vb = Float16Vector.fromArray(species, bBits, 0); + + // Same vector: equal in both modes. + Verify.checkEQ(va, va); + Verify.checkEQWithRawBits(va, va); + + // Distinct NaN encodings: equal in non-raw mode (canonicalized) ... + Verify.checkEQ(va, vb); + // ... but not equal in raw mode. + checkNEWithRawBits(va, vb); + + // A real value mismatch must fail in both modes. + short[] oneBits = new short[len]; + short[] twoBits = new short[len]; + Arrays.fill(oneBits, Float16.float16ToShortBits(Float16.valueOf(1f))); + Arrays.fill(twoBits, Float16.float16ToShortBits(Float16.valueOf(2f))); + Float16Vector vOne = Float16Vector.fromArray(species, oneBits, 0); + Float16Vector vTwo = Float16Vector.fromArray(species, twoBits, 0); + Verify.checkEQ(vOne, vOne); + Verify.checkEQWithRawBits(vOne, vOne); + checkNE(vOne, vTwo); + checkNEWithRawBits(vOne, vTwo); + + // NaN vs a real number: not equal in either mode. + checkNE(va, vOne); + checkNEWithRawBits(va, vOne); + } + + public static void testFloat16VectorCarrierRandom() { + var species = Float16Vector.SPECIES_128; + int len = species.length(); + // Testing all 2^16 * 2^16 = 2^32 would take a bit long, so we randomly sample instead. + for (int i = 0; i < 10_000; i++) { + short bitsA = (short)RANDOM.nextInt(); + short bitsB = (short)RANDOM.nextInt(); + short[] aBits = new short[len]; + short[] bBits = new short[len]; + Arrays.fill(aBits, bitsA); + Arrays.fill(bBits, bitsB); + Float16Vector va = Float16Vector.fromArray(species, aBits, 0); + Float16Vector vb = Float16Vector.fromArray(species, bBits, 0); + + // Raw mode: equal iff identical carrier bits. + if (bitsA == bitsB) { + Verify.checkEQWithRawBits(va, vb); + } else { + checkNEWithRawBits(va, vb); + } + + // Non-raw mode: equal iff the canonicalized Float16 values match. + if (Float.floatToIntBits(Float.float16ToFloat(bitsA)) == + Float.floatToIntBits(Float.float16ToFloat(bitsB))) { + Verify.checkEQ(va, vb); + } else { + checkNE(va, vb); + } + } + } + public static void checkNE(Object a, Object b) { try { Verify.checkEQ(a, b); From 5926bbfa0a479a84ccfaad8f4f11ee081fe9adaf Mon Sep 17 00:00:00 2001 From: Patrick Fontanilla Date: Tue, 7 Jul 2026 20:23:42 +0000 Subject: [PATCH 098/305] 8386512: Shenandoah: Add a diagnostic option to facilitate testing pinned regions Reviewed-by: kdnilsen, wkemper --- .../gc/shenandoah/shenandoahConcurrentGC.cpp | 18 ++++++++++ .../gc/shenandoah/shenandoahDegeneratedGC.cpp | 1 + .../share/gc/shenandoah/shenandoahFullGC.cpp | 1 + .../share/gc/shenandoah/shenandoahHeap.cpp | 36 ++++++++++++++++++- .../share/gc/shenandoah/shenandoahHeap.hpp | 13 +++++++ .../share/gc/shenandoah/shenandoahOldGC.cpp | 1 + .../gc/shenandoah/shenandoah_globals.hpp | 6 ++++ .../jtreg/gc/TestAllocHumongousFragment.java | 10 ++++++ .../gc/shenandoah/TestAllocIntArrays.java | 10 ++++++ .../gc/shenandoah/TestAllocObjectArrays.java | 16 +++++++++ .../jtreg/gc/shenandoah/TestAllocObjects.java | 10 ++++++ .../jtreg/gc/shenandoah/TestJcmdHeapDump.java | 5 +++ .../jtreg/gc/shenandoah/TestLotsOfCycles.java | 6 ++++ .../gc/shenandoah/TestRetainObjects.java | 10 ++++++ .../jtreg/gc/shenandoah/TestSieveObjects.java | 10 ++++++ .../gcbasher/TestGCBasherWithShenandoah.java | 11 ++++++ .../stress/gcold/TestGCOldWithShenandoah.java | 5 +++ 17 files changed, 168 insertions(+), 1 deletion(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp index 28f04de2f86..18f8f5a4142 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahConcurrentGC.cpp @@ -109,6 +109,7 @@ void ShenandoahConcurrentGC::entry_concurrent_update_refs_prepare(ShenandoahHeap ShenandoahConcurrentPhase gc_phase(msg, ShenandoahPhaseTimings::conc_update_refs_prepare); EventMark em("%s", msg); + heap->try_inject_pin(); // Evacuation is complete, retire gc labs and change gc state heap->concurrent_prepare_for_update_refs(); } @@ -125,6 +126,7 @@ void ShenandoahConcurrentGC::entry_update_card_table() { ShenandoahWorkerPolicy::calc_workers_for_conc_evac(), "concurrent update cards"); + heap->try_inject_pin(); // Heap needs to be parsable here. // Also, parallel heap region iterate must have a phase set. assert(ShenandoahTimingsTracker::is_current_phase_valid(), "Current phase must be set"); @@ -301,6 +303,7 @@ void ShenandoahConcurrentGC::entry_complete_abbreviated_cycle() { ShenandoahWorkerPolicy::calc_workers_for_conc_evac(), msg); + heap->try_inject_pin(); // We chose not to evacuate because we found sufficient immediate garbage. // However, there may still be regions to promote in place, so do that now. if (heap->old_generation()->has_in_place_promotions()) { @@ -335,6 +338,7 @@ void ShenandoahConcurrentGC::vmop_entry_final_mark() { heap->try_inject_alloc_failure(); VM_ShenandoahFinalMarkStartEvac op(this); VMThread::execute(&op); // jump to entry_final_mark under safepoint + heap->try_inject_pin(); } void ShenandoahConcurrentGC::vmop_entry_init_update_refs() { @@ -343,6 +347,7 @@ void ShenandoahConcurrentGC::vmop_entry_init_update_refs() { ShenandoahTimingsTracker timing(ShenandoahPhaseTimings::init_update_refs_gross); heap->try_inject_alloc_failure(); + heap->try_inject_pin(); VM_ShenandoahInitUpdateRefs op(this); VMThread::execute(&op); } @@ -353,6 +358,7 @@ void ShenandoahConcurrentGC::vmop_entry_final_update_refs() { ShenandoahTimingsTracker timing(ShenandoahPhaseTimings::final_update_refs_gross); heap->try_inject_alloc_failure(); + heap->try_inject_pin(); VM_ShenandoahFinalUpdateRefs op(this); VMThread::execute(&op); } @@ -364,6 +370,7 @@ void ShenandoahConcurrentGC::vmop_entry_final_verify() { // This phase does not use workers, no need for setup heap->try_inject_alloc_failure(); + heap->try_inject_pin(); VM_ShenandoahFinalVerify op(this); VMThread::execute(&op); } @@ -423,6 +430,7 @@ void ShenandoahConcurrentGC::entry_final_verify() { void ShenandoahConcurrentGC::entry_reset() { ShenandoahHeap* const heap = ShenandoahHeap::heap(); + heap->release_injected_pins(); heap->try_inject_alloc_failure(); TraceCollectorStats tcs(heap->monitoring_support()->concurrent_collection_counters()); @@ -483,6 +491,7 @@ void ShenandoahConcurrentGC::entry_mark() { heap->try_inject_alloc_failure(); op_mark(); + heap->try_inject_pin(); } void ShenandoahConcurrentGC::entry_thread_roots() { @@ -496,6 +505,7 @@ void ShenandoahConcurrentGC::entry_thread_roots() { msg); heap->try_inject_alloc_failure(); + heap->try_inject_pin(); op_thread_roots(); } @@ -510,6 +520,7 @@ void ShenandoahConcurrentGC::entry_weak_refs() { "concurrent weak references"); heap->try_inject_alloc_failure(); + heap->try_inject_pin(); op_weak_refs(); } @@ -525,6 +536,7 @@ void ShenandoahConcurrentGC::entry_weak_roots() { "concurrent weak root"); heap->try_inject_alloc_failure(); + heap->try_inject_pin(); op_weak_roots(); } @@ -540,6 +552,7 @@ void ShenandoahConcurrentGC::entry_class_unloading() { "concurrent class unloading"); heap->try_inject_alloc_failure(); + heap->try_inject_pin(); op_class_unloading(); } @@ -557,6 +570,7 @@ void ShenandoahConcurrentGC::entry_strong_roots() { "concurrent strong root"); heap->try_inject_alloc_failure(); + heap->try_inject_pin(); op_strong_roots(); } @@ -569,6 +583,7 @@ void ShenandoahConcurrentGC::entry_cleanup_early() { // This phase does not use workers, no need for setup heap->try_inject_alloc_failure(); + heap->try_inject_pin(); op_cleanup_early(); if (!heap->is_evacuation_in_progress()) { // This is an abbreviated cycle. Rebuild the freeset in order to establish reserves for the next GC cycle. Doing @@ -591,6 +606,7 @@ void ShenandoahConcurrentGC::entry_evacuate() { "concurrent evacuation"); heap->try_inject_alloc_failure(); + heap->try_inject_pin(); op_evacuate(); } @@ -604,6 +620,7 @@ void ShenandoahConcurrentGC::entry_update_thread_roots() { // No workers used in this phase, no setup required heap->try_inject_alloc_failure(); + heap->try_inject_pin(); op_update_thread_roots(); } @@ -619,6 +636,7 @@ void ShenandoahConcurrentGC::entry_update_refs() { "concurrent reference update"); heap->try_inject_alloc_failure(); + heap->try_inject_pin(); op_update_refs(); } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahDegeneratedGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahDegeneratedGC.cpp index cfa56c3ec20..3c3cdc4a90a 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahDegeneratedGC.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahDegeneratedGC.cpp @@ -91,6 +91,7 @@ void ShenandoahDegenGC::entry_degenerated() { void ShenandoahDegenGC::op_degenerated() { ShenandoahHeap* const heap = ShenandoahHeap::heap(); + heap->release_injected_pins(); // Degenerated GC is STW, but it can also fail. Current mechanics communicates // GC failure via cancelled_concgc() flag. So, if we detect the failure after // some phase, we have to upgrade the Degenerate GC to Full GC. diff --git a/src/hotspot/share/gc/shenandoah/shenandoahFullGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahFullGC.cpp index cd04db383ed..cab0db7e78a 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahFullGC.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahFullGC.cpp @@ -136,6 +136,7 @@ void ShenandoahFullGC::op_full(GCCause::Cause cause) { void ShenandoahFullGC::do_it(GCCause::Cause gc_cause) { ShenandoahHeap* heap = ShenandoahHeap::heap(); + heap->release_injected_pins(); // A full GC may be entered directly, or as an upgrade from a failed // degenerated GC. In the latter case, self-forwarded objects may be diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp index ae0c873fa58..7731ad911c5 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.cpp @@ -595,7 +595,8 @@ ShenandoahHeap::ShenandoahHeap(ShenandoahCollectorPolicy* policy) : _aux_bitmap_region_special(false), _liveness_cache(nullptr), _collection_set(nullptr), - _evac_tracker(new ShenandoahEvacuationTracker()) + _evac_tracker(new ShenandoahEvacuationTracker()), + _injected_pin_count(0) { // Initialize GC mode early, many subsequent initialization procedures depend on it initialize_mode(); @@ -2752,6 +2753,39 @@ bool ShenandoahHeap::should_inject_alloc_failure() { return _inject_alloc_failure.is_set() && _inject_alloc_failure.try_unset(); } +void ShenandoahHeap::try_inject_pin() { + assert(!ShenandoahSafepoint::is_at_shenandoah_safepoint(), "try_inject_pin() must be called outside a safepoint."); + assert(active_generation() != nullptr, "Active generation must be set before we inject pins."); + assert(is_concurrent_mark_in_progress() || active_generation()->is_mark_complete(), + "try_inject_pin() requires marking is in progress or has completed."); + if (ShenandoahPinRegionRate && !cancelled_gc() && ((uintx)(os::random() % 1000) < ShenandoahPinRegionRate) && + _injected_pin_count < MAX_INJECTED_PINS) { + const size_t idx = os::random() % num_regions(); + ShenandoahHeapRegion* r = get_region(idx); + if ((r->is_regular() || r->is_humongous_start()) && r->has_live()) { + r->record_pin(); + _injected_pin_indices[_injected_pin_count] = idx; + _injected_pin_count++; + } + } +} + +void ShenandoahHeap::release_injected_pins() { + if (_injected_pin_count == 0) { + return; + } + + assert(_injected_pin_count <= MAX_INJECTED_PINS, + "Injected pin count: %u exceeds max: %u.", _injected_pin_count, MAX_INJECTED_PINS); + for (uint i = 0; i < _injected_pin_count; i++) { + const size_t idx = _injected_pin_indices[i]; + ShenandoahHeapRegion* r = get_region(idx); + assert(r->pin_count() > 0, "Region %zu in tracker must contain a pin.", idx); + r->record_unpin(); + } + _injected_pin_count = 0; +} + void ShenandoahHeap::initialize_serviceability() { _memory_pool = new ShenandoahMemoryPool(this); _cycle_memory_manager.add_pool(_memory_pool); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp index 9810b316c21..171f473d06a 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.hpp @@ -868,6 +868,19 @@ private: void try_inject_alloc_failure(); bool should_inject_alloc_failure(); + + // Randomly pin a region when ShenandoahPinRegionRate > 0. Pin injection is only called after + // the cycle has populated _live_data and runs concurrently on the control thread. Releasing + // injected pins is done at the start of every cycle preventing stale pinned region states. + void try_inject_pin(); + void release_injected_pins(); + + // Maximum number of regions that can be injected with pins. + static const uint MAX_INJECTED_PINS = 32; + + // Tracker for injected pins added by try_inject_pin(). + size_t _injected_pin_indices[MAX_INJECTED_PINS]; + uint _injected_pin_count; }; #endif // SHARE_GC_SHENANDOAH_SHENANDOAHHEAP_HPP diff --git a/src/hotspot/share/gc/shenandoah/shenandoahOldGC.cpp b/src/hotspot/share/gc/shenandoah/shenandoahOldGC.cpp index df41069d922..c98b96c689b 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahOldGC.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahOldGC.cpp @@ -84,6 +84,7 @@ bool ShenandoahOldGC::collect(GCCause::Cause cause) { auto heap = ShenandoahGenerationalHeap::heap(); assert(!_old_generation->is_doing_mixed_evacuations(), "Should not start an old gc with pending mixed evacuations"); assert(!_old_generation->is_preparing_for_mark(), "Old regions need to be parsable during concurrent mark."); + heap->release_injected_pins(); // Enable preemption of old generation mark. _allow_preemption.set(); diff --git a/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp b/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp index 3647a818490..793b2f3b6d1 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp @@ -461,6 +461,12 @@ product(bool, ShenandoahAllocFailureALot, false, DIAGNOSTIC, \ "Testing: make lots of artificial allocation failures.") \ \ + product(uintx, ShenandoahPinRegionRate, 0, DIAGNOSTIC, \ + "Testing: rate at which to artificially pin regions. Expressed " \ + "as N in 1000 chances for a region to be randomly pinned per " \ + "injection attempt.") \ + range(0, 1000) \ + \ product(uintx, ShenandoahCoalesceChance, 0, DIAGNOSTIC, \ "Testing: Abandon remaining mixed collections with this " \ "likelihood. Following each mixed collection, abandon all " \ diff --git a/test/hotspot/jtreg/gc/TestAllocHumongousFragment.java b/test/hotspot/jtreg/gc/TestAllocHumongousFragment.java index 446cf3c27bb..bcd6e33c81e 100644 --- a/test/hotspot/jtreg/gc/TestAllocHumongousFragment.java +++ b/test/hotspot/jtreg/gc/TestAllocHumongousFragment.java @@ -71,6 +71,11 @@ * * @run main/othervm -Xmx1g -Xms1g -Xlog:gc -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:ShenandoahTargetNumRegions=2048 * -XX:+UseShenandoahGC -XX:ShenandoahGCHeuristics=aggressive + * -XX:ShenandoahPinRegionRate=1000 -XX:+ShenandoahVerify + * TestAllocHumongousFragment + * + * @run main/othervm -Xmx1g -Xms1g -Xlog:gc -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:ShenandoahTargetNumRegions=2048 + * -XX:+UseShenandoahGC -XX:ShenandoahGCHeuristics=aggressive * -XX:+ShenandoahOOMDuringEvacALot * TestAllocHumongousFragment * @@ -78,6 +83,11 @@ * -XX:+UseShenandoahGC -XX:ShenandoahGCHeuristics=aggressive * -XX:+ShenandoahAllocFailureALot * TestAllocHumongousFragment + * + * @run main/othervm -Xmx1g -Xms1g -Xlog:gc -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -XX:ShenandoahTargetNumRegions=2048 + * -XX:+UseShenandoahGC -XX:ShenandoahGCHeuristics=aggressive + * -XX:ShenandoahPinRegionRate=1000 + * TestAllocHumongousFragment */ /* diff --git a/test/hotspot/jtreg/gc/shenandoah/TestAllocIntArrays.java b/test/hotspot/jtreg/gc/shenandoah/TestAllocIntArrays.java index 457af294f6f..8488b8f4a8d 100644 --- a/test/hotspot/jtreg/gc/shenandoah/TestAllocIntArrays.java +++ b/test/hotspot/jtreg/gc/shenandoah/TestAllocIntArrays.java @@ -70,6 +70,11 @@ * * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -Xmx1g -Xms1g * -XX:+UseShenandoahGC -XX:ShenandoahGCHeuristics=aggressive + * -XX:ShenandoahPinRegionRate=1000 -XX:+ShenandoahVerify + * TestAllocIntArrays + * + * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -Xmx1g -Xms1g + * -XX:+UseShenandoahGC -XX:ShenandoahGCHeuristics=aggressive * -XX:+ShenandoahOOMDuringEvacALot * TestAllocIntArrays * @@ -80,6 +85,11 @@ * * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -Xmx1g -Xms1g * -XX:+UseShenandoahGC -XX:ShenandoahGCHeuristics=aggressive + * -XX:ShenandoahPinRegionRate=1000 + * TestAllocIntArrays + * + * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -Xmx1g -Xms1g + * -XX:+UseShenandoahGC -XX:ShenandoahGCHeuristics=aggressive * TestAllocIntArrays */ diff --git a/test/hotspot/jtreg/gc/shenandoah/TestAllocObjectArrays.java b/test/hotspot/jtreg/gc/shenandoah/TestAllocObjectArrays.java index 1df8f7453f7..bc8c451450c 100644 --- a/test/hotspot/jtreg/gc/shenandoah/TestAllocObjectArrays.java +++ b/test/hotspot/jtreg/gc/shenandoah/TestAllocObjectArrays.java @@ -70,6 +70,11 @@ * * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -Xmx1g -Xms1g * -XX:+UseShenandoahGC -XX:ShenandoahGCHeuristics=aggressive + * -XX:ShenandoahPinRegionRate=1000 -XX:+ShenandoahVerify + * TestAllocObjectArrays + * + * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -Xmx1g -Xms1g + * -XX:+UseShenandoahGC -XX:ShenandoahGCHeuristics=aggressive * -XX:+ShenandoahOOMDuringEvacALot * TestAllocObjectArrays * @@ -80,6 +85,11 @@ * * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -Xmx1g -Xms1g * -XX:+UseShenandoahGC -XX:ShenandoahGCHeuristics=aggressive + * -XX:ShenandoahPinRegionRate=1000 + * TestAllocObjectArrays + * + * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -Xmx1g -Xms1g + * -XX:+UseShenandoahGC -XX:ShenandoahGCHeuristics=aggressive * TestAllocObjectArrays */ @@ -126,6 +136,12 @@ * * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -Xmx1g -Xms1g * -XX:+UseShenandoahGC -XX:ShenandoahGCHeuristics=adaptive -XX:ShenandoahGCMode=generational + * -XX:ShenandoahPinRegionRate=1000 + * -XX:+ShenandoahVerify + * TestAllocObjectArrays + * + * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -Xmx1g -Xms1g + * -XX:+UseShenandoahGC -XX:ShenandoahGCHeuristics=adaptive -XX:ShenandoahGCMode=generational * TestAllocObjectArrays */ diff --git a/test/hotspot/jtreg/gc/shenandoah/TestAllocObjects.java b/test/hotspot/jtreg/gc/shenandoah/TestAllocObjects.java index fa6f3ab9b04..a1d06945b79 100644 --- a/test/hotspot/jtreg/gc/shenandoah/TestAllocObjects.java +++ b/test/hotspot/jtreg/gc/shenandoah/TestAllocObjects.java @@ -64,6 +64,11 @@ * -XX:+ShenandoahAllocFailureALot -XX:+ShenandoahVerify * TestAllocObjects * + * @run main/othervm/timeout=480 -Xmx1g -Xms1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions + * -XX:+UseShenandoahGC -XX:ShenandoahGCHeuristics=aggressive + * -XX:ShenandoahPinRegionRate=1000 -XX:+ShenandoahVerify + * TestAllocObjects + * * @run main/othervm -Xmx1g -Xms1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -XX:ShenandoahGCHeuristics=aggressive * -XX:+ShenandoahOOMDuringEvacALot @@ -76,6 +81,11 @@ * * @run main/othervm -Xmx1g -Xms1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -XX:ShenandoahGCHeuristics=aggressive + * -XX:ShenandoahPinRegionRate=1000 + * TestAllocObjects + * + * @run main/othervm -Xmx1g -Xms1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions + * -XX:+UseShenandoahGC -XX:ShenandoahGCHeuristics=aggressive * TestAllocObjects */ diff --git a/test/hotspot/jtreg/gc/shenandoah/TestJcmdHeapDump.java b/test/hotspot/jtreg/gc/shenandoah/TestJcmdHeapDump.java index 1b607bf96ca..e790851e2e8 100644 --- a/test/hotspot/jtreg/gc/shenandoah/TestJcmdHeapDump.java +++ b/test/hotspot/jtreg/gc/shenandoah/TestJcmdHeapDump.java @@ -58,6 +58,11 @@ * * @run main/othervm/timeout=480 -Xmx16m -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -XX:ShenandoahGCHeuristics=aggressive + * -XX:ShenandoahPinRegionRate=1000 + * TestJcmdHeapDump + * + * @run main/othervm/timeout=480 -Xmx16m -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions + * -XX:+UseShenandoahGC -XX:ShenandoahGCHeuristics=aggressive * TestJcmdHeapDump */ diff --git a/test/hotspot/jtreg/gc/shenandoah/TestLotsOfCycles.java b/test/hotspot/jtreg/gc/shenandoah/TestLotsOfCycles.java index 569406fa95c..fbf3cd5c34b 100644 --- a/test/hotspot/jtreg/gc/shenandoah/TestLotsOfCycles.java +++ b/test/hotspot/jtreg/gc/shenandoah/TestLotsOfCycles.java @@ -58,6 +58,12 @@ * * @run main/othervm/timeout=480 -Xmx16m -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -XX:ShenandoahGCHeuristics=aggressive + * -XX:ShenandoahPinRegionRate=1000 + * -Dtarget=1000 + * TestLotsOfCycles + * + * @run main/othervm/timeout=480 -Xmx16m -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions + * -XX:+UseShenandoahGC -XX:ShenandoahGCHeuristics=aggressive * -Dtarget=1000 * TestLotsOfCycles */ diff --git a/test/hotspot/jtreg/gc/shenandoah/TestRetainObjects.java b/test/hotspot/jtreg/gc/shenandoah/TestRetainObjects.java index d25c8dd0f5e..010bdb5e4f1 100644 --- a/test/hotspot/jtreg/gc/shenandoah/TestRetainObjects.java +++ b/test/hotspot/jtreg/gc/shenandoah/TestRetainObjects.java @@ -66,6 +66,11 @@ * * @run main/othervm -Xmx1g -Xms1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -XX:ShenandoahGCHeuristics=aggressive + * -XX:ShenandoahPinRegionRate=1000 + * TestRetainObjects + * + * @run main/othervm -Xmx1g -Xms1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions + * -XX:+UseShenandoahGC -XX:ShenandoahGCHeuristics=aggressive * TestRetainObjects */ @@ -106,6 +111,11 @@ * * @run main/othervm -Xmx1g -Xms1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -XX:ShenandoahGCHeuristics=adaptive -XX:ShenandoahGCMode=generational + * -XX:ShenandoahPinRegionRate=1000 -XX:+ShenandoahVerify + * TestRetainObjects + * + * @run main/othervm -Xmx1g -Xms1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions + * -XX:+UseShenandoahGC -XX:ShenandoahGCHeuristics=adaptive -XX:ShenandoahGCMode=generational * TestRetainObjects */ diff --git a/test/hotspot/jtreg/gc/shenandoah/TestSieveObjects.java b/test/hotspot/jtreg/gc/shenandoah/TestSieveObjects.java index 79259168bf3..fa140d62a66 100644 --- a/test/hotspot/jtreg/gc/shenandoah/TestSieveObjects.java +++ b/test/hotspot/jtreg/gc/shenandoah/TestSieveObjects.java @@ -68,6 +68,11 @@ * -XX:+ShenandoahAllocFailureALot * TestSieveObjects * + * @run main/othervm/timeout=240 -Xmx1g -Xms1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions + * -XX:+UseShenandoahGC -XX:ShenandoahGCHeuristics=aggressive + * -XX:ShenandoahPinRegionRate=1000 + * TestSieveObjects + * * @run main/othervm -Xmx1g -Xms1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -XX:ShenandoahGCHeuristics=aggressive * TestSieveObjects @@ -108,6 +113,11 @@ * -XX:+ShenandoahAllocFailureALot -XX:+ShenandoahVerify * TestSieveObjects * + * @run main/othervm/timeout=480 -Xmx1g -Xms1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions + * -XX:+UseShenandoahGC -XX:ShenandoahGCHeuristics=adaptive -XX:ShenandoahGCMode=generational + * -XX:ShenandoahPinRegionRate=1000 -XX:+ShenandoahVerify + * TestSieveObjects + * * @run main/othervm -Xmx1g -Xms1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -XX:ShenandoahGCHeuristics=adaptive -XX:ShenandoahGCMode=generational * TestSieveObjects diff --git a/test/hotspot/jtreg/gc/stress/gcbasher/TestGCBasherWithShenandoah.java b/test/hotspot/jtreg/gc/stress/gcbasher/TestGCBasherWithShenandoah.java index 3bf0e59dce3..532bf6c07de 100644 --- a/test/hotspot/jtreg/gc/stress/gcbasher/TestGCBasherWithShenandoah.java +++ b/test/hotspot/jtreg/gc/stress/gcbasher/TestGCBasherWithShenandoah.java @@ -66,6 +66,11 @@ import java.io.IOException; * * @run main/othervm/timeout=200 -Xlog:gc*=info -Xmx1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -XX:ShenandoahGCHeuristics=aggressive + * -XX:ShenandoahPinRegionRate=1000 + * gc.stress.gcbasher.TestGCBasherWithShenandoah 120000 + * + * @run main/othervm/timeout=200 -Xlog:gc*=info -Xmx1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions + * -XX:+UseShenandoahGC -XX:ShenandoahGCHeuristics=aggressive * gc.stress.gcbasher.TestGCBasherWithShenandoah 120000 */ @@ -144,6 +149,12 @@ import java.io.IOException; * @run main/othervm/timeout=200 -Xlog:gc*=info,nmethod+barrier=trace -Xmx1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -XX:ShenandoahGCHeuristics=aggressive * -XX:+DeoptimizeNMethodBarriersALot -XX:-Inline + * -XX:ShenandoahPinRegionRate=1000 + * gc.stress.gcbasher.TestGCBasherWithShenandoah 120000 + * + * @run main/othervm/timeout=200 -Xlog:gc*=info,nmethod+barrier=trace -Xmx1g -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions + * -XX:+UseShenandoahGC -XX:ShenandoahGCHeuristics=aggressive + * -XX:+DeoptimizeNMethodBarriersALot -XX:-Inline * gc.stress.gcbasher.TestGCBasherWithShenandoah 120000 */ diff --git a/test/hotspot/jtreg/gc/stress/gcold/TestGCOldWithShenandoah.java b/test/hotspot/jtreg/gc/stress/gcold/TestGCOldWithShenandoah.java index 5418bb12492..9b2eb530b2a 100644 --- a/test/hotspot/jtreg/gc/stress/gcold/TestGCOldWithShenandoah.java +++ b/test/hotspot/jtreg/gc/stress/gcold/TestGCOldWithShenandoah.java @@ -71,6 +71,11 @@ package gc.stress.gcold; * * @run main/othervm -Xmx384M -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+UseShenandoahGC -XX:ShenandoahGCHeuristics=aggressive + * -XX:ShenandoahPinRegionRate=1000 + * gc.stress.gcold.TestGCOld 50 1 20 10 10000 + * + * @run main/othervm -Xmx384M -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions + * -XX:+UseShenandoahGC -XX:ShenandoahGCHeuristics=aggressive * gc.stress.gcold.TestGCOld 50 1 20 10 10000 */ From ff32e76f4892c0b4c0fbe1efa1ebf7b94023870b Mon Sep 17 00:00:00 2001 From: Jan Lahoda Date: Wed, 8 Jul 2026 05:44:30 +0000 Subject: [PATCH 099/305] 8387865: ThisEscapeAnalyzer crashes for erroneous source code Reviewed-by: asotona, vromero --- .../tools/javac/comp/ThisEscapeAnalyzer.java | 3 +- .../tools/javac/recovery/AttrRecovery.java | 29 ++++++++++++++++++- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java index 6fb1feed08d..e24b331dd63 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/ThisEscapeAnalyzer.java @@ -873,11 +873,10 @@ public class ThisEscapeAnalyzer extends TreeScanner { @Override public void visitAssign(JCAssign tree) { - VarSymbol sym = (VarSymbol)TreeInfo.symbolFor(tree.lhs); scan(tree.lhs); refs.discardExprs(depth); scan(tree.rhs); - if (isParamOrVar(sym)) + if (TreeInfo.symbolFor(tree.lhs) instanceof VarSymbol sym && isParamOrVar(sym)) refs.replaceExprs(depth, ref -> new VarRef(sym, ref)); else refs.discardExprs(depth); // we don't track fields yet diff --git a/test/langtools/tools/javac/recovery/AttrRecovery.java b/test/langtools/tools/javac/recovery/AttrRecovery.java index 64aaad2a184..fb852b5b3f2 100644 --- a/test/langtools/tools/javac/recovery/AttrRecovery.java +++ b/test/langtools/tools/javac/recovery/AttrRecovery.java @@ -23,7 +23,7 @@ /* * @test - * @bug 8301580 8322159 8333107 8332230 8338678 8351260 8366196 8372336 8373094 8384229 + * @bug 8301580 8322159 8333107 8332230 8338678 8351260 8366196 8372336 8373094 8384229 8387865 * @summary Verify error recovery w.r.t. Attr * @library /tools/lib * @modules jdk.compiler/com.sun.tools.javac.api @@ -860,6 +860,33 @@ public class AttrRecovery { .writeAll(); } + @Test //JDK-8387865 + public void testThisEscapeUnknownField() throws Exception { + String code = """ + public class C { + public C() { + this.unknown = unknown; + } + } + """; + List actual = new JavacTask(tb) + .options("-XDrawDiagnostics", "-XDdev", + "-XDshould-stop.at=WARN", "-Xlint:this-escape") + .sources(code) + .outdir(base) + .run(Expect.FAIL) + .writeAll() + .getOutputLines(OutputKind.DIRECT); + + List expected = List.of( + "C.java:3:13: compiler.err.cant.resolve: kindname.variable, unknown, , ", + "C.java:3:24: compiler.err.cant.resolve.location: kindname.variable, unknown, , , (compiler.misc.location: kindname.class, C, null)", + "2 errors" + ); + + assertEquals(expected, actual); + } + @BeforeEach public void setUp(TestInfo info) throws IOException { base = Path.of(info.getTestMethod().orElseThrow().getName()); From 597053969e4055227d1931d4bce8e96930bd997f Mon Sep 17 00:00:00 2001 From: Thomas Schatzl Date: Wed, 8 Jul 2026 06:01:28 +0000 Subject: [PATCH 100/305] 8387754: G1: Let Eden/SurvivorRegions use Atomic instead of volatile Reviewed-by: aboldtch --- src/hotspot/share/gc/g1/g1EdenRegions.hpp | 14 +++++++------- src/hotspot/share/gc/g1/g1RegionsOnNodes.cpp | 12 +++++------- src/hotspot/share/gc/g1/g1RegionsOnNodes.hpp | 5 +++-- src/hotspot/share/gc/g1/g1SurvivorRegions.cpp | 6 +++--- src/hotspot/share/gc/g1/g1SurvivorRegions.hpp | 7 ++++--- 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/hotspot/share/gc/g1/g1EdenRegions.hpp b/src/hotspot/share/gc/g1/g1EdenRegions.hpp index 7cb4f93519e..14a2eb65329 100644 --- a/src/hotspot/share/gc/g1/g1EdenRegions.hpp +++ b/src/hotspot/share/gc/g1/g1EdenRegions.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,15 +27,15 @@ #include "gc/g1/g1HeapRegion.hpp" #include "gc/g1/g1RegionsOnNodes.hpp" +#include "runtime/atomic.hpp" #include "runtime/globals.hpp" #include "utilities/debug.hpp" class G1EdenRegions { -private: - uint _length; + uint _length; // Sum of used bytes from all retired eden regions. // I.e. updated when mutator regions are retired. - volatile size_t _used_bytes; + Atomic _used_bytes; G1RegionsOnNodes _regions_on_node; public: @@ -49,17 +49,17 @@ public: void clear() { _length = 0; - _used_bytes = 0; + _used_bytes.store_relaxed(0); _regions_on_node.clear(); } uint length() const { return _length; } uint regions_on_node(uint node_index) const { return _regions_on_node.count(node_index); } - size_t used_bytes() const { return _used_bytes; } + size_t used_bytes() const { return _used_bytes.load_relaxed(); } void add_used_bytes(size_t used_bytes) { - _used_bytes += used_bytes; + _used_bytes.add_then_fetch(used_bytes, memory_order_relaxed); } }; diff --git a/src/hotspot/share/gc/g1/g1RegionsOnNodes.cpp b/src/hotspot/share/gc/g1/g1RegionsOnNodes.cpp index 9550e57698e..2e509e79ec3 100644 --- a/src/hotspot/share/gc/g1/g1RegionsOnNodes.cpp +++ b/src/hotspot/share/gc/g1/g1RegionsOnNodes.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,7 +27,7 @@ #include "gc/g1/g1RegionsOnNodes.hpp" G1RegionsOnNodes::G1RegionsOnNodes() : _count_per_node(nullptr), _numa(G1NUMA::numa()) { - _count_per_node = NEW_C_HEAP_ARRAY(uint, _numa->num_active_nodes(), mtGC); + _count_per_node = NEW_C_HEAP_ARRAY(Atomic, _numa->num_active_nodes(), mtGC); clear(); } @@ -40,16 +40,14 @@ void G1RegionsOnNodes::add(G1HeapRegion* hr) { // Update only if the node index is valid. if (node_index < _numa->num_active_nodes()) { - *(_count_per_node + node_index) += 1; + _count_per_node[node_index].add_then_fetch(1u, memory_order_relaxed); } } void G1RegionsOnNodes::clear() { - for (uint i = 0; i < _numa->num_active_nodes(); i++) { - _count_per_node[i] = 0; - } + ::new (_count_per_node) Atomic[_numa->num_active_nodes()]{}; } uint G1RegionsOnNodes::count(uint node_index) const { - return _count_per_node[node_index]; + return _count_per_node[node_index].load_relaxed(); } diff --git a/src/hotspot/share/gc/g1/g1RegionsOnNodes.hpp b/src/hotspot/share/gc/g1/g1RegionsOnNodes.hpp index fb1f2381dba..e528a147150 100644 --- a/src/hotspot/share/gc/g1/g1RegionsOnNodes.hpp +++ b/src/hotspot/share/gc/g1/g1RegionsOnNodes.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,13 +26,14 @@ #define SHARE_VM_GC_G1_G1REGIONS_HPP #include "memory/allocation.hpp" +#include "runtime/atomic.hpp" class G1NUMA; class G1HeapRegion; // Contains per node index region count class G1RegionsOnNodes : public StackObj { - volatile uint* _count_per_node; + Atomic* _count_per_node; G1NUMA* _numa; public: diff --git a/src/hotspot/share/gc/g1/g1SurvivorRegions.cpp b/src/hotspot/share/gc/g1/g1SurvivorRegions.cpp index 84609df4fc9..806df4abacb 100644 --- a/src/hotspot/share/gc/g1/g1SurvivorRegions.cpp +++ b/src/hotspot/share/gc/g1/g1SurvivorRegions.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -55,10 +55,10 @@ void G1SurvivorRegions::convert_to_eden() { void G1SurvivorRegions::clear() { _regions.clear(); - _used_bytes = 0; + _used_bytes.store_relaxed(0); _regions_on_node.clear(); } void G1SurvivorRegions::add_used_bytes(size_t used_bytes) { - _used_bytes += used_bytes; + _used_bytes.add_then_fetch(used_bytes, memory_order_relaxed); } diff --git a/src/hotspot/share/gc/g1/g1SurvivorRegions.hpp b/src/hotspot/share/gc/g1/g1SurvivorRegions.hpp index 4e4966f6797..5ced2fced3c 100644 --- a/src/hotspot/share/gc/g1/g1SurvivorRegions.hpp +++ b/src/hotspot/share/gc/g1/g1SurvivorRegions.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,6 +26,7 @@ #define SHARE_GC_G1_G1SURVIVORREGIONS_HPP #include "gc/g1/g1RegionsOnNodes.hpp" +#include "runtime/atomic.hpp" #include "runtime/globals.hpp" #include "utilities/growableArray.hpp" @@ -36,7 +37,7 @@ class G1HeapRegion; // Set of current survivor regions. class G1SurvivorRegions { GrowableArray _regions; - volatile size_t _used_bytes; + Atomic _used_bytes; G1RegionsOnNodes _regions_on_node; public: @@ -56,7 +57,7 @@ public: } // Used bytes of all survivor regions. - size_t used_bytes() const { return _used_bytes; } + size_t used_bytes() const { return _used_bytes.load_relaxed(); } void add_used_bytes(size_t used_bytes); }; From cc2cd968c5bea6464dae87d2652446c4cb99bdf2 Mon Sep 17 00:00:00 2001 From: Thomas Schatzl Date: Wed, 8 Jul 2026 06:01:46 +0000 Subject: [PATCH 101/305] 8387765: G1: Let G1HeapRegionType::_tag use the Atomic API Reviewed-by: aboldtch --- src/hotspot/share/gc/g1/g1HeapRegionType.cpp | 11 +++---- src/hotspot/share/gc/g1/g1HeapRegionType.hpp | 32 ++++++++++++-------- src/hotspot/share/gc/g1/vmStructs_g1.hpp | 6 ++-- 3 files changed, 27 insertions(+), 22 deletions(-) diff --git a/src/hotspot/share/gc/g1/g1HeapRegionType.cpp b/src/hotspot/share/gc/g1/g1HeapRegionType.cpp index ba6bf7e870d..c62bac8bcbc 100644 --- a/src/hotspot/share/gc/g1/g1HeapRegionType.cpp +++ b/src/hotspot/share/gc/g1/g1HeapRegionType.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -45,8 +45,7 @@ bool G1HeapRegionType::is_valid(Tag tag) { } const char* G1HeapRegionType::get_str() const { - hrt_assert_is_valid(_tag); - switch (_tag) { + switch (get()) { case FreeTag: return "FREE"; case EdenTag: return "EDEN"; case SurvTag: return "SURV"; @@ -60,8 +59,7 @@ const char* G1HeapRegionType::get_str() const { } const char* G1HeapRegionType::get_short_str() const { - hrt_assert_is_valid(_tag); - switch (_tag) { + switch (get()) { case FreeTag: return "F"; case EdenTag: return "E"; case SurvTag: return "S"; @@ -75,8 +73,7 @@ const char* G1HeapRegionType::get_short_str() const { } G1HeapRegionTraceType::Type G1HeapRegionType::get_trace_type() { - hrt_assert_is_valid(_tag); - switch (_tag) { + switch (get()) { case FreeTag: return G1HeapRegionTraceType::Free; case EdenTag: return G1HeapRegionTraceType::Eden; case SurvTag: return G1HeapRegionTraceType::Survivor; diff --git a/src/hotspot/share/gc/g1/g1HeapRegionType.hpp b/src/hotspot/share/gc/g1/g1HeapRegionType.hpp index 92d3efc2f87..3ffa7faecff 100644 --- a/src/hotspot/share/gc/g1/g1HeapRegionType.hpp +++ b/src/hotspot/share/gc/g1/g1HeapRegionType.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,6 +26,7 @@ #define SHARE_GC_G1_G1HEAPREGIONTYPE_HPP #include "gc/g1/g1HeapRegionTraceType.hpp" +#include "runtime/atomic.hpp" #include "utilities/globalDefinitions.hpp" #define hrt_assert_is_valid(tag) \ @@ -34,7 +35,6 @@ class G1HeapRegionType { friend class VMStructs; -private: // We encode the value of the heap region type so the generation can be // determined quickly. The tag is split into two parts: // @@ -73,20 +73,21 @@ private: OldTag = OldMask } Tag; - volatile Tag _tag; + Atomic _tag; static bool is_valid(Tag tag); Tag get() const { - hrt_assert_is_valid(_tag); - return _tag; + Tag result = _tag.load_relaxed(); + hrt_assert_is_valid(result); + return result; } // Sets the type to 'tag'. void set(Tag tag) { hrt_assert_is_valid(tag); - hrt_assert_is_valid(_tag); - _tag = tag; + hrt_assert_is_valid(_tag.load_relaxed()); + _tag.store_relaxed(tag); } // Sets the type to 'tag', expecting the type to be 'before'. This @@ -95,13 +96,12 @@ private: void set_from(Tag tag, Tag before) { hrt_assert_is_valid(tag); hrt_assert_is_valid(before); - hrt_assert_is_valid(_tag); - assert(_tag == before, "HR tag: %u, expected: %u new tag; %u", _tag, before, tag); - _tag = tag; + assert(get() == before, "HR tag: %u, expected: %u new tag; %u", get(), before, tag); + _tag.store_relaxed(tag); } // Private constructor used for static constants - G1HeapRegionType(Tag t) : _tag(t) { hrt_assert_is_valid(_tag); } + G1HeapRegionType(Tag t) : _tag(t) { hrt_assert_is_valid(t); } public: // Queries @@ -159,7 +159,15 @@ public: const char* get_short_str() const; G1HeapRegionTraceType::Type get_trace_type(); - G1HeapRegionType() : _tag(FreeTag) { hrt_assert_is_valid(_tag); } + G1HeapRegionType() : G1HeapRegionType(FreeTag) { } + + G1HeapRegionType(const G1HeapRegionType& other) : G1HeapRegionType(other.get()) { } + G1HeapRegionType& operator=(const G1HeapRegionType& other) { + if (this != &other) { + set(other.get()); + } + return *this; + } static const G1HeapRegionType Eden; static const G1HeapRegionType Survivor; diff --git a/src/hotspot/share/gc/g1/vmStructs_g1.hpp b/src/hotspot/share/gc/g1/vmStructs_g1.hpp index e0179b69646..23beb75211b 100644 --- a/src/hotspot/share/gc/g1/vmStructs_g1.hpp +++ b/src/hotspot/share/gc/g1/vmStructs_g1.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2011, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -44,7 +44,7 @@ nonstatic_field(G1HeapRegion, _end, HeapWord* const) \ nonstatic_field(G1HeapRegion, _pinned_object_count, Atomic) \ \ - nonstatic_field(G1HeapRegionType, _tag, G1HeapRegionType::Tag volatile) \ + nonstatic_field(G1HeapRegionType, _tag, Atomic) \ \ \ nonstatic_field(G1HeapRegionTable, _base, address) \ @@ -104,6 +104,6 @@ declare_toplevel_type(G1HeapRegion*) \ declare_toplevel_type(G1MonitoringSupport*) \ \ - declare_integer_type(G1HeapRegionType::Tag volatile) + declare_integer_type(Atomic) #endif // SHARE_GC_G1_VMSTRUCTS_G1_HPP From cef023b03b6075bf9c0855935fa78ac6e79add3d Mon Sep 17 00:00:00 2001 From: Albert Mingkun Yang Date: Wed, 8 Jul 2026 06:50:30 +0000 Subject: [PATCH 102/305] 8386705: Parallel: Allow NUMA with explicit huge pages and adaptive resizing Reviewed-by: tschatzl, mbaesken --- src/hotspot/os/linux/os_linux.cpp | 14 -------------- src/hotspot/share/gc/parallel/mutableNUMASpace.cpp | 2 +- src/hotspot/share/gc/parallel/mutableSpace.cpp | 2 +- 3 files changed, 2 insertions(+), 16 deletions(-) diff --git a/src/hotspot/os/linux/os_linux.cpp b/src/hotspot/os/linux/os_linux.cpp index ad1f384fa32..aad18edf2a6 100644 --- a/src/hotspot/os/linux/os_linux.cpp +++ b/src/hotspot/os/linux/os_linux.cpp @@ -4662,20 +4662,6 @@ void os::Linux::numa_init() { if (UseNUMA && !UseNUMAInterleaving) { FLAG_SET_ERGO_IF_DEFAULT(UseNUMAInterleaving, true); } - -#if INCLUDE_PARALLELGC - if (UseParallelGC && UseNUMA && UseLargePages && !can_commit_large_page_memory()) { - // With static large pages we cannot uncommit a page, so there's no way - // we can make the adaptive lgrp chunk resizing work. If the user specified both - // UseNUMA and UseLargePages on the command line - warn and disable adaptive resizing. - if (UseAdaptiveSizePolicy || UseAdaptiveNUMAChunkSizing) { - warning("UseNUMA is not fully compatible with +UseLargePages, " - "disabling adaptive resizing (-XX:-UseAdaptiveSizePolicy -XX:-UseAdaptiveNUMAChunkSizing)"); - UseAdaptiveSizePolicy = false; - UseAdaptiveNUMAChunkSizing = false; - } - } -#endif } void os::Linux::disable_numa(const char* reason, bool warning) { diff --git a/src/hotspot/share/gc/parallel/mutableNUMASpace.cpp b/src/hotspot/share/gc/parallel/mutableNUMASpace.cpp index 8b514fe7199..ca4e77bab8e 100644 --- a/src/hotspot/share/gc/parallel/mutableNUMASpace.cpp +++ b/src/hotspot/share/gc/parallel/mutableNUMASpace.cpp @@ -154,7 +154,7 @@ void MutableNUMASpace::bias_region(MemRegion mr, uint lgrp_id) { // First we tell the OS which page size we want in the given range. The underlying // large page can be broken down if we require small pages. os::realign_memory((char*) mr.start(), mr.byte_size(), page_size()); - // Then we uncommit the pages in the range. + // Then we disclaim the pages in the range so they can be faulted in again. os::disclaim_memory((char*) mr.start(), mr.byte_size()); // And make them local/first-touch biased. os::numa_make_local((char*)mr.start(), mr.byte_size(), checked_cast(lgrp_id)); diff --git a/src/hotspot/share/gc/parallel/mutableSpace.cpp b/src/hotspot/share/gc/parallel/mutableSpace.cpp index d99db493989..9b10f9faee2 100644 --- a/src/hotspot/share/gc/parallel/mutableSpace.cpp +++ b/src/hotspot/share/gc/parallel/mutableSpace.cpp @@ -49,7 +49,7 @@ void MutableSpace::numa_setup_pages(MemRegion mr, bool clear_space) { } if (clear_space) { - // Prefer page reallocation to migration. + // Prefer page discard and refault under the requested NUMA policy to migration. os::disclaim_memory((char*) mr.start(), mr.byte_size()); } os::numa_make_global((char*) mr.start(), mr.byte_size()); From 2dc0c1c5a90610460a68803655be351c85bec9e3 Mon Sep 17 00:00:00 2001 From: Thomas Schatzl Date: Wed, 8 Jul 2026 07:54:18 +0000 Subject: [PATCH 103/305] 8358342: G1: G1CodeRootSet performance breaks down on even moderate load Reviewed-by: aboldtch --- src/hotspot/share/gc/g1/g1CodeRootSet.cpp | 35 ++++++- src/hotspot/share/gc/g1/g1CodeRootSet.hpp | 6 +- src/hotspot/share/gc/g1/g1GCPhaseTimes.cpp | 24 ++--- src/hotspot/share/gc/g1/g1GCPhaseTimes.hpp | 23 ++--- .../share/gc/g1/g1HeapRegionRemSet.cpp | 4 + .../share/gc/g1/g1HeapRegionRemSet.hpp | 1 + src/hotspot/share/gc/g1/g1NMethodClosure.cpp | 46 +++++++++- src/hotspot/share/gc/g1/g1NMethodClosure.hpp | 19 +++- .../share/gc/g1/g1ParScanThreadState.cpp | 91 ++++++++++++++++--- .../share/gc/g1/g1ParScanThreadState.hpp | 36 +++++++- .../gc/g1/g1ParScanThreadState.inline.hpp | 33 ++++++- src/hotspot/share/gc/g1/g1Policy.cpp | 6 +- src/hotspot/share/gc/g1/g1RemSet.cpp | 2 + src/hotspot/share/gc/g1/g1RootClosures.hpp | 4 +- src/hotspot/share/gc/g1/g1SharedClosures.hpp | 4 +- .../gc/g1/g1YoungGCPostEvacuateTasks.cpp | 86 ++++++++++++++---- .../gc/g1/g1YoungGCPostEvacuateTasks.hpp | 11 ++- .../jtreg/gc/g1/TestGCLogMessages.java | 5 +- .../gc/collection/TestG1ParallelPhases.java | 7 +- 19 files changed, 360 insertions(+), 83 deletions(-) diff --git a/src/hotspot/share/gc/g1/g1CodeRootSet.cpp b/src/hotspot/share/gc/g1/g1CodeRootSet.cpp index ca4487876b9..7f1dec462d4 100644 --- a/src/hotspot/share/gc/g1/g1CodeRootSet.cpp +++ b/src/hotspot/share/gc/g1/g1CodeRootSet.cpp @@ -196,12 +196,12 @@ public: clean(delete_check); } - // Calculate the log2 of the table size we want to shrink to. - size_t log2_target_shrink_size(size_t current_size) const { + // Calculate the log2 of the table size we want to change to. + size_t log2_target_size(size_t new_size) const { // A table with the new size should be at most filled by this factor. Otherwise // we would grow again quickly. const float WantedLoadFactor = 0.5; - size_t min_expected_size = checked_cast(ceil(current_size / WantedLoadFactor)); + size_t min_expected_size = checked_cast(ceil(new_size / WantedLoadFactor)); size_t result = Log2DefaultNumBuckets; if (min_expected_size != 0) { @@ -214,12 +214,34 @@ public: // Shrink to keep table size appropriate to the given number of entries. void shrink_to_match(size_t current_size) { size_t prev_log2size = _table.get_size_log2(Thread::current()); - size_t new_log2_table_size = log2_target_shrink_size(current_size); + size_t new_log2_table_size = log2_target_size(current_size); if (new_log2_table_size < prev_log2size) { _table.shrink(Thread::current(), new_log2_table_size); } } + void grow_to_match_unsafe(size_t new_size) { + assert_at_safepoint(); + + size_t prev_log2size = _table.get_size_log2(Thread::current()); + size_t new_log2_table_size = log2_target_size(new_size); + // If there is nothing in the table, we can reset directly. Otherwise double + // the table in size until the target is reached, which is the only grow + // operation CHT supports. + if ((prev_log2size != new_log2_table_size) && (number_of_entries() == 0)) { + _table.unsafe_reset(new_log2_table_size); + } else { + while (new_log2_table_size > prev_log2size) { + if (!_table.grow(Thread::current(), new_log2_table_size)) { + // Should always succeed during safepoint. + ShouldNotReachHere(); + break; + } + prev_log2size = _table.get_size_log2(Thread::current()); + } + } + } + void reset_table_scanner() { _table_scanner.set(&_table, BucketClaimSize); } @@ -269,6 +291,11 @@ void G1CodeRootSet::bulk_remove() { _table->bulk_remove(); } +void G1CodeRootSet::prepare_for_adding_code_roots(size_t num_new_code_roots) { + assert(!_is_iterating, "should not mutate while iterating the table"); + _table->grow_to_match_unsafe(_table->number_of_entries() + num_new_code_roots); +} + bool G1CodeRootSet::contains(nmethod* method) { return _table->contains(method); } diff --git a/src/hotspot/share/gc/g1/g1CodeRootSet.hpp b/src/hotspot/share/gc/g1/g1CodeRootSet.hpp index ffa1cddbe54..b298bbfb914 100644 --- a/src/hotspot/share/gc/g1/g1CodeRootSet.hpp +++ b/src/hotspot/share/gc/g1/g1CodeRootSet.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -45,6 +45,10 @@ class G1CodeRootSet { void add(nmethod* method); bool remove(nmethod* method); void bulk_remove(); + // Notify the code root set that we are about to add the given + // number of code roots. Only to be used during safepoint, not + // in parallel to other modifications. + void prepare_for_adding_code_roots(size_t num_code_roots); bool contains(nmethod* method); void clear(); diff --git a/src/hotspot/share/gc/g1/g1GCPhaseTimes.cpp b/src/hotspot/share/gc/g1/g1GCPhaseTimes.cpp index a04b50ec1e7..e5bf8137811 100644 --- a/src/hotspot/share/gc/g1/g1GCPhaseTimes.cpp +++ b/src/hotspot/share/gc/g1/g1GCPhaseTimes.cpp @@ -94,7 +94,8 @@ G1GCPhaseTimes::G1GCPhaseTimes(STWGCTimer* gc_timer, uint max_gc_threads) : _gc_par_phases[GCWorkerTotal] = new WorkerDataArray("GCWorkerTotal", "GC Worker Total (ms):", max_gc_threads); _gc_par_phases[GCWorkerEnd] = new WorkerDataArray("GCWorkerEnd", "GC Worker End (ms):", max_gc_threads); _gc_par_phases[Other] = new WorkerDataArray("Other", "GC Worker Other (ms):", max_gc_threads); - _gc_par_phases[MergePSS] = new WorkerDataArray("MergePSS", "Merge Per-Thread State (ms):", max_gc_threads); + _gc_par_phases[FlushPSS] = new WorkerDataArray("FlushPSS", "Flush Per-Thread State (ms):", max_gc_threads); + _gc_par_phases[DestroyPSS] = new WorkerDataArray("DestroyPSS", "Destroy Per-Thread State (ms):", max_gc_threads); _gc_par_phases[RestoreEvacuationFailedRegions] = new WorkerDataArray("RestoreEvacuationFailedRegions", "Restore Evacuation Failed Regions (ms):", max_gc_threads); _gc_par_phases[RemoveSelfForwards] = new WorkerDataArray("RemoveSelfForwards", "Remove Self Forwards (ms):", max_gc_threads); _gc_par_phases[ClearCardTable] = new WorkerDataArray("ClearPendingCards", "Clear Pending Cards (ms):", max_gc_threads); @@ -103,7 +104,7 @@ G1GCPhaseTimes::G1GCPhaseTimes(STWGCTimer* gc_timer, uint max_gc_threads) : _gc_par_phases[UpdateDerivedPointers] = new WorkerDataArray("UpdateDerivedPointers", "Update Derived Pointers (ms):", max_gc_threads); #endif // COMPILER2 _gc_par_phases[EagerlyReclaimHumongousObjects] = new WorkerDataArray("EagerlyReclaimHumongousObjects", "Eagerly Reclaim Humongous Objects (ms):", max_gc_threads); - _gc_par_phases[ResetPartialArrayStateManager] = new WorkerDataArray("ResetPartialArrayStateManager", "Reset Partial Array State Manager (ms):", max_gc_threads); + _gc_par_phases[UpdateCodeRoots] = new WorkerDataArray("UpdateCodeRoots", "Update Code Roots (ms):", _max_gc_threads); _gc_par_phases[ProcessEvacuationFailedRegions] = new WorkerDataArray("ProcessEvacuationFailedRegions", "Process Evacuation Failed Regions (ms):", max_gc_threads); _gc_par_phases[ScanHR]->create_thread_work_items("Pending Cards:", ScanHRPendingCards); @@ -126,13 +127,13 @@ G1GCPhaseTimes::G1GCPhaseTimes(STWGCTimer* gc_timer, uint max_gc_threads) : _gc_par_phases[OptCodeRoots]->create_thread_work_items("Scanned Nmethods:", CodeRootsScannedNMethods); - _gc_par_phases[MergePSS]->create_thread_work_items("Copied Bytes:", MergePSSCopiedBytes); - _gc_par_phases[MergePSS]->create_thread_work_items("LAB Waste:", MergePSSLABWasteBytes); - _gc_par_phases[MergePSS]->create_thread_work_items("LAB Undo Waste:", MergePSSLABUndoWasteBytes); - _gc_par_phases[MergePSS]->create_thread_work_items("Pending Cards:", MergePSSPendingCards); - _gc_par_phases[MergePSS]->create_thread_work_items("To-Young-Gen Cards:", MergePSSToYoungGenCards); - _gc_par_phases[MergePSS]->create_thread_work_items("Evac-Fail Cards:", MergePSSEvacFail); - _gc_par_phases[MergePSS]->create_thread_work_items("Marked Cards:", MergePSSMarked); + _gc_par_phases[FlushPSS]->create_thread_work_items("Copied Bytes:", FlushPSSCopiedBytes); + _gc_par_phases[FlushPSS]->create_thread_work_items("LAB Waste:", FlushPSSLABWasteBytes); + _gc_par_phases[FlushPSS]->create_thread_work_items("LAB Undo Waste:", FlushPSSLABUndoWasteBytes); + _gc_par_phases[FlushPSS]->create_thread_work_items("Pending Cards:", FlushPSSPendingCards); + _gc_par_phases[FlushPSS]->create_thread_work_items("To-Young-Gen Cards:", FlushPSSToYoungGenCards); + _gc_par_phases[FlushPSS]->create_thread_work_items("Evac-Fail Cards:", FlushPSSEvacFail); + _gc_par_phases[FlushPSS]->create_thread_work_items("Marked Cards:", FlushPSSMarked); _gc_par_phases[RestoreEvacuationFailedRegions]->create_thread_work_items("Evacuation Failed Regions:", RestoreEvacFailureRegionsEvacFailedNum); _gc_par_phases[RestoreEvacuationFailedRegions]->create_thread_work_items("Pinned Regions:", RestoreEvacFailureRegionsPinnedNum); @@ -495,7 +496,8 @@ double G1GCPhaseTimes::print_post_evacuate_collection_set(bool evacuation_failed _weak_phase_times.log_subtotals(3); debug_time("Post Evacuate Cleanup 1", _cur_post_evacuate_cleanup_1_time_ms); - debug_phase(_gc_par_phases[MergePSS], 1); + debug_phase(_gc_par_phases[FlushPSS], 1); + debug_phase(_gc_par_phases[UpdateCodeRoots], 1); debug_phase(_gc_par_phases[ClearCardTable], 1); debug_phase(_gc_par_phases[RecalculateUsed], 1); if (evacuation_failed) { @@ -512,7 +514,7 @@ double G1GCPhaseTimes::print_post_evacuate_collection_set(bool evacuation_failed debug_phase(_gc_par_phases[UpdateDerivedPointers], 1); #endif // COMPILER2 debug_phase(_gc_par_phases[EagerlyReclaimHumongousObjects], 1); - trace_phase(_gc_par_phases[ResetPartialArrayStateManager]); + trace_phase(_gc_par_phases[DestroyPSS]); if (G1CollectedHeap::heap()->should_sample_collection_set_candidates()) { debug_phase(_gc_par_phases[SampleCollectionSetCandidates], 1); diff --git a/src/hotspot/share/gc/g1/g1GCPhaseTimes.hpp b/src/hotspot/share/gc/g1/g1GCPhaseTimes.hpp index 31bfd38ddb9..078a819986c 100644 --- a/src/hotspot/share/gc/g1/g1GCPhaseTimes.hpp +++ b/src/hotspot/share/gc/g1/g1GCPhaseTimes.hpp @@ -76,7 +76,7 @@ class G1GCPhaseTimes : public CHeapObj { ResizeThreadLABs, RebuildFreeList, SampleCollectionSetCandidates, - MergePSS, + FlushPSS, RestoreEvacuationFailedRegions, RemoveSelfForwards, ClearCardTable, @@ -85,7 +85,8 @@ class G1GCPhaseTimes : public CHeapObj { UpdateDerivedPointers, #endif // COMPILER2 EagerlyReclaimHumongousObjects, - ResetPartialArrayStateManager, + UpdateCodeRoots, + DestroyPSS, ProcessEvacuationFailedRegions, ResetMarkingState, NoteStartOfMark, @@ -134,15 +135,15 @@ class G1GCPhaseTimes : public CHeapObj { CodeRootsScannedNMethods }; - enum GCMergePSSWorkItems { - MergePSSCopiedBytes, - MergePSSLABSize, - MergePSSLABWasteBytes, - MergePSSLABUndoWasteBytes, - MergePSSPendingCards, // To be scanned cards generated by GC (from cross-references and evacuation failure). - MergePSSToYoungGenCards, // To-young-gen cards generated by GC. - MergePSSEvacFail, // Evacuation failure generated dirty cards by GC. - MergePSSMarked, // Total newly marked cards. + enum GCFlushPSSWorkItems { + FlushPSSCopiedBytes, + FlushPSSLABSize, + FlushPSSLABWasteBytes, + FlushPSSLABUndoWasteBytes, + FlushPSSPendingCards, // To be scanned cards generated by GC (from cross-references and evacuation failure). + FlushPSSToYoungGenCards, // To-young-gen cards generated by GC. + FlushPSSEvacFail, // Evacuation failure generated dirty cards by GC. + FlushPSSMarked, // Total newly marked cards. }; enum RestoreEvacFailureRegionsWorkItems { diff --git a/src/hotspot/share/gc/g1/g1HeapRegionRemSet.cpp b/src/hotspot/share/gc/g1/g1HeapRegionRemSet.cpp index ef42538d4d6..e2009b0e77d 100644 --- a/src/hotspot/share/gc/g1/g1HeapRegionRemSet.cpp +++ b/src/hotspot/share/gc/g1/g1HeapRegionRemSet.cpp @@ -132,6 +132,10 @@ void G1HeapRegionRemSet::bulk_remove_code_roots() { _code_roots.bulk_remove(); } +void G1HeapRegionRemSet::prepare_for_adding_code_roots(size_t num_code_roots) { + _code_roots.prepare_for_adding_code_roots(num_code_roots); +} + void G1HeapRegionRemSet::code_roots_do(NMethodClosure* blk) const { _code_roots.nmethods_do(blk); } diff --git a/src/hotspot/share/gc/g1/g1HeapRegionRemSet.hpp b/src/hotspot/share/gc/g1/g1HeapRegionRemSet.hpp index 2e97d6a7597..20f7b785f45 100644 --- a/src/hotspot/share/gc/g1/g1HeapRegionRemSet.hpp +++ b/src/hotspot/share/gc/g1/g1HeapRegionRemSet.hpp @@ -182,6 +182,7 @@ public: void add_code_root(nmethod* nm); void remove_code_root(nmethod* nm); void bulk_remove_code_roots(); + void prepare_for_adding_code_roots(size_t num_code_roots); // Applies blk->do_nmethod() to each of the entries in _code_roots void code_roots_do(NMethodClosure* blk) const; diff --git a/src/hotspot/share/gc/g1/g1NMethodClosure.cpp b/src/hotspot/share/gc/g1/g1NMethodClosure.cpp index d74aa5eae1d..d7dcbeb87fb 100644 --- a/src/hotspot/share/gc/g1/g1NMethodClosure.cpp +++ b/src/hotspot/share/gc/g1/g1NMethodClosure.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -35,13 +35,47 @@ template void G1NMethodClosure::HeapRegionGatheringOopClosure::do_oop_work(T* p) { + T old_oop_or_narrowoop = RawAccess<>::oop_load(p); + _work->do_oop(p); T oop_or_narrowoop = RawAccess<>::oop_load(p); - if (!CompressedOops::is_null(oop_or_narrowoop)) { + // If the oop moved, we need to update the code root set at the new location. If it did not + // change, it is either in the existing code root set, or an earlier evacuation round already + // enqueued it for deferred update. + // + // We defer actual update to the code roots to later. This can, in presence of optional + // collections, ultimately result in duplicates in the per-thread code root set update list. + // We consider this negligible, given that optional collection is rare and typically does + // not cover many regions/nmethods. + if (oop_or_narrowoop != old_oop_or_narrowoop) { + // If the oop moved, it must not have been null. + assert(!CompressedOops::is_null(oop_or_narrowoop), "must be"); oop o = CompressedOops::decode_not_null(oop_or_narrowoop); + assert(!_g1h->is_in_cset(o), "must be"); + G1HeapRegion* hr = _g1h->heap_region_containing(o); - assert(!_g1h->is_in_cset(o) || hr->rem_set()->code_roots_list_contains(_nm), "if o still in collection set then evacuation failed and nm must already be in the remset"); - hr->add_code_root(_nm); + _affected_regions.append_if_missing(hr); + } else { + // We could be tempted to verify that for a non-null oop, the _nm is already in the target code root + // set or in one of the deferred code root set update lists. It would not be sufficient to verify the + // current thread's list, because across evacuation rounds (i.e. initial/multiple optional) different + // threads may have worked on a given oop from an nmethod. + // This is rather expensive, not only requiring looking at all threads' lists, but also making sure + // that there are no memory ordering issues when doing that. So we skip it. + } +} + +G1NMethodClosure::HeapRegionGatheringOopClosure::HeapRegionGatheringOopClosure(OopClosure* oc, G1ParScanThreadState* pss) : + _g1h(G1CollectedHeap::heap()), + _work(oc), + _pss(pss), + _nm(nullptr), + _affected_regions(5) { +} + +void G1NMethodClosure::HeapRegionGatheringOopClosure::add_to_remsets() { + while (!_affected_regions.is_empty()) { + _pss->remember_nmethod_into_region(_affected_regions.pop(), _nm); } } @@ -74,11 +108,13 @@ void G1NMethodClosure::MarkingOopClosure::do_oop(narrowOop* o) { } void G1NMethodClosure::do_evacuation_and_fixup(nmethod* nm) { - _oc.set_nm(nm); + _oc.set_nmethod(nm); // Evacuate objects pointed to by the nmethod nm->oops_do(&_oc); + _oc.add_to_remsets(); + if (_strong) { // CodeCache unloading support nm->mark_as_maybe_on_stack(); diff --git a/src/hotspot/share/gc/g1/g1NMethodClosure.hpp b/src/hotspot/share/gc/g1/g1NMethodClosure.hpp index 91906932d4f..95d0ee1942d 100644 --- a/src/hotspot/share/gc/g1/g1NMethodClosure.hpp +++ b/src/hotspot/share/gc/g1/g1NMethodClosure.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,8 +27,10 @@ #include "gc/g1/g1CollectedHeap.hpp" #include "memory/iterator.hpp" +#include "utilities/growableArray.hpp" class G1ConcurrentMark; +class G1ParScanThreadState; class nmethod; class G1NMethodClosure : public NMethodClosure { @@ -36,20 +38,27 @@ class G1NMethodClosure : public NMethodClosure { class HeapRegionGatheringOopClosure : public OopClosure { G1CollectedHeap* _g1h; OopClosure* _work; + G1ParScanThreadState* _pss; + nmethod* _nm; + GrowableArrayCHeap _affected_regions; template void do_oop_work(T* p); public: - HeapRegionGatheringOopClosure(OopClosure* oc) : _g1h(G1CollectedHeap::heap()), _work(oc), _nm(nullptr) {} + HeapRegionGatheringOopClosure(OopClosure* oc, G1ParScanThreadState* pss); + ~HeapRegionGatheringOopClosure() = default; void do_oop(oop* o); void do_oop(narrowOop* o); - void set_nm(nmethod* nm) { + void set_nmethod(nmethod* nm) { + assert(_affected_regions.is_empty(), "must be"); _nm = nm; } + + void add_to_remsets(); }; // Mark all oops below TAMS. @@ -72,8 +81,8 @@ class G1NMethodClosure : public NMethodClosure { bool _strong; public: - G1NMethodClosure(uint worker_id, OopClosure* oc, bool strong) : - _oc(oc), _marking_oc(worker_id), _strong(strong) { } + G1NMethodClosure(uint worker_id, OopClosure* oc, bool strong, G1ParScanThreadState* pss) : + _oc(oc, pss), _marking_oc(worker_id), _strong(strong) { } void do_evacuation_and_fixup(nmethod* nm); void do_marking(nmethod* nm); diff --git a/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp b/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp index 5a66f64090a..3e6f8758744 100644 --- a/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp +++ b/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp @@ -55,12 +55,20 @@ // Explicit NOINLINE to block ATTRIBUTE_FLATTENing. #define MAYBE_INLINE_EVACUATION NOT_DEBUG(inline) DEBUG_ONLY(NOINLINE) +// Good estimate for the initial table size. +static uint initial_nmethod_table_size(G1CollectedHeap* g1h) { + // The +1 is both to consider the retained old region likely to be added, and avoid zero-sized initial tables. + return MIN3(g1h->collection_set()->num_regions(), g1h->max_num_regions() / 2, g1h->num_available_regions()) + 1; +} + G1ParScanThreadState::G1ParScanThreadState(G1CollectedHeap* g1h, + G1ParScanThreadStateSet* per_thread_states, uint worker_id, uint num_workers, G1CollectionSet* collection_set, G1EvacFailureRegions* evac_failure_regions) : _g1h(g1h), + _per_thread_states(per_thread_states), _task_queue(g1h->task_queue(worker_id)), _ct(g1h->refinement_table()), _closures(nullptr), @@ -83,6 +91,10 @@ G1ParScanThreadState::G1ParScanThreadState(G1CollectedHeap* g1h, _max_num_optional_regions(collection_set->num_optional_regions()), _numa(g1h->numa()), _obj_alloc_stat(nullptr), + // The initial size estimate is relatively conservative, assuming that all regions + // in the collection set get evacuated into the same amount of new regions. + _nmethods_to_add(initial_nmethod_table_size(g1h), + MAX2(initial_nmethod_table_size(g1h), _g1h->max_num_regions() / 2)), ALLOCATION_FAILURE_INJECTOR_ONLY(_allocation_failure_inject_counter(0) COMMA) _evacuation_failed_info(), _evac_failure_regions(evac_failure_regions), @@ -129,6 +141,12 @@ size_t G1ParScanThreadState::flush_stats(size_t* surviving_young_words, uint num } G1ParScanThreadState::~G1ParScanThreadState() { + auto delete_all = [&] (uint region, G1NmethodSet* nmethods) -> bool { + delete nmethods; + return true; + }; + _nmethods_to_add.iterate(delete_all); + delete _plab_allocator; delete _closures; FREE_C_HEAP_ARRAY(_surviving_young_words_base); @@ -575,6 +593,7 @@ G1ParScanThreadState* G1ParScanThreadStateSet::state_for_worker(uint worker_id) if (_states[worker_id] == nullptr) { _states[worker_id] = new G1ParScanThreadState(_g1h, + this, worker_id, _num_workers, _collection_set, @@ -606,21 +625,59 @@ void G1ParScanThreadStateSet::flush_stats() { size_t evac_failure_cards = pss->num_cards_from_evac_failure(); size_t marked_cards = pss->num_cards_marked(); - p->record_or_add_thread_work_item(G1GCPhaseTimes::MergePSS, worker_id, copied_bytes, G1GCPhaseTimes::MergePSSCopiedBytes); - p->record_or_add_thread_work_item(G1GCPhaseTimes::MergePSS, worker_id, lab_waste_bytes, G1GCPhaseTimes::MergePSSLABWasteBytes); - p->record_or_add_thread_work_item(G1GCPhaseTimes::MergePSS, worker_id, lab_undo_waste_bytes, G1GCPhaseTimes::MergePSSLABUndoWasteBytes); - p->record_or_add_thread_work_item(G1GCPhaseTimes::MergePSS, worker_id, pending_cards, G1GCPhaseTimes::MergePSSPendingCards); - p->record_or_add_thread_work_item(G1GCPhaseTimes::MergePSS, worker_id, to_young_gen_cards, G1GCPhaseTimes::MergePSSToYoungGenCards); - p->record_or_add_thread_work_item(G1GCPhaseTimes::MergePSS, worker_id, evac_failure_cards, G1GCPhaseTimes::MergePSSEvacFail); - p->record_or_add_thread_work_item(G1GCPhaseTimes::MergePSS, worker_id, marked_cards, G1GCPhaseTimes::MergePSSMarked); - - delete pss; - _states[worker_id] = nullptr; + p->record_or_add_thread_work_item(G1GCPhaseTimes::FlushPSS, worker_id, copied_bytes, G1GCPhaseTimes::FlushPSSCopiedBytes); + p->record_or_add_thread_work_item(G1GCPhaseTimes::FlushPSS, worker_id, lab_waste_bytes, G1GCPhaseTimes::FlushPSSLABWasteBytes); + p->record_or_add_thread_work_item(G1GCPhaseTimes::FlushPSS, worker_id, lab_undo_waste_bytes, G1GCPhaseTimes::FlushPSSLABUndoWasteBytes); + p->record_or_add_thread_work_item(G1GCPhaseTimes::FlushPSS, worker_id, pending_cards, G1GCPhaseTimes::FlushPSSPendingCards); + p->record_or_add_thread_work_item(G1GCPhaseTimes::FlushPSS, worker_id, to_young_gen_cards, G1GCPhaseTimes::FlushPSSToYoungGenCards); + p->record_or_add_thread_work_item(G1GCPhaseTimes::FlushPSS, worker_id, evac_failure_cards, G1GCPhaseTimes::FlushPSSEvacFail); + p->record_or_add_thread_work_item(G1GCPhaseTimes::FlushPSS, worker_id, marked_cards, G1GCPhaseTimes::FlushPSSMarked); } _flushed = true; } +void G1ParScanThreadStateSet::destroy_worker_states() { + assert(_flushed, "statistics must already be flushed"); + for (uint worker_id = 0; worker_id < _num_workers; ++worker_id) { + delete _states[worker_id]; + _states[worker_id] = nullptr; + } +} + +void G1ParScanThreadStateSet::update_nmethod_regions_to_add(G1NmethodsToAdd* nmethods) { + if (nmethods->number_of_entries() == 0) { + return; + } + + // Take the key set, look which are not yet in the global set, and update the necessary ones. + ResourceMark rm; + GrowableArray regions_to_add = GrowableArray(nmethods->table_size()); + + nmethods->iterate_all([&] (uint& region, void*) { + if (_has_nmethods_to_add.par_set_bit(region, memory_order_relaxed)) { + regions_to_add.push(region); + } + }); + + uint num_regions_to_add = (uint)regions_to_add.length(); + + if (num_regions_to_add == 0) { + return; + } + + uint first_index = _num_nmethod_regions_to_add.fetch_then_add(num_regions_to_add, memory_order_relaxed); + guarantee(first_index + num_regions_to_add <= _g1h->max_num_regions(), "must be"); + + memcpy(&_nmethod_regions_to_add[first_index], regions_to_add.adr_at(0), num_regions_to_add * sizeof(uint)); +} + +void G1ParScanThreadStateSet::par_iterate_nmethod_regions_to_add(G1HeapRegionClosure* cl, + G1HeapRegionClaimer* claimer, + uint worker_id) { + _g1h->par_iterate_regions_array(cl, claimer, _nmethod_regions_to_add, num_nmethod_regions_to_add(), worker_id); +} + void G1ParScanThreadStateSet::record_unused_optional_region(G1HeapRegion* hr) { for (uint worker_index = 0; worker_index < _num_workers; ++worker_index) { G1ParScanThreadState* pss = _states[worker_index]; @@ -676,6 +733,10 @@ oop G1ParScanThreadState::handle_evacuation_failure_par(oop old, markWord m, Kla } } +void G1ParScanThreadState::update_nmethod_regions_to_add() { + _per_thread_states->update_nmethod_regions_to_add(&_nmethods_to_add); +} + void G1ParScanThreadState::initialize_numa_stats() { if (_numa->is_enabled()) { LogTarget(Info, gc, heap, numa) lt; @@ -720,7 +781,10 @@ G1ParScanThreadStateSet::G1ParScanThreadStateSet(G1CollectedHeap* g1h, _surviving_young_words_total(NEW_C_HEAP_ARRAY(size_t, collection_set->num_young_regions() + 1, mtGC)), _num_workers(num_workers), _flushed(false), - _evac_failure_regions(evac_failure_regions) + _evac_failure_regions(evac_failure_regions), + _has_nmethods_to_add(g1h->max_num_regions(), mtGC), + _num_nmethod_regions_to_add(0), + _nmethod_regions_to_add(NEW_C_HEAP_ARRAY(uint, g1h->max_num_regions(), mtGC)) // Conservative length estimation. { for (uint i = 0; i < num_workers; ++i) { _states[i] = nullptr; @@ -729,7 +793,10 @@ G1ParScanThreadStateSet::G1ParScanThreadStateSet(G1CollectedHeap* g1h, } G1ParScanThreadStateSet::~G1ParScanThreadStateSet() { - assert(_flushed, "thread local state from the per thread states should have been flushed"); + for (uint i = 0; i < _num_workers; i++) { + assert(_states[i] == nullptr, "must be"); + } + FREE_C_HEAP_ARRAY(_nmethod_regions_to_add); FREE_C_HEAP_ARRAY(_states); FREE_C_HEAP_ARRAY(_surviving_young_words_total); } diff --git a/src/hotspot/share/gc/g1/g1ParScanThreadState.hpp b/src/hotspot/share/gc/g1/g1ParScanThreadState.hpp index 3fb080d40be..efecbe1f786 100644 --- a/src/hotspot/share/gc/g1/g1ParScanThreadState.hpp +++ b/src/hotspot/share/gc/g1/g1ParScanThreadState.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -37,6 +37,9 @@ #include "gc/shared/taskqueue.hpp" #include "memory/allocation.hpp" #include "oops/oop.hpp" +#include "runtime/atomic.hpp" +#include "utilities/growableArray.hpp" +#include "utilities/resizableHashTable.hpp" #include "utilities/ticks.hpp" class G1CardTable; @@ -44,12 +47,16 @@ class G1CollectionSet; class G1EvacFailureRegions; class G1EvacuationRootClosures; class G1OopStarChunkedList; +class G1ParScanThreadStateSet; class G1PLABAllocator; class G1HeapRegion; class outputStream; +typedef GrowableArrayCHeap G1NmethodSet; +typedef ResizeableHashTable G1NmethodsToAdd; class G1ParScanThreadState : public CHeapObj { G1CollectedHeap* _g1h; + G1ParScanThreadStateSet* _per_thread_states; G1ScannerTasksQueue* _task_queue; G1CardTable* _ct; G1EvacuationRootClosures* _closures; @@ -96,6 +103,9 @@ class G1ParScanThreadState : public CHeapObj { // transferred when flushed. size_t* _obj_alloc_stat; + // The nmethods that were found during code root scan that need to be redistributed. + G1NmethodsToAdd _nmethods_to_add; + // Per-thread evacuation failure data structures. ALLOCATION_FAILURE_INJECTOR_ONLY(size_t _allocation_failure_inject_counter;) @@ -114,6 +124,7 @@ class G1ParScanThreadState : public CHeapObj { public: G1ParScanThreadState(G1CollectedHeap* g1h, + G1ParScanThreadStateSet* per_thread_states, uint worker_id, uint num_workers, G1CollectionSet* collection_set, @@ -243,6 +254,16 @@ public: // An attempt to evacuate "obj" has failed; take necessary steps. oop handle_evacuation_failure_par(oop obj, markWord m, Klass* klass, G1HeapRegionAttr attr, size_t word_sz, bool cause_pinned); + inline void remember_nmethod_into_region(G1HeapRegion* r, nmethod* nm); + // Updates the global set of regions that need updates to the code root set + // later with the ones gathered so far. + void update_nmethod_regions_to_add(); + + inline size_t num_nmethods(uint index) const; + // Iterate nmethods stored for the given region index. + template + inline void iterate_nmethods(uint index, Function fn); + template inline void remember_root_into_optional_region(T* p); template @@ -260,6 +281,10 @@ class G1ParScanThreadStateSet : public StackObj { bool _flushed; G1EvacFailureRegions* _evac_failure_regions; + CHeapBitMap _has_nmethods_to_add; + Atomic _num_nmethod_regions_to_add; + uint* _nmethod_regions_to_add; + public: G1ParScanThreadStateSet(G1CollectedHeap* g1h, uint num_workers, @@ -268,6 +293,15 @@ class G1ParScanThreadStateSet : public StackObj { ~G1ParScanThreadStateSet(); void flush_stats(); + void destroy_worker_states(); + + // Updates the region set that has code root updates with the regions in the given set. + void update_nmethod_regions_to_add(G1NmethodsToAdd* nmethods); + void par_iterate_nmethod_regions_to_add(G1HeapRegionClosure* cl, + G1HeapRegionClaimer* claimer, + uint worker_id); + uint num_nmethod_regions_to_add() const { return _num_nmethod_regions_to_add.load_relaxed(); } + void record_unused_optional_region(G1HeapRegion* hr); #if TASKQUEUE_STATS void print_partial_array_task_stats(); diff --git a/src/hotspot/share/gc/g1/g1ParScanThreadState.inline.hpp b/src/hotspot/share/gc/g1/g1ParScanThreadState.inline.hpp index 854c341f720..c42f5f4c4f6 100644 --- a/src/hotspot/share/gc/g1/g1ParScanThreadState.inline.hpp +++ b/src/hotspot/share/gc/g1/g1ParScanThreadState.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -70,6 +70,37 @@ inline void G1ParScanThreadState::reset_trim_ticks() { _trim_ticks = Tickspan(); } +inline void G1ParScanThreadState::remember_nmethod_into_region(G1HeapRegion* r, nmethod* nm) { + uint index = r->hrm_index(); + + G1NmethodSet** nmethods = _nmethods_to_add.get(index); + if (nmethods != nullptr) { + (*nmethods)->push(nm); + } else { + G1NmethodSet* new_set = new G1NmethodSet(3); + new_set->push(nm); + bool put_result = _nmethods_to_add.put(index, new_set); + assert(put_result, "must be"); + _nmethods_to_add.maybe_grow(3 /* load_factor */); + } +} + +inline size_t G1ParScanThreadState::num_nmethods(uint region) const { + G1NmethodSet** nmethods = _nmethods_to_add.get(region); + return nmethods != nullptr ? (size_t)(*nmethods)->length() : 0; +} + +template +inline void G1ParScanThreadState::iterate_nmethods(uint index, Function fn) { + G1NmethodSet** nmethods = _nmethods_to_add.get(index); + if (nmethods == nullptr) { + return; + } + for (nmethod* nm : **nmethods) { + fn(nm); + } +} + template inline void G1ParScanThreadState::remember_root_into_optional_region(T* p) { oop o = RawAccess::oop_load(p); diff --git a/src/hotspot/share/gc/g1/g1Policy.cpp b/src/hotspot/share/gc/g1/g1Policy.cpp index d271a8a610a..f9b1c182a38 100644 --- a/src/hotspot/share/gc/g1/g1Policy.cpp +++ b/src/hotspot/share/gc/g1/g1Policy.cpp @@ -913,7 +913,7 @@ G1CollectorState G1Policy::record_young_collection_end(bool concurrent_operation } // Update prediction for copy cost per byte - size_t copied_bytes = p->sum_thread_work_items(G1GCPhaseTimes::MergePSS, G1GCPhaseTimes::MergePSSCopiedBytes); + size_t copied_bytes = p->sum_thread_work_items(G1GCPhaseTimes::FlushPSS, G1GCPhaseTimes::FlushPSSCopiedBytes); if (copied_bytes > 0) { double avg_copy_time = average_time_ms(G1GCPhaseTimes::ObjCopy) + average_time_ms(G1GCPhaseTimes::OptObjCopy); @@ -950,8 +950,8 @@ G1CollectorState G1Policy::record_young_collection_end(bool concurrent_operation mutator_end_time, pending_cards_from_refinement_table, yield_duration_ms, - phase_times()->sum_thread_work_items(G1GCPhaseTimes::MergePSS, G1GCPhaseTimes::MergePSSPendingCards), - phase_times()->sum_thread_work_items(G1GCPhaseTimes::MergePSS, G1GCPhaseTimes::MergePSSToYoungGenCards)); + phase_times()->sum_thread_work_items(G1GCPhaseTimes::FlushPSS, G1GCPhaseTimes::FlushPSSPendingCards), + phase_times()->sum_thread_work_items(G1GCPhaseTimes::FlushPSS, G1GCPhaseTimes::FlushPSSToYoungGenCards)); } if (collector_state()->is_in_prepare_mixed_gc()) { diff --git a/src/hotspot/share/gc/g1/g1RemSet.cpp b/src/hotspot/share/gc/g1/g1RemSet.cpp index bcb50dcc98f..5f58ca2e053 100644 --- a/src/hotspot/share/gc/g1/g1RemSet.cpp +++ b/src/hotspot/share/gc/g1/g1RemSet.cpp @@ -630,6 +630,8 @@ void G1RemSet::scan_collection_set_code_roots(G1ParScanThreadState* pss, // set regions for all threads. _g1h->collection_set_iterate_increment_from(&cl, worker_id); + pss->update_nmethod_regions_to_add(); + p->record_or_add_thread_work_item(coderoots_phase, worker_id, cl.code_roots_scanned(), G1GCPhaseTimes::CodeRootsScannedNMethods); } diff --git a/src/hotspot/share/gc/g1/g1RootClosures.hpp b/src/hotspot/share/gc/g1/g1RootClosures.hpp index 35ce038e1f8..c1c80655c91 100644 --- a/src/hotspot/share/gc/g1/g1RootClosures.hpp +++ b/src/hotspot/share/gc/g1/g1RootClosures.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -42,6 +42,8 @@ public: // Applied to nmethods reachable as strong roots. virtual NMethodClosure* strong_nmethods() = 0; + + virtual ~G1RootClosures() = default; }; class G1EvacuationRootClosures : public G1RootClosures { diff --git a/src/hotspot/share/gc/g1/g1SharedClosures.hpp b/src/hotspot/share/gc/g1/g1SharedClosures.hpp index a81f62ff308..dc6ff646271 100644 --- a/src/hotspot/share/gc/g1/g1SharedClosures.hpp +++ b/src/hotspot/share/gc/g1/g1SharedClosures.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -55,7 +55,7 @@ public: _oops_in_cld(g1h, pss), _oops_in_nmethod(g1h, pss), _clds(&_oops_in_cld, process_only_dirty), - _nmethods(pss->worker_id(), &_oops_in_nmethod, should_mark) {} + _nmethods(pss->worker_id(), &_oops_in_nmethod, should_mark, pss) {} }; #endif // SHARE_GC_G1_G1SHAREDCLOSURES_HPP diff --git a/src/hotspot/share/gc/g1/g1YoungGCPostEvacuateTasks.cpp b/src/hotspot/share/gc/g1/g1YoungGCPostEvacuateTasks.cpp index 97378d0542e..e561252ab25 100644 --- a/src/hotspot/share/gc/g1/g1YoungGCPostEvacuateTasks.cpp +++ b/src/hotspot/share/gc/g1/g1YoungGCPostEvacuateTasks.cpp @@ -54,12 +54,12 @@ #include "utilities/bitMap.inline.hpp" #include "utilities/ticks.hpp" -class G1PostEvacuateCollectionSetCleanupTask1::MergePssTask : public G1AbstractSubTask { +class G1PostEvacuateCollectionSetCleanupTask1::FlushPssTask : public G1AbstractSubTask { G1ParScanThreadStateSet* _per_thread_states; public: - MergePssTask(G1ParScanThreadStateSet* per_thread_states) : - G1AbstractSubTask(G1GCPhaseTimes::MergePSS), + FlushPssTask(G1ParScanThreadStateSet* per_thread_states) : + G1AbstractSubTask(G1GCPhaseTimes::FlushPSS), _per_thread_states(per_thread_states) { } double worker_cost() const override { return 1.0; } @@ -119,6 +119,58 @@ public: } }; +class G1PostEvacuateCollectionSetCleanupTask1::UpdateCodeRootsTask + : public G1AbstractSubTask +{ + class ProcessRegionClosure : public G1HeapRegionClosure { + G1ParScanThreadStateSet* _psss; + + public: + ProcessRegionClosure(G1ParScanThreadStateSet* psss) : _psss(psss) { } + + bool do_heap_region(G1HeapRegion* r) override { + uint index = r->hrm_index(); + + size_t num_nmethods = 0; + for (uint i = 0; i < _psss->num_workers(); i++) { + G1ParScanThreadState* pss = _psss->state_for_worker(i); + num_nmethods += pss->num_nmethods(index); + } + if (num_nmethods != 0) { + // Notify the code root sets that we are going to add code roots. + r->rem_set()->prepare_for_adding_code_roots(num_nmethods); + + // Add roots. + for (uint i = 0; i < _psss->num_workers(); i++) { + G1ParScanThreadState* pss = _psss->state_for_worker(i); + pss->iterate_nmethods(index, [&] (nmethod* nm) { r->add_code_root(nm); }); + } + } + return false; + } + }; + + G1ParScanThreadStateSet* _psss; + G1HeapRegionClaimer _claimer; + +public: + UpdateCodeRootsTask(G1ParScanThreadStateSet* per_thread_states) + : G1AbstractSubTask(G1GCPhaseTimes::UpdateCodeRoots), _psss(per_thread_states), _claimer(0) { } + + double worker_cost() const override { + return _psss->num_nmethod_regions_to_add(); + } + + void set_max_workers(uint max_workers) override { + _claimer.set_n_workers(max_workers); + } + + void do_work(uint worker_id) override { + ProcessRegionClosure cl(_psss); + _psss->par_iterate_nmethod_regions_to_add(&cl, &_claimer, worker_id); + } +}; + class G1PostEvacuateCollectionSetCleanupTask1::RestoreEvacFailureRegionsTask : public G1AbstractSubTask { G1CollectedHeap* _g1h; G1ConcurrentMark* _cm; @@ -327,11 +379,13 @@ G1PostEvacuateCollectionSetCleanupTask1::G1PostEvacuateCollectionSetCleanupTask1 bool evac_failed = evac_failure_regions->has_regions_evac_failed(); bool alloc_failed = evac_failure_regions->has_regions_alloc_failed(); - add_serial_task(new MergePssTask(per_thread_states)); + add_serial_task(new FlushPssTask(per_thread_states)); add_serial_task(new RecalculateUsedTask(evac_failed, alloc_failed)); if (SampleCollectionSetCandidatesTask::should_execute()) { add_serial_task(new SampleCollectionSetCandidatesTask()); } + add_parallel_task(new UpdateCodeRootsTask(per_thread_states)); + add_parallel_task(G1CollectedHeap::heap()->rem_set()->create_cleanup_after_scan_heap_roots_task()); if (evac_failed) { add_parallel_task(new RestoreEvacFailureRegionsTask(evac_failure_regions)); @@ -871,21 +925,19 @@ public: } }; -class G1PostEvacuateCollectionSetCleanupTask2::ResetPartialArrayStateManagerTask - : public G1AbstractSubTask -{ -public: - ResetPartialArrayStateManagerTask() - : G1AbstractSubTask(G1GCPhaseTimes::ResetPartialArrayStateManager) - {} +class G1PostEvacuateCollectionSetCleanupTask2::DestroyPssTask : public G1AbstractSubTask { + G1ParScanThreadStateSet* _per_thread_states; - double worker_cost() const override { - return AlmostNoWork; - } +public: + DestroyPssTask(G1ParScanThreadStateSet* per_thread_states) : + G1AbstractSubTask(G1GCPhaseTimes::DestroyPSS), + _per_thread_states(per_thread_states) { } + + double worker_cost() const override { return 1.0; } void do_work(uint worker_id) override { - // This must be in phase2 cleanup, after phase1 has destroyed all of the - // associated allocators. + _per_thread_states->destroy_worker_states(); + // This must be here after above destroyed the per-thread allocators. G1CollectedHeap::heap()->partial_array_state_manager()->reset(); } }; @@ -901,7 +953,7 @@ G1PostEvacuateCollectionSetCleanupTask2::G1PostEvacuateCollectionSetCleanupTask2 if (G1CollectedHeap::heap()->has_humongous_reclaim_candidates()) { add_serial_task(new EagerlyReclaimHumongousObjectsTask()); } - add_serial_task(new ResetPartialArrayStateManagerTask()); + add_serial_task(new DestroyPssTask(per_thread_states)); if (evac_failure_regions->has_regions_evac_failed()) { add_parallel_task(new ProcessEvacuationFailedRegionsTask(evac_failure_regions)); diff --git a/src/hotspot/share/gc/g1/g1YoungGCPostEvacuateTasks.hpp b/src/hotspot/share/gc/g1/g1YoungGCPostEvacuateTasks.hpp index 557ce454c78..95d0fee6ad7 100644 --- a/src/hotspot/share/gc/g1/g1YoungGCPostEvacuateTasks.hpp +++ b/src/hotspot/share/gc/g1/g1YoungGCPostEvacuateTasks.hpp @@ -35,16 +35,18 @@ class G1EvacInfo; class G1ParScanThreadStateSet; // First set of post evacuate collection set tasks containing ("s" means serial): -// - Merge PSS (s) +// - Flush PSS (s) // - Recalculate Used (s) // - Sample Collection Set Candidates (s) // - Clear Card Table // - Restore evac failure regions (on evacuation failure) +// - Update code roots (for regions that need code roots to be added) class G1PostEvacuateCollectionSetCleanupTask1 : public G1BatchedTask { - class MergePssTask; + class FlushPssTask; class RecalculateUsedTask; class SampleCollectionSetCandidatesTask; class RestoreEvacFailureRegionsTask; + class UpdateCodeRootsTask; public: G1PostEvacuateCollectionSetCleanupTask1(G1ParScanThreadStateSet* per_thread_states, @@ -54,10 +56,10 @@ public: // Second set of post evacuate collection set tasks containing (s means serial): // - Eagerly Reclaim Humongous Objects (s) // - Update Derived Pointers (s) +// - Destroy PSS (s) + Reset the reusable PartialArrayStateManager // - Clear Retained Region Data (on evacuation failure) // - Free Collection Set // - Resize TLABs and Swap Card Table -// - Reset the reusable PartialArrayStateManager. class G1PostEvacuateCollectionSetCleanupTask2 : public G1BatchedTask { class EagerlyReclaimHumongousObjectsTask; #ifdef COMPILER2 @@ -67,7 +69,8 @@ class G1PostEvacuateCollectionSetCleanupTask2 : public G1BatchedTask { class ProcessEvacuationFailedRegionsTask; class FreeCollectionSetTask; class ResizeTLABsAndSwapCardTableTask; - class ResetPartialArrayStateManagerTask; + + class DestroyPssTask; public: G1PostEvacuateCollectionSetCleanupTask2(G1ParScanThreadStateSet* per_thread_states, diff --git a/test/hotspot/jtreg/gc/g1/TestGCLogMessages.java b/test/hotspot/jtreg/gc/g1/TestGCLogMessages.java index 68391893a32..16b73d4c354 100644 --- a/test/hotspot/jtreg/gc/g1/TestGCLogMessages.java +++ b/test/hotspot/jtreg/gc/g1/TestGCLogMessages.java @@ -170,7 +170,8 @@ public class TestGCLogMessages { // Post Evacuate Cleanup 1 new LogMessageWithLevel("Post Evacuate Cleanup 1:", Level.DEBUG), - new LogMessageWithLevel("Merge Per-Thread State \\(ms\\):", Level.DEBUG), + new LogMessageWithLevel("Flush Per-Thread State \\(ms\\):", Level.DEBUG), + new LogMessageWithLevel("Update Code Roots \\(ms\\):", Level.DEBUG), new LogMessageWithLevel("LAB Waste:", Level.DEBUG), new LogMessageWithLevel("LAB Undo Waste:", Level.DEBUG), new LogMessageWithLevel("Pending Cards:", Level.DEBUG), @@ -188,7 +189,7 @@ public class TestGCLogMessages { new LogMessageWithLevel("Serial Free Collection Set:", Level.TRACE), new LogMessageWithLevel("Young Free Collection Set \\(ms\\):", Level.TRACE), new LogMessageWithLevel("Non-Young Free Collection Set \\(ms\\):", Level.TRACE), - new LogMessageWithLevel("Reset Partial Array State Manager \\(ms\\)", Level.TRACE), + new LogMessageWithLevel("Destroy Per-Thread State \\(ms\\):", Level.TRACE), // Misc Top-level new LogMessageWithLevel("Rebuild Free List:", Level.DEBUG), diff --git a/test/jdk/jdk/jfr/event/gc/collection/TestG1ParallelPhases.java b/test/jdk/jdk/jfr/event/gc/collection/TestG1ParallelPhases.java index d69d47f1911..cde561a68e7 100644 --- a/test/jdk/jdk/jfr/event/gc/collection/TestG1ParallelPhases.java +++ b/test/jdk/jdk/jfr/event/gc/collection/TestG1ParallelPhases.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -107,9 +107,10 @@ public class TestG1ParallelPhases { "FreeCSet", "UpdateDerivedPointers", "EagerlyReclaimHumongousObjects", - "ResetPartialArrayStateManager", "ClearPendingCards", - "MergePSS", + "FlushPSS", + "DestroyPSS", + "UpdateCodeRoots", "NonYoungFreeCSet", "YoungFreeCSet", "RebuildFreeList", From dca8681fb737a9a71ddb06169f6202ac1cf64be3 Mon Sep 17 00:00:00 2001 From: Jorn Vernee Date: Wed, 8 Jul 2026 15:24:43 +0000 Subject: [PATCH 104/305] 8386848: testBool in java/foreign/normalize/TestNormalize.java fails on Zero VM with expected [true] but found [false] Reviewed-by: mcimadamore, vlivanov --- .../modules/GensrcStreamPreProcessing.gmk | 2 +- .../java.base/gensrc/GensrcVarHandles.gmk | 2 +- .../X-VarHandleSegmentView.java.template | 162 ++++++++++-------- test/jdk/ProblemList.txt | 2 - .../TestNormalizeBooleanVarHandle.java | 93 ++++++++++ 5 files changed, 182 insertions(+), 79 deletions(-) create mode 100644 test/jdk/java/foreign/normalize/TestNormalizeBooleanVarHandle.java diff --git a/make/common/modules/GensrcStreamPreProcessing.gmk b/make/common/modules/GensrcStreamPreProcessing.gmk index a48e3c98d4b..eb92ed99ed4 100644 --- a/make/common/modules/GensrcStreamPreProcessing.gmk +++ b/make/common/modules/GensrcStreamPreProcessing.gmk @@ -116,7 +116,7 @@ Conv_A = \ # Return integer type with same size as the type Conv_memtype = \ - $(if $(filter float, $1), int, $(if $(filter double, $1), long, $1)) + $(if $(filter float, $1), int, $(if $(filter double, $1), long, $(if $(filter boolean, $1), byte, $1))) # Return capitalized integer type with same size as the type Conv_Memtype = \ diff --git a/make/modules/java.base/gensrc/GensrcVarHandles.gmk b/make/modules/java.base/gensrc/GensrcVarHandles.gmk index 341a8c9dc2c..4b1697fd354 100644 --- a/make/modules/java.base/gensrc/GensrcVarHandles.gmk +++ b/make/modules/java.base/gensrc/GensrcVarHandles.gmk @@ -111,7 +111,7 @@ define GenerateVarHandleMemorySegment $1_KEYS += CAS endif ifneq ($$(filter boolean byte, $1),) - $1_KEYS += byte + $1_KEYS += ByteOrBoolean endif ifneq ($$(filter float double, $1),) $1_KEYS += floatingPoint diff --git a/src/java.base/share/classes/java/lang/invoke/X-VarHandleSegmentView.java.template b/src/java.base/share/classes/java/lang/invoke/X-VarHandleSegmentView.java.template index aa8c7b28617..0147810cb4e 100644 --- a/src/java.base/share/classes/java/lang/invoke/X-VarHandleSegmentView.java.template +++ b/src/java.base/share/classes/java/lang/invoke/X-VarHandleSegmentView.java.template @@ -33,20 +33,20 @@ import static java.lang.invoke.SegmentVarHandle.*; #warn -{#if[byte]?final:sealed} class VarHandleSegmentAs$Type$s { +{#if[ByteOrBoolean]?final:sealed} class VarHandleSegmentAs$Type$s { -#if[!byte] +#if[!ByteOrBoolean] static final int NON_PLAIN_ACCESS_MIN_ALIGN_MASK = $BoxType$.BYTES - 1; -#end[byte] +#end[ByteOrBoolean] static VarForm selectForm(long alignmentMask, boolean constantOffset) { -#if[byte] +#if[ByteOrBoolean] return constantOffset ? CONSTANT_OFFSET_FORM : VARIABLE_OFFSET_FORM; -#else[byte] +#else[ByteOrBoolean] return (alignmentMask & NON_PLAIN_ACCESS_MIN_ALIGN_MASK) != NON_PLAIN_ACCESS_MIN_ALIGN_MASK ? (constantOffset ? CONSTANT_OFFSET_FORM : VARIABLE_OFFSET_FORM) : (constantOffset ? VarHandleSegmentAs$Type$sAligned.CONSTANT_OFFSET_FORM : VarHandleSegmentAs$Type$sAligned.VARIABLE_OFFSET_FORM); -#end[byte] +#end[ByteOrBoolean] } static final VarForm CONSTANT_OFFSET_FORM = new VarForm(VarHandleSegmentAs$Type$s.class, MemorySegment.class, $type$.class, long.class); @@ -70,16 +70,16 @@ import static java.lang.invoke.SegmentVarHandle.*; handle.be); return $Type$.$rawType$BitsTo$Type$(rawValue); #else[floatingPoint] -#if[byte] - return SCOPED_MEMORY_ACCESS.get$Type$(bb.sessionImpl(), +#if[ByteOrBoolean] + return SCOPED_MEMORY_ACCESS.get$RawType$(bb.sessionImpl(), bb.unsafeGetBase(), - offset(bb, base, offset)); -#else[byte] + offset(bb, base, offset)){#if[boolean]? != 0}; +#else[ByteOrBoolean] return SCOPED_MEMORY_ACCESS.get$Type$Unaligned(bb.sessionImpl(), bb.unsafeGetBase(), offset(bb, base, offset), handle.be); -#end[byte] +#end[ByteOrBoolean] #end[floatingPoint] } @@ -99,21 +99,21 @@ import static java.lang.invoke.SegmentVarHandle.*; $Type$.$type$ToRaw$RawType$Bits(value), handle.be); #else[floatingPoint] -#if[byte] +#if[ByteOrBoolean] SCOPED_MEMORY_ACCESS.put$Type$(bb.sessionImpl(), bb.unsafeGetBase(), offset(bb, base, offset), value); -#else[byte] +#else[ByteOrBoolean] SCOPED_MEMORY_ACCESS.put$Type$Unaligned(bb.sessionImpl(), bb.unsafeGetBase(), offset(bb, base, offset), value, handle.be); -#end[byte] +#end[ByteOrBoolean] #end[floatingPoint] } -#if[!byte] +#if[!ByteOrBoolean] } // This class must be accessed through non-aligned VarHandleSegmentAs$Type$s @@ -123,7 +123,7 @@ final class VarHandleSegmentAs$Type$sAligned extends VarHandleSegmentAs$Type$s { static final VarForm VARIABLE_OFFSET_FORM = new VarForm(VarHandleSegmentAs$Type$sAligned.class, VarHandleSegmentAs$Type$s.VARIABLE_OFFSET_FORM); VarHandleSegmentAs$Type$sAligned() { throw new AssertionError(); } -#end[byte] +#end[ByteOrBoolean] #if[floatingPoint] @ForceInline @@ -138,17 +138,29 @@ final class VarHandleSegmentAs$Type$sAligned extends VarHandleSegmentAs$Type$s { return $Type$.$rawType$BitsTo$Type$(rv); } #else[floatingPoint] -#if[byte] +#if[ByteOrBoolean] +#if[boolean] + @ForceInline + static $rawType$ convEndian(boolean big, $type$ v) { + return (byte) (v ? 1 : 0); + } + + @ForceInline + static $type$ convEndian(boolean big, $rawType$ n) { + return n != 0; + } +#else[boolean] @ForceInline static $type$ convEndian(boolean big, $type$ n) { return n; } -#else[byte] +#end[boolean] +#else[ByteOrBoolean] @ForceInline static $type$ convEndian(boolean big, $type$ n) { return big == BE ? n : $BoxType$.reverseBytes(n); } -#end[byte] +#end[ByteOrBoolean] #end[floatingPoint] @ForceInline @@ -424,18 +436,18 @@ final class VarHandleSegmentAs$Type$sAligned extends VarHandleSegmentAs$Type$s { static $type$ getAndAdd(VarHandle ob, Object obb, long base, long offset, $type$ delta) { SegmentVarHandle handle = (SegmentVarHandle)ob; AbstractMemorySegmentImpl bb = handle.checkSegment(obb, base, false); -#if[!byte] +#if[!ByteOrBoolean] if (handle.be == BE) { -#end[byte] +#end[ByteOrBoolean] return SCOPED_MEMORY_ACCESS.getAndAdd$RawType$(bb.sessionImpl(), bb.unsafeGetBase(), offset(bb, base, offset), delta); -#if[!byte] +#if[!ByteOrBoolean] } else { return getAndAddConvEndianWithCAS(bb, offset(bb, base, offset), delta); } -#end[byte] +#end[ByteOrBoolean] } @ForceInline @@ -447,18 +459,18 @@ final class VarHandleSegmentAs$Type$sAligned extends VarHandleSegmentAs$Type$s { static $type$ getAndAddAcquire(VarHandle ob, Object obb, long base, long offset, $type$ delta) { SegmentVarHandle handle = (SegmentVarHandle)ob; AbstractMemorySegmentImpl bb = handle.checkSegment(obb, base, false); -#if[!byte] +#if[!ByteOrBoolean] if (handle.be == BE) { -#end[byte] +#end[ByteOrBoolean] return SCOPED_MEMORY_ACCESS.getAndAdd$RawType$Acquire(bb.sessionImpl(), bb.unsafeGetBase(), offset(bb, base, offset), delta); -#if[!byte] +#if[!ByteOrBoolean] } else { return getAndAddConvEndianWithCAS(bb, offset(bb, base, offset), delta); } -#end[byte] +#end[ByteOrBoolean] } @ForceInline @@ -470,20 +482,20 @@ final class VarHandleSegmentAs$Type$sAligned extends VarHandleSegmentAs$Type$s { static $type$ getAndAddRelease(VarHandle ob, Object obb, long base, long offset, $type$ delta) { SegmentVarHandle handle = (SegmentVarHandle)ob; AbstractMemorySegmentImpl bb = handle.checkSegment(obb, base, false); -#if[!byte] +#if[!ByteOrBoolean] if (handle.be == BE) { -#end[byte] +#end[ByteOrBoolean] return SCOPED_MEMORY_ACCESS.getAndAdd$RawType$Release(bb.sessionImpl(), bb.unsafeGetBase(), offset(bb, base, offset), delta); -#if[!byte] +#if[!ByteOrBoolean] } else { return getAndAddConvEndianWithCAS(bb, offset(bb, base, offset), delta); } -#end[byte] +#end[ByteOrBoolean] } -#if[!byte] +#if[!ByteOrBoolean] @ForceInline static $type$ getAndAddConvEndianWithCAS(AbstractMemorySegmentImpl bb, long offset, $type$ delta) { @@ -496,7 +508,7 @@ final class VarHandleSegmentAs$Type$sAligned extends VarHandleSegmentAs$Type$s { nativeExpectedValue, $RawBoxType$.reverseBytes({#if[ShorterThanInt]?($type$) }(expectedValue + delta)))); return expectedValue; } -#end[byte] +#end[ByteOrBoolean] #end[AtomicAdd] #if[Bitwise] @@ -509,18 +521,18 @@ final class VarHandleSegmentAs$Type$sAligned extends VarHandleSegmentAs$Type$s { static $type$ getAndBitwiseOr(VarHandle ob, Object obb, long base, long offset, $type$ value) { SegmentVarHandle handle = (SegmentVarHandle)ob; AbstractMemorySegmentImpl bb = handle.checkSegment(obb, base, false); -#if[!byte] +#if[!ByteOrBoolean] if (handle.be == BE) { -#end[byte] +#end[ByteOrBoolean] return SCOPED_MEMORY_ACCESS.getAndBitwiseOr$RawType$(bb.sessionImpl(), bb.unsafeGetBase(), offset(bb, base, offset), value); -#if[!byte] +#if[!ByteOrBoolean] } else { return getAndBitwiseOrConvEndianWithCAS(bb, offset(bb, base, offset), value); } -#end[byte] +#end[ByteOrBoolean] } @ForceInline @@ -532,18 +544,18 @@ final class VarHandleSegmentAs$Type$sAligned extends VarHandleSegmentAs$Type$s { static $type$ getAndBitwiseOrRelease(VarHandle ob, Object obb, long base, long offset, $type$ value) { SegmentVarHandle handle = (SegmentVarHandle)ob; AbstractMemorySegmentImpl bb = handle.checkSegment(obb, base, false); -#if[!byte] +#if[!ByteOrBoolean] if (handle.be == BE) { -#end[byte] +#end[ByteOrBoolean] return SCOPED_MEMORY_ACCESS.getAndBitwiseOr$RawType$Release(bb.sessionImpl(), bb.unsafeGetBase(), offset(bb, base, offset), value); -#if[!byte] +#if[!ByteOrBoolean] } else { return getAndBitwiseOrConvEndianWithCAS(bb, offset(bb, base, offset), value); } -#end[byte] +#end[ByteOrBoolean] } @ForceInline @@ -555,20 +567,20 @@ final class VarHandleSegmentAs$Type$sAligned extends VarHandleSegmentAs$Type$s { static $type$ getAndBitwiseOrAcquire(VarHandle ob, Object obb, long base, long offset, $type$ value) { SegmentVarHandle handle = (SegmentVarHandle)ob; AbstractMemorySegmentImpl bb = handle.checkSegment(obb, base, false); -#if[!byte] +#if[!ByteOrBoolean] if (handle.be == BE) { -#end[byte] +#end[ByteOrBoolean] return SCOPED_MEMORY_ACCESS.getAndBitwiseOr$RawType$Acquire(bb.sessionImpl(), bb.unsafeGetBase(), offset(bb, base, offset), value); -#if[!byte] +#if[!ByteOrBoolean] } else { return getAndBitwiseOrConvEndianWithCAS(bb, offset(bb, base, offset), value); } -#end[byte] +#end[ByteOrBoolean] } -#if[!byte] +#if[!ByteOrBoolean] @ForceInline static $type$ getAndBitwiseOrConvEndianWithCAS(AbstractMemorySegmentImpl bb, long offset, $type$ value) { @@ -581,7 +593,7 @@ final class VarHandleSegmentAs$Type$sAligned extends VarHandleSegmentAs$Type$s { nativeExpectedValue, $RawBoxType$.reverseBytes({#if[ShorterThanInt]?($type$) }(expectedValue | value)))); return expectedValue; } -#end[byte] +#end[ByteOrBoolean] @ForceInline static $type$ getAndBitwiseAnd(VarHandle ob, Object obb, long base, $type$ value) { @@ -592,18 +604,18 @@ final class VarHandleSegmentAs$Type$sAligned extends VarHandleSegmentAs$Type$s { static $type$ getAndBitwiseAnd(VarHandle ob, Object obb, long base, long offset, $type$ value) { SegmentVarHandle handle = (SegmentVarHandle)ob; AbstractMemorySegmentImpl bb = handle.checkSegment(obb, base, false); -#if[!byte] +#if[!ByteOrBoolean] if (handle.be == BE) { -#end[byte] +#end[ByteOrBoolean] return SCOPED_MEMORY_ACCESS.getAndBitwiseAnd$RawType$(bb.sessionImpl(), bb.unsafeGetBase(), offset(bb, base, offset), value); -#if[!byte] +#if[!ByteOrBoolean] } else { return getAndBitwiseAndConvEndianWithCAS(bb, offset(bb, base, offset), value); } -#end[byte] +#end[ByteOrBoolean] } @ForceInline @@ -615,18 +627,18 @@ final class VarHandleSegmentAs$Type$sAligned extends VarHandleSegmentAs$Type$s { static $type$ getAndBitwiseAndRelease(VarHandle ob, Object obb, long base, long offset, $type$ value) { SegmentVarHandle handle = (SegmentVarHandle)ob; AbstractMemorySegmentImpl bb = handle.checkSegment(obb, base, false); -#if[!byte] +#if[!ByteOrBoolean] if (handle.be == BE) { -#end[byte] +#end[ByteOrBoolean] return SCOPED_MEMORY_ACCESS.getAndBitwiseAnd$RawType$Release(bb.sessionImpl(), bb.unsafeGetBase(), offset(bb, base, offset), value); -#if[!byte] +#if[!ByteOrBoolean] } else { return getAndBitwiseAndConvEndianWithCAS(bb, offset(bb, base, offset), value); } -#end[byte] +#end[ByteOrBoolean] } @ForceInline @@ -639,20 +651,20 @@ final class VarHandleSegmentAs$Type$sAligned extends VarHandleSegmentAs$Type$s { static $type$ getAndBitwiseAndAcquire(VarHandle ob, Object obb, long base, long offset, $type$ value) { SegmentVarHandle handle = (SegmentVarHandle)ob; AbstractMemorySegmentImpl bb = handle.checkSegment(obb, base, false); -#if[!byte] +#if[!ByteOrBoolean] if (handle.be == BE) { -#end[byte] +#end[ByteOrBoolean] return SCOPED_MEMORY_ACCESS.getAndBitwiseAnd$RawType$Acquire(bb.sessionImpl(), bb.unsafeGetBase(), offset(bb, base, offset), value); -#if[!byte] +#if[!ByteOrBoolean] } else { return getAndBitwiseAndConvEndianWithCAS(bb, offset(bb, base, offset), value); } -#end[byte] +#end[ByteOrBoolean] } -#if[!byte] +#if[!ByteOrBoolean] @ForceInline static $type$ getAndBitwiseAndConvEndianWithCAS(AbstractMemorySegmentImpl bb, long offset, $type$ value) { @@ -665,7 +677,7 @@ final class VarHandleSegmentAs$Type$sAligned extends VarHandleSegmentAs$Type$s { nativeExpectedValue, $RawBoxType$.reverseBytes({#if[ShorterThanInt]?($type$) }(expectedValue & value)))); return expectedValue; } -#end[byte] +#end[ByteOrBoolean] @ForceInline static $type$ getAndBitwiseXor(VarHandle ob, Object obb, long base, $type$ value) { @@ -676,18 +688,18 @@ final class VarHandleSegmentAs$Type$sAligned extends VarHandleSegmentAs$Type$s { static $type$ getAndBitwiseXor(VarHandle ob, Object obb, long base, long offset, $type$ value) { SegmentVarHandle handle = (SegmentVarHandle)ob; AbstractMemorySegmentImpl bb = handle.checkSegment(obb, base, false); -#if[!byte] +#if[!ByteOrBoolean] if (handle.be == BE) { -#end[byte] +#end[ByteOrBoolean] return SCOPED_MEMORY_ACCESS.getAndBitwiseXor$RawType$(bb.sessionImpl(), bb.unsafeGetBase(), offset(bb, base, offset), value); -#if[!byte] +#if[!ByteOrBoolean] } else { return getAndBitwiseXorConvEndianWithCAS(bb, offset(bb, base, offset), value); } -#end[byte] +#end[ByteOrBoolean] } @ForceInline @@ -699,18 +711,18 @@ final class VarHandleSegmentAs$Type$sAligned extends VarHandleSegmentAs$Type$s { static $type$ getAndBitwiseXorRelease(VarHandle ob, Object obb, long base, long offset, $type$ value) { SegmentVarHandle handle = (SegmentVarHandle)ob; AbstractMemorySegmentImpl bb = handle.checkSegment(obb, base, false); -#if[!byte] +#if[!ByteOrBoolean] if (handle.be == BE) { -#end[byte] +#end[ByteOrBoolean] return SCOPED_MEMORY_ACCESS.getAndBitwiseXor$RawType$Release(bb.sessionImpl(), bb.unsafeGetBase(), offset(bb, base, offset), value); -#if[!byte] +#if[!ByteOrBoolean] } else { return getAndBitwiseXorConvEndianWithCAS(bb, offset(bb, base, offset), value); } -#end[byte] +#end[ByteOrBoolean] } @ForceInline @@ -722,20 +734,20 @@ final class VarHandleSegmentAs$Type$sAligned extends VarHandleSegmentAs$Type$s { static $type$ getAndBitwiseXorAcquire(VarHandle ob, Object obb, long base, long offset, $type$ value) { SegmentVarHandle handle = (SegmentVarHandle)ob; AbstractMemorySegmentImpl bb = handle.checkSegment(obb, base, false); -#if[!byte] +#if[!ByteOrBoolean] if (handle.be == BE) { -#end[byte] +#end[ByteOrBoolean] return SCOPED_MEMORY_ACCESS.getAndBitwiseXor$RawType$Acquire(bb.sessionImpl(), bb.unsafeGetBase(), offset(bb, base, offset), value); -#if[!byte] +#if[!ByteOrBoolean] } else { return getAndBitwiseXorConvEndianWithCAS(bb, offset(bb, base, offset), value); } -#end[byte] +#end[ByteOrBoolean] } -#if[!byte] +#if[!ByteOrBoolean] @ForceInline static $type$ getAndBitwiseXorConvEndianWithCAS(AbstractMemorySegmentImpl bb, long offset, $type$ value) { @@ -748,6 +760,6 @@ final class VarHandleSegmentAs$Type$sAligned extends VarHandleSegmentAs$Type$s { nativeExpectedValue, $RawBoxType$.reverseBytes({#if[ShorterThanInt]?($type$) }(expectedValue ^ value)))); return expectedValue; } -#end[byte] +#end[ByteOrBoolean] #end[Bitwise] } diff --git a/test/jdk/ProblemList.txt b/test/jdk/ProblemList.txt index 5e730af92b0..fcde1d9c01d 100644 --- a/test/jdk/ProblemList.txt +++ b/test/jdk/ProblemList.txt @@ -648,8 +648,6 @@ jdk/jfr/event/oldobject/TestZ.java 8375615 generic- # jdk_foreign -java/foreign/normalize/TestNormalize.java 8386848 generic-all - ############################################################################ # Client manual tests diff --git a/test/jdk/java/foreign/normalize/TestNormalizeBooleanVarHandle.java b/test/jdk/java/foreign/normalize/TestNormalizeBooleanVarHandle.java new file mode 100644 index 00000000000..acca0d095c3 --- /dev/null +++ b/test/jdk/java/foreign/normalize/TestNormalizeBooleanVarHandle.java @@ -0,0 +1,93 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @run testng TestNormalizeBooleanVarHandle + */ + +import org.testng.annotations.DataProvider; +import org.testng.annotations.Test; + +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.lang.invoke.VarHandle; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; +import java.util.function.Predicate; + +import static java.lang.foreign.ValueLayout.*; +import static org.testng.Assert.*; + +// test normalization of smaller than int primitive types +public class TestNormalizeBooleanVarHandle { + + static final VarHandle VH = JAVA_BOOLEAN.varHandle(); + + @Test(dataProvider = "bools") + public void testBool(Function segmentFactory, Predicate accessor, + byte testValue, boolean expected) { + try (Arena arena = Arena.ofConfined()) { + MemorySegment ms = segmentFactory.apply(arena); + ms.set(JAVA_BYTE, 0L, testValue); + + boolean b = accessor.test(ms); + assertEquals(b, expected); + } + } + + @DataProvider + public static Object[][] bools() { + List cases = new ArrayList<>(); + for (Function segmentFactory : factories()) { + for (Predicate accessor : accessors()) { + cases.add(new Object[]{ segmentFactory, accessor, + (byte) 0b0 , false }); // canonical false + cases.add(new Object[]{ segmentFactory, accessor, + (byte) 0b01, true }); // canonical true + cases.add(new Object[]{ segmentFactory, accessor, + (byte) 0b10, true }); // zero least significant bit, but non-zero first byte + } + } + + return cases.toArray(Object[][]::new); + } + + private static List> factories() { + return List.of( + a -> a.allocate(JAVA_BYTE), + _ -> MemorySegment.ofArray(new byte[1]) + ); + } + + private static List> accessors() { + return List.of( + ms -> ms.get(JAVA_BOOLEAN, 0L), + ms -> (boolean) VH.get(ms, 0L), + ms -> (boolean) VH.getVolatile(ms, 0L), + ms -> (boolean) VH.getAcquire(ms, 0L), + ms -> (boolean) VH.getOpaque(ms, 0L) + ); + } +} From 2130e2555b45f3d53602ab19433f4562458bbd43 Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Wed, 8 Jul 2026 16:14:12 +0000 Subject: [PATCH 105/305] 8387707: Shenandoah: Simplify reserved queue handling in mark loop Reviewed-by: kdnilsen, xpeng --- .../share/gc/shenandoah/shenandoahMark.cpp | 69 ++++++++++--------- .../share/gc/shenandoah/shenandoahMark.hpp | 4 ++ 2 files changed, 40 insertions(+), 33 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahMark.cpp b/src/hotspot/share/gc/shenandoah/shenandoahMark.cpp index a72c557a5fe..fc508dddd84 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahMark.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahMark.cpp @@ -57,12 +57,17 @@ ShenandoahMark::ShenandoahMark(ShenandoahGeneration* generation) : template void ShenandoahMark::mark_loop_prework(uint w, TaskTerminator *t, StringDedup::Requests* const req, bool update_refs) { + ShenandoahObjToScanQueueSet* queues = task_queues(); ShenandoahObjToScanQueue* q = get_queue(w); ShenandoahObjToScanQueue* old_q = get_old_queue(w); ShenandoahReferenceProcessor *rp = _generation->ref_processor(); ShenandoahHeap* const heap = ShenandoahHeap::heap(); ShenandoahLiveData* ld = heap->get_liveness_cache(w); + // Take outstanding work from queues not covered by current workers. + // We expect there is little work in those queues. + mark_drain_extra_queues(queues, q); + // TODO: We can clean up this if we figure out how to do templated oop closures that // play nice with specialized_oop_iterators. if (update_refs) { @@ -120,53 +125,51 @@ void ShenandoahMark::mark_loop(uint worker_id, TaskTerminator* terminator, Shena } } +template +void ShenandoahMark::mark_drain_extra_queues(ShenandoahObjToScanQueueSet* queues, ShenandoahObjToScanQueue* local_q) { + uintx stride = ShenandoahMarkLoopStride; + + ShenandoahHeap* heap = ShenandoahHeap::heap(); + ShenandoahMarkTask t; + + assert(queues->get_reserved() == heap->workers()->active_workers(), + "Safety: claimable queues do not intersect with worker queues: %u == %u", + queues->get_reserved(), heap->workers()->active_workers()); + + ShenandoahObjToScanQueue* q = queues->claim_next(); + while (q != nullptr) { + while (!q->is_empty()) { + if (CANCELLABLE && heap->check_cancelled_gc_and_yield()) { + return; + } + for (uint i = 0; i < stride; i++) { + if (q->pop(t)) { + local_q->push(t); + } else { + break; + } + } + } + q = queues->claim_next(); + } +} + template void ShenandoahMark::mark_loop_work(T* cl, ShenandoahLiveData* live_data, uint worker_id, TaskTerminator *terminator, StringDedup::Requests* const req) { uintx stride = ShenandoahMarkLoopStride; ShenandoahHeap* heap = ShenandoahHeap::heap(); ShenandoahObjToScanQueueSet* queues = task_queues(); - ShenandoahObjToScanQueue* q; + ShenandoahObjToScanQueue* q = get_queue(worker_id); + ShenandoahObjToScanQueue* old_q = get_old_queue(worker_id); ShenandoahMarkTask t; assert(_generation->type() == GENERATION, "Sanity: %d != %d", _generation->type(), GENERATION); _generation->ref_processor()->set_mark_closure(worker_id, cl); - /* - * Process outstanding queues, if any. - * - * There can be more queues than workers. To deal with the imbalance, we claim - * extra queues first. Since marking can push new tasks into the queue associated - * with this worker id, we come back to process this queue in the normal loop. - */ - assert(queues->get_reserved() == heap->workers()->active_workers(), - "Need to reserve proper number of queues: reserved: %u, active: %u", queues->get_reserved(), heap->workers()->active_workers()); - - q = queues->claim_next(); - while (q != nullptr) { - if (CANCELLABLE && heap->check_cancelled_gc_and_yield()) { - return; - } - - for (uint i = 0; i < stride; i++) { - if (q->pop(t)) { - do_task(q, cl, live_data, req, &t, worker_id); - } else { - assert(q->is_empty(), "Must be empty"); - q = queues->claim_next(); - break; - } - } - } - q = get_queue(worker_id); - ShenandoahObjToScanQueue* old_q = get_old_queue(worker_id); - ShenandoahSATBBufferClosure drain_satb(q, old_q); SATBMarkQueueSet& satb_mq_set = ShenandoahBarrierSet::satb_mark_queue_set(); - /* - * Normal marking loop: - */ while (true) { if (CANCELLABLE && heap->check_cancelled_gc_and_yield()) { return; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahMark.hpp b/src/hotspot/share/gc/shenandoah/shenandoahMark.hpp index 1ba2cd067b6..69d792d0277 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahMark.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahMark.hpp @@ -109,6 +109,10 @@ private: NOINLINE // Main hot loop, start inlining from here void mark_loop_work(T* cl, ShenandoahLiveData* live_data, uint worker_id, TaskTerminator *t, StringDedup::Requests* const req); + template + NOINLINE // Utility loop, maybe hot, start inlining from here + void mark_drain_extra_queues(ShenandoahObjToScanQueueSet* queues, ShenandoahObjToScanQueue* local_q); + protected: template void mark_loop(uint worker_id, TaskTerminator* terminator, ShenandoahGenerationType generation_type, From 1911bd7782e075f01eca7578c07d3f805c41c21d Mon Sep 17 00:00:00 2001 From: Boris Ulasevich Date: Wed, 8 Jul 2026 18:05:14 +0000 Subject: [PATCH 106/305] 8378719: CompiledDirectCall::set_to_interpreted() fails with guarantee(chk == -1 || chk == 0) failed: Field too big for insn Reviewed-by: eastigeevich, dlong --- src/hotspot/cpu/aarch64/aarch64.ad | 8 +- .../cpu/aarch64/macroAssembler_aarch64.cpp | 14 +- .../cpu/aarch64/macroAssembler_aarch64.hpp | 19 +-- .../codecache/TestNonNMethodHeapOverflow.java | 138 ++++++++++++++++++ 4 files changed, 156 insertions(+), 23 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/codecache/TestNonNMethodHeapOverflow.java diff --git a/src/hotspot/cpu/aarch64/aarch64.ad b/src/hotspot/cpu/aarch64/aarch64.ad index 05e4321b663..be9d79d03c7 100644 --- a/src/hotspot/cpu/aarch64/aarch64.ad +++ b/src/hotspot/cpu/aarch64/aarch64.ad @@ -1198,8 +1198,12 @@ class HandlerImpl { static int emit_deopt_handler(C2_MacroAssembler* masm); static uint size_deopt_handler() { - // count one branch instruction and one far call instruction sequence - return NativeInstruction::instruction_size + MacroAssembler::far_codestub_branch_size(); + bool use_far_branch = MacroAssembler::target_needs_far_branch(SharedRuntime::deopt_blob()->unpack()); + // far: adrp, add, blr; near: bl + uint target_branch_instructions = use_far_branch ? 3 : 1; + // target branch + one branch instruction + uint deopt_handler_instructions = target_branch_instructions + 1; + return deopt_handler_instructions * NativeInstruction::instruction_size; } }; diff --git a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp index 62a6f61599c..f2208aa0ad6 100644 --- a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp @@ -622,20 +622,18 @@ void MacroAssembler::set_last_Java_frame(Register last_java_sp, } } -static inline bool target_needs_far_branch(address addr) { +bool MacroAssembler::target_needs_far_branch(address addr) { if (AOTCodeCache::is_on_for_dump()) { return true; } - // codecache size <= 128M - if (!MacroAssembler::far_branches()) { + if (!far_branches()) { return false; } - // codecache size > 240M - if (MacroAssembler::codestub_branch_needs_far_jump()) { - return true; + if (CodeCache::is_non_nmethod(addr) && + CodeCache::max_distance_to_non_nmethod() <= branch_range) { + return false; } - // codecache size: 128M..240M - return !CodeCache::is_non_nmethod(addr); + return true; } void MacroAssembler::far_call(Address entry, Register tmp) { diff --git a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp index 740b783cbd4..b39596aab53 100644 --- a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp @@ -1354,15 +1354,16 @@ public: static bool far_branches() { return ReservedCodeCacheSize > branch_range; } - - // Check if branches to the non nmethod section require a far jump + // Check if the static call stub branch needs a far jump. static bool codestub_branch_needs_far_jump() { if (AOTCodeCache::is_on_for_dump()) { - // To calculate far_codestub_branch_size correctly. + // To calculate static_call_stub_size correctly. return true; } - return CodeCache::max_distance_to_non_nmethod() > branch_range; + return far_branches(); } + // Check if a branch to the given address needs a far jump. + static bool target_needs_far_branch(address addr); // Emit a direct call/jump if the entry address will always be in range, // otherwise a far call/jump. @@ -1374,18 +1375,10 @@ public: // In the case of a far call/jump, the entry address is put in the tmp register. // The tmp register is invalidated. // - // Far_jump returns the amount of the emitted code. void far_call(Address entry, Register tmp = rscratch1); + // Far_jump returns the amount of the emitted code. int far_jump(Address entry, Register tmp = rscratch1); - static int far_codestub_branch_size() { - if (codestub_branch_needs_far_jump()) { - return 3 * 4; // adrp, add, br - } else { - return 4; - } - } - // Emit the CompiledIC call idiom address ic_call(address entry, jint method_index = 0); static int ic_check_size(); diff --git a/test/hotspot/jtreg/compiler/codecache/TestNonNMethodHeapOverflow.java b/test/hotspot/jtreg/compiler/codecache/TestNonNMethodHeapOverflow.java new file mode 100644 index 00000000000..27562575459 --- /dev/null +++ b/test/hotspot/jtreg/compiler/codecache/TestNonNMethodHeapOverflow.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8378719 + * @summary Reproduces a RuntimeStub::resolve_static_call_blob pd_patch_instruction_size guarantee + * - forces adapters to be allocated outside the NonNMethod heap + * - puts c2i adapter and compiled method at 128+ MB distance + * @requires vm.flagless + * @requires os.arch == "aarch64" + * @requires vm.debug == false + * @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:+UnlockExperimentalVMOptions + * -XX:+WhiteBoxAPI + * -XX:ReservedCodeCacheSize=240M + * -XX:NonNMethodCodeHeapSize=8M + * -XX:ProfiledCodeHeapSize=116M + * -XX:NonProfiledCodeHeapSize=116M + * -XX:CodeCacheMinBlockLength=1 + * -XX:CodeCacheSegmentSize=128 + * -XX:-UseCodeCacheFlushing + * -XX:CompileCommand=dontinline,compiler.codecache.TestNonNMethodHeapOverflowTarget::a + * -XX:CompileCommand=exclude,compiler.codecache.TestNonNMethodHeapOverflowTarget::a + * -XX:CompileCommand=compileonly,compiler.codecache.TestNonNMethodHeapOverflowTarget::b + * compiler.codecache.TestNonNMethodHeapOverflow + */ + +package compiler.codecache; + +import jdk.test.whitebox.WhiteBox; +import jdk.test.whitebox.code.BlobType; +import jdk.test.whitebox.code.CodeBlob; +import jdk.test.whitebox.code.NMethod; + +import java.lang.reflect.Method; + +public class TestNonNMethodHeapOverflow { + private static final WhiteBox WB = WhiteBox.getWhiteBox(); + private static final int HEAP_BLOCK_HEADER_SIZE = 8; + + public static void main(String[] args) throws Exception { + WB.lockCompilation(); + + BlobType blobType; + int blobSize = 1024; + int allocSize = blobSize - HEAP_BLOCK_HEADER_SIZE; + // fill the NonNMethod heap + do { + long addr = WB.allocateCodeBlob(allocSize, BlobType.NonNMethod.id); + if (addr == 0) { + throw new RuntimeException("Failed to allocate in BlobType.NonNMethod"); + } + blobType = CodeBlob.getCodeBlob(addr).code_blob_type; + } while (blobType == BlobType.NonNMethod); + + if (blobType != BlobType.MethodNonProfiled) { + throw new RuntimeException("NonNMethod->NonProfiled fallback mechanism was changed? Need to update the test"); + } + + long heapSize = BlobType.MethodNonProfiled.getSize(); + int allocated = 0; + // fill the first half of NonProfiled heap + while (allocated < heapSize / 2) { + long addr = WB.allocateCodeBlob(allocSize, BlobType.MethodNonProfiled.id); + if (addr == 0) { + throw new RuntimeException("Failed to allocate in MethodNonProfiled"); + } + allocated += blobSize; + } + + WB.unlockCompilation(); + + // loading triggers i2c/c2i adapter generation; NonNMethod heap is full, adapters go into a middle of NonProfiled heap + Class c = Class.forName("compiler.codecache.TestNonNMethodHeapOverflowTarget"); + Method methodB = c.getDeclaredMethod("b"); + methodB.invoke(null); + + // compile b() at level 2 so the nmethod goes into the beginning of Profiled heap + int compLevel = 2; + WB.enqueueMethodForCompilation(methodB, compLevel); + while (WB.isMethodQueuedForCompilation(methodB)) { + Thread.sleep(100); + } + if (WB.getMethodCompilationLevel(methodB) != compLevel) { + throw new IllegalStateException("b() is not compiled at the compilation level " + compLevel + + ". Got: " + WB.getMethodCompilationLevel(methodB)); + } + + // The distance from the static call stub in nmethod to the c2i adapter exceeds 128MB (AArch64 near-branch range): + // + // | Profiled | NonNMethod | NonProfiled | + // -------------------------------- ------------ -------------------------------- + // |[nmethod] |############|################[c2i] | + + NMethod nm = NMethod.get(methodB, false); + System.out.println("b() at 0x" + Long.toHexString(nm.address) + " heap=" + nm.code_blob_type); + if (nm.code_blob_type != BlobType.MethodProfiled) { + throw new RuntimeException("b() is expected to be in MethodProfiled heap, got: " + nm.code_blob_type); + } + + // invoke compiled b(): triggers resolve_static_call_blob to patch the static call stub + // in nmethod to point to the c2i adapter for a() + methodB.invoke(null); + } +} + +class TestNonNMethodHeapOverflowTarget { + static float a(float f1, double d1, long l1, int i1, float f2, double d2) { + return f1; + } + static float b() { + return a(1.0f, 2.0, 3L, 4, 5.0f, 6.0); + } +} From d5dcbe860da9adf41f2ed0ecbb6f5aa17468d0ee Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Wed, 8 Jul 2026 21:25:57 +0000 Subject: [PATCH 107/305] 8387907: Shenandoah: Marking loop prefetch Reviewed-by: wkemper, xpeng, stuefe --- .../gc/shenandoah/shenandoahMark.inline.hpp | 3 +- .../shenandoah/shenandoahPrefetch.inline.hpp | 77 +++++++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 src/hotspot/share/gc/shenandoah/shenandoahPrefetch.inline.hpp diff --git a/src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp index 72129ff9e14..8a7ce7ea831 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp @@ -37,13 +37,13 @@ #include "gc/shenandoah/shenandoahHeap.inline.hpp" #include "gc/shenandoah/shenandoahMarkingContext.inline.hpp" #include "gc/shenandoah/shenandoahOldGeneration.hpp" +#include "gc/shenandoah/shenandoahPrefetch.inline.hpp" #include "gc/shenandoah/shenandoahScanRemembered.inline.hpp" #include "gc/shenandoah/shenandoahTaskqueue.inline.hpp" #include "gc/shenandoah/shenandoahUtils.hpp" #include "memory/iterator.inline.hpp" #include "oops/compressedOops.inline.hpp" #include "oops/oop.inline.hpp" -#include "runtime/prefetch.inline.hpp" #include "utilities/devirtualizer.inline.hpp" #include "utilities/powerOfTwo.hpp" @@ -365,6 +365,7 @@ inline void ShenandoahMark::mark_ref(ShenandoahObjToScanQueue* q, marked = mark_context->mark_strong(obj, /* was_upgraded = */ skip_live); } if (marked) { + ShenandoahPrefetch::prefetch(obj); bool pushed = q->push(ShenandoahMarkTask(obj, skip_live, weak)); assert(pushed, "overflow queue should always succeed pushing"); } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahPrefetch.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahPrefetch.inline.hpp new file mode 100644 index 00000000000..35aa297e629 --- /dev/null +++ b/src/hotspot/share/gc/shenandoah/shenandoahPrefetch.inline.hpp @@ -0,0 +1,77 @@ +/* + * 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. + * + */ + +#ifndef SHARE_GC_SHENANDOAH_SHENANDOAHPREFETCH_INLINE_HPP +#define SHARE_GC_SHENANDOAH_SHENANDOAHPREFETCH_INLINE_HPP + +// No shenandoahPrefetch.hpp + +#include "memory/allStatic.hpp" +#include "runtime/prefetch.inline.hpp" + +// Utility to centralize prefetching decisions. +// +// Prefetching needs to strike the balance between the latency savings +// from upcoming accesses and the excess memory throughput for accesses +// that are prefetched but are never used. +// +// A common access pattern for the object in hot GC code is: +// [mark word] // sometimes, for forwarding pointer accesses +// [klass word] // very often, to discover object type +// ... +// [oop field N] // often, to traverse the heap or fix references +// +// Prefetches work on cache line granularity, so we can pick and choose +// good static offsets at which to prefetch. It also frees us from +// polling mark/klass word offsets at runtime. +// +// It stands to reason that prefetching at zero is most beneficial. +// Since it is almost guaranteed to be used by future accesses, there is +// little downside. For objects that are fully within the cache line, +// that zero-prefetch also picks up oop fields nicely. +// +// Experiments suggest it is also important to handle the case when +// object crosses the cache line. In this case, zero-prefetch is likely +// to miss the oop fields cache line. In extreme case, it can prefetch only +// the mark word, leaving klass word unprefetched. We can prefetch +// the full next cache line to deal with this case, but it is wasteful, +// especially on platforms with very large cache lines. +// +// Therefore, the second prefetch is done at some small offset to balance +// the crossing case. If second prefetch hits the same cache line as the +// first one, there is little downside. This also works automagically with +// platforms with larger cache line sizes, as both prefetches would converge. +// If prefetch hits another cache line, it likely means the object crosses +// the cache line, and that the second prefetch is profitable. +// +class ShenandoahPrefetch : AllStatic { +public: + static void prefetch(oop obj) { + void* addr = obj->base_addr(); + Prefetch::read(addr, 0); + Prefetch::read(addr, 32); + } +}; + +#endif // SHARE_GC_SHENANDOAH_SHENANDOAHPREFETCH_INLINE_HPP From 182b0cb2063012c6f5cb40617f33165fc8a13249 Mon Sep 17 00:00:00 2001 From: April Ivy Date: Thu, 9 Jul 2026 00:17:45 +0000 Subject: [PATCH 108/305] 8387937: C1: aarch64: two-arg add_debug_info_for_branch looks obsolete Reviewed-by: dlong --- src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp | 10 ---------- src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.hpp | 3 +-- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp index 202f3227e2d..0290a200366 100644 --- a/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp @@ -452,16 +452,6 @@ int LIR_Assembler::emit_deopt_handler() { return entry_offset; } -void LIR_Assembler::add_debug_info_for_branch(address adr, CodeEmitInfo* info) { - _masm->code_section()->relocate(adr, relocInfo::poll_type); - int pc_offset = code_offset(); - flush_debug_info(pc_offset); - info->record_debug_info(compilation()->debug_info_recorder(), pc_offset); - if (info->exception_handlers() != nullptr) { - compilation()->add_exception_handlers_for_pco(pc_offset, info->exception_handlers()); - } -} - void LIR_Assembler::return_op(LIR_Opr result, C1SafepointPollStub* code_stub) { assert(result->is_illegal() || !result->is_single_cpu() || result->as_register() == r0, "word returns are in r0,"); diff --git a/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.hpp b/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.hpp index 367256d2f69..bebc9543b40 100644 --- a/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2014, Red Hat Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -52,7 +52,6 @@ friend class ArrayCopyStub; // Record the type of the receiver in ReceiverTypeData void type_profile_helper(Register mdo, ciMethodData *md, ciProfileData *data, Register recv); - void add_debug_info_for_branch(address adr, CodeEmitInfo* info); void casw(Register addr, Register newval, Register cmpval); void casl(Register addr, Register newval, Register cmpval); From 05c93a1dbb0d6f9c4da10d5a7d924a64408d40d2 Mon Sep 17 00:00:00 2001 From: Emanuel Peter Date: Thu, 9 Jul 2026 04:49:49 +0000 Subject: [PATCH 109/305] 8387411: C2: assert((in_vt->isa_pvectmask() == nullptr) == (vt->isa_pvectmask() == nullptr)) failed: Both BVectMask, or both NVectMask, or both PVectMask Reviewed-by: chagedorn, thartmann, vlivanov --- src/hotspot/share/opto/vectornode.cpp | 8 +- .../TestMaskUnboxingTypeMismatch.java | 80 +++++++++++++++++++ 2 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 test/hotspot/jtreg/compiler/vectorapi/TestMaskUnboxingTypeMismatch.java diff --git a/src/hotspot/share/opto/vectornode.cpp b/src/hotspot/share/opto/vectornode.cpp index a0454a41044..20857eed35c 100644 --- a/src/hotspot/share/opto/vectornode.cpp +++ b/src/hotspot/share/opto/vectornode.cpp @@ -2309,7 +2309,13 @@ Node* VectorUnboxNode::Ideal(PhaseGVN* phase, bool can_reshape) { if (is_vector_mask) { // VectorUnbox (VectorBox vmask) ==> VectorMaskCast vmask const TypeVect* vmask_type = TypeVect::makemask(out_vt->element_basic_type(), out_vt->length()); - return new VectorMaskCastNode(value, vmask_type); + const TypeVect* value_type = value->bottom_type()->is_vect(); + // Very rarely, profiling can give us output types that are not + // compatible with the input type, where one is PVectMask and + // the other not. Such a path should be unreachable anyway. + if ((value_type->isa_pvectmask() == nullptr) == (vmask_type->isa_pvectmask() == nullptr)) { + return new VectorMaskCastNode(value, vmask_type); + } } else { // Vector type mismatch is only supported for masks, but sometimes it happens in pathological cases. } diff --git a/test/hotspot/jtreg/compiler/vectorapi/TestMaskUnboxingTypeMismatch.java b/test/hotspot/jtreg/compiler/vectorapi/TestMaskUnboxingTypeMismatch.java new file mode 100644 index 00000000000..f24d2d9d087 --- /dev/null +++ b/test/hotspot/jtreg/compiler/vectorapi/TestMaskUnboxingTypeMismatch.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.vectorapi; + +import jdk.incubator.vector.*; + +/* + * @test id=vanilla + * @bug 8387411 + * @modules jdk.incubator.vector + * + * @run driver ${test.main.class} + */ + +/* + * @test id=KNL + * @bug 8387411 + * @modules jdk.incubator.vector + * + * @run main/othervm -Xbatch + * -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions -XX:+UseKNLSetting + * -XX:CompileCommand=compileonly,${test.main.class}::test + * ${test.main.class} + */ + +public class TestMaskUnboxingTypeMismatch { + + public static Object pollute() { + VectorMask intMask = VectorMask.fromLong(IntVector.SPECIES_512, 1L); + // Profile "andNot" with I512. + return intMask.andNot(intMask); + } + + public static Object test() { + var v0 = ByteVector.broadcast(ByteVector.SPECIES_128, (byte)7); + var v1 = VectorMask.fromLong(ByteVector.SPECIES_128, 1L); + var v2 = VectorMask.fromLong(ByteVector.SPECIES_128, 2L); + // Use "andNot" with B128. + // We can get some boxing of B128 mask, which is later unboxed + // as profiled I512, which is impossible. When trying to insert + // an VectorMaskCast in VectorUnboxNode::Ideal, we hit an assert, + // because with UseKNLSetting, B128 mask is a NVectMask, and I512 + // a PVectMask. + var v3 = v1.andNot(v2); + var v4 = v0.lanewise(VectorOperators.UMAX, (byte)42, v3); + return v4; + } + + public static void main(String[] args) { + // Sufficient repetitions to get some profiling. + for (int i = 0; i < 10_000; i++) { + pollute(); + } + // Sufficient repetitions to get compilation. + for (int i = 0; i < 50_000; i++) { + test(); + } + } +} From 333deb2cc63d8a16600a09e5f933c2a5091fda3d Mon Sep 17 00:00:00 2001 From: Benjamin Peterson Date: Thu, 9 Jul 2026 06:57:14 +0000 Subject: [PATCH 110/305] 8380967: Canceled HttpClient.sendAsync futures throw inconsistent exceptions Reviewed-by: dfuchs --- .../net/http/common/MinimalFuture.java | 13 +++++--- .../net/httpclient/CancelRequestTest.java | 29 +++++------------ .../net/http/common/MinimalFutureTest.java | 32 +++++++++++++++++++ 3 files changed, 48 insertions(+), 26 deletions(-) diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/common/MinimalFuture.java b/src/java.net.http/share/classes/jdk/internal/net/http/common/MinimalFuture.java index ddbcce661aa..268705f6c1f 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/common/MinimalFuture.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/common/MinimalFuture.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -102,11 +102,14 @@ public final class MinimalFuture extends CompletableFuture { @Override public boolean cancel(boolean mayInterruptIfRunning) { - boolean result = false; - if (cancelable != null && !isDone()) { - result = cancelable.cancel(mayInterruptIfRunning); + if (!super.cancel(mayInterruptIfRunning)) { + assert isDone(); + return false; } - return super.cancel(mayInterruptIfRunning) || result; + if (cancelable != null) { + cancelable.cancel(mayInterruptIfRunning); + } + return true; } private Cancelable cancelable() { diff --git a/test/jdk/java/net/httpclient/CancelRequestTest.java b/test/jdk/java/net/httpclient/CancelRequestTest.java index f21d13d5e98..418127c7735 100644 --- a/test/jdk/java/net/httpclient/CancelRequestTest.java +++ b/test/jdk/java/net/httpclient/CancelRequestTest.java @@ -23,7 +23,7 @@ /* * @test - * @bug 8245462 8229822 8254786 8297075 8297149 8298340 8302635 8377181 + * @bug 8245462 8229822 8254786 8297075 8297149 8298340 8302635 8377181 8380967 * @summary Tests cancelling the request. * @library /test/lib /test/jdk/java/net/httpclient/lib * @key randomness @@ -79,6 +79,7 @@ import org.junit.jupiter.api.AfterAll; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import org.junit.jupiter.api.Assumptions; @@ -394,15 +395,9 @@ public class CancelRequestTest implements HttpServerAdapters { requestLatch.countDown(); } - // Cancelling the request may cause an IOException instead... - boolean hasCancellationException = false; - try { - cf1.get(); - } catch (CancellationException | ExecutionException x) { - out.println(now() + "Got expected exception: " + x); - assertTrue(isCancelled(x)); - hasCancellationException = x instanceof CancellationException; - } + var cancelX = assertThrows(CancellationException.class, cf1::get); + out.println(now() + "Got expected exception: " + cancelX); + assertTrue(cf1.isCancelled()); // because it's cf1 that was cancelled then response might not have // completed yet - so wait for it here... @@ -447,7 +442,6 @@ public class CancelRequestTest implements HttpServerAdapters { assertTrue(response.isDone()); assertFalse(response.isCancelled()); - assertEquals(hasCancellationException, cf1.isCancelled()); assertTrue(cf2.isDone()); assertFalse(cf2.isCancelled()); assertEquals(0, latch.getCount()); @@ -529,15 +523,9 @@ public class CancelRequestTest implements HttpServerAdapters { requestLatch.countDown(); } - // Cancelling the request may cause an IOException instead... - boolean hasCancellationException = false; - try { - cf1.get(); - } catch (CancellationException | ExecutionException x) { - out.println(now() + "Got expected exception: " + x); - assertTrue(isCancelled(x)); - hasCancellationException = x instanceof CancellationException; - } + var cancelX = assertThrows(CancellationException.class, cf1::get); + out.println(now() + "Got expected exception: " + cancelX); + assertTrue(cf1.isCancelled()); // because it's cf1 that was cancelled then response might not have // completed yet - so wait for it here... @@ -576,7 +564,6 @@ public class CancelRequestTest implements HttpServerAdapters { assertTrue(response.isDone()); assertFalse(response.isCancelled()); - assertEquals(hasCancellationException, cf1.isCancelled()); assertTrue(cf2.isDone()); assertFalse(cf2.isCancelled()); assertEquals(0, latch.getCount()); diff --git a/test/jdk/java/net/httpclient/whitebox/java.net.http/jdk/internal/net/http/common/MinimalFutureTest.java b/test/jdk/java/net/httpclient/whitebox/java.net.http/jdk/internal/net/http/common/MinimalFutureTest.java index 2c33f6f0018..ccac8fcf439 100644 --- a/test/jdk/java/net/httpclient/whitebox/java.net.http/jdk/internal/net/http/common/MinimalFutureTest.java +++ b/test/jdk/java/net/httpclient/whitebox/java.net.http/jdk/internal/net/http/common/MinimalFutureTest.java @@ -27,10 +27,16 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; public class MinimalFutureTest { @@ -101,6 +107,32 @@ public class MinimalFutureTest { } } + @Test + public void testCancel() { + AtomicInteger cancelCount = new AtomicInteger(); + AtomicBoolean cancelled = new AtomicBoolean(); + Cancelable cancelable = mayInterruptIfRunning -> { + cancelCount.incrementAndGet(); + if (mayInterruptIfRunning) { + cancelled.set(true); + } + return cancelled.get(); + }; + MinimalFuture future = new MinimalFuture<>(cancelable); + CompletableFuture dependent = future.copy().whenComplete((x,t) -> + System.out.println("expected: " + t)); + assertTrue(dependent.cancel(false)); + assertTrue(dependent.isCancelled()); + assertFalse(future.isCancelled()); + assertFalse(cancelled.get()); + assertEquals(1, cancelCount.get()); + assertTrue(dependent.cancel(true)); + assertTrue(dependent.isCancelled()); + assertFalse(future.isCancelled()); + assertTrue(cancelled.get()); + assertEquals(2, cancelCount.get()); + } + private static CompletableFuture otherFuture() { return MinimalFuture.completedFuture(new Object()); } From 31dede3f96d78dd0b1c93d84adb1f97b34ff339f Mon Sep 17 00:00:00 2001 From: Dingli Zhang Date: Thu, 9 Jul 2026 07:47:14 +0000 Subject: [PATCH 111/305] 8368180: RISC-V: Remove redundant ext_Zicboz.enable_feature() Reviewed-by: fyang, gcao --- src/hotspot/os_cpu/linux_riscv/vm_version_linux_riscv.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/hotspot/os_cpu/linux_riscv/vm_version_linux_riscv.cpp b/src/hotspot/os_cpu/linux_riscv/vm_version_linux_riscv.cpp index 648131b94a3..c9556d32cc5 100644 --- a/src/hotspot/os_cpu/linux_riscv/vm_version_linux_riscv.cpp +++ b/src/hotspot/os_cpu/linux_riscv/vm_version_linux_riscv.cpp @@ -307,7 +307,6 @@ void VM_Version::rivos_features() { ext_Zfh.enable_feature(); - ext_Zicboz.enable_feature(); ext_Zicsr.enable_feature(); ext_Zifencei.enable_feature(); ext_Zic64b.enable_feature(); From 7753c98686006bc5710169dd4d3ca312495a8ad1 Mon Sep 17 00:00:00 2001 From: Ashutosh Mehra Date: Thu, 9 Jul 2026 13:19:37 +0000 Subject: [PATCH 112/305] 8386475: C2 x64: -XX:-UseBMI2Instructions is broken for AVX-512 Reviewed-by: galder, epeter, kvn --- src/hotspot/cpu/x86/macroAssembler_x86.cpp | 6 +- src/hotspot/cpu/x86/stubGenerator_x86_64.cpp | 4 +- .../cpuflags/TestUseBMI2Instructions.java | 62 +++++++++++++++++++ 3 files changed, 67 insertions(+), 5 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/cpuflags/TestUseBMI2Instructions.java diff --git a/src/hotspot/cpu/x86/macroAssembler_x86.cpp b/src/hotspot/cpu/x86/macroAssembler_x86.cpp index d1250f0820f..6c0b1178b0e 100644 --- a/src/hotspot/cpu/x86/macroAssembler_x86.cpp +++ b/src/hotspot/cpu/x86/macroAssembler_x86.cpp @@ -5874,7 +5874,7 @@ void MacroAssembler::xmm_clear_mem(Register base, Register cnt, Register rtmp, X // cnt - number of qwords (8-byte words). // base - start address, qword aligned. Label L_zero_64_bytes, L_loop, L_sloop, L_tail, L_end; - bool use64byteVector = (MaxVectorSize == 64) && (CopyAVX3Threshold == 0); + bool use64byteVector = (MaxVectorSize == 64) && (CopyAVX3Threshold == 0) && VM_Version::supports_bmi2(); if (use64byteVector) { vpxor(xtmp, xtmp, xtmp, AVX_512bit); } else if (MaxVectorSize >= 32) { @@ -5921,7 +5921,7 @@ void MacroAssembler::xmm_clear_mem(Register base, Register cnt, Register rtmp, X BIND(L_tail); addptr(cnt, 4); jccb(Assembler::lessEqual, L_end); - if (UseAVX > 2 && MaxVectorSize >= 32 && VM_Version::supports_avx512vl()) { + if (UseAVX > 2 && MaxVectorSize >= 32 && VM_Version::supports_avx512vl() && VM_Version::supports_bmi2()) { fill32_masked(3, base, 0, xtmp, mask, cnt, rtmp); } else { decrement(cnt); @@ -6984,7 +6984,7 @@ void MacroAssembler::vectorized_mismatch(Register obja, Register objb, Register xorq(result, result); if ((AVX3Threshold == 0) && (UseAVX > 2) && - VM_Version::supports_avx512vlbw() && UseCountTrailingZerosInstruction) { + VM_Version::supports_avx512vlbw() && UseCountTrailingZerosInstruction && VM_Version::supports_bmi2()) { Label VECTOR64_LOOP, VECTOR64_NOT_EQUAL, VECTOR32_TAIL; cmpq(length, 64); diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64.cpp index afd9c126a21..2b37e39ec86 100644 --- a/src/hotspot/cpu/x86/stubGenerator_x86_64.cpp +++ b/src/hotspot/cpu/x86/stubGenerator_x86_64.cpp @@ -3069,7 +3069,7 @@ address StubGenerator::generate_base64_decodeBlock() { // If AVX512 VBMI not supported, just compile non-AVX code if(VM_Version::supports_avx512_vbmi() && - VM_Version::supports_avx512bw()) { + VM_Version::supports_avx512bw() && VM_Version::supports_bmi2()) { __ cmpl(length, 31); // 32-bytes is break-even for AVX-512 __ jcc(Assembler::lessEqual, L_lastChunk); @@ -4887,7 +4887,7 @@ void StubGenerator::generate_compiler_stubs() { StubRoutines::_data_cache_writeback = generate_data_cache_writeback(); StubRoutines::_data_cache_writeback_sync = generate_data_cache_writeback_sync(); - if ((UseAVX == 2) && EnableX86ECoreOpts && UseCountTrailingZerosInstruction) { + if ((UseAVX == 2) && EnableX86ECoreOpts && UseCountTrailingZerosInstruction && VM_Version::supports_bmi2()) { generate_string_indexof(StubRoutines::_string_indexof_array); } diff --git a/test/hotspot/jtreg/compiler/cpuflags/TestUseBMI2Instructions.java b/test/hotspot/jtreg/compiler/cpuflags/TestUseBMI2Instructions.java new file mode 100644 index 00000000000..df595a5ff26 --- /dev/null +++ b/test/hotspot/jtreg/compiler/cpuflags/TestUseBMI2Instructions.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8386475 + * @summary Verify no assertions with -XX:+UseBMI2Instructions + * @requires os.simpleArch == "x64" + * @run main/othervm -XX:+UseBMI2Instructions ${test.main.class} + */ + +/* + * @test + * @bug 8386475 + * @summary Verify no assertions with -XX:-UseBMI2Instructions + * @requires os.simpleArch == "x64" + * @run main/othervm -Xcomp -XX:CompileCommand=compileonly,java.lang.CharacterDataLatin1:: -XX:+UnlockDiagnosticVMOptions -XX:CopyAVX3Threshold=0 -XX:-UseBMI2Instructions ${test.main.class} + */ + +/* + * @test + * @bug 8386475 + * @summary Verify no assertions when generating vectorizedMismatch stub with -XX:-UseBMI2Instructions + * @requires os.simpleArch == "x64" & vm.cpu.features ~= ".*avx2.*" + * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:AVX3Threshold=0 -XX:-UseBMI2Instructions ${test.main.class} + */ + +/* + * @test + * @bug 8386475 + * @summary Verify no assertions when generating string_indexof stub with -XX:-UseBMI2Instructions + * @requires os.simpleArch == "x64" & vm.cpu.features ~= ".*avx2.*" + * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:UseAVX=2 -XX:+EnableX86ECoreOpts -XX:-UseBMI2Instructions ${test.main.class} + */ + +package compiler.cpuflags; + +public class TestUseBMI2Instructions { + public static void main(String args[]) { + // intentionally empty + } +} From d425cbe2f0df08aacd9f5093a758455309ab0cf5 Mon Sep 17 00:00:00 2001 From: Amit Kumar Date: Thu, 9 Jul 2026 13:38:52 +0000 Subject: [PATCH 113/305] 8286300: Port JEP 425 to S390X 8377034: Enable full JSR166TestCase.java test for s390x 8380035: compiler/intrinsics/TestReturnOopSetForJFRWriteCheckpoint.java crashes on s390x 8335163: [s390x] test failure - PrintClasses.java Co-authored-by: Andrew Haley Co-authored-by: Richard Reingruber Reviewed-by: rrich, aph, pchilanomate --- .../cpu/s390/abstractInterpreter_s390.cpp | 8 +- src/hotspot/cpu/s390/assembler_s390.hpp | 5 +- src/hotspot/cpu/s390/c1_LIRAssembler_s390.cpp | 4 +- src/hotspot/cpu/s390/c1_Runtime1_s390.cpp | 49 +- .../cpu/s390/continuationEntry_s390.hpp | 6 +- .../s390/continuationEntry_s390.inline.hpp | 23 +- .../continuationFreezeThaw_s390.inline.hpp | 277 ++++++++- .../s390/continuationHelper_s390.inline.hpp | 116 ++-- src/hotspot/cpu/s390/frame_s390.cpp | 85 ++- src/hotspot/cpu/s390/frame_s390.hpp | 49 +- src/hotspot/cpu/s390/frame_s390.inline.hpp | 106 +++- src/hotspot/cpu/s390/globals_s390.hpp | 2 +- src/hotspot/cpu/s390/interp_masm_s390.cpp | 126 +++- src/hotspot/cpu/s390/interp_masm_s390.hpp | 8 +- src/hotspot/cpu/s390/macroAssembler_s390.cpp | 114 +++- src/hotspot/cpu/s390/macroAssembler_s390.hpp | 26 +- .../cpu/s390/macroAssembler_s390.inline.hpp | 14 +- src/hotspot/cpu/s390/nativeInst_s390.cpp | 31 +- src/hotspot/cpu/s390/nativeInst_s390.hpp | 44 +- src/hotspot/cpu/s390/s390.ad | 35 ++ src/hotspot/cpu/s390/sharedRuntime_s390.cpp | 582 +++++++++++++++++- .../cpu/s390/smallRegisterMap_s390.inline.hpp | 17 +- .../stackChunkFrameStream_s390.inline.hpp | 90 ++- .../cpu/s390/stackChunkOop_s390.inline.hpp | 11 +- .../cpu/s390/stubDeclarations_s390.hpp | 4 +- src/hotspot/cpu/s390/stubGenerator_s390.cpp | 184 +++++- .../templateInterpreterGenerator_s390.cpp | 71 ++- src/hotspot/cpu/s390/templateTable_s390.cpp | 8 +- src/hotspot/cpu/s390/upcallLinker_s390.cpp | 4 + .../share/oops/stackChunkOop.inline.hpp | 4 +- src/hotspot/share/runtime/continuation.cpp | 2 +- .../share/runtime/continuationFreezeThaw.cpp | 29 +- src/hotspot/share/runtime/frame.cpp | 6 +- src/hotspot/share/runtime/sharedRuntime.cpp | 10 +- test/hotspot/jtreg/ProblemList.txt | 2 - test/jdk/ProblemList.txt | 18 - .../util/concurrent/tck/JSR166TestCase.java | 14 - 37 files changed, 1840 insertions(+), 344 deletions(-) diff --git a/src/hotspot/cpu/s390/abstractInterpreter_s390.cpp b/src/hotspot/cpu/s390/abstractInterpreter_s390.cpp index 96990f0ce94..c54f1a4b010 100644 --- a/src/hotspot/cpu/s390/abstractInterpreter_s390.cpp +++ b/src/hotspot/cpu/s390/abstractInterpreter_s390.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -197,8 +197,10 @@ void AbstractInterpreter::layout_activation(Method* method, assert(is_bottom_frame && (sender_sp == caller->unextended_sp()), "must initialize sender_sp of bottom skeleton frame when pushing it"); } else { - assert(caller->is_entry_frame() || caller->is_upcall_stub_frame(), "is there a new frame type??"); - sender_sp = caller->sp(); // Call_stub only uses it's fp. + // For entry, upcall_stub, and native frames, sender_sp is simply the caller's sp. + // These frames use the standard C ABI and don't require adjustment. + assert(caller->is_entry_frame() || caller->is_upcall_stub_frame() || caller->is_native_frame(), "is there a new frame type??"); + sender_sp = caller->sp(); } interpreter_frame->interpreter_frame_set_method(method); diff --git a/src/hotspot/cpu/s390/assembler_s390.hpp b/src/hotspot/cpu/s390/assembler_s390.hpp index c0cee5bd555..95ae442bb49 100644 --- a/src/hotspot/cpu/s390/assembler_s390.hpp +++ b/src/hotspot/cpu/s390/assembler_s390.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016, 2024 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -3279,6 +3279,9 @@ class Assembler : public AbstractAssembler { static bool is_z_nop(address x) { return is_z_nop(* (short *) x); } + static bool is_z_illtrap(address x) { + return *(uint16_t*)x == 0u; + } static bool is_z_br(long x) { return is_z_bcr(x) && ((x & 0x00f0) == 0x00f0); } diff --git a/src/hotspot/cpu/s390/c1_LIRAssembler_s390.cpp b/src/hotspot/cpu/s390/c1_LIRAssembler_s390.cpp index 08f922a0b9a..db3f2f6218f 100644 --- a/src/hotspot/cpu/s390/c1_LIRAssembler_s390.cpp +++ b/src/hotspot/cpu/s390/c1_LIRAssembler_s390.cpp @@ -524,6 +524,7 @@ void LIR_Assembler::call(LIR_OpJavaCall* op, relocInfo::relocType rtype) { __ z_nop(); __ z_brasl(Z_R14, op->addr()); add_call_info(code_offset(), op->info()); + __ post_call_nop(); } void LIR_Assembler::ic_call(LIR_OpJavaCall* op) { @@ -539,7 +540,7 @@ void LIR_Assembler::ic_call(LIR_OpJavaCall* op) { // CALL to fixup routine. Fixup routine uses ScopeDesc info // to determine who we intended to call. __ relocate(virtual_call_Relocation::spec(virtual_call_oop_addr)); - call(op, relocInfo::none); + call(op, relocInfo::none); // call will emit a post call nop, see above method. } void LIR_Assembler::move_regs(Register from_reg, Register to_reg) { @@ -2792,6 +2793,7 @@ void LIR_Assembler::rt_call(LIR_Opr result, address dest, if (info != nullptr) { add_call_info_here(info); } + __ post_call_nop(); } void LIR_Assembler::volatile_move_op(LIR_Opr src, LIR_Opr dest, BasicType type, CodeEmitInfo* info) { diff --git a/src/hotspot/cpu/s390/c1_Runtime1_s390.cpp b/src/hotspot/cpu/s390/c1_Runtime1_s390.cpp index e78b04fe911..d26db67d078 100644 --- a/src/hotspot/cpu/s390/c1_Runtime1_s390.cpp +++ b/src/hotspot/cpu/s390/c1_Runtime1_s390.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016, 2023 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -52,13 +52,8 @@ int StubAssembler::call_RT(Register oop_result1, Register metadata_result, addre set_num_rt_args(0); // Nothing on stack. assert(!(oop_result1->is_valid() || metadata_result->is_valid()) || oop_result1 != metadata_result, "registers must be different"); - // We cannot trust that code generated by the C++ compiler saves R14 - // to z_abi_160.return_pc, because sometimes it spills R14 using stmg at - // z_abi_160.gpr14 (e.g. InterpreterRuntime::_new()). - // Therefore we load the PC into Z_R1_scratch and let set_last_Java_frame() save - // it into the frame anchor. - address pc = get_PC(Z_R1_scratch); - int call_offset = (int)(pc - addr_at(0)); + Label resume; + z_larl(Z_R1_scratch, resume); set_last_Java_frame(Z_SP, Z_R1_scratch); // ARG1 must hold thread address. @@ -67,9 +62,12 @@ int StubAssembler::call_RT(Register oop_result1, Register metadata_result, addre address return_pc = nullptr; align_call_far_patchable(this->pc()); return_pc = call_c_opt(entry_point); + + bind(resume); + int call_offset = offset(); assert(return_pc != nullptr, "const section overflow"); - reset_last_Java_frame(); + reset_last_Java_frame(/* check_last_java_sp= */ false); // Check for pending exceptions. { @@ -208,8 +206,37 @@ void Runtime1::initialize_pd() { } uint Runtime1::runtime_blob_current_thread_offset(frame f) { - Unimplemented(); - return 0; + CodeBlob* cb = f.cb(); + assert(cb == Runtime1::blob_for(StubId::c1_monitorenter_id) || + cb == Runtime1::blob_for(StubId::c1_monitorenter_nofpu_id), "must be"); + assert(cb != nullptr && cb->is_runtime_stub(), "invalid frame"); + + // Calculate the offset of Z_thread (Z_R8) in the saved register area. + // Both c1_monitorenter_id and c1_monitorenter_nofpu_id have the same frame layout: + // - c1_monitorenter_id uses RegisterSaver::all_registers (saves FPU regs) + // - c1_monitorenter_nofpu_id uses RegisterSaver::all_integer_registers (excludes FPU regs but reserves space) + // + // From RegisterSaver_LiveRegs and RegisterSaver_LiveIntRegs: + // Both have 15 float register slots (F0, F2-F15, F1 is excluded as scratch) + // Then integer registers: R2, R3, R4, R5, R6, R7, R8, R9, R10, R11, R12, R13 + // Z_thread is Z_R8, which is the 7th integer register (index 6 from R2) + // + // Stack layout from SP: + // [0..159] : z_abi_160 + // [160..279] : 15 float register slots (15 * 8 = 120 bytes) + // [280..327] : R2-R7 (6 * 8 = 48 bytes) + // [328..335] : R8 (Z_thread) <- this is what we need + // + // Offset = 160 + 120 + 48 = 328 bytes from SP + // Return value is in 64-bit words: 328 / 8 = 41 + + const int float_reg_slots = 15; // F0, F2-F15 (F1 is scratch, excluded) + const int int_regs_before_r8 = 6; // R2, R3, R4, R5, R6, R7 + const int z_thread_offset = frame::z_abi_160_size + + (float_reg_slots * 8) + + (int_regs_before_r8 * 8); + + return z_thread_offset / wordSize; } OopMapSet* Runtime1::generate_exception_throw(StubAssembler* sasm, address target, bool has_argument) { diff --git a/src/hotspot/cpu/s390/continuationEntry_s390.hpp b/src/hotspot/cpu/s390/continuationEntry_s390.hpp index e4e611d2b15..15b1347ce0a 100644 --- a/src/hotspot/cpu/s390/continuationEntry_s390.hpp +++ b/src/hotspot/cpu/s390/continuationEntry_s390.hpp @@ -1,5 +1,6 @@ /* * Copyright (c) 2022 SAP SE. All rights reserved. + * Copyright (c) 2026 IBM Corporation. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,8 +26,11 @@ #ifndef CPU_S390_CONTINUATIONENTRY_S390_HPP #define CPU_S390_CONTINUATIONENTRY_S390_HPP +#include "runtime/frame.hpp" + class ContinuationEntryPD { - // empty + // This is needed to position the ContinuationEntry at the unextended sp of the entry frame + frame::z_abi_160_base _abi; }; #endif // CPU_S390_CONTINUATIONENTRY_S390_HPP diff --git a/src/hotspot/cpu/s390/continuationEntry_s390.inline.hpp b/src/hotspot/cpu/s390/continuationEntry_s390.inline.hpp index 1d4e3c2439d..58ff8f0d194 100644 --- a/src/hotspot/cpu/s390/continuationEntry_s390.inline.hpp +++ b/src/hotspot/cpu/s390/continuationEntry_s390.inline.hpp @@ -1,5 +1,6 @@ /* - * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2026 IBM Corporation. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,22 +26,28 @@ #ifndef CPU_S390_CONTINUATIONENTRY_S390_INLINE_HPP #define CPU_S390_CONTINUATIONENTRY_S390_INLINE_HPP +#include "oops/method.inline.hpp" +#include "runtime/frame.inline.hpp" +#include "runtime/registerMap.hpp" +#include "utilities/macros.hpp" #include "runtime/continuationEntry.hpp" -// TODO: Implement - inline frame ContinuationEntry::to_frame() const { - Unimplemented(); - return frame(); + static CodeBlob* cb = CodeCache::find_blob_fast(entry_pc()); + assert(cb != nullptr, ""); + assert(cb->as_nmethod()->method()->is_continuation_enter_intrinsic(), ""); + return frame(entry_sp(), entry_pc(), entry_sp(), entry_fp(), cb); } inline intptr_t* ContinuationEntry::entry_fp() const { - Unimplemented(); - return nullptr; + return (intptr_t*)((address)this + size()); } inline void ContinuationEntry::update_register_map(RegisterMap* map) const { - Unimplemented(); + // No register map update needed for s390. + // In the Java calling convention on s390, all registers are volatile (caller-saved), + // so there are no non-volatile (callee-saved) registers that need to be tracked + // in the register map for continuation entry frames. } #endif // CPU_S390_CONTINUATIONENTRY_S390_INLINE_HPP diff --git a/src/hotspot/cpu/s390/continuationFreezeThaw_s390.inline.hpp b/src/hotspot/cpu/s390/continuationFreezeThaw_s390.inline.hpp index 1102a745ac0..2f7660052c0 100644 --- a/src/hotspot/cpu/s390/continuationFreezeThaw_s390.inline.hpp +++ b/src/hotspot/cpu/s390/continuationFreezeThaw_s390.inline.hpp @@ -1,5 +1,6 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2026 IBM Corporation. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,98 +30,316 @@ #include "runtime/frame.hpp" #include "runtime/frame.inline.hpp" +inline void patch_callee_link(const frame& f, intptr_t* fp) { + *ContinuationHelper::Frame::callee_link_address(f) = fp; +} + +inline void patch_callee_link_relative(const frame& f, intptr_t* fp) { + intptr_t* la = (intptr_t*)ContinuationHelper::Frame::callee_link_address(f); + intptr_t new_value = fp - la; + *la = new_value; +} + inline void FreezeBase::set_top_frame_metadata_pd(const frame& hf) { - Unimplemented(); + stackChunkOop chunk = _cont.tail(); + assert(chunk->is_in_chunk(hf.sp()), "hf.sp()=" PTR_FORMAT, p2i(hf.sp())); + + hf.own_abi()->return_pc = (uint64_t)hf.pc(); + if (hf.is_interpreted_frame()) { + patch_callee_link_relative(hf, hf.fp()); + } else { +#ifdef ASSERT + // See also FreezeBase::patch_pd() + patch_callee_link(hf, (intptr_t*)badAddress); +#endif + } } template inline frame FreezeBase::sender(const frame& f) { - Unimplemented(); - return frame(); + assert(FKind::is_instance(f), ""); + + if (FKind::interpreted) { + return frame(f.sender_sp(), f.sender_pc(), f.interpreter_frame_sender_sp()); + } + + intptr_t* sender_sp = f.sender_sp(); + address sender_pc = f.sender_pc(); + assert(sender_sp != f.sp(), "must have changed"); + int slot = 0; + CodeBlob* sender_cb = CodeCache::find_blob_and_oopmap(sender_pc, slot); + return sender_cb != nullptr + ? frame(sender_sp, sender_sp, nullptr, sender_pc, sender_cb, slot == -1 ? nullptr : sender_cb->oop_map_for_slot(slot, sender_pc)) + : frame(sender_sp, sender_pc, sender_sp); } template frame FreezeBase::new_heap_frame(frame& f, frame& caller) { - Unimplemented(); - return frame(); + assert(FKind::is_instance(f), ""); + intptr_t *sp, *fp; + if (FKind::interpreted) { + intptr_t locals_offset = *f.addr_at(_z_ijava_idx(locals)); + + // If the caller.is_empty(), i.e. we're freezing into an empty chunk, then we set + // the chunk's argsize in finalize_freeze and make room for it above the unextended_sp + // See also comment on StackChunkFrameStream::interpreter_frame_size() + + int overlap = + (caller.is_interpreted_frame() || caller.is_empty()) + ? ContinuationHelper::InterpretedFrame::stack_argsize(f) + frame::metadata_words_at_top + : 0; + + // Calculate the new frame's FP in the heap chunk. + // Starting from caller's unextended_sp, we: + // - subtract 1 for the z_parent_ijava_frame_abi (which sits just below the locals) + // - subtract locals_offset (distance from FP to locals in the original frame) + // - add overlap (to account for shared stack args when caller is interpreted or empty) + // This positions FP such that locals are correctly placed relative to the caller's frame. + fp = caller.unextended_sp() - 1 - locals_offset + overlap; + + // esp points one slot below the last argument + intptr_t* x86_64_like_unextended_sp = f.interpreter_frame_esp() + 1 - frame::metadata_words_at_top; + + sp = fp - (f.fp() - x86_64_like_unextended_sp); + assert (sp <= fp && (fp <= caller.unextended_sp() || caller.is_interpreted_frame()), + "sp=" PTR_FORMAT " fp=" PTR_FORMAT " caller.unextended_sp()=" PTR_FORMAT " caller.is_interpreted_frame()=%d", + p2i(sp), p2i(fp), p2i(caller.unextended_sp()), caller.is_interpreted_frame()); + caller.set_sp(fp); + + assert(_cont.tail()->is_in_chunk(sp), ""); + + frame hf(sp, sp, fp, f.pc(), nullptr, nullptr, true /* on_heap */); + // frame_top() and frame_bottom() read these before relativize_interpreted_frame_metadata() is called + *hf.addr_at(_z_ijava_idx(locals)) = locals_offset; + *hf.addr_at(_z_ijava_idx(esp)) = f.interpreter_frame_esp() - f.fp(); + return hf; + } else { + int fsize = FKind::size(f); + sp = caller.unextended_sp() - fsize; + if (caller.is_interpreted_frame()) { + // If the caller is interpreted, our stackargs are not supposed to overlap with it + // so we make more room by moving sp down by argsize + int argsize = FKind::stack_argsize(f); + sp -= argsize + frame::metadata_words_at_top; + } + fp = sp + fsize; + caller.set_sp(fp); + + assert(_cont.tail()->is_in_chunk(sp), ""); + + return frame(sp, sp, fp, f.pc(), nullptr, nullptr, true /* on_heap */); + } } void FreezeBase::adjust_interpreted_frame_unextended_sp(frame& f) { - Unimplemented(); + // Nothing to do on s390 and ppc. On x86/aarch64/riscv, the unextended_sp is stored + // in interpreter_frame_last_sp and needs to be restored from there. On s390/ppc, + // the frame structure doesn't have interpreter_frame_last_sp; instead, the unextended_sp + // is directly maintained in the frame and doesn't need adjustment. } inline void FreezeBase::prepare_freeze_interpreted_top_frame(frame& f) { - Unimplemented(); + // Nothing to do. We don't save a last sp because we cannot use sp as esp. + // Instead the top frame is trimmed when making an i2i call. The original + // top_frame_sp is set when the frame is pushed (see generate_fixed_frame()). + // An interpreter top frame that was just thawed is resized to top_frame_sp by the + // resume adapter (see generate_cont_resume_interpreter_adapter()). So the assertion is + // false, if we freeze again right after thawing as we do when redoing a vm call wasn't + // successful. + assert(_thread->interp_redoing_vm_call() || + ((intptr_t*)f.at_relative(_z_ijava_idx(top_frame_sp)) == f.unextended_sp()), + "top_frame_sp:" PTR_FORMAT " usp:" PTR_FORMAT, f.at_relative(_z_ijava_idx(top_frame_sp)), p2i(f.unextended_sp())); } inline void FreezeBase::relativize_interpreted_frame_metadata(const frame& f, const frame& hf) { - Unimplemented(); + intptr_t* vfp = f.fp(); + intptr_t* hfp = hf.fp(); + assert(f.fp() > (intptr_t*)f.interpreter_frame_esp(), ""); + + // There is alignment padding between vfp and f's locals array in the original + // frame, because we freeze the padding (see recurse_freeze_interpreted_frame) + // in order to keep the same relativized locals pointer, we don't need to change it here. + + // Make sure that monitors is already relativized. + assert(hf.at_absolute(_z_ijava_idx(monitors)) <= -(frame::z_ijava_state_size / wordSize), ""); + // Make sure that esp is already relativized. + assert(hf.at_absolute(_z_ijava_idx(esp)) <= hf.at_absolute(_z_ijava_idx(monitors)), ""); + // top_frame_sp is already relativized + + // hfp == hf.sp() + (f.fp() - f.sp()) is not true on ppc because the stack frame has room for + // the maximal expression stack and the expression stack in the heap frame is trimmed. + assert(hf.fp() == hf.interpreter_frame_esp() + (f.fp() - f.interpreter_frame_esp()), ""); + assert(hf.fp() <= (intptr_t*)hf.at(_z_ijava_idx(locals)), ""); } inline void FreezeBase::patch_pd(frame& hf, const frame& caller) { - Unimplemented(); + if (caller.is_interpreted_frame()) { + assert(!caller.is_empty(), ""); + patch_callee_link_relative(caller, caller.fp()); + } +#ifdef ASSERT + else { + // For compiled frames the back link is actually redundant. It gets computed + // as unextended_sp + frame_size. + + // Note a difference from x86_64: the link is not made relative if the caller + // is a compiled frame because there rbp is used as a non-volatile register by + // c1/c2 so it could be a computed value local to the caller. + + // See also: + // - FreezeBase::set_top_frame_metadata_pd + // - StackChunkFrameStream::fp() + // - UseContinuationFastPath: compiled frames are copied in a batch w/o patching the back link. + // The backlinks are restored when thawing (see Thaw::patch_caller_links()) + patch_callee_link(hf, (intptr_t*)badAddress); + } +#endif } inline void FreezeBase::patch_pd_unused(intptr_t* sp) { - Unimplemented(); } inline void FreezeBase::patch_stack_pd(intptr_t* frame_sp, intptr_t* heap_sp) { - Unimplemented(); + // Nothing to do. The backchain is reconstructed when thawing (see Thaw::patch_caller_links()) } inline intptr_t* AnchorMark::anchor_mark_set_pd() { - Unimplemented(); - return nullptr; + // Nothing to do on s390 because the interpreter does not use SP as expression stack pointer. + // Instead there is a dedicated register Z_esp which is not affected by VM calls. + return _top_frame.sp(); } inline void AnchorMark::anchor_mark_clear_pd() { - Unimplemented(); + // Nothing to do. See anchor_mark_set_pd(). } inline frame ThawBase::new_entry_frame() { - Unimplemented(); - return frame(); + intptr_t* sp = _cont.entrySP(); + return frame(sp, _cont.entryPC(), sp, _cont.entryFP()); } template frame ThawBase::new_stack_frame(const frame& hf, frame& caller, bool bottom) { - Unimplemented(); - return frame(); + assert(FKind::is_instance(hf), ""); + + assert(is_aligned(caller.fp(), frame::frame_alignment), PTR_FORMAT, p2i(caller.fp())); + // caller.sp() can be unaligned. This is fixed below. + if (FKind::interpreted) { + // Note: we have to overlap with the caller, at least if it is interpreted, to match the + // max_thawing_size calculation during freeze. See also comment above. + intptr_t* heap_sp = hf.unextended_sp(); + const int fsize = ContinuationHelper::InterpretedFrame::frame_bottom(hf) - hf.unextended_sp(); + const int overlap = !caller.is_interpreted_frame() ? 0 + : ContinuationHelper::InterpretedFrame::stack_argsize(hf) + frame::metadata_words_at_top; + intptr_t* frame_sp = caller.unextended_sp() + overlap - fsize; + intptr_t* fp = frame_sp + (hf.fp() - heap_sp); + // align fp + int padding = fp - align_down(fp, frame::frame_alignment); + fp -= padding; + // alignment of sp is done by callee or in finish_thaw() + frame_sp -= padding; + + // On s390 esp points to the first free slot on the expression stack (see frame_s390.hpp). + // The assertion verifies that frame_sp + metadata_words_at_top points to the slot above esp, + // which corresponds to the last parameter position. + DEBUG_ONLY(intptr_t* esp = fp + *hf.addr_at(_z_ijava_idx(esp));) + assert(frame_sp + frame::metadata_words_at_top == esp+1, " frame_sp=" PTR_FORMAT " esp=" PTR_FORMAT, p2i(frame_sp), p2i(esp)); + caller.set_sp(fp); + frame f(frame_sp, hf.pc(), frame_sp, fp); + // we need to set the locals so that the caller of new_stack_frame() can call + // ContinuationHelper::InterpretedFrame::frame_bottom + // copy relativized locals from the heap frame + *f.addr_at(_z_ijava_idx(locals)) = *hf.addr_at(_z_ijava_idx(locals)); + + return f; + } else { + int fsize = FKind::size(hf); + int argsize = FKind::stack_argsize(hf); + intptr_t* frame_sp = caller.sp() - fsize; + + if ((bottom && argsize > 0) || caller.is_interpreted_frame()) { + assert(!_should_patch_caller_pc, "what??"); + _should_patch_caller_pc = caller.is_interpreted_frame(); + frame_sp -= argsize + frame::metadata_words_at_top; + frame_sp = align_down(frame_sp, frame::alignment_in_bytes); + caller.set_sp(frame_sp + fsize); + } + + assert(hf.cb() != nullptr, ""); + assert(hf.oop_map() != nullptr, ""); + intptr_t* fp = frame_sp + fsize; + return frame(frame_sp, frame_sp, fp, hf.pc(), hf.cb(), hf.oop_map(), false); + } } inline void ThawBase::derelativize_interpreted_frame_metadata(const frame& hf, const frame& f) { - Unimplemented(); + // Make sure that monitors is still relativized. + assert(f.at_absolute(_z_ijava_idx(monitors)) <= -(frame::z_ijava_state_size / wordSize), ""); + // Make sure that esp is still relativized. + assert(f.at_absolute(_z_ijava_idx(esp)) <= f.at_absolute(_z_ijava_idx(monitors)), ""); + // Keep top_frame_sp relativized. } inline intptr_t* ThawBase::align(const frame& hf, intptr_t* frame_sp, frame& caller, bool bottom) { - Unimplemented(); + // Unused. Alignment is done directly in new_stack_frame() / finish_thaw(). return nullptr; } inline void ThawBase::patch_pd(frame& f, const frame& caller) { - Unimplemented(); + patch_callee_link(caller, caller.fp()); + // Prevent assertion if f gets deoptimized right away before it's fully initialized + f.mark_not_fully_initialized(); } inline void ThawBase::patch_pd(frame& f, intptr_t* caller_sp) { - Unimplemented(); + assert(f.own_abi()->callers_sp == (uint64_t)caller_sp, "should have been fixed by patch_caller_links"); } inline intptr_t* ThawBase::push_cleanup_continuation() { - Unimplemented(); - return nullptr; + frame enterSpecial = new_entry_frame(); + frame::z_common_abi* enterSpecial_abi = (frame::z_common_abi*)enterSpecial.sp(); + + enterSpecial_abi->return_pc = (intptr_t)ContinuationEntry::cleanup_pc(); + + log_develop_trace(continuations, preempt)("push_cleanup_continuation enterSpecial sp: " INTPTR_FORMAT " cleanup pc: " INTPTR_FORMAT, + p2i(enterSpecial_abi), + p2i(ContinuationEntry::cleanup_pc())); + + return enterSpecial.sp(); } inline intptr_t* ThawBase::push_preempt_adapter() { - Unimplemented(); - return nullptr; + frame enterSpecial = new_entry_frame(); + frame::z_common_abi* enterSpecial_abi = (frame::z_common_abi*)enterSpecial.sp(); + + enterSpecial_abi->return_pc = (intptr_t)StubRoutines::cont_preempt_stub(); + + log_develop_trace(continuations, preempt)("push_preempt_adapter enterSpecial sp: " INTPTR_FORMAT " adapter pc: " INTPTR_FORMAT, + p2i(enterSpecial_abi), + p2i(StubRoutines::cont_preempt_stub())); + + return enterSpecial.sp(); } template inline void Thaw::patch_caller_links(intptr_t* sp, intptr_t* bottom) { - Unimplemented(); + for (intptr_t* callers_sp; sp < bottom; sp = callers_sp) { + address pc = (address)((frame::z_java_abi*) sp)->return_pc; + assert(pc != nullptr, ""); + // see ThawBase::patch_return() which gets called just before + bool is_entry_frame = pc == StubRoutines::cont_returnBarrier() || pc == _cont.entryPC(); + if (is_entry_frame) { + callers_sp = _cont.entryFP(); + } else { + assert(!Interpreter::contains(pc), "sp:" PTR_FORMAT " pc:" PTR_FORMAT, p2i(sp), p2i(pc)); + CodeBlob* cb = CodeCache::find_blob(pc); + callers_sp = sp + cb->frame_size(); + } + // set the back link + ((frame::z_java_abi*) sp)->callers_sp = (intptr_t) callers_sp; + } } inline void ThawBase::prefetch_chunk_pd(void* start, int size) { - Unimplemented(); + // TODO: implement in future; } #endif // CPU_S390_CONTINUATION_S390_INLINE_HPP diff --git a/src/hotspot/cpu/s390/continuationHelper_s390.inline.hpp b/src/hotspot/cpu/s390/continuationHelper_s390.inline.hpp index fb7d998c458..11944a8f040 100644 --- a/src/hotspot/cpu/s390/continuationHelper_s390.inline.hpp +++ b/src/hotspot/cpu/s390/continuationHelper_s390.inline.hpp @@ -1,5 +1,6 @@ /* - * Copyright (c) 2022, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2026 IBM Corporation. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,108 +28,133 @@ #include "runtime/continuationHelper.hpp" -// TODO: Implement - -template -static inline intptr_t** link_address(const frame& f) { - Unimplemented(); - return nullptr; -} - static inline void patch_return_pc_with_preempt_stub(frame& f) { - Unimplemented(); + if (f.is_runtime_frame()) { + // Patch the pc of the now old last Java frame (we already set the anchor to enterSpecial) + // so that when target returns to Java it will actually return to the preempt cleanup stub. + // We step over the runtime stub frame and patch the return PC in the caller's frame. + intptr_t* caller_sp = f.sp() + f.cb()->frame_size(); + frame::z_common_abi* abi = (frame::z_common_abi*)caller_sp; + abi->return_pc = (uint64_t)StubRoutines::cont_preempt_stub(); + } else { + // The target will check for preemption once it returns to the interpreter + // or the native wrapper code and will manually jump to the preempt stub. + JavaThread *thread = JavaThread::current(); + DEBUG_ONLY(Method* m = f.is_interpreted_frame() ? f.interpreter_frame_method() : f.cb()->as_nmethod()->method();) + assert(m->is_object_wait0() || thread->interp_at_preemptable_vmcall_cnt() > 0, + "preemptable VM call not using call_VM_preemptable"); + thread->set_preempt_alternate_return(StubRoutines::cont_preempt_stub()); + } } inline int ContinuationHelper::frame_align_words(int size) { - Unimplemented(); + // S390 requires 8-byte (1-word) frame alignment, not 16-byte like other platforms. + // Because frames are already 8-byte aligned, no additional padding words are needed. + // Other platforms (x86, aarch64, ppc) return size & 1 to ensure 16-byte alignment, + // but s390's 8-byte alignment requirement is already satisfied. return 0; } -inline intptr_t* ContinuationHelper::frame_align_pointer(intptr_t* sp) { - Unimplemented(); - return nullptr; +inline intptr_t* ContinuationHelper::frame_align_pointer(intptr_t* p) { + return align_down(p, frame::frame_alignment); } template inline void ContinuationHelper::update_register_map(const frame& f, RegisterMap* map) { - Unimplemented(); + // All registers are considered volatile and saved in the caller (Java) frame if needed. + // No register map update required for s390. } inline void ContinuationHelper::update_register_map_with_callee(const frame& f, RegisterMap* map) { - Unimplemented(); + // All registers are considered volatile and saved in the caller (Java) frame if needed. + // No register map update required for s390. } inline void ContinuationHelper::push_pd(const frame& f) { - Unimplemented(); + f.own_abi()->callers_sp = (uint64_t)f.fp(); } inline void ContinuationHelper::set_anchor_to_entry_pd(JavaFrameAnchor* anchor, ContinuationEntry* cont) { - Unimplemented(); + // No frame pointer update needed for s390. + // Unlike x86/aarch64, s390 doesn't require setting last_Java_fp in the anchor. } inline void ContinuationHelper::set_anchor_pd(JavaFrameAnchor* anchor, intptr_t* sp) { - Unimplemented(); + // No frame pointer update needed for s390. + // Unlike x86/aarch64, s390 doesn't require setting last_Java_fp in the anchor. } #ifdef ASSERT inline bool ContinuationHelper::Frame::assert_frame_laid_out(frame f) { - Unimplemented(); - return false; + intptr_t* sp = f.sp(); + address pc = *(address*)(sp - frame::sender_sp_ret_address_offset()); + intptr_t* fp = (intptr_t*)f.own_abi()->callers_sp; + assert(f.raw_pc() == pc, "f.ra_pc: " INTPTR_FORMAT " actual: " INTPTR_FORMAT, p2i(f.raw_pc()), p2i(pc)); + assert(f.fp() == fp, "f.fp: " INTPTR_FORMAT " actual: " INTPTR_FORMAT, p2i(f.fp()), p2i(fp)); + return f.raw_pc() == pc && f.fp() == fp; } #endif inline intptr_t** ContinuationHelper::Frame::callee_link_address(const frame& f) { - Unimplemented(); - return nullptr; -} - -template -static inline intptr_t* real_fp(const frame& f) { - Unimplemented(); - return nullptr; + return (intptr_t**)&f.own_abi()->callers_sp; } inline address* ContinuationHelper::InterpretedFrame::return_pc_address(const frame& f) { - Unimplemented(); - return nullptr; + return (address*)&f.callers_abi()->return_pc; } inline void ContinuationHelper::InterpretedFrame::patch_sender_sp(frame& f, const frame& caller) { - Unimplemented(); + intptr_t* sp = caller.unextended_sp(); + if (!f.is_heap_frame() && caller.is_interpreted_frame()) { + // When the caller is an interpreted frame, we need to use the caller's top_frame_sp + // instead of unextended_sp. This is because the interpreter resizes the caller's + // frame before making a call + sp = (intptr_t*)caller.at_relative(_z_ijava_idx(top_frame_sp)); + } + assert(f.is_interpreted_frame(), ""); + assert(f.is_heap_frame() || is_aligned(sp, frame::alignment_in_bytes), ""); + intptr_t* la = f.addr_at(_z_ijava_idx(sender_sp)); + *la = f.is_heap_frame() ? (intptr_t)(sp - f.fp()) : (intptr_t)sp; } inline address* ContinuationHelper::Frame::return_pc_address(const frame& f) { - Unimplemented(); - return nullptr; + return (address*)&f.callers_abi()->return_pc; } inline address ContinuationHelper::Frame::real_pc(const frame& f) { - Unimplemented(); - return nullptr; + return (address)f.own_abi()->return_pc; } inline void ContinuationHelper::Frame::patch_pc(const frame& f, address pc) { - Unimplemented(); + f.own_abi()->return_pc = (uint64_t)pc; } inline intptr_t* ContinuationHelper::InterpretedFrame::frame_top(const frame& f, InterpreterOopMap* mask) { // inclusive; this will be copied with the frame - Unimplemented(); - return nullptr; + int expression_stack_sz = expression_stack_size(f, mask); + intptr_t* res = (intptr_t*)f.interpreter_frame_monitor_end() - expression_stack_sz; + assert(res <= (intptr_t*)f.ijava_state() - expression_stack_sz, + "res=" PTR_FORMAT " f.ijava_state()=" PTR_FORMAT " expression_stack_sz=%d", + p2i(res), p2i(f.ijava_state()), expression_stack_sz); + assert(res >= f.unextended_sp(), + "res: " INTPTR_FORMAT " ijava_state: " INTPTR_FORMAT " esp: " INTPTR_FORMAT " unextended_sp: " INTPTR_FORMAT " expression_stack_size: %d", + p2i(res), p2i(f.ijava_state()), f.ijava_state()->esp, p2i(f.unextended_sp()), expression_stack_sz); + return res; } inline intptr_t* ContinuationHelper::InterpretedFrame::frame_bottom(const frame& f) { // exclusive; this will not be copied with the frame - Unimplemented(); - return nullptr; + return (intptr_t*)f.at_relative(_z_ijava_idx(locals)) + 1; // exclusive; this will not be copied with the frame } inline intptr_t* ContinuationHelper::InterpretedFrame::frame_top(const frame& f, int callee_argsize, bool callee_interpreted) { - Unimplemented(); - return nullptr; + intptr_t* pseudo_unextended_sp = f.interpreter_frame_esp() + 1 - frame::metadata_words_at_top; + // callee_argsize includes metadata (frame::metadata_words_at_top). + // When the callee is interpreted, we add callee_argsize to account for the arguments + // that are part of the caller's frame but logically belong to the callee. + return pseudo_unextended_sp + (callee_interpreted ? callee_argsize : 0); } inline intptr_t* ContinuationHelper::InterpretedFrame::callers_sp(const frame& f) { - Unimplemented(); - return nullptr; + return f.fp(); } #endif // CPU_S390_CONTINUATIONHELPER_S390_INLINE_HPP diff --git a/src/hotspot/cpu/s390/frame_s390.cpp b/src/hotspot/cpu/s390/frame_s390.cpp index b602d0adce5..af4c670132a 100644 --- a/src/hotspot/cpu/s390/frame_s390.cpp +++ b/src/hotspot/cpu/s390/frame_s390.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016, 2023 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -54,6 +54,10 @@ void RegisterMap::check_location_valid() { // Profiling/safepoint support bool frame::safe_for_sender(JavaThread *thread) { + if (is_heap_frame()) { + return true; + } + address sp = (address)_sp; address fp = (address)_fp; address unextended_sp = (address)_unextended_sp; @@ -120,6 +124,13 @@ bool frame::safe_for_sender(JavaThread *thread) { intptr_t* sender_sp = (intptr_t*) fp; address sender_pc = (address) sender_abi->return_pc; + if (Continuation::is_return_barrier_entry(sender_pc)) { + // If our sender_pc is the return barrier, then our "real" sender is the continuation entry + frame s = Continuation::continuation_bottom_sender(thread, *this, sender_sp); + sender_sp = s.sp(); + sender_pc = s.pc(); + } + // We must always be able to find a recognizable pc. CodeBlob* sender_blob = CodeCache::find_blob(sender_pc); if (sender_blob == nullptr) { @@ -192,7 +203,8 @@ void frame::interpreter_frame_set_locals(intptr_t* locs) { // sender_sp intptr_t* frame::interpreter_frame_sender_sp() const { - return sender_sp(); + assert(is_interpreted_frame(), "interpreted frame expected"); + return (intptr_t*)at(_z_ijava_idx(sender_sp)); } frame frame::sender_for_entry_frame(RegisterMap *map) const { @@ -244,16 +256,52 @@ frame frame::sender_for_upcall_stub_frame(RegisterMap* map) const { frame fr(jfa->last_Java_sp(), jfa->last_Java_pc()); return fr; + } +#if defined(ASSERT) +static address get_register_address_in_stub(const frame& stub_fr, VMReg reg) { + RegisterMap map(nullptr, + RegisterMap::UpdateMap::include, + RegisterMap::ProcessFrames::skip, + RegisterMap::WalkContinuation::skip); + stub_fr.oop_map()->update_register_map(&stub_fr, &map); + return map.location(reg, stub_fr.sp()); +} +#endif + JavaThread** frame::saved_thread_address(const frame& f) { - Unimplemented(); - return nullptr; + CodeBlob* cb = f.cb(); + assert(cb != nullptr && cb->is_runtime_stub(), "invalid frame"); + + JavaThread** thread_addr; +#ifdef COMPILER1 + if (cb == Runtime1::blob_for(StubId::c1_monitorenter_id) || + cb == Runtime1::blob_for(StubId::c1_monitorenter_nofpu_id)) { + thread_addr = (JavaThread**)(f.sp() + Runtime1::runtime_blob_current_thread_offset(f)); + } else +#endif + { + // c2 only saves Z_fp in the stub frame so nothing to do. + thread_addr = nullptr; + } + assert(get_register_address_in_stub(f, SharedRuntime::thread_register()) == (address)thread_addr, "wrong thread address"); + return thread_addr; } frame frame::sender_for_interpreter_frame(RegisterMap *map) const { - // Pass callers sender_sp as unextended_sp. - return frame(sender_sp(), sender_pc(), (intptr_t*)(ijava_state()->sender_sp)); + // This is the sp before any possible extension (adapter/locals). + intptr_t* unextended_sp = interpreter_frame_sender_sp(); + address sender_pc = this->sender_pc(); + if (Continuation::is_return_barrier_entry(sender_pc)) { + if (map->walk_cont()) { // about to walk into an h-stack + return Continuation::top_frame(*this, map); + } else { + return Continuation::continuation_bottom_sender(map->thread(), *this, sender_sp()); + } + } + + return frame(sender_sp(), sender_pc, unextended_sp); } void frame::patch_pc(Thread* thread, address pc) { @@ -284,7 +332,7 @@ void frame::patch_pc(Thread* thread, address pc) { #ifdef ASSERT { - frame f(this->sp(), pc, this->unextended_sp()); + frame f(sp(), unextended_sp(), fp(), pc, cb(), oop_map(), is_heap_frame()); assert(f.is_deoptimized_frame() == this->is_deoptimized_frame() && f.pc() == this->pc() && f.raw_pc() == this->raw_pc(), "must be (f.is_deoptimized_frame(): %d this->is_deoptimized_frame(): %d " "f.pc(): " INTPTR_FORMAT " this->pc(): " INTPTR_FORMAT " f.raw_pc(): " INTPTR_FORMAT " this->raw_pc(): " INTPTR_FORMAT ")", @@ -648,6 +696,8 @@ extern "C" void bt_max(intptr_t *start_sp, intptr_t *top_pc, int max_frames) { } #if !defined(PRODUCT) +#define DESCRIBE_ADDRESS_MAGIC(name) \ + values.describe(frame_no, (intptr_t*)&ijava_state()->name, #name "_number_debug"); #define DESCRIBE_ADDRESS(name) \ values.describe(frame_no, (intptr_t*)&ijava_state()->name, #name); @@ -656,25 +706,38 @@ void frame::describe_pd(FrameValues& values, int frame_no) { if (is_interpreted_frame()) { // Describe z_ijava_state elements. DESCRIBE_ADDRESS(method); + DESCRIBE_ADDRESS(mirror); DESCRIBE_ADDRESS(locals); DESCRIBE_ADDRESS(monitors); DESCRIBE_ADDRESS(cpoolCache); DESCRIBE_ADDRESS(bcp); - DESCRIBE_ADDRESS(mdx); DESCRIBE_ADDRESS(esp); - DESCRIBE_ADDRESS(sender_sp); + DESCRIBE_ADDRESS(mdx); DESCRIBE_ADDRESS(top_frame_sp); + DESCRIBE_ADDRESS(sender_sp); DESCRIBE_ADDRESS(oop_tmp); DESCRIBE_ADDRESS(lresult); DESCRIBE_ADDRESS(fresult); + DESCRIBE_ADDRESS_MAGIC(magic); + } + + if (is_java_frame() || Continuation::is_continuation_enterSpecial(*this)) { + intptr_t* ret_pc_loc = (intptr_t*)&own_abi()->return_pc; + address ret_pc = *(address*)ret_pc_loc; + values.describe(frame_no, ret_pc_loc, + Continuation::is_return_barrier_entry(ret_pc) ? "return address (return barrier)" : "return address"); } } #endif // !PRODUCT intptr_t *frame::initial_deoptimization_info() { - // Used to reset the saved FP. - return fp(); + // `this` is the caller of the deoptee. We want to trim it, if compiled, to + // unextended_sp. This is necessary if the deoptee frame is the bottom frame + // of a continuation on stack (more frames could be in a StackChunk) as it + // will pop its stack args. Otherwise the recursion in + // FreezeBase::recurse_freeze_java_frame() would not stop at the bottom frame. + return is_compiled_frame() ? unextended_sp() : sp(); } BasicObjectLock* frame::interpreter_frame_monitor_end() const { diff --git a/src/hotspot/cpu/s390/frame_s390.hpp b/src/hotspot/cpu/s390/frame_s390.hpp index 664a49fdd21..36fc5970cd8 100644 --- a/src/hotspot/cpu/s390/frame_s390.hpp +++ b/src/hotspot/cpu/s390/frame_s390.hpp @@ -130,6 +130,7 @@ enum { z_native_abi_size = sizeof(z_native_abi), + z_abi_160_base_size = sizeof(z_abi_160_base), z_abi_160_size = sizeof(z_abi_160_base) }; @@ -442,6 +443,14 @@ private: + + #ifdef ASSERT + enum special_backlink_values : uint64_t { + NOT_FULLY_INITIALIZED = 0xDEADBEEF8 + }; + bool is_fully_initialized() const { return (uint64_t)_fp != NOT_FULLY_INITIALIZED; } +#endif // ASSERT + // STACK: // ... // [THIS_FRAME] <-- this._sp (stack pointer for this frame) @@ -452,10 +461,16 @@ // NOTE: Stack pointer is now held in the base class, so remove it from here. // Needed by deoptimization. - intptr_t* _unextended_sp; + union { + intptr_t* _unextended_sp; + int _offset_unextended_sp; // for use in stack-chunk frames + }; // Frame pointer for this frame. - intptr_t* _fp; + union { + intptr_t* _fp; // frame pointer + int _offset_fp; // relative frame pointer for use in stack-chunk frames + }; public: @@ -464,17 +479,25 @@ // Accessors inline intptr_t* fp() const { assert_absolute(); return _fp; } + void set_fp(intptr_t* newfp) { _fp = newfp; } + int offset_fp() const { assert_offset(); return _offset_fp; } + void set_offset_fp(int value) { assert_on_heap(); _offset_fp = value; } + + // Mark a frame as not fully initialized. Must not be used for frames in the valid back chain. + void mark_not_fully_initialized() const { DEBUG_ONLY(own_abi()->callers_sp = NOT_FULLY_INITIALIZED;) } private: // Initialize frame members (_pc and _sp must be given) inline void setup(); - // Constructors - public: + + // Constructors + inline frame(intptr_t* sp, intptr_t* fp, address pc); // To be used, if sp was not extended to match callee's calling convention. inline frame(intptr_t* sp, address pc, intptr_t* unextended_sp = nullptr, intptr_t* fp = nullptr, CodeBlob* cb = nullptr); + inline frame(intptr_t* sp, intptr_t* unextended_sp, intptr_t* fp, address pc, CodeBlob* cb, const ImmutableOopMap* oop_map, bool on_heap); inline frame(intptr_t* sp, intptr_t* unextended_sp, intptr_t* fp, address pc, CodeBlob* cb, const ImmutableOopMap* oop_map = nullptr); // Access frame via stack pointer. @@ -495,11 +518,8 @@ // template interpreter state inline z_ijava_state* ijava_state_unchecked() const; - private: - - inline z_ijava_state* ijava_state() const; - public: + inline z_ijava_state* ijava_state() const; inline intptr_t* interpreter_frame_esp() const; // Where z_ijava_state.esp is saved. @@ -542,14 +562,19 @@ unsigned long flags, int max_frames = 0); enum { - metadata_words = 0, + // size, in words, of frame metadata (e.g. pc and link) + metadata_words = sizeof(z_java_abi) >> LogBytesPerWord, metadata_words_at_bottom = 0, - metadata_words_at_top = 0, - frame_alignment = 16, + metadata_words_at_top = sizeof(z_java_abi) >> LogBytesPerWord, + // in bytes + frame_alignment = 8, // size, in words, of maximum shift in frame position due to alignment - align_wiggle = 1 + align_wiggle = 0 }; static jint interpreter_frame_expression_stack_direction() { return -1; } + // returns the sending frame, without applying any barriers + inline frame sender_raw(RegisterMap* map) const; + #endif // CPU_S390_FRAME_S390_HPP diff --git a/src/hotspot/cpu/s390/frame_s390.inline.hpp b/src/hotspot/cpu/s390/frame_s390.inline.hpp index 6fcd36c57d1..e31b0d5a426 100644 --- a/src/hotspot/cpu/s390/frame_s390.inline.hpp +++ b/src/hotspot/cpu/s390/frame_s390.inline.hpp @@ -26,7 +26,8 @@ #ifndef CPU_S390_FRAME_S390_INLINE_HPP #define CPU_S390_FRAME_S390_INLINE_HPP -#include "code/codeCache.hpp" +#include "code/codeBlob.inline.hpp" +#include "code/codeCache.inline.hpp" #include "code/vmreg.inline.hpp" #include "runtime/sharedRuntime.hpp" #include "utilities/align.hpp" @@ -44,14 +45,25 @@ inline void frame::setup() { _cb = CodeCache::find_blob(_pc); } - if (_fp == nullptr) { - _fp = (intptr_t*)own_abi()->callers_sp; - } - if (_unextended_sp == nullptr) { _unextended_sp = _sp; } + if (_fp == nullptr) { + // The back link for compiled frames on the heap is not valid + if (is_heap_frame()) { + // fp for interpreted frames should have been derelativized and passed to the constructor + assert(is_compiled_frame() + || is_native_frame() // native wrapper (nmethod) for j.l.Object::wait0 + || is_runtime_frame(), // e.g. Runtime1::monitorenter, SharedRuntime::complete_monitor_locking_C + "sp:" PTR_FORMAT " fp:" PTR_FORMAT " name:%s", p2i(_sp), p2i(_unextended_sp + _cb->frame_size()), _cb->name()); + // The back link for compiled frames on the heap is invalid. + _fp = _unextended_sp + _cb->frame_size(); + } else { + _fp = (intptr_t *) own_abi()->callers_sp; + } + } + // When thawing continuation frames the _unextended_sp passed to the constructor is not aligend assert(_on_heap || (is_aligned(_sp, alignment_in_bytes) && is_aligned(_fp, alignment_in_bytes)), "invalid alignment sp:" PTR_FORMAT " unextended_sp:" PTR_FORMAT " fp:" PTR_FORMAT, p2i(_sp), p2i(_unextended_sp), p2i(_fp)); @@ -70,7 +82,12 @@ inline void frame::setup() { } } - // assert(_on_heap || is_aligned(_sp, frame::frame_alignment), "SP must be 8-byte aligned"); + // Continuation frames on the java heap are not aligned. + // When thawing interpreted frames the sp can be unaligned (see new_stack_frame()). + assert(_on_heap || + ((is_aligned(_sp, alignment_in_bytes) || is_interpreted_frame()) && + (is_aligned(_fp, alignment_in_bytes) || !is_fully_initialized())), + "invalid alignment sp:" PTR_FORMAT " unextended_sp:" PTR_FORMAT " fp:" PTR_FORMAT, p2i(_sp), p2i(_unextended_sp), p2i(_fp)); } // Constructors @@ -87,11 +104,26 @@ inline frame::frame(intptr_t* sp, address pc, intptr_t* unextended_sp, intptr_t* inline frame::frame(intptr_t* sp) : frame(sp, nullptr) {} +inline frame::frame(intptr_t* sp, intptr_t* fp, address pc) + : _sp(sp), _pc(pc), _cb(nullptr), _oop_map(nullptr), _deopt_state(unknown), + _on_heap(false), DEBUG_ONLY(_frame_index(-1) COMMA) _unextended_sp(nullptr), _fp(fp) { + setup(); +} + inline frame::frame(intptr_t* sp, intptr_t* unextended_sp, intptr_t* fp, address pc, CodeBlob* cb, const ImmutableOopMap* oop_map) :_sp(sp), _pc(pc), _cb(cb), _oop_map(oop_map), _on_heap(false), DEBUG_ONLY(_frame_index(-1) COMMA) _unextended_sp(unextended_sp), _fp(fp) { setup(); } +inline frame::frame(intptr_t* sp, intptr_t* unextended_sp, intptr_t* fp, address pc, CodeBlob* cb, const ImmutableOopMap* oop_map, bool on_heap) + :_sp(sp), _pc(pc), _cb(cb), _oop_map(oop_map), _on_heap(on_heap), DEBUG_ONLY(_frame_index(-1) COMMA) _unextended_sp(unextended_sp), _fp(fp) { + // In thaw, non-heap frames use this constructor to pass oop_map. I don't know why. + assert(_on_heap || _cb != nullptr, "these frames are always heap frames"); + if (cb != nullptr) { + setup(); + } +} + // Generic constructor. Used by pns() in debug.cpp only #ifndef PRODUCT inline frame::frame(void* sp, void* pc, void* unextended_sp) @@ -295,11 +327,11 @@ inline JavaCallWrapper** frame::entry_frame_call_wrapper_addr() const { } inline oop frame::saved_oop_result(RegisterMap* map) const { - return *((oop*) map->location(Z_R2->as_VMReg(), nullptr)); // R2 is return register. + return *((oop*) map->location(Z_R2->as_VMReg(), sp())); // R2 is return register. } inline void frame::set_saved_oop_result(RegisterMap* map, oop obj) { - *((oop*) map->location(Z_R2->as_VMReg(), nullptr)) = obj; // R2 is return register. + *((oop*) map->location(Z_R2->as_VMReg(), sp())) = obj; // R2 is return register. } inline intptr_t* frame::real_fp() const { @@ -307,40 +339,55 @@ inline intptr_t* frame::real_fp() const { } inline int frame::compiled_frame_stack_argsize() const { - Unimplemented(); - return 0; + assert(cb()->is_nmethod(), "what ?"); + return (cb()->as_nmethod()->num_stack_arg_slots() * VMRegImpl::stack_slot_size) >> LogBytesPerWord; } inline void frame::interpreted_frame_oop_map(InterpreterOopMap* mask) const { - Unimplemented(); + assert(mask != nullptr, ""); + Method* m = interpreter_frame_method(); + int bci = interpreter_frame_bci(); + m->mask_for(bci, mask); // OopMapCache::compute_one_oop_map(m, bci, mask); } inline int frame::sender_sp_ret_address_offset() { - Unimplemented(); - return 0; + return -(int)(_z_common_abi(return_pc) >> LogBytesPerWord); } inline void frame::set_unextended_sp(intptr_t* value) { - Unimplemented(); + _unextended_sp = value; } inline int frame::offset_unextended_sp() const { - Unimplemented(); - return 0; + assert_offset(); return _offset_unextended_sp; } inline void frame::set_offset_unextended_sp(int value) { - Unimplemented(); + assert_on_heap(); _offset_unextended_sp = value; } //------------------------------------------------------------------------------ // frame::sender inline frame frame::sender(RegisterMap* map) const { + frame result = sender_raw(map); + + if (map->process_frames() && !map->in_cont()) { + StackWatermarkSet::on_iteration(map->thread(), result); + } + + return result; +} + +inline frame frame::sender_raw(RegisterMap* map) const { // Default is we don't have to follow them. The sender_for_xxx will // update it accordingly. map->set_include_argument_oops(false); + if (map->in_cont()) { // already in an h-stack + return map->stack_chunk()->sender(*this, map); + } + if (is_entry_frame()) return sender_for_entry_frame(map); if (is_upcall_stub_frame()) return sender_for_upcall_stub_frame(map); if (is_interpreted_frame()) return sender_for_interpreter_frame(map); @@ -362,12 +409,31 @@ inline frame frame::sender_for_compiled_frame(RegisterMap *map) const { // Now adjust the map. if (map->update_map()) { // Tell GC to use argument oopmaps for some runtime stubs that need it. - map->set_include_argument_oops(_cb->caller_must_gc_arguments(map->thread())); - if (_cb->oop_maps() != nullptr) { - OopMapSet::update_register_map(this, map); + + // For C1, some runtime stubs don't have oop maps (e.g., slow_subtype_check, + // unwind_exception), so set this flag outside of update_register_map to ensure + // the GC can handle arguments correctly even when oop_map() is null. + if (!_cb->is_nmethod()) { // compiled frames do not use callee-saved registers + map->set_include_argument_oops(_cb->caller_must_gc_arguments(map->thread())); + if (oop_map() != nullptr) { + _oop_map->update_register_map(this, map); + } + } else { + assert(!_cb->caller_must_gc_arguments(map->thread()), ""); + assert(!map->include_argument_oops(), ""); + assert(oop_map() == nullptr || !oop_map()->has_any(OopMapValue::callee_saved_value), "callee-saved value in compiled frame"); } } + assert(sender_sp != sp(), "must have changed"); + + if (Continuation::is_return_barrier_entry(sender_pc)) { + if (map->walk_cont()) { // about to walk into an h-stack + return Continuation::top_frame(*this, map); + } else { + return Continuation::continuation_bottom_sender(map->thread(), *this, sender_sp); + } + } return frame(sender_sp, sender_pc); } diff --git a/src/hotspot/cpu/s390/globals_s390.hpp b/src/hotspot/cpu/s390/globals_s390.hpp index 80ed6d1acc8..745a6171ca2 100644 --- a/src/hotspot/cpu/s390/globals_s390.hpp +++ b/src/hotspot/cpu/s390/globals_s390.hpp @@ -64,7 +64,7 @@ define_pd_global(intx, StackRedPages, DEFAULT_STACK_RED_PAGES); define_pd_global(intx, StackShadowPages, DEFAULT_STACK_SHADOW_PAGES); define_pd_global(intx, StackReservedPages, DEFAULT_STACK_RESERVED_PAGES); -define_pd_global(bool, VMContinuations, false); +define_pd_global(bool, VMContinuations, true); define_pd_global(bool, RewriteBytecodes, true); define_pd_global(bool, RewriteFrequentPairs, true); diff --git a/src/hotspot/cpu/s390/interp_masm_s390.cpp b/src/hotspot/cpu/s390/interp_masm_s390.cpp index d50cb833e68..5d86a0c3182 100644 --- a/src/hotspot/cpu/s390/interp_masm_s390.cpp +++ b/src/hotspot/cpu/s390/interp_masm_s390.cpp @@ -165,6 +165,109 @@ void InterpreterMacroAssembler::dispatch_via(TosState state, address *table) { // to perform additional, template interpreter specific tasks before actually // calling their MacroAssembler counterparts. +void InterpreterMacroAssembler::call_VM_preemptable(Register oop_result, address entry_point, + Register arg_1, bool check_exceptions) { + if (!Continuations::enabled()) { + call_VM(oop_result, entry_point, arg_1, check_exceptions); + return; + } + call_VM_preemptable(oop_result, entry_point, arg_1, noreg /* arg_2 */, check_exceptions); +} + +void InterpreterMacroAssembler::call_VM_preemptable(Register oop_result, address entry_point, + Register arg_1, Register arg_2, bool check_exceptions) { + if (!Continuations::enabled()) { + call_VM(oop_result, entry_point, arg_1, arg_2, check_exceptions); + return; + } + + Label resume_pc, not_preempted; + Register tmp = Z_R1_scratch; + assert(InterpreterRuntime::is_preemptable_call(entry_point), "VM call not preemptable, should use call_VM()"); + assert_different_registers(arg_1, tmp); + assert_different_registers(arg_2, tmp); + +#ifdef ASSERT + { + NearLabel L1; + asm_assert_mem8_is_zero(in_bytes(JavaThread::preempt_alternate_return_offset()), Z_thread, + "Should not have alternate return address set", 100); + // We check this counter in patch_return_pc_with_preempt_stub() during freeze. + z_asi(Address(Z_thread, JavaThread::interp_at_preemptable_vmcall_cnt_offset()), 1); + z_lt(tmp, Address(Z_thread, JavaThread::interp_at_preemptable_vmcall_cnt_offset())); + z_brh(L1); + stop("call_VM_preemptable_helper: should be > 0"); + bind(L1); + } +#endif // ASSERT + + lgr_if_needed(Z_ARG2, arg_1); + assert(arg_2 != Z_ARG2, "smashed argument"); + + if (arg_2 != noreg) { + lgr_if_needed(Z_ARG3, arg_2); + } + + // Force freeze slow path. + push_cont_fastpath(); + // Make VM call. In case of preemption set last_pc to the one we want to resume to. + // Note: call_VM_base will use resume_pc label to set last_Java_pc. + call_VM(noreg, entry_point, false /*check_exceptions*/, &resume_pc /* last_java_pc */); + pop_cont_fastpath(); + + +#ifdef ASSERT + { + NearLabel L; + z_asi(Address(Z_thread, JavaThread::interp_at_preemptable_vmcall_cnt_offset()), -1); + z_lt(tmp, Address(Z_thread, JavaThread::interp_at_preemptable_vmcall_cnt_offset())); + z_brnl(L); + stop("call_VM_preemptable_helper: should be >= 0"); + bind(L); + } +#endif // ASSERT + + // Check if preempted. + z_ltg(tmp, Address(Z_thread, JavaThread::preempt_alternate_return_offset())); + z_brz(not_preempted); + + // Preempted. Frames are already frozen on heap. + z_mvghi(Address(Z_thread, JavaThread::preempt_alternate_return_offset()), 0); + z_br(tmp); // branch to handler in Z_R1_scratch + + bind(resume_pc); // Location to resume execution + restore_after_resume(); + + bind(not_preempted); + + if (check_exceptions) { + NearLabel ok; + load_and_test_long(tmp, Address(Z_thread, Thread::pending_exception_offset())); + z_bre(ok); + load_const_optimized(tmp, StubRoutines::forward_exception_entry()); + z_br(tmp); + bind(ok); + } + + // get oop result if there is one and reset the value in the thread + if (oop_result->is_valid()) { + get_vm_result_oop(oop_result); + } +} + +void InterpreterMacroAssembler::restore_after_resume() { + if (!Continuations::enabled()) return; + load_const_optimized(Z_R1, Interpreter::cont_resume_interpreter_adapter()); + call(Z_R1); +#ifdef ASSERT + NearLabel ok; + z_cg(Z_fp, _z_common_abi(callers_sp), Z_SP); + z_bre(ok); + stop(FILE_AND_LINE ": FP is expected in Z_fp"); + bind(ok); +#endif // ASSERT +} + void InterpreterMacroAssembler::call_VM_leaf_base(address entry_point) { bool allow_relocation = true; // Fenerally valid variant. Assume code is relocated. // interpreter specific @@ -193,20 +296,20 @@ void InterpreterMacroAssembler::call_VM_base(Register oop_result, Register last_ save_esp(); // super call MacroAssembler::call_VM_base(oop_result, last_java_sp, - entry_point, allow_relocation, check_exceptions); + entry_point, allow_relocation, check_exceptions, nullptr); restore_bcp(); } void InterpreterMacroAssembler::call_VM_base(Register oop_result, Register last_java_sp, address entry_point, bool allow_relocation, - bool check_exceptions) { + bool check_exceptions, Label* last_java_pc) { // interpreter specific save_bcp(); save_esp(); // super call MacroAssembler::call_VM_base(oop_result, last_java_sp, - entry_point, allow_relocation, check_exceptions); + entry_point, allow_relocation, check_exceptions, last_java_pc); restore_bcp(); } @@ -697,7 +800,7 @@ void InterpreterMacroAssembler::get_monitors(Register reg) { bind(ok); #endif // ASSERT mem2reg_opt(reg, Address(Z_fp, _z_ijava_state_neg(monitors))); - z_slag(reg, reg, Interpreter::logStackElementSize); + z_slag(reg, reg, Interpreter::logStackElementSize); // sign preserved z_agr(reg, Z_fp); } @@ -968,6 +1071,14 @@ void InterpreterMacroAssembler::remove_activation(TosState state, bool install_monitor_exception, bool notify_jvmti) { BLOCK_COMMENT("remove_activation {"); + +#ifdef ASSERT + { + asm_assert_mem8_is_zero(in_bytes(JavaThread::preempt_alternate_return_offset()), Z_thread, + "remove_activation: should not have alternate return address set", 101); + } +#endif // ASSERT + unlock_if_synchronized_method(state, throw_monitor_exception, install_monitor_exception); // Save result (push state before jvmti call and pop it afterwards) and notify jvmti. @@ -1003,6 +1114,7 @@ void InterpreterMacroAssembler::remove_activation(TosState state, verify_oop(Z_tos, state); pop_interpreter_frame(return_pc, Z_ARG2, Z_ARG3); + pop_cont_fastpath(); BLOCK_COMMENT("} remove_activation"); } @@ -1023,9 +1135,9 @@ void InterpreterMacroAssembler::lock_object(Register monitor, Register object) { z_bru(done); bind(slow_case); - call_VM(noreg, - CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter), - monitor); + call_VM_preemptable(noreg, + CAST_FROM_FN_PTR(address, InterpreterRuntime::monitorenter), + monitor); bind(done); } diff --git a/src/hotspot/cpu/s390/interp_masm_s390.hpp b/src/hotspot/cpu/s390/interp_masm_s390.hpp index a210588d062..6921fd05ff0 100644 --- a/src/hotspot/cpu/s390/interp_masm_s390.hpp +++ b/src/hotspot/cpu/s390/interp_masm_s390.hpp @@ -46,7 +46,8 @@ class InterpreterMacroAssembler: public MacroAssembler { Register last_java_sp, address entry_point, bool allow_relocation, - bool check_exceptions); + bool check_exceptions, + Label *last_java_pc); // Base routine for all dispatches. void dispatch_base(TosState state, address* table, bool generate_poll = false); @@ -55,9 +56,14 @@ class InterpreterMacroAssembler: public MacroAssembler { InterpreterMacroAssembler(CodeBuffer* c) : MacroAssembler(c) {} + void restore_after_resume(); virtual void check_and_handle_popframe(Register java_thread); virtual void check_and_handle_earlyret(Register java_thread); + // Use for vthread preemption + void call_VM_preemptable(Register oop_result, address entry_point, Register arg_1, bool check_exceptions = true); + void call_VM_preemptable(Register oop_result, address entry_point, Register arg_1, Register arg_2, bool check_exceptions = true); + void jump_to_entry(address entry, Register Rscratch); virtual void load_earlyret_value(TosState state); diff --git a/src/hotspot/cpu/s390/macroAssembler_s390.cpp b/src/hotspot/cpu/s390/macroAssembler_s390.cpp index ea75d483e5f..5d5c7570e27 100644 --- a/src/hotspot/cpu/s390/macroAssembler_s390.cpp +++ b/src/hotspot/cpu/s390/macroAssembler_s390.cpp @@ -32,6 +32,7 @@ #include "gc/shared/barrierSetAssembler.hpp" #include "gc/shared/collectedHeap.inline.hpp" #include "interpreter/interpreter.hpp" +#include "interpreter/interpreterRuntime.hpp" #include "gc/shared/cardTableBarrierSet.hpp" #include "memory/resourceArea.hpp" #include "memory/universe.hpp" @@ -1932,6 +1933,12 @@ unsigned long MacroAssembler::patched_branch(address dest_pos, unsigned long ins // Only called when binding labels (share/vm/asm/assembler.cpp) // Pass arguments as intended. Do not pre-calculate distance. void MacroAssembler::pd_patch_instruction(address branch, address target, const char* file, int line) { + + if (is_load_const(branch)) { + patch_const(branch, (long)target); + return; + } + unsigned long stub_inst; int inst_len = get_instruction(branch, &stub_inst); @@ -2249,7 +2256,8 @@ void MacroAssembler::call_VM_base(Register oop_result, Register last_java_sp, address entry_point, bool allow_relocation, - bool check_exceptions) { // Defaults to true. + bool check_exceptions, // Defaults to true. + Label *last_java_pc) { // Allow_relocation indicates, if true, that the generated code shall // be fit for code relocation or referenced data relocation. In other // words: all addresses must be considered variable. PC-relative addressing @@ -2263,7 +2271,7 @@ void MacroAssembler::call_VM_base(Register oop_result, last_java_sp = Z_SP; // Load Z_SP as SP. } - set_top_ijava_frame_at_SP_as_last_Java_frame(last_java_sp, Z_R1, allow_relocation); + set_top_ijava_frame_at_SP_as_last_Java_frame(last_java_sp, Z_R1, allow_relocation, last_java_pc); // ARG1 must hold thread address. z_lgr(Z_ARG1, Z_thread); @@ -2309,14 +2317,14 @@ void MacroAssembler::call_VM_base(Register oop_result, address entry_point, bool check_exceptions) { // Defaults to true. bool allow_relocation = true; - call_VM_base(oop_result, last_java_sp, entry_point, allow_relocation, check_exceptions); + call_VM_base(oop_result, last_java_sp, entry_point, allow_relocation, check_exceptions, nullptr); } // VM calls without explicit last_java_sp. -void MacroAssembler::call_VM(Register oop_result, address entry_point, bool check_exceptions) { +void MacroAssembler::call_VM(Register oop_result, address entry_point, bool check_exceptions, Label* last_java_pc) { // Call takes possible detour via InterpreterMacroAssembler. - call_VM_base(oop_result, noreg, entry_point, true, check_exceptions); + call_VM_base(oop_result, noreg, entry_point, true, check_exceptions, last_java_pc); } void MacroAssembler::call_VM(Register oop_result, address entry_point, Register arg_1, bool check_exceptions) { @@ -2348,7 +2356,7 @@ void MacroAssembler::call_VM(Register oop_result, address entry_point, Register void MacroAssembler::call_VM_static(Register oop_result, address entry_point, bool check_exceptions) { // Call takes possible detour via InterpreterMacroAssembler. - call_VM_base(oop_result, noreg, entry_point, false, check_exceptions); + call_VM_base(oop_result, noreg, entry_point, false, check_exceptions, nullptr); } void MacroAssembler::call_VM_static(Register oop_result, address entry_point, Register arg_1, Register arg_2, @@ -2366,7 +2374,7 @@ void MacroAssembler::call_VM_static(Register oop_result, address entry_point, Re void MacroAssembler::call_VM(Register oop_result, Register last_java_sp, address entry_point, bool check_exceptions) { // Call takes possible detour via InterpreterMacroAssembler. - call_VM_base(oop_result, last_java_sp, entry_point, true, check_exceptions); + call_VM_base(oop_result, last_java_sp, entry_point, true, check_exceptions, nullptr); } void MacroAssembler::call_VM(Register oop_result, Register last_java_sp, address entry_point, Register arg_1, bool check_exceptions) { @@ -3810,19 +3818,21 @@ void MacroAssembler::set_last_Java_frame(Register last_Java_sp, Register last_Ja BLOCK_COMMENT("} set_last_Java_frame"); } -void MacroAssembler::reset_last_Java_frame(bool allow_relocation) { +void MacroAssembler::reset_last_Java_frame(bool check_last_java_sp, bool allow_relocation) { BLOCK_COMMENT("reset_last_Java_frame {"); - if (allow_relocation) { - asm_assert_mem8_isnot_zero(in_bytes(JavaThread::last_Java_sp_offset()), - Z_thread, - "SP was not set, still zero", - 0x202); - } else { - asm_assert_mem8_isnot_zero_static(in_bytes(JavaThread::last_Java_sp_offset()), - Z_thread, - "SP was not set, still zero", - 0x202); + if (check_last_java_sp) { + if (allow_relocation) { + asm_assert_mem8_isnot_zero(in_bytes(JavaThread::last_Java_sp_offset()), + Z_thread, + "SP was not set, still zero", + 0x202); + } else { + asm_assert_mem8_isnot_zero_static(in_bytes(JavaThread::last_Java_sp_offset()), + Z_thread, + "SP was not set, still zero", + 0x202); + } } // _last_Java_sp = 0 @@ -3836,15 +3846,14 @@ void MacroAssembler::reset_last_Java_frame(bool allow_relocation) { return; } -void MacroAssembler::set_top_ijava_frame_at_SP_as_last_Java_frame(Register sp, Register tmp1, bool allow_relocation) { +void MacroAssembler::set_top_ijava_frame_at_SP_as_last_Java_frame(Register sp, Register tmp1, bool allow_relocation, Label* jpc) { assert_different_registers(sp, tmp1); - // We cannot trust that code generated by the C++ compiler saves R14 - // to z_abi_160.return_pc, because sometimes it spills R14 using stmg at - // z_abi_160.gpr14 (e.g. InterpreterRuntime::_new()). - // Therefore we load the PC into tmp1 and let set_last_Java_frame() save - // it into the frame anchor. - get_PC(tmp1); + if (jpc == nullptr || jpc->is_bound()) { + load_const_optimized(tmp1, jpc == nullptr ? pc() : target(*jpc)); + } else { + load_const(tmp1, *jpc); + } set_last_Java_frame(/*sp=*/sp, /*pc=*/tmp1, allow_relocation); } @@ -5890,7 +5899,7 @@ bool is_excluded(Register excluded_register[], Register reg, int n) { } void MacroAssembler::clobber_volatile_registers(Register excluded_register[], int n) { - const int magic_number = 0x82; + const int magic_number = 0xbadbad; for (int i = 0; i < 6 /* R0 to R5 */; i++) { Register reg = as_Register(i); @@ -5899,6 +5908,26 @@ void MacroAssembler::clobber_volatile_registers(Register excluded_register[], in } } } + +void MacroAssembler::clobber_nonvolatile_registers() { + BLOCK_COMMENT("clobber_nonvolatile_registers {"); + static const Register regs[] = { + Z_R6, + Z_R7, + // don't zap Z_thread (Z_R8) + Z_R9, + Z_R10, + Z_R11, + Z_R12, + Z_R13 + }; + Register bad = regs[0]; + load_const_optimized(bad, 0xbad0101babe11111); + for (uint32_t i = 1; i < (sizeof(regs) / sizeof(Register)); i++) { + z_lgr(regs[i], bad); + } + BLOCK_COMMENT("} clobber_nonvolatile_registers"); +} #endif // ASSERT // Save and restore functions: Exclude Z_R0. @@ -6742,6 +6771,39 @@ void MacroAssembler::pop_count_int_with_ext3(Register r_dst, Register r_src) { BLOCK_COMMENT("} pop_count_int_with_ext3"); } +void MacroAssembler::post_call_nop() { + // Make inline again when loom is always enabled. + if (!Continuations::enabled()) { + return; + } + nop(); + // TODO: + // 1. https://bugs.openjdk.org/browse/JDK-8300002 + // 2. https://bugs.openjdk.org/browse/JDK-8290965 +} + +void MacroAssembler::push_cont_fastpath() { + BLOCK_COMMENT("push_cont_fastpath {"); + if (!Continuations::enabled()) return; + NearLabel done; + z_clg(Z_SP, Address(Z_thread, JavaThread::cont_fastpath_offset())); + z_brnh(done); // bcondNotHigh -> less than equal + z_stg(Z_SP, Address(Z_thread, JavaThread::cont_fastpath_offset())); + bind(done); + BLOCK_COMMENT("} push_cont_fastpath"); +} + +void MacroAssembler::pop_cont_fastpath() { + BLOCK_COMMENT("pop_cont_fastpath {"); + if (!Continuations::enabled()) return; + NearLabel done; + z_clg(Z_SP, Address(Z_thread, JavaThread::cont_fastpath_offset())); + z_brl(done); + z_mvghi(Address(Z_thread, JavaThread::cont_fastpath_offset()), 0); + bind(done); + BLOCK_COMMENT("} pop_cont_fastpath"); +} + // LOAD HALFWORD IMMEDIATE ON CONDITION (32 <- 16) void MacroAssembler::load_on_condition_imm_32(Register dst, int64_t i2, branch_condition cc) { if (VM_Version::has_LoadStoreConditional2()) { // z_lochi works on z13 or above diff --git a/src/hotspot/cpu/s390/macroAssembler_s390.hpp b/src/hotspot/cpu/s390/macroAssembler_s390.hpp index 8e2834ba9b7..9dd4054a36c 100644 --- a/src/hotspot/cpu/s390/macroAssembler_s390.hpp +++ b/src/hotspot/cpu/s390/macroAssembler_s390.hpp @@ -1,7 +1,7 @@ /* * Copyright (c) 2016, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016, 2024 SAP SE. All rights reserved. - * Copyright (c) 2024 IBM Corporation. All rights reserved. + * Copyright (c) 2024, 2026, IBM Corporation. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -525,12 +525,13 @@ class MacroAssembler: public Assembler { Register last_java_sp, // To set up last_Java_frame in stubs; use noreg otherwise. address entry_point, // The entry point. bool allow_relocation, // Flag to request generation of relocatable code. - bool check_exception); // Flag which indicates if exception should be checked. + bool check_exception, // Flag which indicates if exception should be checked. + Label *last_java_pc); // Call into the VM. // Passes the thread pointer (in Z_ARG1) as a prepended argument. // Makes sure oop return values are visible to the GC. - void call_VM(Register oop_result, address entry_point, bool check_exceptions = true); + void call_VM(Register oop_result, address entry_point, bool check_exceptions = true, Label* last_java_pc = nullptr); void call_VM(Register oop_result, address entry_point, Register arg_1, bool check_exceptions = true); void call_VM(Register oop_result, address entry_point, Register arg_1, Register arg_2, bool check_exceptions = true); void call_VM(Register oop_result, address entry_point, Register arg_1, Register arg_2, @@ -575,6 +576,8 @@ class MacroAssembler: public Assembler { // Get the pc where the last call will return to. Returns _last_calls_return_pc. inline address last_calls_return_pc(); + void post_call_nop(); + static int ic_check_size(); int ic_check(int end_alignment); @@ -805,14 +808,14 @@ class MacroAssembler: public Assembler { // Support for last Java frame (but use call_VM instead where possible). private: void set_last_Java_frame(Register last_Java_sp, Register last_Java_pc, bool allow_relocation); - void reset_last_Java_frame(bool allow_relocation); - void set_top_ijava_frame_at_SP_as_last_Java_frame(Register sp, Register tmp1, bool allow_relocation); + void reset_last_Java_frame(bool check_last_java_sp, bool allow_relocation); + void set_top_ijava_frame_at_SP_as_last_Java_frame(Register sp, Register tmp1, bool allow_relocation, Label* last_java_pc = nullptr); public: inline void set_last_Java_frame(Register last_java_sp, Register last_Java_pc); inline void set_last_Java_frame_static(Register last_java_sp, Register last_Java_pc); - inline void reset_last_Java_frame(void); - inline void reset_last_Java_frame_static(void); - inline void set_top_ijava_frame_at_SP_as_last_Java_frame(Register sp, Register tmp1); + inline void reset_last_Java_frame(bool check_last_java_sp = true); + inline void reset_last_Java_frame_static(bool check_last_java_sp = true); + inline void set_top_ijava_frame_at_SP_as_last_Java_frame(Register sp, Register tmp1, Label* jpc = nullptr); inline void set_top_ijava_frame_at_SP_as_last_Java_frame_static(Register sp, Register tmp1); void set_thread_state(JavaThreadState new_state); @@ -979,6 +982,10 @@ class MacroAssembler: public Assembler { } void asm_assert_frame_size(Register expected_size, Register tmp, const char* msg, int id); + // Load bad values into registers that are nonvolatile according to the ABI except Z_thread. + // This is done after vthread preemption and before vthread resume. + void clobber_nonvolatile_registers() NOT_DEBUG_RETURN; + // Save and restore functions: Exclude Z_R0. void save_volatile_regs( Register dst, int offset, bool include_fp, bool include_flags); void restore_volatile_regs(Register src, int offset, bool include_fp, bool include_flags); @@ -1109,6 +1116,9 @@ class MacroAssembler: public Assembler { void pop_count_int_with_ext3(Register dst, Register src); void pop_count_long_with_ext3(Register dst, Register src); + void push_cont_fastpath(); + void pop_cont_fastpath(); + void load_on_condition_imm_32(Register dst, int64_t i2, branch_condition cc); void load_on_condition_imm_64(Register dst, int64_t i2, branch_condition cc); diff --git a/src/hotspot/cpu/s390/macroAssembler_s390.inline.hpp b/src/hotspot/cpu/s390/macroAssembler_s390.inline.hpp index 72724fb66d1..24bec32f8b4 100644 --- a/src/hotspot/cpu/s390/macroAssembler_s390.inline.hpp +++ b/src/hotspot/cpu/s390/macroAssembler_s390.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016, 2023 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -295,16 +295,16 @@ inline void MacroAssembler::set_last_Java_frame_static(Register last_Java_sp, Re set_last_Java_frame(last_Java_sp, last_Java_pc, false); } -inline void MacroAssembler::reset_last_Java_frame(void) { - reset_last_Java_frame(true); +inline void MacroAssembler::reset_last_Java_frame(bool check_last_java_sp) { + reset_last_Java_frame(check_last_java_sp, true); } -inline void MacroAssembler::reset_last_Java_frame_static(void) { - reset_last_Java_frame(false); +inline void MacroAssembler::reset_last_Java_frame_static(bool check_last_java_sp) { + reset_last_Java_frame(check_last_java_sp, false); } -inline void MacroAssembler::set_top_ijava_frame_at_SP_as_last_Java_frame(Register sp, Register tmp1) { - set_top_ijava_frame_at_SP_as_last_Java_frame(sp, tmp1, true); +inline void MacroAssembler::set_top_ijava_frame_at_SP_as_last_Java_frame(Register sp, Register tmp1, Label *jpc) { + set_top_ijava_frame_at_SP_as_last_Java_frame(sp, tmp1, true, jpc); } inline void MacroAssembler::set_top_ijava_frame_at_SP_as_last_Java_frame_static(Register sp, Register tmp1) { diff --git a/src/hotspot/cpu/s390/nativeInst_s390.cpp b/src/hotspot/cpu/s390/nativeInst_s390.cpp index 546f8b13397..3520e9a3493 100644 --- a/src/hotspot/cpu/s390/nativeInst_s390.cpp +++ b/src/hotspot/cpu/s390/nativeInst_s390.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -630,3 +630,32 @@ void NativeGeneralJump::replace_mt_safe(address instr_addr, address code_buffer) *(intptr_t*)instr_addr = load_const_bytes | bytes_after_jump; ICache::invalidate_range(instr_addr, 6); } + +void NativeDeoptInstruction::verify() { +} + +void NativePostCallNop::make_deopt() { + NativeDeoptInstruction::insert(addr_at(0)); +} + +void NativeDeoptInstruction::insert(address code_pos) { + ResourceMark rm; + int code_size = 2; // z_illtrap is of 2 bytes + CodeBuffer cb(code_pos, code_size + 1); + MacroAssembler* a = new MacroAssembler(&cb); + a->z_illtrap(); + // forcing CPU to reload these 2 bytes of instruction by setting current range invalid + ICache::invalidate_range(code_pos, code_size); +} + +bool NativeDeoptInstruction::is_deopt_at(address instr){ + // Check if the instruction is an illtrap (illegal instruction used for deoptimization) + if (!Assembler::is_z_illtrap(instr)) return false; + + // Verify the instruction belongs to an nmethod + CodeBlob* cb = CodeCache::find_blob(instr); + if (cb == nullptr || !cb->is_nmethod()) { + return false; + } + return true; +} diff --git a/src/hotspot/cpu/s390/nativeInst_s390.hpp b/src/hotspot/cpu/s390/nativeInst_s390.hpp index 9852bc410b1..0ba97830bb7 100644 --- a/src/hotspot/cpu/s390/nativeInst_s390.hpp +++ b/src/hotspot/cpu/s390/nativeInst_s390.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016, 2024 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -82,6 +82,11 @@ class NativeInstruction { bool is_illegal(); + bool is_nop() const { + // TODO update: https://bugs.openjdk.org/browse/JDK-8290965 + return Assembler::is_z_nop(addr_at(0)); + } + // Bcrl is currently the only accepted instruction here. bool is_jump(); @@ -650,39 +655,40 @@ class NativeGeneralJump: public NativeInstruction { class NativePostCallNop: public NativeInstruction { public: enum z_specific_constants { - // Once the check is implemented, this has to specify number of bytes checked on the first - // read. If the check would read beyond size of the instruction at the deopt handler stub - // code entry point, then it has to happen in two stages - to prevent out of bounds access - // in case the return address points to the entry point which could be at the end of page. - first_check_size = 0 // check is unimplemented + // The check reads a 2-byte nop instruction. Since s390 nop is 2 bytes (BCR instruction), + // we can safely read it in a single stage without risk of out-of-bounds access. + // The nop instruction is checked by is_nop() which reads a short (2 bytes). + first_check_size = 2 }; - bool check() const { Unimplemented(); return false; } + bool check() const { return is_nop(); } bool decode(int32_t& oopmap_slot, int32_t& cb_offset) const { return false; } bool patch(int32_t oopmap_slot, int32_t cb_offset) { Unimplemented(); return false; } - void make_deopt() { Unimplemented(); } + void make_deopt(); }; inline NativePostCallNop* nativePostCallNop_at(address address) { - // Unimplemented(); + NativePostCallNop* nop = (NativePostCallNop*) address; + if (nop->check()) { + return nop; + } return nullptr; } class NativeDeoptInstruction: public NativeInstruction { public: - address instruction_address() const { Unimplemented(); return nullptr; } - address next_instruction_address() const { Unimplemented(); return nullptr; } + enum { + instruction_offset = 0 + }; - void verify() { Unimplemented(); } + address instruction_address() const { return addr_at(instruction_offset); } + address next_instruction_address() const { return instruction_address() + Assembler::instr_len(addr_at(0)); } - static bool is_deopt_at(address instr) { - // Unimplemented(); - return false; - } + void verify(); + + static bool is_deopt_at(address instr); // MT-safe patching - static void insert(address code_pos) { - Unimplemented(); - } + static void insert(address code_pos); }; #endif // CPU_S390_NATIVEINST_S390_HPP diff --git a/src/hotspot/cpu/s390/s390.ad b/src/hotspot/cpu/s390/s390.ad index c0e51bd2bfd..6cdf40cda9c 100644 --- a/src/hotspot/cpu/s390/s390.ad +++ b/src/hotspot/cpu/s390/s390.ad @@ -2361,6 +2361,7 @@ encode %{ unsigned int actual_ret_off = __ offset(); assert(start_off + size_of_code == actual_ret_off, "wrong return_pc"); #endif + __ post_call_nop(); %} enc_class z_enc_java_static_call(method meth) %{ @@ -2393,6 +2394,7 @@ encode %{ } __ clear_inst_mark(); + __ post_call_nop(); %} // Java dynamic call @@ -2449,6 +2451,7 @@ encode %{ __ z_basr(Z_R14, Z_R1_scratch); unsigned int ret_off = __ offset(); } + __ post_call_nop(); %} enc_class z_enc_cmov_reg(cmpOp cmp, iRegI dst, iRegI src) %{ @@ -5557,6 +5560,38 @@ instruct compareAndSwapN_bool(iRegP mem_ptr, rarg5RegN oldval, iRegN_P2N newval, ins_pipe(pipe_class_dummy); %} +instruct compareAndExchangeN(iRegN res, iRegP mem_ptr, rarg5RegN oldval, iRegN_P2N newval, flagsReg cr) %{ + match(Set res (CompareAndExchangeN mem_ptr (Binary oldval newval))); + predicate(n->as_LoadStore()->barrier_data() == 0); + effect(TEMP_DEF res, USE mem_ptr, USE_KILL oldval, KILL cr); + format %{ "$res = CompareAndExchangeN $oldval,$newval,$mem_ptr" %} + ins_encode %{ + Register Rcomp = reg_to_register_object($oldval$$reg); + Register Rnew = reg_to_register_object($newval$$reg); + Register Raddr = reg_to_register_object($mem_ptr$$reg); + Register Rres = reg_to_register_object($res$$reg); + __ z_lr(Rres, Rcomp); + __ z_cs(Rres, Rnew, 0, Raddr); + %} + ins_pipe(pipe_class_dummy); +%} + +instruct compareAndExchangeP(iRegP res, iRegP mem_ptr, rarg5RegP oldval, iRegP_N2P newval, flagsReg cr) %{ + match(Set res (CompareAndExchangeP mem_ptr (Binary oldval newval))); + predicate(n->as_LoadStore()->barrier_data() == 0); + effect(TEMP_DEF res, USE mem_ptr, USE_KILL oldval, KILL cr); + format %{ "$res = CompareAndExchangeP $oldval,$newval,$mem_ptr" %} + ins_encode %{ + Register Rcomp = reg_to_register_object($oldval$$reg); + Register Rnew = reg_to_register_object($newval$$reg); + Register Raddr = reg_to_register_object($mem_ptr$$reg); + Register Rres = reg_to_register_object($res$$reg); + __ z_lgr(Rres, Rcomp); + __ z_csg(Rres, Rnew, 0, Raddr); + %} + ins_pipe(pipe_class_dummy); +%} + //----------Atomic operations on memory (GetAndSet*, GetAndAdd*)--------------- // Exploit: direct memory arithmetic diff --git a/src/hotspot/cpu/s390/sharedRuntime_s390.cpp b/src/hotspot/cpu/s390/sharedRuntime_s390.cpp index e5a27e66968..1a13e76e930 100644 --- a/src/hotspot/cpu/s390/sharedRuntime_s390.cpp +++ b/src/hotspot/cpu/s390/sharedRuntime_s390.cpp @@ -1,6 +1,7 @@ /* * Copyright (c) 2016, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016, 2024 SAP SE. All rights reserved. + * Copyright (c) 2026 IBM Corporation. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -37,6 +38,8 @@ #include "oops/klass.inline.hpp" #include "prims/methodHandles.hpp" #include "registerSaver_s390.hpp" +#include "runtime/continuation.hpp" +#include "runtime/continuationEntry.inline.hpp" #include "runtime/jniHandles.hpp" #include "runtime/safepointMechanism.hpp" #include "runtime/sharedRuntime.hpp" @@ -1339,6 +1342,395 @@ static void move32_64(MacroAssembler *masm, // Wrap a JNI call. //---------------------------------------------------------------------- #undef USE_RESIZE_FRAME + +static void check_continuation_enter_argument(VMReg actual_vmreg, + Register expected_reg, + const char* name) { + assert(!actual_vmreg->is_stack(), "%s cannot be on stack", name); + assert(actual_vmreg->as_Register() == expected_reg, + "%s is in unexpected register: %s instead of %s", + name, actual_vmreg->as_Register()->name(), expected_reg->name()); +} + +//---------------------------- continuation_enter_setup --------------------------- +// +// Frame setup. +// +// Arguments: +// None. +// +// Results: +// Z_SP: pointer to blank ContinuationEntry in the pushed frame. +// +// Kills: +// Nothing +// +static OopMap* continuation_enter_setup(MacroAssembler* masm, int& framesize_words) { + + assert(ContinuationEntry::size() % VMRegImpl::stack_slot_size == 0, ""); + assert(in_bytes(ContinuationEntry::cont_offset()) % VMRegImpl::stack_slot_size == 0, ""); + assert(in_bytes(ContinuationEntry::chunk_offset()) % VMRegImpl::stack_slot_size == 0, ""); + + const int frame_size_in_bytes = (int)ContinuationEntry::size(); + assert(is_aligned(frame_size_in_bytes, frame::alignment_in_bytes), "alignment error"); + + framesize_words = frame_size_in_bytes / wordSize; + + DEBUG_ONLY(__ block_comment("continuation_enter_setup {")); + __ save_return_pc(); // preserve current Z_R14 + __ push_frame(frame_size_in_bytes); + + OopMap* map = new OopMap((int)frame_size_in_bytes / VMRegImpl::stack_slot_size, 0 /* arg_slots*/); + __ z_mvc(Address(Z_SP, ContinuationEntry::parent_offset()), /* move to */ + Address(Z_thread, JavaThread::cont_entry_offset()), /* move from */ + sizeof(ContinuationEntry*) /* size of data to be moved */ + ); + __ z_stg(Z_SP, Address(Z_thread, JavaThread::cont_entry_offset())); + DEBUG_ONLY(__ block_comment("} continuation_enter_setup")); + return map; +} + +//---------------------------- fill_continuation_entry --------------------------- +// +// Initialize the new ContinuationEntry. +// +// Arguments: +// Z_SP : pointer to blank Continuation entry +// reg_cont_obj : pointer to the continuation +// reg_flags : flags / isVirtualThread +// +// Results: +// Z_SP : pointer to filled out ContinuationEntry +// +// Kills: +// This is peace driven method, doesn't kill anyone. +// +static void fill_continuation_entry(MacroAssembler* masm, Register reg_cont_obj, Register reg_flags) { + assert_different_registers(reg_cont_obj, reg_flags); + DEBUG_ONLY(__ block_comment("fill_continuation_entry {")); +#ifdef ASSERT + assert(Immediate::is_simm16(ContinuationEntry::cookie_value()), "update below instruction"); + __ z_mvhi(Address(Z_SP, ContinuationEntry::cookie_offset()), ContinuationEntry::cookie_value()); +#endif //ASSERT + __ z_stg(reg_cont_obj, Address(Z_SP, ContinuationEntry::cont_offset())); + __ z_st(reg_flags, Address(Z_SP, ContinuationEntry::flags_offset())); + __ z_mvghi(Address(Z_SP, ContinuationEntry::chunk_offset()), 0); + __ z_mvhi( Address(Z_SP, ContinuationEntry::argsize_offset()), 0); + __ z_mvhi( Address(Z_SP, ContinuationEntry::pin_count_offset()), 0); + + __ z_mvc(Address(Z_SP, ContinuationEntry::parent_cont_fastpath_offset()), /* move to */ + Address(Z_thread, JavaThread::cont_fastpath_offset()), /* move from */ + sizeof(ContinuationEntry*) /* size of data to be moved */ + ); + + __ z_mvghi(Address(Z_thread, JavaThread::cont_fastpath_offset()), 0); + + DEBUG_ONLY(__ block_comment("} fill_continuation_entry")); +} + +//---------------------------- continuation_enter_cleanup --------------------------- +// +// Copy corresponding attributes from the top ContinuationEntry to the JavaThread +// before deleting it. +// +// Arguments: +// Z_SP: pointer to the ContinuationEntry +// +// Results: +// None. +// +// Kills: +// Z_R0_scratch (in debug builds) +// Z_R10 (when CheckJNICalls is enabled) +// +static void continuation_enter_cleanup(MacroAssembler* masm) { + __ block_comment("continuation_enter_cleanup {"); + +#ifdef ASSERT + __ z_cg(Z_SP, Address(Z_thread, JavaThread::cont_entry_offset())); + __ asm_assert(Assembler::bcondEqual, FILE_AND_LINE ": incorrect Z_SP", 0x1bb); + + __ z_lgf(Z_R0, Address(Z_SP, ContinuationEntry::cookie_offset())); + __ z_cfi(Z_R0, ContinuationEntry::cookie_value()); + __ asm_assert(Assembler::bcondEqual, FILE_AND_LINE ": incorrect cookie value", 0x1cc); +#endif // ASSERT + + __ z_mvc(Address(Z_thread, JavaThread::cont_fastpath_offset()), /* move to */ + Address(Z_SP, ContinuationEntry::parent_cont_fastpath_offset()), /* move from */ + sizeof(ContinuationEntry*) /* size of data to be moved */ + ); + + __ z_mvc(Address(Z_thread, JavaThread::cont_entry_offset()), /* move to */ + Address(Z_SP, ContinuationEntry::parent_offset()), /* move from */ + sizeof(ContinuationEntry*) /* size of data to be moved */ + ); + + __ block_comment("} continuation_enter_cleanup"); +} +static void gen_continuation_enter(MacroAssembler* masm, + const VMRegPair* regs, + int& exception_offset, + OopMapSet* oop_maps, + int& frame_complete, + int& framesize_words, + int& interpreted_entry_offset, + int& compiled_entry_offset) { + // enterSpecial(Continuation c, boolean isContinue, boolean isVirtualThread) + int pos_cont_obj = 0; + int pos_is_cont = 1; + int pos_is_virtual = 2; + + // The platform-specific calling convention may present the arguments in various registers. + // To simplify the rest of the code, we expect the arguments to reside at these known + // registers, and we additionally check the placement here in case calling convention ever + // changes. + Register reg_cont_obj = Z_ARG1; + Register reg_is_cont = Z_ARG2; + Register reg_is_virtual = Z_ARG3; + + check_continuation_enter_argument(regs[pos_cont_obj].first(), reg_cont_obj, "Continuation object"); + check_continuation_enter_argument(regs[pos_is_cont].first(), reg_is_cont, "isContinue"); + check_continuation_enter_argument(regs[pos_is_virtual].first(), reg_is_virtual, "isVirtualThread"); + + address resolve_static_call = SharedRuntime::get_resolve_static_call_stub(); + + address start = __ pc(); + + Label L_thaw, L_exit; + + // i2i entry used at interp_only_mode only + interpreted_entry_offset = __ pc() - start; + { +#ifdef ASSERT + NearLabel is_interp_only; + __ load_and_test_int(Z_R0_scratch, Address(Z_thread, JavaThread::interp_only_mode_offset())); + __ z_brnz(is_interp_only); + __ stop("enterSpecial interpreter entry called when not in interp_only_mode"); + __ bind(is_interp_only); +#endif + + // Read interpreter arguments into registers (this is an ad-hoc i2c adapter) + // s390x stores frame pointer in the slot 0, so argument will be loaded from slot 1 + __ z_lg(reg_cont_obj, Address(Z_esp, Interpreter::stackElementSize*3)); + __ z_llgf(reg_is_cont, Address(Z_esp, Interpreter::stackElementSize*2)); + __ z_llgf(reg_is_virtual, Address(Z_esp, Interpreter::stackElementSize*1)); + + __ push_cont_fastpath(); + + OopMap* map = continuation_enter_setup(masm, framesize_words); + + // The frame is complete here, but we only record it for the compiled entry, so the frame would appear unsafe, + // but that's okay because at the very worst we'll miss an async sample, but we're in interp_only_mode anyway. + + __ verify_oop(reg_cont_obj); + + fill_continuation_entry(masm, reg_cont_obj, reg_is_virtual); + + // If isContinue, call to thaw. Otherwise, call Continuation.enter(Continuation c, boolean isContinue) + __ compare32_and_branch(reg_is_cont, 0, Assembler::bcondNotZero, L_thaw); + + // --- call Continuation.enter(Continuation c, boolean isContinue) + + // Emit compiled static call. The call will be always resolved to the c2i + // entry of Continuation.enter(Continuation c, boolean isContinue). + // There are special cases in SharedRuntime::resolve_static_call_C() and + // SharedRuntime::resolve_sub_helper_internal() to achieve this + // See also corresponding call below. + // Make sure the call is patchable + + __ align(NativeCall::call_far_pcrelative_displacement_alignment, + __ offset() + NativeCall::call_far_pcrelative_displacement_offset); + + // Emit stub for static call + address stub = CompiledDirectCall::emit_to_interp_stub(masm, __ pc()); + if (stub == nullptr) { + fatal("CodeCache is full at gen_continuation_enter"); + } + __ relocate(relocInfo::static_call_type); + __ z_nop(); + __ z_brasl(Z_R14, resolve_static_call); + oop_maps->add_gc_map(__ pc() - start, map); + __ post_call_nop(); + __ branch_optimized(Assembler::bcondAlways, L_exit); + } + + // compiled entry + __ align(CodeEntryAlignment); + compiled_entry_offset = __ pc() - start; + + OopMap* map = continuation_enter_setup(masm, framesize_words); + + // Frame is now completed as far as size and linkage. + + frame_complete =__ pc() - start; + + __ verify_oop(reg_cont_obj); + + fill_continuation_entry(masm, reg_cont_obj, reg_is_virtual); + + // If isContinue, call to thaw. Otherwise, call Continuation.enter(Continuation c, boolean isContinue) + __ z_ltr(reg_is_cont, reg_is_cont); + __ branch_optimized(Assembler::bcondNotEqual, L_thaw); // was reg_is_cont equal to 0 ? + + // --- call Continuation.enter(Continuation c, boolean isContinue) + + // Make sure the call is patchable + __ align(NativeCall::call_far_pcrelative_displacement_alignment, + __ offset() + NativeCall::call_far_pcrelative_displacement_offset); + + // Emit stub for static call + address stub = CompiledDirectCall::emit_to_interp_stub(masm, __ pc()); + guarantee(stub != nullptr, "CodeCache is full at gen_continuation_enter"); + + assert((__ offset() + NativeCall::call_far_pcrelative_displacement_offset) % NativeCall::call_far_pcrelative_displacement_alignment == 0, + "must be aligned (offset=%d)", __ offset()); + + // The call needs to be resolved. There's a special case for this in + // SharedRuntime::find_callee_info_helper() which calls + // LinkResolver::resolve_continuation_enter() which resolves the call to + // Continuation.enter(Continuation c, boolean isContinue). + __ relocate(relocInfo::static_call_type); + __ z_nop(); + __ z_brasl(Z_R14, resolve_static_call); + oop_maps->add_gc_map(__ pc() - start, map); + __ post_call_nop(); + + __ branch_optimized(Assembler::bcondAlways, L_exit); + + // --- Thawing path + + __ bind(L_thaw); + ContinuationEntry::_thaw_call_pc_offset = __ pc() - start; + __ load_const_optimized(Z_R1_scratch, StubRoutines::cont_thaw()); + __ call(Z_R1_scratch); + oop_maps->add_gc_map(__ pc() - start, map->deep_copy()); + ContinuationEntry::_return_pc_offset = __ pc() - start; + __ post_call_nop(); + + // --- Normal exit (resolve/thawing) + __ bind(L_exit); + ContinuationEntry::_cleanup_offset = __ pc() - start; + continuation_enter_cleanup(masm); + + // Pop frame and return + DEBUG_ONLY(__ z_lg(Z_R0, Address(Z_SP, 0))); + __ add2reg(Z_SP, framesize_words * wordSize); + +#ifdef ASSERT + NearLabel ok; + __ z_cgr(Z_R0, Z_SP); + __ z_bre(ok); + __ stop("inconsistent frame size"); + __ bind(ok); +#endif // ASSERT + + __ restore_return_pc(); + __ z_br(Z_R14); + + // --- Exception handling path + exception_offset = __ pc() - start; + + continuation_enter_cleanup(masm); + + // Load caller's return pc + __ z_lg(Z_ARG2, _z_common_abi(callers_sp), Z_SP); + __ z_lg(Z_ARG2, _z_common_abi(return_pc), Z_ARG2); + + __ save_return_pc(); + __ push_frame_abi160(0 + 2 * BytesPerWord); + + __ z_stg(Z_ARG1, 0 * BytesPerWord + frame::z_abi_160_size, Z_SP); // save return value containing the exception oop + __ z_stg(Z_ARG2, 1 * BytesPerWord + frame::z_abi_160_size, Z_SP); // save exception_pc + + // Find exception handler. + __ call_VM_leaf(CAST_FROM_FN_PTR(address, SharedRuntime::exception_handler_for_return_address), + Z_thread, + Z_ARG2); + + // Copy handler's address. + __ z_lgr(Z_R1, Z_RET); + + // Set up the arguments for the exception handler: + // - Z_ARG1: exception oop + // - Z_ARG2: exception pc + __ z_lg(Z_ARG1, 0 * BytesPerWord + frame::z_abi_160_size, Z_SP); // load the exception oop + __ z_lg(Z_ARG2, 1 * BytesPerWord + frame::z_abi_160_size, Z_SP); // load the exception pc + + __ pop_frame(); // pop frame pushed before runtime call + // __ restore_return_pc(); // can be skipped + + __ pop_frame(); // pop enterSpecial frame + __ restore_return_pc(); + + // Jump to exception handler + __ z_br(Z_R1 /*handler address*/); +} + +static void gen_continuation_yield(MacroAssembler* masm, + const VMRegPair* regs, + OopMapSet* oop_maps, + int& frame_complete, + int& framesize_words, + int& compiled_entry_offset) { + const int framesize_bytes = (int)align_up((int)frame::z_abi_160_size, frame::alignment_in_bytes); + framesize_words = framesize_bytes / wordSize; + + Register Rtmp = Z_R1_scratch; + + address start = __ pc(); + compiled_entry_offset = __ pc() - start; + + // Save return pc and push entry frame + __ save_return_pc(); + __ push_frame(framesize_bytes); + + DEBUG_ONLY(__ block_comment("Frame Complete (gen_continuation_yield):")); + frame_complete = __ pc() - start; + address last_java_pc = __ pc(); + + + // This nop must be exactly at the PC we push into the frame info. + // We use this nop for fast CodeBlob lookup, associate the OopMap + // with it right away. + __ post_call_nop(); + OopMap* map = new OopMap(framesize_bytes / VMRegImpl::stack_slot_size, 1); + oop_maps->add_gc_map(last_java_pc - start, map); + + __ z_larl(Rtmp, last_java_pc); + __ set_last_Java_frame(Z_SP, Rtmp); + __ call_VM_leaf(Continuation::freeze_entry(), Z_thread, Z_SP); + __ reset_last_Java_frame(); + + NearLabel L_pinned; + __ z_cij(Z_RET, 0, Assembler::bcondNotEqual, L_pinned); + + // Pop frames of continuation including this stub's frame + __ z_lg(Z_SP, Address(Z_thread, JavaThread::cont_entry_offset())); + // The frame pushed by gen_continuation_enter() is on top now again + continuation_enter_cleanup(masm); + // Pop frame and return + Label L_return; + __ bind(L_return); + __ pop_frame(); + __ restore_return_pc(); + __ z_br(Z_R14); + + // yield failed - continuation is pinned + __ bind(L_pinned); + + // handle pending exception thrown by freeze + __ load_and_test_long(Rtmp, Address(Z_thread, Thread::pending_exception_offset())); + __ z_bre(L_return); // return if no exception is pending + __ pop_frame(); + __ restore_return_pc(); + __ load_const_optimized(Z_R1_scratch, StubRoutines::forward_exception_entry()); + __ z_br(Z_R1_scratch); +} + +void SharedRuntime::continuation_enter_cleanup(MacroAssembler* masm) { + ::continuation_enter_cleanup(masm); +} + nmethod *SharedRuntime::generate_native_wrapper(MacroAssembler *masm, const methodHandle& method, int compile_id, @@ -1346,6 +1738,66 @@ nmethod *SharedRuntime::generate_native_wrapper(MacroAssembler *masm, VMRegPair *in_regs, BasicType ret_type) { int total_in_args = method->size_of_parameters(); + if (method->is_continuation_native_intrinsic()) { + int exception_offset = -1; + OopMapSet* oop_maps = new OopMapSet(); + int frame_complete = -1; + int stack_slots = -1; + int interpreted_entry_offset = -1; + int vep_offset = -1; // verified entry point offset + if (method->is_continuation_enter_intrinsic()) { + gen_continuation_enter(masm, + in_regs, + exception_offset, + oop_maps, + frame_complete, + stack_slots, + interpreted_entry_offset, + vep_offset); + } else if(method->is_continuation_yield_intrinsic()) { + gen_continuation_yield(masm, + in_regs, + oop_maps, + frame_complete, + stack_slots, + vep_offset); + } else { + guarantee(false, "Unknown Continuation native intrinsic"); + } + +#ifdef ASSERT + if (method->is_continuation_enter_intrinsic()) { + assert(interpreted_entry_offset != -1, "Must be set"); + assert(exception_offset != -1, "Must be set"); + } else { + assert(interpreted_entry_offset == -1, "Must be unset"); + assert(exception_offset == -1, "Must be unset"); + } + assert(frame_complete != -1, "Must be set"); + assert(stack_slots != -1, "Must be set"); + assert(vep_offset != -1, "Must be set"); +#endif + + __ flush(); + nmethod* nm = nmethod::new_native_nmethod(method, + compile_id, + masm->code(), + vep_offset, + frame_complete, + stack_slots, + in_ByteSize(-1), + in_ByteSize(-1), + oop_maps, + exception_offset); + if (nm == nullptr) return nm; + if (method->is_continuation_enter_intrinsic()) { + ContinuationEntry::set_enter_code(nm, interpreted_entry_offset); + } else if (method->is_continuation_yield_intrinsic()) { + _cont_doYield_stub = nm; + } + return nm; + } + if (method->is_method_handle_intrinsic()) { vmIntrinsics::ID iid = method->intrinsic_id(); intptr_t start = (intptr_t) __ pc(); @@ -1545,6 +1997,7 @@ nmethod *SharedRuntime::generate_native_wrapper(MacroAssembler *masm, unsigned int wrapper_FrameDone; unsigned int wrapper_CRegsSet; Label handle_pending_exception; + Label last_java_pc; //--------------------------------------------------------------------- // Unverified entry point (UEP) @@ -1726,16 +2179,9 @@ nmethod *SharedRuntime::generate_native_wrapper(MacroAssembler *masm, // So if we must call out we must push a new frame. ////////////////////////////////////////////////////////////////////// - - // Calc the current pc into Z_R10 and into wrapper_CRegsSet. - // Both values represent the same position. - __ get_PC(Z_R10); // PC into register - wrapper_CRegsSet = __ offset(); // and into into variable. - - // Z_R10 now has the pc loaded that we will use when we finally call to native. - - // We use the same pc/oopMap repeatedly when we call out. - oop_maps->add_gc_map((int)(wrapper_CRegsSet-wrapper_CodeStart), map); + // The last java pc will also be used as resume pc if this is the wrapper for wait0. + // For this purpose the precise location matters but not for oopmap lookup. + __ z_larl(Z_R10, last_java_pc); // Lock a synchronized method. @@ -1780,10 +2226,13 @@ nmethod *SharedRuntime::generate_native_wrapper(MacroAssembler *masm, __ z_lgr(Z_ARG3, Z_thread); __ set_last_Java_frame(oldSP, Z_R10 /* gc map pc */); + assert(Z_R10->is_nonvolatile(), "Z_R10 needs to be preserved accross complete_monitor_locking_C call"); // Do the call. + __ push_cont_fastpath(); __ load_const_optimized(Z_R1_scratch, CAST_FROM_FN_PTR(address, SharedRuntime::complete_monitor_locking_C)); __ call(Z_R1_scratch); + __ pop_cont_fastpath(); __ reset_last_Java_frame(); @@ -1910,6 +2359,23 @@ nmethod *SharedRuntime::generate_native_wrapper(MacroAssembler *masm, // Transition from _thread_in_native_trans to _thread_in_Java. __ set_thread_state(_thread_in_Java); + // Check preemption for Object.wait() + if (method->is_object_wait0()) { + NearLabel not_preempted; + __ z_ltg(Z_R1_scratch, Address(Z_thread, JavaThread::preempt_alternate_return_offset())); + __ z_brz(not_preempted); // if 0, jump to not_preempted + __ z_mvghi(Address(Z_thread, JavaThread::preempt_alternate_return_offset()), 0); + __ z_br(Z_R1_scratch); + __ bind(not_preempted); + } + __ bind(last_java_pc); + + // Calc the current pc into wrapper_CRegsSet. + wrapper_CRegsSet = __ offset(); // and into into variable. + + // We use the same pc/oopMap repeatedly when we call out. + oop_maps->add_gc_map((int)(wrapper_CRegsSet-wrapper_CodeStart), map); + //-------------------------------------------------------------------- // Reguard any pages if necessary. // Protect native result from being destroyed. @@ -2012,7 +2478,10 @@ nmethod *SharedRuntime::generate_native_wrapper(MacroAssembler *masm, // Clear "last Java frame" SP and PC. //-------------------------------------------------------------------- - __ reset_last_Java_frame(); + + // Last java frame won't be set if we're resuming after preemption + bool maybe_preempted = method->is_object_wait0(); + __ reset_last_Java_frame(/* check_last_java_sp = */ !maybe_preempted); // Unpack oop result, e.g. JNIHandles::resolve result. if (is_reference_type(ret_type)) { @@ -2317,6 +2786,8 @@ void SharedRuntime::gen_i2c_adapter(MacroAssembler *masm, } } + __ push_cont_fastpath(); // Set JavaThread::_cont_fastpath to the sp of the oldest interpreted frame we know about + // Jump to the compiled code just as if compiled code was doing it. // load target address from method: __ z_lg(Z_R1_scratch, Address(Z_method, Method::from_compiled_offset())); @@ -2416,8 +2887,7 @@ uint SharedRuntime::out_preserve_stack_slots() { } VMReg SharedRuntime::thread_register() { - Unimplemented(); - return nullptr; + return Z_thread->as_VMReg(); } // @@ -2678,6 +3148,13 @@ void SharedRuntime::generate_deopt_blob() { // stack: (caller_of_deoptee, ...). + // Freezing continuation frames requires that the caller is trimmed to unextended sp if compiled. + // If not compiled the loaded value is equal to the current SP (see frame::initial_deoptimization_info()) + // and the frame is effectively not resized. + Register caller_sp = Z_R1_scratch; + __ z_lg(caller_sp, Address(unroll_block_reg, Deoptimization::UnrollBlock::initial_info_offset())); + __ resize_frame_absolute(caller_sp, Z_R0, true); + // loop through the `UnrollBlock' info and create interpreter frames. push_skeleton_frames(masm, true/*deopt*/, unroll_block_reg, @@ -2809,6 +3286,13 @@ UncommonTrapBlob* OptoRuntime::generate_uncommon_trap_blob() { __ zap_from_to(Z_SP, Z_SP, Z_R0_scratch, Z_R1, 500, -1); + // Freezing continuation frames requires that the caller is trimmed to unextended sp if compiled. + // If not compiled the loaded value is equal to the current SP (see frame::initial_deoptimization_info()) + // and the frame is effectively not resized. + Register caller_sp = Z_R1_scratch; + __ z_lg(caller_sp, Address(unroll_block_reg, Deoptimization::UnrollBlock::initial_info_offset())); + __ resize_frame_absolute(caller_sp, Z_R0, true); + // allocate new interpreter frame(s) and possibly resize the caller's frame // (no more adapters !) push_skeleton_frames(masm, false/*deopt*/, @@ -3387,16 +3871,76 @@ int SpinPause() { } #if INCLUDE_JFR + +// For c2: c_rarg0 is junk, call to runtime to write a checkpoint. +// It returns a jobject handle to the event writer. +// The handle is dereferenced and the return value is the event writer oop. RuntimeStub* SharedRuntime::generate_jfr_write_checkpoint() { - if (!Continuations::enabled()) return nullptr; - Unimplemented(); - return nullptr; + const char* name = SharedRuntime::stub_name(StubId::shared_jfr_write_checkpoint_id); + CodeBuffer code(name, 512, 64); + MacroAssembler* masm = new MacroAssembler(&code); + + int framesize = frame::z_abi_160_size / VMRegImpl::stack_slot_size; + address start = __ pc(); + __ save_return_pc(); // save return_pc (Z_R14) + __ push_frame_abi160(0); + int frame_complete = __ pc() - start; + __ set_last_Java_frame(Z_SP, noreg); + + __ call_VM_leaf(CAST_FROM_FN_PTR(address, JfrIntrinsicSupport::write_checkpoint), Z_thread); + address calls_return_pc = __ last_calls_return_pc(); + __ reset_last_Java_frame(); + + // The handle is dereferenced through a load barrier. + __ resolve_global_jobject(Z_ARG1, Z_tmp_1, Z_tmp_2); + __ pop_frame(); + __ restore_return_pc(); + __ z_br(Z_R14); + + OopMapSet* oop_maps = new OopMapSet(); + OopMap* map = new OopMap(framesize, 0); + oop_maps->add_gc_map(calls_return_pc - start, map); + + RuntimeStub* stub = // codeBlob framesize is in words (not VMRegImpl::slot_size) + RuntimeStub::new_runtime_stub(name, &code, frame_complete, + (framesize >> (LogBytesPerWord - LogBytesPerInt)), + oop_maps, false); + + return stub; } +// For c2: call to return a leased buffer. RuntimeStub* SharedRuntime::generate_jfr_return_lease() { - if (!Continuations::enabled()) return nullptr; - Unimplemented(); - return nullptr; + const char* name = SharedRuntime::stub_name(StubId::shared_jfr_return_lease_id); + CodeBuffer code(name, 512, 64); + MacroAssembler* masm = new MacroAssembler(&code); + + int framesize = frame::z_abi_160_size / VMRegImpl::stack_slot_size; + address start = __ pc(); + __ save_return_pc(); // save return_pc (Z_R14) + __ push_frame_abi160(0); + int frame_complete = __ pc() - start; + __ set_last_Java_frame(Z_SP, noreg); + + __ call_VM_leaf(CAST_FROM_FN_PTR(address, JfrIntrinsicSupport::return_lease), Z_thread); + address calls_return_pc = __ last_calls_return_pc(); + + __ reset_last_Java_frame(); + + __ pop_frame(); + __ restore_return_pc(); + __ z_br(Z_R14); + + OopMapSet* oop_maps = new OopMapSet(); + OopMap* map = new OopMap(framesize, 0); + oop_maps->add_gc_map(calls_return_pc - start, map); + + RuntimeStub* stub = // codeBlob framesize is in words (not VMRegImpl::slot_size) + RuntimeStub::new_runtime_stub(name, &code, frame_complete, + (framesize >> (LogBytesPerWord - LogBytesPerInt)), + oop_maps, false); + + return stub; } #endif // INCLUDE_JFR diff --git a/src/hotspot/cpu/s390/smallRegisterMap_s390.inline.hpp b/src/hotspot/cpu/s390/smallRegisterMap_s390.inline.hpp index f338fb192ad..630a9516831 100644 --- a/src/hotspot/cpu/s390/smallRegisterMap_s390.inline.hpp +++ b/src/hotspot/cpu/s390/smallRegisterMap_s390.inline.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -30,7 +30,7 @@ class SmallRegisterMap; -// Java frames don't have callee saved registers (except for rfp), so we can use a smaller SmallRegisterMapType +// Java frames don't have callee saved registers, so we can use a smaller RegisterMap template class SmallRegisterMapType { friend SmallRegisterMap; @@ -39,8 +39,6 @@ class SmallRegisterMapType { ~SmallRegisterMapType() = default; NONCOPYABLE(SmallRegisterMapType); - static void assert_is_rfp(VMReg r) NOT_DEBUG_RETURN - DEBUG_ONLY({ Unimplemented(); }) public: // as_RegisterMap is used when we didn't want to templatize and abstract over RegisterMap type to support SmallRegisterMap // Consider enhancing SmallRegisterMap to support those cases @@ -48,20 +46,21 @@ public: RegisterMap* as_RegisterMap() { return nullptr; } RegisterMap* copy_to_RegisterMap(RegisterMap* map, intptr_t* sp) const { - Unimplemented(); + map->clear(); + map->set_include_argument_oops(this->include_argument_oops()); return map; } inline address location(VMReg reg, intptr_t* sp) const { - Unimplemented(); + assert(false, "Reg: %s", reg->name()); return nullptr; } - inline void set_location(VMReg reg, address loc) { assert_is_rfp(reg); } + inline void set_location(VMReg reg, address loc) { assert(false, "Reg: %s", reg->name()); } JavaThread* thread() const { #ifndef ASSERT - guarantee (false, ""); + guarantee (false, "unreachable"); #endif return nullptr; } @@ -76,7 +75,7 @@ public: #ifdef ASSERT bool should_skip_missing() const { return false; } VMReg find_register_spilled_here(void* p, intptr_t* sp) { - Unimplemented(); + assert(false, "Shouldn't reach here! p:" PTR_FORMAT " sp:" PTR_FORMAT, p2i(p), p2i(p)); return nullptr; } void print() const { print_on(tty); } diff --git a/src/hotspot/cpu/s390/stackChunkFrameStream_s390.inline.hpp b/src/hotspot/cpu/s390/stackChunkFrameStream_s390.inline.hpp index e598117fe7d..3a5b860b7a7 100644 --- a/src/hotspot/cpu/s390/stackChunkFrameStream_s390.inline.hpp +++ b/src/hotspot/cpu/s390/stackChunkFrameStream_s390.inline.hpp @@ -1,5 +1,6 @@ /* - * Copyright (c) 2019, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2026 IBM Corporation. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -32,75 +33,120 @@ #ifdef ASSERT template inline bool StackChunkFrameStream::is_in_frame(void* p0) const { - Unimplemented(); - return true; + assert(!is_done(), ""); + assert(is_compiled(), ""); + intptr_t* p = (intptr_t*)p0; + int argsize = (_cb->as_nmethod()->num_stack_arg_slots() * VMRegImpl::stack_slot_size) >> LogBytesPerWord; + int frame_size = _cb->frame_size() + (argsize > 0 ? argsize + frame::metadata_words_at_top : 0); + return (p - unextended_sp()) >= 0 && (p - unextended_sp()) < frame_size; } #endif template inline frame StackChunkFrameStream::to_frame() const { - Unimplemented(); - return frame(); + if (is_done()) { + return frame(_sp, _sp, nullptr, nullptr, nullptr, nullptr, true); + } else { + // Compiled frames on heap don't have back links on s390. The back link is redundant + // and gets computed as unextended_sp + frame_size. In debug builds, FreezeBase::patch_pd() + // explicitly sets it to badAddress. + return frame(sp(), unextended_sp(), Interpreter::contains(pc()) ? fp() : nullptr, pc(), cb(), _oopmap, true); + } } template inline address StackChunkFrameStream::get_pc() const { - Unimplemented(); - return nullptr; + assert(!is_done(), ""); + return (address)((frame::z_common_abi*) _sp)->return_pc; } template inline intptr_t* StackChunkFrameStream::fp() const { - Unimplemented(); - return nullptr; + // See FreezeBase::patch_pd() and frame::setup() + assert((frame_kind == ChunkFrames::Mixed && is_interpreted()), ""); + intptr_t* fp_addr = (intptr_t*)&((frame::z_common_abi*)_sp)->callers_sp; + assert(*(intptr_t**)fp_addr != nullptr, ""); + // derelativize + return fp_addr + *fp_addr; } template inline intptr_t* StackChunkFrameStream::derelativize(int offset) const { - Unimplemented(); - return nullptr; + intptr_t* fp = this->fp(); + assert(fp != nullptr, ""); + return fp + fp[offset]; } template inline intptr_t* StackChunkFrameStream::unextended_sp_for_interpreter_frame() const { - Unimplemented(); - return nullptr; + assert_is_interpreted_and_frame_type_mixed(); + // Compute the unextended SP (stack pointer before any extension for arguments). + // On s390, esp points to the next free slot above the operand stack, so we add 1 + // to get the actual top of the operand stack, then subtract metadata_words to + // account for the frame metadata (callers_sp and return_pc) at the top of the frame. + return derelativize(_z_ijava_idx(esp)) + 1 - frame::metadata_words; } template inline void StackChunkFrameStream::next_for_interpreter_frame() { - Unimplemented(); + assert_is_interpreted_and_frame_type_mixed(); + if (derelativize(_z_ijava_idx(locals)) + 1 >= _end) { + _unextended_sp = _end; + _sp = _end; + } else { + _unextended_sp = derelativize(_z_ijava_idx(sender_sp)); + _sp = this->fp(); + } } template inline int StackChunkFrameStream::interpreter_frame_size() const { - Unimplemented(); - return 0; + assert_is_interpreted_and_frame_type_mixed(); + intptr_t* top = unextended_sp(); // later subtract argsize if callee is interpreted + intptr_t* bottom = derelativize(_z_ijava_idx(locals)) + 1; + return (int)(bottom - top); } +// Size of stack args in words (P0..Pn above). Only valid if the caller is also +// interpreted. The function is also called if the caller is compiled but the +// result is not used in that case (same on x86). +// See also setting of sender_sp in ContinuationHelper::InterpretedFrame::patch_sender_sp() template inline int StackChunkFrameStream::interpreter_frame_stack_argsize() const { - Unimplemented(); - return 0; + assert_is_interpreted_and_frame_type_mixed(); + frame::z_ijava_state* state = (frame::z_ijava_state*)((uintptr_t)fp() - frame::z_ijava_state_size); + int diff = (int)(state->locals - (state->sender_sp + frame::metadata_words_at_top) + 1); + assert(diff == -frame::metadata_words_at_top || ((Method*)state->method)->size_of_parameters() == diff, + "size_of_parameters(): %d diff: %d sp: " PTR_FORMAT " fp:" PTR_FORMAT, + ((Method*)state->method)->size_of_parameters(), diff, p2i(sp()), p2i(fp())); + return diff; } template template inline int StackChunkFrameStream::interpreter_frame_num_oops(RegisterMapT* map) const { - Unimplemented(); - return 0; + assert_is_interpreted_and_frame_type_mixed(); + ResourceMark rm; + frame f = to_frame(); + InterpreterOopCount closure; + f.oops_interpreted_do(&closure, map); + return closure.count(); } template<> template<> inline void StackChunkFrameStream::update_reg_map_pd(RegisterMap* map) { - Unimplemented(); + // No register map update needed for s390. + // In the Java calling convention on s390, all registers are volatile (caller-saved), + // so there are no non-volatile (callee-saved) registers that need to be tracked. } template<> template<> inline void StackChunkFrameStream::update_reg_map_pd(RegisterMap* map) { - Unimplemented(); + // No register map update needed for s390. + // In the Java calling convention on s390, all registers are volatile (caller-saved), + // so there are no non-volatile (callee-saved) registers that need to be tracked. } template diff --git a/src/hotspot/cpu/s390/stackChunkOop_s390.inline.hpp b/src/hotspot/cpu/s390/stackChunkOop_s390.inline.hpp index dfd3562c9d9..c97751d0d1e 100644 --- a/src/hotspot/cpu/s390/stackChunkOop_s390.inline.hpp +++ b/src/hotspot/cpu/s390/stackChunkOop_s390.inline.hpp @@ -1,5 +1,6 @@ /* - * Copyright (c) 2019, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2019, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2026 IBM Corporation. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,11 +27,15 @@ #define CPU_S390_STACKCHUNKOOP_S390_INLINE_HPP inline void stackChunkOopDesc::relativize_frame_pd(frame& fr) const { - Unimplemented(); + if (fr.is_interpreted_frame()) { + fr.set_offset_fp(relativize_address(fr.fp())); + } } inline void stackChunkOopDesc::derelativize_frame_pd(frame& fr) const { - Unimplemented(); + if (fr.is_interpreted_frame()) { + fr.set_fp(derelativize_address(fr.offset_fp())); + } } #endif // CPU_S390_STACKCHUNKOOP_S390_INLINE_HPP diff --git a/src/hotspot/cpu/s390/stubDeclarations_s390.hpp b/src/hotspot/cpu/s390/stubDeclarations_s390.hpp index d0e26beedab..d773b6ce759 100644 --- a/src/hotspot/cpu/s390/stubDeclarations_s390.hpp +++ b/src/hotspot/cpu/s390/stubDeclarations_s390.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2025, Red Hat, Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -47,7 +47,7 @@ do_arch_entry, \ do_arch_entry_init, \ do_arch_entry_array) \ - do_arch_blob(continuation, 2000) \ + do_arch_blob(continuation, 5000) \ #define STUBGEN_COMPILER_BLOBS_ARCH_DO(do_stub, \ diff --git a/src/hotspot/cpu/s390/stubGenerator_s390.cpp b/src/hotspot/cpu/s390/stubGenerator_s390.cpp index 5309158fc74..d1601d4f147 100644 --- a/src/hotspot/cpu/s390/stubGenerator_s390.cpp +++ b/src/hotspot/cpu/s390/stubGenerator_s390.cpp @@ -1,6 +1,7 @@ /* * Copyright (c) 2016, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016, 2024 SAP SE. All rights reserved. + * Copyright (c) 2026 IBM Corporation. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -37,6 +38,8 @@ #include "oops/oop.inline.hpp" #include "prims/methodHandles.hpp" #include "prims/upcallLinker.hpp" +#include "runtime/continuation.hpp" +#include "runtime/continuationEntry.inline.hpp" #include "runtime/frame.inline.hpp" #include "runtime/handles.inline.hpp" #include "runtime/javaThread.hpp" @@ -330,6 +333,8 @@ class StubGenerator: public StubCodeGenerator { // Pop frame. Done here to minimize stalls. __ pop_frame(); + __ pop_cont_fastpath(); + // Reload some volatile registers which we've spilled before the call // to template interpreter / native entry. // Access all locals via frame pointer, because we know nothing about @@ -3223,28 +3228,180 @@ class StubGenerator: public StubCodeGenerator { return start; } - address generate_cont_thaw(bool return_barrier, bool exception) { + address generate_cont_thaw(StubId stub_id) { if (!Continuations::enabled()) return nullptr; - Unimplemented(); - return nullptr; + + Continuation::thaw_kind kind; + bool return_barrier; + bool return_barrier_exception; + + switch (stub_id) { + case StubId::stubgen_cont_thaw_id: + kind = Continuation::thaw_top; + return_barrier = false; + return_barrier_exception = false; + break; + case StubId::stubgen_cont_returnBarrier_id: + kind = Continuation::thaw_return_barrier; + return_barrier = true; + return_barrier_exception = false; + break; + case StubId::stubgen_cont_returnBarrierExc_id: + kind = Continuation::thaw_return_barrier_exception; + return_barrier = true; + return_barrier_exception = true; + break; + default: + ShouldNotReachHere(); + } + + StubCodeMark mark(this, stub_id); + address start = __ pc(); + + // TODO: Handle Valhalla return types. May require generating different return barriers. + + if (kind == Continuation::thaw_top) { + __ clobber_nonvolatile_registers(); // Except Z_thread + } + + if (return_barrier) { + // Save return values in non-volatile float registers to preserve them across VM calls. + // Z_F8 and Z_F9 are non-volatile (callee-saved) registers on s390 (F8-F15 are non-volatile). + // They are safe to use here because: + // 1. clobber_nonvolatile_registers() is NOT called for return_barrier cases (only for thaw_top) + // 2. These registers are preserved across the VM leaf calls (prepare_thaw, thaw_entry) + __ z_ldgr(Z_F8, Z_RET); // Save integer return value in non-volatile float register + __ z_ldr(Z_F9, Z_FRET); // Save float return value in non-volatile float register + + DEBUG_ONLY(__ z_lg(Z_R1_scratch, _z_common_abi(callers_sp), Z_SP);) + __ z_lg(Z_SP, Address(Z_thread, JavaThread::cont_entry_offset())); +#ifdef ASSERT + __ z_cg(Z_R1_scratch, _z_common_abi(callers_sp), Z_SP); + __ asm_assert(/* check_equal=*/ true, FILE_AND_LINE ": callers sp is corrupt at thaw entry", 69); +#endif + + } + +#ifdef ASSERT + __ z_cg(Z_SP, Address(Z_thread, JavaThread::cont_entry_offset())); + __ asm_assert(/* check_equal=*/ true, FILE_AND_LINE ": incorrect Z_SP", 70); +#endif + + __ z_lghi(Z_ARG2, return_barrier ? 1 : 0); + __ call_VM_leaf(CAST_FROM_FN_PTR(address, Continuation::prepare_thaw), Z_thread, Z_ARG2); + +#ifdef ASSERT + __ z_cg(Z_SP, Address(Z_thread, JavaThread::cont_entry_offset())); + __ asm_assert(/* check equal = */ true, FILE_AND_LINE ": incorrect Z_SP after prepare_thaw", 48); +#endif // ASSERT + + // Z_RET contains the size of the frames to thaw, 0 if overflow or no more frames + NearLabel L_thaw_success; + __ z_ltgr(Z_RET, Z_RET); + __ branch_optimized(Assembler::bcondNotEqual, L_thaw_success); + __ load_const_optimized(Z_R1_scratch, (SharedRuntime::throw_StackOverflowError_entry())); + __ call(Z_R1_scratch); + __ bind(L_thaw_success); + + // Make room for the thawed frames and align the stack. + __ add64(Z_RET, frame::z_abi_160_size); + + { // stack alignment + __ z_lcgr(Z_RET, Z_RET); // negate Z_RET value + __ z_nill(Z_RET, -frame::alignment_in_bytes); + } + __ resize_frame( /* offset = */ Z_RET,/* fp = */ Z_R1, /* load_fp = */ true); + + __ z_lghi(Z_ARG2, kind); + __ add64(Z_SP, -frame::z_abi_160_size); // Register save area for Continuation::thaw + __ call_VM_leaf(Continuation::thaw_entry(), Z_thread, Z_ARG2); + __ z_lgr(Z_SP, Z_RET); // Z_RET contains the SP of the thawed top frame + + if (return_barrier) { + // we're now in the caller of the frame that returned to the barrier + // restore return value (no safepoint in the call to thaw, so even an oop return value should be OK) + + __ z_lgdr(Z_RET, Z_F8); // Restore integer return value + __ z_ldr(Z_FRET, Z_F9); // Restore float return value + } else { + // we're now on the yield frame (which is in an address above us b/c rsp has been pushed down) + __ z_lghi(Z_RET, 0); // return 0 (success) from doYield + } + + if (return_barrier_exception) { + Register handler = Z_R1_scratch; + __ z_lg(Z_ARG2, _z_common_abi(return_pc), Z_SP); // exception pc + __ save_return_pc(); + __ push_frame_abi160(0 + 2 * BytesPerWord); + __ z_stg(Z_RET , 0 * BytesPerWord + frame::z_abi_160_size, Z_SP); // save return value containing the exception oop + + __ z_stg(Z_ARG2, 1 * BytesPerWord + frame::z_abi_160_size, Z_SP); // save exception_pc + __ call_VM_leaf(CAST_FROM_FN_PTR(address, SharedRuntime::exception_handler_for_return_address), Z_thread, Z_ARG2); + + // Copy handler's address. + __ z_lgr(handler, Z_RET); + + // Set up the arguments for the exception handler: + // - Z_ARG1: exception oop + // - Z_ARG2: exception pc + __ z_lg(Z_ARG1, 0 * BytesPerWord + frame::z_abi_160_size, Z_SP); // load the exception oop + __ z_lg(Z_ARG2, 1 * BytesPerWord + frame::z_abi_160_size, Z_SP); // load the exception pc + __ pop_frame(); + __ restore_return_pc(); + } else { + // We're "returning" into the topmost thawed frame; see Thaw::push_return_frame + __ z_lg(Z_R1_scratch, _z_common_abi(return_pc), Z_SP); + } + __ z_br(Z_R1_scratch); + + return start; } address generate_cont_thaw() { - if (!Continuations::enabled()) return nullptr; - Unimplemented(); - return nullptr; + return generate_cont_thaw(StubId::stubgen_cont_thaw_id); } address generate_cont_returnBarrier() { - if (!Continuations::enabled()) return nullptr; - Unimplemented(); - return nullptr; + return generate_cont_thaw(StubId::stubgen_cont_returnBarrier_id); } address generate_cont_returnBarrier_exception() { + return generate_cont_thaw(StubId::stubgen_cont_returnBarrierExc_id); + } + + address generate_cont_preempt_stub() { if (!Continuations::enabled()) return nullptr; - Unimplemented(); - return nullptr; + StubId stub_id = StubId::stubgen_cont_preempt_id; + StubCodeMark mark(this, stub_id); + address start = __ pc(); + + __ clobber_nonvolatile_registers(); // Except Z_thread + + __ reset_last_Java_frame(/*check_last_java_sp=*/ false); + + // Set sp to enterSpecial frame, i.e. remove all frames copied into the heap. + __ z_lg(Z_SP, Address(Z_thread, JavaThread::cont_entry_offset())); + + Label preemption_cancelled; + + __ z_cli(in_bytes(JavaThread::preemption_cancelled_offset()), Z_thread, 0); + __ z_brne(preemption_cancelled); + + // Remove enterSpecial frame from the stack and return to Continuation.run() to unmount. + SharedRuntime::continuation_enter_cleanup(_masm); + __ pop_frame(); + __ restore_return_pc(); + __ z_br(Z_R14); + + // We acquired the monitor after freezing the frames so call thaw to continue execution. + __ bind(preemption_cancelled); + __ z_mvi(in_bytes(JavaThread::preemption_cancelled_offset()), Z_thread, 0); + + __ load_const_optimized(Z_R1, ContinuationEntry::thaw_call_pc_address()); + __ z_lg(Z_R1, Address(Z_R1)); + __ z_br(Z_R1); + + return start; } // exception handler for upcall stubs @@ -3327,9 +3484,10 @@ class StubGenerator: public StubCodeGenerator { if (!Continuations::enabled()) return; // Continuation stubs: - StubRoutines::_cont_thaw = generate_cont_thaw(); - StubRoutines::_cont_returnBarrier = generate_cont_returnBarrier(); + StubRoutines::_cont_thaw = generate_cont_thaw(); + StubRoutines::_cont_returnBarrier = generate_cont_returnBarrier(); StubRoutines::_cont_returnBarrierExc = generate_cont_returnBarrier_exception(); + StubRoutines::_cont_preempt_stub = generate_cont_preempt_stub(); } void generate_final_stubs() { diff --git a/src/hotspot/cpu/s390/templateInterpreterGenerator_s390.cpp b/src/hotspot/cpu/s390/templateInterpreterGenerator_s390.cpp index dba04fc0e85..03470597ab5 100644 --- a/src/hotspot/cpu/s390/templateInterpreterGenerator_s390.cpp +++ b/src/hotspot/cpu/s390/templateInterpreterGenerator_s390.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016, 2024 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -717,13 +717,31 @@ address TemplateInterpreterGenerator::generate_safept_entry_for (TosState state, address runtime_entry) { address entry = __ pc(); __ push(state); + __ push_cont_fastpath(); __ call_VM(noreg, runtime_entry); + __ pop_cont_fastpath(); __ dispatch_via(vtos, Interpreter::_normal_table.table_for (vtos)); return entry; } address TemplateInterpreterGenerator::generate_cont_resume_interpreter_adapter() { - return nullptr; + if (!Continuations::enabled()) return nullptr; + address start = __ pc(); + __ z_lg(Z_fp, _z_common_abi(callers_sp), Z_SP); + { + Register top_frame_sp = Z_R1_scratch; // anyway going to load it with correct value + __ z_lg(top_frame_sp, Address(Z_fp, _z_ijava_state_neg(top_frame_sp))); + __ z_slag(top_frame_sp, top_frame_sp, Interpreter::logStackElementSize); + __ z_agr(top_frame_sp, Z_fp); + + __ resize_frame_absolute(top_frame_sp, /* temp = */ Z_R0, /* load_fp = */ true); + } + __ restore_bcp(); + __ restore_locals(); + __ restore_esp(); + + __ z_br(Z_R14); + return start; } @@ -1468,8 +1486,13 @@ address TemplateInterpreterGenerator::generate_native_entry(bool synchronized) { __ bind(call_signature_handler); + bool support_vthread_preemption = Continuations::enabled(); + // We have a TOP_IJAVA_FRAME here, which belongs to us. - __ set_top_ijava_frame_at_SP_as_last_Java_frame(Z_SP, Z_R1/*tmp*/); + Label last_java_pc; + Label *resume_pc = support_vthread_preemption ? &last_java_pc : nullptr; + + __ set_top_ijava_frame_at_SP_as_last_Java_frame(Z_SP, Z_R1/*tmp*/, resume_pc); // Call signature handler and pass locals address in Z_ARG1. __ z_lgr(Z_ARG1, Z_locals); @@ -1526,7 +1549,18 @@ address TemplateInterpreterGenerator::generate_native_entry(bool synchronized) { // overwritten since "__ call_stub(signature_handler);" (except for // ARG1 and ARG2 for static methods). + if (support_vthread_preemption) { + // Rresult_handler is a nonvolatile register. Its value will be preserved across + // the native call but only if the call isn't preempted. To preserve its value even + // in the case of preemption we save it in the lresult slot. It is restored at + // resume_pc if, and only if the call was preempted. This works because only + // j.l.Object::wait calls are preempted which don't return a result. + + __ z_stg(Rresult_handler, _z_ijava_state_neg(lresult), Z_fp); + } + __ push_cont_fastpath(); __ call_c(Z_R1/*native_method_entry*/); + __ pop_cont_fastpath(); // NOTE: frame::interpreter_frame_result() depends on these stores. __ z_stg(Z_RET, _z_ijava_state_neg(lresult), Z_fp); @@ -1610,6 +1644,32 @@ address TemplateInterpreterGenerator::generate_native_entry(bool synchronized) { __ z_lg(Z_bcp, Address(Rmethod, Method::const_offset())); // get constMethod __ add2reg(Z_bcp, in_bytes(ConstMethod::codes_offset())); // get codebase + if (support_vthread_preemption) { + // Check preemption for Object.wait() + Label not_preempted; + __ z_ltg(Z_R1_scratch, Address(Z_thread, JavaThread::preempt_alternate_return_offset())); + __ z_brz(not_preempted); // if 0, jump to not_preempted + __ z_mvghi(Address(Z_thread, JavaThread::preempt_alternate_return_offset()), 0); + __ z_br(Z_R1_scratch); + + // Execution will be resumed here when the vthread becomes runnable again. + __ bind(*resume_pc); + __ restore_after_resume(); + // We saved the result handler before the call + __ z_lg(Rresult_handler, _z_ijava_state_neg(lresult), Z_fp); +#ifdef ASSERT + // Clobber result slots. Only native methods returning void can be preemted currently. + __ load_const(Z_RET, UCONST64(0xbad01001)); + __ z_stg(Z_RET, _z_ijava_state_neg(lresult), Z_fp); + __ z_stg(Z_RET, _z_ijava_state_neg(fresult), Z_fp); + // reset_last_Java_frame() below asserts that a last java sp is set + __ asm_assert_mem8_is_zero(in_bytes(JavaThread::last_Java_sp_offset()), + Z_thread, FILE_AND_LINE ": Last java sp should not be set when resuming", 69); + __ z_stg(Z_RET, in_bytes(JavaThread::last_Java_sp_offset()), Z_thread); +#endif + __ bind(not_preempted); + } + if (CheckJNICalls) { // clear_pending_jni_exception_check __ clear_mem(Address(Z_thread, JavaThread::pending_jni_exception_check_fn_offset()), sizeof(oop)); @@ -2030,7 +2090,7 @@ address TemplateInterpreterGenerator::generate_CRC32C_updateBytes_entry(Abstract address TemplateInterpreterGenerator::generate_currentThread() { uint64_t entry_off = __ offset(); - __ z_lg(Z_RET, Address(Z_thread, JavaThread::threadObj_offset())); + __ z_lg(Z_RET, Address(Z_thread, JavaThread::vthread_offset())); __ resolve_oop_handle(Z_RET, Z_R0_scratch, Z_R1_scratch); // Restore caller sp for c2i case. @@ -2176,6 +2236,7 @@ void TemplateInterpreterGenerator::generate_throw_exception() { JavaThread::popframe_force_deopt_reexecution_bit, Z_tmp_1, false); + __ pop_cont_fastpath(); // Continue in deoptimization handler. __ z_br(Z_R14); @@ -2191,6 +2252,7 @@ void TemplateInterpreterGenerator::generate_throw_exception() { false, // install_monitor_exception false); // notify_jvmdi __ z_lg(Z_fp, _z_abi(callers_sp), Z_SP); // Restore frame pointer. + __ pop_cont_fastpath(); { Register top_frame_sp = Z_R1_scratch; __ z_lg(top_frame_sp, Address(Z_fp, _z_ijava_state_neg(top_frame_sp))); @@ -2264,6 +2326,7 @@ void TemplateInterpreterGenerator::generate_throw_exception() { // Remove the activation (without doing throws on illegalMonitorExceptions). __ remove_activation(vtos, noreg/*ret.pc already loaded*/, false/*throw exc*/, true/*install exc*/, false/*notify jvmti*/); __ z_lg(Z_fp, _z_abi(callers_sp), Z_SP); // Restore frame pointer. + __ pop_cont_fastpath(); __ get_vm_result_oop(Z_ARG1); // Restore exception. __ verify_oop(Z_ARG1); diff --git a/src/hotspot/cpu/s390/templateTable_s390.cpp b/src/hotspot/cpu/s390/templateTable_s390.cpp index 3b0929608a3..1da24c0378c 100644 --- a/src/hotspot/cpu/s390/templateTable_s390.cpp +++ b/src/hotspot/cpu/s390/templateTable_s390.cpp @@ -2336,7 +2336,9 @@ void TemplateTable::_return(TosState state) { __ z_tm(poll_byte_addr, SafepointMechanism::poll_bit()); __ z_braz(no_safepoint); __ push(state); + __ push_cont_fastpath(); __ call_VM(noreg, CAST_FROM_FN_PTR(address, InterpreterRuntime::at_safepoint)); + __ pop_cont_fastpath(); __ pop(state); __ bind(no_safepoint); } @@ -2395,7 +2397,7 @@ void TemplateTable::resolve_cache_and_index_for_method(int byte_no, // Class initialization barrier slow path lands here as well. address entry = CAST_FROM_FN_PTR(address, InterpreterRuntime::resolve_from_cache); __ load_const_optimized(Z_ARG2, (int)code); - __ call_VM(noreg, entry, Z_ARG2); + __ call_VM_preemptable(noreg, entry, Z_ARG2); // Update registers with resolved info. __ load_method_entry(Rcache, index); @@ -2445,7 +2447,7 @@ void TemplateTable::resolve_cache_and_index_for_field(int byte_no, // Class initialization barrier slow path lands here as well. address entry = CAST_FROM_FN_PTR(address, InterpreterRuntime::resolve_from_cache); __ load_const_optimized(Z_ARG2, (int)code); - __ call_VM(noreg, entry, Z_ARG2); + __ call_VM_preemptable(noreg, entry, Z_ARG2); // Update registers with resolved info. __ load_field_entry(cache, index); @@ -4022,7 +4024,7 @@ void TemplateTable::_new() { __ bind(slow_case); __ get_constant_pool(Z_ARG2); __ get_2_byte_integer_at_bcp(Z_ARG3/*dest*/, 1, InterpreterMacroAssembler::Unsigned); - call_VM(Z_tos, CAST_FROM_FN_PTR(address, InterpreterRuntime::_new), Z_ARG2, Z_ARG3); + __ call_VM_preemptable(Z_tos, CAST_FROM_FN_PTR(address, InterpreterRuntime::_new), Z_ARG2, Z_ARG3); __ verify_oop(Z_tos); // continue diff --git a/src/hotspot/cpu/s390/upcallLinker_s390.cpp b/src/hotspot/cpu/s390/upcallLinker_s390.cpp index 23ac80ddf48..de57e5e0cc4 100644 --- a/src/hotspot/cpu/s390/upcallLinker_s390.cpp +++ b/src/hotspot/cpu/s390/upcallLinker_s390.cpp @@ -220,9 +220,13 @@ address UpcallLinker::make_upcall_stub(jobject receiver, Symbol* signature, __ call(call_target_address); // load taget Method* into Z_method __ block_comment("} load_target"); + __ push_cont_fastpath(); + __ z_lg(call_target_address, Address(Z_method, in_bytes(Method::from_compiled_offset()))); __ call(call_target_address); + __ pop_cont_fastpath(); + // return value shuffle assert(!needs_return_buffer, "unexpected needs_return_buffer"); // CallArranger can pick a return type that goes in the same reg for both CCs. diff --git a/src/hotspot/share/oops/stackChunkOop.inline.hpp b/src/hotspot/share/oops/stackChunkOop.inline.hpp index d0ddbe8dfe6..3ad28190d02 100644 --- a/src/hotspot/share/oops/stackChunkOop.inline.hpp +++ b/src/hotspot/share/oops/stackChunkOop.inline.hpp @@ -369,7 +369,7 @@ inline void stackChunkOopDesc::copy_from_stack_to_chunk(intptr_t* from, intptr_t assert(to >= start_address(), "Chunk underflow"); assert(to + size <= end_address(), "Chunk overflow"); -#if !(defined(AMD64) || defined(AARCH64) || defined(RISCV64) || defined(PPC64)) || defined(ZERO) +#if !(defined(AMD64) || defined(AARCH64) || defined(RISCV64) || defined(PPC64) || defined(S390)) || defined(ZERO) // Suppress compilation warning-as-error on unimplemented architectures // that stub out arch-specific methods. Some compilers are smart enough // to figure out the argument is always null and then warn about it. @@ -388,7 +388,7 @@ inline void stackChunkOopDesc::copy_from_chunk_to_stack(intptr_t* from, intptr_t assert(from >= start_address(), ""); assert(from + size <= end_address(), ""); -#if !(defined(AMD64) || defined(AARCH64) || defined(RISCV64) || defined(PPC64)) || defined(ZERO) +#if !(defined(AMD64) || defined(AARCH64) || defined(RISCV64) || defined(PPC64) || defined(S390)) || defined(ZERO) // Suppress compilation warning-as-error on unimplemented architectures // that stub out arch-specific methods. Some compilers are smart enough // to figure out the argument is always null and then warn about it. diff --git a/src/hotspot/share/runtime/continuation.cpp b/src/hotspot/share/runtime/continuation.cpp index f8af2545c37..e80720072c5 100644 --- a/src/hotspot/share/runtime/continuation.cpp +++ b/src/hotspot/share/runtime/continuation.cpp @@ -317,7 +317,7 @@ frame Continuation::continuation_parent_frame(RegisterMap* map) { map->set_stack_chunk(nullptr); -#if (defined(X86) || defined(AARCH64) || defined(RISCV64) || defined(PPC64)) && !defined(ZERO) +#if (defined(X86) || defined(AARCH64) || defined(RISCV64) || defined(PPC64) || defined(S390)) && !defined(ZERO) frame sender(cont.entrySP(), cont.entryFP(), cont.entryPC()); #else frame sender = frame(); diff --git a/src/hotspot/share/runtime/continuationFreezeThaw.cpp b/src/hotspot/share/runtime/continuationFreezeThaw.cpp index d76652edf36..e9b6325d03b 100644 --- a/src/hotspot/share/runtime/continuationFreezeThaw.cpp +++ b/src/hotspot/share/runtime/continuationFreezeThaw.cpp @@ -220,7 +220,6 @@ template static inline freeze_result freeze_inte static inline int prepare_thaw_internal(JavaThread* thread, bool return_barrier); template static inline intptr_t* thaw_internal(JavaThread* thread, const Continuation::thaw_kind kind); - // Entry point to freeze. Transitions are handled manually // Called from gen_continuation_yield() in sharedRuntime_.cpp through Continuation::freeze_entry(); template @@ -507,13 +506,7 @@ FreezeBase::FreezeBase(JavaThread* thread, ContinuationWrapper& cont, intptr_t* assert(!Interpreter::contains(_cont.entryPC()), ""); - _bottom_address = _cont.entrySP() - _cont.entry_frame_extension(); -#ifdef _LP64 - if (((intptr_t)_bottom_address & 0xf) != 0) { - _bottom_address--; - } - assert(is_aligned(_bottom_address, frame::frame_alignment), ""); -#endif + _bottom_address = align_down(_cont.entrySP() - _cont.entry_frame_extension(), frame::frame_alignment); log_develop_trace(continuations)("bottom_address: " INTPTR_FORMAT " entrySP: " INTPTR_FORMAT " argsize: " PTR_FORMAT, p2i(_bottom_address), p2i(_cont.entrySP()), (_cont.entrySP() - _bottom_address) << LogBytesPerWord); @@ -523,13 +516,17 @@ FreezeBase::FreezeBase(JavaThread* thread, ContinuationWrapper& cont, intptr_t* assert(_cont.chunk_invariant(), ""); assert(!Interpreter::contains(_cont.entryPC()), ""); -#if !defined(PPC64) || defined(ZERO) - static const int doYield_stub_frame_size = frame::metadata_words; -#else +#if defined(PPC64) && !defined(ZERO) static const int doYield_stub_frame_size = frame::native_abi_reg_args_size >> LogBytesPerWord; +#elif defined(S390) && !defined(ZERO) + static const int doYield_stub_frame_size = frame::z_abi_160_base_size >> LogBytesPerWord; +#else + static const int doYield_stub_frame_size = frame::metadata_words; #endif // With preemption doYield() might not have been resolved yet - assert(_preempt || SharedRuntime::cont_doYield_stub()->frame_size() == doYield_stub_frame_size, ""); + assert(_preempt || SharedRuntime::cont_doYield_stub()->frame_size() == doYield_stub_frame_size, + "_preempt = %d, cont_doYield_stub()->frame_size() = %d, doYield_stub_frame_size = %d", + (_preempt ? 1 : 0), SharedRuntime::cont_doYield_stub()->frame_size(), doYield_stub_frame_size); if (preempt) { _last_frame = _thread->last_frame(); @@ -2597,7 +2594,13 @@ inline void ThawBase::patch(frame& f, const frame& caller, bool bottom) { } else if (_should_patch_caller_pc) { // Caller was deoptimized during thaw but we've overwritten the return address when copying f from the heap. // Also, on some platforms, if the caller is interpreted but the callee not we also need to patch. - assert(caller.is_deoptimized_frame() PPC64_ONLY(|| caller.is_interpreted_frame()), ""); + +#if defined(PPC64) || defined(S390) + assert(caller.is_deoptimized_frame() || caller.is_interpreted_frame(), ""); +#else + assert(caller.is_deoptimized_frame(), ""); +#endif + ContinuationHelper::Frame::patch_pc(caller, caller.raw_pc()); _should_patch_caller_pc = false; } diff --git a/src/hotspot/share/runtime/frame.cpp b/src/hotspot/share/runtime/frame.cpp index ae04d398043..3e45b6fe310 100644 --- a/src/hotspot/share/runtime/frame.cpp +++ b/src/hotspot/share/runtime/frame.cpp @@ -1685,13 +1685,13 @@ void FrameValues::print_on(outputStream* st, int min_index, int max_index, intpt // 4. Recognize it as being part of the "fixed frame". if (*fv.location != 0 && *fv.location > -100 && *fv.location < 100 && fp != nullptr && *fv.description != '#' -#if !defined(PPC64) +#if !defined(PPC64) && !defined(S390) && (strncmp(fv.description, "interpreter_frame_", 18) == 0 || strstr(fv.description, " method ")) -#else // !defined(PPC64) +#else // !defined(PPC64) && !defined(S390) && (strcmp(fv.description, "sender_sp") == 0 || strcmp(fv.description, "top_frame_sp") == 0 || strcmp(fv.description, "esp") == 0 || strcmp(fv.description, "monitors") == 0 || strcmp(fv.description, "locals") == 0 || strstr(fv.description, " method ")) -#endif //!defined(PPC64) +#endif // !defined(PPC64) && !defined(S390) ) { st->print_cr(" " INTPTR_FORMAT ": " INTPTR_FORMAT " %-32s (relativized: fp%+d)", p2i(fv.location), p2i(&fp[*fv.location]), fv.description, (int)*fv.location); diff --git a/src/hotspot/share/runtime/sharedRuntime.cpp b/src/hotspot/share/runtime/sharedRuntime.cpp index 5489735da39..bcb7f5488f5 100644 --- a/src/hotspot/share/runtime/sharedRuntime.cpp +++ b/src/hotspot/share/runtime/sharedRuntime.cpp @@ -3104,14 +3104,16 @@ void AdapterHandlerLibrary::create_native_wrapper(const methodHandle& method) { struct { double data[20]; } locs_buf; struct { double data[20]; } stubs_locs_buf; buffer.insts()->initialize_shared_locs((relocInfo*)&locs_buf, sizeof(locs_buf) / sizeof(relocInfo)); -#if defined(AARCH64) || defined(PPC64) +#if defined(AARCH64) // On AArch64 with ZGC and nmethod entry barriers, we need all oops to be // in the constant pool to ensure ordering between the barrier and oops // accesses. For native_wrappers we need a constant. - // On PPC64 the continuation enter intrinsic needs the constant pool for the compiled + buffer.initialize_consts_size(8); +#elif defined(PPC64) || defined(S390) + // On PPC64/S390 the continuation enter intrinsic needs the constant pool for the compiled // static java call that is resolved in the runtime. - if (PPC64_ONLY(method->is_continuation_enter_intrinsic() &&) true) { - buffer.initialize_consts_size(8 PPC64_ONLY(+ 24)); + if (method->is_continuation_enter_intrinsic()) { + buffer.initialize_consts_size(8 PPC64_ONLY(+ 24) S390_ONLY(+ 17)); } #endif buffer.stubs()->initialize_shared_locs((relocInfo*)&stubs_locs_buf, sizeof(stubs_locs_buf) / sizeof(relocInfo)); diff --git a/test/hotspot/jtreg/ProblemList.txt b/test/hotspot/jtreg/ProblemList.txt index a9f70fc97a4..e0005bfde07 100644 --- a/test/hotspot/jtreg/ProblemList.txt +++ b/test/hotspot/jtreg/ProblemList.txt @@ -64,7 +64,6 @@ compiler/floatingpoint/TestSubnormalDouble.java 8317810 generic-i586 compiler/codecache/CodeCacheFullCountTest.java 8332954 generic-all compiler/interpreter/Test6833129.java 8335266 generic-i586 -compiler/intrinsics/TestReturnOopSetForJFRWriteCheckpoint.java 8286300 linux-s390x compiler/c2/aarch64/TestStaticCallStub.java 8359963 generic-aarch64 @@ -105,7 +104,6 @@ runtime/ErrorHandling/MachCodeFramesInErrorFile.java 8313315 linux-ppc64le runtime/NMT/VirtualAllocCommitMerge.java 8309698 linux-s390x runtime/Thread/TestAlwaysPreTouchStacks.java 8383372 macosx-aarch64 -applications/ctw/modules/jdk_jfr.java 8286300 linux-s390x applications/jcstress/copy.java 8229852 linux-all containers/docker/TestJFREvents.java 8327723 linux-x64 diff --git a/test/jdk/ProblemList.txt b/test/jdk/ProblemList.txt index fcde1d9c01d..8879aa2e5b6 100644 --- a/test/jdk/ProblemList.txt +++ b/test/jdk/ProblemList.txt @@ -612,24 +612,6 @@ jdk/incubator/vector/LoadJsvmlTest.java 8305390 windows- # jdk_jfr -jdk/jfr/api/consumer/TestRecordingFileWrite.java 8286300 linux-s390x -jdk/jfr/api/consumer/streaming/TestCrossProcessStreaming.java 8286300 linux-s390x -jdk/jfr/api/consumer/streaming/TestFilledChunks.java 8286300 linux-s390x -jdk/jfr/api/consumer/streaming/TestRemovedChunks.java 8286300 linux-s390x -jdk/jfr/api/recording/misc/TestGetStreamWithFailure.java 8286300 linux-s390x -jdk/jfr/api/settings/TestSettingControl.java 8286300 linux-s390x -jdk/jfr/event/runtime/TestBackToBackSensitive.java 8286300 linux-s390x -jdk/jfr/event/runtime/TestSyncOnValueBasedClassEvent.java 8286300 linux-s390x -jdk/jfr/event/tracing/TestMultipleThreads.java 8286300 linux-s390x -jdk/jfr/event/tracing/TestTracedString.java 8286300 linux-s390x -jdk/jfr/javaagent/TestLoadedAgent.java 8286300 linux-s390x -jdk/jfr/javaagent/TestPremainAgent.java 8286300 linux-s390x -jdk/jfr/jmx/streaming/TestClose.java 8286300 linux-s390x -jdk/jfr/jmx/streaming/TestMaxSize.java 8286300 linux-s390x -jdk/jfr/jvm/TestChunkIntegrity.java 8286300 linux-s390x -jdk/jfr/jvm/TestJFRIntrinsic.java 8286300 linux-s390x -jdk/jfr/tool/TestDisassemble.java 8286300 linux-s390x -jdk/jfr/tool/TestScrub.java 8286300 linux-s390x jdk/jfr/event/compiler/TestCodeSweeper.java 8338127 generic-all jdk/jfr/event/oldobject/TestShenandoah.java 8342951 generic-all jdk/jfr/event/runtime/TestResidentSetSizeEvent.java 8309846 aix-ppc64 diff --git a/test/jdk/java/util/concurrent/tck/JSR166TestCase.java b/test/jdk/java/util/concurrent/tck/JSR166TestCase.java index 641fbf2e495..f1f32bee310 100644 --- a/test/jdk/java/util/concurrent/tck/JSR166TestCase.java +++ b/test/jdk/java/util/concurrent/tck/JSR166TestCase.java @@ -37,9 +37,7 @@ /* * @test id=default * @summary Conformance testing variant of JSR-166 tck tests. - * @library /test/lib * @build * - * @build jdk.test.lib.Platform * @modules java.management java.base/jdk.internal.util * @run junit/othervm/timeout=1000 JSR166TestCase */ @@ -48,9 +46,7 @@ * @test id=forkjoinpool-common-parallelism * @summary Test implementation details variant of JSR-166 * tck tests with ForkJoinPool common parallelism. - * @library /test/lib * @build * - * @build jdk.test.lib.Platform * @modules java.management java.base/jdk.internal.util * @run junit/othervm/timeout=1000 * --add-opens java.base/java.util.concurrent=ALL-UNNAMED @@ -72,9 +68,7 @@ * @summary Remaining test implementation details variant of * JSR-166 tck tests apart from ForkJoinPool common * parallelism. - * @library /test/lib * @build * - * @build jdk.test.lib.Platform * @modules java.management java.base/jdk.internal.util * @run junit/othervm/timeout=1000 * --add-opens java.base/java.util.concurrent=ALL-UNNAMED @@ -141,7 +135,6 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.regex.Pattern; -import jdk.test.lib.Platform; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestResult; @@ -631,13 +624,6 @@ public class JSR166TestCase extends TestCase { "SynchronousQueue20Test", "ReentrantReadWriteLock20Test" }; - - if (Platform.isS390x()) { - java20TestClassNames = new String[] { - "ForkJoinPool20Test", - }; - } - addNamedTestClasses(suite, java20TestClassNames); } From ab116d00a88046d662210539b4bc12db3a364c86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20H=C3=A4ssig?= Date: Thu, 9 Jul 2026 13:45:41 +0000 Subject: [PATCH 114/305] 8385945: Deprecate the CompilationMode flag Reviewed-by: dholmes, ayang --- src/hotspot/share/compiler/compiler_globals.hpp | 2 +- src/hotspot/share/runtime/arguments.cpp | 1 + .../hotspot/jtreg/runtime/CommandLine/VMDeprecatedOptions.java | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/hotspot/share/compiler/compiler_globals.hpp b/src/hotspot/share/compiler/compiler_globals.hpp index e1f8d9f8922..effe6cc0725 100644 --- a/src/hotspot/share/compiler/compiler_globals.hpp +++ b/src/hotspot/share/compiler/compiler_globals.hpp @@ -273,7 +273,7 @@ "mode if posssible") \ \ product(ccstr, CompilationMode, "default", \ - "Compilation modes: " \ + "(Deprecated) Compilation modes: " \ "default: normal tiered compilation; " \ "quick-only: C1-only mode; " \ "high-only: C2-only mode.") \ diff --git a/src/hotspot/share/runtime/arguments.cpp b/src/hotspot/share/runtime/arguments.cpp index 2804224ed01..269a8b39e6b 100644 --- a/src/hotspot/share/runtime/arguments.cpp +++ b/src/hotspot/share/runtime/arguments.cpp @@ -529,6 +529,7 @@ static SpecialFlag const special_jvm_flags[] = { { "DynamicDumpSharedSpaces", JDK_Version::jdk(18), JDK_Version::jdk(19), JDK_Version::undefined() }, { "RequireSharedSpaces", JDK_Version::jdk(18), JDK_Version::jdk(19), JDK_Version::undefined() }, { "UseSharedSpaces", JDK_Version::jdk(18), JDK_Version::jdk(19), JDK_Version::undefined() }, + { "CompilationMode", JDK_Version::jdk(28), JDK_Version::jdk(29), JDK_Version::jdk(30)}, // --- Deprecated alias flags (see also aliased_jvm_flags) - sorted by obsolete_in then expired_in: { "CreateMinidumpOnCrash", JDK_Version::jdk(9), JDK_Version::undefined(), JDK_Version::undefined() }, { "InitiatingHeapOccupancyPercent", JDK_Version::jdk(27), JDK_Version::jdk(28), JDK_Version::jdk(29) }, diff --git a/test/hotspot/jtreg/runtime/CommandLine/VMDeprecatedOptions.java b/test/hotspot/jtreg/runtime/CommandLine/VMDeprecatedOptions.java index 99c2d27f8d3..8c530936065 100644 --- a/test/hotspot/jtreg/runtime/CommandLine/VMDeprecatedOptions.java +++ b/test/hotspot/jtreg/runtime/CommandLine/VMDeprecatedOptions.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -58,6 +58,7 @@ public class VMDeprecatedOptions { // { , } // deprecated non-alias flags: {"AllowRedefinitionToAddDeleteMethods", "true"}, + {"CompilationMode", "default"}, // deprecated alias flags (see also aliased_jvm_flags): {"CreateMinidumpOnCrash", "false"} From 095cf06e734dad42ffec82292f23cf85bcfae62e Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Thu, 9 Jul 2026 16:02:22 +0000 Subject: [PATCH 115/305] 8387404: Make ClassLoaderData::oops_do inlineable Reviewed-by: coleenp, xpeng --- .../share/classfile/classLoaderData.cpp | 19 +------------------ .../share/classfile/classLoaderData.hpp | 5 +++-- .../classfile/classLoaderData.inline.hpp | 19 +++++++++++++++++++ 3 files changed, 23 insertions(+), 20 deletions(-) diff --git a/src/hotspot/share/classfile/classLoaderData.cpp b/src/hotspot/share/classfile/classLoaderData.cpp index d1ea9c09d4c..95e1ef3b877 100644 --- a/src/hotspot/share/classfile/classLoaderData.cpp +++ b/src/hotspot/share/classfile/classLoaderData.cpp @@ -289,19 +289,6 @@ void ClassLoaderData::verify_not_claimed(int claim) { } #endif -bool ClassLoaderData::try_claim(int claim) { - for (;;) { - int old_claim = AtomicAccess::load(&_claim); - if ((old_claim & claim) == claim) { - return false; - } - int new_claim = old_claim | claim; - if (AtomicAccess::cmpxchg(&_claim, old_claim, new_claim) == old_claim) { - return true; - } - } -} - void ClassLoaderData::demote_strong_roots() { // The oop handle area contains strong roots that the GC traces from. We are about // to demote them to strong native oops that the GC does *not* trace from. Conceptually, @@ -369,11 +356,7 @@ void ClassLoaderData::dec_keep_alive_ref_count() { } } -void ClassLoaderData::oops_do(OopClosure* f, int claim_value, bool clear_mod_oops) { - if (claim_value != ClassLoaderData::_claim_none && !try_claim(claim_value)) { - return; - } - +void ClassLoaderData::oops_do_slow(OopClosure* f, bool clear_mod_oops) { // Only clear modified_oops after the ClassLoaderData is claimed. if (clear_mod_oops) { clear_modified_oops(); diff --git a/src/hotspot/share/classfile/classLoaderData.hpp b/src/hotspot/share/classfile/classLoaderData.hpp index 64fcfb7519f..3a0a05126af 100644 --- a/src/hotspot/share/classfile/classLoaderData.hpp +++ b/src/hotspot/share/classfile/classLoaderData.hpp @@ -242,7 +242,7 @@ private: void verify_not_claimed(int claim) NOT_DEBUG_RETURN; bool claimed() const { return _claim != 0; } bool claimed(int claim) const { return (_claim & claim) == claim; } - bool try_claim(int claim); + inline bool try_claim(int claim); // Computes if the CLD is alive or not. This is safe to call in concurrent // contexts. @@ -305,7 +305,8 @@ private: void initialize_holder(Handle holder); - void oops_do(OopClosure* f, int claim_value, bool clear_modified_oops = false); + inline void oops_do(OopClosure* f, int claim_value, bool clear_modified_oops = false); + void oops_do_slow(OopClosure* f, bool clear_modified_oops); void classes_do(KlassClosure* klass_closure); Klass* klasses() { return _klasses; } diff --git a/src/hotspot/share/classfile/classLoaderData.inline.hpp b/src/hotspot/share/classfile/classLoaderData.inline.hpp index 4c4427b19e1..df29dca053b 100644 --- a/src/hotspot/share/classfile/classLoaderData.inline.hpp +++ b/src/hotspot/share/classfile/classLoaderData.inline.hpp @@ -85,4 +85,23 @@ inline ClassLoaderData* ClassLoaderData::class_loader_data(oop loader) { return loader_data; } +inline bool ClassLoaderData::try_claim(int claim) { + for (;;) { + int old_claim = AtomicAccess::load(&_claim); + if ((old_claim & claim) == claim) { + return false; + } + int new_claim = old_claim | claim; + if (AtomicAccess::cmpxchg(&_claim, old_claim, new_claim) == old_claim) { + return true; + } + } +} + +inline void ClassLoaderData::oops_do(OopClosure* f, int claim_value, bool clear_mod_oops) { + if (claim_value == _claim_none || try_claim(claim_value)) { + oops_do_slow(f, clear_mod_oops); + } +} + #endif // SHARE_CLASSFILE_CLASSLOADERDATA_INLINE_HPP From a230a6099e2ec1b2f8f35515964c8ccb7c1523ec Mon Sep 17 00:00:00 2001 From: Naoto Sato Date: Thu, 9 Jul 2026 16:28:56 +0000 Subject: [PATCH 116/305] 8387795: Remove hard coded set of locales in LocaleData Reviewed-by: jlu --- .../ResourceBundleGenerator.java | 40 +++++++++++++++++-- .../util/cldr/CLDRLocaleProviderAdapter.java | 6 ++- .../provider/JRELocaleProviderAdapter.java | 4 ++ .../provider/ResourceBundleBasedAdapter.java | 12 +++++- .../sun/util/resources/LocaleData.java | 21 +++++----- 5 files changed, 66 insertions(+), 17 deletions(-) diff --git a/make/jdk/src/classes/build/tools/cldrconverter/ResourceBundleGenerator.java b/make/jdk/src/classes/build/tools/cldrconverter/ResourceBundleGenerator.java index 84657ae94f0..8e9635ab519 100644 --- a/make/jdk/src/classes/build/tools/cldrconverter/ResourceBundleGenerator.java +++ b/make/jdk/src/classes/build/tools/cldrconverter/ResourceBundleGenerator.java @@ -29,6 +29,7 @@ import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.util.Arrays; +import java.util.Comparator; import java.util.Formatter; import java.util.HashSet; import java.util.HashMap; @@ -39,6 +40,7 @@ import java.util.Objects; import java.util.Set; import java.util.SortedSet; import java.util.stream.Collectors; +import static java.util.ResourceBundle.Control; class ResourceBundleGenerator implements BundleGenerator { // preferred timezones - keeping compatibility with JDK1.1 3 letter abbreviations @@ -69,6 +71,9 @@ class ResourceBundleGenerator implements BundleGenerator { // For duplicated values private static final String META_VALUE_PREFIX = "metaValue_"; + // locales in the base module + private final Set baseModuleLocales = new HashSet<>(); + @Override public void generateBundle(String packageName, String baseName, String localeID, Map map, BundleType type) throws IOException { @@ -80,8 +85,15 @@ class ResourceBundleGenerator implements BundleGenerator { return; } - // Assume that non-base resources go into jdk.localedata - if (!CLDRConverter.isBaseModule) { + if (CLDRConverter.isBaseModule) { + if (!localeID.equals("root")) { + baseModuleLocales.addAll( + Control.getControl(Control.FORMAT_DEFAULT) + .getCandidateLocales("", + Locale.forLanguageTag(CLDRConverter.toLanguageTag(localeID)))); + } + } else { + // Assume that non-base resources go into jdk.localedata dirName = dirName + File.separator + "ext"; packageName = packageName + ".ext"; } @@ -284,6 +296,7 @@ class ResourceBundleGenerator implements BundleGenerator { import java.util.HashMap; import java.util.Locale; import java.util.Map; + import java.util.Set; import sun.util.locale.provider.LocaleDataMetaInfo; import sun.util.locale.provider.LocaleProviderAdapter; @@ -296,6 +309,7 @@ class ResourceBundleGenerator implements BundleGenerator { out.printf(""" private static final Map parentLocalesMap = HashMap.newHashMap(%d); private static final Map languageAliasMap = HashMap.newHashMap(%d); + private static final Set baseModuleLocales; static final boolean nonlikelyScript = %s; // package access from CLDRLocaleProviderAdapter static { @@ -322,7 +336,23 @@ class ResourceBundleGenerator implements BundleGenerator { CLDRConverter.handlerSupplMeta.getLanguageAliasData().forEach((key, value) -> { out.printf(" languageAliasMap.put(\"%s\", \"%s\");\n", CLDRConverter.escape(key), CLDRConverter.escape(value)); }); - out.printf(" }\n\n"); + out.println(); + + // for baseModuleLocales + out.printf(" baseModuleLocales = Set.of(\n"); + out.printf(" %s", + baseModuleLocales.stream() + .map(Locale::toLanguageTag) + .sorted(Comparator.comparing(l -> l.equals("und") ? "" : l)) + .map(l -> switch(l) { + case "und" -> "Locale.ROOT"; + case "en" -> "Locale.ENGLISH"; + case "en-US" -> "Locale.US"; + default -> "Locale.forLanguageTag(\"" + l + "\")"; + }) + .collect(Collectors.joining(",\n "))); + out.printf("\n );"); + out.println("\n }\n"); // end of static initializer block. @@ -391,6 +421,10 @@ class ResourceBundleGenerator implements BundleGenerator { return parentLocalesMap; } + public Set baseModuleLocales() { + return baseModuleLocales; + } + // package access from CLDRLocaleProviderAdapter Map likelyScriptMap() { return CLDRMapHolder.likelyScriptMap; diff --git a/src/java.base/share/classes/sun/util/cldr/CLDRLocaleProviderAdapter.java b/src/java.base/share/classes/sun/util/cldr/CLDRLocaleProviderAdapter.java index 573187ba3d0..1e80bce3839 100644 --- a/src/java.base/share/classes/sun/util/cldr/CLDRLocaleProviderAdapter.java +++ b/src/java.base/share/classes/sun/util/cldr/CLDRLocaleProviderAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -288,6 +288,10 @@ public class CLDRLocaleProviderAdapter extends JRELocaleProviderAdapter { || langtags.contains(getEquivalentLoc(locale).toLanguageTag()); } + public Set baseModuleLocales() { + return baseMetaInfo.baseModuleLocales(); + } + /** * Returns the canonical ID for the given ID */ diff --git a/src/java.base/share/classes/sun/util/locale/provider/JRELocaleProviderAdapter.java b/src/java.base/share/classes/sun/util/locale/provider/JRELocaleProviderAdapter.java index 7b8b3b06eb3..2d6d95b509f 100644 --- a/src/java.base/share/classes/sun/util/locale/provider/JRELocaleProviderAdapter.java +++ b/src/java.base/share/classes/sun/util/locale/provider/JRELocaleProviderAdapter.java @@ -488,4 +488,8 @@ public class JRELocaleProviderAdapter extends LocaleProviderAdapter implements R "th-TH-TH".equals(oldname) || "no-NO-NY".equals(oldname); } + + public Set baseModuleLocales() { + return Set.of(Locale.ROOT); + } } diff --git a/src/java.base/share/classes/sun/util/locale/provider/ResourceBundleBasedAdapter.java b/src/java.base/share/classes/sun/util/locale/provider/ResourceBundleBasedAdapter.java index 613b1ee5158..48d9b832b13 100644 --- a/src/java.base/share/classes/sun/util/locale/provider/ResourceBundleBasedAdapter.java +++ b/src/java.base/share/classes/sun/util/locale/provider/ResourceBundleBasedAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,6 +27,8 @@ package sun.util.locale.provider; import java.util.List; import java.util.Locale; +import java.util.Set; + import sun.util.resources.LocaleData; /** @@ -40,5 +42,11 @@ public interface ResourceBundleBasedAdapter { /** * candidate locales customization */ - public List getCandidateLocales(String baseName, Locale locale); + List getCandidateLocales(String baseName, Locale locale); + + /** + * Returns the locales whose resource bundles are resolved from + * the java.base module for this adapter. + */ + Set baseModuleLocales(); } diff --git a/src/java.base/share/classes/sun/util/resources/LocaleData.java b/src/java.base/share/classes/sun/util/resources/LocaleData.java index 20e8e0f8fe9..884f9610ca7 100644 --- a/src/java.base/share/classes/sun/util/resources/LocaleData.java +++ b/src/java.base/share/classes/sun/util/resources/LocaleData.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -45,7 +45,6 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.ResourceBundle; -import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.spi.ResourceBundleProvider; import sun.util.locale.provider.JRELocaleProviderAdapter; @@ -180,9 +179,6 @@ public class LocaleData { private static class LocaleDataStrategy implements Bundles.Strategy { private static final LocaleDataStrategy INSTANCE = new LocaleDataStrategy(); - // TODO: avoid hard-coded Locales - private static final Set JAVA_BASE_LOCALES - = Set.of(Locale.ROOT, Locale.ENGLISH, Locale.US, Locale.of("en", "US", "POSIX")); private LocaleDataStrategy() { } @@ -202,11 +198,8 @@ public class LocaleData { String key = baseName + '-' + locale.toLanguageTag(); List candidates = CANDIDATES_MAP.get(key); if (candidates == null) { - LocaleProviderAdapter.Type type = baseName.contains(DOTCLDR) ? CLDR : JRE; - LocaleProviderAdapter adapter = LocaleProviderAdapter.forType(type); - candidates = adapter instanceof ResourceBundleBasedAdapter rbba ? - rbba.getCandidateLocales(baseName, locale) : - defaultControl.getCandidateLocales(baseName, locale); + var adapter = getAdapter(baseName); + candidates = adapter.getCandidateLocales(baseName, locale); // Weed out Locales which are known to have no resource bundles int lastDot = baseName.lastIndexOf('.'); @@ -227,7 +220,13 @@ public class LocaleData { } boolean inJavaBaseModule(String baseName, Locale locale) { - return JAVA_BASE_LOCALES.contains(locale); + return getAdapter(baseName).baseModuleLocales().contains(locale); + } + + private static ResourceBundleBasedAdapter getAdapter(String baseName) { + return (ResourceBundleBasedAdapter)(baseName.contains(DOTCLDR) ? + LocaleProviderAdapter.forType(CLDR) : + LocaleProviderAdapter.forType(JRE)); } @Override From 7544c91a81858b8738e06a611f93bd95a7a8197c Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Thu, 9 Jul 2026 16:46:29 +0000 Subject: [PATCH 117/305] 8387961: Shenandoah: Rework marked objects loop prefetch Reviewed-by: xpeng, wkemper, kdnilsen --- .../gc/shenandoah/shenandoahHeap.inline.hpp | 86 ++++++------------- .../gc/shenandoah/shenandoah_globals.hpp | 5 -- 2 files changed, 26 insertions(+), 65 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.inline.hpp index 69eaf1589d2..b3c847cadaf 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahHeap.inline.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahHeap.inline.hpp @@ -45,6 +45,7 @@ #include "gc/shenandoah/shenandoahHeapRegion.inline.hpp" #include "gc/shenandoah/shenandoahHeapRegionSet.inline.hpp" #include "gc/shenandoah/shenandoahMarkingContext.inline.hpp" +#include "gc/shenandoah/shenandoahPrefetch.inline.hpp" #include "gc/shenandoah/shenandoahThreadLocalData.hpp" #include "gc/shenandoah/shenandoahWorkGroup.hpp" #include "oops/compressedOops.inline.hpp" @@ -515,74 +516,35 @@ inline void ShenandoahHeap::marked_object_iterate(ShenandoahHeapRegion* region, template inline void ShenandoahHeap::marked_object_iterate(ShenandoahHeapRegion* region, T* cl, HeapWord* limit) { - assert(! region->is_humongous_continuation(), "no humongous continuation regions here"); + assert(!region->is_humongous_continuation(), "no humongous continuation regions here"); + assert(limit <= region->top(), "sanity"); ShenandoahMarkingContext* const ctx = marking_context(); HeapWord* tams = ctx->top_at_mark_start(region); - - size_t skip_bitmap_delta = 1; - HeapWord* start = region->bottom(); - HeapWord* end = MIN2(tams, region->end()); - - // Step 1. Scan below the TAMS based on bitmap data. HeapWord* limit_bitmap = MIN2(limit, tams); + // Step 1. Scan below the TAMS based on bitmap data. // Try to scan the initial candidate. If the candidate is above the TAMS, it would // fail the subsequent "< limit_bitmap" checks, and fall through to Step 2. - HeapWord* cb = ctx->get_next_marked_addr(start, end); + HeapWord* cb = ctx->get_next_marked_addr(region->bottom(), limit_bitmap); + while (cb < limit_bitmap) { + assert (cb < tams, "only objects below TAMS here: " PTR_FORMAT " (" PTR_FORMAT ")", p2i(cb), p2i(tams)); + assert (cb < limit, "only objects below limit here: " PTR_FORMAT " (" PTR_FORMAT ")", p2i(cb), p2i(limit)); + oop obj = cast_to_oop(cb); + assert(oopDesc::is_oop(obj), "sanity"); + assert(ctx->is_marked(obj), "object expected to be marked"); - intx dist = ShenandoahMarkScanPrefetch; - if (dist > 0) { - // Batched scan that prefetches the oop data, anticipating the access to - // either header, oop field, or forwarding pointer. Not that we cannot - // touch anything in oop, while it still being prefetched to get enough - // time for prefetch to work. This is why we try to scan the bitmap linearly, - // disregarding the object size. However, since we know forwarding pointer - // precedes the object, we can skip over it. Once we cannot trust the bitmap, - // there is no point for prefetching the oop contents, as oop->size() will - // touch it prematurely. - - // No variable-length arrays in standard C++, have enough slots to fit - // the prefetch distance. - static const int SLOT_COUNT = 256; - guarantee(dist <= SLOT_COUNT, "adjust slot count"); - HeapWord* slots[SLOT_COUNT]; - - int avail; - do { - avail = 0; - for (int c = 0; (c < dist) && (cb < limit_bitmap); c++) { - Prefetch::read(cb, oopDesc::mark_offset_in_bytes()); - slots[avail++] = cb; - cb += skip_bitmap_delta; - if (cb < limit_bitmap) { - cb = ctx->get_next_marked_addr(cb, limit_bitmap); - } - } - - for (int c = 0; c < avail; c++) { - assert (slots[c] < tams, "only objects below TAMS here: " PTR_FORMAT " (" PTR_FORMAT ")", p2i(slots[c]), p2i(tams)); - assert (slots[c] < limit, "only objects below limit here: " PTR_FORMAT " (" PTR_FORMAT ")", p2i(slots[c]), p2i(limit)); - oop obj = cast_to_oop(slots[c]); - assert(oopDesc::is_oop(obj), "sanity"); - assert(ctx->is_marked(obj), "object expected to be marked"); - cl->do_object(obj); - } - } while (avail > 0); - } else { - while (cb < limit_bitmap) { - assert (cb < tams, "only objects below TAMS here: " PTR_FORMAT " (" PTR_FORMAT ")", p2i(cb), p2i(tams)); - assert (cb < limit, "only objects below limit here: " PTR_FORMAT " (" PTR_FORMAT ")", p2i(cb), p2i(limit)); - oop obj = cast_to_oop(cb); - assert(oopDesc::is_oop(obj), "sanity"); - assert(ctx->is_marked(obj), "object expected to be marked"); - cl->do_object(obj); - cb += skip_bitmap_delta; - if (cb < limit_bitmap) { - cb = ctx->get_next_marked_addr(cb, limit_bitmap); - } + // Compute the next object address and initiate prefetches for it, + // while we are processing current object. + constexpr size_t skip_bitmap_delta = 1; + cb += skip_bitmap_delta; + if (cb < limit_bitmap) { + cb = ctx->get_next_marked_addr(cb, limit_bitmap); } + ShenandoahPrefetch::prefetch(cast_to_oop(cb)); + + cl->do_object(obj); } // Step 2. Accurate size-based traversal, happens past the TAMS. @@ -595,9 +557,13 @@ inline void ShenandoahHeap::marked_object_iterate(ShenandoahHeapRegion* region, oop obj = cast_to_oop(cs); assert(oopDesc::is_oop(obj), "sanity"); assert(ctx->is_marked(obj), "object expected to be marked"); - size_t size = ShenandoahForwarding::size(obj); + + // Compute the next object address and initiate prefetches for it, + // while we are processing current object. + cs += ShenandoahForwarding::size(obj); + ShenandoahPrefetch::prefetch(cast_to_oop(cs)); + cl->do_object(obj); - cs += size; } } diff --git a/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp b/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp index 793b2f3b6d1..d76348b030a 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp @@ -476,11 +476,6 @@ "evacuated.") \ range(0, 100) \ \ - product(intx, ShenandoahMarkScanPrefetch, 32, EXPERIMENTAL, \ - "How many objects to prefetch ahead when traversing mark bitmaps."\ - "Set to 0 to disable prefetching.") \ - range(0, 256) \ - \ product(uintx, ShenandoahMarkLoopStride, 1000, EXPERIMENTAL, \ "How many items to process during one marking iteration before " \ "checking for cancellation, yielding, etc. Larger values improve "\ From 4d3723b802bf582572e9d71d1ff1660e68637e04 Mon Sep 17 00:00:00 2001 From: Vladimir Ivanov Date: Thu, 9 Jul 2026 16:59:16 +0000 Subject: [PATCH 118/305] 8382536: C2: sharpen_type_after_if: assert(val->find_edge(con) > 0) failed: mismatch Reviewed-by: chagedorn, mchevalier --- src/hotspot/share/opto/parse2.cpp | 32 +++++++----- .../types/TestSubTypeCheckConstantCastII.java | 51 +++++++++++++++++++ 2 files changed, 71 insertions(+), 12 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/types/TestSubTypeCheckConstantCastII.java diff --git a/src/hotspot/share/opto/parse2.cpp b/src/hotspot/share/opto/parse2.cpp index 8ac4cf47558..9cb20cfcd00 100644 --- a/src/hotspot/share/opto/parse2.cpp +++ b/src/hotspot/share/opto/parse2.cpp @@ -1749,6 +1749,10 @@ static bool match_type_check(PhaseGVN& gvn, Node* con, const Type* tcon, Node* val, const Type* tval, Node** obj, const TypeOopPtr** cast_type) { // out-parameters + assert(tcon->singleton(), "not a constant: %s", Type::str(tcon)); + assert(tcon == gvn.type(con), "mismatch: %s != %s", Type::str(tcon), Type::str(gvn.type(con))); + assert(tval == gvn.type(val), "mismatch: %s != %s", Type::str(tval), Type::str(gvn.type(val))); + // Look for opportunities to sharpen the type of a node whose klass is compared with a constant klass. // The constant klass being tested against can come from many bytecode instructions (implicitly or explicitly), // and also from profile data used by speculative casts. @@ -1783,14 +1787,14 @@ static bool match_type_check(PhaseGVN& gvn, // Region // \ ConI ConI // \ | / - // val -> Phi ConI <- con - // \ / - // CmpI - // | - // Bool [btest] - // | + // val -> Phi ConI|CastII <- con + // \ / + // CmpI + // | + // Bool [btest] + // | // - if (tval->isa_int() && val->is_Phi() && val->in(0)->as_Region()->is_diamond()) { + if (tcon->isa_int() && val->is_Phi() && val->in(0)->as_Region()->is_diamond()) { RegionNode* diamond = val->in(0)->as_Region(); IfNode* if1 = diamond->in(1)->in(0)->as_If(); BoolNode* b1 = if1->in(1)->isa_Bool(); @@ -1799,12 +1803,16 @@ static bool match_type_check(PhaseGVN& gvn, b1->_test._test == BoolTest::ne, "%d", b1->_test._test); ProjNode* success_proj = if1->proj_out(b1->_test._test == BoolTest::eq ? 1 : 0); - int idx = diamond->find_edge(success_proj); - assert(idx == 1 || idx == 2, ""); - Node* vcon = val->in(idx); + int success_idx = diamond->find_edge(success_proj); + assert(success_idx == 1 || success_idx == 2, ""); + assert(val->req() == 3, "not a diamond"); - if ((btest == BoolTest::eq && vcon == con) || (btest == BoolTest::ne && vcon != con)) { - assert(val->find_edge(con) > 0, "mismatch"); + // gen_instanceof() emits 1 on success and 0 on failure. + // Check whether current comparison selects the success value. + const Type* success_tval = gvn.type(val->in(success_idx)); + assert(success_tval->isa_int(), "not an int: %s", Type::str(success_tval)); + if ((btest == BoolTest::eq && tcon == success_tval) || + (btest == BoolTest::ne && tcon->join(success_tval)->empty())) { SubTypeCheckNode* sub = b1->in(1)->as_SubTypeCheck(); Node* obj_or_subklass = sub->in(SubTypeCheckNode::ObjOrSubKlass); Node* superklass = sub->in(SubTypeCheckNode::SuperKlass); diff --git a/test/hotspot/jtreg/compiler/types/TestSubTypeCheckConstantCastII.java b/test/hotspot/jtreg/compiler/types/TestSubTypeCheckConstantCastII.java new file mode 100644 index 00000000000..d3ce317ed30 --- /dev/null +++ b/test/hotspot/jtreg/compiler/types/TestSubTypeCheckConstantCastII.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8382536 + * @summary C2: sharpen_type_after_if: assert(val->find_edge(con) > 0) failed: mismatch + * + * @run main/othervm -Xcomp -XX:CompileCommand=compileonly,${test.main.class}::test ${test.main.class} + */ +package compiler.types; + +public class TestSubTypeCheckConstantCastII { + static class A {} + + static boolean isInstanceOfA(Object obj) { + return (obj instanceof A); + } + + static void test(boolean b, Object obj) { + if (b) { + return; + } + // b == false + if (b != isInstanceOfA(obj)) {} + } + + public static void main(String[] args) { + test(true, new A()); + } +} From cd7b5fc7a5c294c8572f644d4bbf7451f8cfbec2 Mon Sep 17 00:00:00 2001 From: Patrick Fontanilla Date: Thu, 9 Jul 2026 22:34:25 +0000 Subject: [PATCH 119/305] 8386872: Test gc/shenandoah/generational/TestOldGrowthTriggers still fails intermittently Reviewed-by: wkemper, kdnilsen --- .../generational/TestOldGrowthTriggers.java | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/test/hotspot/jtreg/gc/shenandoah/generational/TestOldGrowthTriggers.java b/test/hotspot/jtreg/gc/shenandoah/generational/TestOldGrowthTriggers.java index 2af784fd034..fe3c8a5a476 100644 --- a/test/hotspot/jtreg/gc/shenandoah/generational/TestOldGrowthTriggers.java +++ b/test/hotspot/jtreg/gc/shenandoah/generational/TestOldGrowthTriggers.java @@ -41,9 +41,11 @@ import jdk.test.lib.process.OutputAnalyzer; public class TestOldGrowthTriggers { public static void makeOldAllocations() { - // Expect most of the BitSet entries placed into array to be promoted, and most will eventually become garbage within old + // Keep the majority of BitSet entries (5/8, 960) long-lived so they promote and grow old generation + // well past the old GC trigger threshold. A smaller long-lived set can fall just short and only + // intermittently trigger an old GC, so don't reduce the array size or the promoted fraction. - final int ArraySize = 1024; // 1K entries + final int ArraySize = 1536; // 1536 entries (1024 + 512) final int RefillIterations = 128; BitSet[] array = new BitSet[ArraySize]; @@ -57,8 +59,10 @@ public class TestOldGrowthTriggers { int replaceIndex = i; int deriveIndex = i-1; + // 3/8 entries are replaced each pass to trigger young gcs. + // 5/8 entries are never touched, so they age each cycle. switch (i & 0x7) { - case 0,1,2 -> { + case 0,1 -> { // creates new BitSet, releases old BitSet, // create ephemeral data while computing BitSet result = (BitSet) array[deriveIndex].clone(); @@ -67,12 +71,12 @@ public class TestOldGrowthTriggers { } array[replaceIndex] = result; } - case 3,4 -> { + case 2 -> { // creates new BitSet, releases old BitSet BitSet result = (BitSet) array[deriveIndex].clone(); array[replaceIndex] = result; } - case 5,6,7 -> { + default -> { // do nothing, let all objects in the array age to increase pressure on old generation } } @@ -110,6 +114,8 @@ public class TestOldGrowthTriggers { "-XX:ShenandoahMinOldGenGrowthRemainingHeapPercent=100", "-XX:ShenandoahGuaranteedYoungGCInterval=0", "-XX:ShenandoahGuaranteedOldGCInterval=0", + "-XX:ShenandoahGenerationalMinTenuringAge=2", + "-XX:ShenandoahGenerationalMaxTenuringAge=2", "-XX:-UseCompactObjectHeaders" ); @@ -127,6 +133,8 @@ public class TestOldGrowthTriggers { "-XX:ShenandoahMinOldGenGrowthRemainingHeapPercent=100", "-XX:ShenandoahGuaranteedYoungGCInterval=0", "-XX:ShenandoahGuaranteedOldGCInterval=0", + "-XX:ShenandoahGenerationalMinTenuringAge=2", + "-XX:ShenandoahGenerationalMaxTenuringAge=2", "-XX:+UseCompactObjectHeaders" ); } From 05be7e5439ceee17419ede343799dd4367b34fc5 Mon Sep 17 00:00:00 2001 From: Xiaolong Peng Date: Fri, 10 Jul 2026 02:40:40 +0000 Subject: [PATCH 120/305] 8387806: Shenandoah: Reduce allocation-path contention from ShenandoahAllocRate byte accounting Reviewed-by: shade, wkemper, kdnilsen --- .../gc/shenandoah/shenandoahAllocRate.hpp | 51 +++++-- .../shenandoah/shenandoahAllocRate.inline.hpp | 59 ++++---- .../shenandoah/shenandoahStripedCounter.cpp | 38 ++++++ .../shenandoah/shenandoahStripedCounter.hpp | 79 +++++++++++ .../shenandoahStripedCounter.inline.hpp | 74 ++++++++++ .../test_shenandoahAllocationRate.cpp | 128 ++++++++++++++++++ .../test_shenandoahStripedCounter.cpp | 118 ++++++++++++++++ 7 files changed, 513 insertions(+), 34 deletions(-) create mode 100644 src/hotspot/share/gc/shenandoah/shenandoahStripedCounter.cpp create mode 100644 src/hotspot/share/gc/shenandoah/shenandoahStripedCounter.hpp create mode 100644 src/hotspot/share/gc/shenandoah/shenandoahStripedCounter.inline.hpp create mode 100644 test/hotspot/gtest/gc/shenandoah/test_shenandoahStripedCounter.cpp diff --git a/src/hotspot/share/gc/shenandoah/shenandoahAllocRate.hpp b/src/hotspot/share/gc/shenandoah/shenandoahAllocRate.hpp index 24221e504fd..ca94b91200a 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahAllocRate.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahAllocRate.hpp @@ -25,7 +25,7 @@ #ifndef SHARE_GC_SHENANDOAH_SHENANDOAHALLOCRATE_HPP #define SHARE_GC_SHENANDOAH_SHENANDOAHALLOCRATE_HPP -#include "gc/shenandoah/shenandoahPadding.hpp" +#include "gc/shenandoah/shenandoahStripedCounter.hpp" #include "gc/shenandoah/shenandoahWeightedSeq.hpp" #include "runtime/atomic.hpp" #include "runtime/mutex.hpp" @@ -111,12 +111,26 @@ class ShenandoahAllocRate { static constexpr size_t ALLOC_SAMPLE_MAX = G; PaddedMonitor _sample_lock; - shenandoah_padding(0); - Atomic _allocated_bytes_since_last_sample; - shenandoah_padding(1); - Atomic _minimum_sample_size; // bytes, read by mutator, updated by gc + ShenandoahStripedCounter _unsampled; + // Packed minimum_sample_size and log_per_stripe_threshold for one alloc-path load. + Atomic _sample_params; jlong _last_sample_time; + static uint64_t encode_sample_params(const uint32_t minimum_sample_size, const uint32_t log_per_stripe_threshold) { + return (static_cast(log_per_stripe_threshold) << 32) | + minimum_sample_size; + } + + static size_t decode_min_sample_size(const uint64_t params) { + return static_cast(params); + } + + static uint32_t decode_log_per_stripe_threshold(const uint64_t params) { + return static_cast(params >> 32); + } + + void maybe_take_sample(size_t minimum_sample_size, size_t striped_unsampled); + ShenandoahWeightedSeq _baseline; ShenandoahWeightedSeq _recent; ShenandoahWeightedSeq _momentary; @@ -127,22 +141,19 @@ public: const uint recent_window_size = ShenandoahRecentAllocRateSampleWindow, const uint momentary_window_size = ShenandoahMomentaryAllocRateSampleWindow) : _sample_lock(Mutex::nosafepoint - 2, "ShenandoahAllocSample_lock", true) - , _allocated_bytes_since_last_sample(0) - , _minimum_sample_size(minimum_sample_size) , _last_sample_time(Clock::elapsed_counter()) , _baseline(baseline_window_size) , _recent(recent_window_size) , _momentary(momentary_window_size) { + set_minimum_sample_size(minimum_sample_size); } // Update minimum sample size based on the given available bytes void update_minimum_sample_size(size_t available); - // Set minimum sample size in bytes - void set_minimum_sample_size(const size_t minimum_sample_size) { - _minimum_sample_size.store_relaxed(minimum_sample_size); - } + // Set minimum sample size and its per-stripe trigger shift. + void set_minimum_sample_size(size_t minimum_sample_size); // Indicate that this many bytes have been allocated (by the mutator). void allocated(size_t allocated_bytes); @@ -173,6 +184,24 @@ public: } private: + // Log2 of the per-stripe trigger threshold. + uint32_t log_per_stripe_threshold_for(size_t minimum_sample_size) const; + + // Fast, lock-free: did this add carry the calling thread's stripe across a per-stripe threshold + // multiple? The threshold is a power of two, so a crossing is a change in the bits above it. + static bool striped_threshold_exceeded(size_t striped_unsampled, size_t previous_striped_unsampled, uint32_t log_per_stripe_threshold) { + return (striped_unsampled >> log_per_stripe_threshold) > (previous_striped_unsampled >> log_per_stripe_threshold); + } + + // Whether the unsampled bytes are still below the sampling floor. Must be called under the sample + // lock: drains only happen under the lock, so reading the live stripe value and sum() here filters + // out false positives from a concurrent drain that already reset the counter. + bool unsampled_below_floor(size_t minimum_sample_size, size_t striped_unsampled) const { + assert(_sample_lock.owned_by_self(), "Caller must hold lock"); + return (_unsampled.num_stripes() > 1 && _unsampled.current_stripe_value() < striped_unsampled) || + _unsampled.sum() < minimum_sample_size; + } + // Record the sample under the sample lock void take_sample(jlong now, jlong elapsed, size_t unsampled); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahAllocRate.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahAllocRate.inline.hpp index 9ffad0d312c..e317721cd7b 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahAllocRate.inline.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahAllocRate.inline.hpp @@ -27,8 +27,10 @@ #include "gc/shenandoah/shenandoahAllocRate.hpp" +#include "gc/shenandoah/shenandoahStripedCounter.inline.hpp" #include "gc/shenandoah/shenandoahUtils.hpp" #include "logging/log.hpp" +#include "utilities/powerOfTwo.hpp" inline size_t ShenandoahAnticipatedConsumption::baseline_consumption() const { @@ -56,40 +58,56 @@ void ShenandoahAllocRate::update_minimum_sample_size(const size_t availab } template -void ShenandoahAllocRate::allocated(const size_t allocated_bytes) { - size_t unsampled = _allocated_bytes_since_last_sample.add_then_fetch(allocated_bytes, memory_order_relaxed); - const size_t minimum_sample_size = _minimum_sample_size.load_relaxed(); - if (unsampled < minimum_sample_size) { - // Not enough to sample yet - return; - } +uint32_t ShenandoahAllocRate::log_per_stripe_threshold_for(const size_t minimum_sample_size) const { + // Floor-log2 of the per-stripe share. Clamps to 0 for a 1-byte trigger. + const int log_threshold = log2i(minimum_sample_size) - (int) _unsampled.log_num_stripes(); + return log_threshold > 0 ? (uint32_t) log_threshold : 0u; +} +template +void ShenandoahAllocRate::set_minimum_sample_size(const size_t minimum_sample_size) { + assert(minimum_sample_size > 0, "minimum sample size must be non-zero"); + _sample_params.store_relaxed(encode_sample_params(checked_cast(minimum_sample_size), log_per_stripe_threshold_for(minimum_sample_size))); +} + +template +void ShenandoahAllocRate::maybe_take_sample(const size_t minimum_sample_size, const size_t striped_unsampled) { if (!_sample_lock.try_lock()) { - // Another thread has the lock and will take the sample + // Another thread has the lock and will take the sample. return; } - unsampled = _allocated_bytes_since_last_sample.load_relaxed(); - if (unsampled < minimum_sample_size) { - // Another thread has sampled and reset the allocated bytes under the lock + if (unsampled_below_floor(minimum_sample_size, striped_unsampled)) { + // Either another thread already sampled and drained, or this thread's stripe crossed its share + // while the aggregate is still short (skewed distribution). Wait for more. _sample_lock.unlock(); return; } - const jlong now = Clock::elapsed_counter(); const jlong elapsed = now - _last_sample_time; - if (elapsed <= 0) { - // Avoid sampling nonsense allocation rates + // Avoid sampling nonsense allocation rates. _sample_lock.unlock(); return; } - - take_sample(now, elapsed, unsampled); - + take_sample(now, elapsed, _unsampled.drain()); _sample_lock.unlock(); } +template +void ShenandoahAllocRate::allocated(const size_t allocated_bytes) { + const size_t striped_unsampled = _unsampled.add(allocated_bytes); + const size_t previous_striped_unsampled = striped_unsampled - allocated_bytes; + + const uint64_t params = _sample_params.load_relaxed(); + const uint32_t log_per_stripe_threshold = decode_log_per_stripe_threshold(params); + + // Re-arm the trigger at every per-stripe threshold crossing. + if (striped_threshold_exceeded(striped_unsampled, previous_striped_unsampled, log_per_stripe_threshold)) { + maybe_take_sample(decode_min_sample_size(params), striped_unsampled); + } +} + template void ShenandoahAllocRate::force_update() { if (!_sample_lock.try_lock()) { @@ -97,7 +115,6 @@ void ShenandoahAllocRate::force_update() { return; } - const size_t unsampled = _allocated_bytes_since_last_sample.load_relaxed(); const jlong now = Clock::elapsed_counter(); const jlong elapsed = now - _last_sample_time; @@ -107,7 +124,7 @@ void ShenandoahAllocRate::force_update() { return; } - take_sample(now, elapsed, unsampled); + take_sample(now, elapsed, _unsampled.drain()); _sample_lock.unlock(); } @@ -118,10 +135,6 @@ void ShenandoahAllocRate::take_sample(jlong now, jlong elapsed, size_t un _last_sample_time = now; - // We are recording this sample, deduct it from the counter. It may be increased - // concurrently by other threads outside the lock, so we still use an atomic access. - _allocated_bytes_since_last_sample.sub_then_fetch(unsampled, memory_order_relaxed); - const double timestamp = static_cast(_last_sample_time) / Clock::elapsed_frequency(); const double rate_seconds = static_cast(unsampled) * Clock::elapsed_frequency() / elapsed; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahStripedCounter.cpp b/src/hotspot/share/gc/shenandoah/shenandoahStripedCounter.cpp new file mode 100644 index 00000000000..d6e55d06248 --- /dev/null +++ b/src/hotspot/share/gc/shenandoah/shenandoahStripedCounter.cpp @@ -0,0 +1,38 @@ +/* + * 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 "gc/shenandoah/shenandoahStripedCounter.hpp" +#include "memory/padded.inline.hpp" +#include "runtime/os.hpp" +#include "utilities/globalDefinitions.hpp" +#include "utilities/powerOfTwo.hpp" + +ShenandoahStripedCounter::ShenandoahStripedCounter() + : _num_stripes(round_down_power_of_2((uint32_t) MAX2(os::processor_count(), 1))) + , _stripe_mask(_num_stripes - 1) + , _log_num_stripes(log2i_exact(_num_stripes)) { + _stripes = PaddedArray, mtGC>::create_unfreeable(_num_stripes); +} + +ShenandoahStripedCounter::~ShenandoahStripedCounter() { } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahStripedCounter.hpp b/src/hotspot/share/gc/shenandoah/shenandoahStripedCounter.hpp new file mode 100644 index 00000000000..ad1086005a6 --- /dev/null +++ b/src/hotspot/share/gc/shenandoah/shenandoahStripedCounter.hpp @@ -0,0 +1,79 @@ +/* + * 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. + * + */ + +#ifndef SHARE_GC_SHENANDOAH_SHENANDOAHSTRIPEDCOUNTER_HPP +#define SHARE_GC_SHENANDOAH_SHENANDOAHSTRIPEDCOUNTER_HPP + +#include "memory/allocation.hpp" +#include "memory/padded.hpp" +#include "runtime/atomic.hpp" +#include "utilities/globalDefinitions.hpp" + +// A contended-counter optimized for many concurrent writers and infrequent reads. +// Each writer accumulates into a stripe chosen by its thread hash, each on its own cache line to +// avoid false sharing. Stripes are shared when live writers outnumber stripes (num_stripes <= CPU +// count). The value of the counter is always sum(stripes). +// +// Reads (sum) are approximate under concurrent writes and exact when quiescent. +// This counter is monotonic per epoch: add() only increases it; drain() atomically reads and resets +// to begin a new epoch (0), preserving concurrent adds that race with the drain. +class ShenandoahStripedCounter : public CHeapObj { + typedef PaddedEnd> PaddedCounter; + + PaddedCounter* _stripes; // _num_stripes entries + // Number of stripes: a power of two, rounded down from the CPU count. Keeping it a power of two + // lets current_stripe() map a thread hash into range with a mask (& _stripe_mask) instead of a + // modulo on the hot path. + uint32_t const _num_stripes; + uint32_t const _stripe_mask; // _num_stripes - 1 + uint32_t const _log_num_stripes; + + // The stripe this thread uses. + uint32_t current_stripe() const; + +public: + ShenandoahStripedCounter(); + ~ShenandoahStripedCounter(); + + // Add `bytes` to the current stripe of the counter and return the resulting total of the current stripe. + size_t add(size_t bytes); + + // Current total of all stripes of the counter. No reset. + // Approximate under concurrent writes. + size_t sum() const; + + // Current value of the calling thread's own stripe. O(1), no reset. + size_t current_stripe_value() const; + + // Read the total and atomically reset it to zero, returning the amount consumed. + // Concurrent adds racing with the drain accumulate toward the next epoch rather than being lost. + size_t drain(); + + // Number of stripes (a power of two, <= CPU count), and its base-2 log. Exposed so a caller can + // scale a threshold to a per-stripe share with a shift (>> log_num_stripes) instead of a divide. + uint32_t num_stripes() const; + uint32_t log_num_stripes() const; +}; + +#endif // SHARE_GC_SHENANDOAH_SHENANDOAHSTRIPEDCOUNTER_HPP diff --git a/src/hotspot/share/gc/shenandoah/shenandoahStripedCounter.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahStripedCounter.inline.hpp new file mode 100644 index 00000000000..58c40089324 --- /dev/null +++ b/src/hotspot/share/gc/shenandoah/shenandoahStripedCounter.inline.hpp @@ -0,0 +1,74 @@ +/* + * 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. + * + */ + +#ifndef SHARE_GC_SHENANDOAH_SHENANDOAHSTRIPEDCOUNTER_INLINE_HPP +#define SHARE_GC_SHENANDOAH_SHENANDOAHSTRIPEDCOUNTER_INLINE_HPP + +#include "gc/shenandoah/shenandoahStripedCounter.hpp" + +#include "runtime/thread.hpp" + +inline uint32_t ShenandoahStripedCounter::current_stripe() const { + if (_num_stripes == 1u) { + return 0u; + } + // Per-thread probe into [0, _num_stripes). Hashing the thread pointer spreads threads across + // stripes. This is a pure, stable function of (thread pointer, _num_stripes) + const uintptr_t t = (uintptr_t) Thread::current(); + return (uint32_t) ((t ^ (t >> 20) ^ (t >> 9)) & _stripe_mask); +} + +inline uint32_t ShenandoahStripedCounter::num_stripes() const { + return _num_stripes; +} + +inline uint32_t ShenandoahStripedCounter::log_num_stripes() const { + return _log_num_stripes; +} + +inline size_t ShenandoahStripedCounter::add(const size_t bytes) { + return _stripes[current_stripe()].add_then_fetch(bytes, memory_order_relaxed); +} + +inline size_t ShenandoahStripedCounter::sum() const { + size_t total = 0; + for (uint32_t i = 0; i < _num_stripes; i++) { + total += _stripes[i].load_relaxed(); + } + return total; +} + +inline size_t ShenandoahStripedCounter::current_stripe_value() const { + return _stripes[current_stripe()].load_relaxed(); +} + +inline size_t ShenandoahStripedCounter::drain() { + size_t total = 0; + for (uint32_t i = 0; i < _num_stripes; i++) { + total += _stripes[i].exchange(0, memory_order_relaxed); + } + return total; +} + +#endif // SHARE_GC_SHENANDOAH_SHENANDOAHSTRIPEDCOUNTER_INLINE_HPP diff --git a/test/hotspot/gtest/gc/shenandoah/test_shenandoahAllocationRate.cpp b/test/hotspot/gtest/gc/shenandoah/test_shenandoahAllocationRate.cpp index af066657377..a4169ff6ba6 100644 --- a/test/hotspot/gtest/gc/shenandoah/test_shenandoahAllocationRate.cpp +++ b/test/hotspot/gtest/gc/shenandoah/test_shenandoahAllocationRate.cpp @@ -26,6 +26,9 @@ #include "gc/shared/gc_globals.hpp" #include "gc/shenandoah/shenandoahAllocRate.inline.hpp" +#include "gc/shenandoah/shenandoahStripedCounter.inline.hpp" +#include "runtime/atomic.hpp" +#include "threadHelper.inline.hpp" class ShenandoahMockClock { public: @@ -120,6 +123,131 @@ TEST_VM_F(ShenandoahAllocationRateTest, accelerated_consumption_momentary_spike) EXPECT_EQ(consumption.accelerated_consumption(), 0UL); } +TEST_VM_F(ShenandoahAllocationRateTest, event_driven_sampling_single_dominant_allocator) { + // Single mutator: one stripe allocates, other stripes stay empty. + ShenandoahStripedCounter stripes; + if (stripes.num_stripes() == 1) { + // Regression requires multiple stripes. + return; + } + + ShenandoahAllocRate rate(MINIMUM_SAMPLE_SIZE, BASELINE_SAMPLES, RECENT_SAMPLES, MOMENTARY_SAMPLES); + // Multiple epochs prove the allocation-path trigger re-fires without force_update(). + constexpr size_t alloc_size = 64; + constexpr size_t epochs = 4; + for (size_t allocated = 0; allocated < MINIMUM_SAMPLE_SIZE * epochs; allocated += alloc_size) { + allocate(rate, alloc_size); + } + + // Old one-shot trigger left the average at zero until force_update(). + EXPECT_GT(rate.weighted_average(), 0.0); +} + +TEST_VM_F(ShenandoahAllocationRateTest, event_driven_sampling_rearms_when_floor_lowered) { + // Lowering the floor must re-arm a stripe that crossed the old share. + constexpr size_t high_floor = 1 * M; + constexpr size_t low_floor = 1024; + constexpr size_t alloc_size = 64; + + ShenandoahAllocRate rate(high_floor, BASELINE_SAMPLES, RECENT_SAMPLES, MOMENTARY_SAMPLES); + + // Accumulate below the high floor, but above the later lowered share. + constexpr size_t phase1_bytes = high_floor / 4; + for (size_t allocated = 0; allocated < phase1_bytes; allocated += alloc_size) { + allocate(rate, alloc_size); + } + EXPECT_DOUBLE_EQ(rate.weighted_average(), 0.0); // nothing drained yet + + // A GC lowers the floor. + rate.set_minimum_sample_size(low_floor); + + // New crossings under the lowered floor must sample without force_update(). + for (size_t allocated = 0; allocated < low_floor * 16; allocated += alloc_size) { + allocate(rate, alloc_size); + } + + EXPECT_GT(rate.weighted_average(), 0.0); +} + +// Concurrent multi-threaded sampling. Many threads drive allocated() past the aggregate floor at +// the same time, so distinct JavaThreads spread across stripes and stay hot simultaneously. This is +// the regime the sampling guard is written for: contended try_lock (multiple threads cross their +// per-stripe share at once, only one wins the lock), multi-stripe sum() aggregation (the floor is +// reached by several occupied stripes, not one), and the drain-race clause (one thread's add() +// captures a stripe value that another thread drains before the first takes the lock). +class ConcurrentAllocators { +public: + static constexpr int kThreads = 8; + static constexpr size_t kPerThreadEpochs = 500; + static constexpr size_t kAllocSize = 64; + // Every thread allocates this many bytes; the grand total spans many minimum-sample-size epochs. + static constexpr size_t kPerThreadBytes = MINIMUM_SAMPLE_SIZE * kPerThreadEpochs; +}; + +TEST_VM_F(ShenandoahAllocationRateTest, event_driven_sampling_concurrent_allocators) { + ShenandoahAllocRate rate(MINIMUM_SAMPLE_SIZE, BASELINE_SAMPLES, RECENT_SAMPLES, MOMENTARY_SAMPLES); + + auto worker = [&](Thread*, int) { + for (size_t allocated = 0; allocated < ConcurrentAllocators::kPerThreadBytes; + allocated += ConcurrentAllocators::kAllocSize) { + rate.allocated(ConcurrentAllocators::kAllocSize); + } + }; + TestThreadGroup ttg(worker, ConcurrentAllocators::kThreads); + ttg.doit(); + ttg.join(); + + // No force_update() was called: every sample came from the contended allocation path. Across + // thousands of epochs driven by all threads, sampling must have fired and drained repeatedly. + EXPECT_GT(rate.weighted_average(), 0.0); +} + +// Concurrent skew: a few threads hold their stripes just below the per-stripe share and keep them +// hot (spinning at the barrier), while a heavy thread pushes the aggregate over the floor. The +// sample can then only be taken because sum() aggregates the heavy stripe with the held stripes -- +// exercising the multi-stripe floor crossing, not a single dominant stripe. +class ConcurrentSkew { +public: + static constexpr int kHolderThreads = 6; + static constexpr size_t kHeavyEpochs = 300; + static constexpr size_t kAllocSize = 64; +}; + +TEST_VM_F(ShenandoahAllocationRateTest, event_driven_sampling_concurrent_skew) { + ShenandoahStripedCounter stripes; + if (stripes.num_stripes() == 1) { + // A multi-stripe aggregate crossing is only meaningful with more than one stripe. + return; + } + + ShenandoahAllocRate rate(MINIMUM_SAMPLE_SIZE, BASELINE_SAMPLES, RECENT_SAMPLES, MOMENTARY_SAMPLES); + + // Each holder adds just under the per-stripe share once, then stays live for the whole run, so + // several stripes remain simultaneously occupied below their individual share. Their adds never + // cross a share alone, but they contend on the counter and feed sum(). + Atomic stop(false); + const size_t per_stripe_share = MINIMUM_SAMPLE_SIZE / stripes.num_stripes(); + const size_t holder_target = per_stripe_share > 2 ? per_stripe_share - 1 : 1; + auto holder = [&](Thread*, int) { + rate.allocated(holder_target); + while (!stop.load_relaxed()) { /* keep the thread (and its stripe) live */ } + }; + TestThreadGroup holders(holder, ConcurrentSkew::kHolderThreads); + holders.doit(); + + // Heavy stream on the main thread's own stripe. Its crossings, added to the held stripes, take + // sum() over the floor; the re-armed trigger must sample every epoch off the allocation path. + const size_t heavy_bytes = MINIMUM_SAMPLE_SIZE * ConcurrentSkew::kHeavyEpochs; + for (size_t allocated = 0; allocated < heavy_bytes; allocated += ConcurrentSkew::kAllocSize) { + allocate(rate, ConcurrentSkew::kAllocSize); + } + + stop.store_relaxed(true); + holders.join(); + + EXPECT_GT(rate.weighted_average(), 0.0); +} + TEST_VM_F(ShenandoahAllocationRateTest, accelerated_consumption_accelerating) { ShenandoahAllocRate rate(256, BASELINE_SAMPLES, RECENT_SAMPLES, MOMENTARY_SAMPLES); for (uint i = 0; i < BASELINE_SAMPLES; ++i) { diff --git a/test/hotspot/gtest/gc/shenandoah/test_shenandoahStripedCounter.cpp b/test/hotspot/gtest/gc/shenandoah/test_shenandoahStripedCounter.cpp new file mode 100644 index 00000000000..db4933a9264 --- /dev/null +++ b/test/hotspot/gtest/gc/shenandoah/test_shenandoahStripedCounter.cpp @@ -0,0 +1,118 @@ +/* + * 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 "gc/shenandoah/shenandoahStripedCounter.inline.hpp" +#include "runtime/atomic.hpp" +#include "threadHelper.inline.hpp" +#include "unittest.hpp" + +// Single thread: every add() maps to the same stripe, so add() returns the running total and +// sum()/drain() are exact. +TEST_VM(ShenandoahStripedCounter, single_thread_exact) { + ShenandoahStripedCounter c; + size_t expected = 0; + for (size_t i = 1; i <= 1000; i++) { + const size_t got = c.add(i); + expected += i; + // A lone writer owns one stripe, so its stripe total is the whole total. + EXPECT_EQ(got, expected); + EXPECT_EQ(c.sum(), expected); + } + // drain() returns everything and resets to zero; a second drain sees nothing. + EXPECT_EQ(c.drain(), expected); + EXPECT_EQ(c.sum(), (size_t) 0); + EXPECT_EQ(c.drain(), (size_t) 0); +} + +// Draining mid-stream starts a fresh epoch, and sum()/drain() stay exact across the boundary. +TEST_VM(ShenandoahStripedCounter, drain_epochs) { + ShenandoahStripedCounter c; + size_t expected = 0; + for (size_t i = 0; i < 500; i++) { + c.add(7); + expected += 7; + } + EXPECT_EQ(c.sum(), expected); + // Drain (starts a new epoch), then keep adding. + EXPECT_EQ(c.drain(), expected); + expected = 0; + for (size_t i = 0; i < 500; i++) { + c.add(13); + expected += 13; + } + EXPECT_EQ(c.sum(), expected); + EXPECT_EQ(c.drain(), expected); +} + +// Multi-threaded stress. N threads each add a fixed number of bytes; when quiescent, sum() must +// equal the grand total, and the periodic-drain variant must lose nothing (every byte lands in +// exactly one drain or the final sum). Distinct JavaThreads make current_stripe() actually spread +// writers across stripes. +class StripedCounterStress { +public: + static constexpr int kThreads = 8; + static constexpr size_t kPerThreadAdds = 20000; + static constexpr size_t kBytesPerAdd = 8; + static constexpr size_t kGrandTotal = (size_t) kThreads * kPerThreadAdds * kBytesPerAdd; +}; + +TEST_VM(ShenandoahStripedCounter, mt_quiescent_sum_exact) { + ShenandoahStripedCounter c; + auto worker = [&](Thread*, int) { + for (size_t i = 0; i < StripedCounterStress::kPerThreadAdds; i++) { + c.add(StripedCounterStress::kBytesPerAdd); + } + }; + TestThreadGroup ttg(worker, StripedCounterStress::kThreads); + ttg.doit(); + ttg.join(); + // All writers quiesced: sum() is now exact and must account for every byte. + EXPECT_EQ(c.sum(), StripedCounterStress::kGrandTotal); + EXPECT_EQ(c.drain(), StripedCounterStress::kGrandTotal); + EXPECT_EQ(c.sum(), (size_t) 0); +} + +TEST_VM(ShenandoahStripedCounter, mt_concurrent_drain_loses_nothing) { + ShenandoahStripedCounter c; + Atomic drained(0); + Atomic done(0); + auto worker = [&](Thread*, int) { + for (size_t i = 0; i < StripedCounterStress::kPerThreadAdds; i++) { + c.add(StripedCounterStress::kBytesPerAdd); + } + done.add_then_fetch(1); + }; + TestThreadGroup ttg(worker, StripedCounterStress::kThreads); + ttg.doit(); + // Drain concurrently with the adds; each drain moves bytes to a new epoch without losing them. + while (done.load_relaxed() < StripedCounterStress::kThreads) { + drained.add_then_fetch(c.drain()); + } + ttg.join(); + // Final drain sweeps up whatever raced the last concurrent drain. + drained.add_then_fetch(c.drain()); + // Every byte added landed in exactly one drain. + EXPECT_EQ(drained.load_relaxed(), StripedCounterStress::kGrandTotal); + EXPECT_EQ(c.sum(), (size_t) 0); +} From 3354ad3b6f599c603d55447d4f747e98020e773a Mon Sep 17 00:00:00 2001 From: Amit Kumar Date: Fri, 10 Jul 2026 06:05:42 +0000 Subject: [PATCH 121/305] 8386586: [s390x] TestSyncOnValueBasedClassEvent.java fails due to incorrect branch Reviewed-by: aph, hdhiman --- src/hotspot/cpu/s390/macroAssembler_s390.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/hotspot/cpu/s390/macroAssembler_s390.cpp b/src/hotspot/cpu/s390/macroAssembler_s390.cpp index 5d5c7570e27..6eb14452401 100644 --- a/src/hotspot/cpu/s390/macroAssembler_s390.cpp +++ b/src/hotspot/cpu/s390/macroAssembler_s390.cpp @@ -6178,7 +6178,7 @@ void MacroAssembler::fast_lock(Register basic_lock, Register obj, Register temp1 if (DiagnoseSyncOnValueBasedClasses != 0) { load_klass(temp1, obj); z_tm(Address(temp1, Klass::misc_flags_offset()), KlassFlags::_misc_is_value_based_class); - z_brne(slow); + z_brnaz(slow); } // First we need to check if the lock-stack has room for pushing the object reference. From 3e2366e7f5d29be03c8d5ddb4f62e5e1f7185550 Mon Sep 17 00:00:00 2001 From: Albert Mingkun Yang Date: Fri, 10 Jul 2026 06:36:00 +0000 Subject: [PATCH 122/305] 8387742: Reclaim CodeCache nmethods more promptly Reviewed-by: tschatzl, shade --- src/hotspot/share/gc/g1/g1CollectedHeap.cpp | 7 +- src/hotspot/share/gc/g1/g1CollectedHeap.hpp | 1 - src/hotspot/share/gc/g1/g1ConcurrentMark.cpp | 5 +- src/hotspot/share/gc/g1/g1FullCollector.cpp | 3 + .../share/gc/parallel/psParallelCompact.cpp | 2 +- src/hotspot/share/gc/serial/serialFullGC.cpp | 1 + src/hotspot/share/gc/serial/serialHeap.cpp | 1 - .../hotspot/jtreg/gc/TestCodeCacheUnload.java | 177 ++++++++++++++++++ ...stCodeCacheUnloadDuringConcurrentMark.java | 115 ++++++++++++ 9 files changed, 302 insertions(+), 10 deletions(-) create mode 100644 test/hotspot/jtreg/gc/TestCodeCacheUnload.java create mode 100644 test/hotspot/jtreg/gc/g1/TestCodeCacheUnloadDuringConcurrentMark.java diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp index 3c41133e572..f60ce9b15b4 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp @@ -868,7 +868,7 @@ void G1CollectedHeap::prepare_for_mutator_after_full_collection(size_t allocatio // Rebuild the code root lists for each region rebuild_code_roots(); - finish_codecache_marking_cycle(); + CodeCache::arm_all_nmethods(); start_new_collection_set(); _allocator->init_mutator_alloc_regions(); @@ -3342,8 +3342,3 @@ void G1CollectedHeap::start_codecache_marking_cycle_if_inactive(bool concurrent_ CodeCache::arm_all_nmethods(); } } - -void G1CollectedHeap::finish_codecache_marking_cycle() { - CodeCache::on_gc_marking_cycle_finish(); - CodeCache::arm_all_nmethods(); -} diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.hpp b/src/hotspot/share/gc/g1/g1CollectedHeap.hpp index cb466a5e120..672dea9b7b0 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.hpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.hpp @@ -953,7 +953,6 @@ public: void fill_with_dummy_object(HeapWord* start, HeapWord* end, bool zap) override; static void start_codecache_marking_cycle_if_inactive(bool concurrent_mark_start); - static void finish_codecache_marking_cycle(); // The shared block offset table array. G1BlockOffsetTable* bot() const { return _bot; } diff --git a/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp b/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp index 233901c30f8..73a697f8c51 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp @@ -24,6 +24,7 @@ #include "classfile/classLoaderData.hpp" #include "classfile/classLoaderDataGraph.hpp" +#include "code/codeCache.hpp" #include "cppstdlib/new.hpp" #include "gc/g1/g1BarrierSet.hpp" #include "gc/g1/g1BatchedTask.hpp" @@ -1378,6 +1379,8 @@ void G1ConcurrentMark::remark() { if (mark_finished) { weak_refs_work(); + CodeCache::on_gc_marking_cycle_finish(); + // Unload Klasses, String, Code Cache, etc. if (ClassUnloadingWithConcurrentMark) { G1CMIsAliveClosure is_alive(this); @@ -1446,7 +1449,7 @@ void G1ConcurrentMark::remark() { // Completely reset the marking state (except bitmaps) since marking completed. reset_at_marking_complete(); - G1CollectedHeap::finish_codecache_marking_cycle(); + CodeCache::arm_all_nmethods(); { GCTraceTime(Debug, gc, phases) debug("Report Object Count", _gc_timer_cm); diff --git a/src/hotspot/share/gc/g1/g1FullCollector.cpp b/src/hotspot/share/gc/g1/g1FullCollector.cpp index c5af4a8220b..1e838b344b7 100644 --- a/src/hotspot/share/gc/g1/g1FullCollector.cpp +++ b/src/hotspot/share/gc/g1/g1FullCollector.cpp @@ -23,6 +23,7 @@ */ #include "classfile/classLoaderDataGraph.hpp" +#include "code/codeCache.hpp" #include "cppstdlib/new.hpp" #include "gc/g1/g1CollectedHeap.hpp" #include "gc/g1/g1FullCollector.inline.hpp" @@ -330,6 +331,8 @@ void G1FullCollector::phase1_mark_live_objects() { assert(marker(0)->task_queue()->is_empty(), "Should be no oops on the stack"); } + CodeCache::on_gc_marking_cycle_finish(); + { GCTraceTime(Debug, gc, phases) debug("Phase 1: Flush Mark Stats Cache", scope()->timer()); for (uint i = 0; i < workers(); i++) { diff --git a/src/hotspot/share/gc/parallel/psParallelCompact.cpp b/src/hotspot/share/gc/parallel/psParallelCompact.cpp index ff757f205a2..777b734c59e 100644 --- a/src/hotspot/share/gc/parallel/psParallelCompact.cpp +++ b/src/hotspot/share/gc/parallel/psParallelCompact.cpp @@ -644,7 +644,6 @@ void PSParallelCompact::post_compact() GCTraceTime(Info, gc, phases) tm("Post Compact", &_gc_timer); ParCompactionManager::remove_all_shadow_regions(); - CodeCache::on_gc_marking_cycle_finish(); CodeCache::arm_all_nmethods(); // Need to clear claim bits for the next full-gc (marking and adjust-pointers). @@ -1216,6 +1215,7 @@ void PSParallelCompact::marking_phase(ParallelOldTracer *gc_tracer) { // This is the point where the entire marking should have completed. ParCompactionManager::verify_all_marking_stack_empty(); + CodeCache::on_gc_marking_cycle_finish(); { GCTraceTime(Debug, gc, phases) tm("Weak Processing", &_gc_timer); diff --git a/src/hotspot/share/gc/serial/serialFullGC.cpp b/src/hotspot/share/gc/serial/serialFullGC.cpp index 13532dea07d..a88a0878305 100644 --- a/src/hotspot/share/gc/serial/serialFullGC.cpp +++ b/src/hotspot/share/gc/serial/serialFullGC.cpp @@ -512,6 +512,7 @@ void SerialFullGC::phase1_mark(bool clear_all_softrefs) { // This is the point where the entire marking should have completed. assert(_marking_stack.is_empty(), "Marking should have completed"); + CodeCache::on_gc_marking_cycle_finish(); { GCTraceTime(Debug, gc, phases) tm_m("Weak Processing", gc_timer()); diff --git a/src/hotspot/share/gc/serial/serialHeap.cpp b/src/hotspot/share/gc/serial/serialHeap.cpp index 3de562e886d..eb2bed109b5 100644 --- a/src/hotspot/share/gc/serial/serialHeap.cpp +++ b/src/hotspot/share/gc/serial/serialHeap.cpp @@ -589,7 +589,6 @@ void SerialHeap::do_full_collection(bool clear_all_soft_refs) { gc_timer->register_gc_end(); gc_tracer->report_gc_end(gc_timer->gc_end(), gc_timer->time_partitions()); - CodeCache::on_gc_marking_cycle_finish(); CodeCache::arm_all_nmethods(); COMPILER2_PRESENT(DerivedPointerTable::update_pointers()); diff --git a/test/hotspot/jtreg/gc/TestCodeCacheUnload.java b/test/hotspot/jtreg/gc/TestCodeCacheUnload.java new file mode 100644 index 00000000000..03c857dba1e --- /dev/null +++ b/test/hotspot/jtreg/gc/TestCodeCacheUnload.java @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package gc; + +/* + * @test id=serial + * @summary Tests that one full GC unloads a freshly not-entrant nmethod. + * @requires vm.gc.Serial + * @requires vm.compiler1.enabled + * @requires vm.opt.ClassUnloading != false + * @requires vm.opt.MethodFlushing != false + * @library /test/lib / + * @modules java.base/jdk.internal.misc + * java.management + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions + * -XX:+WhiteBoxAPI -Xbatch -XX:-BackgroundCompilation + * -XX:+UseSerialGC gc.TestCodeCacheUnload + */ + +/* + * @test id=parallel + * @summary Tests that one full GC unloads a freshly not-entrant nmethod. + * @requires vm.gc.Parallel + * @requires vm.compiler1.enabled + * @requires vm.opt.ClassUnloading != false + * @requires vm.opt.MethodFlushing != false + * @library /test/lib / + * @modules java.base/jdk.internal.misc + * java.management + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions + * -XX:+WhiteBoxAPI -Xbatch -XX:-BackgroundCompilation + * -XX:+UseParallelGC gc.TestCodeCacheUnload + */ + +/* + * @test id=g1 + * @summary Tests that one full GC unloads a freshly not-entrant nmethod. + * @requires vm.gc.G1 + * @requires vm.compiler1.enabled + * @requires vm.opt.ClassUnloading != false + * @requires vm.opt.MethodFlushing != false + * @library /test/lib / + * @modules java.base/jdk.internal.misc + * java.management + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions + * -XX:+WhiteBoxAPI -Xbatch -XX:-BackgroundCompilation + * -XX:+UseG1GC gc.TestCodeCacheUnload + */ + +/* + * @test id=shenandoah + * @summary Tests that one full GC unloads a freshly not-entrant nmethod. + * @requires vm.gc.Shenandoah + * @requires vm.compiler1.enabled + * @requires vm.opt.ClassUnloading != false + * @requires vm.opt.MethodFlushing != false + * @library /test/lib / + * @modules java.base/jdk.internal.misc + * java.management + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions + * -XX:+WhiteBoxAPI -Xbatch -XX:-BackgroundCompilation + * -XX:+UseShenandoahGC gc.TestCodeCacheUnload + */ + +/* + * @test id=z + * @summary Tests that one full GC unloads a freshly not-entrant nmethod. + * @requires vm.gc.Z + * @requires vm.compiler1.enabled + * @requires vm.opt.ClassUnloading != false + * @requires vm.opt.MethodFlushing != false + * @library /test/lib / + * @modules java.base/jdk.internal.misc + * java.management + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions + * -XX:+WhiteBoxAPI -Xbatch -XX:-BackgroundCompilation + * -XX:+UseZGC gc.TestCodeCacheUnload + */ + +import java.lang.reflect.Method; + +import jdk.test.lib.dcmd.JMXExecutor; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.whitebox.WhiteBox; + +public class TestCodeCacheUnload { + private static final WhiteBox WB = WhiteBox.getWhiteBox(); + + public static class Target { + public static int test(int value) { + return value + 1; + } + } + + private static void compileAndMakeNotEntrant() throws Exception { + Method method = Target.class.getDeclaredMethod("test", int.class); + + method.invoke(null, 1); + if (!WB.enqueueMethodForCompilation(method, 1 /* compLevel */)) { + throw new AssertionError("Failed to enqueue target for compilation"); + } + while (WB.isMethodQueuedForCompilation(method)) { + Thread.sleep(50); + } + if (!WB.isMethodCompiled(method)) { + throw new AssertionError("Target is not compiled"); + } + + int deoptimized = WB.deoptimizeMethod(method); + if (deoptimized == 0) { + throw new AssertionError("No target nmethod was made not-entrant"); + } + } + + private static int countNotEntrantEntries() { + OutputAnalyzer output = new JMXExecutor().execute("Compiler.codelist"); + String target = "gc.TestCodeCacheUnload$Target.test"; + int result = 0; + + for (String line : output.asLines()) { + if (!line.contains(target)) { + continue; + } + + System.out.println("Found codelist entry: " + line); + String[] parts = line.trim().split("\\s+"); + int codeState = Integer.parseInt(parts[2]); + if (codeState == 1 /* not_entrant */) { + result++; + } + } + + return result; + } + + public static void main(String[] args) throws Exception { + compileAndMakeNotEntrant(); + WB.fullGC(); + + int notEntrantEntries = countNotEntrantEntries(); + System.out.println("Target not-entrant entries after 1 full GC: " + notEntrantEntries); + if (notEntrantEntries != 0) { + throw new AssertionError("Expected one full GC to unload the not-entrant nmethod"); + } + } +} diff --git a/test/hotspot/jtreg/gc/g1/TestCodeCacheUnloadDuringConcurrentMark.java b/test/hotspot/jtreg/gc/g1/TestCodeCacheUnloadDuringConcurrentMark.java new file mode 100644 index 00000000000..0c2d473b021 --- /dev/null +++ b/test/hotspot/jtreg/gc/g1/TestCodeCacheUnloadDuringConcurrentMark.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package gc.g1; + +/* + * @test TestCodeCacheUnloadDuringConcurrentMark + * @summary Tests that G1 concurrent marking unloads a freshly not-entrant nmethod. + * @requires vm.gc.G1 + * @requires vm.compiler1.enabled + * @requires vm.opt.ClassUnloading != false + * @requires vm.opt.ClassUnloadingWithConcurrentMark != false + * @requires vm.opt.MethodFlushing != false + * @library /test/lib + * @modules java.base/jdk.internal.misc + * java.management + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions + * -XX:+WhiteBoxAPI -Xbatch -XX:-BackgroundCompilation + * -XX:+UseG1GC + * -XX:+ClassUnloadingWithConcurrentMark + * gc.g1.TestCodeCacheUnloadDuringConcurrentMark + */ + +import java.lang.reflect.Method; + +import jdk.test.lib.dcmd.JMXExecutor; +import jdk.test.whitebox.WhiteBox; + +public class TestCodeCacheUnloadDuringConcurrentMark { + private static final WhiteBox WB = WhiteBox.getWhiteBox(); + + static class Target { + public static int test(int value) { + return value + 1; + } + } + + private static void compileAndMakeNotEntrant(Method method) throws Exception { + Target.test(1); + if (!WB.enqueueMethodForCompilation(method, 1 /* compLevel */)) { + throw new AssertionError("Failed to enqueue target for compilation"); + } + while (WB.isMethodQueuedForCompilation(method)) { + Thread.sleep(50); + } + if (!WB.isMethodCompiled(method)) { + throw new AssertionError("Target is not compiled"); + } + + int deoptimized = WB.deoptimizeMethod(method); + if (deoptimized == 0) { + throw new AssertionError("No target nmethod was made not-entrant"); + } + } + + private static int countNotEntrantEntries() { + String target = TestCodeCacheUnloadDuringConcurrentMark.class.getName() + "$Target.test"; + int result = 0; + + for (String line : new JMXExecutor().execute("Compiler.codelist", true).asLines()) { + if (!line.contains(target)) { + continue; + } + + System.out.println("Found codelist entry: " + line); + String[] parts = line.trim().split("\\s+"); + int codeState = Integer.parseInt(parts[2]); + if (codeState == 1 /* not_entrant */) { + result++; + } + } + + return result; + } + + public static void main(String[] args) throws Exception { + compileAndMakeNotEntrant(Target.class.getDeclaredMethod("test", int.class)); + + int notEntrantEntries = countNotEntrantEntries(); + System.out.println("Target not-entrant entries before concurrent mark: " + notEntrantEntries); + if (notEntrantEntries == 0) { + throw new AssertionError("Expected a not-entrant target nmethod before concurrent mark"); + } + + WB.g1RunConcurrentGC(); + + notEntrantEntries = countNotEntrantEntries(); + System.out.println("Target not-entrant entries after concurrent mark: " + notEntrantEntries); + if (notEntrantEntries != 0) { + throw new AssertionError("Expected concurrent mark to unload the not-entrant target nmethod"); + } + } +} From 978dfecb6545166aee93a8e68c9c82535b0cb3e2 Mon Sep 17 00:00:00 2001 From: Vladimir Petko Date: Fri, 10 Jul 2026 07:07:20 +0000 Subject: [PATCH 123/305] 8387580: [S390x] OpenJDK build crashes with SIGSEGV in HashMap::resize() Reviewed-by: amitkumar, hdhiman --- src/hotspot/cpu/s390/templateTable_s390.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hotspot/cpu/s390/templateTable_s390.cpp b/src/hotspot/cpu/s390/templateTable_s390.cpp index 1da24c0378c..1db9c54aef5 100644 --- a/src/hotspot/cpu/s390/templateTable_s390.cpp +++ b/src/hotspot/cpu/s390/templateTable_s390.cpp @@ -1055,7 +1055,7 @@ void TemplateTable::lstore() { void TemplateTable::fstore() { transition(ftos, vtos); locals_index(Z_R1_scratch); - __ freg2mem_opt(Z_ftos, faddress(_masm, Z_R1_scratch)); + __ freg2mem_opt(Z_ftos, faddress(_masm, Z_R1_scratch), false); } void TemplateTable::dstore() { @@ -3506,7 +3506,7 @@ void TemplateTable::fast_xaccess(TosState state) { __ verify_oop(Z_tos); break; case ftos: - __ mem2freg_opt(Z_ftos, field); + __ mem2freg_opt(Z_ftos, field, false); break; default: ShouldNotReachHere(); From 7295b8aa2cdb3b47126986239a24488520816d20 Mon Sep 17 00:00:00 2001 From: Artur Barashev Date: Fri, 10 Jul 2026 14:27:29 +0000 Subject: [PATCH 124/305] 8386953: sun/security/ssl/CertificateCompression/CompressedCertMsgCache.java failing Reviewed-by: mullan --- .../ssl/CertificateCompression/CompressedCertMsgCache.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/jdk/sun/security/ssl/CertificateCompression/CompressedCertMsgCache.java b/test/jdk/sun/security/ssl/CertificateCompression/CompressedCertMsgCache.java index 42e9701c6d0..be0f0154e32 100644 --- a/test/jdk/sun/security/ssl/CertificateCompression/CompressedCertMsgCache.java +++ b/test/jdk/sun/security/ssl/CertificateCompression/CompressedCertMsgCache.java @@ -52,7 +52,7 @@ import jdk.test.lib.security.CertificateBuilder; * java.base/sun.security.util * @library /javax/net/ssl/templates * /test/lib - * @run main/othervm CompressedCertMsgCache + * @run main/othervm -Djdk.tls.server.newSessionTicketCount=0 CompressedCertMsgCache */ public class CompressedCertMsgCache extends SSLSocketTemplate { From 6eccdd862aa27c64a5c2c41b220edcfca390fd10 Mon Sep 17 00:00:00 2001 From: Artur Barashev Date: Fri, 10 Jul 2026 14:33:11 +0000 Subject: [PATCH 125/305] 8387578: Test sun/security/ssl/SSLSessionImpl/ResumeChecksServer.java failed: Existing session was used: FAIL Reviewed-by: mullan --- .../security/ssl/SSLSessionImpl/ResumeChecksServer.java | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/test/jdk/sun/security/ssl/SSLSessionImpl/ResumeChecksServer.java b/test/jdk/sun/security/ssl/SSLSessionImpl/ResumeChecksServer.java index d1918aab7f1..87c032728dc 100644 --- a/test/jdk/sun/security/ssl/SSLSessionImpl/ResumeChecksServer.java +++ b/test/jdk/sun/security/ssl/SSLSessionImpl/ResumeChecksServer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -93,6 +93,9 @@ public class ResumeChecksServer extends SSLContextTemplate { System.err.println("firstSession.getCreationTime() = " + firstSession.getCreationTime()); + // Sleep 100ms between 2 connections to avoid test flakiness. + Thread.sleep(100); + long secondStartTime = System.currentTimeMillis(); secondSession = c.test(); @@ -128,7 +131,8 @@ public class ResumeChecksServer extends SSLContextTemplate { case SIGNATURE_SCHEME: case LOCAL_CERTS: // fail if a new session is not created - if (secondSession.getCreationTime() < secondStartTime) { + if (secondSession.getCreationTime() == + firstSession.getCreationTime()) { throw new AssertionError("Existing session was used: FAIL"); } System.out.println("secondSession not resumed: PASS"); From 119fe211c8fb04afc5459cca7508b3066dfb1f08 Mon Sep 17 00:00:00 2001 From: Lawrence Andrews Date: Fri, 10 Jul 2026 16:43:32 +0000 Subject: [PATCH 126/305] 8388001: Test java/awt/Frame/PackTwiceTest.java fails because the frame title is displayed as 'PackTwiceTest TestFrame' instead of 'TestFrame' Reviewed-by: azvegint, prr --- test/jdk/java/awt/Frame/PackTwiceTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/jdk/java/awt/Frame/PackTwiceTest.java b/test/jdk/java/awt/Frame/PackTwiceTest.java index 63cd20612f0..ee948665d15 100644 --- a/test/jdk/java/awt/Frame/PackTwiceTest.java +++ b/test/jdk/java/awt/Frame/PackTwiceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -36,7 +36,7 @@ import java.awt.TextField; public class PackTwiceTest { public static void main(String[] args) throws Exception { String INSTRUCTIONS = """ - 1. You would see a Frame titled 'TestFrame' + 1. You would see a Frame titled 'PackTwiceTest TestFrame' 2. The Frame displays a text as below: 'I am a lengthy sentence...can you see me?' 3. If you can see the full text without resizing the frame From c0a3082cf1a84962eba3c3b1b48f98e1f9b3fc64 Mon Sep 17 00:00:00 2001 From: Evgeny Astigeevich Date: Fri, 10 Jul 2026 17:29:07 +0000 Subject: [PATCH 127/305] 8388008: AArch64: data race accessing CodeHeap::high in CodeCache::max_distance_to_non_nmethod Reviewed-by: aph, shade, mhaessig, bulasevich --- src/hotspot/share/code/codeCache.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hotspot/share/code/codeCache.cpp b/src/hotspot/share/code/codeCache.cpp index 94cf8ebdec1..efe5d6549eb 100644 --- a/src/hotspot/share/code/codeCache.cpp +++ b/src/hotspot/share/code/codeCache.cpp @@ -1182,8 +1182,8 @@ size_t CodeCache::max_distance_to_non_nmethod() { CodeHeap* blob = get_code_heap(CodeBlobType::NonNMethod); // the max distance is minimized by placing the NonNMethod segment // in between MethodProfiled and MethodNonProfiled segments - size_t dist1 = (size_t)blob->high() - (size_t)_low_bound; - size_t dist2 = (size_t)_high_bound - (size_t)blob->low(); + size_t dist1 = (size_t)blob->high_boundary() - (size_t)_low_bound; + size_t dist2 = (size_t)_high_bound - (size_t)blob->low_boundary(); return dist1 > dist2 ? dist1 : dist2; } } From d3e5304c0f70aa03a52f5449cb38645a184b23dc Mon Sep 17 00:00:00 2001 From: April Ivy Date: Fri, 10 Jul 2026 21:48:11 +0000 Subject: [PATCH 128/305] 8382841: Revert annotation parsing changes from libgraal Reviewed-by: liach, darcy --- .../classes/sun/reflect/annotation/AnnotationParser.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/java.base/share/classes/sun/reflect/annotation/AnnotationParser.java b/src/java.base/share/classes/sun/reflect/annotation/AnnotationParser.java index b40ed946648..ba804757e45 100644 --- a/src/java.base/share/classes/sun/reflect/annotation/AnnotationParser.java +++ b/src/java.base/share/classes/sun/reflect/annotation/AnnotationParser.java @@ -77,14 +77,14 @@ public class AnnotationParser { * Like {@link #parseAnnotations(byte[], sun.reflect.ConstantPool, Class)} * with an additional parameter {@code selectAnnotationClasses} which selects the * annotation types to parse (other than selected are quickly skipped).

    - * This method is used to parse select meta annotations in the construction + * This method is only used to parse select meta annotations in the construction * phase of {@link AnnotationType} instances to prevent infinite recursion. * * @param selectAnnotationClasses an array of annotation types to select when parsing */ @SafeVarargs @SuppressWarnings("varargs") // selectAnnotationClasses is used safely - public static Map, Annotation> parseSelectAnnotations( + static Map, Annotation> parseSelectAnnotations( byte[] rawAnnotations, ConstantPool constPool, Class container, From 7a5e6ef6aaaac5681df007a75b823af44a0b745e Mon Sep 17 00:00:00 2001 From: zifeihan Date: Mon, 13 Jul 2026 01:50:44 +0000 Subject: [PATCH 129/305] 8388035: RISC-V: Auto-enable Zfa extension features Reviewed-by: fyang, dzhang --- src/hotspot/cpu/riscv/globals_riscv.hpp | 2 +- src/hotspot/os_cpu/linux_riscv/riscv_hwprobe.cpp | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/hotspot/cpu/riscv/globals_riscv.hpp b/src/hotspot/cpu/riscv/globals_riscv.hpp index dc3915aa398..d399bc13082 100644 --- a/src/hotspot/cpu/riscv/globals_riscv.hpp +++ b/src/hotspot/cpu/riscv/globals_riscv.hpp @@ -103,7 +103,7 @@ define_pd_global(intx, InlineSmallCode, 1000); product(bool, UseZbb, false, DIAGNOSTIC, "Use Zbb instructions") \ product(bool, UseZbkb, false, EXPERIMENTAL, "Use Zbkb instructions") \ product(bool, UseZbs, false, DIAGNOSTIC, "Use Zbs instructions") \ - product(bool, UseZfa, false, EXPERIMENTAL, "Use Zfa instructions") \ + product(bool, UseZfa, false, DIAGNOSTIC, "Use Zfa instructions") \ product(bool, UseZfh, false, DIAGNOSTIC, "Use Zfh instructions") \ product(bool, UseZfhmin, false, DIAGNOSTIC, "Use Zfhmin instructions") \ product(bool, UseZacas, false, EXPERIMENTAL, "Use Zacas instructions") \ diff --git a/src/hotspot/os_cpu/linux_riscv/riscv_hwprobe.cpp b/src/hotspot/os_cpu/linux_riscv/riscv_hwprobe.cpp index 3ede62e14cd..fe555ec5ffb 100644 --- a/src/hotspot/os_cpu/linux_riscv/riscv_hwprobe.cpp +++ b/src/hotspot/os_cpu/linux_riscv/riscv_hwprobe.cpp @@ -215,11 +215,9 @@ void RiscvHwprobe::add_features_from_query_result() { if (is_set(RISCV_HWPROBE_KEY_IMA_EXT_0, RISCV_HWPROBE_EXT_ZBS)) { VM_Version::ext_Zbs.enable_feature(); } -#ifndef PRODUCT if (is_set(RISCV_HWPROBE_KEY_IMA_EXT_0, RISCV_HWPROBE_EXT_ZFA)) { VM_Version::ext_Zfa.enable_feature(); } -#endif if (is_set(RISCV_HWPROBE_KEY_IMA_EXT_0, RISCV_HWPROBE_EXT_ZFH)) { VM_Version::ext_Zfh.enable_feature(); } From b3d98954d1f18bd7cd0804deea0c34f1d3dbc9be Mon Sep 17 00:00:00 2001 From: April Ivy Date: Mon, 13 Jul 2026 04:31:35 +0000 Subject: [PATCH 130/305] 8387996: Remove reference to -d64 from serviceability tool manpages Reviewed-by: dholmes --- src/jdk.jcmd/share/man/jinfo.md | 7 ++----- src/jdk.jcmd/share/man/jstack.md | 5 ++--- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/src/jdk.jcmd/share/man/jinfo.md b/src/jdk.jcmd/share/man/jinfo.md index b70bc4c45ee..8365c5af8a5 100644 --- a/src/jdk.jcmd/share/man/jinfo.md +++ b/src/jdk.jcmd/share/man/jinfo.md @@ -1,5 +1,5 @@ --- -# Copyright (c) 2004, 2024, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -50,10 +50,7 @@ jinfo - generate Java configuration information for a specified Java process The `jinfo` command prints Java configuration information for a specified Java process. The configuration information includes Java system properties and JVM -command-line flags. If the specified process is running on a 64-bit JVM, then -you might need to specify the `-J-d64` option, for example: - -> `jinfo -J-d64 -sysprops` *pid* +command-line flags. This command is unsupported and might not be available in future releases of the JDK. In Windows Systems where `dbgeng.dll` is not present, the Debugging diff --git a/src/jdk.jcmd/share/man/jstack.md b/src/jdk.jcmd/share/man/jstack.md index 15849502d8c..2e95abf36c4 100644 --- a/src/jdk.jcmd/share/man/jstack.md +++ b/src/jdk.jcmd/share/man/jstack.md @@ -1,5 +1,5 @@ --- -# Copyright (c) 2004, 2024, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -52,8 +52,7 @@ The `jstack` command prints Java stack traces of Java threads for a specified Java process. For each Java frame, the full class name, method name, byte code index (BCI), and line number, when available, are printed. C++ mangled names aren't demangled. To demangle C++ names, the output of this command can be -piped to `c++filt`. When the specified process is running on a 64-bit JVM, you -might need to specify the `-J-d64` option, for example: `jstack -J-d64` *pid*. +piped to `c++filt`. **Note:** From f6d897614d9dbe07448fbeb04d168814de588eba Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Mon, 13 Jul 2026 07:08:31 +0000 Subject: [PATCH 131/305] 8387702: Linux/clang: enable linktime-gc on libjvm.so too, when it is configured Reviewed-by: mdoerr, lucy, clanger --- make/autoconf/flags-ldflags.m4 | 1 + 1 file changed, 1 insertion(+) diff --git a/make/autoconf/flags-ldflags.m4 b/make/autoconf/flags-ldflags.m4 index 7876511328b..1da98f5cdeb 100644 --- a/make/autoconf/flags-ldflags.m4 +++ b/make/autoconf/flags-ldflags.m4 @@ -81,6 +81,7 @@ AC_DEFUN([FLAGS_SETUP_LDFLAGS_HELPER], fi if test "x$ENABLE_LINKTIME_GC" = xtrue; then + BASIC_LDFLAGS_JVM_ONLY="$BASIC_LDFLAGS_JVM_ONLY -Wl,--gc-sections -Wl,--undefined=_ZTV8Metadata" BASIC_LDFLAGS_JDK_ONLY="$BASIC_LDFLAGS_JDK_ONLY -Wl,--gc-sections" fi fi From 6247550cec70f76420ccf7e3a8aaa57e93315439 Mon Sep 17 00:00:00 2001 From: Amit Kumar Date: Mon, 13 Jul 2026 08:09:54 +0000 Subject: [PATCH 132/305] 8388117: [s390x] is_z_illtrap should recognise all forms Reviewed-by: lucy, hdhiman --- src/hotspot/cpu/s390/assembler_s390.hpp | 2 +- .../gtest/s390/test_assembler_s390.cpp | 69 +++++++++++++++++++ 2 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 test/hotspot/gtest/s390/test_assembler_s390.cpp diff --git a/src/hotspot/cpu/s390/assembler_s390.hpp b/src/hotspot/cpu/s390/assembler_s390.hpp index 95ae442bb49..c834a71ec0c 100644 --- a/src/hotspot/cpu/s390/assembler_s390.hpp +++ b/src/hotspot/cpu/s390/assembler_s390.hpp @@ -3280,7 +3280,7 @@ class Assembler : public AbstractAssembler { return is_z_nop(* (short *) x); } static bool is_z_illtrap(address x) { - return *(uint16_t*)x == 0u; + return *(uint8_t*)x == 0u; } static bool is_z_br(long x) { return is_z_bcr(x) && ((x & 0x00f0) == 0x00f0); diff --git a/test/hotspot/gtest/s390/test_assembler_s390.cpp b/test/hotspot/gtest/s390/test_assembler_s390.cpp new file mode 100644 index 00000000000..2e677508a8d --- /dev/null +++ b/test/hotspot/gtest/s390/test_assembler_s390.cpp @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2025, IBM Corporation. and/or its affiliates. All rights reserved. + * 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(S390) && !defined(ZERO) + +#include "asm/assembler.hpp" +#include "asm/assembler.inline.hpp" +#include "unittest.hpp" + +// --------------------------------------------------------------------------- +// Tests for Assembler::is_z_illtrap +// +// The three emitter forms and what they write into memory (big-endian): +// +// z_illtrap() -> 0x00 0x00 (id == 0) +// z_illtrap(int id) -> 0x00 (e.g. 0x00 0xba) +// z_illtrap_eyecatcher(...) -> ends with z_illtrap(xpattern) -> 0x00 +// +// All forms share: high byte (first byte in memory) == 0x00. +// is_z_illtrap must recognise all of them, not just 0x0000. +// --------------------------------------------------------------------------- + +TEST(AssemblerS390, is_z_illtrap_no_id) { + // z_illtrap() emits 0x0000 — must be detected. + uint8_t buf[] = { 0x00, 0x00 }; + EXPECT_TRUE(Assembler::is_z_illtrap((address)buf)) + << "z_illtrap() (0x0000) must be recognised as illtrap"; +} + +TEST(AssemblerS390, is_z_illtrap_with_id) { + // z_illtrap(id) emits 0x00 — must also be detected. + // Tests a representative set of ids actually used in the source. + const uint8_t ids[] = { 0x22, 0x55, 0x66, 0x99, 0xba, 0xd1, 0xd2, 0xee }; + for (uint8_t id : ids) { + uint8_t buf[] = { 0x00, id }; + EXPECT_TRUE(Assembler::is_z_illtrap((address)buf)) + << "z_illtrap(0x" << std::hex << (int)id << ") must be recognised as illtrap"; + } +} + +TEST(AssemblerS390, is_z_illtrap_false_positive) { + // A non-zero high byte must NOT be recognised as an illtrap. + uint8_t buf[] = { 0x07, 0x00 }; // BCR 0,0 (a NOP — not an illtrap) + EXPECT_FALSE(Assembler::is_z_illtrap((address)buf)) + << "BCR 0,0 (0x0700) must not be recognised as illtrap"; +} + +#endif // S390 && !ZERO + From bc1bd75e7bc6cea24bbc77c45410338b0b110218 Mon Sep 17 00:00:00 2001 From: Albert Mingkun Yang Date: Mon, 13 Jul 2026 12:32:57 +0000 Subject: [PATCH 133/305] 8388122: NMT: Remove unused comm_size variable from RegionsTree::visit_committed_regions Reviewed-by: stuefe --- src/hotspot/share/nmt/regionsTree.inline.hpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/hotspot/share/nmt/regionsTree.inline.hpp b/src/hotspot/share/nmt/regionsTree.inline.hpp index 793a5c5f1fa..7f1714fb939 100644 --- a/src/hotspot/share/nmt/regionsTree.inline.hpp +++ b/src/hotspot/share/nmt/regionsTree.inline.hpp @@ -32,7 +32,6 @@ template void RegionsTree::visit_committed_regions(const VirtualMemoryRegion& rgn, F func) { position start = (position)rgn.base(); size_t end = reinterpret_cast(rgn.end()) + 1; - size_t comm_size = 0; NodeHelper prev; visit_range_in_order(start, end, [&](Node* node) { From 0dcfa722aa02cfa9c097c459bab0c1233fbd1897 Mon Sep 17 00:00:00 2001 From: April Ivy Date: Mon, 13 Jul 2026 13:36:26 +0000 Subject: [PATCH 134/305] 8388120: [s390x] c2: c_return_value is redundant Reviewed-by: amitkumar, rrich --- src/hotspot/cpu/s390/s390.ad | 39 +++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/src/hotspot/cpu/s390/s390.ad b/src/hotspot/cpu/s390/s390.ad index 6cdf40cda9c..256e39b03c2 100644 --- a/src/hotspot/cpu/s390/s390.ad +++ b/src/hotspot/cpu/s390/s390.ad @@ -2617,28 +2617,31 @@ frame %{ // stack slot. return_addr(REG Z_R14); - // Location of native (C/C++) and interpreter return values. This - // is specified to be the same as Java. In the 32-bit VM, long - // values are actually returned from native calls in O0:O1 and - // returned to the interpreter in I0:I1. The copying to and from - // the register pairs is done by the appropriate call and epilog - // opcodes. This simplifies the register allocator. - // - // Use register pair for c return value. - c_return_value %{ - assert(ideal_reg >= Op_RegI && ideal_reg <= Op_RegL, "only return normal values"); - static int typeToRegLo[Op_RegL+1] = { 0, 0, Z_R2_num, Z_R2_num, Z_R2_num, Z_F0_num, Z_F0_num, Z_R2_num }; - static int typeToRegHi[Op_RegL+1] = { 0, 0, OptoReg::Bad, OptoReg::Bad, Z_R2_H_num, OptoReg::Bad, Z_F0_H_num, Z_R2_H_num }; - return OptoRegPair(typeToRegHi[ideal_reg], typeToRegLo[ideal_reg]); - %} - // Use register pair for return value. // Location of compiled Java return values. Same as C return_value %{ assert(ideal_reg >= Op_RegI && ideal_reg <= Op_RegL, "only return normal values"); - static int typeToRegLo[Op_RegL+1] = { 0, 0, Z_R2_num, Z_R2_num, Z_R2_num, Z_F0_num, Z_F0_num, Z_R2_num }; - static int typeToRegHi[Op_RegL+1] = { 0, 0, OptoReg::Bad, OptoReg::Bad, Z_R2_H_num, OptoReg::Bad, Z_F0_H_num, Z_R2_H_num }; - return OptoRegPair(typeToRegHi[ideal_reg], typeToRegLo[ideal_reg]); + static const int lo[Op_RegL + 1] = { + 0, + 0, + Z_R2_num, // Op_RegN + Z_R2_num, // Op_RegI + Z_R2_num, // Op_RegP + Z_F0_num, // Op_RegF + Z_F0_num, // Op_RegD + Z_R2_num // Op_RegL + }; + static const int hi[Op_RegL + 1] = { + 0, + 0, + OptoReg::Bad, // Op_RegN + OptoReg::Bad, // Op_RegI + Z_R2_H_num, // Op_RegP + OptoReg::Bad, // Op_RegF + Z_F0_H_num, // Op_RegD + Z_R2_H_num // Op_RegL + }; + return OptoRegPair(hi[ideal_reg], lo[ideal_reg]); %} %} From d343e6c854f2851c3bd09d850898b0d79950e718 Mon Sep 17 00:00:00 2001 From: Justin Lu Date: Mon, 13 Jul 2026 16:23:47 +0000 Subject: [PATCH 135/305] 8387455: Restrict legacy Locale compatibility handling to exact matches Reviewed-by: naoto --- .../share/classes/java/util/Locale.java | 32 ++++++++----- .../classes/java/util/ResourceBundle.java | 2 +- .../util/locale/InternalLocaleBuilder.java | 48 ++++++++++--------- .../classes/sun/util/locale/LanguageTag.java | 3 +- .../provider/LocaleServiceProviderPool.java | 14 ++---- .../java/util/Locale/LocaleEnhanceTest.java | 47 +++++++++++++++++- .../Control/DefaultControlTest.java | 29 +++++++++-- 7 files changed, 124 insertions(+), 51 deletions(-) diff --git a/src/java.base/share/classes/java/util/Locale.java b/src/java.base/share/classes/java/util/Locale.java index 462cd5755c2..f727d301954 100644 --- a/src/java.base/share/classes/java/util/Locale.java +++ b/src/java.base/share/classes/java/util/Locale.java @@ -523,25 +523,24 @@ import sun.util.locale.provider.TimeZoneNameUtility; *

    For compatibility reasons, two * non-conforming locales are treated as special cases. These are * {@code ja_JP_JP} and {@code th_TH_TH}. These are ill-formed - * in BCP 47 since the {@linkplain ##def_variant variants} are too short. To ease migration to BCP 47, - * these are treated specially during construction. These two cases (and only - * these) cause a constructor to generate an extension, all other values behave - * exactly as they did prior to Java 7. + * in BCP 47 since the {@linkplain ##def_variant variants} are too short. To ease + * migration to BCP 47, these are treated specially during creation. Creation + * of these two cases generates a compatibility extension. * *

    Java has used {@code ja_JP_JP} to represent Japanese as used in * Japan together with the Japanese Imperial calendar. This is now * representable using a Unicode locale extension, by specifying the * Unicode locale key {@code ca} (for "calendar") and type - * {@code japanese}. When the Locale constructor is called with the - * arguments "ja", "JP", "JP", the extension "u-ca-japanese" is - * automatically added. + * {@code japanese}. When a {@code Locale} is created with language "ja", an + * empty script, country "JP", variant "JP", and no extensions, the extension + * "u-ca-japanese" is automatically added. * *

    Java has used {@code th_TH_TH} to represent Thai as used in * Thailand together with Thai digits. This is also now representable using * a Unicode locale extension, by specifying the Unicode locale key - * {@code nu} (for "number") and value {@code thai}. When the Locale - * constructor is called with the arguments "th", "TH", "TH", the - * extension "u-nu-thai" is automatically added. + * {@code nu} (for "number") and value {@code thai}. When a {@code Locale} is + * created with language "th", an empty script, country "TH", variant "TH", and + * no extensions, the extension "u-nu-thai" is automatically added. * *

    Legacy language codes

    * @@ -1612,9 +1611,9 @@ public final class Locale implements Cloneable, Serializable { *
  2. Deprecated ISO language codes "iw", "ji", and "in" are * converted to "he", "yi", and "id", respectively. * - *
  3. A locale with language "no", country "NO", and variant - * "NY", representing Norwegian Nynorsk (Norway), is converted - * to a language tag "nn-NO".
  4. + *
  5. A locale with language "no", an empty script, country "NO", variant + * "NY", and no extensions, representing Norwegian Nynorsk (Norway), is + * converted to a language tag "nn-NO".
  6. * *

    Note: Although the language tag obtained by this * method is well-formed (satisfies the syntax requirements @@ -2693,6 +2692,13 @@ public final class Locale implements Cloneable, Serializable { *

  7. Locale("th", "TH", "TH") is treated as "th-TH-u-nu-thai" *
  8. Locale("no", "NO", "NY") is treated as "nn-NO" * + *

    For all three cases, compatibility handling only applies when the script + * is empty. Additionally, the Japanese case requires exactly the + * {@code u-ca-japanese} extension, the Thai case requires + * exactly the {@code u-nu-thai} extension, and the Norwegian case + * requires no extensions. If these conditions are not met, the two-letter + * variant is treated as ill-formed, and an {@code IllformedLocaleException} is thrown. + * * @param locale the locale * @return This builder. * @throws IllformedLocaleException if {@code locale} has diff --git a/src/java.base/share/classes/java/util/ResourceBundle.java b/src/java.base/share/classes/java/util/ResourceBundle.java index f91db79891b..2483e184e2e 100644 --- a/src/java.base/share/classes/java/util/ResourceBundle.java +++ b/src/java.base/share/classes/java/util/ResourceBundle.java @@ -2842,7 +2842,7 @@ public abstract class ResourceBundle { boolean isNorwegianBokmal = false; boolean isNorwegianNynorsk = false; if (language.equals("no")) { - if (region.equals("NO") && variant.equals("NY")) { + if (region.equals("NO") && variant.equals("NY") && script.isEmpty()) { variant = ""; isNorwegianNynorsk = true; } else { diff --git a/src/java.base/share/classes/sun/util/locale/InternalLocaleBuilder.java b/src/java.base/share/classes/sun/util/locale/InternalLocaleBuilder.java index 5da725d59c8..499cb757125 100644 --- a/src/java.base/share/classes/sun/util/locale/InternalLocaleBuilder.java +++ b/src/java.base/share/classes/sun/util/locale/InternalLocaleBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -380,27 +380,31 @@ public final class InternalLocaleBuilder { String variant = base.getVariant(); // Special backward compatibility support - - // Exception 1 - ja_JP_JP - if (language.equals("ja") && region.equals("JP") && variant.equals("JP")) { - // When locale ja_JP_JP is created, ca-japanese is always there. - // The builder ignores the variant "JP" - assert("japanese".equals(localeExtensions.getUnicodeLocaleType("ca"))); - variant = ""; - } - // Exception 2 - th_TH_TH - else if (language.equals("th") && region.equals("TH") && variant.equals("TH")) { - // When locale th_TH_TH is created, nu-thai is always there. - // The builder ignores the variant "TH" - assert("thai".equals(localeExtensions.getUnicodeLocaleType("nu"))); - variant = ""; - } - // Exception 3 - no_NO_NY - else if (language.equals("no") && region.equals("NO") && variant.equals("NY")) { - // no_NO_NY is a valid locale and used by Java 6 or older versions. - // The build ignores the variant "NY" and change the language to "nn". - language = "nn"; - variant = ""; + if (script.isEmpty()) { + // Exception 1 - ja_JP_JP + if (language.equals("ja") && region.equals("JP") && variant.equals("JP") + && LocaleExtensions.CALENDAR_JAPANESE.equals(localeExtensions)) { + // When locale ja_JP_JP is created, ca-japanese is always added. + // If the extension exists, the builder ignores the variant "JP" + // otherwise "JP" is merely an ill-formed variant + variant = ""; + } + // Exception 2 - th_TH_TH + else if (language.equals("th") && region.equals("TH") && variant.equals("TH") + && LocaleExtensions.NUMBER_THAI.equals(localeExtensions)){ + // When locale th_TH_TH is created, nu-thai is always added. + // If the extension exists, the builder ignores the variant "TH" + // otherwise "TH" is merely an ill-formed variant + variant = ""; + } + // Exception 3 - no_NO_NY + else if (language.equals("no") && region.equals("NO") && variant.equals("NY") + && localeExtensions == null) { + // no_NO_NY is a valid locale and used by Java 6 or older versions. + // The builder ignores the variant "NY" and changes the language to "nn". + language = "nn"; + variant = ""; + } } // Validate base locale fields before updating internal state. diff --git a/src/java.base/share/classes/sun/util/locale/LanguageTag.java b/src/java.base/share/classes/sun/util/locale/LanguageTag.java index 5ce62a275cc..485fb7f5ca6 100644 --- a/src/java.base/share/classes/sun/util/locale/LanguageTag.java +++ b/src/java.base/share/classes/sun/util/locale/LanguageTag.java @@ -418,7 +418,8 @@ public record LanguageTag(String language, } // Special handling for no_NO_NY - use nn_NO for language tag - if (language.equals("no") && region.equals("NO") && baseVariant.equals("NY")) { + if (language.equals("no") && region.equals("NO") && baseVariant.equals("NY") + && script.isEmpty() && localeExtensions == null) { language = "nn"; baseVariant = EMPTY_SUBTAG; } diff --git a/src/java.base/share/classes/sun/util/locale/provider/LocaleServiceProviderPool.java b/src/java.base/share/classes/sun/util/locale/provider/LocaleServiceProviderPool.java index cc9a805fe0d..f3d0990429d 100644 --- a/src/java.base/share/classes/sun/util/locale/provider/LocaleServiceProviderPool.java +++ b/src/java.base/share/classes/sun/util/locale/provider/LocaleServiceProviderPool.java @@ -370,16 +370,10 @@ public final class LocaleServiceProviderPool { locbld.clearExtensions(); lookupLocale = locbld.build(); } catch (IllformedLocaleException e) { - // A Locale with non-empty extensions - // should have well-formed fields except - // for ja_JP_JP and th_TH_TH. Therefore, - // it should never enter in this catch clause. - System.getLogger(LocaleServiceProviderPool.class.getCanonicalName()) - .log(System.Logger.Level.INFO, - "A locale(" + locale + ") has non-empty extensions, but has illformed fields."); - - // Fallback - script field will be lost. - lookupLocale = Locale.of(locale.getLanguage(), locale.getCountry(), locale.getVariant()); + // E.g. "en-Latn-US-a-foo-x-lvariant-xy" + // Extensions can exist while variant is ill-formed + // Simply strip the extensions so that all fields are preserved + lookupLocale = lookupLocale.stripExtensions(); } } return lookupLocale; diff --git a/test/jdk/java/util/Locale/LocaleEnhanceTest.java b/test/jdk/java/util/Locale/LocaleEnhanceTest.java index 3fe3745034d..a4c056d3a77 100644 --- a/test/jdk/java/util/Locale/LocaleEnhanceTest.java +++ b/test/jdk/java/util/Locale/LocaleEnhanceTest.java @@ -58,7 +58,7 @@ import static org.junit.jupiter.api.Assertions.fail; * @test * @bug 6875847 6992272 7002320 7015500 7023613 7032820 7033504 7004603 * 7044019 8008577 8176853 8255086 8263202 8287868 8174269 8369452 - * 8369590 8387185 8387253 + * 8369590 8387185 8387253 8387455 * @summary test API changes to Locale * @modules jdk.localedata * @run junit/othervm -esa LocaleEnhanceTest @@ -498,6 +498,23 @@ public class LocaleEnhanceTest { // private use only language tag is preserved (no extra "und") {"x-elmer", "x-elmer"}, {"x-lvariant-JP", "x-lvariant-JP"}, + // Legacy locale cases + // no/NO/NY case is normalized during `toLanguageTag` + // ja/JP/JP & th/TH/TH case is normalized during `forLanguageTag` + // Script prevents the legacy conversions + {"no-Latn-NO-x-lvariant-NY", + "no-Latn-NO-x-lvariant-NY"}, + {"ja-Jpan-JP-x-lvariant-JP", + "ja-Jpan-JP-x-lvariant-JP"}, + {"th-Thai-TH-x-lvariant-TH", + "th-Thai-TH-x-lvariant-TH"}, + // Unexpected extensions prevent the legacy conversions + {"no-NO-a-foo-x-lvariant-NY", + "no-NO-a-foo-x-lvariant-NY"}, + {"ja-JP-a-foo-x-lvariant-JP", + "ja-JP-a-foo-x-lvariant-JP"}, + {"th-TH-a-foo-x-lvariant-TH", + "th-TH-a-foo-x-lvariant-TH"}, }; for (String[] test : tests1) { Locale locale = Locale.forLanguageTag(test[0]); @@ -729,6 +746,34 @@ public class LocaleEnhanceTest { assertEquals("nn", locale.getLanguage(), "no_NO_NY language"); assertEquals("", locale.getVariant(), "no_NO_NY variant"); + // Legacy locales that stripped their compatibility extensions are invalid + assertThrows(IllformedLocaleException.class, + () -> new Builder().setLocale(Locale.of("ja", "JP", "JP").stripExtensions())); + assertThrows(IllformedLocaleException.class, + () -> new Builder().setLocale(Locale.of("th", "TH", "TH").stripExtensions())); + + // Legacy locales without the correct Unicode locale extension value are invalid + assertThrows(IllformedLocaleException.class, + () -> new Builder().setLocale(Locale.forLanguageTag("ja-JP-u-ca-foobar-x-lvariant-JP"))); + assertThrows(IllformedLocaleException.class, + () -> new Builder().setLocale(Locale.forLanguageTag("th-TH-u-nu-foobar-x-lvariant-TH"))); + + // Legacy locales with additional extensions are invalid + assertThrows(IllformedLocaleException.class, + () -> new Builder().setLocale(Locale.forLanguageTag("no-NO-a-foo-x-lvariant-NY"))); + assertThrows(IllformedLocaleException.class, + () -> new Builder().setLocale(Locale.forLanguageTag("ja-JP-a-foo-x-lvariant-JP"))); + assertThrows(IllformedLocaleException.class, + () -> new Builder().setLocale(Locale.forLanguageTag("th-TH-a-foo-x-lvariant-TH"))); + + // Legacy locales with non-empty script are invalid + assertThrows(IllformedLocaleException.class, + () -> new Builder().setLocale(Locale.forLanguageTag("ja-Jpan-JP-u-ca-japanese-x-lvariant-JP"))); + assertThrows(IllformedLocaleException.class, + () -> new Builder().setLocale(Locale.forLanguageTag("th-Thai-TH-u-nu-thai-x-lvariant-TH"))); + assertThrows(IllformedLocaleException.class, + () -> new Builder().setLocale(Locale.forLanguageTag("no-Latn-NO-x-lvariant-NY"))); + // non-canonical, non-legacy locales are invalid assertThrows(IllformedLocaleException.class, () -> new Builder().setLocale(Locale.of("123", "4567", "89")), "123_4567_89"); diff --git a/test/jdk/java/util/ResourceBundle/Control/DefaultControlTest.java b/test/jdk/java/util/ResourceBundle/Control/DefaultControlTest.java index b8b49406c05..a70ebf9e527 100644 --- a/test/jdk/java/util/ResourceBundle/Control/DefaultControlTest.java +++ b/test/jdk/java/util/ResourceBundle/Control/DefaultControlTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2007, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -22,7 +22,7 @@ */ /* * @test - * @bug 5102289 6278334 8261179 + * @bug 5102289 6278334 8261179 8387455 * @summary Test the default Control implementation. The expiration * functionality of newBundle, getTimeToLive, and needsReload is * tested by ExpirationTest.sh. The factory methods are tested @@ -171,6 +171,15 @@ public class DefaultControlTest { candidateData.put(Locale.ROOT, new Locale[] { Locale.ROOT }); + // Norwegian Nynorsk + candidateData.put(Locale.of("no", "NO", "NY"), new Locale[] { + Locale.of("nn", "NO"), + Locale.of("nn"), + Locale.of("no", "NO", "NY"), + Locale.of("no", "NO"), + Locale.of("no"), + Locale.ROOT}); + // Norwegian Bokmal candidateData.put(Locale.forLanguageTag("nb-NO-POSIX"), new Locale[] { Locale.forLanguageTag("nb-NO-POSIX"), @@ -188,7 +197,21 @@ public class DefaultControlTest { Locale.forLanguageTag("no"), Locale.forLanguageTag("nb"), Locale.ROOT}); - + // Appears as no-NO-NY legacy locale (but contains script) so treat as Norwegian Bokmal + candidateData.put(Locale.forLanguageTag("no-Latn-NO-x-lvariant-NY"), new Locale[] { + Locale.forLanguageTag("no-Latn-NO-x-lvariant-NY"), + Locale.forLanguageTag("nb-Latn-NO-x-lvariant-NY"), + Locale.forLanguageTag("no-Latn-NO"), + Locale.forLanguageTag("nb-Latn-NO"), + Locale.forLanguageTag("no-Latn"), + Locale.forLanguageTag("nb-Latn"), + Locale.forLanguageTag("no-NO-x-lvariant-NY"), + Locale.forLanguageTag("nb-NO-x-lvariant-NY"), + Locale.forLanguageTag("no-NO"), + Locale.forLanguageTag("nb-NO"), + Locale.forLanguageTag("no"), + Locale.forLanguageTag("nb"), + Locale.ROOT}); for (Locale locale : candidateData.keySet()) { List candidates = CONTROL.getCandidateLocales("any", locale); From 151516fae22ee71c12ea76a51fcf5be69f2e5dbf Mon Sep 17 00:00:00 2001 From: Elif Aslan Date: Mon, 13 Jul 2026 16:46:50 +0000 Subject: [PATCH 136/305] 8387985: sun/tools/jstat shell tests fail on platforms that do not support ParallelGC Reviewed-by: cjplummer, dholmes --- test/jdk/sun/tools/jstat/jstatGcCapacityOutput1.sh | 3 ++- test/jdk/sun/tools/jstat/jstatGcMetaCapacityOutput1.sh | 3 ++- test/jdk/sun/tools/jstat/jstatGcNewCapacityOutput1.sh | 3 ++- test/jdk/sun/tools/jstat/jstatGcNewOutput1.sh | 3 ++- test/jdk/sun/tools/jstat/jstatGcOldCapacityOutput1.sh | 5 +++-- test/jdk/sun/tools/jstat/jstatGcOldOutput1.sh | 3 ++- test/jdk/sun/tools/jstat/jstatGcOutput1.sh | 3 ++- test/jdk/sun/tools/jstat/jstatLineCounts1.sh | 3 ++- test/jdk/sun/tools/jstat/jstatLineCounts2.sh | 3 ++- test/jdk/sun/tools/jstat/jstatLineCounts3.sh | 3 ++- test/jdk/sun/tools/jstat/jstatLineCounts4.sh | 3 ++- test/jdk/sun/tools/jstat/jstatTimeStamp1.sh | 3 ++- 12 files changed, 25 insertions(+), 13 deletions(-) diff --git a/test/jdk/sun/tools/jstat/jstatGcCapacityOutput1.sh b/test/jdk/sun/tools/jstat/jstatGcCapacityOutput1.sh index c1908855ea7..6e184e9dc31 100644 --- a/test/jdk/sun/tools/jstat/jstatGcCapacityOutput1.sh +++ b/test/jdk/sun/tools/jstat/jstatGcCapacityOutput1.sh @@ -1,5 +1,5 @@ # -# Copyright (c) 2004, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -23,6 +23,7 @@ # @test # @bug 4990825 +# @requires vm.gc.Parallel # @run shell jstatGcCapacityOutput1.sh # @summary Test that output of 'jstat -gccapacity 0' has expected line counts diff --git a/test/jdk/sun/tools/jstat/jstatGcMetaCapacityOutput1.sh b/test/jdk/sun/tools/jstat/jstatGcMetaCapacityOutput1.sh index b16d0e38d02..b5caccb1768 100644 --- a/test/jdk/sun/tools/jstat/jstatGcMetaCapacityOutput1.sh +++ b/test/jdk/sun/tools/jstat/jstatGcMetaCapacityOutput1.sh @@ -1,5 +1,5 @@ # -# Copyright (c) 2013, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2013, 2026, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -23,6 +23,7 @@ # @test # @bug 4990825 +# @requires vm.gc.Parallel # @run shell jstatGcMetaCapacityOutput1.sh # @summary Test that output of 'jstat -gcmetacapacity 0' has expected line counts diff --git a/test/jdk/sun/tools/jstat/jstatGcNewCapacityOutput1.sh b/test/jdk/sun/tools/jstat/jstatGcNewCapacityOutput1.sh index 64ce2efd455..96f0722a9e1 100644 --- a/test/jdk/sun/tools/jstat/jstatGcNewCapacityOutput1.sh +++ b/test/jdk/sun/tools/jstat/jstatGcNewCapacityOutput1.sh @@ -1,5 +1,5 @@ # -# Copyright (c) 2004, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -23,6 +23,7 @@ # @test # @bug 4990825 +# @requires vm.gc.Parallel # @run shell jstatGcNewCapacityOutput1.sh # @summary Test that output of 'jstat -gcnewcapacity 0' has expected line counts diff --git a/test/jdk/sun/tools/jstat/jstatGcNewOutput1.sh b/test/jdk/sun/tools/jstat/jstatGcNewOutput1.sh index b15ec02d2b0..96e2db61488 100644 --- a/test/jdk/sun/tools/jstat/jstatGcNewOutput1.sh +++ b/test/jdk/sun/tools/jstat/jstatGcNewOutput1.sh @@ -1,5 +1,5 @@ # -# Copyright (c) 2004, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -23,6 +23,7 @@ # @test # @bug 4990825 +# @requires vm.gc.Parallel # @run shell jstatGcNewOutput1.sh # @summary Test that output of 'jstat -gcnew 0' has expected line counts diff --git a/test/jdk/sun/tools/jstat/jstatGcOldCapacityOutput1.sh b/test/jdk/sun/tools/jstat/jstatGcOldCapacityOutput1.sh index 1c13d6f916d..0c5e2e19894 100644 --- a/test/jdk/sun/tools/jstat/jstatGcOldCapacityOutput1.sh +++ b/test/jdk/sun/tools/jstat/jstatGcOldCapacityOutput1.sh @@ -1,5 +1,5 @@ # -# Copyright (c) 2004, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -23,8 +23,9 @@ # @test # @bug 4990825 +# @requires vm.gc.Parallel # @run shell jstatGcOldCapacityOutput1.sh -# @summary Test that output of 'jstat -gcoldcapcaity 0' has expected line counts +# @summary Test that output of 'jstat -gcoldcapacity 0' has expected line counts . ${TESTSRC-.}/../../jvmstat/testlibrary/utils.sh diff --git a/test/jdk/sun/tools/jstat/jstatGcOldOutput1.sh b/test/jdk/sun/tools/jstat/jstatGcOldOutput1.sh index 7f505228b12..0f857ccb1e6 100644 --- a/test/jdk/sun/tools/jstat/jstatGcOldOutput1.sh +++ b/test/jdk/sun/tools/jstat/jstatGcOldOutput1.sh @@ -1,5 +1,5 @@ # -# Copyright (c) 2004, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -23,6 +23,7 @@ # @test # @bug 4990825 +# @requires vm.gc.Parallel # @run shell jstatGcOldOutput1.sh # @summary Test that output of 'jstat -gcold 0' has expected line counts diff --git a/test/jdk/sun/tools/jstat/jstatGcOutput1.sh b/test/jdk/sun/tools/jstat/jstatGcOutput1.sh index dfffa2d1a55..5862fda3fd7 100644 --- a/test/jdk/sun/tools/jstat/jstatGcOutput1.sh +++ b/test/jdk/sun/tools/jstat/jstatGcOutput1.sh @@ -1,5 +1,5 @@ # -# Copyright (c) 2004, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -23,6 +23,7 @@ # @test # @bug 4990825 +# @requires vm.gc.Parallel # @run shell jstatGcOutput1.sh # @summary Test that output of 'jstat -gc 0' has expected line counts diff --git a/test/jdk/sun/tools/jstat/jstatLineCounts1.sh b/test/jdk/sun/tools/jstat/jstatLineCounts1.sh index 97338b8e793..ca6adce96a5 100644 --- a/test/jdk/sun/tools/jstat/jstatLineCounts1.sh +++ b/test/jdk/sun/tools/jstat/jstatLineCounts1.sh @@ -1,5 +1,5 @@ # -# Copyright (c) 2004, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -23,6 +23,7 @@ # @test # @bug 4990825 +# @requires vm.gc.Parallel # @run shell jstatLineCounts1.sh # @summary Test that output of 'jstat -gcutil 0 250 5' has expected line counts diff --git a/test/jdk/sun/tools/jstat/jstatLineCounts2.sh b/test/jdk/sun/tools/jstat/jstatLineCounts2.sh index eab19f3931e..a668df72e0e 100644 --- a/test/jdk/sun/tools/jstat/jstatLineCounts2.sh +++ b/test/jdk/sun/tools/jstat/jstatLineCounts2.sh @@ -1,5 +1,5 @@ # -# Copyright (c) 2004, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -23,6 +23,7 @@ # @test # @bug 4990825 +# @requires vm.gc.Parallel # @run shell jstatLineCounts2.sh # @summary Test that output of 'jstat -gcutil 0' has expected line counts diff --git a/test/jdk/sun/tools/jstat/jstatLineCounts3.sh b/test/jdk/sun/tools/jstat/jstatLineCounts3.sh index 9a769a92464..bffffc8a38e 100644 --- a/test/jdk/sun/tools/jstat/jstatLineCounts3.sh +++ b/test/jdk/sun/tools/jstat/jstatLineCounts3.sh @@ -1,5 +1,5 @@ # -# Copyright (c) 2004, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -23,6 +23,7 @@ # @test # @bug 4990825 +# @requires vm.gc.Parallel # @run shell jstatLineCounts3.sh # @summary Test that output of 'jstat -gcutil -h 10 250 10' has expected line counts diff --git a/test/jdk/sun/tools/jstat/jstatLineCounts4.sh b/test/jdk/sun/tools/jstat/jstatLineCounts4.sh index 817c3b14f62..9ad1f57a5d5 100644 --- a/test/jdk/sun/tools/jstat/jstatLineCounts4.sh +++ b/test/jdk/sun/tools/jstat/jstatLineCounts4.sh @@ -1,5 +1,5 @@ # -# Copyright (c) 2004, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -23,6 +23,7 @@ # @test # @bug 4990825 +# @requires vm.gc.Parallel # @run shell jstatLineCounts4.sh # @summary Test that output of 'jstat -gcutil -h 10 250 11' has expected line counts diff --git a/test/jdk/sun/tools/jstat/jstatTimeStamp1.sh b/test/jdk/sun/tools/jstat/jstatTimeStamp1.sh index db71314fcf9..4e4cb8df426 100644 --- a/test/jdk/sun/tools/jstat/jstatTimeStamp1.sh +++ b/test/jdk/sun/tools/jstat/jstatTimeStamp1.sh @@ -1,5 +1,5 @@ # -# Copyright (c) 2004, 2020, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -23,6 +23,7 @@ # @test # @bug 4990825 +# @requires vm.gc.Parallel # @run shell jstatTimeStamp1.sh # @summary Test that output of 'jstat -gcutil -t 0' has expected format From 30abe0b3a6ee2d9a8ef992e58e8e81d5aadaf49f Mon Sep 17 00:00:00 2001 From: Vladimir Ivanov Date: Mon, 13 Jul 2026 17:51:11 +0000 Subject: [PATCH 137/305] 8373487: Out-of-bounds access in AlignmentGapAccess test Reviewed-by: dlong, ayang --- test/hotspot/jtreg/ProblemList.txt | 2 -- test/hotspot/jtreg/compiler/unsafe/AlignmentGapAccess.java | 7 ++++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/test/hotspot/jtreg/ProblemList.txt b/test/hotspot/jtreg/ProblemList.txt index e0005bfde07..0a98477be69 100644 --- a/test/hotspot/jtreg/ProblemList.txt +++ b/test/hotspot/jtreg/ProblemList.txt @@ -67,8 +67,6 @@ compiler/interpreter/Test6833129.java 8335266 generic-i586 compiler/c2/aarch64/TestStaticCallStub.java 8359963 generic-aarch64 -compiler/unsafe/AlignmentGapAccess.java 8373487 generic-all - compiler/escapeAnalysis/TestBCEscapeAnalyzerOverflow.java 8387392 windows-aarch64 ############################################################################# diff --git a/test/hotspot/jtreg/compiler/unsafe/AlignmentGapAccess.java b/test/hotspot/jtreg/compiler/unsafe/AlignmentGapAccess.java index ac3c4b0278a..8b2ee067140 100644 --- a/test/hotspot/jtreg/compiler/unsafe/AlignmentGapAccess.java +++ b/test/hotspot/jtreg/compiler/unsafe/AlignmentGapAccess.java @@ -38,17 +38,22 @@ public class AlignmentGapAccess { static class A { int fa; } static class B extends A { byte fb; } + static class C extends B { int fc; } static final long FA_OFFSET = UNSAFE.objectFieldOffset(A.class, "fa"); static final long FB_OFFSET = UNSAFE.objectFieldOffset(B.class, "fb"); + static final long FC_OFFSET = UNSAFE.objectFieldOffset(C.class, "fc"); static int test(B obj) { return UNSAFE.getInt(obj, FB_OFFSET + 1); } public static void main(String[] args) { + System.out.printf("Layout: +%d: fa; +%d: fb; +%d: fc\n", + FA_OFFSET, FB_OFFSET, FC_OFFSET); + for (int i = 0; i < 20_000; i++) { - test(new B()); + test(new C()); } System.out.println("TEST PASSED"); } From 6ec04bb20423110d6a991c827104f37571e4ead0 Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Mon, 13 Jul 2026 18:04:04 +0000 Subject: [PATCH 138/305] 8387675: IS_WINVISTA macro is obsolete Reviewed-by: aivanov, stuefe, prr --- .../sun/awt/Win32GraphicsEnvironment.java | 9 +---- .../sun/awt/windows/WComponentPeer.java | 10 ++---- .../classes/sun/awt/windows/WWindowPeer.java | 36 +++++-------------- .../libawt/java2d/windows/WindowsFlags.cpp | 5 ++- .../windows/native/libawt/windows/awt.h | 6 +--- .../libawt/windows/awt_DesktopProperties.cpp | 14 +------- .../native/libawt/windows/awt_MenuItem.cpp | 8 ++--- .../native/libawt/windows/awt_TextArea.cpp | 4 +-- .../native/libawt/windows/awt_TextField.cpp | 4 +-- .../libawt/windows/awt_Win32GraphicsEnv.cpp | 19 +--------- 10 files changed, 22 insertions(+), 93 deletions(-) diff --git a/src/java.desktop/windows/classes/sun/awt/Win32GraphicsEnvironment.java b/src/java.desktop/windows/classes/sun/awt/Win32GraphicsEnvironment.java index 9d09f13e525..8bb7f04420c 100644 --- a/src/java.desktop/windows/classes/sun/awt/Win32GraphicsEnvironment.java +++ b/src/java.desktop/windows/classes/sun/awt/Win32GraphicsEnvironment.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -255,11 +255,4 @@ public final class Win32GraphicsEnvironment extends SunGraphicsEnvironment { private static void dwmCompositionChanged(boolean enabled) { isDWMCompositionEnabled = enabled; } - - /** - * Used to find out if the OS is Windows Vista or later. - * - * @return {@code true} if the OS is Vista or later, {@code false} otherwise - */ - public static native boolean isVistaOS(); } diff --git a/src/java.desktop/windows/classes/sun/awt/windows/WComponentPeer.java b/src/java.desktop/windows/classes/sun/awt/windows/WComponentPeer.java index 00ad60c8bb3..b288d5beb07 100644 --- a/src/java.desktop/windows/classes/sun/awt/windows/WComponentPeer.java +++ b/src/java.desktop/windows/classes/sun/awt/windows/WComponentPeer.java @@ -1082,16 +1082,10 @@ public abstract class WComponentPeer extends WObjectPeer */ public boolean isAccelCapable() { if (!isAccelCapable || - !isContainingTopLevelAccelCapable((Component)target)) - { + !isContainingTopLevelAccelCapable((Component)target)) { return false; } - - boolean isTranslucent = - SunToolkit.isContainingTopLevelTranslucent((Component)target); - // D3D/OGL and translucent windows interacted poorly in Windows XP; - // these problems are no longer present in Vista - return !isTranslucent || Win32GraphicsEnvironment.isVistaOS(); + return true; } /** diff --git a/src/java.desktop/windows/classes/sun/awt/windows/WWindowPeer.java b/src/java.desktop/windows/classes/sun/awt/windows/WWindowPeer.java index 9c1c7665f4b..3c8e4de23cb 100644 --- a/src/java.desktop/windows/classes/sun/awt/windows/WWindowPeer.java +++ b/src/java.desktop/windows/classes/sun/awt/windows/WWindowPeer.java @@ -683,16 +683,6 @@ public class WWindowPeer extends WPanelPeer implements WindowPeer, throw new IllegalArgumentException( "The value of opacity should be in the range [0.0f .. 1.0f]."); } - - if (((this.opacity == 1.0f && opacity < 1.0f) || - (this.opacity < 1.0f && opacity == 1.0f)) && - !Win32GraphicsEnvironment.isVistaOS()) - { - // non-Vista OS: only replace the surface data if opacity status - // changed (see WComponentPeer.isAccelCapable() for more) - replaceSurfaceDataRecursively((Component)getTarget()); - } - this.opacity = opacity; final int maxOpacity = 0xff; @@ -734,14 +724,6 @@ public class WWindowPeer extends WPanelPeer implements WindowPeer, } } - boolean isVistaOS = Win32GraphicsEnvironment.isVistaOS(); - - if (this.isOpaque != isOpaque && !isVistaOS) { - // non-Vista OS: only replace the surface data if the opacity - // status changed (see WComponentPeer.isAccelCapable() for more) - replaceSurfaceDataRecursively(target); - } - synchronized (getStateLock()) { this.isOpaque = isOpaque; setOpaqueImpl(isOpaque); @@ -756,16 +738,14 @@ public class WWindowPeer extends WPanelPeer implements WindowPeer, } } - if (isVistaOS) { - // On Vista: setting the window non-opaque makes the window look - // rectangular, though still catching the mouse clicks within - // its shape only. To restore the correct visual appearance - // of the window (i.e. w/ the correct shape) we have to reset - // the shape. - Shape shape = target.getShape(); - if (shape != null) { - target.setShape(shape); - } + // Since Vista: setting the window non-opaque makes the window look + // rectangular, though still catching the mouse clicks within + // its shape only. To restore the correct visual appearance + // of the window (i.e. w/ the correct shape) we have to reset + // the shape. + Shape shape = target.getShape(); + if (shape != null) { + target.setShape(shape); } if (target.isVisible()) { diff --git a/src/java.desktop/windows/native/libawt/java2d/windows/WindowsFlags.cpp b/src/java.desktop/windows/native/libawt/java2d/windows/WindowsFlags.cpp index 189525c39a1..c294303f4c1 100644 --- a/src/java.desktop/windows/native/libawt/java2d/windows/WindowsFlags.cpp +++ b/src/java.desktop/windows/native/libawt/java2d/windows/WindowsFlags.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -88,8 +88,7 @@ void GetFlagValues(JNIEnv *env, jclass wFlagsClass) } useD3D = d3dEnabled; forceD3DUsage = d3dSet; - setHighDPIAware = - (IS_WINVISTA && GetStaticBoolean(env, wFlagsClass, "setHighDPIAware")); + setHighDPIAware = GetStaticBoolean(env, wFlagsClass, "setHighDPIAware"); JNU_CHECK_EXCEPTION(env); J2dTraceLn(J2D_TRACE_INFO, "WindowsFlags (native):"); diff --git a/src/java.desktop/windows/native/libawt/windows/awt.h b/src/java.desktop/windows/native/libawt/windows/awt.h index c367471afa9..8a09d6af994 100644 --- a/src/java.desktop/windows/native/libawt/windows/awt.h +++ b/src/java.desktop/windows/native/libawt/windows/awt.h @@ -154,12 +154,8 @@ typedef AwtObject* PDATA; JNI_TRUE) /* /NEW JNI */ -/* - * IS_WINVISTA returns TRUE on Vista - */ -#define IS_WINVISTA (LOBYTE(LOWORD(::GetVersion())) >= 6) #define IS_WIN8 ( \ - (IS_WINVISTA && (HIBYTE(LOWORD(::GetVersion())) >= 2)) || \ + (LOBYTE(LOWORD(::GetVersion())) == 6 && (HIBYTE(LOWORD(::GetVersion())) >= 2)) || \ (LOBYTE(LOWORD(::GetVersion())) > 6)) #define IS_WINVER_ATLEAST(maj, min) \ diff --git a/src/java.desktop/windows/native/libawt/windows/awt_DesktopProperties.cpp b/src/java.desktop/windows/native/libawt/windows/awt_DesktopProperties.cpp index d5ad022c1e0..a00938f764f 100644 --- a/src/java.desktop/windows/native/libawt/windows/awt_DesktopProperties.cpp +++ b/src/java.desktop/windows/native/libawt/windows/awt_DesktopProperties.cpp @@ -272,20 +272,8 @@ void AwtDesktopProperties::GetNonClientParameters() { // general window properties // NONCLIENTMETRICS ncmetrics; + ncmetrics.cbSize = sizeof(ncmetrics); - // Fix for 6944516: specify correct size for ncmetrics on WIN2K/XP - // Microsoft recommend to subtract the size of 'iPaddedBorderWidth' field - // when running on XP. However this can't be referenced at compile time - // with the older SDK, so there use 'lfMessageFont' plus its size. - if (!IS_WINVISTA) { -#if defined(_MSC_VER) - ncmetrics.cbSize = offsetof(NONCLIENTMETRICS, iPaddedBorderWidth); -#else - ncmetrics.cbSize = offsetof(NONCLIENTMETRICS,lfMessageFont) + sizeof(LOGFONT); -#endif - } else { - ncmetrics.cbSize = sizeof(ncmetrics); - } VERIFY( SystemParametersInfo(SPI_GETNONCLIENTMETRICS, ncmetrics.cbSize, &ncmetrics, FALSE) ); float invScaleX; diff --git a/src/java.desktop/windows/native/libawt/windows/awt_MenuItem.cpp b/src/java.desktop/windows/native/libawt/windows/awt_MenuItem.cpp index ff7e01df3e8..d1a4fc68d03 100644 --- a/src/java.desktop/windows/native/libawt/windows/awt_MenuItem.cpp +++ b/src/java.desktop/windows/native/libawt/windows/awt_MenuItem.cpp @@ -395,9 +395,7 @@ AwtMenuItem::DrawSelf(DRAWITEMSTRUCT& drawInfo) //draw check mark int checkWidth = ::GetSystemMetrics(SM_CXMENUCHECK); // Workaround for CR#6401956 - if (IS_WINVISTA) { - AdjustCheckWidth(checkWidth); - } + AdjustCheckWidth(checkWidth); if (IsCheckbox()) { // means that target is a java.awt.CheckboxMenuItem @@ -558,9 +556,7 @@ void AwtMenuItem::MeasureSelf(HDC hDC, MEASUREITEMSTRUCT& measureInfo) if (!IsTopMenu()) { int checkWidth = ::GetSystemMetrics(SM_CXMENUCHECK); // Workaround for CR#6401956 - if (IS_WINVISTA) { - AdjustCheckWidth(checkWidth); - } + AdjustCheckWidth(checkWidth); measureInfo.itemWidth += checkWidth; // Add in shortcut width, if one exists. diff --git a/src/java.desktop/windows/native/libawt/windows/awt_TextArea.cpp b/src/java.desktop/windows/native/libawt/windows/awt_TextArea.cpp index 6dc21c5adda..282bd194f2f 100644 --- a/src/java.desktop/windows/native/libawt/windows/awt_TextArea.cpp +++ b/src/java.desktop/windows/native/libawt/windows/awt_TextArea.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -78,7 +78,7 @@ void AwtTextArea::EditSetSel(CHARRANGE &cr) { SendMessage(EM_EXSETSEL, 0, reinterpret_cast(&cr)); SendMessage(EM_HIDESELECTION, TRUE, TRUE); // 6417581: force expected drawing - if (IS_WINVISTA && cr.cpMin == cr.cpMax) { + if (cr.cpMin == cr.cpMax) { ::InvalidateRect(GetHWnd(), NULL, TRUE); } } diff --git a/src/java.desktop/windows/native/libawt/windows/awt_TextField.cpp b/src/java.desktop/windows/native/libawt/windows/awt_TextField.cpp index 5518ab91145..c1b682ffa46 100644 --- a/src/java.desktop/windows/native/libawt/windows/awt_TextField.cpp +++ b/src/java.desktop/windows/native/libawt/windows/awt_TextField.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -55,7 +55,7 @@ void AwtTextField::EditSetSel(CHARRANGE &cr) { SendMessage(EM_EXSETSEL, 0, reinterpret_cast(&cr)); // 6417581: force expected drawing - if (IS_WINVISTA && cr.cpMin == cr.cpMax) { + if (cr.cpMin == cr.cpMax) { ::InvalidateRect(GetHWnd(), NULL, TRUE); } diff --git a/src/java.desktop/windows/native/libawt/windows/awt_Win32GraphicsEnv.cpp b/src/java.desktop/windows/native/libawt/windows/awt_Win32GraphicsEnv.cpp index cc53f4a3322..5aa7731c8a6 100644 --- a/src/java.desktop/windows/native/libawt/windows/awt_Win32GraphicsEnv.cpp +++ b/src/java.desktop/windows/native/libawt/windows/awt_Win32GraphicsEnv.cpp @@ -90,20 +90,13 @@ void DWMResetCompositionEnabled() { } /** - * Returns true if dwm composition is enabled, false if it is not applicable - * (if the OS is not Vista) or dwm composition is disabled. + * Returns true if DWM composition is enabled, false if DWM composition is disabled. */ BOOL DWMIsCompositionEnabled() { - // cheaper to check than whether it's vista or not if (dwmIsCompositionEnabled != DWM_COMP_UNDEFINED) { return (BOOL)dwmIsCompositionEnabled; } - if (!IS_WINVISTA) { - dwmIsCompositionEnabled = FALSE; - return FALSE; - } - BOOL bRes = FALSE; try { @@ -337,13 +330,3 @@ Java_sun_awt_Win32GraphicsEnvironment_getYResolution(JNIEnv *env, jobject wge) CATCH_BAD_ALLOC_RET(0); } -/* - * Class: sun_awt_Win32GraphicsEnvironment - * Method: isVistaOS - * Signature: ()Z - */ -JNIEXPORT jboolean JNICALL Java_sun_awt_Win32GraphicsEnvironment_isVistaOS - (JNIEnv *env, jclass wgeclass) -{ - return IS_WINVISTA; -} From be40b6bcdab37368ea3c769e575b36e290d0c6a1 Mon Sep 17 00:00:00 2001 From: Ashay Rane Date: Tue, 14 Jul 2026 09:12:36 +0000 Subject: [PATCH 139/305] 8387188: JImageExtractTest.java test should deny APPEND_DATA ACL in `testExtractToReadOnlyDir` Reviewed-by: alanb, dbalek --- test/jdk/tools/jimage/JImageExtractTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/jdk/tools/jimage/JImageExtractTest.java b/test/jdk/tools/jimage/JImageExtractTest.java index eec10439cf5..e5f62232097 100644 --- a/test/jdk/tools/jimage/JImageExtractTest.java +++ b/test/jdk/tools/jimage/JImageExtractTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -188,7 +188,8 @@ public class JImageExtractTest extends JImageCliTest { AclEntry entry = AclEntry.newBuilder() .setType(AclEntryType.DENY) .setPrincipal(fileOwner) - .setPermissions(AclEntryPermission.WRITE_DATA) + .setPermissions(AclEntryPermission.WRITE_DATA, + AclEntryPermission.APPEND_DATA) .setFlags(AclEntryFlag.FILE_INHERIT, AclEntryFlag.DIRECTORY_INHERIT) .build(); List acl = view.getAcl(); From 6e2a4f847fc91ab25a167ca833bdeaaad5ee8d48 Mon Sep 17 00:00:00 2001 From: Jatin Bhateja Date: Tue, 14 Jul 2026 12:57:49 +0000 Subject: [PATCH 140/305] 8386322: Float16Vector.toString should render lane values using Float16.toString Reviewed-by: psandoz, rgiulietti --- .../jdk/incubator/vector/Float16Vector.java | 12 ++++++++---- .../jdk/incubator/vector/X-Vector.java.template | 16 ++++++++++++++++ .../incubator/vector/Float16Vector128Tests.java | 3 ++- .../incubator/vector/Float16Vector256Tests.java | 3 ++- .../incubator/vector/Float16Vector512Tests.java | 3 ++- .../incubator/vector/Float16Vector64Tests.java | 3 ++- .../incubator/vector/Float16VectorMaxTests.java | 3 ++- .../vector/templates/Unit-Miscellaneous.template | 7 ++++++- 8 files changed, 40 insertions(+), 10 deletions(-) diff --git a/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/Float16Vector.java b/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/Float16Vector.java index ce3a67357f9..a42fb44dd02 100644 --- a/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/Float16Vector.java +++ b/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/Float16Vector.java @@ -3707,8 +3707,10 @@ public abstract sealed class Float16Vector extends AbstractVector * in lane order. * * The string is produced as if by a call to {@link - * java.util.Arrays#toString(short[]) Arrays.toString()}, - * as appropriate to the {@code short} array returned by + * java.util.Arrays#toString(Object[]) Arrays.toString()}, + * as appropriate to a {@code Float16} array whose elements + * are obtained by applying {@link Float16#shortBitsToFloat16(short)} + * to each element of the {@code short[]} array returned by * {@link #toArray this.toArray()}. * * @return a string of the form {@code "[0,1,2...]"} @@ -3718,8 +3720,10 @@ public abstract sealed class Float16Vector extends AbstractVector @ForceInline public final String toString() { - // now that toArray is strongly typed, we can define this - return Arrays.toString(toArray()); + // Render the lanes as Float16 values; Float16.toString produces + // human-readable text and canonicalizes NaN, Infinity and -0.0 + // independent of the underlying bit encoding. + return Arrays.toString(toFloat16Array()); } /** diff --git a/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/X-Vector.java.template b/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/X-Vector.java.template index f11c6283685..00445cc8ac5 100644 --- a/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/X-Vector.java.template +++ b/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/X-Vector.java.template @@ -5723,10 +5723,19 @@ public abstract sealed class $abstractvectortype$ extends AbstractVector<$Boxtyp * {@code "[0,1,2...]"}, reporting the lane values of this vector, * in lane order. * +#if[FP16] + * The string is produced as if by a call to {@link + * java.util.Arrays#toString(Object[]) Arrays.toString()}, + * as appropriate to a {@code Float16} array whose elements + * are obtained by applying {@link Float16#shortBitsToFloat16(short)} + * to each element of the {@code short[]} array returned by + * {@link #toArray this.toArray()}. +#else[FP16] * The string is produced as if by a call to {@link * java.util.Arrays#toString($type$[]) Arrays.toString()}, * as appropriate to the {@code $type$} array returned by * {@link #toArray this.toArray()}. +#end[FP16] * * @return a string of the form {@code "[0,1,2...]"} * reporting the lane values of this vector @@ -5735,8 +5744,15 @@ public abstract sealed class $abstractvectortype$ extends AbstractVector<$Boxtyp @ForceInline public final String toString() { +#if[FP16] + // Render the lanes as Float16 values; Float16.toString produces + // human-readable text and canonicalizes NaN, Infinity and -0.0 + // independent of the underlying bit encoding. + return Arrays.toString(toFloat16Array()); +#else[FP16] // now that toArray is strongly typed, we can define this return Arrays.toString(toArray()); +#end[FP16] } /** diff --git a/test/jdk/jdk/incubator/vector/Float16Vector128Tests.java b/test/jdk/jdk/incubator/vector/Float16Vector128Tests.java index a33e83d14ea..215f6ef9c47 100644 --- a/test/jdk/jdk/incubator/vector/Float16Vector128Tests.java +++ b/test/jdk/jdk/incubator/vector/Float16Vector128Tests.java @@ -5412,7 +5412,8 @@ public class Float16Vector128Tests extends AbstractVectorTest { String str = av.toString(); short subarr[] = Arrays.copyOfRange(a, i, i + SPECIES.length()); - Assert.assertTrue(str.equals(Arrays.toString(subarr)), "at index " + i + ", string should be = " + Arrays.toString(subarr) + ", but is = " + str); + String expectedStr = Arrays.toString(toFloat16Array(subarr)); + Assert.assertTrue(str.equals(expectedStr), "at index " + i + ", string should be = " + expectedStr + ", but is = " + str); } } diff --git a/test/jdk/jdk/incubator/vector/Float16Vector256Tests.java b/test/jdk/jdk/incubator/vector/Float16Vector256Tests.java index 99b167d4024..f198a4a970f 100644 --- a/test/jdk/jdk/incubator/vector/Float16Vector256Tests.java +++ b/test/jdk/jdk/incubator/vector/Float16Vector256Tests.java @@ -5412,7 +5412,8 @@ public class Float16Vector256Tests extends AbstractVectorTest { String str = av.toString(); short subarr[] = Arrays.copyOfRange(a, i, i + SPECIES.length()); - Assert.assertTrue(str.equals(Arrays.toString(subarr)), "at index " + i + ", string should be = " + Arrays.toString(subarr) + ", but is = " + str); + String expectedStr = Arrays.toString(toFloat16Array(subarr)); + Assert.assertTrue(str.equals(expectedStr), "at index " + i + ", string should be = " + expectedStr + ", but is = " + str); } } diff --git a/test/jdk/jdk/incubator/vector/Float16Vector512Tests.java b/test/jdk/jdk/incubator/vector/Float16Vector512Tests.java index 1c391497015..3d2b7de23f9 100644 --- a/test/jdk/jdk/incubator/vector/Float16Vector512Tests.java +++ b/test/jdk/jdk/incubator/vector/Float16Vector512Tests.java @@ -5412,7 +5412,8 @@ public class Float16Vector512Tests extends AbstractVectorTest { String str = av.toString(); short subarr[] = Arrays.copyOfRange(a, i, i + SPECIES.length()); - Assert.assertTrue(str.equals(Arrays.toString(subarr)), "at index " + i + ", string should be = " + Arrays.toString(subarr) + ", but is = " + str); + String expectedStr = Arrays.toString(toFloat16Array(subarr)); + Assert.assertTrue(str.equals(expectedStr), "at index " + i + ", string should be = " + expectedStr + ", but is = " + str); } } diff --git a/test/jdk/jdk/incubator/vector/Float16Vector64Tests.java b/test/jdk/jdk/incubator/vector/Float16Vector64Tests.java index 6ef651860ad..c44f0f7c576 100644 --- a/test/jdk/jdk/incubator/vector/Float16Vector64Tests.java +++ b/test/jdk/jdk/incubator/vector/Float16Vector64Tests.java @@ -5412,7 +5412,8 @@ public class Float16Vector64Tests extends AbstractVectorTest { String str = av.toString(); short subarr[] = Arrays.copyOfRange(a, i, i + SPECIES.length()); - Assert.assertTrue(str.equals(Arrays.toString(subarr)), "at index " + i + ", string should be = " + Arrays.toString(subarr) + ", but is = " + str); + String expectedStr = Arrays.toString(toFloat16Array(subarr)); + Assert.assertTrue(str.equals(expectedStr), "at index " + i + ", string should be = " + expectedStr + ", but is = " + str); } } diff --git a/test/jdk/jdk/incubator/vector/Float16VectorMaxTests.java b/test/jdk/jdk/incubator/vector/Float16VectorMaxTests.java index 61efa3de9a0..92945d74ceb 100644 --- a/test/jdk/jdk/incubator/vector/Float16VectorMaxTests.java +++ b/test/jdk/jdk/incubator/vector/Float16VectorMaxTests.java @@ -5418,7 +5418,8 @@ public class Float16VectorMaxTests extends AbstractVectorTest { String str = av.toString(); short subarr[] = Arrays.copyOfRange(a, i, i + SPECIES.length()); - Assert.assertTrue(str.equals(Arrays.toString(subarr)), "at index " + i + ", string should be = " + Arrays.toString(subarr) + ", but is = " + str); + String expectedStr = Arrays.toString(toFloat16Array(subarr)); + Assert.assertTrue(str.equals(expectedStr), "at index " + i + ", string should be = " + expectedStr + ", but is = " + str); } } diff --git a/test/jdk/jdk/incubator/vector/templates/Unit-Miscellaneous.template b/test/jdk/jdk/incubator/vector/templates/Unit-Miscellaneous.template index 0ae9342539f..b673e5e1fab 100644 --- a/test/jdk/jdk/incubator/vector/templates/Unit-Miscellaneous.template +++ b/test/jdk/jdk/incubator/vector/templates/Unit-Miscellaneous.template @@ -87,7 +87,12 @@ String str = av.toString(); $type$ subarr[] = Arrays.copyOfRange(a, i, i + SPECIES.length()); - Assert.assertTrue(str.equals(Arrays.toString(subarr)), "at index " + i + ", string should be = " + Arrays.toString(subarr) + ", but is = " + str); +#if[FP16] + String expectedStr = Arrays.toString(toFloat16Array(subarr)); +#else[FP16] + String expectedStr = Arrays.toString(subarr); +#end[FP16] + Assert.assertTrue(str.equals(expectedStr), "at index " + i + ", string should be = " + expectedStr + ", but is = " + str); } } From feb944c6c9396c7aa2b34e70366402d02b42693f Mon Sep 17 00:00:00 2001 From: Benjamin Peterson Date: Tue, 14 Jul 2026 13:29:26 +0000 Subject: [PATCH 141/305] 8387637: Dead code for upwards interpreter stacks Reviewed-by: coleenp, dholmes, shade --- src/hotspot/cpu/aarch64/frame_aarch64.hpp | 2 -- src/hotspot/cpu/arm/frame_arm.hpp | 2 -- src/hotspot/cpu/ppc/frame_ppc.hpp | 2 -- src/hotspot/cpu/riscv/frame_riscv.hpp | 2 -- src/hotspot/cpu/s390/frame_s390.hpp | 2 -- src/hotspot/cpu/x86/frame_x86.hpp | 2 -- src/hotspot/cpu/zero/frame_zero.hpp | 2 -- .../share/interpreter/abstractInterpreter.hpp | 6 ++--- src/hotspot/share/runtime/frame.cpp | 25 +++++-------------- src/hotspot/share/runtime/vframe.cpp | 6 +---- src/hotspot/share/runtime/vframeArray.cpp | 7 +----- 11 files changed, 11 insertions(+), 47 deletions(-) diff --git a/src/hotspot/cpu/aarch64/frame_aarch64.hpp b/src/hotspot/cpu/aarch64/frame_aarch64.hpp index ac4740645b8..55a6dde38f5 100644 --- a/src/hotspot/cpu/aarch64/frame_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/frame_aarch64.hpp @@ -186,8 +186,6 @@ // deoptimization support void interpreter_frame_set_last_sp(intptr_t* sp); - static jint interpreter_frame_expression_stack_direction() { return -1; } - // returns the sending frame, without applying any barriers inline frame sender_raw(RegisterMap* map) const; diff --git a/src/hotspot/cpu/arm/frame_arm.hpp b/src/hotspot/cpu/arm/frame_arm.hpp index 2ef44414e1c..8ca7a555b93 100644 --- a/src/hotspot/cpu/arm/frame_arm.hpp +++ b/src/hotspot/cpu/arm/frame_arm.hpp @@ -122,6 +122,4 @@ // helper to update a map with callee-saved FP static void update_map_with_saved_link(RegisterMap* map, intptr_t** link_addr); - static jint interpreter_frame_expression_stack_direction() { return -1; } - #endif // CPU_ARM_FRAME_ARM_HPP diff --git a/src/hotspot/cpu/ppc/frame_ppc.hpp b/src/hotspot/cpu/ppc/frame_ppc.hpp index bf49bbb7e01..43d5fd41068 100644 --- a/src/hotspot/cpu/ppc/frame_ppc.hpp +++ b/src/hotspot/cpu/ppc/frame_ppc.hpp @@ -407,8 +407,6 @@ align_wiggle = 1 }; - static jint interpreter_frame_expression_stack_direction() { return -1; } - // returns the sending frame, without applying any barriers inline frame sender_raw(RegisterMap* map) const; diff --git a/src/hotspot/cpu/riscv/frame_riscv.hpp b/src/hotspot/cpu/riscv/frame_riscv.hpp index d5f04ee3ff7..5cf341aa21b 100644 --- a/src/hotspot/cpu/riscv/frame_riscv.hpp +++ b/src/hotspot/cpu/riscv/frame_riscv.hpp @@ -218,8 +218,6 @@ // deoptimization support void interpreter_frame_set_last_sp(intptr_t* last_sp); - static jint interpreter_frame_expression_stack_direction() { return -1; } - // returns the sending frame, without applying any barriers inline frame sender_raw(RegisterMap* map) const; diff --git a/src/hotspot/cpu/s390/frame_s390.hpp b/src/hotspot/cpu/s390/frame_s390.hpp index 36fc5970cd8..8a99bdb8df5 100644 --- a/src/hotspot/cpu/s390/frame_s390.hpp +++ b/src/hotspot/cpu/s390/frame_s390.hpp @@ -572,8 +572,6 @@ align_wiggle = 0 }; - static jint interpreter_frame_expression_stack_direction() { return -1; } - // returns the sending frame, without applying any barriers inline frame sender_raw(RegisterMap* map) const; diff --git a/src/hotspot/cpu/x86/frame_x86.hpp b/src/hotspot/cpu/x86/frame_x86.hpp index d97e6b847b4..50f0c6b7eb6 100644 --- a/src/hotspot/cpu/x86/frame_x86.hpp +++ b/src/hotspot/cpu/x86/frame_x86.hpp @@ -170,8 +170,6 @@ // deoptimization support void interpreter_frame_set_last_sp(intptr_t* sp); - static jint interpreter_frame_expression_stack_direction() { return -1; } - // returns the sending frame, without applying any barriers inline frame sender_raw(RegisterMap* map) const; diff --git a/src/hotspot/cpu/zero/frame_zero.hpp b/src/hotspot/cpu/zero/frame_zero.hpp index 45d1cb82e82..514b134e36b 100644 --- a/src/hotspot/cpu/zero/frame_zero.hpp +++ b/src/hotspot/cpu/zero/frame_zero.hpp @@ -82,8 +82,6 @@ char* buf, int buflen) const; - static jint interpreter_frame_expression_stack_direction() { return -1; } - inline address* sender_pc_addr() const; template diff --git a/src/hotspot/share/interpreter/abstractInterpreter.hpp b/src/hotspot/share/interpreter/abstractInterpreter.hpp index 23618cb037e..6c555f0c008 100644 --- a/src/hotspot/share/interpreter/abstractInterpreter.hpp +++ b/src/hotspot/share/interpreter/abstractInterpreter.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -262,8 +262,8 @@ class AbstractInterpreter: AllStatic { #endif // Local values relative to locals[n] - static int local_offset_in_bytes(int n) { - return ((frame::interpreter_frame_expression_stack_direction() * n) * stackElementSize); + static int local_offset_in_bytes(int n) { + return -n * stackElementSize; } // access to stacked values according to type: diff --git a/src/hotspot/share/runtime/frame.cpp b/src/hotspot/share/runtime/frame.cpp index 3e45b6fe310..2b0dd59deba 100644 --- a/src/hotspot/share/runtime/frame.cpp +++ b/src/hotspot/share/runtime/frame.cpp @@ -500,23 +500,16 @@ intptr_t* frame::interpreter_frame_local_at(int index) const { } intptr_t* frame::interpreter_frame_expression_stack_at(jint offset) const { - const int i = offset * interpreter_frame_expression_stack_direction(); - const int n = i * Interpreter::stackElementWords; - return &(interpreter_frame_expression_stack()[n]); + const int n = offset * Interpreter::stackElementWords; + return interpreter_frame_expression_stack() - n; } jint frame::interpreter_frame_expression_stack_size() const { // Number of elements on the interpreter expression stack // Callers should span by stackElementWords int element_size = Interpreter::stackElementWords; - size_t stack_size = 0; - if (frame::interpreter_frame_expression_stack_direction() < 0) { - stack_size = (interpreter_frame_expression_stack() - - interpreter_frame_tos_address() + 1)/element_size; - } else { - stack_size = (interpreter_frame_tos_address() - - interpreter_frame_expression_stack() + 1)/element_size; - } + size_t stack_size = (interpreter_frame_expression_stack() - + interpreter_frame_tos_address() + 1)/element_size; assert(stack_size <= (size_t)max_jint, "stack size too big"); return (jint)stack_size; } @@ -791,14 +784,8 @@ class InterpreterFrameClosure : public OffsetClosure { } else { addr = (oop*) _fr->interpreter_frame_expression_stack_at((offset - _max_locals)); // In case of exceptions, the expression stack is invalid and the esp will be reset to express - // this condition. Therefore, we call f only if addr is 'inside' the stack (i.e., addr >= esp for Intel). - bool in_stack; - if (frame::interpreter_frame_expression_stack_direction() > 0) { - in_stack = (intptr_t*)addr <= _fr->interpreter_frame_tos_address(); - } else { - in_stack = (intptr_t*)addr >= _fr->interpreter_frame_tos_address(); - } - if (in_stack) { + // this condition. Therefore, we call f only if addr is 'inside' the stack (i.e., addr >= esp). + if ((intptr_t*)addr >= _fr->interpreter_frame_tos_address()) { _f->do_oop(addr); } } diff --git a/src/hotspot/share/runtime/vframe.cpp b/src/hotspot/share/runtime/vframe.cpp index c9628255e45..02386537004 100644 --- a/src/hotspot/share/runtime/vframe.cpp +++ b/src/hotspot/share/runtime/vframe.cpp @@ -318,13 +318,9 @@ static StackValue* create_stack_value_from_oop_map(const InterpreterOopMap& oop_ static bool is_in_expression_stack(const frame& fr, const intptr_t* const addr) { assert(addr != nullptr, "invariant"); - // Ensure to be 'inside' the expression stack (i.e., addr >= sp for Intel). + // Ensure to be 'inside' the expression stack (i.e., addr >= sp). // In case of exceptions, the expression stack is invalid and the sp // will be reset to express this condition. - if (frame::interpreter_frame_expression_stack_direction() > 0) { - return addr <= fr.interpreter_frame_tos_address(); - } - return addr >= fr.interpreter_frame_tos_address(); } diff --git a/src/hotspot/share/runtime/vframeArray.cpp b/src/hotspot/share/runtime/vframeArray.cpp index 6810d7bb8d3..0885262eefb 100644 --- a/src/hotspot/share/runtime/vframeArray.cpp +++ b/src/hotspot/share/runtime/vframeArray.cpp @@ -473,12 +473,7 @@ void vframeArrayElement::unpack_on_stack(int caller_actual_parameters, "expression stack size should have been extended"); #endif // ASSERT int top_element = iframe()->interpreter_frame_expression_stack_size()-1; - intptr_t* base; - if (frame::interpreter_frame_expression_stack_direction() < 0) { - base = iframe()->interpreter_frame_expression_stack_at(top_element); - } else { - base = iframe()->interpreter_frame_expression_stack(); - } + intptr_t* base = iframe()->interpreter_frame_expression_stack_at(top_element); Copy::conjoint_jbytes(saved_args, base, popframe_preserved_args_size_in_bytes); From f71c37bdb3370d56e830de2957bdcaf4879c25cb Mon Sep 17 00:00:00 2001 From: Mark Powers Date: Tue, 14 Jul 2026 14:47:16 +0000 Subject: [PATCH 142/305] =?UTF-8?q?8358549:=20O(n=C2=B2)=20time=20complexi?= =?UTF-8?q?ty=20in=20java.security.Provider.parseLegacy()=20method?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reviewed-by: mullan, djelinski --- .../share/classes/java/security/Provider.java | 13 ++++++++++--- .../java/security/Provider/SupportsParameter.java | 8 +++++--- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/java.base/share/classes/java/security/Provider.java b/src/java.base/share/classes/java/security/Provider.java index f95caa1d920..e4b6109bfb0 100644 --- a/src/java.base/share/classes/java/security/Provider.java +++ b/src/java.base/share/classes/java/security/Provider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -1084,9 +1084,16 @@ public abstract class Provider extends Properties { String stdAlg = attrString.substring(0, i).intern(); String attrName = attrString.substring(i + 1); // kill additional spaces - while (attrName.startsWith(" ")) { - attrName = attrName.substring(1); + int pos = 0; + for (; pos < attrName.length(); pos++) { + if (attrName.charAt(pos) != ' ') { + break; + } } + if (pos > 0) { + attrName = attrName.substring(pos); + } + attrName = attrName.intern(); ServiceKey stdKey = new ServiceKey(type, stdAlg, true); Service stdService = legacyMap.get(stdKey); diff --git a/test/jdk/java/security/Provider/SupportsParameter.java b/test/jdk/java/security/Provider/SupportsParameter.java index 039fb3d0797..3325ad9680a 100644 --- a/test/jdk/java/security/Provider/SupportsParameter.java +++ b/test/jdk/java/security/Provider/SupportsParameter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,7 +23,7 @@ /** * @test - * @bug 4911081 8130181 + * @bug 4911081 8130181 8358549 * @library /test/lib * @summary verify that Provider.Service.supportsParameter() works * @author Andreas Sterbenz @@ -112,7 +112,9 @@ public class SupportsParameter { put("Signature.DSA0", "foo.DSA0"); put("Signature.DSA", "foo.DSA"); - put("Signature.DSA SupportedKeyClasses", + // Extra spaces between "Signature.DSA" and "SupportedKeyClasses" + // are used to verify that whitespace is trimmed. + put("Signature.DSA SupportedKeyClasses", "java.security.interfaces.DSAPublicKey" + "|java.security.interfaces.DSAPrivateKey"); From e452e6c8b06e7ff3d47d0b3cd6fa8d73eb85b655 Mon Sep 17 00:00:00 2001 From: Vladimir Ivanov Date: Tue, 14 Jul 2026 16:13:18 +0000 Subject: [PATCH 143/305] 8374783: C2 compilation asserts with "slice of address and input slice don't match" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Vladimir Ivanov Co-authored-by: Roberto Castañeda Lozano Reviewed-by: chagedorn, qamai --- src/hotspot/share/opto/callGenerator.cpp | 36 +-- src/hotspot/share/opto/classes.hpp | 1 + src/hotspot/share/opto/compile.cpp | 8 + src/hotspot/share/opto/graphKit.hpp | 2 +- src/hotspot/share/opto/opaquenode.cpp | 8 + src/hotspot/share/opto/opaquenode.hpp | 13 + .../TestLateInliningWithSliceNarrowing.java | 225 ++++++++++++++++++ 7 files changed, 277 insertions(+), 16 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/inlining/TestLateInliningWithSliceNarrowing.java diff --git a/src/hotspot/share/opto/callGenerator.cpp b/src/hotspot/share/opto/callGenerator.cpp index d0b48982b0f..10df25abcb8 100644 --- a/src/hotspot/share/opto/callGenerator.cpp +++ b/src/hotspot/share/opto/callGenerator.cpp @@ -717,26 +717,32 @@ void CallGenerator::do_late_inline_helper() { C->inline_printer()->record(method(), jvms, InliningResult::SUCCESS, "late inline succeeded"); } - // Capture any exceptional control flow - GraphKit kit(new_jvms); - - // Find the result object - Node* result = C->top(); - int result_size = method()->return_type()->size(); - if (result_size != 0 && !kit.stopped()) { - result = (result_size == 1) ? kit.pop() : kit.pop_pair(); - } - - if (call->is_CallStaticJava() && call->as_CallStaticJava()->is_boxing_method()) { - result = kit.must_be_not_null(result, false); - } - if (inline_cg()->is_inline()) { C->set_has_loops(C->has_loops() || inline_cg()->method()->has_loops()); C->env()->notice_inlined_method(inline_cg()->method()); } C->set_inlining_progress(true); - C->set_do_cleanup(kit.stopped()); // path is dead; needs cleanup + + // Find the result object and capture any exceptional control flow. + GraphKit kit(new_jvms); + Node* result = C->top(); + + assert(!C->do_cleanup(), "already set"); + if (kit.stopped()) { + C->set_do_cleanup(true); // path is dead; needs cleanup + } else { + result = kit.pop_node(method()->return_type()->basic_type()); + if (result != C->top() && !result_not_used) { + if (call->is_CallStaticJava() && + call->as_CallStaticJava()->is_boxing_method()) { + result = kit.must_be_not_null(result, false); + } + // Limit result type propagation until next IGVN cleanup. + const Type* result_type = kit.gvn().type(callprojs.resproj); + result = kit.gvn().transform(new OpaqueParseNode(C, result, result_type)); + } + } + kit.replace_call(call, result, true, do_asserts); } } diff --git a/src/hotspot/share/opto/classes.hpp b/src/hotspot/share/opto/classes.hpp index 4d06e20875a..53a72f979db 100644 --- a/src/hotspot/share/opto/classes.hpp +++ b/src/hotspot/share/opto/classes.hpp @@ -288,6 +288,7 @@ macro(OpaqueZeroTripGuard) macro(OpaqueConstantBool) macro(OpaqueInitializedAssertionPredicate) macro(OpaqueTemplateAssertionPredicate) +macro(OpaqueParse) macro(PowD) macro(ProfileBoolean) macro(OrI) diff --git a/src/hotspot/share/opto/compile.cpp b/src/hotspot/share/opto/compile.cpp index e5f91875516..0c7083ed723 100644 --- a/src/hotspot/share/opto/compile.cpp +++ b/src/hotspot/share/opto/compile.cpp @@ -2051,10 +2051,13 @@ void Compile::inline_string_calls(bool parse_time) { _late_inlines_pos = _late_inlines.length(); } + assert(!do_cleanup(), "already set"); + while (_string_late_inlines.length() > 0) { CallGenerator* cg = _string_late_inlines.pop(); cg->do_late_inline(); if (failing()) return; + set_do_cleanup(false); // ignore and reset } _string_late_inlines.trunc_to(0); } @@ -2070,10 +2073,13 @@ void Compile::inline_boxing_calls(PhaseIterGVN& igvn) { _late_inlines_pos = _late_inlines.length(); + assert(!do_cleanup(), "already set"); + while (_boxing_late_inlines.length() > 0) { CallGenerator* cg = _boxing_late_inlines.pop(); cg->do_late_inline(); if (failing()) return; + set_do_cleanup(false); // ignore and reset } _boxing_late_inlines.trunc_to(0); @@ -2647,12 +2653,14 @@ void Compile::check_no_dead_use() const { #endif void Compile::inline_vector_reboxing_calls() { + assert(!do_cleanup(), "already set"); if (C->_vector_reboxing_late_inlines.length() > 0) { _late_inlines_pos = C->_late_inlines.length(); while (_vector_reboxing_late_inlines.length() > 0) { CallGenerator* cg = _vector_reboxing_late_inlines.pop(); cg->do_late_inline(); if (failing()) return; + assert(!do_cleanup(), "should not be set"); print_method(PHASE_INLINE_VECTOR_REBOX, 3, cg->call_node()); } _vector_reboxing_late_inlines.trunc_to(0); diff --git a/src/hotspot/share/opto/graphKit.hpp b/src/hotspot/share/opto/graphKit.hpp index d371dfb2e32..ef160ac6f1a 100644 --- a/src/hotspot/share/opto/graphKit.hpp +++ b/src/hotspot/share/opto/graphKit.hpp @@ -500,7 +500,7 @@ class GraphKit : public Phase { int n_size = type2size[n_type]; if (n_size == 1) return pop(); else if (n_size == 2) return pop_pair(); - else return nullptr; + else return C->top(); } Node* control() const { return map_not_null()->control(); } diff --git a/src/hotspot/share/opto/opaquenode.cpp b/src/hotspot/share/opto/opaquenode.cpp index 428379e84ae..a3b50d269fb 100644 --- a/src/hotspot/share/opto/opaquenode.cpp +++ b/src/hotspot/share/opto/opaquenode.cpp @@ -183,6 +183,14 @@ void OpaqueInitializedAssertionPredicateNode::dump_spec(outputStream* st) const } #endif // NOT PRODUCT +// Do NOT remove the opaque node until subsequent IGVN pass. +Node* OpaqueParseNode::Identity(PhaseGVN* phase) { + if (phase->is_IterGVN()) { + return in(1); + } + return this; +} + uint ProfileBooleanNode::hash() const { return NO_HASH; } bool ProfileBooleanNode::cmp( const Node &n ) const { return (&n == this); diff --git a/src/hotspot/share/opto/opaquenode.hpp b/src/hotspot/share/opto/opaquenode.hpp index bb3da2aa65f..7ec7e23144a 100644 --- a/src/hotspot/share/opto/opaquenode.hpp +++ b/src/hotspot/share/opto/opaquenode.hpp @@ -258,6 +258,19 @@ class OpaqueInitializedAssertionPredicateNode : public Node { NOT_PRODUCT(void dump_spec(outputStream* st) const); }; +// The node is used during late inlining to limit type propagation between cleanup phases. +// It avoids type paradoxes caused by divergence between recorded type and IR shapes +// during successive late inlining attempts. +class OpaqueParseNode : public TypeNode { + public: + OpaqueParseNode(Compile* C, Node* n, const Type* t) : TypeNode(t, 2) { + init_req(1, n); + C->record_for_igvn(this); + } + virtual int Opcode() const; + virtual Node* Identity(PhaseGVN* phase); +}; + //------------------------------ProfileBooleanNode------------------------------- // A node represents value profile for a boolean during parsing. // Once parsing is over, the node goes away (during IGVN). diff --git a/test/hotspot/jtreg/compiler/inlining/TestLateInliningWithSliceNarrowing.java b/test/hotspot/jtreg/compiler/inlining/TestLateInliningWithSliceNarrowing.java new file mode 100644 index 00000000000..fbae0454d07 --- /dev/null +++ b/test/hotspot/jtreg/compiler/inlining/TestLateInliningWithSliceNarrowing.java @@ -0,0 +1,225 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.inlining; + +import java.lang.reflect.Field; +import jdk.internal.misc.Unsafe; +import jdk.test.lib.Asserts; + +/** + * @test + * @bug 8374783 + * @summary Test that address type refinements after an incremental inlining + * step are propagated by IGVN before the next step. Failing to + * propagate such refinements could lead to slice mismatches between + * field-derived and IGVN-recorded address types when parsing bytecode + * in subsequent inlining steps. + * @library /test/lib + * @modules java.base/jdk.internal.misc + * @run main ${test.main.class} + * @run main/othervm -Xbatch + -XX:CompileCommand=compileonly,${test.main.class}::test* + -XX:CompileCommand=dontinline,${test.main.class}::notInlined* + -XX:CompileCommand=delayinline,${test.main.class}::late* + ${test.main.class} + */ + +class A { + int f; +} + +public class TestLateInliningWithSliceNarrowing { + + private static Unsafe UNSAFE = Unsafe.getUnsafe(); + private static final long F_OFFSET; + private static final long INT_ARRAY_OFFSET; + + static { + try { + Field fField = A.class.getDeclaredField("f"); + F_OFFSET = UNSAFE.objectFieldOffset(fField); + } catch (Exception e) { + throw new RuntimeException(e); + } + INT_ARRAY_OFFSET = UNSAFE.arrayBaseOffset(int[].class); + } + + static A notInlinedId(A a) { + return a; + } + + static long lateOffset() { + return F_OFFSET; + } + + static long lateOffsetMinusFour() { + return F_OFFSET - 4; + } + + static long lateOffsetDividedByTwo() { + return F_OFFSET / 2; + } + + static long lateArrayOffset() { + return INT_ARRAY_OFFSET; + } + + static void lateStore(A a) { + a.f = 42; + } + + static void lateArrayStore(int[] a) { + a[0] = 42; + } + + static int lateLoad(A a) { + return a.f; + } + + static Object lateBase(A a) { + return a; + } + + // Test that when lateStore() is inlined, the IGVN-recorded type of the + // accessed memory address (captured by an AddP) has been updated to reflect + // the compiler-known offset discovered by inlining lateOffset(). Failure to + // do so leads to a slice mismatch when parsing the inlined store. + static int testLoadFromLateDiscoveredOffsetThenStoreAtConstOffset(A a) { + long o = lateOffset(); + int val = UNSAFE.getInt(a, o); + lateStore(a); + return val; + } + + // Test that when lateLoad() is inlined, the IGVN-recorded type of the + // accessed memory address (captured by an AddP) has been updated to reflect + // the compiler-known offset discovered by inlining lateOffset(). Failure to + // do so leads to a slice mismatch when parsing the inlined load. + static int testLoadFromLateDiscoveredOffsetThenLoadFromConstOffset(A a) { + long o = lateOffset(); + int val = UNSAFE.getInt(a, o); + lateLoad(a); + return val; + } + + // Test a variation of the above where lateOffsetMinusFour() is not used + // directly by an AddP node. This test does not require updating the + // IGVN-recorded type of the accessed memory address for correctness, + // because lateStore() does not reuse the corresponding AddP node. + static int testLoadFromLateDiscoveredOffsetPlusFourThenStoreAtConstOffset(A a) { + long o = lateOffsetMinusFour(); + int val = UNSAFE.getInt(a, o + 4); + lateStore(a); + return val; + } + + // Test a variation of the above using a different arithmetic operation, + // with the same expectations. + static int testLoadFromLateDiscoveredOffsetTimesTwoThenStoreAtConstOffset(A a) { + long o = lateOffsetDividedByTwo(); + int val = UNSAFE.getInt(a, o * 2); + lateStore(a); + return val; + } + + // Test a variation of the first test where failing to update the + // IGVN-recorded type of the accessed memory address would result in a slice + // mismatch that will lead to an incorrect memory graph (the memory input of + // the last load would bypass the memory output of the store). + static int testLoadFromLateDiscoveredOffsetThenStoreAtConstOffsetThenReloadFromConstOffset(A a) { + A a2 = notInlinedId(a); + long o = lateOffset(); + int val = UNSAFE.getInt(a, o); + lateStore(a); + return a2.f + val; + } + + // Test a variation of the first test where the offset is compiler-known + // from the beginning, but the unsafe base address is only discovered by + // inlining lateBase(). This variation does not require a cleanup between + // the late inlining of lateBase() and lateLoad() for correctness: a slice + // mismatch cannot occur because the memory access within lateLoad() does + // not reuse the same address node (AddP) as the unsafe load. The unsafe + // load address node is not reusable by the lateLoad() access because it is + // obscured by casts by the time lateLoad() is late inlined. Making the + // address node reusable by both loads would require a cleanup round, which + // would prevent the mismatch from happening in the first place. + static int testLoadFromLateDiscoveredBaseThenLoadFromKnownBase(A a) { + Object obj = lateBase(a); + int val = UNSAFE.getInt(obj, F_OFFSET); + lateLoad(a); + return val; + } + + // Test a variation of the first test using an array instead of a class + // instance. No slice mismatch occurs because the address types for both + // memory accesses lead to the same slice, regardless of whether the offset + // is compiler-known. + static int testArrayLoadFromLateDiscoveredOffsetThenStoreAtConstOffset(int[] a) { + long o = lateArrayOffset(); + int val = UNSAFE.getInt(a, o); + lateArrayStore(a); + return val; + } + + public static void main(String[] args) { + for (int i = 0; i < 10_000; i++) { + { + A a = new A(); + int result = testLoadFromLateDiscoveredOffsetThenStoreAtConstOffset(a); + Asserts.assertEquals(0, result); + } + { + A a = new A(); + int result = testLoadFromLateDiscoveredOffsetThenLoadFromConstOffset(a); + Asserts.assertEquals(0, result); + } + { + A a = new A(); + int result = testLoadFromLateDiscoveredOffsetPlusFourThenStoreAtConstOffset(a); + Asserts.assertEquals(0, result); + } + { + A a = new A(); + int result = testLoadFromLateDiscoveredOffsetTimesTwoThenStoreAtConstOffset(a); + Asserts.assertEquals(0, result); + } + { + A a = new A(); + int result = testLoadFromLateDiscoveredOffsetThenStoreAtConstOffsetThenReloadFromConstOffset(a); + Asserts.assertEquals(42, result); + } + { + A a = new A(); + int result = testLoadFromLateDiscoveredBaseThenLoadFromKnownBase(a); + Asserts.assertEquals(0, result); + } + { + int[] a = new int[1]; + int result = testArrayLoadFromLateDiscoveredOffsetThenStoreAtConstOffset(a); + Asserts.assertEquals(0, result); + } + } + } +} From 499a25b2d05194461c2a4ed1d7bdb745b115f98f Mon Sep 17 00:00:00 2001 From: Quan Anh Mai Date: Tue, 14 Jul 2026 16:47:49 +0000 Subject: [PATCH 144/305] 8386503: C2: assert(adr_type == nullptr || adr_type->isa_aryptr() != nullptr) failed: unexpected type-unsafe store Reviewed-by: epeter, vlivanov --- src/hotspot/share/opto/memnode.cpp | 9 +- .../parsing/TestTypeUnsafeFieldStore.java | 240 ++++++++++++++++++ 2 files changed, 246 insertions(+), 3 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/parsing/TestTypeUnsafeFieldStore.java diff --git a/src/hotspot/share/opto/memnode.cpp b/src/hotspot/share/opto/memnode.cpp index 00ccb3e3dbc..165eb9e430c 100644 --- a/src/hotspot/share/opto/memnode.cpp +++ b/src/hotspot/share/opto/memnode.cpp @@ -1290,9 +1290,12 @@ Node* MemNode::can_see_stored_value(Node* st, PhaseValues* phase) const { return res; } - // Type-unsafe stores must be due to array polymorphism - const TypePtr* adr_type = this->adr_type(); - assert(adr_type == nullptr || adr_type->isa_aryptr() != nullptr, "unexpected type-unsafe store"); + // There are some cases in which the Type of the load is narrower than the Type of the value + // that is stored into that location. The most common case is array polymorphism, when the + // type of an array element depends on the type of the array. In addition, there are some + // corner cases, the first one is concurrent class loading, when CHA can result in a narrower + // Type than what is declared only after the child class is loaded, and the second case is + // unsafe accesses when we do not check for type safety. See JDK-8388184. return nullptr; } diff --git a/test/hotspot/jtreg/compiler/parsing/TestTypeUnsafeFieldStore.java b/test/hotspot/jtreg/compiler/parsing/TestTypeUnsafeFieldStore.java new file mode 100644 index 00000000000..d6bd097eec0 --- /dev/null +++ b/test/hotspot/jtreg/compiler/parsing/TestTypeUnsafeFieldStore.java @@ -0,0 +1,240 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package compiler.parsing; + +import jdk.internal.misc.Unsafe; +import jdk.test.whitebox.WhiteBox; + +/* + * @test + * @bug 8386503 + * @summary Test load folding from a field store with a less precise type + * @library /test/lib + * @requires vm.compiler2.enabled + * @modules java.base/jdk.internal.misc + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. -Xbatch -XX:-TieredCompilation + * -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI + * -XX:CompileOnly=${test.main.class}::test* + * -XX:CompileCommand=inline,${test.main.class}::inline* + * ${test.main.class} + */ +public class TestTypeUnsafeFieldStore { + private static final Unsafe UNSAFE = Unsafe.getUnsafe(); + private static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox(); + private static volatile Throwable failure; + + public static void main(String[] args) throws Exception { + for (int i = 0; i <= 10; i++) { + testConcurrentClassLoading(i); + } + Holder h = new Holder(); + Integer obj = 0; + for (int i = 0; i < 20000; i++) { + testUnsafeAccess(h, obj); + } + } + + // It's hard to coordinate the compiler thread with the thread that load the child class, so we + // randomly delay one of the threads + private static void testConcurrentClassLoading(int idx) throws Exception { + var parentClass = Class.forName("compiler.parsing.TestTypeUnsafeFieldStore$P" + idx); + var testMethod = TestTypeUnsafeFieldStore.class.getDeclaredMethod("testMethod" + idx, parentClass); + Thread compiler = new Thread(() -> { + try { + if (idx < 5) { + Thread.sleep((5 - idx) * 10L); + } + WHITE_BOX.markMethodProfiled(testMethod); + if (!WHITE_BOX.enqueueMethodForCompilation(testMethod, 4)) { + throw new RuntimeException("Could not enqueue the test method for C2 compilation"); + } + while (WHITE_BOX.isMethodQueuedForCompilation(testMethod)) { + Thread.yield(); + } + } catch (Throwable t) { + failure = t; + } + }); + compiler.start(); + if (idx > 5) { + Thread.sleep((idx - 5) * 10L); + } + Class.forName("compiler.parsing.TestTypeUnsafeFieldStore$C" + idx); + compiler.join(); + if (failure != null) { + throw new RuntimeException(failure); + } + } + + private static Integer testUnsafeAccess(Holder h, Object obj) { + UNSAFE.putReference(h, Holder.V_OFFSET, obj); + return h.v; + } + + private static class Holder { + private static final long V_OFFSET = UNSAFE.objectFieldOffset(Holder.class, "v"); + Integer v; + } + + // When the compiler parses the store, C has not been loaded, so obj is of type P. However, + // when the compiler parses the load, C has been loaded and is observed to be the unique + // concrete subclass of P, so the result of the load is of type C. Folding the load to obj will + // drop this information, thus is incorrect. + private static abstract class P0 {} + private static class C0 extends P0 {} + private static P0 staticField0; + private static P0 testMethod0(P0 obj) { + staticField0 = obj; + inline0(); + return staticField0; + } + + private static abstract class P1 {} + private static class C1 extends P1 {} + private static P1 staticField1; + private static P1 testMethod1(P1 obj) { + staticField1 = obj; + inline0(); + return staticField1; + } + + private static abstract class P2 {} + private static class C2 extends P2 {} + private static P2 staticField2; + private static P2 testMethod2(P2 obj) { + staticField2 = obj; + inline0(); + return staticField2; + } + + private static abstract class P3 {} + private static class C3 extends P3 {} + private static P3 staticField3; + private static P3 testMethod3(P3 obj) { + staticField3 = obj; + inline0(); + return staticField3; + } + + private static abstract class P4 {} + private static class C4 extends P4 {} + private static P4 staticField4; + private static P4 testMethod4(P4 obj) { + staticField4 = obj; + inline0(); + return staticField4; + } + + private static abstract class P5 {} + private static class C5 extends P5 {} + private static P5 staticField5; + private static P5 testMethod5(P5 obj) { + staticField5 = obj; + inline0(); + return staticField5; + } + + private static abstract class P6 {} + private static class C6 extends P6 {} + private static P6 staticField6; + private static P6 testMethod6(P6 obj) { + staticField6 = obj; + inline0(); + return staticField6; + } + + private static abstract class P7 {} + private static class C7 extends P7 {} + private static P7 staticField7; + private static P7 testMethod7(P7 obj) { + staticField7 = obj; + inline0(); + return staticField7; + } + + private static abstract class P8 {} + private static class C8 extends P8 {} + private static P8 staticField8; + private static P8 testMethod8(P8 obj) { + staticField8 = obj; + inline0(); + return staticField8; + } + + private static abstract class P9 {} + private static class C9 extends P9 {} + private static P9 staticField9; + private static P9 testMethod9(P9 obj) { + staticField9 = obj; + inline0(); + return staticField9; + } + + private static abstract class P10 {} + private static class C10 extends P10 {} + private static P10 staticField10; + private static P10 testMethod10(P10 obj) { + staticField10 = obj; + inline0(); + return staticField10; + } + + private static void inline0() { + inline1(); + inline1(); + inline1(); + inline1(); + } + + private static void inline1() { + inline2(); + inline2(); + inline2(); + inline2(); + } + + private static void inline2() { + inline3(); + inline3(); + inline3(); + inline3(); + } + + private static void inline3() { + inline4(); + inline4(); + inline4(); + inline4(); + } + + private static void inline4() { + inline5(); + inline5(); + inline5(); + inline5(); + } + + private static void inline5() {} +} From 5d24cecccd9d39d22c9d736b0f5283c49d8bc440 Mon Sep 17 00:00:00 2001 From: Albert Mingkun Yang Date: Tue, 14 Jul 2026 17:25:01 +0000 Subject: [PATCH 145/305] 8387048: NMTCommittedVirtualMemoryTracker.test_committed_virtualmemory_region_vm fails due to found_stack_top Reviewed-by: rtoyonaga, stuefe --- .../gtest/runtime/test_committed_virtualmemory.cpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/test/hotspot/gtest/runtime/test_committed_virtualmemory.cpp b/test/hotspot/gtest/runtime/test_committed_virtualmemory.cpp index 5d475d2f955..fcc1354c773 100644 --- a/test/hotspot/gtest/runtime/test_committed_virtualmemory.cpp +++ b/test/hotspot/gtest/runtime/test_committed_virtualmemory.cpp @@ -58,16 +58,15 @@ public: address i_addr = (address)&i; bool found_i_addr = false; - // stack grows downward + // Stack grows downward. address stack_top = stack_end + stack_size; - bool found_stack_top = false; { MemTracker::NmtVirtualMemoryLocker vml; + // For thread stacks, this historically named API visits resident ranges. + // Not all committed pages have to be resident. VirtualMemoryTracker::Instance::tree()->visit_committed_regions(rgn_found, [&](const VirtualMemoryRegion& rgn) { - if (rgn.base() + rgn.size() == stack_top) { - EXPECT_TRUE(rgn.size() <= stack_size); - found_stack_top = true; - } + EXPECT_GE(rgn.base(), stack_end); + EXPECT_LE(rgn.end(), stack_top); if (i_addr < stack_top && i_addr >= rgn.base()) { found_i_addr = true; } @@ -76,10 +75,9 @@ public: }); } - // stack and guard pages may be contiguous as one region + // Stack and guard pages may be contiguous as one region. ASSERT_TRUE(i >= 1); ASSERT_TRUE(found_i_addr); - ASSERT_TRUE(found_stack_top); } static const int PAGE_CONTAINED_IN_RANGE_TAG = -1; From 0efadf5f5401e1b3f7230c19e832d0acb35bd9d0 Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Tue, 14 Jul 2026 17:58:31 +0000 Subject: [PATCH 146/305] 8388139: Shenandoah: -XX:+VerifyOops fails on forwarded objects with COH Reviewed-by: wkemper, xpeng --- .../shenandoahBarrierSetAssembler_aarch64.cpp | 26 +++++++++++++++++++ .../shenandoahBarrierSetAssembler_aarch64.hpp | 1 + .../shenandoahBarrierSetAssembler_ppc.cpp | 20 ++++++++++++++ .../shenandoahBarrierSetAssembler_ppc.hpp | 2 ++ .../shenandoahBarrierSetAssembler_riscv.cpp | 26 +++++++++++++++++++ .../shenandoahBarrierSetAssembler_riscv.hpp | 1 + .../shenandoahBarrierSetAssembler_x86.cpp | 26 +++++++++++++++++++ .../shenandoahBarrierSetAssembler_x86.hpp | 1 + 8 files changed, 103 insertions(+) diff --git a/src/hotspot/cpu/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.cpp index 7406aa0c1c4..19c82ed77ef 100644 --- a/src/hotspot/cpu/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.cpp @@ -420,6 +420,32 @@ void ShenandoahBarrierSetAssembler::try_peek_weak_handle_in_nmethod(MacroAssembl __ bind(done); } +void ShenandoahBarrierSetAssembler::check_oop(MacroAssembler* masm, Register obj, Register tmp1, Register tmp2, Label& L_error) { + // Check if the oop is in the right area of memory + __ mov(tmp2, (intptr_t) Universe::verify_oop_mask()); + __ andr(tmp1, obj, tmp2); + __ mov(tmp2, (intptr_t) Universe::verify_oop_bits()); + + // Compare tmp1 and tmp2. We don't use a compare + // instruction here because the flags register is live. + __ eor(tmp1, tmp1, tmp2); + __ cbnz(tmp1, L_error); + + // This routine is sometimes called before applying GC barriers. + // With +COH, loading the klass may end up loading forwarding pointer instead. + Label L_skip; + if (UseCompactObjectHeaders) { + Address gc_state(rthread, in_bytes(ShenandoahThreadLocalData::gc_state_offset())); + __ ldrb(tmp1, gc_state); + __ tbnz(tmp1, ShenandoahHeap::HAS_FORWARDED_BITPOS, L_skip); + } + + // Make sure klass is 'reasonable', which is not zero. + __ load_narrow_klass(tmp1, obj); + __ cbz(tmp1, L_error); + __ bind(L_skip); +} + void ShenandoahBarrierSetAssembler::gen_write_ref_array_post_barrier(MacroAssembler* masm, DecoratorSet decorators, Register start, Register count, Register scratch) { assert(ShenandoahCardBarrier, "Should have been checked by caller"); diff --git a/src/hotspot/cpu/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.hpp b/src/hotspot/cpu/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.hpp index d25dd8871f9..62273a44da2 100644 --- a/src/hotspot/cpu/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.hpp @@ -74,6 +74,7 @@ public: Register obj, Register tmp, Label& slowpath); virtual void try_peek_weak_handle_in_nmethod(MacroAssembler* masm, Register weak_handle, Register obj, Register tmp, Label& slow_path); + virtual void check_oop(MacroAssembler* masm, Register obj, Register tmp1, Register tmp2, Label& L_error); #ifdef COMPILER1 void keepalive_barrier_c1_stub(LIR_Assembler* ce, ShenandoahKeepaliveBarrierStub* stub); diff --git a/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.cpp b/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.cpp index b17f0f924ae..7dbb0182266 100644 --- a/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.cpp +++ b/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.cpp @@ -659,6 +659,26 @@ void ShenandoahBarrierSetAssembler::try_peek_weak_handle_in_nmethod(MacroAssembl __ block_comment("} try_peek_weak_handle_in_nmethod (shenandoahgc)"); } +void ShenandoahBarrierSetAssembler::check_oop(MacroAssembler *masm, Register obj, const char* msg) { + if (!VerifyOops) { + return; + } + + __ mr(R0, obj); + + // This routine is sometimes called before applying GC barriers. + // With +COH, verification can touch the klass that may end up loading forwarding pointer instead. + Label L_skip; + if (UseCompactObjectHeaders) { + __ lbz(R0, in_bytes(ShenandoahThreadLocalData::gc_state_offset()), R16_thread); + __ andi_(R0, R0, ShenandoahHeap::HAS_FORWARDED); + __ bne(CR0, L_skip); + } + + __ verify_oop(R0, msg); + __ bind(L_skip); +} + void ShenandoahBarrierSetAssembler::gen_write_ref_array_post_barrier(MacroAssembler* masm, DecoratorSet decorators, Register addr, Register count, Register preserve) { assert(ShenandoahCardBarrier, "Should have been checked by caller"); diff --git a/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.hpp b/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.hpp index 8d741e6104b..0784c8b7148 100644 --- a/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.hpp +++ b/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.hpp @@ -125,6 +125,8 @@ public: virtual void try_peek_weak_handle_in_nmethod(MacroAssembler* masm, Register weak_handle, Register obj, Register tmp, Label& slow_path); + virtual void check_oop(MacroAssembler *masm, Register obj, const char* msg); + #ifdef COMPILER2 // Entry points from Matcher void load_c2(const MachNode* node, MacroAssembler* masm, Register dst, Register addr, int disp, Register tmp1, Register tmp2, bool narrow, bool acquire); diff --git a/src/hotspot/cpu/riscv/gc/shenandoah/shenandoahBarrierSetAssembler_riscv.cpp b/src/hotspot/cpu/riscv/gc/shenandoah/shenandoahBarrierSetAssembler_riscv.cpp index 574c70c8ea4..d7cfcb11205 100644 --- a/src/hotspot/cpu/riscv/gc/shenandoah/shenandoahBarrierSetAssembler_riscv.cpp +++ b/src/hotspot/cpu/riscv/gc/shenandoah/shenandoahBarrierSetAssembler_riscv.cpp @@ -433,6 +433,32 @@ void ShenandoahBarrierSetAssembler::try_peek_weak_handle_in_nmethod(MacroAssembl __ bind(done); } +void ShenandoahBarrierSetAssembler::check_oop(MacroAssembler* masm, Register obj, Register tmp1, Register tmp2, Label& L_error) { + // Check if the oop is in the right area of memory + __ mv(tmp2, (intptr_t) Universe::verify_oop_mask()); + __ andr(tmp1, obj, tmp2); + __ mv(tmp2, (intptr_t) Universe::verify_oop_bits()); + + // Compare tmp1 and tmp2. + __ bne(tmp1, tmp2, L_error); + + // This routine is sometimes called before applying GC barriers. + // With +COH, loading the klass may end up loading forwarding pointer instead. + Label L_skip; + if (UseCompactObjectHeaders) { + Address gc_state(xthread, ShenandoahThreadLocalData::gc_state_offset()); + __ lbu(tmp1, gc_state); + __ test_bit(tmp1, tmp1, ShenandoahHeap::HAS_FORWARDED_BITPOS); + __ bnez(tmp1, L_skip); + } + + // Make sure klass is 'reasonable', which is not zero. + __ load_narrow_klass(tmp1, obj); + __ beqz(tmp1, L_error); + + __ bind(L_skip); +} + void ShenandoahBarrierSetAssembler::gen_write_ref_array_post_barrier(MacroAssembler* masm, DecoratorSet decorators, Register start, Register count, Register tmp) { assert(ShenandoahCardBarrier, "Did you mean to enable ShenandoahCardBarrier?"); diff --git a/src/hotspot/cpu/riscv/gc/shenandoah/shenandoahBarrierSetAssembler_riscv.hpp b/src/hotspot/cpu/riscv/gc/shenandoah/shenandoahBarrierSetAssembler_riscv.hpp index ecb63e68a01..eb8ac653e2e 100644 --- a/src/hotspot/cpu/riscv/gc/shenandoah/shenandoahBarrierSetAssembler_riscv.hpp +++ b/src/hotspot/cpu/riscv/gc/shenandoah/shenandoahBarrierSetAssembler_riscv.hpp @@ -79,6 +79,7 @@ public: Register obj, Register tmp, Label& slowpath); virtual void try_peek_weak_handle_in_nmethod(MacroAssembler* masm, Register weak_handle, Register obj, Register tmp, Label& slow_path); + virtual void check_oop(MacroAssembler* masm, Register obj, Register tmp1, Register tmp2, Label& L_error); #ifdef COMPILER1 void keepalive_barrier_c1_stub(LIR_Assembler* ce, ShenandoahKeepaliveBarrierStub* stub); diff --git a/src/hotspot/cpu/x86/gc/shenandoah/shenandoahBarrierSetAssembler_x86.cpp b/src/hotspot/cpu/x86/gc/shenandoah/shenandoahBarrierSetAssembler_x86.cpp index bdb98d4b4c0..9ee1d2c0704 100644 --- a/src/hotspot/cpu/x86/gc/shenandoah/shenandoahBarrierSetAssembler_x86.cpp +++ b/src/hotspot/cpu/x86/gc/shenandoah/shenandoahBarrierSetAssembler_x86.cpp @@ -507,6 +507,32 @@ void ShenandoahBarrierSetAssembler::try_peek_weak_handle_in_nmethod(MacroAssembl __ bind(done); } +void ShenandoahBarrierSetAssembler::check_oop(MacroAssembler* masm, Register obj, Register tmp1, Register tmp2, Label& L_error) { + // Check if the oop is in the right area of memory + __ movptr(tmp1, obj); + __ movptr(tmp2, (intptr_t) Universe::verify_oop_mask()); + __ andptr(tmp1, tmp2); + __ movptr(tmp2, (intptr_t) Universe::verify_oop_bits()); + __ cmpptr(tmp1, tmp2); + __ jcc(Assembler::notZero, L_error); + + // This routine is sometimes called before applying GC barriers. + // With +COH, loading the klass may end up loading forwarding pointer instead. + Label L_skip; + if (UseCompactObjectHeaders) { + Address gc_state(r15_thread, ShenandoahThreadLocalData::gc_state_offset()); + __ testb(gc_state, ShenandoahHeap::HAS_FORWARDED); + __ jcc(Assembler::notZero, L_skip); + } + + // Make sure klass is 'reasonable', which is not zero. + __ load_narrow_klass(tmp1, obj); + __ testl(tmp1, tmp1); + __ jcc(Assembler::zero, L_error); + + __ bind(L_skip); +} + #ifdef PRODUCT #define BLOCK_COMMENT(str) /* nothing */ #else diff --git a/src/hotspot/cpu/x86/gc/shenandoah/shenandoahBarrierSetAssembler_x86.hpp b/src/hotspot/cpu/x86/gc/shenandoah/shenandoahBarrierSetAssembler_x86.hpp index 7f417d3c262..7c1a89b74f5 100644 --- a/src/hotspot/cpu/x86/gc/shenandoah/shenandoahBarrierSetAssembler_x86.hpp +++ b/src/hotspot/cpu/x86/gc/shenandoah/shenandoahBarrierSetAssembler_x86.hpp @@ -71,6 +71,7 @@ public: virtual void try_resolve_jobject_in_native(MacroAssembler* masm, Register jni_env, Register obj, Register tmp, Label& slowpath); virtual void try_peek_weak_handle_in_nmethod(MacroAssembler* masm, Register weak_handle, Register obj, Label& slowpath); + virtual void check_oop(MacroAssembler* masm, Register obj, Register tmp1, Register tmp2, Label& L_error); #ifdef COMPILER1 void keepalive_barrier_c1_stub(LIR_Assembler* ce, ShenandoahKeepaliveBarrierStub* stub); From 189dde7dfe55ba6c41eda9e7020abdfb50638935 Mon Sep 17 00:00:00 2001 From: William Kemper Date: Tue, 14 Jul 2026 18:42:48 +0000 Subject: [PATCH 147/305] 8388173: Shenandoah: Overly strict assertion failure in CAS barrier Reviewed-by: shade, xpeng --- .../gc/shenandoah/shenandoahBarrierSetAssembler_x86.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/hotspot/cpu/x86/gc/shenandoah/shenandoahBarrierSetAssembler_x86.cpp b/src/hotspot/cpu/x86/gc/shenandoah/shenandoahBarrierSetAssembler_x86.cpp index 9ee1d2c0704..480e484f4b1 100644 --- a/src/hotspot/cpu/x86/gc/shenandoah/shenandoahBarrierSetAssembler_x86.cpp +++ b/src/hotspot/cpu/x86/gc/shenandoah/shenandoahBarrierSetAssembler_x86.cpp @@ -705,10 +705,9 @@ void ShenandoahBarrierSetAssembler::compare_and_set_c2(const MachNode* node, Mac assert(oldval == rax, "must be in rax for implicit use in cmpxchg"); - // Oldval and newval can be in the same register, but all other registers should be - // distinct for extra safety, as we shuffle register values around. - assert_different_registers(oldval, tmp, addr.base(), addr.index()); - assert_different_registers(newval, tmp, addr.base(), addr.index()); + // Oldval and newval cannot be clobbered by aliasing with tmp. + assert_different_registers(oldval, tmp); + assert_different_registers(newval, tmp); ShenandoahBarrierStubC2::load_store_pre(masm, node, addr, tmp, noreg, noreg, narrow); @@ -729,7 +728,7 @@ void ShenandoahBarrierSetAssembler::compare_and_set_c2(const MachNode* node, Mac } void ShenandoahBarrierSetAssembler::get_and_set_c2(const MachNode* node, MacroAssembler* masm, Register newval, Address addr, Register tmp, bool narrow) { - assert_different_registers(newval, tmp, addr.base(), addr.index()); + assert_different_registers(newval, tmp); ShenandoahBarrierStubC2::load_store_pre(masm, node, addr, tmp, noreg, noreg, narrow); From fe430ad121464d731402ef4add6d57dadffe723e Mon Sep 17 00:00:00 2001 From: Vladimir Ivanov Date: Tue, 14 Jul 2026 19:14:06 +0000 Subject: [PATCH 148/305] 8387940: C2: Stress allocation elimination failures Reviewed-by: chagedorn, kvn, qamai --- src/hotspot/share/opto/c2_globals.hpp | 8 +++ src/hotspot/share/opto/compile.cpp | 3 +- src/hotspot/share/opto/macro.cpp | 26 ++++--- .../compiler/arguments/TestStressOptions.java | 6 +- .../StressEliminateAllocationsIRTest.java | 67 +++++++++++++++++++ 5 files changed, 100 insertions(+), 10 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/escapeAnalysis/StressEliminateAllocationsIRTest.java diff --git a/src/hotspot/share/opto/c2_globals.hpp b/src/hotspot/share/opto/c2_globals.hpp index cbe149fd01a..9ff88e8c310 100644 --- a/src/hotspot/share/opto/c2_globals.hpp +++ b/src/hotspot/share/opto/c2_globals.hpp @@ -601,6 +601,14 @@ "Number of fields in instance limit for scalar replacement") \ range(0, max_jint) \ \ + product(bool, StressEliminateAllocations, false, DIAGNOSTIC, \ + "Randomly fail allocation elimination attempts") \ + \ + product(uint, StressEliminateAllocationsMean, 20, DIAGNOSTIC, \ + "The expected number of elimination checks made until " \ + "a random failure.") \ + range(1, max_juint) \ + \ product(bool, OptimizePtrCompare, true, \ "Use escape analysis to optimize pointers compare") \ \ diff --git a/src/hotspot/share/opto/compile.cpp b/src/hotspot/share/opto/compile.cpp index 0c7083ed723..93d8e4c425d 100644 --- a/src/hotspot/share/opto/compile.cpp +++ b/src/hotspot/share/opto/compile.cpp @@ -748,7 +748,8 @@ Compile::Compile(ciEnv* ci_env, ciMethod* target, int osr_bci, if (StressLCM || StressGCM || StressIGVN || StressCCP || StressIncrementalInlining || StressMacroExpansion || StressMacroElimination || StressUnstableIfTraps || - StressBailout || StressLoopPeeling || StressCountedLoop) { + StressBailout || StressLoopPeeling || StressCountedLoop || + StressEliminateAllocations) { initialize_stress_seed(directive); } diff --git a/src/hotspot/share/opto/macro.cpp b/src/hotspot/share/opto/macro.cpp index 0ee073e8b06..a4d03970fcf 100644 --- a/src/hotspot/share/opto/macro.cpp +++ b/src/hotspot/share/opto/macro.cpp @@ -931,7 +931,9 @@ SafePointScalarObjectNode* PhaseMacroExpand::create_scalarized_object_descriptio // We weren't able to find a value for this field, // give up on eliminating this allocation. - if (field_val == nullptr) { + bool force_scalarization_failure = StressEliminateAllocations && + (C->random() % StressEliminateAllocationsMean == 0); + if (field_val == nullptr || force_scalarization_failure) { uint last = sfpt->req() - 1; for (int k = 0; k < j; k++) { sfpt->del_req(last--); @@ -940,13 +942,21 @@ SafePointScalarObjectNode* PhaseMacroExpand::create_scalarized_object_descriptio #ifndef PRODUCT if (PrintEliminateAllocations) { - if (field != nullptr) { - tty->print("=== At SafePoint node %d can't find value of field: ", sfpt->_idx); - field->print(); - int field_idx = C->get_alias_index(field_addr_type); - tty->print(" (alias_idx=%d)", field_idx); - } else { // Array's element - tty->print("=== At SafePoint node %d can't find value of array element [%d]", sfpt->_idx, j); + tty->print("=== At SafePoint node %d ", sfpt->_idx); + if (field_val == nullptr) { + tty->print_raw("can't find value of "); + + if (field != nullptr) { + tty->print_raw("field: "); + field->print(); + int field_idx = C->get_alias_index(field_addr_type); + tty->print(" (alias_idx=%d)", field_idx); + } else { // Array's element + tty->print("array element [%d]", j); + } + } else { + assert(force_scalarization_failure, "sanity"); + tty->print_raw("forcibly abort elimination"); } tty->print(", which prevents elimination of: "); if (res == nullptr) diff --git a/test/hotspot/jtreg/compiler/arguments/TestStressOptions.java b/test/hotspot/jtreg/compiler/arguments/TestStressOptions.java index 534ec9d2d97..99cf06110d6 100644 --- a/test/hotspot/jtreg/compiler/arguments/TestStressOptions.java +++ b/test/hotspot/jtreg/compiler/arguments/TestStressOptions.java @@ -24,7 +24,7 @@ /* * @test * @key stress randomness - * @bug 8252219 8256535 8317349 8319879 8335334 8325478 + * @bug 8252219 8256535 8317349 8319879 8335334 8325478 8387940 * @requires vm.compiler2.enabled * @summary Tests that different combinations of stress options and * -XX:StressSeed=N are accepted. @@ -60,6 +60,10 @@ * compiler.arguments.TestStressOptions * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+StressMacroElimination -XX:StressSeed=42 * compiler.arguments.TestStressOptions + * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+StressEliminateAllocations + * compiler.arguments.TestStressOptions + * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+StressEliminateAllocations -XX:StressSeed=42 + * compiler.arguments.TestStressOptions */ package compiler.arguments; diff --git a/test/hotspot/jtreg/compiler/escapeAnalysis/StressEliminateAllocationsIRTest.java b/test/hotspot/jtreg/compiler/escapeAnalysis/StressEliminateAllocationsIRTest.java new file mode 100644 index 00000000000..b97847b9b28 --- /dev/null +++ b/test/hotspot/jtreg/compiler/escapeAnalysis/StressEliminateAllocationsIRTest.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8387940 + * @requires vm.compiler2.enabled + * @summary C2: Stress allocation elimination failures + * + * @library /test/lib / + * @run driver ${test.main.class} + */ + +package compiler.escapeAnalysis; + +import compiler.lib.ir_framework.*; + +public class StressEliminateAllocationsIRTest { + public static void main(String[] args) { + TestFramework.runWithFlags("-XX:+UnlockDiagnosticVMOptions", + "-XX:+StressEliminateAllocations", + "-XX:StressEliminateAllocationsMean=1"); + } + + static class A { + final int i; + A(int i) { + this.i = i; + } + } + + @Test + @IR(counts = {IRNode.ALLOC, "1"}) + @Arguments(values = Argument.NUMBER_42) + private static int test(int i) { + // Even though the object is scalar replaceable, + // allocation elimination unconditionally fails in stress mode. + A a = new A(i); + + dontInline(); + + return a.i; + } + + @DontInline + private static void dontInline() {} +} From 0381286adedbd7daf1eb01a820ad8179dbb7da15 Mon Sep 17 00:00:00 2001 From: Hai-May Chao Date: Tue, 14 Jul 2026 20:46:53 +0000 Subject: [PATCH 149/305] 8376748: Emit runtime warnings for JCE algorithms that will be disabled Reviewed-by: mullan --- .../share/classes/java/security/KeyStore.java | 128 +++++++-- .../classes/java/security/MessageDigest.java | 75 ++++- .../classes/java/security/Signature.java | 74 ++++- .../share/classes/javax/crypto/Cipher.java | 81 +++++- .../util/CryptoAlgorithmConstraints.java | 94 +++++-- .../share/conf/security/java.security | 35 ++- .../KeyStore/TestLegacyAlgorithms.java | 259 ++++++++++++++++++ .../MessageDigest/TestLegacyAlgorithms.java | 212 ++++++++++++++ .../Signature/TestLegacyAlgorithms.java | 212 ++++++++++++++ .../crypto/Cipher/TestLegacyAlgorithms.java | 221 +++++++++++++++ 10 files changed, 1312 insertions(+), 79 deletions(-) create mode 100644 test/jdk/java/security/KeyStore/TestLegacyAlgorithms.java create mode 100644 test/jdk/java/security/MessageDigest/TestLegacyAlgorithms.java create mode 100644 test/jdk/java/security/Signature/TestLegacyAlgorithms.java create mode 100644 test/jdk/javax/crypto/Cipher/TestLegacyAlgorithms.java diff --git a/src/java.base/share/classes/java/security/KeyStore.java b/src/java.base/share/classes/java/security/KeyStore.java index 434aa57e3ac..f7adfcbcd62 100644 --- a/src/java.base/share/classes/java/security/KeyStore.java +++ b/src/java.base/share/classes/java/security/KeyStore.java @@ -37,6 +37,9 @@ import javax.crypto.SecretKey; import javax.security.auth.DestroyFailedException; import javax.security.auth.callback.*; +import jdk.internal.reflect.CallerSensitive; +import jdk.internal.reflect.Reflection; + import sun.security.util.Debug; import sun.security.util.CryptoAlgorithmConstraints; @@ -854,8 +857,18 @@ public class KeyStore { *

  9. the {@code jdk.crypto.disabledAlgorithms} * {@link Security#getProperty(String) Security} property to determine * if the specified keystore type is allowed. If the - * {@systemProperty jdk.crypto.disabledAlgorithms} is set, it supersedes - * the security property value. + * {@systemProperty jdk.crypto.disabledAlgorithms} system property + * is set, it supersedes the security property value. + *
  10. + *
  11. the {@code jdk.crypto.legacyAlgorithms} + * {@link Security#getProperty(String) Security} property to determine + * if the specified keystore type is considered legacy. + * If so, a warning is emitted at runtime when this method is called + * with the keystore type. This warning is shown once per caller for + * each legacy keystore type. If the keystore type is also disabled, + * the warning will not be shown. + * If the {@systemProperty jdk.crypto.legacyAlgorithms} system property + * is set, it supersedes the security property value. *
  12. * * @@ -876,6 +889,7 @@ public class KeyStore { * * @see Provider */ + @CallerSensitive public static KeyStore getInstance(String type) throws KeyStoreException { @@ -885,6 +899,11 @@ public class KeyStore { throw new KeyStoreException(type + " is disabled"); } + if (CryptoAlgorithmConstraints.isLegacy("KeyStore", type)) { + CryptoAlgorithmConstraints.warn("KeyStore", type, + Reflection.getCallerClass()); + } + try { Object[] objs = Security.getImpl(type, "KeyStore", (String)null); return new KeyStore((KeyStoreSpi)objs[0], (Provider)objs[1], type); @@ -906,11 +925,24 @@ public class KeyStore { * * @implNote * The JDK Reference Implementation additionally uses - * the {@code jdk.crypto.disabledAlgorithms} + *
      + *
    • the {@code jdk.crypto.disabledAlgorithms} * {@link Security#getProperty(String) Security} property to determine * if the specified keystore type is allowed. If the - * {@systemProperty jdk.crypto.disabledAlgorithms} is set, it supersedes - * the security property value. + * {@systemProperty jdk.crypto.disabledAlgorithms} system property + * is set, it supersedes the security property value. + *
    • + *
    • the {@code jdk.crypto.legacyAlgorithms} + * {@link Security#getProperty(String) Security} property to determine + * if the specified keystore type is considered legacy. + * If so, a warning is emitted at runtime when this method is called + * with the keystore type. This warning is shown once per caller for + * each legacy keystore type. If the keystore type is also disabled, + * the warning will not be shown. + * If the {@systemProperty jdk.crypto.legacyAlgorithms} system property + * is set, it supersedes the security property value. + *
    • + *
    * * @param type the type of keystore. * See the KeyStore section in the + *
  13. the {@code jdk.crypto.disabledAlgorithms} * {@link Security#getProperty(String) Security} property to determine * if the specified keystore type is allowed. If the - * {@systemProperty jdk.crypto.disabledAlgorithms} is set, it supersedes - * the security property value. + * {@systemProperty jdk.crypto.disabledAlgorithms} system property + * is set, it supersedes the security property value. + *
  14. + *
  15. the {@code jdk.crypto.legacyAlgorithms} + * {@link Security#getProperty(String) Security} property to determine + * if the specified keystore type is considered legacy. + * If so, a warning is emitted at runtime when this method is called + * with the keystore type. This warning is shown once per caller for + * each legacy keystore type. If the keystore type is also disabled, + * the warning will not be shown. + * If the {@systemProperty jdk.crypto.legacyAlgorithms} system property + * is set, it supersedes the security property value. + *
  16. + * * * @param type the type of keystore. * See the KeyStore section in the
    + *
  17. the {@code jdk.crypto.disabledAlgorithms} * {@link Security#getProperty(String) Security} property to determine * if the specified keystore type is allowed. If the - * {@systemProperty jdk.crypto.disabledAlgorithms} is set, it supersedes - * the security property value. Disallowed type will be skipped. + * {@systemProperty jdk.crypto.disabledAlgorithms} system property + * is set, it supersedes the security property value. + * Disallowed type will be skipped. + *
  18. + *
  19. the {@code jdk.crypto.legacyAlgorithms} + * {@link Security#getProperty(String) Security} property to determine + * if the specified keystore type is considered legacy. + * If so, a warning is emitted at runtime when this method is called + * with the keystore type. This warning is shown once per caller for + * each legacy keystore type. If the keystore type is also disabled, + * the warning will not be shown. + * If the {@systemProperty jdk.crypto.legacyAlgorithms} system property + * is set, it supersedes the security property value. + *
  20. + * * * @param file the keystore file * @param password the keystore password, which may be {@code null} @@ -1785,10 +1856,12 @@ public class KeyStore { * * @since 9 */ + @CallerSensitive public static final KeyStore getInstance(File file, char[] password) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException { - return getInstance(file, password, null, true); + return getInstance(file, password, null, true, + Reflection.getCallerClass()); } /** @@ -1815,11 +1888,25 @@ public class KeyStore { * * @implNote * The JDK Reference Implementation additionally uses - * the {@code jdk.crypto.disabledAlgorithms} + *
      + *
    • the {@code jdk.crypto.disabledAlgorithms} * {@link Security#getProperty(String) Security} property to determine * if the specified keystore type is allowed. If the - * {@systemProperty jdk.crypto.disabledAlgorithms} is set, it supersedes - * the security property value. Disallowed type will be skipped. + * {@systemProperty jdk.crypto.disabledAlgorithms} system property + * is set, it supersedes the security property value. + * Disallowed type will be skipped. + *
    • + *
    • the {@code jdk.crypto.legacyAlgorithms} + * {@link Security#getProperty(String) Security} property to determine + * if the specified keystore type is considered legacy. + * If so, a warning is emitted at runtime when this method is called + * with the keystore type. This warning is shown once per caller for + * each legacy keystore type. If the keystore type is also disabled, + * the warning will not be shown. + * If the {@systemProperty jdk.crypto.legacyAlgorithms} system property + * is set, it supersedes the security property value. + *
    • + *
    * * @param file the keystore file * @param param the {@code LoadStoreParameter} that specifies how to load @@ -1847,15 +1934,17 @@ public class KeyStore { * * @since 9 */ + @CallerSensitive public static final KeyStore getInstance(File file, LoadStoreParameter param) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException { - return getInstance(file, null, param, false); + return getInstance(file, null, param, false, + Reflection.getCallerClass()); } // Used by getInstance(File, char[]) & getInstance(File, LoadStoreParameter) private static final KeyStore getInstance(File file, char[] password, - LoadStoreParameter param, boolean hasPassword) + LoadStoreParameter param, boolean hasPassword, Class callerClass) throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException { @@ -1893,6 +1982,11 @@ public class KeyStore { String ksAlgo = s.getAlgorithm(); if (CryptoAlgorithmConstraints.permits( "KEYSTORE", ksAlgo)) { + if (CryptoAlgorithmConstraints.isLegacy( + "KeyStore", ksAlgo)) { + CryptoAlgorithmConstraints.warn( + "KeyStore", ksAlgo, callerClass); + } keystore = new KeyStore(impl, p, ksAlgo); } else { matched = ksAlgo; diff --git a/src/java.base/share/classes/java/security/MessageDigest.java b/src/java.base/share/classes/java/security/MessageDigest.java index 6e8f64f7ebe..943459b4bf7 100644 --- a/src/java.base/share/classes/java/security/MessageDigest.java +++ b/src/java.base/share/classes/java/security/MessageDigest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -30,6 +30,9 @@ import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.nio.ByteBuffer; +import jdk.internal.reflect.CallerSensitive; +import jdk.internal.reflect.Reflection; + import sun.security.jca.GetInstance; import sun.security.util.Debug; import sun.security.util.MessageDigestSpi2; @@ -168,8 +171,18 @@ public abstract class MessageDigest extends MessageDigestSpi { *
  21. the {@code jdk.crypto.disabledAlgorithms} * {@link Security#getProperty(String) Security} property to determine * if the specified algorithm is allowed. If the - * {@systemProperty jdk.crypto.disabledAlgorithms} is set, it supersedes - * the security property value. + * {@systemProperty jdk.crypto.disabledAlgorithms} system property + * is set, it supersedes the security property value. + *
  22. + *
  23. the {@code jdk.crypto.legacyAlgorithms} + * {@link Security#getProperty(String) Security} property to determine + * if the specified algorithm is considered legacy. + * If so, a warning is emitted at runtime when this method is called + * with the algorithm. This warning is shown once per caller for + * each legacy algorithm. If the algorithm is also disabled, + * the warning will not be shown. + * If the {@systemProperty jdk.crypto.legacyAlgorithms} system property + * is set, it supersedes the security property value. *
  24. * * @@ -191,6 +204,7 @@ public abstract class MessageDigest extends MessageDigestSpi { * * @see Provider */ + @CallerSensitive public static MessageDigest getInstance(String algorithm) throws NoSuchAlgorithmException { @@ -200,6 +214,11 @@ public abstract class MessageDigest extends MessageDigestSpi { throw new NoSuchAlgorithmException(algorithm + " is disabled"); } + if (CryptoAlgorithmConstraints.isLegacy("MessageDigest", algorithm)) { + CryptoAlgorithmConstraints.warn("MessageDigest", algorithm, + Reflection.getCallerClass()); + } + GetInstance.Instance instance = GetInstance.getInstance("MessageDigest", MessageDigestSpi.class, algorithm); MessageDigest md; @@ -233,11 +252,24 @@ public abstract class MessageDigest extends MessageDigestSpi { * * @implNote * The JDK Reference Implementation additionally uses - * the {@code jdk.crypto.disabledAlgorithms} + *
      + *
    • the {@code jdk.crypto.disabledAlgorithms} * {@link Security#getProperty(String) Security} property to determine * if the specified algorithm is allowed. If the - * {@systemProperty jdk.crypto.disabledAlgorithms} is set, it supersedes - * the security property value. + * {@systemProperty jdk.crypto.disabledAlgorithms} system property + * is set, it supersedes the security property value. + *
    • + *
    • the {@code jdk.crypto.legacyAlgorithms} + * {@link Security#getProperty(String) Security} property to determine + * if the specified algorithm is considered legacy. + * If so, a warning is emitted at runtime when this method is called + * with the algorithm. This warning is shown once per caller for + * each legacy algorithm. If the algorithm is also disabled, + * the warning will not be shown. + * If the {@systemProperty jdk.crypto.legacyAlgorithms} system property + * is set, it supersedes the security property value. + *
    • + *
    * * @param algorithm the name of the algorithm requested. * See the MessageDigest section in the
    + *
  25. the {@code jdk.crypto.disabledAlgorithms} * {@link Security#getProperty(String) Security} property to determine * if the specified algorithm is allowed. If the - * {@systemProperty jdk.crypto.disabledAlgorithms} is set, it supersedes - * the security property value. + * {@systemProperty jdk.crypto.disabledAlgorithms} system property + * is set, it supersedes the security property value. + *
  26. + *
  27. the {@code jdk.crypto.legacyAlgorithms} + * {@link Security#getProperty(String) Security} property to determine + * if the specified algorithm is considered legacy. + * If so, a warning is emitted at runtime when this method is called + * with the algorithm. This warning is shown once per caller for + * each legacy algorithm. If the algorithm is also disabled, + * the warning will not be shown. + * If the {@systemProperty jdk.crypto.legacyAlgorithms} system property + * is set, it supersedes the security property value. + *
  28. + * * * @param algorithm the name of the algorithm requested. * See the MessageDigest section in the
    the {@code jdk.crypto.disabledAlgorithms} * {@link Security#getProperty(String) Security} property to determine * if the specified algorithm is allowed. If the - * {@systemProperty jdk.crypto.disabledAlgorithms} is set, it supersedes - * the security property value. + * {@systemProperty jdk.crypto.disabledAlgorithms} system property + * is set, it supersedes the security property value. + * + *
  29. the {@code jdk.crypto.legacyAlgorithms} + * {@link Security#getProperty(String) Security} property to determine + * if the specified algorithm is considered legacy. + * If so, a warning is emitted at runtime when this method is called + * with the algorithm. This warning is shown once per caller for + * each legacy algorithm. If the algorithm is also disabled, + * the warning will not be shown. + * If the {@systemProperty jdk.crypto.legacyAlgorithms} system property + * is set, it supersedes the security property value. *
  30. * * @@ -259,6 +271,7 @@ public abstract class Signature extends SignatureSpi { * * @see Provider */ + @CallerSensitive public static Signature getInstance(String algorithm) throws NoSuchAlgorithmException { Objects.requireNonNull(algorithm, "null algorithm name"); @@ -267,6 +280,11 @@ public abstract class Signature extends SignatureSpi { throw new NoSuchAlgorithmException(algorithm + " is disabled"); } + if (CryptoAlgorithmConstraints.isLegacy("Signature", algorithm)) { + CryptoAlgorithmConstraints.warn("Signature", algorithm, + Reflection.getCallerClass()); + } + Iterator t = GetInstance.getServices("Signature", algorithm); if (!t.hasNext()) { throw new NoSuchAlgorithmException @@ -362,11 +380,24 @@ public abstract class Signature extends SignatureSpi { * * @implNote * The JDK Reference Implementation additionally uses - * the {@code jdk.crypto.disabledAlgorithms} + *
      + *
    • the {@code jdk.crypto.disabledAlgorithms} * {@link Security#getProperty(String) Security} property to determine * if the specified algorithm is allowed. If the - * {@systemProperty jdk.crypto.disabledAlgorithms} is set, it supersedes - * the security property value. + * {@systemProperty jdk.crypto.disabledAlgorithms} system property + * is set, it supersedes the security property value. + *
    • + *
    • the {@code jdk.crypto.legacyAlgorithms} + * {@link Security#getProperty(String) Security} property to determine + * if the specified algorithm is considered legacy. + * If so, a warning is emitted at runtime when this method is called + * with the algorithm. This warning is shown once per caller for + * each legacy algorithm. If the algorithm is also disabled, + * the warning will not be shown. + * If the {@systemProperty jdk.crypto.legacyAlgorithms} system property + * is set, it supersedes the security property value. + *
    • + *
    * * @param algorithm the name of the algorithm requested. * See the Signature section in the
    + *
  31. the {@code jdk.crypto.disabledAlgorithms} * {@link Security#getProperty(String) Security} property to determine * if the specified algorithm is allowed. If the - * {@systemProperty jdk.crypto.disabledAlgorithms} is set, it supersedes - * the security property value. + * {@systemProperty jdk.crypto.disabledAlgorithms} system property + * is set, it supersedes the security property value. + *
  32. + *
  33. the {@code jdk.crypto.legacyAlgorithms} + * {@link Security#getProperty(String) Security} property to determine + * if the specified algorithm is considered legacy. + * If so, a warning is emitted at runtime when this method is called + * with the algorithm. This warning is shown once per caller for + * each legacy algorithm. If the algorithm is also disabled, + * the warning will not be shown. + * If the {@systemProperty jdk.crypto.legacyAlgorithms} system property + * is set, it supersedes the security property value. + *
  34. + * * * @param algorithm the name of the algorithm requested. * See the Signature section in the
    the {@code jdk.crypto.disabledAlgorithms} * {@link Security#getProperty(String) Security} property to determine * if the specified algorithm is allowed. If the - * {@systemProperty jdk.crypto.disabledAlgorithms} is set, it supersedes - * the security property value. + * {@systemProperty jdk.crypto.disabledAlgorithms} system property + * is set, it supersedes the security property value. + * + *
  35. the {@code jdk.crypto.legacyAlgorithms} + * {@link Security#getProperty(String) Security} property to determine + * if the specified algorithm is considered legacy. + * If so, a warning is emitted at runtime when this method is called + * with the algorithm. This warning is shown once per caller for + * each legacy algorithm. If the algorithm is also disabled, + * the warning will not be shown. + * If the {@systemProperty jdk.crypto.legacyAlgorithms} system property + * is set, it supersedes the security property value. *
  36. * * @@ -541,6 +553,7 @@ public class Cipher { * * @see java.security.Provider */ + @CallerSensitive public static final Cipher getInstance(String transformation) throws NoSuchAlgorithmException, NoSuchPaddingException { @@ -554,6 +567,11 @@ public class Cipher { " is disabled"); } + if (CryptoAlgorithmConstraints.isLegacy("Cipher", transformation)) { + CryptoAlgorithmConstraints.warn("Cipher", transformation, + Reflection.getCallerClass()); + } + List transforms = getTransforms(transformation); List cipherServices = new ArrayList<>(transforms.size()); for (Transform transform : transforms) { @@ -623,11 +641,24 @@ public class Cipher { * * @implNote * The JDK Reference Implementation additionally uses - * the {@code jdk.crypto.disabledAlgorithms} + *
      + *
    • the {@code jdk.crypto.disabledAlgorithms} * {@link Security#getProperty(String) Security} property to determine * if the specified algorithm is allowed. If the - * {@systemProperty jdk.crypto.disabledAlgorithms} is set, it supersedes - * the security property value. + * {@systemProperty jdk.crypto.disabledAlgorithms} system property + * is set, it supersedes the security property value. + *
    • + *
    • the {@code jdk.crypto.legacyAlgorithms} + * {@link Security#getProperty(String) Security} property to determine + * if the specified algorithm is considered legacy. + * If so, a warning is emitted at runtime when this method is called + * with the algorithm. This warning is shown once per caller for + * each legacy algorithm. If the algorithm is also disabled, + * the warning will not be shown. + * If the {@systemProperty jdk.crypto.legacyAlgorithms} system property + * is set, it supersedes the security property value. + *
    • + *
    * * @param transformation the name of the transformation, * e.g., AES/CBC/PKCS5Padding. @@ -660,6 +691,7 @@ public class Cipher { * * @see java.security.Provider */ + @CallerSensitive public static final Cipher getInstance(String transformation, String provider) throws NoSuchAlgorithmException, NoSuchProviderException, @@ -676,7 +708,7 @@ public class Cipher { throw new NoSuchProviderException("No such provider: " + provider); } - return getInstance(transformation, p); + return getInstance(transformation, p, Reflection.getCallerClass()); } private String getProviderName() { @@ -705,11 +737,24 @@ public class Cipher { * * @implNote * The JDK Reference Implementation additionally uses - * the {@code jdk.crypto.disabledAlgorithms} + *
      + *
    • the {@code jdk.crypto.disabledAlgorithms} * {@link Security#getProperty(String) Security} property to determine * if the specified algorithm is allowed. If the - * {@systemProperty jdk.crypto.disabledAlgorithms} is set, it supersedes - * the security property value. + * {@systemProperty jdk.crypto.disabledAlgorithms} system property + * is set, it supersedes the security property value. + *
    • + *
    • the {@code jdk.crypto.legacyAlgorithms} + * {@link Security#getProperty(String) Security} property to determine + * if the specified algorithm is considered legacy. + * If so, a warning is emitted at runtime when this method is called + * with the algorithm. This warning is shown once per caller for + * each legacy algorithm. If the algorithm is also disabled, + * the warning will not be shown. + * If the {@systemProperty jdk.crypto.legacyAlgorithms} system property + * is set, it supersedes the security property value. + *
    • + *
    * * @param transformation the name of the transformation, * e.g., AES/CBC/PKCS5Padding. @@ -739,6 +784,7 @@ public class Cipher { * * @see java.security.Provider */ + @CallerSensitive public static final Cipher getInstance(String transformation, Provider provider) throws NoSuchAlgorithmException, NoSuchPaddingException @@ -750,12 +796,27 @@ public class Cipher { throw new IllegalArgumentException("Missing provider"); } + return getInstance(transformation, provider, Reflection.getCallerClass()); + } + + private static Cipher getInstance(String transformation, Provider provider, + Class callerClass) + throws NoSuchAlgorithmException, NoSuchPaddingException { + if (provider == null) { + throw new IllegalArgumentException("Missing provider"); + } + // throws NoSuchAlgorithmException if java.security disables it if (!CryptoAlgorithmConstraints.permits("Cipher", transformation)) { throw new NoSuchAlgorithmException(transformation + " is disabled"); } + if (CryptoAlgorithmConstraints.isLegacy("Cipher", transformation)) { + CryptoAlgorithmConstraints.warn("Cipher", transformation, + callerClass); + } + Exception failure = null; List transforms = getTransforms(transformation); boolean providerChecked = false; diff --git a/src/java.base/share/classes/sun/security/util/CryptoAlgorithmConstraints.java b/src/java.base/share/classes/sun/security/util/CryptoAlgorithmConstraints.java index ad3beab350f..781c1ab2cd2 100644 --- a/src/java.base/share/classes/sun/security/util/CryptoAlgorithmConstraints.java +++ b/src/java.base/share/classes/sun/security/util/CryptoAlgorithmConstraints.java @@ -26,7 +26,9 @@ package sun.security.util; import java.lang.ref.SoftReference; +import java.net.URL; import java.security.AlgorithmParameters; +import java.security.CodeSource; import java.security.CryptoPrimitive; import java.security.Key; import java.util.Arrays; @@ -36,9 +38,10 @@ import java.util.concurrent.ConcurrentHashMap; /** * This class implements the algorithm constraints for the - * "jdk.crypto.disabledAlgorithms" security property. This security property - * can be overridden by the system property of the same name. See the - * java.security file for the syntax of the property value. + * "jdk.crypto.disabledAlgorithms" and "jdk.crypto.legacyAlgorithms" security + * properties. Each security property can be overridden by a system property + * of the same name. See the java.security file for the syntax of the property + * values. */ public class CryptoAlgorithmConstraints extends AbstractAlgorithmConstraints { private static final Debug debug = Debug.getInstance("jca"); @@ -51,11 +54,20 @@ public class CryptoAlgorithmConstraints extends AbstractAlgorithmConstraints { private static final String PROPERTY_CRYPTO_DISABLED_ALGS = "jdk.crypto.disabledAlgorithms"; - private static class CryptoHolder { - static final CryptoAlgorithmConstraints CONSTRAINTS = + // Legacy algorithm security property for JCE crypto services + private static final String PROPERTY_CRYPTO_LEGACY_ALGS = + "jdk.crypto.legacyAlgorithms"; + + private static class DisabledHolder { + private static final CryptoAlgorithmConstraints DISABLED_CONSTRAINTS = new CryptoAlgorithmConstraints(PROPERTY_CRYPTO_DISABLED_ALGS); } + private static class LegacyHolder { + private static final CryptoAlgorithmConstraints LEGACY_CONSTRAINTS = + new CryptoAlgorithmConstraints(PROPERTY_CRYPTO_LEGACY_ALGS); + } + private static void debug(String msg) { if (debug != null) { debug.println("CryptoAlgoConstraints: ", msg); @@ -63,11 +75,47 @@ public class CryptoAlgorithmConstraints extends AbstractAlgorithmConstraints { } public static boolean permits(String service, String algo) { - return CryptoHolder.CONSTRAINTS.cachedCheckAlgorithm( + return DisabledHolder.DISABLED_CONSTRAINTS.cachedCheckAlgorithm( service + "." + algo); } - private final Set disabledServices; // syntax is . + public static boolean isLegacy(String service, String alg) { + return !LegacyHolder.LEGACY_CONSTRAINTS.cachedCheckAlgorithm( + service + "." + alg); + } + + private static class CallersHolder { + static final ClassValue> callers = new ClassValue<>() { + @Override + protected Set computeValue(Class type) { + return ConcurrentHashMap.newKeySet(); + } + }; + } + + public static void warn(String service, String alg, Class callerClass) { + if (callerClass == null) { + callerClass = CryptoAlgorithmConstraints.class; + } + String serviceAndAlg = service + "." + alg; + Set warnedAlgorithms = CallersHolder.callers.get(callerClass); + if (warnedAlgorithms.add(serviceAndAlg)) { + URL url = codeSource(callerClass); + String source = (url == null) ? callerClass.getName() : + callerClass.getName() + " (" + url + ")"; + System.err.printf(""" + WARNING: An outdated %s algorithm has been called by %s + WARNING: %s will be disabled by default in a future release + """, service, source, alg); + } + } + + private static URL codeSource(Class clazz) { + CodeSource cs = clazz.getProtectionDomain().getCodeSource(); + return (cs != null) ? cs.getLocation() : null; + } + + private final Set affectedServices; // syntax is . private volatile SoftReference> cacheRef = new SoftReference<>(null); @@ -76,42 +124,42 @@ public class CryptoAlgorithmConstraints extends AbstractAlgorithmConstraints { * {@code propertyName}. Note that if a system property of the same name * is set, it overrides the security property. * - * @param propertyName the security property name that define the disabled + * @param propertyName the security property name that defines the * algorithm constraints */ CryptoAlgorithmConstraints(String propertyName) { super(null); - disabledServices = getAlgorithms(propertyName, true); - String[] entries = disabledServices.toArray(new String[0]); + affectedServices = getAlgorithms(propertyName, true); + String[] entries = affectedServices.toArray(new String[0]); debug("Before " + Arrays.deepToString(entries)); - for (String dk : entries) { - int idx = dk.indexOf("."); - if (idx < 1 || idx == dk.length() - 1) { + for (String k : entries) { + int idx = k.indexOf("."); + if (idx < 1 || idx == k.length() - 1) { // wrong syntax: missing "." or empty service or algorithm - throw new IllegalArgumentException("Invalid entry: " + dk); + throw new IllegalArgumentException("Invalid entry: " + k); } - String service = dk.substring(0, idx); - String algo = dk.substring(idx + 1); + String service = k.substring(0, idx); + String algo = k.substring(idx + 1); if (SUPPORTED_SERVICES.stream().anyMatch(e -> e.equalsIgnoreCase (service))) { KnownOIDs oid = KnownOIDs.findMatch(algo); if (oid != null) { debug("Add oid: " + oid.value()); - disabledServices.add(service + "." + oid.value()); + affectedServices.add(service + "." + oid.value()); debug("Add oid stdName: " + oid.stdName()); - disabledServices.add(service + "." + oid.stdName()); + affectedServices.add(service + "." + oid.stdName()); for (String a : oid.aliases()) { debug("Add oid alias: " + a); - disabledServices.add(service + "." + a); + affectedServices.add(service + "." + a); } } } else { // unsupported service - throw new IllegalArgumentException("Invalid entry: " + dk); + throw new IllegalArgumentException("Invalid entry: " + k); } } - debug("After " + Arrays.deepToString(disabledServices.toArray())); + debug("After " + Arrays.deepToString(affectedServices.toArray())); } @Override @@ -131,7 +179,7 @@ public class CryptoAlgorithmConstraints extends AbstractAlgorithmConstraints { throw new UnsupportedOperationException("Unsupported permits() method"); } - // Return false if algorithm is found in the disabledServices Set. + // Return false if algorithm is found in the affectedServices Set. // Otherwise, return true. private boolean cachedCheckAlgorithm(String serviceDesc) { Map cache; @@ -147,7 +195,7 @@ public class CryptoAlgorithmConstraints extends AbstractAlgorithmConstraints { if (result != null) { return result; } - result = checkAlgorithm(disabledServices, serviceDesc, null); + result = checkAlgorithm(affectedServices, serviceDesc, null); cache.put(serviceDesc, result); return result; } diff --git a/src/java.base/share/conf/security/java.security b/src/java.base/share/conf/security/java.security index 976604b5cbc..26842d0c845 100644 --- a/src/java.base/share/conf/security/java.security +++ b/src/java.base/share/conf/security/java.security @@ -777,8 +777,8 @@ jdk.tls.disabledAlgorithms=SSLv3, TLSv1, TLSv1.1, DTLSv1.0, RC4, DES, \ # In some environments, certain algorithms may be undesirable for certain # cryptographic services. For example, "MD2" is generally no longer considered # to be a secure hash algorithm. This section describes the mechanism for -# disabling algorithms at the JCA/JCE level based on service name and algorithm -# name. +# disabling algorithms and identifying legacy algorithms at the JCA/JCE +# level based on service name and algorithm name. # # If a system property of the same name is also specified, it supersedes the # security property value defined here. @@ -786,7 +786,10 @@ jdk.tls.disabledAlgorithms=SSLv3, TLSv1, TLSv1.1, DTLSv1.0, RC4, DES, \ # The syntax of the disabled services string is described as follows: # "DisabledService {, DisabledService}" # -# DisabledService: +# The syntax of the legacy services string is described as follows: +# "LegacyService {, LegacyService}" +# +# DisabledService and LegacyService: # Service.AlgorithmName # # Service: (one of the following, more services may be added later) @@ -795,7 +798,7 @@ jdk.tls.disabledAlgorithms=SSLv3, TLSv1, TLSv1.1, DTLSv1.0, RC4, DES, \ # AlgorithmName: # (see below) # -# The "AlgorithmName" is the standard algorithm name of the disabled +# The "AlgorithmName" is the standard algorithm name of the affected # service. See the Java Security Standard Algorithm Names Specification # for information about Standard Algorithm Names. Matching is # performed using a case-insensitive exact matching rule. For Cipher service, @@ -805,18 +808,28 @@ jdk.tls.disabledAlgorithms=SSLv3, TLSv1, TLSv1.1, DTLSv1.0, RC4, DES, \ # unsupported services at the time of checking, an ExceptionInInitializerError # with a cause of IllegalArgumentException will be thrown. # -# Note: The restriction is applied in the various getInstance(...) methods -# of the supported Service classes, i.e. Cipher, KeyStore, MessageDigest, -# and Signature. If the algorithm is disabled, a NoSuchAlgorithmException will -# be thrown by the getInstance methods of Cipher, MessageDigest, and Signature -# and a KeyStoreException by the getInstance methods of KeyStore. +# Note: The jdk.crypto.disabledAlgorithms property is enforced in the various +# getInstance(...) methods of the supported Service classes, i.e. Cipher, +# KeyStore, MessageDigest, and Signature. If the algorithm is disabled, a +# NoSuchAlgorithmException will be thrown by the getInstance methods of +# Cipher, MessageDigest, and Signature and a KeyStoreException by the +# getInstance methods of KeyStore. # -# Note: This property is currently used by the JDK Reference implementation. -# It is not guaranteed to be examined and used by other implementations. +# Note: The jdk.crypto.legacyAlgorithms property is checked in the +# getInstance(...) methods of the supported Service classes, i.e. Cipher, +# KeyStore, MessageDigest, and Signature. If the algorithm is considered legacy, the +# JDK emits a warning at runtime when the algorithm is requested. +# This warning is shown once per caller for each legacy algorithm. +# If the algorithm is also disabled, the warning will not be shown. +# +# Note: These properties are currently used by the JDK Reference implementation. +# They are not guaranteed to be examined and used by other implementations. # # Example: # jdk.crypto.disabledAlgorithms=Cipher.RSA/ECB/PKCS1Padding, MessageDigest.MD2 +# jdk.crypto.legacyAlgorithms=Cipher.RSA/ECB/PKCS1Padding, MessageDigest.MD2 # +#jdk.crypto.legacyAlgorithms= #jdk.crypto.disabledAlgorithms= # diff --git a/test/jdk/java/security/KeyStore/TestLegacyAlgorithms.java b/test/jdk/java/security/KeyStore/TestLegacyAlgorithms.java new file mode 100644 index 00000000000..516191cfa01 --- /dev/null +++ b/test/jdk/java/security/KeyStore/TestLegacyAlgorithms.java @@ -0,0 +1,259 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8376748 + * @summary Test JCE layer legacy algorithm warning for KeyStore + * @library /test/lib + * @run main/othervm TestLegacyAlgorithms KEYSTORE.JKs true + * @run main/othervm TestLegacyAlgorithms keySTORE.what false + * @run main/othervm TestLegacyAlgorithms kEYstoRe.jceKS false + * @run main/othervm -Djdk.crypto.legacyAlgorithms=KEYSTORE.JKS + * -Djdk.crypto.disabledAlgorithms=KEYSTORE.JKS + * TestLegacyAlgorithms KEYSTORE.JKS false true + */ + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.PrintStream; +import java.nio.charset.StandardCharsets; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.Provider; +import java.security.Security; +import java.util.List; + +import jdk.test.lib.Asserts; +import jdk.test.lib.Utils; + +public class TestLegacyAlgorithms { + + private static final String PROP_NAME = "jdk.crypto.legacyAlgorithms"; + private static final String DIR = System.getProperty("test.src", "."); + private static final char[] PASSWD = "passphrase".toCharArray(); + private static final String JKS_FN = "keystore.jks"; + + private static final List ALG_LIST = + List.of("JKS", "jkS"); + + private static String saveWarn(ThrowingRunnable action) throws Exception { + PrintStream origErr = System.err; + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + PrintStream ps = new PrintStream(bOut, true, StandardCharsets.UTF_8); + try { + System.setErr(ps); + action.run(); + } finally { + ps.flush(); + System.setErr(origErr); + } + return bOut.toString(StandardCharsets.UTF_8); + } + + private static int countWarn(String warnS, String msg) { + int num = 0; + int index = 0; + while ((index = warnS.indexOf(msg, index)) >= 0) { + num++; + index += msg.length(); + } + return num; + } + + private static void checkOneWarn(String warnS, String alg) { + String warn1 = + "WARNING: An outdated KeyStore algorithm has been called by"; + String warn2 = "WARNING: " + alg + + " will be disabled by default in a future release"; + + Asserts.assertEQ(countWarn(warnS, warn1), 1, + "Expected one legacy warning for KeyStore " + alg + + " but got:\n" + warnS); + Asserts.assertEQ(countWarn(warnS, warn2), 1, + "Expected one future-disable warning for KeyStore " + + alg + " but got:\n" + warnS); + Asserts.assertTrue(warnS.contains("TestLegacyAlgorithms"), + "Expected warning to preserve caller: " + warnS); + } + + private static void checkNoWarn(String warnS) { + String warn1 = + "WARNING: An outdated KeyStore algorithm has been called by"; + String warn2 = + "will be disabled by default in a future release"; + Asserts.assertFalse(warnS.contains(warn1), + "Unexpected legacy warning for KeyStore: " + warnS); + Asserts.assertFalse(warnS.contains(warn2), + "Unexpected future-disable warning for KeyStore: " + warnS); + } + + private static void checkWarn(String label, String alg, + boolean shouldWarn, ThrowingRunnable action) throws Exception { + System.out.println("Testing " + label); + String warnS = saveWarn(action); + System.out.println("Warning emitted:\n" + warnS); + if (shouldWarn) { + checkOneWarn(warnS, alg); + } else { + checkNoWarn(warnS); + } + } + + // Disable the algorithm and check that a warning is not emitted. + private static void warnDisabledTest() + throws Exception { + File jksFile = new File(DIR, JKS_FN); + checkWarn("no warning when the algorithm is disabled", + "JKS", false, () -> { + Utils.runAndCheckException( + () -> KeyStore.getInstance("JKS"), + KeyStoreException.class); + Utils.runAndCheckException( + () -> KeyStore.getInstance(jksFile, PASSWD), + KeyStoreException.class); + }); + } + + private static void runTests(boolean shouldWarn) throws Exception { + for (String a : ALG_LIST) { + checkWarn("default provider: alg " + a, a, shouldWarn, + () -> DefaultKS.run(a)); + } + + File jksFile = new File(DIR, JKS_FN); + + checkWarn("file with password: " + jksFile, "JKS", shouldWarn, + () -> PasswordKS.run(jksFile)); + + checkWarn("file with LoadStoreParameter: " + jksFile, "JKS", + shouldWarn, () -> LoadStoreParamKS.run(jksFile)); + + Provider[] providers = Security.getProviders("KeyStore.JKS"); + if (providers.length > 0) { + // First provider should warn, and later provider for the same + // algorithm will not warn. This is because warning is determined + // by caller class and algorithm string, not by provider. + Provider p = providers[0]; + for (String a : ALG_LIST) { + checkWarn("provider object " + p.getName() + ": alg " + a, + a, shouldWarn, () -> ProvObjKS.run(a, p)); + + checkWarn("provider name " + p.getName() + ": alg " + a, + a, shouldWarn, () -> ProvNameKS.run(a, p)); + } + } + } + + public static void main(String[] args) throws Exception { + String propValue = args[0]; + boolean shouldWarn = Boolean.parseBoolean(args[1]); + boolean warnDisabled = + args.length > 2 && Boolean.parseBoolean(args[2]); + System.out.println("Setting Security Prop " + PROP_NAME + " = " + + propValue); + Security.setProperty(PROP_NAME, propValue); + if (warnDisabled) { + warnDisabledTest(); + } else { + runTests(shouldWarn); + } + } + + @FunctionalInterface + private interface ThrowingRunnable { + void run() throws Exception; + } + + private static final class DefaultKS { + static void run(String alg) throws Exception { + KeyStore k = KeyStore.getInstance(alg); + System.out.println(" type lookup: got KeyStore w/ alg " + + k.getType()); + + // Call the method twice, and make sure that only get one + // warning per caller. + k = KeyStore.getInstance(alg); + System.out.println(" type lookup again: got KeyStore w/ alg " + + k.getType()); + } + } + + private static final class PasswordKS { + static void run(File jksFile) throws Exception { + KeyStore k = KeyStore.getInstance(jksFile, PASSWD); + System.out.println(" file+password: got KeyStore w/ alg " + + k.getType()); + + // Call the method twice, and make sure that only get one + // warning per caller. + k = KeyStore.getInstance(jksFile, PASSWD); + System.out.println(" file+password again: got KeyStore " + + "w/ alg " + k.getType()); + } + } + + private static final class LoadStoreParamKS { + static void run(File jksFile) throws Exception { + KeyStore k = KeyStore.getInstance(jksFile, + () -> new KeyStore.PasswordProtection(PASSWD)); + System.out.println(" file+LoadStoreParameter: got KeyStore " + + "w/ alg " + k.getType()); + + // Call the method twice, and make sure that only get one + // warning per caller. + k = KeyStore.getInstance(jksFile, + () -> new KeyStore.PasswordProtection(PASSWD)); + System.out.println(" file+LoadStoreParameter again: got " + + "KeyStore w/ alg " + k.getType()); + } + } + + private static final class ProvObjKS { + static void run(String alg, Provider provider) throws Exception { + KeyStore k = KeyStore.getInstance(alg, provider); + System.out.println(" provider object: got KeyStore w/ alg " + + k.getType()); + + // Call the method twice, and make sure that only get one + // warning per caller. + k = KeyStore.getInstance(alg, provider); + System.out.println(" provider object again: got KeyStore " + + "w/ alg " + k.getType()); + } + } + + private static final class ProvNameKS { + static void run(String alg, Provider provider) throws Exception { + KeyStore k = KeyStore.getInstance(alg, provider.getName()); + System.out.println(" provider name: got KeyStore w/ alg " + + k.getType()); + + // Call the method twice, and make sure that only get one + // warning per caller. + k = KeyStore.getInstance(alg, provider.getName()); + System.out.println(" provider name again: got KeyStore " + + "w/ alg " + k.getType()); + } + } +} diff --git a/test/jdk/java/security/MessageDigest/TestLegacyAlgorithms.java b/test/jdk/java/security/MessageDigest/TestLegacyAlgorithms.java new file mode 100644 index 00000000000..61275b03f5d --- /dev/null +++ b/test/jdk/java/security/MessageDigest/TestLegacyAlgorithms.java @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8376748 + * @summary Test JCE layer legacy algorithm warning for MessageDigest + * @library /test/lib + * @run main/othervm TestLegacyAlgorithms MESSAGEdigest.Sha-512 true + * @run main/othervm TestLegacyAlgorithms messageDIGest.what false + * @run main/othervm TestLegacyAlgorithms meSSagedIgest.sHA-512/224 false + * @run main/othervm -Djdk.crypto.legacyAlgorithms=MESSAGEdigest.Sha-512 + * -Djdk.crypto.disabledAlgorithms=MESSAGEdigest.Sha-512 + * TestLegacyAlgorithms MESSAGEdigest.Sha-512 false true + */ + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.Provider; +import java.security.Security; +import java.util.List; + +import jdk.test.lib.Asserts; +import jdk.test.lib.Utils; + +public class TestLegacyAlgorithms { + + private static final String PROP_NAME = "jdk.crypto.legacyAlgorithms"; + private static final List ALG_LIST = + List.of("sHA-512", "shA-512", "2.16.840.1.101.3.4.2.3"); + + private static String saveWarn(ThrowingRunnable action) throws Exception { + PrintStream origErr = System.err; + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + PrintStream ps = new PrintStream(bOut, true, StandardCharsets.UTF_8); + try { + System.setErr(ps); + action.run(); + } finally { + ps.flush(); + System.setErr(origErr); + } + return bOut.toString(StandardCharsets.UTF_8); + } + + private static int countWarn(String warnS, String msg) { + int num = 0; + int index = 0; + while ((index = warnS.indexOf(msg, index)) >= 0) { + num++; + index += msg.length(); + } + return num; + } + + private static void checkOneWarn(String warnS, String alg) { + String warn1 = + "WARNING: An outdated MessageDigest algorithm has been called by"; + String warn2 = "WARNING: " + alg + + " will be disabled by default in a future release"; + + Asserts.assertEQ(countWarn(warnS, warn1), 1, + "Expected one legacy warning for MessageDigest " + alg + + " but got:\n" + warnS); + Asserts.assertEQ(countWarn(warnS, warn2), 1, + "Expected one future-disable warning for MessageDigest " + + alg + " but got:\n" + warnS); + Asserts.assertTrue(warnS.contains("TestLegacyAlgorithms"), + "Expected warning to preserve caller: " + warnS); + } + + private static void checkNoWarn(String warnS) { + String warn1 = + "WARNING: An outdated MessageDigest algorithm has been called by"; + String warn2 = + "will be disabled by default in a future release"; + Asserts.assertFalse(warnS.contains(warn1), + "Unexpected legacy warning for MessageDigest: " + warnS); + Asserts.assertFalse(warnS.contains(warn2), + "Unexpected future-disable warning for MessageDigest: " + warnS); + } + + private static void checkwarn(String label, String alg, + boolean shouldWarn, ThrowingRunnable action) throws Exception { + System.out.println("Testing " + label); + String warnS = saveWarn(action); + System.out.println("Warning emitted:\n" + warnS); + if (shouldWarn) { + checkOneWarn(warnS, alg); + } else { + checkNoWarn(warnS); + } + } + + // Disable the algorithm and check that a warning is not emitted. + private static void warnDisabledTest() + throws Exception { + checkwarn("no warning when the algorithm is disabled", + "SHA-512", false, () -> { + Utils.runAndCheckException( + () -> MessageDigest.getInstance("SHA-512"), + NoSuchAlgorithmException.class); + }); + } + + private static void runTests(boolean shouldWarn) throws Exception { + for (String a : ALG_LIST) { + checkwarn("default provider: alg " + a, a, shouldWarn, + () -> DefaultMD.run(a)); + } + + Provider[] providers = Security.getProviders("MessageDigest.SHA-512"); + if (providers.length > 0) { + // First provider should warn, and later provider for the same + // algorithm will not warn. This is because warning is determined + // by caller class and algorithm string, not by provider. + Provider p = providers[0]; + for (String a : ALG_LIST) { + checkwarn("provider object " + p.getName() + ": alg " + a, + a, shouldWarn, () -> ProvObjMD.run(a, p)); + + checkwarn("provider name " + p.getName() + ": alg " + a, + a, shouldWarn, () -> ProvNameMD.run(a, p)); + } + } + } + + public static void main(String[] args) throws Exception { + String propValue = args[0]; + boolean shouldWarn = Boolean.parseBoolean(args[1]); + boolean warnDisabled = + args.length > 2 && Boolean.parseBoolean(args[2]); + System.out.println("Setting Security Prop " + PROP_NAME + " = " + + propValue); + Security.setProperty(PROP_NAME, propValue); + if (warnDisabled) { + warnDisabledTest(); + } else { + runTests(shouldWarn); + } + } + + @FunctionalInterface + private interface ThrowingRunnable { + void run() throws Exception; + } + + private static final class DefaultMD { + static void run(String alg) throws Exception { + MessageDigest m = MessageDigest.getInstance(alg); + System.out.println(" type lookup: got MessageDigest w/ alg " + + m.getAlgorithm()); + + // Call the method twice, and make sure that only get one + // warning per caller. + m = MessageDigest.getInstance(alg); + System.out.println(" type lookup again: got MessageDigest w/ alg " + + m.getAlgorithm()); + } + } + + private static final class ProvObjMD { + static void run(String alg, Provider provider) throws Exception { + MessageDigest m = MessageDigest.getInstance(alg, provider); + System.out.println(" provider object: got MessageDigest w/ alg " + + m.getAlgorithm()); + + // Call the method twice, and make sure that only get one + // warning per caller. + m = MessageDigest.getInstance(alg, provider); + System.out.println(" provider object again: got MessageDigest " + + "w/ alg " + m.getAlgorithm()); + } + } + + private static final class ProvNameMD { + static void run(String alg, Provider provider) throws Exception { + MessageDigest m = MessageDigest.getInstance(alg, provider.getName()); + System.out.println(" provider name: got MessageDigest w/ alg " + + m.getAlgorithm()); + + // Call the method twice, and make sure that only get one + // warning per caller. + m = MessageDigest.getInstance(alg, provider.getName()); + System.out.println(" provider name again: got MessageDigest " + + "w/ alg " + m.getAlgorithm()); + } + } +} diff --git a/test/jdk/java/security/Signature/TestLegacyAlgorithms.java b/test/jdk/java/security/Signature/TestLegacyAlgorithms.java new file mode 100644 index 00000000000..bf745fba3e1 --- /dev/null +++ b/test/jdk/java/security/Signature/TestLegacyAlgorithms.java @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8376748 + * @summary Test JCE layer legacy algorithm warning for Signature + * @library /test/lib + * @run main/othervm TestLegacyAlgorithms SIGNATURe.sha512withRSA true + * @run main/othervm TestLegacyAlgorithms signaturE.what false + * @run main/othervm TestLegacyAlgorithms SiGnAtUrE.SHa512/224withRSA false + * @run main/othervm -Djdk.crypto.legacyAlgorithms=SIGNATURe.sha512withRSA + * -Djdk.crypto.disabledAlgorithms=SIGNATURe.sha512withRSA + * TestLegacyAlgorithms SIGNATURe.sha512withRSA false true + */ + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.nio.charset.StandardCharsets; +import java.security.NoSuchAlgorithmException; +import java.security.Provider; +import java.security.Security; +import java.security.Signature; +import java.util.List; + +import jdk.test.lib.Asserts; +import jdk.test.lib.Utils; + +public class TestLegacyAlgorithms { + + private static final String PROP_NAME = "jdk.crypto.legacyAlgorithms"; + private static final List ALG_LIST = + List.of("sha512withRsa", "1.2.840.113549.1.1.13"); + + private static String saveWarn(ThrowingRunnable action) throws Exception { + PrintStream origErr = System.err; + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + PrintStream ps = new PrintStream(bOut, true, StandardCharsets.UTF_8); + try { + System.setErr(ps); + action.run(); + } finally { + ps.flush(); + System.setErr(origErr); + } + return bOut.toString(StandardCharsets.UTF_8); + } + + private static int countWarn(String warnS, String msg) { + int num = 0; + int index = 0; + while ((index = warnS.indexOf(msg, index)) >= 0) { + num++; + index += msg.length(); + } + return num; + } + + private static void checkOneWarn(String warnS, String alg) { + String warn1 = + "WARNING: An outdated Signature algorithm has been called by"; + String warn2 = "WARNING: " + alg + + " will be disabled by default in a future release"; + + Asserts.assertEQ(countWarn(warnS, warn1), 1, + "Expected one legacy warning for Signature " + alg + + " but got:\n" + warnS); + Asserts.assertEQ(countWarn(warnS, warn2), 1, + "Expected one future-disable warning for Signature " + + alg + " but got:\n" + warnS); + Asserts.assertTrue(warnS.contains("TestLegacyAlgorithms"), + "Expected warning to preserve caller: " + warnS); + } + + private static void checkNoWarn(String warnS) { + String warn1 = + "WARNING: An outdated Signature algorithm has been called by"; + String warn2 = + "will be disabled by default in a future release"; + Asserts.assertFalse(warnS.contains(warn1), + "Unexpected legacy warning for Signature: " + warnS); + Asserts.assertFalse(warnS.contains(warn2), + "Unexpected future-disable warning for Signature: " + warnS); + } + + private static void checkWarn(String label, String alg, + boolean shouldWarn, ThrowingRunnable action) throws Exception { + System.out.println("Testing " + label); + String warnS = saveWarn(action); + System.out.println("Warning emitted:\n" + warnS); + if (shouldWarn) { + checkOneWarn(warnS, alg); + } else { + checkNoWarn(warnS); + } + } + + // Disable the algorithm and check that a warning is not emitted. + private static void warnDisabledTest() + throws Exception { + checkWarn("no warning when the algorithm is disabled", + "sha512withRSA", false, () -> { + Utils.runAndCheckException( + () -> Signature.getInstance("sha512withRSA"), + NoSuchAlgorithmException.class); + }); + } + + private static void runTests(boolean shouldWarn) throws Exception { + for (String a : ALG_LIST) { + checkWarn("default provider: alg " + a, a, shouldWarn, + () -> DefaultSig.run(a)); + } + + Provider[] providers = Security.getProviders("Signature.SHA512withRSA"); + if (providers.length > 0) { + // First provider should warn, and later provider for the same + // algorithm will not warn. This is because warning is determined + // by caller class and algorithm string, not by provider. + Provider p = providers[0]; + for (String a : ALG_LIST) { + checkWarn("provider object " + p.getName() + ": alg " + a, + a, shouldWarn, () -> ProvObjSig.run(a, p)); + + checkWarn("provider name " + p.getName() + ": alg " + a, + a, shouldWarn, () -> ProvNameSig.run(a, p)); + } + } + } + + public static void main(String[] args) throws Exception { + String propValue = args[0]; + boolean shouldWarn = Boolean.parseBoolean(args[1]); + boolean warnDisabled = + args.length > 2 && Boolean.parseBoolean(args[2]); + System.out.println("Setting Security Prop " + PROP_NAME + " = " + + propValue); + Security.setProperty(PROP_NAME, propValue); + if (warnDisabled) { + warnDisabledTest(); + } else { + runTests(shouldWarn); + } + } + + @FunctionalInterface + private interface ThrowingRunnable { + void run() throws Exception; + } + + private static final class DefaultSig { + static void run(String alg) throws Exception { + Signature s = Signature.getInstance(alg); + System.out.println(" type lookup: got Signature w/ alg " + + s.getAlgorithm()); + + // Call the method twice, and make sure that only get one + // warning per caller. + s = Signature.getInstance(alg); + System.out.println(" type lookup again: got Signature w/ alg " + + s.getAlgorithm()); + } + } + + private static final class ProvObjSig { + static void run(String alg, Provider provider) throws Exception { + Signature s = Signature.getInstance(alg, provider); + System.out.println(" provider object: got Signature w/ alg " + + s.getAlgorithm()); + + // Call the method twice, and make sure that only get one + // warning per caller. + s = Signature.getInstance(alg, provider); + System.out.println(" provider object again: got Signature " + + "w/ alg " + s.getAlgorithm()); + } + } + + private static final class ProvNameSig { + static void run(String alg, Provider provider) throws Exception { + Signature s = Signature.getInstance(alg, provider.getName()); + System.out.println(" provider name: got Signature w/ alg " + + s.getAlgorithm()); + + // Call the method twice, and make sure that only get one + // warning per caller. + s = Signature.getInstance(alg, provider.getName()); + System.out.println(" provider name again: got Signature " + + "w/ alg " + s.getAlgorithm()); + } + } +} diff --git a/test/jdk/javax/crypto/Cipher/TestLegacyAlgorithms.java b/test/jdk/javax/crypto/Cipher/TestLegacyAlgorithms.java new file mode 100644 index 00000000000..f4336646cbd --- /dev/null +++ b/test/jdk/javax/crypto/Cipher/TestLegacyAlgorithms.java @@ -0,0 +1,221 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8376748 + * @summary Test JCE layer legacy algorithm warning for Cipher + * @library /test/lib + * @run main/othervm TestLegacyAlgorithms CIPHEr.Rsa/ECB/PKCS1Padding true + * @run main/othervm TestLegacyAlgorithms cipheR.rsA true + * @run main/othervm TestLegacyAlgorithms CIPher.what false + * @run main/othervm TestLegacyAlgorithms cipHER.RSA/ECB/PKCS1Padding2 false + * @run main/othervm -Djdk.crypto.legacyAlgorithms=CIPHER.RSA + * -Djdk.crypto.disabledAlgorithms=CIPHER.RSA + * TestLegacyAlgorithms CIPHER.RSA false true + + */ + +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.nio.charset.StandardCharsets; +import java.security.NoSuchAlgorithmException; +import java.security.Provider; +import java.security.Security; +import java.util.List; +import javax.crypto.Cipher; + +import jdk.test.lib.Asserts; +import jdk.test.lib.Utils; + +public class TestLegacyAlgorithms { + + private static final String PROP_NAME = "jdk.crypto.legacyAlgorithms"; + private static final List ALG_LIST = + List.of("Rsa/ECB/PKCS1Padding", "rSA"); + + private static String saveWarn(ThrowingRunnable action) throws Exception { + PrintStream origErr = System.err; + ByteArrayOutputStream bOut = new ByteArrayOutputStream(); + PrintStream ps = new PrintStream(bOut, true, StandardCharsets.UTF_8); + try { + System.setErr(ps); + action.run(); + } finally { + ps.flush(); + System.setErr(origErr); + } + return bOut.toString(StandardCharsets.UTF_8); + } + + private static int countWarn(String warnS, String msg) { + int num = 0; + int index = 0; + while ((index = warnS.indexOf(msg, index)) >= 0) { + num++; + index += msg.length(); + } + return num; + } + + private static void checkOneWarn(String warnS, String alg) { + String warn1 = + "WARNING: An outdated Cipher algorithm has been called by"; + String warn2 = "WARNING: " + alg + + " will be disabled by default in a future release"; + + Asserts.assertEQ(countWarn(warnS, warn1), 1, + "Expected one legacy warning for Cipher " + alg + + " but got:\n" + warnS); + Asserts.assertEQ(countWarn(warnS, warn2), 1, + "Expected one future-disable warning for Cipher " + + alg + " but got:\n" + warnS); + Asserts.assertTrue(warnS.contains("TestLegacyAlgorithms"), + "Expected warning to preserve caller: " + warnS); + } + + private static void checkNoWarn(String warnS) { + String warn1 = + "WARNING: An outdated Cipher algorithm has been called by"; + String warn2 = + "will be disabled by default in a future release"; + Asserts.assertFalse(warnS.contains(warn1), + "Unexpected legacy warning for Cipher: " + warnS); + Asserts.assertFalse(warnS.contains(warn2), + "Unexpected future-disable warning for Cipher: " + warnS); + } + + private static void checkWarn(String label, String alg, + boolean shouldWarn, ThrowingRunnable action) throws Exception { + System.out.println("Testing " + label); + String warnS = saveWarn(action); + System.out.println("Warning emitted:\n" + warnS); + if (shouldWarn) { + checkOneWarn(warnS, alg); + } else { + checkNoWarn(warnS); + } + } + + private static void warnDisabledTest() + throws Exception { + checkWarn("no warning when the algorithm is disabled", + "RSA", false, () -> { + Utils.runAndCheckException( + () -> Cipher.getInstance("RSA"), + NoSuchAlgorithmException.class); + }); + } + + private static void runTests(boolean shouldWarn) throws Exception { + for (String a : ALG_LIST) { + checkWarn("default provider: alg " + a, a, shouldWarn, + () -> DefaultCipher.run(a)); + } + + Provider provider = null; + for (Provider p : Security.getProviders()) { + // First provider should warn, and later provider for the same + // algorithm will not warn. This is because warning is determined + // by caller class and algorithm string, not by provider. + if (p.getService("Cipher", "RSA") != null) { + provider = p; + break; + } + } + if (provider != null) { + final Provider fp = provider; + for (String a : ALG_LIST) { + checkWarn("provider object " + fp.getName() + + ": alg " + a, a, shouldWarn, + () -> ProvObjCipher.run(a, fp)); + + checkWarn("provider name " + fp.getName() + + ": alg " + a, a, shouldWarn, + () -> ProvNameCipher.run(a, fp)); + } + } + } + + public static void main(String[] args) throws Exception { + String propValue = args[0]; + boolean shouldWarn = Boolean.parseBoolean(args[1]); + boolean warnDisabled = + args.length > 2 && Boolean.parseBoolean(args[2]); + System.out.println("Setting Security Prop " + PROP_NAME + " = " + + propValue); + Security.setProperty(PROP_NAME, propValue); + if (warnDisabled) { + warnDisabledTest(); + } else { + runTests(shouldWarn); + } + } + + @FunctionalInterface + private interface ThrowingRunnable { + void run() throws Exception; + } + + private static final class DefaultCipher { + static void run(String alg) throws Exception { + Cipher c = Cipher.getInstance(alg); + System.out.println(" type lookup: got Cipher w/ alg " + + c.getAlgorithm()); + + // Call the method twice, and make sure that only get one + // warning per caller. + c = Cipher.getInstance(alg); + System.out.println(" type lookup again: got Cipher w/ alg " + + c.getAlgorithm()); + } + } + + private static final class ProvObjCipher { + static void run(String alg, Provider provider) throws Exception { + Cipher c = Cipher.getInstance(alg, provider); + System.out.println(" provider object: got Cipher w/ alg " + + c.getAlgorithm()); + + // Call the method twice, and make sure that only get one + // warning per caller. + c = Cipher.getInstance(alg, provider); + System.out.println(" provider object again: got Cipher " + + "w/ alg " + c.getAlgorithm()); + } + } + + private static final class ProvNameCipher { + static void run(String alg, Provider provider) throws Exception { + Cipher c = Cipher.getInstance(alg, provider.getName()); + System.out.println(" provider name: got Cipher w/ alg " + + c.getAlgorithm()); + + // Call the method twice, and make sure that only get one + // warning per caller. + c = Cipher.getInstance(alg, provider.getName()); + System.out.println(" provider name again: got Cipher " + + "w/ alg " + c.getAlgorithm()); + } + } +} From 6ae23a0d6574dc8139aea93ea3c562a7410fcb34 Mon Sep 17 00:00:00 2001 From: William Kemper Date: Tue, 14 Jul 2026 23:41:25 +0000 Subject: [PATCH 150/305] 8388058: Shenandoah: Refactor arraycopy_work Reviewed-by: kdnilsen, shade --- .../gc/shenandoah/shenandoahBarrierSet.hpp | 10 +- .../shenandoahBarrierSet.inline.hpp | 113 +++++++++++------- 2 files changed, 76 insertions(+), 47 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.hpp b/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.hpp index 51b355e7042..ac896f24739 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.hpp @@ -33,6 +33,7 @@ class ShenandoahHeap; class ShenandoahBarrierSetAssembler; class ShenandoahCardTable; +class ShenandoahMarkingContext; class ShenandoahBarrierSet: public BarrierSet { private: @@ -126,6 +127,10 @@ public: private: template void arraycopy_marking(T* dst, size_t count); + + template + bool is_above_tams(const ShenandoahMarkingContext* ctx, T* dst) const; + template inline void arraycopy_evacuation(T* src, size_t count); template @@ -134,10 +139,7 @@ private: template inline void clone_work(oop src); - template - inline void arraycopy_work(T* src, size_t count); - - inline bool need_bulk_update(HeapWord* dst); + inline bool need_bulk_update(HeapWord* dst) const; public: // Callbacks for runtime accesses. template diff --git a/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.inline.hpp index f4b859afc44..af0622693fb 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.inline.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.inline.hpp @@ -509,40 +509,6 @@ OopCopyResult ShenandoahBarrierSet::AccessBarrier::oop_ return result; } -template -void ShenandoahBarrierSet::arraycopy_work(T* src, size_t count) { - // Young cycles are allowed to run when old marking is in progress. When old marking is in progress, - // this barrier will be called with ENQUEUE=true and HAS_FWD=false, even though the young generation - // may have forwarded objects. - assert(HAS_FWD == _heap->has_forwarded_objects() || _heap->is_concurrent_old_mark_in_progress(), "Forwarded object status is sane"); - // This function cannot be called to handle marking and evacuation at the same time (they operate on - // different sides of the copy). - static_assert((HAS_FWD || EVAC) != ENQUEUE, "Cannot evacuate and mark both sides of copy."); - - Thread* thread = Thread::current(); - SATBMarkQueue& queue = ShenandoahThreadLocalData::satb_mark_queue(thread); - ShenandoahMarkingContext* ctx = _heap->marking_context(); - const ShenandoahCollectionSet* const cset = _heap->collection_set(); - T* end = src + count; - for (T* elem_ptr = src; elem_ptr < end; elem_ptr++) { - T o = RawAccess<>::oop_load(elem_ptr); - if (!CompressedOops::is_null(o)) { - oop obj = CompressedOops::decode_not_null(o); - if (HAS_FWD && cset->is_in(obj)) { - oop fwd = ShenandoahForwarding::get_forwardee(obj); - if (EVAC && obj == fwd) { - fwd = _heap->evacuate_object(obj, thread); - } - shenandoah_assert_forwarded_except(elem_ptr, obj, _heap->cancelled_gc()); - ShenandoahHeap::atomic_update_oop(fwd, elem_ptr, o); - } - if (ENQUEUE && !ctx->is_marked_strong(obj)) { - _satb_mark_queue_set.enqueue_known_active(queue, obj); - } - } - } -} - template void ShenandoahBarrierSet::arraycopy_barrier(T* src, T* dst, size_t count) { if (count == 0) { @@ -574,31 +540,92 @@ void ShenandoahBarrierSet::arraycopy_barrier(T* src, T* dst, size_t count) { template void ShenandoahBarrierSet::arraycopy_marking(T* dst, size_t count) { assert(_heap->is_concurrent_mark_in_progress(), "only during marking"); - if (ShenandoahSATBBarrier) { - if (!_heap->marking_context()->allocated_after_mark_start(reinterpret_cast(dst)) || - (IS_GENERATIONAL && _heap->heap_region_containing(dst)->is_old() && _heap->is_concurrent_young_mark_in_progress())) { - arraycopy_work(dst, count); + if (!ShenandoahSATBBarrier) { + return; + } + + const ShenandoahMarkingContext* ctx = _heap->marking_context(); + // Everything allocated above TAMS is alive and doesn't need the barrier to keep it that way + if (is_above_tams(ctx, dst)) { + return; + } + + assert(!_heap->has_forwarded_objects() || _heap->is_concurrent_old_mark_in_progress(), "Forwarded object status is sane"); + Thread* thread = Thread::current(); + SATBMarkQueue& queue = ShenandoahThreadLocalData::satb_mark_queue(thread); + T* end = dst + count; + for (T* elem_ptr = dst; elem_ptr < end; ++elem_ptr) { + T o = RawAccess<>::oop_load(elem_ptr); + if (!CompressedOops::is_null(o)) { + oop obj = CompressedOops::decode_not_null(o); + if (!ctx->is_marked_strong(obj)) { + _satb_mark_queue_set.enqueue_known_active(queue, obj); + } } } } -inline bool ShenandoahBarrierSet::need_bulk_update(HeapWord* ary) { +template +bool ShenandoahBarrierSet::is_above_tams(const ShenandoahMarkingContext* ctx, T* dst) const { + // TAMS for an old region is unreliable during a young-only mark, so overwritten pointers in old dst arrays must + // be enqueued to preserve old->young referents copied in and overwritten after init mark. See JDK-8373116. + return ctx->allocated_after_mark_start(reinterpret_cast(dst)) + && !(IS_GENERATIONAL + && _heap->heap_region_containing(dst)->is_old() + && _heap->is_concurrent_young_mark_in_progress()); +} + +inline bool ShenandoahBarrierSet::need_bulk_update(HeapWord* ary) const { return ary < _heap->heap_region_containing(ary)->get_update_watermark(); } template void ShenandoahBarrierSet::arraycopy_evacuation(T* src, size_t count) { assert(_heap->is_evacuation_in_progress(), "only during evacuation"); - if (need_bulk_update(reinterpret_cast(src))) { - arraycopy_work(src, count); + if (!need_bulk_update(reinterpret_cast(src))) { + return; + } + + assert(_heap->has_forwarded_objects(), "Forwarded object status is sane"); + Thread* thread = Thread::current(); + const ShenandoahCollectionSet* const cset = _heap->collection_set(); + T* end = src + count; + for (T* elem_ptr = src; elem_ptr < end; ++elem_ptr) { + T o = RawAccess<>::oop_load(elem_ptr); + if (!CompressedOops::is_null(o)) { + oop obj = CompressedOops::decode_not_null(o); + if (cset->is_in(obj)) { + oop fwd = ShenandoahForwarding::get_forwardee(obj); + if (obj == fwd) { + fwd = _heap->evacuate_object(obj, thread); + } + shenandoah_assert_forwarded_except(elem_ptr, obj, _heap->cancelled_gc()); + ShenandoahHeap::atomic_update_oop(fwd, elem_ptr, o); + } + } } } template void ShenandoahBarrierSet::arraycopy_update(T* src, size_t count) { assert(_heap->is_update_refs_in_progress(), "only during update-refs"); - if (need_bulk_update(reinterpret_cast(src))) { - arraycopy_work(src, count); + if (!need_bulk_update(reinterpret_cast(src))) { + return; + } + + assert(_heap->has_forwarded_objects(), "Forwarded object status is sane"); + const ShenandoahCollectionSet* const cset = _heap->collection_set(); + T* end = src + count; + for (T* elem_ptr = src; elem_ptr < end; ++elem_ptr) { + T o = RawAccess<>::oop_load(elem_ptr); + if (!CompressedOops::is_null(o)) { + oop obj = CompressedOops::decode_not_null(o); + if (cset->is_in(obj)) { + oop fwd = ShenandoahForwarding::get_forwardee(obj); + shenandoah_assert_forwarded_except(elem_ptr, obj, _heap->cancelled_gc()); + ShenandoahHeap::atomic_update_oop(fwd, elem_ptr, o); + } + } } } From 10ec62d643a0c0174cd9ca74041bf13fa127d20e Mon Sep 17 00:00:00 2001 From: Harshit Dhiman Date: Wed, 15 Jul 2026 04:17:27 +0000 Subject: [PATCH 151/305] 8388016: [s390x] Remove the alignment from stubGenerator Reviewed-by: aph, amitkumar --- src/hotspot/cpu/s390/stubGenerator_s390.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/hotspot/cpu/s390/stubGenerator_s390.cpp b/src/hotspot/cpu/s390/stubGenerator_s390.cpp index d1601d4f147..381d1c02277 100644 --- a/src/hotspot/cpu/s390/stubGenerator_s390.cpp +++ b/src/hotspot/cpu/s390/stubGenerator_s390.cpp @@ -3306,10 +3306,12 @@ class StubGenerator: public StubCodeGenerator { // Make room for the thawed frames and align the stack. __ add64(Z_RET, frame::z_abi_160_size); - { // stack alignment - __ z_lcgr(Z_RET, Z_RET); // negate Z_RET value - __ z_nill(Z_RET, -frame::alignment_in_bytes); - } +#ifdef ASSERT + __ z_tmll(Z_RET, frame::alignment_in_bytes - 1); + __ asm_assert(Assembler::bcondAllZero, FILE_AND_LINE ": size is not aligned properly", 71); +#endif // ASSERT + + __ z_lcgr(Z_RET, Z_RET); // negate Z_RET value __ resize_frame( /* offset = */ Z_RET,/* fp = */ Z_R1, /* load_fp = */ true); __ z_lghi(Z_ARG2, kind); From 5c673a17c06d941ea0e058c05665abe9d08f158e Mon Sep 17 00:00:00 2001 From: Daniel Skantz Date: Wed, 15 Jul 2026 06:36:49 +0000 Subject: [PATCH 152/305] 8387414: Insufficient feature gate in vm_version_x86 for UseKyberIntrinsics Reviewed-by: semery, kvn --- .../cpu/x86/stubGenerator_x86_64_kyber.cpp | 16 +++++++--------- src/hotspot/cpu/x86/vm_version_x86.cpp | 2 +- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64_kyber.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64_kyber.cpp index c35a2a1bba6..840f848d3ba 100644 --- a/src/hotspot/cpu/x86/stubGenerator_x86_64_kyber.cpp +++ b/src/hotspot/cpu/x86/stubGenerator_x86_64_kyber.cpp @@ -1108,15 +1108,13 @@ address generate_kyberBarrettReduce_avx512(StubGenerator *stubgen, void StubGenerator::generate_kyber_stubs() { // Generate Kyber intrinsics code if (UseKyberIntrinsics) { - if (VM_Version::supports_evex()) { - StubRoutines::_kyberNtt = generate_kyberNtt_avx512(this, _masm); - StubRoutines::_kyberInverseNtt = generate_kyberInverseNtt_avx512(this, _masm); - StubRoutines::_kyberNttMult = generate_kyberNttMult_avx512(this, _masm); - StubRoutines::_kyberAddPoly_2 = generate_kyberAddPoly_2_avx512(this, _masm); - StubRoutines::_kyberAddPoly_3 = generate_kyberAddPoly_3_avx512(this, _masm); - StubRoutines::_kyber12To16 = generate_kyber12To16_avx512(this, _masm); - StubRoutines::_kyberBarrettReduce = generate_kyberBarrettReduce_avx512(this, _masm); - } + StubRoutines::_kyberNtt = generate_kyberNtt_avx512(this, _masm); + StubRoutines::_kyberInverseNtt = generate_kyberInverseNtt_avx512(this, _masm); + StubRoutines::_kyberNttMult = generate_kyberNttMult_avx512(this, _masm); + StubRoutines::_kyberAddPoly_2 = generate_kyberAddPoly_2_avx512(this, _masm); + StubRoutines::_kyberAddPoly_3 = generate_kyberAddPoly_3_avx512(this, _masm); + StubRoutines::_kyber12To16 = generate_kyber12To16_avx512(this, _masm); + StubRoutines::_kyberBarrettReduce = generate_kyberBarrettReduce_avx512(this, _masm); } } diff --git a/src/hotspot/cpu/x86/vm_version_x86.cpp b/src/hotspot/cpu/x86/vm_version_x86.cpp index 53696ee6ef3..6112c280a1d 100644 --- a/src/hotspot/cpu/x86/vm_version_x86.cpp +++ b/src/hotspot/cpu/x86/vm_version_x86.cpp @@ -1270,7 +1270,7 @@ void VM_Version::get_processor_features() { // Kyber Intrinsics // Currently we only have them for AVX512 - if (supports_evex() && supports_avx512bw()) { + if (supports_avx512vlbw()) { if (FLAG_IS_DEFAULT(UseKyberIntrinsics)) { UseKyberIntrinsics = true; } From 2b05a136cb80221a4252719510f817e268da5d5a Mon Sep 17 00:00:00 2001 From: Quan Anh Mai Date: Wed, 15 Jul 2026 07:08:24 +0000 Subject: [PATCH 153/305] 8387328: C2: A Phi must not have a narrower Type than its inputs Reviewed-by: thartmann, vlivanov --- src/hotspot/share/opto/parse.hpp | 1 + src/hotspot/share/opto/parse1.cpp | 31 ++- .../jtreg/compiler/parsing/TestNarrowPhi.java | 196 ++++++++++++++++++ 3 files changed, 225 insertions(+), 3 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/parsing/TestNarrowPhi.java diff --git a/src/hotspot/share/opto/parse.hpp b/src/hotspot/share/opto/parse.hpp index 5118019fc31..426720b5bba 100644 --- a/src/hotspot/share/opto/parse.hpp +++ b/src/hotspot/share/opto/parse.hpp @@ -480,6 +480,7 @@ class Parse : public GraphKit { // Helper: Merge the current mapping into the given basic block void merge_common(Block* target, int pnum); // Helper functions for merging individual cells. + Node* maybe_narrow_phi_input(Node* ctrl, Node* n, const Type* phi_type); PhiNode *ensure_phi( int idx, bool nocreate = false); PhiNode *ensure_memory_phi(int idx, bool nocreate = false); // Helper to merge the current memory state into the given basic block diff --git a/src/hotspot/share/opto/parse1.cpp b/src/hotspot/share/opto/parse1.cpp index 6a400631bff..2d74866e570 100644 --- a/src/hotspot/share/opto/parse1.cpp +++ b/src/hotspot/share/opto/parse1.cpp @@ -1883,7 +1883,8 @@ void Parse::merge_common(Parse::Block* target, int pnum) { if (phi != nullptr) { assert(n != top() || r->in(pnum) == top(), "live value must not be garbage"); assert(phi->region() == r, ""); - phi->set_req(pnum, n); // Then add 'n' to the merge + phi->set_req(pnum, maybe_narrow_phi_input(r->in(pnum), n, _gvn.type(phi))); + if (pnum == PhiNode::Input) { // Last merge for this Phi. // So far, Phis have had a reasonable type from ciTypeFlow. @@ -2060,6 +2061,21 @@ int Parse::Block::add_new_path() { return pnum; } +// The verifier ensures that the ciType of phi is not narrower than its inputs. However, since +// TypeOopPtr::make_from_klass may be aggressive if it finds that the ciType has only a single +// concrete subtype, and concurrent class loading/unloading may change this property during the +// compilation process, it may be the case that the Type of phi is narrower than its inputs. In +// those cases, we need to insert a CheckCastPP, otherwise several PhiNode idealization may be +// unsound, as we may replace a Phi which has a narrower Type with one of its input which has a +// wider Type. +Node* Parse::maybe_narrow_phi_input(Node* ctrl, Node* n, const Type* phi_type) { + if (phi_type->isa_oopptr() != nullptr && !_gvn.type(n)->higher_equal(phi_type)) { + n = new CheckCastPPNode(ctrl, n, phi_type, ConstraintCastNode::DependencyType::NonFloatingNarrowing); + n = _gvn.transform(n); + } + return n; +} + //------------------------------ensure_phi------------------------------------- // Turn the idx'th entry of the current map into a Phi PhiNode *Parse::ensure_phi(int idx, bool nocreate) { @@ -2108,9 +2124,18 @@ PhiNode *Parse::ensure_phi(int idx, bool nocreate) { return nullptr; } - PhiNode* phi = PhiNode::make(region, o, t); + PhiNode* phi = new PhiNode(region, t); gvn().set_type(phi, t); - if (C->do_escape_analysis()) record_for_igvn(phi); + for (uint i = 1; i < phi->req(); i++) { + Node* ctrl = region->in(i); + if (ctrl != nullptr) { + phi->init_req(i, maybe_narrow_phi_input(ctrl, o, t)); + } + } + + if (C->do_escape_analysis()) { + record_for_igvn(phi); + } map->set_req(idx, phi); return phi; } diff --git a/test/hotspot/jtreg/compiler/parsing/TestNarrowPhi.java b/test/hotspot/jtreg/compiler/parsing/TestNarrowPhi.java new file mode 100644 index 00000000000..879234a62cf --- /dev/null +++ b/test/hotspot/jtreg/compiler/parsing/TestNarrowPhi.java @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package compiler.parsing; + +import java.io.IOException; +import java.util.Objects; +import jdk.test.lib.Asserts; +import jdk.test.whitebox.WhiteBox; +import jdk.test.lib.process.ProcessTools; + +/* + * @test + * @bug 8387328 + * @summary A Phi having a narrower Type than its inputs may result in incorrect scheduling + * @library /test/lib + * @requires vm.compiler2.enabled + * @modules java.base/jdk.internal.misc + * @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 + * ${test.main.class} + */ +public class TestNarrowPhi { + private static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox(); + private static volatile Throwable failure; + + private static abstract class P { + int u; + + private static P allocate() { + return new C(); + } + } + + private static class C extends P { + int v; + } + + public static void main(String[] args) throws IOException, InterruptedException, NoSuchMethodException { + if (args.length == 0) { + spawnTestProcesses(); + } else { + int idx = Integer.parseInt(args[0]); + runTest(idx); + } + } + + private static void spawnTestProcesses() throws IOException, InterruptedException { + String testClassName = TestNarrowPhi.class.getName(); + // Since we cannot reliably coordinate the compiler thread and the thread that load the + // child class, randomly delaying one of them + for (int i = 0; i <= 10; i++) { + var builder = ProcessTools.createTestJavaProcessBuilder( + "-Xbootclasspath/a:.", + "-Xbatch", + "-XX:-TieredCompilation", + "-XX:+UnlockDiagnosticVMOptions", + "-XX:+WhiteBoxAPI", + "-XX:CompileOnly=" + testClassName + "::test*", + "-XX:CompileCommand=inline," + testClassName + "::inline*", + "-XX:CompileCommand=dontinline," + testClassName + "::nonInline", + "-XX:CompileCommand=delayinline," + testClassName + "::inlineTestHelper", + testClassName, + Integer.toString(i)); + builder.redirectOutput(ProcessBuilder.Redirect.INHERIT); + builder.redirectError(ProcessBuilder.Redirect.INHERIT); + var process = builder.start(); + process.waitFor(); + Asserts.assertEQ(0, process.exitValue()); + } + } + + private static void runTest(int idx) throws InterruptedException, NoSuchMethodException { + var testMethod = TestNarrowPhi.class.getDeclaredMethod("testMethod", boolean.class, P.class, P.class, P.class); + var _ = Objects.class; + Thread loader = new Thread(() -> { + try { + if (idx < 5) { + Thread.sleep((5 - idx) * 10L); + } + var _ = C.class; + } catch (Exception e) { + failure = e; + } + }); + loader.start(); + + if (idx > 5) { + Thread.sleep((idx - 5) * 10L); + } + if (!WHITE_BOX.enqueueMethodForCompilation(testMethod, 4)) { + throw new RuntimeException("Could not enqueue the test method for C2 compilation"); + } + while (WHITE_BOX.isMethodQueuedForCompilation(testMethod)) { + Thread.yield(); + } + P p = P.allocate(); + Asserts.assertEQ(0, testMethod(true, p, p, p)); + loader.join(); + if (failure != null) { + throw new RuntimeException(failure); + } + } + + private static int testMethod(boolean b, P p1, P p2, P p3) { + // Arbitrarily delay the parser between generating the Type for P1 and for the loop Phi + // below + inline0(); + // This method is late-inlined, which increases the chance that C has been loaded then + return inlineTestHelper(b, p1, p2, p3); + } + + private static int inlineTestHelper(boolean b, P p1, P p2, P p3) { + // Random access that can be used as an implicit null-check, so that the load below can + // float freely + p1.u = 0; + P p = p1; + for (int i = 0; i < 1; i++) { + if (i % 2 != 0) { + p = p2; + } + } + + C cp = (C) Objects.requireNonNull(p); + C cp3 = (C) Objects.requireNonNull(p3); + int res = cp.v; + cp3.v = 1; + if (b) { + cp3.v = 2; + return res; + } else { + return nonInline(); + } + } + + private static int nonInline() { + return 0; + } + + private static void inline0() { + inline1(); + inline1(); + inline1(); + inline1(); + } + + private static void inline1() { + inline2(); + inline2(); + inline2(); + inline2(); + } + + private static void inline2() { + inline3(); + inline3(); + inline3(); + inline3(); + } + + private static void inline3() { + inline4(); + inline4(); + inline4(); + inline4(); + } + + private static void inline4() { + inline5(); + inline5(); + inline5(); + inline5(); + } + + private static void inline5() {} +} From f146847ca1289da313b3d07f14591ab669abedc1 Mon Sep 17 00:00:00 2001 From: EunHyunsu Date: Wed, 15 Jul 2026 07:16:10 +0000 Subject: [PATCH 154/305] 8380549: HttpCookie.expiryDate2DeltaSeconds returns 0 on parse failure, causing immediate cookie expiration Reviewed-by: vyazici, michaelm --- .../share/classes/java/net/HttpCookie.java | 21 ++++++------ test/jdk/java/net/CookieHandler/B6791927.java | 4 +-- .../net/HttpCookie/ExpiredCookieTest.java | 33 ++++++++++++++----- .../java.base/java/net/MaxAgeExpires.java | 23 +++++++++++++ 4 files changed, 60 insertions(+), 21 deletions(-) diff --git a/src/java.base/share/classes/java/net/HttpCookie.java b/src/java.base/share/classes/java/net/HttpCookie.java index 3c633522bdf..2b3a5cbb6a5 100644 --- a/src/java.base/share/classes/java/net/HttpCookie.java +++ b/src/java.base/share/classes/java/net/HttpCookie.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -1005,12 +1005,13 @@ public final class HttpCookie implements Cloneable { } } catch (NumberFormatException ignored) {} - try { - if (expiresValue != null) { - long delta = cookie.expiryDate2DeltaSeconds(expiresValue); + if (expiresValue != null) { + Calendar cal = parseExpires(expiresValue); + if (cal != null) { + long delta = (cal.getTimeInMillis() - cookie.whenCreated) / 1000; cookie.maxAge = (delta > 0 ? delta : 0); } - } catch (NumberFormatException ignored) {} + } } private static void assignAttribute(HttpCookie cookie, @@ -1082,10 +1083,10 @@ public final class HttpCookie implements Cloneable { * @param dateString * a date string in one of the formats defined in Netscape cookie spec * - * @return delta seconds between this cookie's creation time and the time - * specified by dateString + * @return the parsed date as a Calendar, or null if none of the + * formats could parse the given date string */ - private long expiryDate2DeltaSeconds(String dateString) { + private static Calendar parseExpires(String dateString) { Calendar cal = new GregorianCalendar(GMT); for (int i = 0; i < COOKIE_DATE_FORMATS.length; i++) { SimpleDateFormat df = new SimpleDateFormat(COOKIE_DATE_FORMATS[i], @@ -1108,12 +1109,12 @@ public final class HttpCookie implements Cloneable { } cal.set(Calendar.YEAR, year); } - return (cal.getTimeInMillis() - whenCreated) / 1000; + return cal; } catch (Exception e) { // Ignore, try the next date format } } - return 0; + return null; } /* diff --git a/test/jdk/java/net/CookieHandler/B6791927.java b/test/jdk/java/net/CookieHandler/B6791927.java index bc5374b2a98..ceeff260665 100644 --- a/test/jdk/java/net/CookieHandler/B6791927.java +++ b/test/jdk/java/net/CookieHandler/B6791927.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2009, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,7 +24,7 @@ /** * @test * @bug 6791927 8233886 - * @summary Wrong Locale in HttpCookie::expiryDate2DeltaSeconds + * @summary Wrong Locale in HttpCookie::parseExpires * @run main/othervm B6791927 */ diff --git a/test/jdk/java/net/HttpCookie/ExpiredCookieTest.java b/test/jdk/java/net/HttpCookie/ExpiredCookieTest.java index 5cf7208d2ca..e2769d8dd61 100644 --- a/test/jdk/java/net/HttpCookie/ExpiredCookieTest.java +++ b/test/jdk/java/net/HttpCookie/ExpiredCookieTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,7 +23,7 @@ /* * @test - * @bug 8000525 + * @bug 8000525 8380549 * @library /test/lib */ @@ -33,6 +33,8 @@ import java.io.*; import java.text.*; import jdk.test.lib.net.URIBuilder; +import static jdk.test.lib.Asserts.assertEquals; + public class ExpiredCookieTest { // lifted from HttpCookie.java private final static String[] COOKIE_DATE_FORMATS = { @@ -92,15 +94,28 @@ public class ExpiredCookieTest { cm.put(uri, header); CookieStore cookieJar = cm.getCookieStore(); - List cookies = cookieJar.getCookies(); + Set names = new TreeSet<>(); + for (HttpCookie cookie : cookieJar.getCookies()) + names.add(cookie.getName()); + + Set expected; if (COOKIE_DATE_FORMATS[i].contains("yyyy")) { - if (cookies.size() != 2) - throw new RuntimeException( - "Incorrectly parsing a bad date"); - } else if (cookies.size() != 1) { - throw new RuntimeException( - "Incorrectly parsing a bad date"); + // Four-digit years parse unambiguously: TEST1 and TEST2 are + // in the past and expire, while TEST3 and TEST4 remain. + expected = new TreeSet<>(List.of("TEST3", "TEST4")); + } else { + // Two-digit years make TEST2 and TEST3 resolve to a mismatched + // day-of-week, so strict parsing rejects the Expires value; per + // RFC 6265 section 5.2.1 an unparseable Expires is ignored, so + // they remain as session cookies. TEST1 parses cleanly but is + // already expired, so it is dropped. TEST4's two-digit year + // round-trips to itself (69 -> 2069), so it parses and remains + // because its expiry is still in the future. + expected = new TreeSet<>(List.of("TEST2", "TEST3", "TEST4")); } + assertEquals(expected, names, + "Incorrectly parsing a bad date, format: " + + COOKIE_DATE_FORMATS[i]); } } } diff --git a/test/jdk/java/net/HttpCookie/whitebox/java.base/java/net/MaxAgeExpires.java b/test/jdk/java/net/HttpCookie/whitebox/java.base/java/net/MaxAgeExpires.java index 6704a290836..79139d69e50 100644 --- a/test/jdk/java/net/HttpCookie/whitebox/java.base/java/net/MaxAgeExpires.java +++ b/test/jdk/java/net/HttpCookie/whitebox/java.base/java/net/MaxAgeExpires.java @@ -33,6 +33,7 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; public class MaxAgeExpires { @@ -138,4 +139,26 @@ public class MaxAgeExpires { cookie.setMaxAge(-2); assertEquals(-2, cookie.getMaxAge()); } + + public static Object[][] unparseableDates() { + return new Object[][] { + { "GARBAGE" }, + { "2024-01-01T00:00:00Z" }, // format not supported by RFC-6265 + { "January 1, 2099 00:00:00 GMT" } // format not supported by RFC-6265 + }; + } + + @ParameterizedTest + @MethodSource("unparseableDates") + public void testUnparseableExpires(String badDate) { + // RFC 6265 section 5.2.1: if the expires value fails to parse, + // the cookie-av should be ignored. + // That results in the HttpCookie implementation to have maxAge value of -1. + HttpCookie cookie = HttpCookie.parse( + "Set-Cookie: name=value; expires=" + badDate).get(0); + assertEquals(-1, cookie.getMaxAge(), + "Unparseable expires=\"" + badDate + "\" should be ignored"); + assertFalse(cookie.hasExpired(), + "Cookie with ignored expires should not be expired"); + } } From d6899460c7ca5daf402258e5d3ccb399a5607d3a Mon Sep 17 00:00:00 2001 From: Roland Westrelin Date: Wed, 15 Jul 2026 09:25:58 +0000 Subject: [PATCH 155/305] 8380166: C2: crash in compiled code due to zero division because of widened CastII Reviewed-by: qamai, chagedorn --- src/hotspot/share/opto/c2_globals.hpp | 4 +- src/hotspot/share/opto/castnode.cpp | 3 - src/hotspot/share/opto/cfgnode.cpp | 14 +- src/hotspot/share/opto/classes.hpp | 1 + src/hotspot/share/opto/compile.cpp | 25 +- src/hotspot/share/opto/compile.hpp | 9 +- src/hotspot/share/opto/convertnode.cpp | 14 - src/hotspot/share/opto/divnode.cpp | 14 + src/hotspot/share/opto/divnode.hpp | 13 +- src/hotspot/share/opto/loopopts.cpp | 2 +- src/hotspot/share/opto/movenode.cpp | 5 - src/hotspot/share/opto/node.cpp | 37 +- src/hotspot/share/opto/node.hpp | 13 +- src/hotspot/share/opto/parse2.cpp | 2 +- src/hotspot/share/opto/phaseX.cpp | 123 +- src/hotspot/share/opto/phaseX.hpp | 6 +- src/hotspot/share/opto/rootnode.cpp | 45 + src/hotspot/share/opto/rootnode.hpp | 31 + src/hotspot/share/opto/vectornode.cpp | 2 +- .../c2/TestDeadPathManyDeadDataNodes.java | 1301 +++++++++++++++++ .../TestDivByZeroInLiveCFGPath.java | 64 + .../TestZeroDivModWidenedCastII.java | 1122 ++++++++++++++ ...yAccessAboveRCAfterRCCastIIEliminated.java | 24 +- 23 files changed, 2793 insertions(+), 81 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/c2/TestDeadPathManyDeadDataNodes.java create mode 100644 test/hotspot/jtreg/compiler/integerArithmetic/TestDivByZeroInLiveCFGPath.java create mode 100644 test/hotspot/jtreg/compiler/integerArithmetic/TestZeroDivModWidenedCastII.java diff --git a/src/hotspot/share/opto/c2_globals.hpp b/src/hotspot/share/opto/c2_globals.hpp index 9ff88e8c310..dd9288f7617 100644 --- a/src/hotspot/share/opto/c2_globals.hpp +++ b/src/hotspot/share/opto/c2_globals.hpp @@ -922,8 +922,8 @@ "Use StoreStore barrier instead of Release barrier at the end " \ "of constructors") \ \ - develop(bool, KillPathsReachableByDeadTypeNode, true, \ - "When a Type node becomes top, make paths where the node is " \ + develop(bool, KillPathsReachableByDeadDataNode, true, \ + "When a data node becomes top, make paths where the node is " \ "used dead by replacing them with a Halt node. Turning this off " \ "could corrupt the graph in rare cases and should be used with " \ "care.") \ diff --git a/src/hotspot/share/opto/castnode.cpp b/src/hotspot/share/opto/castnode.cpp index 7bb6b1dcb77..076a95acfd8 100644 --- a/src/hotspot/share/opto/castnode.cpp +++ b/src/hotspot/share/opto/castnode.cpp @@ -111,9 +111,6 @@ Node* ConstraintCastNode::Ideal(PhaseGVN* phase, bool can_reshape) { if (in(0) != nullptr && remove_dead_region(phase, can_reshape)) { return this; } - if (in(1) != nullptr && phase->type(in(1)) != Type::TOP) { - return TypeNode::Ideal(phase, can_reshape); - } return nullptr; } diff --git a/src/hotspot/share/opto/cfgnode.cpp b/src/hotspot/share/opto/cfgnode.cpp index 828e5bf299f..ed5da046608 100644 --- a/src/hotspot/share/opto/cfgnode.cpp +++ b/src/hotspot/share/opto/cfgnode.cpp @@ -693,14 +693,13 @@ Node *RegionNode::Ideal(PhaseGVN *phase, bool can_reshape) { if (add_to_worklist) { igvn->add_users_to_worklist(this); // Check for further allowed opts } - for (DUIterator_Last imin, i = last_outs(imin); i >= imin; --i) { + uint edges_removed; + for (DUIterator_Last imin, i = last_outs(imin); i >= imin; i -= edges_removed) { + edges_removed = 1; Node* n = last_out(i); igvn->hash_delete(n); // Remove from worklist before modifying edges if (n->outcnt() == 0) { - int uses_found = n->replace_edge(this, phase->C->top(), igvn); - if (uses_found > 1) { // (--i) done at the end of the loop. - i -= (uses_found - 1); - } + edges_removed = n->replace_edge(this, phase->C->top(), igvn); continue; } if( n->is_Phi() ) { // Collapse all Phis @@ -719,10 +718,7 @@ Node *RegionNode::Ideal(PhaseGVN *phase, bool can_reshape) { } else if( n->is_Region() ) { // Update all incoming edges assert(n != this, "Must be removed from DefUse edges"); - int uses_found = n->replace_edge(this, parent_ctrl, igvn); - if (uses_found > 1) { // (--i) done at the end of the loop. - i -= (uses_found - 1); - } + edges_removed = n->replace_edge(this, parent_ctrl, igvn); } else { assert(n->in(0) == this, "Expect RegionNode to be control parent"); diff --git a/src/hotspot/share/opto/classes.hpp b/src/hotspot/share/opto/classes.hpp index 53a72f979db..c296237de37 100644 --- a/src/hotspot/share/opto/classes.hpp +++ b/src/hotspot/share/opto/classes.hpp @@ -121,6 +121,7 @@ macro(CompareAndExchangeI) macro(CompareAndExchangeL) macro(CompareAndExchangeP) macro(CompareAndExchangeN) +macro(DeadPath) macro(GetAndAddB) macro(GetAndAddS) macro(GetAndAddI) diff --git a/src/hotspot/share/opto/compile.cpp b/src/hotspot/share/opto/compile.cpp index 93d8e4c425d..db43c6fb1c4 100644 --- a/src/hotspot/share/opto/compile.cpp +++ b/src/hotspot/share/opto/compile.cpp @@ -312,6 +312,8 @@ void Compile::identify_useful_nodes(Unique_Node_List &useful) { // If 'top' is cached, declare it useful to preserve cached node if (cached_top_node()) { useful.push(cached_top_node()); } + if (dead_path()) { useful.push(dead_path()); } + // Push all useful nodes onto the list, breadthfirst for( uint next = 0; next < useful.size(); ++next ) { assert( next < unique(), "Unique useful nodes < total nodes"); @@ -388,7 +390,7 @@ void Compile::remove_useless_node(Node* dead) { // it reachable by adding use edges. So, we will NOT count Con nodes // as dead to be conservative about the dead node count at any // given time. - if (!dead->is_Con()) { + if (!dead->is_Con() && dead != dead_path()) { record_dead_node(dead->_idx); } if (dead->is_macro()) { @@ -684,6 +686,7 @@ Compile::Compile(ciEnv* ci_env, ciMethod* target, int osr_bci, _node_arena_one(mtCompiler, Arena::Tag::tag_node), _node_arena_two(mtCompiler, Arena::Tag::tag_node), _node_arena(&_node_arena_one), + _dead_path(nullptr), _mach_constant_base_node(nullptr), _Compile_types(mtCompiler, Arena::Tag::tag_type), _initial_gvn(nullptr), @@ -754,6 +757,7 @@ Compile::Compile(ciEnv* ci_env, ciMethod* target, int osr_bci, } Init(/*do_aliasing=*/ true); + set_dead_path(new DeadPathNode()); print_compile_messages(); @@ -963,6 +967,7 @@ Compile::Compile(ciEnv* ci_env, _node_arena_one(mtCompiler, Arena::Tag::tag_node), _node_arena_two(mtCompiler, Arena::Tag::tag_node), _node_arena(&_node_arena_one), + _dead_path(nullptr), _mach_constant_base_node(nullptr), _Compile_types(mtCompiler, Arena::Tag::tag_type), _initial_gvn(nullptr), @@ -2630,6 +2635,9 @@ void Compile::Optimize() { } } + // Unique DeadPath node should not be used anymore + _dead_path = nullptr; + print_method(PHASE_OPTIMIZE_FINISHED, 2); DEBUG_ONLY(set_phase_optimize_finished();) } @@ -3938,6 +3946,21 @@ void Compile::final_graph_reshaping_main_switch(Node* n, Final_Reshape_Counts& f break; } #endif + case Op_DeadPath: { + // The CFG inputs are dead paths. Replace the DeadPath with a Region and insert a Halt node. + assert(n->req() > 1, "why not removed if no input other than itself?"); + RegionNode* r = new RegionNode(n->req()); + for (uint i = 1; i < n->req(); ++i) { + r->set_req(i, n->in(i)); + } + n->disconnect_inputs(this); + Node* frame = start()->proj_out(TypeFunc::FramePtr); + stringStream ss; + ss.print("dead path discovered by data nodes during igvn"); + Node* halt = new HaltNode(r, frame, ss.as_string(comp_arena())); + root()->set_req(root()->find_edge(n), halt); + break; + } default: assert(!n->is_Call(), ""); assert(!n->is_Mem(), ""); diff --git a/src/hotspot/share/opto/compile.hpp b/src/hotspot/share/opto/compile.hpp index ab36f59a28f..73e136787f8 100644 --- a/src/hotspot/share/opto/compile.hpp +++ b/src/hotspot/share/opto/compile.hpp @@ -57,6 +57,7 @@ class CallStaticJavaNode; class CloneMap; class CompilationFailureInfo; class ConnectionGraph; +class DeadPathNode; class IdealGraphPrinter; class InlineTree; class Matcher; @@ -427,7 +428,7 @@ public: private: RootNode* _root; // Unique root of compilation, or null after bail-out. Node* _top; // Unique top node. (Reset by various phases.) - + DeadPathNode* _dead_path; // Unique DeadPath node Node* _immutable_memory; // Initial memory state Node* _recent_alloc_obj; @@ -897,6 +898,12 @@ public: Arena* old_arena() { return (&_node_arena_one == _node_arena) ? &_node_arena_two : &_node_arena_one; } RootNode* root() const { return _root; } void set_root(RootNode* r) { _root = r; } + DeadPathNode* dead_path() const { return _dead_path; } + + void set_dead_path(DeadPathNode* dead_path) { + assert(_dead_path == nullptr, "can only set once"); + _dead_path = dead_path; + } StartNode* start() const; // (Derived from root.) void verify_start(StartNode* s) const NOT_DEBUG_RETURN; Node* immutable_memory(); diff --git a/src/hotspot/share/opto/convertnode.cpp b/src/hotspot/share/opto/convertnode.cpp index a495814da61..d706a13feb3 100644 --- a/src/hotspot/share/opto/convertnode.cpp +++ b/src/hotspot/share/opto/convertnode.cpp @@ -755,13 +755,6 @@ bool Compile::push_thru_add(PhaseGVN* phase, Node* z, const TypeInteger* tz, con //------------------------------Ideal------------------------------------------ Node* ConvI2LNode::Ideal(PhaseGVN* phase, bool can_reshape) { - if (in(1) != nullptr && phase->type(in(1)) != Type::TOP) { - Node* progress = TypeNode::Ideal(phase, can_reshape); - if (progress != nullptr) { - return progress; - } - } - const TypeLong* this_type = this->type()->is_long(); if (can_reshape && !phase->C->post_loop_opts_phase()) { // makes sure we run ::Value to potentially remove type assertion after loop opts @@ -864,13 +857,6 @@ const Type* ConvL2INode::Value(PhaseGVN* phase) const { // Return a node which is more "ideal" than the current node. // Blow off prior masking to int Node* ConvL2INode::Ideal(PhaseGVN* phase, bool can_reshape) { - if (in(1) != nullptr && phase->type(in(1)) != Type::TOP) { - Node* progress = TypeNode::Ideal(phase, can_reshape); - if (progress != nullptr) { - return progress; - } - } - Node *andl = in(1); uint andl_op = andl->Opcode(); if( andl_op == Op_AndL ) { diff --git a/src/hotspot/share/opto/divnode.cpp b/src/hotspot/share/opto/divnode.cpp index 1687ff2cade..3b51491294e 100644 --- a/src/hotspot/share/opto/divnode.cpp +++ b/src/hotspot/share/opto/divnode.cpp @@ -1031,6 +1031,10 @@ const Type* UDivINode::Value(PhaseGVN* phase) const { if( t1 == Type::TOP ) return Type::TOP; if( t2 == Type::TOP ) return Type::TOP; + if (t2 == TypeInt::ZERO) { + return Type::TOP; + } + // x/x == 1 since we always generate the dynamic divisor check for 0. if (in(1) == in(2)) { return TypeInt::ONE; @@ -1067,6 +1071,10 @@ const Type* UDivLNode::Value(PhaseGVN* phase) const { if( t1 == Type::TOP ) return Type::TOP; if( t2 == Type::TOP ) return Type::TOP; + if (t2 == TypeLong::ZERO) { + return Type::TOP; + } + // x/x == 1 since we always generate the dynamic divisor check for 0. if (in(1) == in(2)) { return TypeLong::ONE; @@ -1380,6 +1388,9 @@ Node* UModINode::Ideal(PhaseGVN* phase, bool can_reshape) { } const Type* UModINode::Value(PhaseGVN* phase) const { + if (phase->type(in(2)) == TypeInt::ZERO) { + return Type::TOP; + } return unsigned_mod_value(phase, this); } @@ -1520,6 +1531,9 @@ Node *UModLNode::Ideal(PhaseGVN *phase, bool can_reshape) { } const Type* UModLNode::Value(PhaseGVN* phase) const { + if (phase->type(in(2)) == TypeLong::ZERO) { + return Type::TOP; + } return unsigned_mod_value(phase, this); } diff --git a/src/hotspot/share/opto/divnode.hpp b/src/hotspot/share/opto/divnode.hpp index 366e3fb882d..de89dcaad06 100644 --- a/src/hotspot/share/opto/divnode.hpp +++ b/src/hotspot/share/opto/divnode.hpp @@ -40,7 +40,9 @@ private: bool _pinned; protected: - DivModIntegerNode(Node* c, Node* dividend, Node* divisor) : Node(c, dividend, divisor), _pinned(false) {} + DivModIntegerNode(Node* c, Node* dividend, Node* divisor) : Node(c, dividend, divisor), _pinned(false) { + init_class_id(Class_DivModInteger); + } private: virtual uint size_of() const override { return sizeof(DivModIntegerNode); } @@ -52,6 +54,15 @@ private: res->_pinned = true; return res; } + +public: + const TypeInteger* zero() const { + if (bottom_type() == TypeInt::INT) { + return TypeInt::ZERO; + } + assert(bottom_type() == TypeLong::LONG, "should be int or long"); + return TypeLong::ZERO; + } }; //------------------------------DivINode--------------------------------------- diff --git a/src/hotspot/share/opto/loopopts.cpp b/src/hotspot/share/opto/loopopts.cpp index ccd53129a87..d525c274ef6 100644 --- a/src/hotspot/share/opto/loopopts.cpp +++ b/src/hotspot/share/opto/loopopts.cpp @@ -1725,7 +1725,7 @@ void PhaseIdealLoop::try_sink_out_of_loop(Node* n) { !n->is_OpaqueTemplateAssertionPredicate() && !is_raw_to_oop_cast && // don't extend live ranges of raw oops n->Opcode() != Op_CreateEx && - (KillPathsReachableByDeadTypeNode || !n->is_Type()) + (KillPathsReachableByDeadDataNode || !n->is_Type()) ) { Node *n_ctrl = get_ctrl(n); IdealLoopTree *n_loop = get_loop(n_ctrl); diff --git a/src/hotspot/share/opto/movenode.cpp b/src/hotspot/share/opto/movenode.cpp index 6b6becb434f..7d38238da2f 100644 --- a/src/hotspot/share/opto/movenode.cpp +++ b/src/hotspot/share/opto/movenode.cpp @@ -90,11 +90,6 @@ Node *CMoveNode::Ideal(PhaseGVN *phase, bool can_reshape) { phase->type(in(IfTrue)) == Type::TOP) { return nullptr; } - Node* progress = TypeNode::Ideal(phase, can_reshape); - if (progress != nullptr) { - return progress; - } - // Check for Min/Max patterns. This is called before constants are pushed to the right input, as that transform can // make BoolTests non-canonical. Node* minmax = Ideal_minmax(phase, this); diff --git a/src/hotspot/share/opto/node.cpp b/src/hotspot/share/opto/node.cpp index 726a3ea1b55..264216ddc6d 100644 --- a/src/hotspot/share/opto/node.cpp +++ b/src/hotspot/share/opto/node.cpp @@ -597,6 +597,7 @@ void Node::setup_is_top() { //------------------------------~Node------------------------------------------ // Fancy destructor; eagerly attempt to reclaim Node numberings and storage void Node::destruct(PhaseValues* phase) { + assert(this != Compile::current()->dead_path(), "we want to keep the unique DeadPath node around"); Compile* compile = (phase != nullptr) ? phase->C : Compile::current(); if (phase != nullptr && phase->is_IterGVN()) { phase->is_IterGVN()->_worklist.remove(this); @@ -735,11 +736,14 @@ void Node::out_grow(uint len) { //------------------------------is_dead---------------------------------------- bool Node::is_dead() const { // Mach and pinch point nodes may look like dead. - if( is_top() || is_Mach() || (Opcode() == Op_Node && _outcnt > 0) ) + if (is_top() || is_Mach() || (Opcode() == Op_Node && _outcnt > 0) || this == Compile::current()->dead_path()) { return false; - for( uint i = 0; i < _max; i++ ) - if( _in[i] != nullptr ) + } + for (uint i = 0; i < _max; i++) { + if (_in[i] != nullptr) { return false; + } + } return true; } @@ -3178,10 +3182,11 @@ uint TypeNode::ideal_reg() const { return _type->ideal_reg(); } -void TypeNode::make_path_dead(PhaseIterGVN* igvn, PhaseIdealLoop* loop, Node* ctrl_use, uint j, const char* phase_str) { +void Node::make_path_dead(PhaseIterGVN* igvn, PhaseIdealLoop* loop, Node* ctrl_use, uint j, const char* phase_str) { Node* c = ctrl_use->in(j); - if (igvn->type(c) != Type::TOP) { - igvn->replace_input_of(ctrl_use, j, igvn->C->top()); + Node* top = igvn->C->top(); + if (c != top) { + igvn->replace_input_of(ctrl_use, j, top); create_halt_path(igvn, c, loop, phase_str); } } @@ -3193,14 +3198,18 @@ void TypeNode::make_path_dead(PhaseIterGVN* igvn, PhaseIdealLoop* loop, Node* ct // constant folds and the control flow that leads to the Type node becomes unreachable. There are cases where that // doesn't happen, however. They are handled here by following uses of the Type node until a CFG or a Phi to find dead // paths. The dead paths are then replaced by a Halt node. -void TypeNode::make_paths_from_here_dead(PhaseIterGVN* igvn, PhaseIdealLoop* loop, const char* phase_str) { +void Node::make_paths_from_here_dead(PhaseIterGVN* igvn, PhaseIdealLoop* loop, const char* phase_str) { Unique_Node_List wq; wq.push(this); for (uint i = 0; i < wq.size(); ++i) { Node* n = wq.at(i); + if (n->is_CFG()) { + n->remove_dead_region(igvn, true); + } for (DUIterator_Fast kmax, k = n->fast_outs(kmax); k < kmax; k++) { Node* u = n->fast_out(k); if (u->is_CFG()) { + wq.push(u); assert(!u->is_Region(), "Can't reach a Region without going through a Phi"); make_path_dead(igvn, loop, u, 0, phase_str); } else if (u->is_Phi()) { @@ -3220,7 +3229,7 @@ void TypeNode::make_paths_from_here_dead(PhaseIterGVN* igvn, PhaseIdealLoop* loo } } -void TypeNode::create_halt_path(PhaseIterGVN* igvn, Node* c, PhaseIdealLoop* loop, const char* phase_str) const { +void Node::create_halt_path(PhaseIterGVN* igvn, Node* c, PhaseIdealLoop* loop, const char* phase_str) { Node* frame = new ParmNode(igvn->C->start(), TypeFunc::FramePtr); if (loop == nullptr) { igvn->register_new_node_with_optimizer(frame); @@ -3239,15 +3248,3 @@ void TypeNode::create_halt_path(PhaseIterGVN* igvn, Node* c, PhaseIdealLoop* loo } igvn->add_input_to(igvn->C->root(), halt); } - -Node* TypeNode::Ideal(PhaseGVN* phase, bool can_reshape) { - if (KillPathsReachableByDeadTypeNode && can_reshape && Value(phase) == Type::TOP) { - PhaseIterGVN* igvn = phase->is_IterGVN(); - Node* top = igvn->C->top(); - ResourceMark rm; - make_paths_from_here_dead(igvn, nullptr, "igvn"); - return top; - } - - return Node::Ideal(phase, can_reshape); -} diff --git a/src/hotspot/share/opto/node.hpp b/src/hotspot/share/opto/node.hpp index b3de7498e50..e593822c313 100644 --- a/src/hotspot/share/opto/node.hpp +++ b/src/hotspot/share/opto/node.hpp @@ -82,6 +82,7 @@ class CountedLoopEndNode; class DecodeNarrowPtrNode; class DecodeNNode; class DecodeNKlassNode; +class DivModIntegerNode; class EncodeNarrowPtrNode; class EncodePNode; class EncodePKlassNode; @@ -829,8 +830,9 @@ public: DEFINE_CLASS_ID(LShift, Node, 21) DEFINE_CLASS_ID(Neg, Node, 22) DEFINE_CLASS_ID(ReachabilityFence, Node, 23) + DEFINE_CLASS_ID(DivModInteger, Node, 24) - _max_classes = ClassMask_Neg + _max_classes = ClassMask_DivModInteger }; #undef DEFINE_CLASS_ID @@ -947,6 +949,7 @@ public: DEFINE_CLASS_QUERY(DecodeNarrowPtr) DEFINE_CLASS_QUERY(DecodeN) DEFINE_CLASS_QUERY(DecodeNKlass) + DEFINE_CLASS_QUERY(DivModInteger) DEFINE_CLASS_QUERY(EncodeNarrowPtr) DEFINE_CLASS_QUERY(EncodeP) DEFINE_CLASS_QUERY(EncodePKlass) @@ -1501,6 +1504,10 @@ public: uint _del_tick; // Bumped when a deletion happens.. #endif #endif + void make_paths_from_here_dead(PhaseIterGVN* igvn, PhaseIdealLoop* loop, const char* phase_str); + + static void create_halt_path(PhaseIterGVN* igvn, Node* c, PhaseIdealLoop* loop, const char* phase_str); + void make_path_dead(PhaseIterGVN* igvn, PhaseIdealLoop* loop, Node* ctrl_use, uint j, const char* phase_str); }; inline bool not_a_node(const Node* n) { @@ -2198,17 +2205,13 @@ public: init_class_id(Class_Type); } virtual const Type* Value(PhaseGVN* phase) const; - virtual Node* Ideal(PhaseGVN* phase, bool can_reshape); virtual const Type *bottom_type() const; virtual uint ideal_reg() const; - void make_path_dead(PhaseIterGVN* igvn, PhaseIdealLoop* loop, Node* ctrl_use, uint j, const char* phase_str); #ifndef PRODUCT virtual void dump_spec(outputStream *st) const; virtual void dump_compact_spec(outputStream *st) const; #endif - void make_paths_from_here_dead(PhaseIterGVN* igvn, PhaseIdealLoop* loop, const char* phase_str); - void create_halt_path(PhaseIterGVN* igvn, Node* c, PhaseIdealLoop* loop, const char* phase_str) const; }; #include "opto/opcodes.hpp" diff --git a/src/hotspot/share/opto/parse2.cpp b/src/hotspot/share/opto/parse2.cpp index 9cb20cfcd00..6e58fae51e1 100644 --- a/src/hotspot/share/opto/parse2.cpp +++ b/src/hotspot/share/opto/parse2.cpp @@ -1843,7 +1843,7 @@ void Parse::sharpen_type_after_if(BoolTest::mask btest, const Type* obj_type = _gvn.type(obj); const Type* tboth = obj_type->filter_speculative(cast_type); assert(tboth->higher_equal(obj_type) && tboth->higher_equal(cast_type), "sanity"); - if (tboth == Type::TOP && KillPathsReachableByDeadTypeNode) { + if (tboth == Type::TOP && KillPathsReachableByDeadDataNode) { // Let dead type node cleaning logic prune effectively dead path for us. // CheckCastPP::Value() == TOP and it will trigger the cleanup during GVN. // Don't materialize the cast when cleanup is disabled, because diff --git a/src/hotspot/share/opto/phaseX.cpp b/src/hotspot/share/opto/phaseX.cpp index a4d6a6c33d0..c124f940a27 100644 --- a/src/hotspot/share/opto/phaseX.cpp +++ b/src/hotspot/share/opto/phaseX.cpp @@ -32,6 +32,7 @@ #include "opto/castnode.hpp" #include "opto/cfgnode.hpp" #include "opto/convertnode.hpp" +#include "opto/divnode.hpp" #include "opto/idealGraphPrinter.hpp" #include "opto/loopnode.hpp" #include "opto/machnode.hpp" @@ -2197,6 +2198,107 @@ Node *PhaseIterGVN::transform( Node *n ) { return transform_old(n); } +DeadPathNode* PhaseIterGVN::dead_path() { + DeadPathNode* dead_path_node = C->dead_path(); + if (!dead_path_node->is_active()) { + dead_path_node->activate(this); + } + assert(C->root()->find_edge(dead_path_node) > 0, "should be reachable from root"); + return dead_path_node; +} + + +// If dead_node is a data node, all CFG nodes reachable from dead_node are dead cfg paths. This method follows uses from +// dead_node until it encounters a cfg node or a phi and eagerly kills these dead cfg paths. This is needed because, in +// some corner cases, a data node dies but some data paths that use it (and are unreachable at runtime) are not proven +// dead by igvn, possibly leading to incorrect IR graphs. +// Also see comment at DeadPathNode declaration. +void PhaseIterGVN::make_dependent_paths_dead_if_top(Node* dead_node, const Type* t) { + if (t != Type::TOP) { + return; + } + if (!KillPathsReachableByDeadDataNode) { + return; + } + // dead_node is going dead, follow uses + ResourceMark rm; + Unique_Node_List wq; + wq.push(dead_node); + for (uint i = 0; i < wq.size(); i++) { + Node* n = wq.at(i); + if (n != dead_node && (n->is_Phi() || n->is_CFG())) { + continue; + } + for (DUIterator_Fast kmax, k = n->fast_outs(kmax); k < kmax; k++) { + Node* u = n->fast_out(k); + wq.push(u); + } + } + for (uint i = 0; i < wq.size(); i++) { + Node* n = wq.at(i); + if (n->is_Phi()) { + Node* region = n->in(0); + // Find out through which of the Phi's input, we reached that Phi and mark the corresponding CFG path dead + for (uint j = 1; j < n->req(); j++) { + Node* in = n->in(j); + // We don't follow uses beyond Phis so if 'in' is a Phi (unless it's dead_node), we couldn't reach this Phi through it + if (in == dead_node || (in != nullptr && !in->is_Phi() && wq.member(in))) { + if (!region->is_top() && region->in(j) != nullptr && !region->in(j)->is_top()) { + // We reached this CFG path through data nodes, record it in dead path to later insert a Halt node, if it + // doesn't die in the meantime + dead_path()->add_req(region->in(j)); + _worklist.push(dead_path()); + replace_input_of(region, j, C->top()); + } + replace_input_of(n, j, C->top()); + if (in->outcnt() == 0) { + remove_dead_node(in, NodeOrigin::Graph); + } + } + } + continue; + } + if (n == dead_node) { + continue; + } + // We don't want to follow CFG nodes but is_CFG() can return false for a cfg projection if its input is top. So + // there's no foolproof way of telling if dead_node is a cfg or not and as a consequence we can reach a Region. + if (n->is_Region()) { + // Find out through which of the Region's input, we reached that Region and mark it dead + for (uint j = 1; j < n->req(); j++) { + Node* in = n->in(j); + // We don't follow uses beyond Regions so if 'in' is a Region, we couldn't reach this Region through it + if (in != nullptr && !in->is_Region() && wq.member(in)) { + replace_input_of(n, j, C->top()); + in->remove_dead_region(this, true); + } + } + continue; + } + // If we reached this CFG node through a data input... + if (n->is_CFG()) { + Node* control_input = n->in(0); + if (control_input != nullptr && !control_input->is_top()) { + // record it in dead path to later insert a Halt node, if it doesn't die in the meantime + dead_path()->add_req(control_input); + _worklist.push(dead_path()); + replace_input_of(n, 0, C->top()); + } + n->remove_dead_region(this, true); + continue; + } + if (n->outcnt() == 0) { + remove_dead_node(n, NodeOrigin::Graph); + } + } +#ifdef ASSERT + for (uint i = 0; i < wq.size(); i++) { + Node* n = wq.at(i); + assert(n->is_Region() || n->is_Phi() || n->is_CFG() || n->outcnt() == 0, "node should be dead now"); + } +#endif +} + Node *PhaseIterGVN::transform_old(Node* n) { NOT_PRODUCT(set_transforms()); // Remove 'n' from hash table in case it gets modified @@ -2288,6 +2390,7 @@ Node *PhaseIterGVN::transform_old(Node* n) { } // If 'k' computes a constant, replace it with a constant if (t->singleton() && !k->is_Con()) { + make_dependent_paths_dead_if_top(k, t); set_progress(); Node* con = makecon(t); // Make a constant add_users_to_worklist(k); @@ -2957,10 +3060,14 @@ void PhaseCCP::analyze_step(Unique_Node_List& worklist, Node* n) { set_type(n, new_type); push_child_nodes_to_worklist(worklist, n); } - if (KillPathsReachableByDeadTypeNode && n->is_Type() && new_type == Type::TOP) { + if (KillPathsReachableByDeadDataNode && n->is_Type() && new_type == Type::TOP) { // Keep track of Type nodes to kill CFG paths that use Type // nodes that become dead. - _maybe_top_type_nodes.push(n); + _maybe_top_type_or_div_mod_nodes.push(n); + } + if (KillPathsReachableByDeadDataNode && new_type == Type::TOP && n->is_DivModInteger() && + type(n->in(2)) == n->as_DivModInteger()->zero()) { + _maybe_top_type_or_div_mod_nodes.push(n); } } @@ -3256,16 +3363,16 @@ Node *PhaseCCP::transform( Node *n ) { // track all visited nodes, so that we can remove the complement Unique_Node_List useful; - if (KillPathsReachableByDeadTypeNode) { - for (uint i = 0; i < _maybe_top_type_nodes.size(); ++i) { - Node* type_node = _maybe_top_type_nodes.at(i); - if (type(type_node) == Type::TOP) { + if (KillPathsReachableByDeadDataNode) { + for (uint i = 0; i < _maybe_top_type_or_div_mod_nodes.size(); ++i) { + Node* data_node = _maybe_top_type_or_div_mod_nodes.at(i); + if (type(data_node) == Type::TOP) { ResourceMark rm; - type_node->as_Type()->make_paths_from_here_dead(this, nullptr, "ccp"); + data_node->make_paths_from_here_dead(this, nullptr, "ccp"); } } } else { - assert(_maybe_top_type_nodes.size() == 0, "we don't need type nodes"); + assert(_maybe_top_type_or_div_mod_nodes.size() == 0, "we don't need type nodes"); } // Initialize the traversal. diff --git a/src/hotspot/share/opto/phaseX.hpp b/src/hotspot/share/opto/phaseX.hpp index 014d16f92f6..7ea7aa99142 100644 --- a/src/hotspot/share/opto/phaseX.hpp +++ b/src/hotspot/share/opto/phaseX.hpp @@ -501,6 +501,10 @@ protected: // Usually returns new_type. Returns old_type if new_type is only a slight // improvement, such that it would take many (>>10) steps to reach 2**32. + DeadPathNode* dead_path(); + + void make_dependent_paths_dead_if_top(Node* dead_node, const Type* t); + public: PhaseIterGVN(PhaseIterGVN* igvn); // Used by CCP constructor @@ -695,7 +699,7 @@ protected: // Should be replaced with combined CCP & GVN someday. class PhaseCCP : public PhaseIterGVN { Unique_Node_List _root_and_safepoints; - Unique_Node_List _maybe_top_type_nodes; + Unique_Node_List _maybe_top_type_or_div_mod_nodes; // Non-recursive. Use analysis to transform single Node. virtual Node* transform_once(Node* n); diff --git a/src/hotspot/share/opto/rootnode.cpp b/src/hotspot/share/opto/rootnode.cpp index 60167c5436a..1e5ef29e79c 100644 --- a/src/hotspot/share/opto/rootnode.cpp +++ b/src/hotspot/share/opto/rootnode.cpp @@ -90,3 +90,48 @@ const Type* HaltNode::Value(PhaseGVN* phase) const { const RegMask &HaltNode::out_RegMask() const { return RegMask::EMPTY; } + +Node* DeadPathNode::Ideal(PhaseGVN* phase, bool can_reshape) { + assert(unique_ctrl_out() == phase->C->root(), "only referenced from root"); + assert(can_reshape, "only used once igvn executes"); + bool modified = false; + for (uint i = 1; i < req(); i++) { // For all inputs + // Check for and remove dead inputs + if (phase->type(in(i)) == Type::TOP) { + del_req(i--); // Delete TOP inputs + modified = true; + } + } + if (req() == 1 && is_active()) { + assert(modified, "only if some inputs were removed"); + deactivate(); + } + return modified ? this : nullptr; +} + +const Type* DeadPathNode::Value(PhaseGVN* phase) const { + if (req() == 1) { + return Type::TOP; + } + return bottom_type(); +} + +void DeadPathNode::activate(PhaseIterGVN* igvn) { + assert(Compile::current()->root()->find_edge(this) < 0, "should be disconnected from root"); + set_req(0, this); + // If an entire subgraph died such as with Node::remove_dead_region(), some dead inputs to the DeadPath node will have + // been left behind + while (req() > 1) { + uint last = req() - 1; + assert(in(last) == nullptr || in(last)->is_top(), "only dead inputs should remain"); + del_req(last); + } + Node* root_node = Compile::current()->root(); + root_node->add_req(this); + igvn->_worklist.push(root_node); + igvn->set_type(this, bottom_type()); +} + +void DeadPathNode::deactivate() { + set_req(0, nullptr); +} diff --git a/src/hotspot/share/opto/rootnode.hpp b/src/hotspot/share/opto/rootnode.hpp index 76f0ec440a9..61ad317d455 100644 --- a/src/hotspot/share/opto/rootnode.hpp +++ b/src/hotspot/share/opto/rootnode.hpp @@ -69,4 +69,35 @@ public: virtual uint match_edge(uint idx) const { return 0; } }; + +// This node collects paths that are found dead by PhaseIterGVN::make_dependent_paths_dead_if_top() + +// There is a single DeadPath node for the lifetime of optimizations. It's initially not active (i.e. unreachable from +// the IR graph). When a cfg path becomes dead it's added as an input to the unique DeadPath node. If after some +// optimizations run, the DeadPath node gets disconnected, it's not destroyed. It becomes inactive and can possibly be +// activated again on a subsequent igvn. When optimizations are over, the DeadPath node, if it is active, is expanded to +// a Region and Halt node in Compile::final_graph_reshaping(). + +// Rather than having this dedicated node, igvn could add a Halt node everytime it finds a dead cfg path from a data +// node. What's likely, however, is that as igvn progresses, that same cfg path is found dead by following cfg edges. +// The Halt node then becomes dead. To avoid this unnecessary cycle of creation of a Halt node only to have it be found +// dead shortly after, dead cfg paths are added to the unique DeadPath node. +class DeadPathNode : public RegionNode { +public: + DeadPathNode() : RegionNode(1) { + deactivate(); + assert(Compile::current()->dead_path() == nullptr, "only one"); + } + virtual int Opcode() const; + virtual const Type* bottom_type() const { return Type::BOTTOM; } + virtual Node* Identity(PhaseGVN* phase) { return this; } + virtual Node* Ideal(PhaseGVN* phase, bool can_reshape); + virtual const Type* Value(PhaseGVN* phase) const; + bool is_active() const { + return in(0) == this; + } + void activate(PhaseIterGVN* igvn); + void deactivate(); +}; + #endif // SHARE_OPTO_ROOTNODE_HPP diff --git a/src/hotspot/share/opto/vectornode.cpp b/src/hotspot/share/opto/vectornode.cpp index 20857eed35c..60eda1204b7 100644 --- a/src/hotspot/share/opto/vectornode.cpp +++ b/src/hotspot/share/opto/vectornode.cpp @@ -2391,7 +2391,7 @@ Node* VectorMaskOpNode::Ideal(PhaseGVN* phase, bool can_reshape) { if (n != nullptr) { return n; } - return TypeNode::Ideal(phase, can_reshape); + return nullptr; } Node* VectorMaskCastNode::Identity(PhaseGVN* phase) { diff --git a/test/hotspot/jtreg/compiler/c2/TestDeadPathManyDeadDataNodes.java b/test/hotspot/jtreg/compiler/c2/TestDeadPathManyDeadDataNodes.java new file mode 100644 index 00000000000..e9c5a8f7529 --- /dev/null +++ b/test/hotspot/jtreg/compiler/c2/TestDeadPathManyDeadDataNodes.java @@ -0,0 +1,1301 @@ +/* + * Copyright (c) 2026 IBM Corporation. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * 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 8380166 + * @summary C2: crash in compiled code due to zero division because of widened CastII + * @run main/othervm -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions + * -Xcomp -XX:CompileOnly=TestDeadPathManyDeadDataNodes::test1 + * -XX:CompileCommand=quiet + * -XX:CompileCommand=inline,TestDeadPathManyDeadDataNodes::inlined1 + * -XX:MaxRecursiveInlineLevel=1000 -XX:MaxInlineLevel=1000 + * -XX:-TieredCompilation -XX:+AlwaysIncrementalInline + * -XX:+DelayAfterInliningCutoff -XX:+IncrementalInlineForceCleanup + * -XX:NodeCountInliningCutoff=100000 -XX:+StressIGVN + * ${test.main.class} + * @run main ${test.main.class} + */ + +package compiler.c2; + +public class TestDeadPathManyDeadDataNodes { + private static int field; + private static boolean boolField2; + private static int arrayLengthField; + + public static void main(String[] args) { + Object o = new Object(); + try { + test1(false, 0); + } catch (NegativeArraySizeException nase) { + } + } + + private static int test1(boolean boolParam, int intParam) { + int length; + int res = 0; + length = -1; + for (int i = 0; i < 2; i++) { + if (boolParam) { + field = 42; + } + int[] array = new int[length]; + arrayLengthField = array.length; + while(true) { + Object o = new Object(); + int arrayLength = arrayLengthField; + arrayLengthField = 0; + switch (intParam) { + case 0: + if (boolField2) { + break; + } + field = 42; + continue; + case 1: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 2: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 3: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 4: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 5: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 6: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 7: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 8: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 9: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 10: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 11: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 12: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 13: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 14: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 15: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 16: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 17: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 18: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 19: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 20: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 21: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 22: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 23: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 24: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 25: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 26: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 27: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 28: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 29: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 30: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 31: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 32: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 33: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 34: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 35: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 36: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 37: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 38: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 39: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 40: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 41: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 42: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 43: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 44: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 45: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 46: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 47: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 48: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 49: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 50: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 51: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 52: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 53: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 54: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 55: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 56: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 57: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 58: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 59: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 60: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 61: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 62: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 63: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 64: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 65: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 66: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 67: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 68: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 69: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 70: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 71: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 72: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 73: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 74: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 75: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 76: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 77: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 78: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 79: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 80: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 81: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 82: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 83: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 84: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 85: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 86: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 87: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 88: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 89: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 90: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 91: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 92: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 93: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 94: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 95: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 96: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 97: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + case 98: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + continue; + case 99: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + continue; + default: + res += inlined1(boolParam, intParam/100, arrayLength, 92); + continue; + } + field = 42; + break; + } + length = lastInlined(); + } + return res; + } + + static int lastInlined() { + return -1; + } + + static int inlined1(boolean boolParam, int intParam, int arrayLength, int count) { + int res = 0; + switch (intParam) { + case 0: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 1: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 2: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 3: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 4: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 5: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 6: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 7: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 8: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 9: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 10: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 11: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 12: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 13: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 14: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 15: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 16: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 17: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 18: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 19: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 20: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 21: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 22: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 23: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 24: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 25: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 26: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 27: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 28: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 29: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 30: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 31: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 32: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 33: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 34: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 35: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 36: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 37: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 38: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 39: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 40: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 41: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 42: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 43: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 44: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 45: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 46: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 47: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 48: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 49: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 50: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 51: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 52: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 53: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 54: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 55: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 56: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 57: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 58: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 59: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 60: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 61: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 62: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 63: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 64: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 65: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 66: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 67: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 68: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 69: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 70: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 71: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 72: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 73: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 74: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 75: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 76: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 77: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 78: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 79: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 80: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 81: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 82: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 83: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 84: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 85: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 86: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 87: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 88: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 89: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 90: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 91: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 92: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 93: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 94: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 95: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 96: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 97: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + case 98: + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + case 99: + if (boolParam) { + res += arrayLength * 2; + } + field = 42; + return res; + default: + if (count == 0) { + if (boolParam) { + res += arrayLength * 1; + } + field = 42; + return res; + } else { + return inlined1(boolParam, intParam / 100, arrayLength, count-1); + } + } + } +} diff --git a/test/hotspot/jtreg/compiler/integerArithmetic/TestDivByZeroInLiveCFGPath.java b/test/hotspot/jtreg/compiler/integerArithmetic/TestDivByZeroInLiveCFGPath.java new file mode 100644 index 00000000000..6eaf3d86e71 --- /dev/null +++ b/test/hotspot/jtreg/compiler/integerArithmetic/TestDivByZeroInLiveCFGPath.java @@ -0,0 +1,64 @@ + +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8383815 + * @summary C2: assert(false) failed: malformed IfNode with 1 outputs + * @run main/othervm -XX:CompileCommand=compileonly,${test.main.class}*::* -XX:-TieredCompilation -Xbatch -XX:PerMethodTrapLimit=0 ${test.main.class} + * @run main ${test.main.class} + */ + +package compiler.integerArithmetic; + +public class TestDivByZeroInLiveCFGPath { + static long lFld; + static int iArr[] = new int[400]; + + public static void main(String[] strArr) { + for (int i = 0; i < 10; i++) { + test(); + } + } + + static void test() { + int x; + for (int i = 9; i < 100; ++i) { + int j = 100; + while (--j > 0) { + iArr[1] = (int) lFld; + } + try { + iArr[1] = (5 / j); + x = (i / iArr[8]); + } catch (ArithmeticException a_e) { + } + } + + for (int i = 18; i < 50; i++) { + iArr[2] += lFld; + } + } +} + diff --git a/test/hotspot/jtreg/compiler/integerArithmetic/TestZeroDivModWidenedCastII.java b/test/hotspot/jtreg/compiler/integerArithmetic/TestZeroDivModWidenedCastII.java new file mode 100644 index 00000000000..a5bc8fc9287 --- /dev/null +++ b/test/hotspot/jtreg/compiler/integerArithmetic/TestZeroDivModWidenedCastII.java @@ -0,0 +1,1122 @@ +/* + * Copyright (c) 2026 IBM Corporation. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * 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 8380166 + * @summary C2: crash in compiled code due to zero division because of widened CastII + * + * @run main/othervm -XX:-TieredCompilation -XX:-UseOnStackReplacement -XX:-BackgroundCompilation + * ${test.main.class} + * @run main ${test.main.class} + * + */ + +package compiler.integerArithmetic; + +public class TestZeroDivModWidenedCastII { + private static int intField; + private static long longField; + private static volatile int volatileField; + + public static void main(String[] args) { + for (int i = 0; i < 20_000; i++) { + test1(0, 9, 1, true, false); + test1(0, 9, 1, false, false); + inlined1_2(9, 1, 1, true, 0); + inlined1_3(0, 0); + test2(0, 9, 1, true, false); + test2(0, 9, 1, false, false); + inlined2_2(9, 1, 1, true, 0); + inlined2_3(0, 0); + test3(0, 9, 1, true, false); + test3(0, 9, 1, false, false); + inlined3_2(9, 1, 1, true, 0); + inlined3_3(0, 0); + test4(0, 9, 1, true, false); + test4(0, 9, 1, false, false); + inlined4_2(9, 1, 1, true, 0); + inlined4_3(0, 0); + test5(0, 9, 1, true, false); + test5(0, 9, 1, false, false); + inlined5_2(9, 1, 1, true, 0); + inlined5_3(0, 0); + test6(0, 9, 1, true, false); + test6(0, 9, 1, false, false); + inlined6_2(9, 1, 1, true, 0); + inlined6_3(0, 0); + test7(0, 9, 1, true, false); + test7(0, 9, 1, false, false); + inlined7_2(9, 1, 1, true, 0); + inlined7_3(0, 0); + test8(0, 9, 1, true, false); + test8(0, 9, 1, false, false); + inlined8_2(9, 1, 1, true, 0); + inlined8_3(0, 0); + test9(0, 9, 1, true, false); + test9(0, 9, 1, false, false); + inlined9_2(9, 1, 1, true, 0); + inlined9_3(0, 0); + test10(0, 9, 1, false); + inlined10_2(9, 1, 1, true, 0); + inlined10_3(0, 0); + test11(0, 9, 1, false); + inlined11_2(9, 1, 1, true, 0); + inlined11_3(0, 0); + test12(0, 9, 1, false); + inlined12_2(9, 1, 1, true, 0); + inlined12_3(0, 0); + test13(0, 9, 1, false); + inlined13_2(9, 1, 1, true, 0); + inlined13_3(0, 0); + test14(0, 9, 1, false); + inlined14_2(9, 1, 1, true, 0); + inlined14_3(0, 0); + test15(0, 9, 1, false); + inlined15_2(9, 1, 1, true, 0); + inlined15_3(0, 0); + test16(0, 9, 1, false); + inlined16_2(9, 1, 1, true, 0); + inlined16_3(0, 0); + test17(0, 9, 1, false); + inlined17_2(9, 1, 1, true, 0); + inlined17_3(0, 0); + } + } + + private static void test1(int k, int j, int flag, boolean flag2, boolean flag3) { + int l = 0; + for (; l < 10; l++); + int m = inlined1_3(j, l); + + int i = inlined1(k, flag2); + j = Integer.min(j, 9); + int[] array = new int[10]; + if (flag == 0) { + throw new RuntimeException("never taken"); + } + if (flag2) { + inlined1_2(j, flag, i, flag3, m); + } else { + inlined1_2(j, flag, i, flag3, m); + } + } + + private static int inlined1_3(int j, int l) { + if (l == 10) { + j = 1; + } + return j; + } + + private static void inlined1_2(int j, int flag, int i, boolean flag3, int m) { + if (flag3) { + float[] newArray = new float[j + 1]; // j + 1 in [0..10] + // RC i Date: Wed, 15 Jul 2026 09:57:38 +0000 Subject: [PATCH 156/305] 8387149: C2: assert(regs[i] != regs[j]) failed: regs[2] and regs[3] are both: v24 Reviewed-by: aph, fgao --- .../cpu/aarch64/c2_MacroAssembler_aarch64.cpp | 9 +- .../TestSelectFromTwoVectorSameOperand.java | 142 ++++++++++++++++++ 2 files changed, 148 insertions(+), 3 deletions(-) create mode 100644 test/hotspot/jtreg/compiler/vectorapi/TestSelectFromTwoVectorSameOperand.java diff --git a/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp index eacfef9618a..fe9180bda5c 100644 --- a/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp @@ -2728,7 +2728,8 @@ void C2_MacroAssembler::reconstruct_frame_pointer(Register rtmp) { void C2_MacroAssembler::select_from_two_vectors_neon(FloatRegister dst, FloatRegister src1, FloatRegister src2, FloatRegister index, FloatRegister tmp, unsigned vector_length_in_bytes) { - assert_different_registers(dst, src1, src2, tmp); + assert_different_registers(src2, tmp); + assert_different_registers(index, tmp); SIMD_Arrangement size = vector_length_in_bytes == 16 ? T16B : T8B; if (vector_length_in_bytes == 16) { @@ -2757,7 +2758,8 @@ void C2_MacroAssembler::select_from_two_vectors_sve(FloatRegister dst, FloatRegi FloatRegister src2, FloatRegister index, FloatRegister tmp, SIMD_RegVariant T, unsigned vector_length_in_bytes) { - assert_different_registers(dst, src1, src2, index, tmp); + assert_different_registers(src2, tmp); + assert_different_registers(index, tmp); if (vector_length_in_bytes == 8) { // We need to fit both the source vectors (src1, src2) in a single vector register because the @@ -2784,7 +2786,8 @@ void C2_MacroAssembler::select_from_two_vectors(FloatRegister dst, FloatRegister FloatRegister tmp, BasicType bt, unsigned vector_length_in_bytes) { - assert_different_registers(dst, src1, src2, index, tmp); + assert_different_registers(dst, src1, src2, tmp); + assert_different_registers(index, tmp); // The cases that can reach this method are - // - UseSVE = 0/1, vector_length_in_bytes = 8 or 16, excluding double and long types diff --git a/test/hotspot/jtreg/compiler/vectorapi/TestSelectFromTwoVectorSameOperand.java b/test/hotspot/jtreg/compiler/vectorapi/TestSelectFromTwoVectorSameOperand.java new file mode 100644 index 00000000000..7c30e439515 --- /dev/null +++ b/test/hotspot/jtreg/compiler/vectorapi/TestSelectFromTwoVectorSameOperand.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test id=SVE + * @bug 8387149 + * @summary Test case for SelectFromTwoVector with index operand same as other inputs. + * @requires vm.compiler2.enabled + * @requires os.arch == "aarch64" & vm.cpu.features ~= ".*sve.*" + * @modules jdk.incubator.vector + * @library /test/lib / + * @run main/othervm + * -XX:+UnlockDiagnosticVMOptions + * -XX:UseSVE=1 + * -XX:-TieredCompilation -Xbatch + * -XX:CompileCommand=dontinline,${test.main.class}::test* + * -XX:CompileCommand=compileonly,${test.main.class}::test* + * ${test.main.class} + */ + +/* + * @test id=NEON + * @bug 8387149 + * @summary Test case for SelectFromTwoVector with index operand same as other inputs. + * @requires vm.compiler2.enabled + * @requires os.arch == "aarch64" & vm.cpu.features ~= ".*asimd.*" + * @modules jdk.incubator.vector + * @library /test/lib / + * @run main/othervm + * -XX:+UnlockDiagnosticVMOptions + * -XX:UseSVE=0 + * -XX:-TieredCompilation -Xbatch + * -XX:CompileCommand=dontinline,${test.main.class}::test* + * -XX:CompileCommand=compileonly,${test.main.class}::test* + * ${test.main.class} + */ + +package compiler.vectorapi; + +import java.util.Random; +import jdk.incubator.vector.*; +import jdk.test.lib.Asserts; + +public class TestSelectFromTwoVectorSameOperand { + static final int SIZE = 8; + + static byte[] byte_input1 = new byte[SIZE]; + static byte[] byte_input2 = new byte[SIZE]; + static byte[] byte_output = new byte[SIZE]; + static final byte byte_index_mask = 15; + + static short[] short_input1 = new short[SIZE / 2]; + static short[] short_input2 = new short[SIZE / 2]; + static short[] short_output = new short[SIZE / 2]; + static final short short_index_mask = 7; + + static { + Random r = new Random(42); + r.nextBytes(byte_input1); + r.nextBytes(byte_input2); + + for (int i = 0; i < SIZE / 2; i++) { + short_input1[i] = byte_input1[i]; + short_input2[i] = byte_input2[i]; + } + } + + public static void main(String[] args) { + for (int i = 0; i < 100_000; ++i) { + test_byte_src1(); + verify_byte(byte_input1, byte_input2, byte_input1, byte_output); + test_byte_src2(); + verify_byte(byte_input1, byte_input2, byte_input2, byte_output); + test_short_src1(); + verify_short(short_input1, short_input2, short_input1, short_output); + test_short_src2(); + verify_short(short_input1, short_input2, short_input2, short_output); + } + } + + static void test_byte_src1() { + ByteVector src1 = ByteVector.fromArray(ByteVector.SPECIES_64, byte_input1, 0).and(byte_index_mask); + ByteVector src2 = ByteVector.fromArray(ByteVector.SPECIES_64, byte_input2, 0).and(byte_index_mask); + src1.selectFrom(src1, src2).intoArray(byte_output, 0); + } + + static void test_byte_src2() { + ByteVector src1 = ByteVector.fromArray(ByteVector.SPECIES_64, byte_input1, 0).and(byte_index_mask); + ByteVector src2 = ByteVector.fromArray(ByteVector.SPECIES_64, byte_input2, 0).and(byte_index_mask); + src2.selectFrom(src1, src2).intoArray(byte_output, 0); + } + + static void test_short_src1() { + ShortVector src1 = ShortVector.fromArray(ShortVector.SPECIES_64, short_input1, 0).and(short_index_mask); + ShortVector src2 = ShortVector.fromArray(ShortVector.SPECIES_64, short_input2, 0).and(short_index_mask); + src1.selectFrom(src1, src2).intoArray(short_output, 0); + } + + static void test_short_src2() { + ShortVector src1 = ShortVector.fromArray(ShortVector.SPECIES_64, short_input1, 0).and(short_index_mask); + ShortVector src2 = ShortVector.fromArray(ShortVector.SPECIES_64, short_input2, 0).and(short_index_mask); + src2.selectFrom(src1, src2).intoArray(short_output, 0); + } + + static void verify_byte(byte[] src1, byte[] src2, byte[] index, byte[] output) { + for (int i = 0; i < SIZE; i++) { + int index_value = index[i] & byte_index_mask; + byte element_value = (index_value < SIZE) ? src1[index_value] : src2[index_value - SIZE]; + byte masked_element_value = (byte) (element_value & byte_index_mask); + Asserts.assertEQ(masked_element_value, output[i]); + } + } + + static void verify_short(short[] src1, short[] src2, short[] index, short[] output) { + for (int i = 0; i < SIZE / 2; i++) { + int index_value = index[i] & short_index_mask; + short element_value = (index_value < SIZE / 2) ? src1[index_value] : src2[index_value - SIZE / 2]; + short masked_element_value = (short) (element_value & short_index_mask); + Asserts.assertEQ(masked_element_value, output[i]); + } + } +} From 4086d114ed3fe82edb9005521cc6ede340ea0299 Mon Sep 17 00:00:00 2001 From: Albert Mingkun Yang Date: Wed, 15 Jul 2026 10:10:31 +0000 Subject: [PATCH 157/305] 8388188: NMT: Remove unused local variable in RegionIterator::next_committed Reviewed-by: phubner, cnorrbin --- src/hotspot/share/nmt/virtualMemoryTracker.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/hotspot/share/nmt/virtualMemoryTracker.cpp b/src/hotspot/share/nmt/virtualMemoryTracker.cpp index e23076a12bf..08ea3699bc2 100644 --- a/src/hotspot/share/nmt/virtualMemoryTracker.cpp +++ b/src/hotspot/share/nmt/virtualMemoryTracker.cpp @@ -281,7 +281,6 @@ private: bool RegionIterator::next_committed(address& committed_start, size_t& committed_size) { if (end() <= _current_start) return false; - const size_t page_sz = os::vm_page_size(); const size_t current_size = end() - _current_start; if (os::first_resident_in_range(_current_start, current_size, committed_start, committed_size)) { assert(committed_start != nullptr, "Must be"); From 723826295aa50a88cbb702128da79a91e6d87c73 Mon Sep 17 00:00:00 2001 From: Erik Gahlin Date: Wed, 15 Jul 2026 11:04:04 +0000 Subject: [PATCH 158/305] 8385957: JFR: Sensitive command-line arguments still in environment variable values Reviewed-by: mgronlun, rtoyonaga --- src/hotspot/share/jfr/dcmd/jfrDcmds.cpp | 5 +- .../share/jfr/periodic/jfrRedactedEvents.cpp | 176 ++++++++++++++---- .../share/jfr/periodic/jfrRedactedEvents.hpp | 4 + src/java.base/share/man/java.md | 8 +- test/jdk/jdk/jfr/startupargs/TestRedact.java | 90 +++++++-- 5 files changed, 228 insertions(+), 55 deletions(-) diff --git a/src/hotspot/share/jfr/dcmd/jfrDcmds.cpp b/src/hotspot/share/jfr/dcmd/jfrDcmds.cpp index a41515edfbb..58d6c029bc1 100644 --- a/src/hotspot/share/jfr/dcmd/jfrDcmds.cpp +++ b/src/hotspot/share/jfr/dcmd/jfrDcmds.cpp @@ -486,8 +486,9 @@ void JfrConfigureFlightRecorderDCmd::print_help(outputStream* out, bool startup) out->print_cr(" The option redact-argument is best-effort and applies only to"); out->print_cr(" command-line arguments in the jdk.JVMInformation event and to"); out->print_cr(" the java.command system property in the jdk.InitialSystemProperty"); - out->print_cr(" event. Other events, such as jdk.ProcessStart (child processes),"); - out->print_cr(" are not redacted."); + out->print_cr(" event, and to matching command-line argument text in the values"); + out->print_cr(" of jdk.InitialEnvironmentVariable events. Other events, such as"); + out->print_cr(" jdk.ProcessStart (child processes), are not redacted."); out->print_cr(""); out->print_cr(" If the redact-argument option is not specified, the following"); out->print_cr(" filters are used by default:"); diff --git a/src/hotspot/share/jfr/periodic/jfrRedactedEvents.cpp b/src/hotspot/share/jfr/periodic/jfrRedactedEvents.cpp index 331c28cffa2..652320c9904 100644 --- a/src/hotspot/share/jfr/periodic/jfrRedactedEvents.cpp +++ b/src/hotspot/share/jfr/periodic/jfrRedactedEvents.cpp @@ -29,6 +29,7 @@ #include "logging/logMessage.hpp" #include "runtime/arguments.hpp" #include "runtime/flags/jvmFlag.hpp" +#include "runtime/javaThread.hpp" #include "runtime/os.hpp" #include "runtime/vm_version.hpp" #include "services/diagnosticArgument.hpp" @@ -46,17 +47,20 @@ using StringFlag = JfrRedactedEvents::StringFlag; using StringKeyValueArray = GrowableArray*; static const char REDACTED[] = "[REDACTED]"; +static const char REDACTED_MARKER = (char)0xFF; static const char DELIMITER[] = " "; +static const char REDACT_ARGUMENT[] = "redact-argument"; static const char REDACT_ARGUMENT_EQUAL[] = "redact-argument="; static const size_t REDACTED_LENGTH = sizeof(REDACTED) -1; static const size_t DELIMITER_LENGTH = sizeof(DELIMITER) -1; -static const size_t REDACT_ARGUMENT_EQUAL_LENGTH = sizeof(REDACT_ARGUMENT_EQUAL) -1; +static const size_t REDACT_ARGUMENT_LENGTH = sizeof(REDACT_ARGUMENT) -1; String* JfrRedactedEvents::_redacted_java_command_line = nullptr; String* JfrRedactedEvents::_redacted_jvm_command_line = nullptr; String* JfrRedactedEvents::_redacted_flags_command_line = nullptr; String* JfrRedactedEvents::_redacted_flight_recorder_options = nullptr; +String* JfrRedactedEvents::_redacted_flight_recorder_options_with_marker = nullptr; StringKeyValueArray JfrRedactedEvents::_initial_environment_variables = nullptr; StringKeyValueArray JfrRedactedEvents::_initial_system_properties = nullptr; @@ -71,6 +75,10 @@ bool JfrRedactedEvents::_initialized = false; bool JfrRedactedEvents::set_argument_filter(const char* filters) { assert (_argument_filters == nullptr, "invariant"); assert (filters != nullptr, "invariant"); + if (strcmp(filters, "*") != 0 && strcmp(filters, "none") != 0) { + _redacted_arguments = new StringArray(); + _redacted_arguments->add(filters); + } _argument_filters = new StringArray(); return append_filters(_argument_filters, true, filters); } @@ -139,9 +147,8 @@ bool JfrRedactedEvents::append_filters(StringArray* target, bool argument, const } if (filters[0] == '\0') { LogMessage(jfr, redact) msg; - msg.warning("Default redaction filters are replaced. Specify:"); - msg.warning("-XX:FlightRecorderOptions:%s=none to disable filters without a warning.", option_name); - return true; + msg.error("Specify -XX:FlightRecorderOptions:%s=none to disable filters completely.", option_name); + return false; } if (strcmp(filters, "none") == 0) { return true; @@ -187,6 +194,67 @@ char* JfrRedactedEvents::new_redacted_text() { return result; } +void JfrRedactedEvents::redact(String* scratch_string, const char* target, const String* redaction) { + if (strchr(redaction->text(), REDACTED_MARKER)) { + return; + } + const char* position = target; + while (true) { + const char* sensitive = strstr(position, redaction->text()); + if (sensitive == nullptr) { + return; + } + size_t index = (size_t)(sensitive - target); + for (size_t i = 0; i < redaction->length(); i++) { + scratch_string->set(index + i, REDACTED_MARKER); + } + position = sensitive + 1; + } +} + +String* JfrRedactedEvents::redact_environment_variable_value(const char* value) { + if (strchr(value, REDACTED_MARKER)) { + return new String(REDACTED); + } + bool changed = false; + String* input = new String(value); + if (_redacted_flight_recorder_options_with_marker != nullptr) { + size_t length = strlen(FlightRecorderOptions); + while (const char* start = strstr(input->text(), FlightRecorderOptions)) { + changed = true; + const char* end = start + length; + stringStream s; + s.write(input->text(), start - input->text()); + s.write(_redacted_flight_recorder_options_with_marker->text(), _redacted_flight_recorder_options_with_marker->length()); + s.write(end, strlen(end)); + String* result = new String(s.base()); + delete input; + input = result; + } + } + String* scratch_string = new String(input->text()); + for (int i = 0; i < _redacted_arguments->length(); i++) { + redact(scratch_string, input->text(), _redacted_arguments->at(i)); + } + stringStream result; + bool inside_redaction = false; + for (size_t i = 0; i < scratch_string->length(); i++) { + if (scratch_string->at(i) == REDACTED_MARKER) { + changed = true; + if (!inside_redaction) { + result.print(REDACTED); + } + inside_redaction = true; + } else { + result.put(scratch_string->at(i)); + inside_redaction = false; + } + } + delete scratch_string; + delete input; + return changed ? new String(result.base()) : nullptr; +} + bool JfrRedactedEvents::emit_initial_environment_variables(bool log) { if (_initial_environment_variables == nullptr) { ensure_initialized(); @@ -207,6 +275,16 @@ bool JfrRedactedEvents::emit_initial_environment_variables(bool log) { if (log) { log_debug(jfr, redact)("Redacted initial environment variable named '%s'", key->text()); } + } else { + String* redacted_value = redact_environment_variable_value(value); + if (redacted_value != nullptr) { + if (log) { + log_debug(jfr, redact)("Redacted argument in initial environment variable value named '%s'", key->text()); + } + _initial_environment_variables->append(new StringKeyValue(key, redacted_value->text())); + delete redacted_value; + continue; + } } _initial_environment_variables->append(new StringKeyValue(key, value)); } @@ -262,6 +340,9 @@ bool JfrRedactedEvents::match_flag(const char* flag_name, const char* arg) { if (flag_name == nullptr || arg == nullptr) { return false; } + if (strncmp(arg, "-XX:", 4) == 0) { + arg += 4; + } while (*flag_name) { if (*arg != *flag_name) { return false; @@ -346,6 +427,34 @@ void JfrRedactedEvents::emit_jvm_information(bool log) { } } +// Method assumes that FlightRecorderOptions has been successfully parsed during startup +String* JfrRedactedEvents::redact_flight_recorder_options(const char* option, bool marker) { + JavaThread* THREAD = JavaThread::current(); + const size_t length = strlen(option); + DCmdArgIter iterator(option, length, ','); + while (iterator.next(THREAD)) { + if (strncmp(iterator.key_addr(), REDACT_ARGUMENT, REDACT_ARGUMENT_LENGTH) == 0) { + const char* start = iterator.value_addr(); + const char* end = start + iterator.value_length(); + stringStream result; + result.write(option, start - option); + if (marker) { + result.put(REDACTED_MARKER); + } else { + result.write(REDACTED, REDACTED_LENGTH); + } + result.write(end, option + length - end); + return new String(result.base()); + } + } + if (HAS_PENDING_EXCEPTION) { + DEBUG_ONLY(ShouldNotReachHere();) + CLEAR_PENDING_EXCEPTION; + return new String(REDACTED); + } + return nullptr; +} + void JfrRedactedEvents::ensure_initialized() { if (_initialized) { return; @@ -359,31 +468,12 @@ void JfrRedactedEvents::ensure_initialized() { add_default_filters(_argument_filters, true); } if (FlightRecorderOptions != nullptr) { - if (strstr(FlightRecorderOptions, REDACT_ARGUMENT_EQUAL) != nullptr) { - DCmdIter iterator(FlightRecorderOptions, ','); - stringStream result; - size_t pos = 0; - while(iterator.has_next()) { - CmdLine line = iterator.next(); - const char* start = line.cmd_addr(); - if (strncmp(start, REDACT_ARGUMENT_EQUAL, REDACT_ARGUMENT_EQUAL_LENGTH) == 0) { - result.print(REDACT_ARGUMENT_EQUAL); - result.print(REDACTED); - // Preserve ',' if there are more tokens - pos = iterator.has_next() ? iterator.cursor() - 1 : iterator.cursor(); - } - while (pos < iterator.cursor()) { - result.write(FlightRecorderOptions + pos, 1); - pos++; - } - } - _redacted_flight_recorder_options = new String(result.base()); - } else { - _redacted_flight_recorder_options = new String(FlightRecorderOptions); - } + _redacted_flight_recorder_options = redact_flight_recorder_options(FlightRecorderOptions, false); + _redacted_flight_recorder_options_with_marker = redact_flight_recorder_options(FlightRecorderOptions, true); + } + if (_redacted_arguments == nullptr) { + _redacted_arguments = new StringArray(); } - - _redacted_arguments = new StringArray(); StringArray* java_args = make_java_args_array(); _redacted_java_command_line = redact_command_line(java_args); @@ -396,7 +486,6 @@ void JfrRedactedEvents::ensure_initialized() { StringArray* flags_args = make_jvm_args_array(Arguments::jvm_flags_array(), Arguments::num_jvm_flags()); _redacted_flags_command_line = redact_command_line(flags_args); delete flags_args; - _initialized = true; } @@ -422,8 +511,8 @@ String* JfrRedactedEvents::redact_command_line(StringArray* arguments) { for (int j = arg_index; j < next_index; j++) { result->add(REDACTED); const char* arg = arguments->at(j)->text(); - if (arg != nullptr && strncmp(arg, "-XX:", 4) == 0) { - _redacted_arguments->add(arg + 4); + if (arg != nullptr) { + _redacted_arguments->add(arg); } } arg_index = next_index; @@ -504,15 +593,23 @@ StringArray* JfrRedactedEvents::make_jvm_args_array(char** jvm_args_array, int a return nullptr; } StringArray* result = new StringArray(array_length); - for(int i = 0; i < array_length; i++) { + for (int i = 0; i < array_length; i++) { char* argument = jvm_args_array[i]; - if (_redacted_flight_recorder_options != nullptr && - strncmp(argument, "-XX:FlightRecorderOptions", 25) == 0) { - const char* text = _redacted_flight_recorder_options->text(); - size_t length = _redacted_flight_recorder_options->length(); - // Length must be at least 26 or the JVM will not start. - result->add(new String(argument, 26, text, length)); - continue; + if (strncmp(argument, "-XX:FlightRecorderOptions", 25) == 0) { + size_t length = strlen(argument); + if (length > 25 && + _redacted_flight_recorder_options != nullptr && + strcmp(argument + 26, FlightRecorderOptions) == 0) { + const char* text = _redacted_flight_recorder_options->text(); + // Length must be at least 26 or the JVM will not start. + result->add(new String(argument, 26, text, _redacted_flight_recorder_options->length())); + continue; + } + if (strstr(argument, REDACT_ARGUMENT_EQUAL) != nullptr) { + _redacted_arguments->add(argument); + result->add("-XX:FlightRecorderOptions:[REDACTED]"); + continue; + } } if (strncmp(argument, "-D", 2) == 0) { const char* key_start = argument + 2; @@ -523,6 +620,7 @@ StringArray* JfrRedactedEvents::make_jvm_args_array(char** jvm_args_array, int a bool redact = match_key(_key_filters, key_tmp->text()); delete key_tmp; if (redact) { + _redacted_arguments->add(argument); size_t unsensitive_length = (size_t)(eq - argument) + 1; result->add(new String(argument, unsensitive_length, REDACTED, REDACTED_LENGTH)); continue; diff --git a/src/hotspot/share/jfr/periodic/jfrRedactedEvents.hpp b/src/hotspot/share/jfr/periodic/jfrRedactedEvents.hpp index dc972190b6c..c3d6fd32cbc 100644 --- a/src/hotspot/share/jfr/periodic/jfrRedactedEvents.hpp +++ b/src/hotspot/share/jfr/periodic/jfrRedactedEvents.hpp @@ -195,6 +195,7 @@ class JfrRedactedEvents: public AllStatic { static String* _redacted_jvm_command_line; static String* _redacted_flags_command_line; static String* _redacted_flight_recorder_options; + static String* _redacted_flight_recorder_options_with_marker; static GrowableArray* _initial_system_properties; static GrowableArray* _initial_environment_variables; static GrowableArray* _string_flags; @@ -217,7 +218,10 @@ class JfrRedactedEvents: public AllStatic { static int match_arguments(StringArray* filter_array, StringArray* arguments, int arg_index); static bool match_key(StringArray* array, const char* text); static bool read_file(StringArray* target, const char* filename); + static void redact(String* scratch_string, const char* target, const String* redaction); + static String* redact_flight_recorder_options(const char* option, bool marker); static String* redact_command_line(StringArray* arguments); + static String* redact_environment_variable_value(const char* value); static StringArray* split(const char* text, char separator); }; diff --git a/src/java.base/share/man/java.md b/src/java.base/share/man/java.md index 89166ae39e1..a0717055864 100644 --- a/src/java.base/share/man/java.md +++ b/src/java.base/share/man/java.md @@ -1215,9 +1215,11 @@ These `java` options control the runtime behavior of the Java HotSpot VM. be replaced with `[REDACTED]`. The option `redact-argument` is best-effort and applies only to command-line arguments in the `jdk.JVMInformation` event and to the `java.command` system property in the - `jdk.InitialSystemProperty` event. Other events, such as `jdk.ProcessStart` - (child processes), are not redacted. Use `-XX:FlightRecorderOptions:help` - to see the default filters used by the `redact-argument` option. + `jdk.InitialSystemProperty` event, and to matching command-line argument + text in the values of `jdk.InitialEnvironmentVariable` events. Other + events, such as `jdk.ProcessStart` (child processes), are not redacted. + Use `-XX:FlightRecorderOptions:help` to see the default filters used by + the `redact-argument` option. `redact-key=`key-filter : Replace the value of environment variables and system properties diff --git a/test/jdk/jdk/jfr/startupargs/TestRedact.java b/test/jdk/jdk/jfr/startupargs/TestRedact.java index 2d96408a3f5..f3f3a9e5fa6 100644 --- a/test/jdk/jdk/jfr/startupargs/TestRedact.java +++ b/test/jdk/jdk/jfr/startupargs/TestRedact.java @@ -42,6 +42,7 @@ import jdk.jfr.consumer.EventStream; import jdk.jfr.consumer.RecordingFile; import jdk.test.lib.Asserts; import jdk.test.lib.jfr.CommonHelper; +import jdk.test.lib.Platform; import jdk.test.lib.process.OutputAnalyzer; import jdk.test.lib.process.ProcessTools; @@ -169,6 +170,7 @@ public class TestRedact { testRedactKey(); testRedactArgument(); testRedactMultiple(); + testOptionVariable(); testWildcards(); testDefaults(); testRedactFile(); @@ -315,15 +317,6 @@ public class TestRedact { private static void testEmpty() throws Exception { var environment = Map.of("API_TOKEN", "Zebra1"); var properties = Map.of("API_KEY", "Zebra2"); - Execution e1 = run(environment, properties, - "-XX:FlightRecorderOptions:redact-key=,redact-argument=", "Zebra3" - ); - e1.output().shouldContain("Default redaction filters are replaced."); - e1.output().shouldContain("redact-key=none to disable filters without a warning"); - e1.output().shouldContain("redact-argument=none to disable filters without a warning"); - e1.assertUnredacted("Zebra1"); - e1.assertUnredacted("Zebra2"); - e1.assertUnredacted("Zebra3"); Execution e2 = run(environment, properties, "-XX:FlightRecorderOptions:redact-argument=none,redact-key=none", "Zebra3" @@ -377,6 +370,12 @@ public class TestRedact { e.assertRedactedArgument("N4711"); e.assertRedactedArgument("Smith:abc123"); e.assertUnredacted("Banana"); + + String option = Platform.isWindows() ? + "-XX:FlightRecorderOptions:redact-argument='Foo,bar'" : + "-XX:FlightRecorderOptions:redact-argument=\"Foo,bar\""; + Execution e2 = run(option,"Foo,bar"); + e2.assertRedactedArgument("Foo,bar"); } private static void testRedactMultiple() throws Exception { @@ -390,6 +389,69 @@ public class TestRedact { e.assertRedactedArgument("Quz"); } + private static void testOptionVariable() throws Exception { + // Simulate shell expansion with the three options: + // SYSTEM_PROPS, JVM_OPTIONS and PROGRAM_OPTIONS + String systemProperty = "-Dsecret=apple"; + String jvmOption = "-XX:FlightRecorderOptions:stackdepth=32,redact-argument=+Aracuan"; + String programOption = "Aracuan"; + Execution e1 = run( + Map.of("SYSTEM_PROPS", systemProperty, + "JVM_OPTIONS", jvmOption, + "PROGRAM_OPTIONS", programOption), + Map.of("secret","apple"), + List.of(systemProperty, jvmOption), + programOption + ); + e1.assertRedactedKey("SYSTEM_PROPS"); + String redactedJVMOption = e1.environment.get("JVM_OPTIONS"); + if (!redactedJVMOption.equals("-XX:FlightRecorderOptions:stackdepth=32,redact-argument=[REDACTED]")) { + throw new Exception("Expected partial redaction for environment variable with -XX:FlightRecorderOptions:redact-argument="); + } + e1.assertRedactedKey("PROGRAM_OPTIONS"); + e1.assertRedactedKey("secret"); + e1.assertRedactedArgument("Aracuan"); + + Execution e2 = run( + Map.of("PROGRAM_OPTIONS", "BLUE RED GREEN GREDELINE"), + Map.of(), + "-XX:FlightRecorderOptions:redact-argument=+*red*", + "BLUE", "RED", "GREEN", "GREDELINE" + ); + String programOptions = e2.environment().get("PROGRAM_OPTIONS"); + if (!programOptions.equals("BLUE [REDACTED] GREEN [REDACTED]")) { + e2.print(); + throw new Exception("Missing redaction inside option variable"); + } + + Execution e3 = run( + Map.of("PROGRAM_OPTIONS", "ZEBRA FISH ZEBRACCOON FISH RACCOON"), + Map.of(), + "-XX:FlightRecorderOptions:redact-argument=+zebra;raccoon", + "ZEBRA", "FISH", "ZEBRACCOON", "FISH", "RACCOON" + ); + programOptions = e3.environment().get("PROGRAM_OPTIONS"); + if (!programOptions.equals("[REDACTED] FISH [REDACTED] FISH [REDACTED]")) { + e3.print(); + throw new Exception("Incorrect redaction when option arguments overlap"); + } + + String option1 = "-XX:FlightRecorderOptions:redact-argument=Zebra,gibberish=,,,"; + String option2 = "-XX:FlightRecorderOptions:redact-argument=Tiger"; + Execution e4 = run( + Map.of("MY_JVM_OPTIONS", option1 + " " + option2), + Map.of(), + List.of(option1, option2), + "TIGER" + ); + e4.assertRedactedArgument("TIGER"); + String redacted = e4.environment().get("MY_JVM_OPTIONS"); + if (!redacted.equals("[REDACTED] -XX:FlightRecorderOptions:redact-argument=[REDACTED]")) { + e4.print(); + throw new Exception("Incorrect redaction with multiple options in environment variables"); + } + } + private static void testRedactKey() throws Exception { Execution e = run( Map.of("cart", "wheel", "banana", "split", "rose", "bud"), @@ -407,14 +469,20 @@ public class TestRedact { return run(Map.of(), Map.of(), options, args); } - private static Execution run(Map environment, Map properties, String options, String... args) throws Exception { + private static Execution run(Map environment, Map properties, String option, String... args) throws Exception { + return run(environment, properties, List.of(option), args); + } + + private static Execution run(Map environment, Map properties, List options, String... args) throws Exception { List arguments = new ArrayList<>(); Path file = Path.of("file.jfr"); for (var entry : properties.entrySet()) { arguments.add("-D" + entry.getKey() + "=" + entry.getValue()); } arguments.add("-XX:StartFlightRecording:filename=" + file.toAbsolutePath().toString()); - arguments.add(options); + for (String option : options) { + arguments.add(option); + } arguments.add("jdk.jfr.startupargs.Application"); arguments.addAll(Arrays.asList(args)); From 376ecc5a9d89b5f8c1593d3e717eb2c748b17449 Mon Sep 17 00:00:00 2001 From: Robert Toyonaga Date: Wed, 15 Jul 2026 13:37:35 +0000 Subject: [PATCH 159/305] 8386546: Fix race when reserving memory with NUMA interleaving on Windows Reviewed-by: asmehra, stuefe --- src/hotspot/os/windows/os_windows.cpp | 175 ++++++++++++- src/hotspot/os/windows/os_windows.hpp | 51 ++++ .../hotspot/gtest/runtime/test_os_windows.cpp | 240 ++++++++++++++++++ 3 files changed, 456 insertions(+), 10 deletions(-) diff --git a/src/hotspot/os/windows/os_windows.cpp b/src/hotspot/os/windows/os_windows.cpp index 0fc636483f5..f62e9c298e8 100644 --- a/src/hotspot/os/windows/os_windows.cpp +++ b/src/hotspot/os/windows/os_windows.cpp @@ -3507,6 +3507,152 @@ char* os::pd_reserve_memory(size_t bytes, bool exec) { return pd_attempt_reserve_memory_at(nullptr /* addr */, bytes, exec); } +// This allocates a placeholder via VirtualAlloc2(MEM_RESERVE_PLACEHOLDER). +os::win32::PlaceholderRegion os::win32::reserve_placeholder_memory(size_t bytes, char* addr) { + assert(bytes > 0, "Size must be a value greater than 0"); + assert(is_aligned(addr, os::vm_allocation_granularity()), "Requested address should be aligned to allocation granularity."); + assert(is_aligned(bytes, os::vm_page_size()), "Requested size, bytes, should be aligned to page size."); + + if (!is_VirtualAlloc2_supported()) { + return PlaceholderRegion(); + } + + char* res = (char*)os::win32::VirtualAlloc2( + GetCurrentProcess(), + addr, + bytes, + MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, + PAGE_NOACCESS, + nullptr, 0); + + if (res != nullptr) { + log_trace(os)("VirtualAlloc2 placeholder of size (%zu) returned " PTR_FORMAT ".", bytes, p2i(res)); + return PlaceholderRegion(res, bytes); + } else { + log_warning(os)("VirtualAlloc2 placeholder reservation of size (%zu) at " PTR_FORMAT ": error %lu.", bytes, p2i(addr), GetLastError()); + return PlaceholderRegion(); + } +} + +os::win32::PlaceholderRegionPair os::win32::split_memory(const PlaceholderRegion& orig, size_t offset) { + guarantee(is_VirtualAlloc2_supported(), "split_memory requires VirtualAlloc2."); + assert(!orig.is_empty(), "Region cannot be empty"); + assert(offset <= orig.size(), "Offset must be less than or equal to region size"); + + char* original_base = orig.base(); + size_t original_size = orig.size(); + + if (offset == 0) { + log_trace(os)("Split memory has offset 0: " RANGEFMT, RANGEFMTARGS(original_base, original_size)); + return { PlaceholderRegion(), orig }; + } else if (offset == original_size) { + log_trace(os)("Split memory consumed the whole region: " RANGEFMT, RANGEFMTARGS(original_base, original_size)); + return { orig, PlaceholderRegion() }; + } + + assert(is_aligned(offset, os::vm_allocation_granularity()), "If the split does not consume the entire original region, the offset should be aligned to allocation granularity since a new Placeholder is spawned the split point."); + + // VirtualFree with MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER splits the + // placeholder [original_base, original_base+original_size) in two: + // [original_base, original_base+offset) and [original_base+offset, original_base+original_size) + // + // With correct inputs, this should not fail. + // A failure indicates either a programming error (e.g., bad alignment, + // region not actually a placeholder) or a catastrophic system problem. + // Crashing with a diagnostic is more useful than attempting recovery. + BOOL result = virtualFree(original_base, offset, MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER); + guarantee(result != FALSE, + "Failed to split placeholder at " PTR_FORMAT " (offset %zu): error %lu.", + p2i(original_base), offset, GetLastError()); + + log_trace(os)("Split placeholder " RANGE_FORMAT " at offset %zu.", + RANGE_FORMAT_ARGS(original_base, original_size), offset); + + return {PlaceholderRegion(original_base, offset), PlaceholderRegion(original_base + offset, original_size - offset)}; +} + +char* os::win32::convert_to_reserved(PlaceholderRegion region, int numa_node) { + guarantee(is_VirtualAlloc2_supported(), "convert_to_reserved requires VirtualAlloc2"); + assert(!region.is_empty(), "Region cannot be empty"); + + char* base = region.base(); + size_t size = region.size(); + + assert(base != nullptr, "Region base cannot be null"); + assert(size > 0, "Region size must be positive"); + + MEM_EXTENDED_PARAMETER param = { 0 }; + MEM_EXTENDED_PARAMETER* param_ptr = nullptr; + ULONG param_count = 0; + + if (numa_node >= 0) { + param.Type = MemExtendedParameterNumaNode; + param.ULong = (DWORD)numa_node; + param_ptr = ¶m; + param_count = 1; + } + + // Similar to split_memory, with correct inputs, this should never fail. + char* reserved = (char*)os::win32::VirtualAlloc2( + GetCurrentProcess(), + base, + size, + MEM_RESERVE | MEM_REPLACE_PLACEHOLDER, + PAGE_READWRITE, + param_ptr, param_count); + guarantee(reserved != nullptr, + "Failed to convert placeholder to reservation at " PTR_FORMAT " (%zu, numa node %d): error %lu.", + p2i(base), size, numa_node, GetLastError()); + + if (numa_node >= 0) { + log_trace(os)("Converted placeholder " RANGE_FORMAT " to reservation on NUMA node %d.", RANGE_FORMAT_ARGS(reserved, size), numa_node); + } else { + log_trace(os)("Converted placeholder " RANGE_FORMAT " to reservation.", RANGE_FORMAT_ARGS(reserved, size)); + } + + return reserved; +} + +// Reserve a region split across NUMA nodes. +// Uses VirtualAlloc2 placeholders in order to avoid races when splitting up the initial reservation into +// chunks assigned to different nodes. Returns the base address of the reserved range, or nullptr on failure. +static char* reserve_with_numa_placeholder(char* addr, size_t bytes) { + assert(is_VirtualAlloc2_supported(), "requires VirtualAlloc2"); + + const size_t chunk_size = NUMAInterleaveGranularity; + + // Reserve the full range as a placeholder. + // If we requested an address, reserve_placeholder_memory will obtain it or fail. + os::win32::PlaceholderRegion whole_range = os::win32::reserve_placeholder_memory(bytes, addr); + if (whole_range.is_empty()) { + log_warning(os)("Failed to reserve placeholder for NUMA interleaving (" PTR_FORMAT ", %zu).", p2i(addr), bytes); + return nullptr; + } + + char* const whole_range_base = whole_range.base(); + log_trace(os)("Created VirtualAlloc2 NUMA placeholder at " RANGE_FORMAT " (%zu bytes).", RANGE_FORMAT_ARGS(whole_range_base, bytes), bytes); + + char* cur = whole_range_base; + size_t remaining_len = whole_range.size(); + + int count = 0; + const int node_count = numa_node_list_holder.get_count(); + + while (remaining_len > 0) { + const size_t bytes_to_rq = MIN2(remaining_len, chunk_size - ((uintptr_t)cur % chunk_size)); + os::win32::PlaceholderRegion remaining(cur, remaining_len); + os::win32::PlaceholderRegionPair split = os::win32::split_memory(remaining, bytes_to_rq); + // Assign 0 for testing on systems without NUMA interleaving + DWORD node = node_count > 0 ? numa_node_list_holder.get_node_list_entry(count % node_count) : 0; + os::win32::convert_to_reserved(split.left, (int)node); + cur = split.right.base(); + remaining_len = split.right.size(); + count++; + } + + return whole_range_base; +} + // Reserve memory at an arbitrary address, only if that area is // available (and not reserved for something else). char* os::pd_attempt_reserve_memory_at(char* addr, size_t bytes, bool exec) { @@ -3516,23 +3662,32 @@ char* os::pd_attempt_reserve_memory_at(char* addr, size_t bytes, bool exec) { char* res; // note that if UseLargePages is on, all the areas that require interleaving // will go thru reserve_memory_special rather than thru here. - bool use_individual = (UseNUMAInterleaving && !UseLargePages); - if (!use_individual) { - res = (char*)virtualAlloc(addr, bytes, MEM_RESERVE, PAGE_READWRITE); - } else { + bool use_numa_interleaving = (UseNUMAInterleaving && !UseLargePages); + if (use_numa_interleaving) { elapsedTimer reserveTimer; if (Verbose && PrintMiscellaneous) reserveTimer.start(); - // in numa interleaving, we have to allocate pages individually - // (well really chunks of NUMAInterleaveGranularity size) - res = allocate_pages_individually(bytes, addr, MEM_RESERVE, PAGE_READWRITE); - if (res == nullptr) { - warning("NUMA page allocation failed"); + if (is_VirtualAlloc2_supported()) { + // Splittable NUMA interleaving with VirtualAlloc2 placeholders. + res = reserve_with_numa_placeholder(addr, bytes); + if (res == nullptr) { + log_warning(os)("NUMA allocation using placeholders failed"); + } + } else { + // Non-splittable NUMA interleaving: allocate_pages_individually (possible races). + // (well really chunks of NUMAInterleaveGranularity size) + res = allocate_pages_individually(bytes, addr, MEM_RESERVE, PAGE_READWRITE); + if (res == nullptr) { + log_warning(os)("NUMA page allocation failed"); + } } if (Verbose && PrintMiscellaneous) { reserveTimer.stop(); tty->print_cr("reserve_memory of %zx bytes took " JLONG_FORMAT " ms (" JLONG_FORMAT " ticks)", bytes, - reserveTimer.milliseconds(), reserveTimer.ticks()); + reserveTimer.milliseconds(), reserveTimer.ticks()); } + } else { + // Standard reservation. + res = (char*)virtualAlloc(addr, bytes, MEM_RESERVE, PAGE_READWRITE); } assert(res == nullptr || addr == nullptr || addr == res, "Unexpected address from reserve."); diff --git a/src/hotspot/os/windows/os_windows.hpp b/src/hotspot/os/windows/os_windows.hpp index 5ebc80c817b..68e77c9957f 100644 --- a/src/hotspot/os/windows/os_windows.hpp +++ b/src/hotspot/os/windows/os_windows.hpp @@ -122,6 +122,57 @@ class os::win32 { typedef PVOID (WINAPI *MapViewOfFile3Fn)(HANDLE, HANDLE, PVOID, ULONG64, SIZE_T, ULONG, ULONG, MEM_EXTENDED_PARAMETER*, ULONG); static MapViewOfFile3Fn MapViewOfFile3; + // A "reserved" region of address space that can be split or converted to a + // normal reservation. Conceptually distinct from a reserved region: + // callers must NOT call commit_memory, map_memory, or other operations + // directly on the raw address. They must first convert it via + // convert_to_reserved(). + class PlaceholderRegion { + char* const _base; + size_t const _size; + public: + PlaceholderRegion() : _base(nullptr), _size(0) {} + PlaceholderRegion(char* base, size_t size) : _base(base), _size(size) { + if (base != nullptr) { + assert(size > 0, "Non-empty Placeholder must have positive size."); + assert(is_aligned(base, os::vm_allocation_granularity()), "New Placeholder base should be aligned to allocation granularity."); + assert(is_aligned(size, os::vm_page_size()), "New Placeholder size should be page-aligned"); + } else { + assert(size == 0, "Empty Placeholder must have zero size."); + } + } + PlaceholderRegion(const PlaceholderRegion& source) : PlaceholderRegion(source._base, source._size) {} + char* base() const { return _base; } + size_t size() const { return _size; } + bool is_empty() const { return _base == nullptr; } + }; + + struct PlaceholderRegionPair { + PlaceholderRegion left; + PlaceholderRegion right; + }; + + // Reserves a virtual memory region that can be split after allocation. + // The returned region must be converted via convert_to_reserved() before committing. + // If the returned PlaceholderRegion is empty, the reservation failed. + // This should only be called after os::init_2() has completed, otherwise the Windows API may not be initialized. + // Uses VirtualAlloc2, which requires the base address be null or aligned to allocation granularity. + static PlaceholderRegion reserve_placeholder_memory(size_t bytes, char* addr); + + // Split 'orig' at 'offset'. Returns left and right placeholder pieces as a PlaceholderRegionPair. + // The caller must not use 'orig' afterward. + // Offset must be aligned to allocation granularity. + // If offset == orig.size(), returns { orig, empty }. + // If offset == 0, returns { empty, orig }. + // This should not fail. If unsuccessful, this function fails fatally. + static PlaceholderRegionPair split_memory(const PlaceholderRegion& orig, size_t offset); + + // Convert a placeholder region into a regular reserved region via VirtualAlloc2(MEM_REPLACE_PLACEHOLDER). + // After conversion the Placeholder region should no longer be used. + // This should not fail. If unsuccessful, this function fails fatally. + // If numa_node >= 0, binds the reservation to that NUMA node. + static char* convert_to_reserved(PlaceholderRegion region, int numa_node = -1); + private: static void initialize_performance_counter(); diff --git a/test/hotspot/gtest/runtime/test_os_windows.cpp b/test/hotspot/gtest/runtime/test_os_windows.cpp index 6822e37b539..5efa0580eda 100644 --- a/test/hotspot/gtest/runtime/test_os_windows.cpp +++ b/test/hotspot/gtest/runtime/test_os_windows.cpp @@ -32,6 +32,8 @@ #include "concurrentTestRunner.inline.hpp" #include "unittest.hpp" +#include + namespace { class MemoryReleaser { char* const _ptr; @@ -873,4 +875,242 @@ TEST_VM(os_windows, SafeFetch32_with_page_guard_protection) { ::VirtualFree(p, 0, MEM_RELEASE); } +#define SKIP_IF_PLACEHOLDER_NOT_SUPPORTED \ + if (os::win32::VirtualAlloc2 == nullptr) GTEST_SKIP() << "VirtualAlloc2 not available"; + +TEST_VM(os, placeholder_reserve_and_convert) { + SKIP_IF_PLACEHOLDER_NOT_SUPPORTED; + + const size_t size = 4 * os::vm_allocation_granularity(); + + os::win32::PlaceholderRegion region = os::win32::reserve_placeholder_memory(size, nullptr); + ASSERT_FALSE(region.is_empty()); + ASSERT_EQ(region.size(), size); + ASSERT_NE(region.base(), (char*)nullptr); + + char* reserved = os::win32::convert_to_reserved(region); + ASSERT_EQ(reserved, region.base()); + + // Commit, but bypass NMT + ASSERT_NE(::VirtualAlloc(reserved, size, MEM_COMMIT, PAGE_READWRITE), nullptr); + // Touch the memory to confirm it's usable. + memset(reserved, 0xAB, size); + EXPECT_EQ((unsigned char)reserved[0], 0xAB); + EXPECT_EQ((unsigned char)reserved[size - 1], 0xAB); + + ASSERT_TRUE(::VirtualFree(reserved, 0, MEM_RELEASE)); +} + +TEST_VM(os, placeholder_split_two_way) { + SKIP_IF_PLACEHOLDER_NOT_SUPPORTED; + + const size_t granularity = os::vm_allocation_granularity(); + const size_t total = 4 * granularity; + const size_t split_offset = 3 * granularity; + + os::win32::PlaceholderRegion region = os::win32::reserve_placeholder_memory(total, nullptr); + ASSERT_FALSE(region.is_empty()); + + char* original_base = region.base(); + os::win32::PlaceholderRegionPair split = os::win32::split_memory(region, split_offset); + + // Leading piece: [base, base+split_offset) + ASSERT_EQ(split.left.base(), original_base); + ASSERT_EQ(split.left.size(), split_offset); + + // Trailing piece: [base+split_offset, base+total) + ASSERT_EQ(split.right.base(), original_base + split_offset); + ASSERT_EQ(split.right.size(), total - split_offset); + + // Convert both and commit. + char* addr1 = os::win32::convert_to_reserved(split.left); + char* addr2 = os::win32::convert_to_reserved(split.right); + ASSERT_EQ(addr1, original_base); + ASSERT_EQ(addr2, original_base + split_offset); + + // Commit, but bypass NMT + ASSERT_NE(::VirtualAlloc(addr1, split_offset, MEM_COMMIT, PAGE_READWRITE), nullptr); + ASSERT_NE(::VirtualAlloc(addr2, total - split_offset, MEM_COMMIT, PAGE_READWRITE), nullptr); + + // Touch the memory to confirm it's usable. + memset(addr1, 0x11, split_offset); + memset(addr2, 0x22, total - split_offset); + EXPECT_EQ((unsigned char)addr1[0], 0x11); + EXPECT_EQ((unsigned char)addr2[0], 0x22); + + // Verify we can release the parts separately. + ASSERT_TRUE(::VirtualFree(addr1, 0, MEM_RELEASE)); + ASSERT_TRUE(::VirtualFree(addr2, 0, MEM_RELEASE)); +} + +TEST_VM(os, placeholder_split_consumes_full_range) { + SKIP_IF_PLACEHOLDER_NOT_SUPPORTED; + + const size_t region_size = os::vm_allocation_granularity(); + os::win32::PlaceholderRegion region = os::win32::reserve_placeholder_memory(region_size, nullptr); + ASSERT_FALSE(region.is_empty()); + + char* original_base = region.base(); + os::win32::PlaceholderRegionPair split = os::win32::split_memory(region, region_size); + + // Leading piece + ASSERT_EQ(split.left.base(), original_base); + ASSERT_EQ(split.left.size(), region_size); + + // Trailing piece + ASSERT_TRUE(split.right.is_empty()); + + // Commit and touch to confirm it's usable. + char* addr = os::win32::convert_to_reserved(split.left); + ASSERT_NE(::VirtualAlloc(addr, region_size, MEM_COMMIT, PAGE_READWRITE), nullptr); + memset(addr, 0x11, region_size); + EXPECT_EQ((unsigned char)addr[0], 0x11); + + ASSERT_TRUE(::VirtualFree(addr, 0, MEM_RELEASE)); +} + +TEST_VM(os, placeholder_split_consumes_nothing) { + SKIP_IF_PLACEHOLDER_NOT_SUPPORTED; + + const size_t region_size = os::vm_allocation_granularity(); + os::win32::PlaceholderRegion region = os::win32::reserve_placeholder_memory(region_size, nullptr); + ASSERT_FALSE(region.is_empty()); + + char* original_base = region.base(); + os::win32::PlaceholderRegionPair split = os::win32::split_memory(region, 0); + + // Leading piece + ASSERT_TRUE(split.left.is_empty()); + + // Trailing piece + ASSERT_EQ(split.right.base(), original_base); + ASSERT_EQ(split.right.size(), region_size); + + // Commit and touch to confirm it's usable. + char* addr = os::win32::convert_to_reserved(split.right); + ASSERT_NE(::VirtualAlloc(addr, region_size, MEM_COMMIT, PAGE_READWRITE), nullptr); + memset(addr, 0x11, region_size); + EXPECT_EQ((unsigned char)addr[0], 0x11); + + ASSERT_TRUE(::VirtualFree(addr, 0, MEM_RELEASE)); +} + +TEST_VM_FATAL_ERROR_MSG(os, placeholder_double_convert, ".*Failed to convert placeholder.*") { + SKIP_IF_PLACEHOLDER_NOT_SUPPORTED; + const size_t size = 4 * os::vm_allocation_granularity(); + + os::win32::PlaceholderRegion region = os::win32::reserve_placeholder_memory(size, nullptr); + ASSERT_FALSE(region.is_empty()); + ASSERT_EQ(region.size(), size); + ASSERT_NE(region.base(), (char*)nullptr); + + // Double convert + char* reserved = os::win32::convert_to_reserved(region); + ASSERT_EQ(reserved, region.base()); + // This second conversion attempt should crash producing the error "...Failed to convert placeholder..." + reserved = os::win32::convert_to_reserved(region); +} + +TEST_VM(os, placeholder_commit_before_convert) { + SKIP_IF_PLACEHOLDER_NOT_SUPPORTED; + const size_t size = 4 * os::vm_allocation_granularity(); + + os::win32::PlaceholderRegion region = os::win32::reserve_placeholder_memory(size, nullptr); + ASSERT_FALSE(region.is_empty()); + ASSERT_EQ(region.size(), size); + ASSERT_NE(region.base(), (char*)nullptr); + + // Committing should fail here, but not crash. + ASSERT_FALSE(::VirtualAlloc(region.base(), size, MEM_COMMIT, PAGE_READWRITE)); + ASSERT_TRUE(::VirtualFree(region.base(), 0, MEM_RELEASE)); +} + +TEST_VM(os, placeholder_release_before_convert) { + SKIP_IF_PLACEHOLDER_NOT_SUPPORTED; + + const size_t size = 4 * os::vm_allocation_granularity(); + + os::win32::PlaceholderRegion region = os::win32::reserve_placeholder_memory(size, nullptr); + ASSERT_FALSE(region.is_empty()); + ASSERT_EQ(region.size(), size); + ASSERT_NE(region.base(), (char*)nullptr); + + ASSERT_TRUE(::VirtualFree(region.base(), 0, MEM_RELEASE)); +} + +// Test that reserve_with_numa_placeholder works correctly. +// On NUMA systems with a single NUMA node, there is no true interleaving +// (all chunks are put on node 0) but the placeholder split/replace path +// is still properly exercised. +TEST_VM(os_windows, placeholder_numa_reserve_commit) { + SKIP_IF_PLACEHOLDER_NOT_SUPPORTED; + + const size_t num_nodes = os::numa_get_groups_num(); + + // Enable NUMA interleaving for this test so the correct code path is taken. + AutoSaveRestore FLAG_GUARD(UseNUMAInterleaving); + AutoSaveRestore FLAG_GUARD(UseLargePages); + FLAG_SET_CMDLINE(UseNUMAInterleaving, true); + FLAG_SET_CMDLINE(UseLargePages, false); + + // Allocate a region large enough to span multiple NUMA interleave chunks. + // NUMAInterleaveGranularity defaults to 2MB + const size_t chunk_size = NUMAInterleaveGranularity; + const size_t num_chunks = 4; + const size_t size = num_chunks * chunk_size; + + char* result = os::attempt_reserve_memory_at(nullptr, size, mtTest); + ASSERT_TRUE(result != nullptr) << "Failed to reserve memory"; + ASSERT_TRUE(is_aligned(result, os::vm_allocation_granularity())); + ASSERT_TRUE(os::commit_memory(result, size, false)); + + // Walk (and touch) the chunks using the same alignment logic as reserve_with_numa_placeholder: + // the first chunk may be shorter (up to the next chunk_size boundary), + // then full chunk_size pieces, with a possible shorter trailing chunk. + PSAPI_WORKING_SET_EX_INFORMATION wsi[num_chunks + 1]; + memset(wsi, 0, sizeof(wsi)); + size_t bytes_remaining = size; + char* addr = result; + size_t actual_chunks = 0; + + while (bytes_remaining > 0) { + size_t this_chunk_size = MIN2(bytes_remaining, chunk_size - ((size_t)addr % chunk_size)); + + memset(addr, 0xDA, this_chunk_size); + + wsi[actual_chunks] = {0}; + wsi[actual_chunks].VirtualAddress = addr; + actual_chunks++; + + bytes_remaining -= this_chunk_size; + addr += this_chunk_size; + } + + BOOL query_ok = QueryWorkingSetEx(GetCurrentProcess(), wsi, sizeof(wsi)); + ASSERT_TRUE(query_ok) << "QueryWorkingSetEx failed: " << GetLastError(); + + // Verify all pages are valid (in the working set). + for (size_t i = 0; i < actual_chunks; i++) { + EXPECT_TRUE(wsi[i].VirtualAttributes.Valid) << "Chunk " << i << " page not valid in working set"; + } + + if (num_nodes > 1) { + // On a multi-NUMA system, verify that not all chunks are assigned to the same node. + ULONG first_node = (ULONG)wsi[0].VirtualAttributes.Node; + bool found_different_node = false; + for (size_t i = 1; i < actual_chunks; i++) { + if (wsi[i].VirtualAttributes.Valid && + (ULONG)wsi[i].VirtualAttributes.Node != first_node) { + found_different_node = true; + break; + } + } + EXPECT_TRUE(found_different_node) + << "All " << actual_chunks << " chunks assigned to NUMA node " << first_node + << "; expected interleaving across " << num_nodes << " nodes"; + } + + os::release_memory(result, size); +} + #endif From d1c87e5b4f3f17c473e2425983b3eb8c7ac15fa8 Mon Sep 17 00:00:00 2001 From: Derek White Date: Wed, 15 Jul 2026 13:42:10 +0000 Subject: [PATCH 160/305] 8388080: Increase static size of some stubs for APX code Reviewed-by: sviswanathan, kvn --- src/hotspot/cpu/x86/methodHandles_x86.hpp | 2 +- src/hotspot/cpu/x86/sharedRuntime_x86_64.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hotspot/cpu/x86/methodHandles_x86.hpp b/src/hotspot/cpu/x86/methodHandles_x86.hpp index c4dde903d29..8fdb6c1fb52 100644 --- a/src/hotspot/cpu/x86/methodHandles_x86.hpp +++ b/src/hotspot/cpu/x86/methodHandles_x86.hpp @@ -27,7 +27,7 @@ // Adapters enum /* platform_dependent_constants */ { - adapter_code_size = 6000 DEBUG_ONLY(+ 6000) + adapter_code_size = 8000 DEBUG_ONLY(+ 6000) }; // Additional helper methods for MethodHandles code generation: diff --git a/src/hotspot/cpu/x86/sharedRuntime_x86_64.cpp b/src/hotspot/cpu/x86/sharedRuntime_x86_64.cpp index 8bb9982a820..7fc105046ff 100644 --- a/src/hotspot/cpu/x86/sharedRuntime_x86_64.cpp +++ b/src/hotspot/cpu/x86/sharedRuntime_x86_64.cpp @@ -3452,7 +3452,7 @@ RuntimeStub* SharedRuntime::generate_jfr_write_checkpoint() { }; const char* name = SharedRuntime::stub_name(StubId::shared_jfr_write_checkpoint_id); - CodeBuffer code(name, 1024, 64); + CodeBuffer code(name, 1024 + (UseAPX ? 1024 : 0), 64); MacroAssembler* masm = new MacroAssembler(&code); address start = __ pc(); From 82a2089959fb47ee0a6bd9f8836de6212de1f02f Mon Sep 17 00:00:00 2001 From: Matias Saavedra Silva Date: Wed, 15 Jul 2026 14:30:08 +0000 Subject: [PATCH 161/305] 8365575: AOT cache should include classes verified using "fail over" verification Reviewed-by: iklam, liach --- src/hotspot/share/classfile/verifier.cpp | 14 +++-- src/hotspot/share/oops/instanceKlass.cpp | 10 ++++ src/hotspot/share/oops/instanceKlass.hpp | 3 + src/hotspot/share/oops/instanceKlassFlags.hpp | 1 + .../cds/appcds/aotCache/VerifierFailOver.java | 59 +++++++++++++++++-- 5 files changed, 77 insertions(+), 10 deletions(-) diff --git a/src/hotspot/share/classfile/verifier.cpp b/src/hotspot/share/classfile/verifier.cpp index 48be24c20dc..8422a39827a 100644 --- a/src/hotspot/share/classfile/verifier.cpp +++ b/src/hotspot/share/classfile/verifier.cpp @@ -222,9 +222,9 @@ bool Verifier::verify(InstanceKlass* klass, bool should_verify_class, TRAPS) { split_verifier.verify_class(THREAD); exception_name = split_verifier.result(); - // If dumping {classic, final} static archive, don't bother to run the old verifier, as + // If dumping classic static archive, don't bother to run the old verifier, as // the class will be excluded from the archive anyway. - bool can_failover = !(CDSConfig::is_dumping_classic_static_archive() || CDSConfig::is_dumping_final_static_archive()) && + bool can_failover = !(CDSConfig::is_dumping_classic_static_archive()) && klass->major_version() < NOFAILOVER_MAJOR_VERSION; if (can_failover && !HAS_PENDING_EXCEPTION && // Split verifier doesn't set PENDING_EXCEPTION for failure @@ -233,9 +233,9 @@ bool Verifier::verify(InstanceKlass* klass, bool should_verify_class, TRAPS) { log_info(verification)("Fail over class verification to old verifier for: %s", klass->external_name()); log_info(class, init)("Fail over class verification to old verifier for: %s", klass->external_name()); #if INCLUDE_CDS - // Exclude any classes that are verified with the old verifier, as the old verifier - // doesn't call SystemDictionaryShared::add_verification_constraint() - if (CDSConfig::is_dumping_archive()) { + // Exclude any classes that are verified with the old verifier when the verification constraints + // cannot be preserved. + if (CDSConfig::is_dumping_archive() && !CDSConfig::is_preserving_verification_constraints()) { SystemDictionaryShared::log_exclusion(klass, "Verified with old verifier"); SystemDictionaryShared::set_excluded(klass); } @@ -244,6 +244,10 @@ bool Verifier::verify(InstanceKlass* klass, bool should_verify_class, TRAPS) { exception_message = message_buffer; exception_name = inference_verify( klass, message_buffer, message_buffer_len, THREAD); + + if (exception_name == nullptr && !HAS_PENDING_EXCEPTION) { + klass->set_fail_over_verified(); + } } if (exception_name != nullptr) { exception_message = split_verifier.exception_message(); diff --git a/src/hotspot/share/oops/instanceKlass.cpp b/src/hotspot/share/oops/instanceKlass.cpp index fd1cf1b1457..8161516421e 100644 --- a/src/hotspot/share/oops/instanceKlass.cpp +++ b/src/hotspot/share/oops/instanceKlass.cpp @@ -2911,6 +2911,16 @@ bool InstanceKlass::can_be_verified_at_dumptime() const { // SystemDictionaryShared::check_verification_constraints() will not work for this class. return false; } + + if (CDSConfig::is_dumping_final_static_archive() && fail_over_verified()) { + // This is a class with version >50 but was verified with the old verifier in the training run, + // which had -XX:+AOTClassLinking. However, we are now in the assembly run with -XX:-AOTClassLinking. + // As SystemDictionaryShared::check_verification_constraints() does not support this case, + // we must exclude this class. + assert(!CDSConfig::is_dumping_aot_linked_classes(), "must be"); + return false; + } + if (super() != nullptr && !super()->can_be_verified_at_dumptime()) { return false; } diff --git a/src/hotspot/share/oops/instanceKlass.hpp b/src/hotspot/share/oops/instanceKlass.hpp index 41f176330fa..721a50c73c6 100644 --- a/src/hotspot/share/oops/instanceKlass.hpp +++ b/src/hotspot/share/oops/instanceKlass.hpp @@ -339,6 +339,9 @@ class InstanceKlass: public Klass { bool has_localvariable_table() const { return _misc_flags.has_localvariable_table(); } void set_has_localvariable_table(bool b) { _misc_flags.set_has_localvariable_table(b); } + bool fail_over_verified() const { return _misc_flags.fail_over_verified(); } + void set_fail_over_verified() { _misc_flags.set_fail_over_verified(true); } + // field sizes int nonstatic_field_size() const { return _nonstatic_field_size; } void set_nonstatic_field_size(int size) { _nonstatic_field_size = size; } diff --git a/src/hotspot/share/oops/instanceKlassFlags.hpp b/src/hotspot/share/oops/instanceKlassFlags.hpp index 1709c02a171..84041a3a0e7 100644 --- a/src/hotspot/share/oops/instanceKlassFlags.hpp +++ b/src/hotspot/share/oops/instanceKlassFlags.hpp @@ -54,6 +54,7 @@ class InstanceKlassFlags { flag(has_miranda_methods , 1 << 12) /* True if this class has miranda methods in it's vtable */ \ flag(has_final_method , 1 << 13) /* True if klass has final method */ \ flag(trust_final_fields , 1 << 14) /* All instance final fields in this class should be trusted */ \ + flag(fail_over_verified , 1 << 15) /* class failed split verification but passed inference verification */ \ /* end of list */ #define IK_FLAGS_ENUM_NAME(name, value) _misc_##name = value, diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/VerifierFailOver.java b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/VerifierFailOver.java index 8107e3fe0a3..82a5ccb2142 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotCache/VerifierFailOver.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotCache/VerifierFailOver.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,6 +24,7 @@ /* * @test + * @bug 8365575 * @summary Sanity test for AOTCache * @requires vm.cds.supports.aot.class.linking * @library /test/lib @@ -33,24 +34,72 @@ * @run driver VerifierFailOver */ +import jdk.test.lib.cds.CDSAppTester; import jdk.test.lib.cds.SimpleCDSAppTester; +import jdk.test.lib.helpers.ClassFileInstaller; import jdk.test.lib.process.OutputAnalyzer; public class VerifierFailOver { + + static final String mainClass = VerifierFailOverApp.class.getName(); + static final String appJar = ClassFileInstaller.getJarPath("app.jar"); + public static void main(String... args) throws Exception { SimpleCDSAppTester.of("VerifierFailOver") .addVmArgs("-Xlog:aot,aot+class=debug") .classpath("app.jar") .appCommandLine("VerifierFailOverApp") .setTrainingChecker((OutputAnalyzer out) -> { - out.shouldContain("Skipping VerifierFailOver_Helper: Verified with old verifier"); + out.shouldMatch("class.* klasses.* VerifierFailOver_Helper"); }) .setAssemblyChecker((OutputAnalyzer out) -> { - // classes verified with fail-over mode should not be cached. - out.shouldMatch("class.* klasses.* VerifierFailOverApp"); - out.shouldNotMatch("class.* klasses.* VerifierFailOver_Helper"); + // Classes verified with fail-over can be cached if AOTClassLinking is on + out.shouldMatch("class.* klasses.* VerifierFailOverApp aot-linked"); + out.shouldMatch("class.* klasses.* VerifierFailOver_Helper aot-linked"); }) .runAOTWorkflow(); + + + // When running an assembly run without AOTClassLinking, any classes verified with + // fail-over need to be excluded. + Tester t = new Tester(); + t.runAOTWorkflow(); + } + + static class Tester extends CDSAppTester { + public Tester() { + super(mainClass); + } + + @Override + public String classpath(RunMode runMode) { + return appJar; + } + + @Override + public String[] vmArgs(RunMode runMode) { + if (runMode == RunMode.ASSEMBLY) { + return new String[] {"-XX:-AOTClassLinking", "-Xlog:aot,aot+class=debug"}; + } else { + return new String[] { "-Xlog:aot,aot+class=debug" }; + } + } + + @Override + public String[] appCommandLine(RunMode runMode) { + return new String[] { mainClass }; + } + + @Override + public void checkExecution(OutputAnalyzer out, RunMode runMode) throws Exception { + if (runMode == RunMode.TRAINING) { + out.shouldMatch("class.* klasses.* VerifierFailOver_Helper"); + } else if (runMode == RunMode.ASSEMBLY) { + out.shouldContain("Skipping VerifierFailOver_Helper: Old class has been linked"); + out.shouldMatch("class.* klasses.* VerifierFailOverApp"); + out.shouldNotMatch("class.* klasses.* VerifierFailOver_Helper"); + } + } } } From 79568c915ae8c348b0fed271b949d21247770d13 Mon Sep 17 00:00:00 2001 From: Coleen Phillimore Date: Wed, 15 Jul 2026 15:02:05 +0000 Subject: [PATCH 162/305] 8384557: Allow configuration of the JVM's temporary directory on Linux Co-authored-by: Kevin Walls Co-authored-by: David Holmes Reviewed-by: dholmes, sspitsyn, jsjolen, kevinw --- src/hotspot/os/linux/globals_linux.hpp | 6 +- src/hotspot/os/linux/os_linux.cpp | 45 ++- src/hotspot/os/posix/attachListener_posix.cpp | 9 +- src/hotspot/os/posix/perfMemory_posix.cpp | 13 +- src/hotspot/share/runtime/arguments.cpp | 1 + src/hotspot/share/runtime/os.hpp | 6 + src/java.base/share/man/java.md | 17 ++ .../sun/tools/attach/VirtualMachineImpl.java | 16 +- .../native/libattach/VirtualMachineImpl.c | 41 ++- .../sun/jvmstat/PlatformSupportImpl.java | 51 ++-- src/jdk.jcmd/share/man/jcmd.md | 10 +- src/jdk.jcmd/share/man/jinfo.md | 4 + src/jdk.jcmd/share/man/jmap.md | 7 +- src/jdk.jcmd/share/man/jps.md | 5 +- src/jdk.jcmd/share/man/jstack.md | 5 + src/jdk.jcmd/share/man/jstat.md | 7 +- .../com/sun/tools/attach/JvmTempDirTest.java | 258 ++++++++++++++++++ .../jdk/com/sun/tools/attach/TempDirTest.java | 6 +- test/jdk/sun/tools/jps/TestJpsTempDir.java | 72 +++++ 19 files changed, 532 insertions(+), 47 deletions(-) create mode 100644 test/jdk/com/sun/tools/attach/JvmTempDirTest.java create mode 100644 test/jdk/sun/tools/jps/TestJpsTempDir.java diff --git a/src/hotspot/os/linux/globals_linux.hpp b/src/hotspot/os/linux/globals_linux.hpp index 90e1e5e5f3f..fa7b5a63c6c 100644 --- a/src/hotspot/os/linux/globals_linux.hpp +++ b/src/hotspot/os/linux/globals_linux.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -94,6 +94,10 @@ " 0 = no timeout (default)") \ range(0,1000000) \ \ + product(ccstr, AltTempDir, nullptr, \ + "Alternate temporary directory for JVM files.") \ + \ + // end of RUNTIME_OS_FLAGS // diff --git a/src/hotspot/os/linux/os_linux.cpp b/src/hotspot/os/linux/os_linux.cpp index aad18edf2a6..12a4ea2bda4 100644 --- a/src/hotspot/os/linux/os_linux.cpp +++ b/src/hotspot/os/linux/os_linux.cpp @@ -112,6 +112,7 @@ # include # include # include +# include # include # include # include @@ -1547,11 +1548,47 @@ int os::current_process_id() { return ::getpid(); } -// DLL functions +static bool is_writable_directory(const char* name) { + struct stat mystat; + int ret_val = stat(name, &mystat); + return (ret_val != -1 && S_ISDIR(mystat.st_mode) > 0 && access(name, R_OK|W_OK|X_OK) == 0); +} -// This must be hard coded because it's the system's temporary -// directory not the java application's temp directory, ala java.io.tmpdir. -const char* os::get_temp_directory() { return "/tmp"; } +// Check that a given alternate temporary directory name specifies an absolute path and is an existing, writable +// directory. + +// If it is not an absolute path, revert back to hardcoded /tmp. If the directory is non existant or not +// writable give a warning but use AltTempDir. In the latter case, we may be connecting to a process that is +// inside a container. +// +// Since the attach mechanism uses the socket name length, this limits the length of the alternate +// temporary directory name. We don't check that here since the temporary directory is +// used for many things. The perfData and attach code will check it. + +void os::pd_check_temp_directory() { + if (AltTempDir != nullptr && AltTempDir[0] != '\0') { + if (AltTempDir[0] != '/') { + log_warning(os)("Warning: AltTempDir is ignored because it must be an absolute pathname"); + AltTempDir = nullptr; + } else { + if (!is_writable_directory(AltTempDir)) { + // This is only a warning and still uses AltTempDir, which is needed to attach to a + // containerized process from the host. + log_warning(os)("Warning: AltTempDir is not an existing or writable directory"); + } + } + } else { + if (!is_writable_directory("/tmp")) { + log_warning(os)("Warning: /tmp is not writable. Consider using -XX:AltTempDir=/
    to set a writable temp directory"); + } + AltTempDir = nullptr; // avoid checking AltTempDir[0] again. + } +} + +const char* os::get_temp_directory() { + // AltTempDir is already checked. + return AltTempDir != nullptr ? AltTempDir : "/tmp"; +} // check if addr is inside libjvm.so bool os::address_is_in_vm(address addr) { diff --git a/src/hotspot/os/posix/attachListener_posix.cpp b/src/hotspot/os/posix/attachListener_posix.cpp index a7cf1703128..152fd6140e0 100644 --- a/src/hotspot/os/posix/attachListener_posix.cpp +++ b/src/hotspot/os/posix/attachListener_posix.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -201,6 +201,8 @@ int PosixAttachListener::init() { n = os::snprintf(initial_path, UNIX_PATH_MAX, "%s.tmp", path); } if (n >= (int)UNIX_PATH_MAX) { + log_warning(attach)("Failed to create temporary file for attach %s/.java_pid%d: file name is too long", + os::get_temp_directory(), os::current_process_id()); return -1; } @@ -346,8 +348,11 @@ void AttachListener::vm_start() { struct stat st; int ret; - os::snprintf_checked(fn, UNIX_PATH_MAX, "%s/.java_pid%d", + int n = os::snprintf(fn, UNIX_PATH_MAX, "%s/.java_pid%d", os::get_temp_directory(), os::current_process_id()); + if (n >= (int)UNIX_PATH_MAX) { + return; + } RESTARTABLE(::stat(fn, &st), ret); if (ret == 0) { diff --git a/src/hotspot/os/posix/perfMemory_posix.cpp b/src/hotspot/os/posix/perfMemory_posix.cpp index 300c86ffc47..aaeb33b6d9b 100644 --- a/src/hotspot/os/posix/perfMemory_posix.cpp +++ b/src/hotspot/os/posix/perfMemory_posix.cpp @@ -135,23 +135,25 @@ static void save_memory_to_file(char* addr, size_t size) { // return the user specific temporary directory name. // the caller is expected to free the allocated memory. // -#define TMP_BUFFER_LEN (4+22) static char* get_user_tmp_dir(const char* user, int vmid, int nspid) { char* tmpdir = (char *)os::get_temp_directory(); + char buffer[PATH_MAX] = {0}; #if defined(LINUX) // On linux, if containerized process, get dirname of // /proc/{vmid}/root/tmp/{PERFDATA_NAME_user} // otherwise /tmp/{PERFDATA_NAME_user} - char buffer[TMP_BUFFER_LEN]; - assert(strlen(tmpdir) == 4, "No longer using /tmp - update buffer size"); + // The /tmp directory can be overridden with AltTempDir. if (nspid != -1) { - jio_snprintf(buffer, TMP_BUFFER_LEN, "/proc/%d/root%s", vmid, tmpdir); + int val = os::snprintf(buffer, PATH_MAX, "/proc/%d/root%s", vmid, tmpdir); + if (val >= (int)PATH_MAX) { + log_warning(perf)("The temporary directory for perf data /proc/%d/root%s name is truncated", + vmid, tmpdir); + } tmpdir = buffer; } #endif #ifdef __APPLE__ - char buffer[PATH_MAX] = {0}; // Check if the current user is root and the target VM is running as non-root. // Otherwise the output of os::get_temp_directory() is used. // @@ -524,7 +526,6 @@ static char* get_user_name_slow(int vmid, int nspid, TRAPS) { char* tmpdirname = (char *)os::get_temp_directory(); #if defined(LINUX) char buffer[MAXPATHLEN + 1]; - assert(strlen(tmpdirname) == 4, "No longer using /tmp - update buffer size"); // On Linux, if nspid != -1, look in /proc/{vmid}/root/tmp for directories // containing nspid, otherwise just look for vmid in /tmp. diff --git a/src/hotspot/share/runtime/arguments.cpp b/src/hotspot/share/runtime/arguments.cpp index 269a8b39e6b..b08e71f559a 100644 --- a/src/hotspot/share/runtime/arguments.cpp +++ b/src/hotspot/share/runtime/arguments.cpp @@ -1703,6 +1703,7 @@ jint Arguments::parse_vm_init_args(GrowableArrayCHeap. and .attach_pid. It is important that this // location is the same for all processes, otherwise the tools // will not be able to find all Hotspot processes. - // Any changes to this needs to be synchronized with HotSpot. - private static final Path TMPDIR = Path.of("/tmp"); + // This calls a Hotspot native method to get a consistent temporary + // directory. + private static final String vmTemp = PlatformSupport.getTemporaryDirectory(); + private static final Path TMPDIR = Path.of(vmTemp); private static final Path PROC = Path.of("/proc"); private static final Path STATUS = Path.of("status"); - private static final Path ROOT_TMP = Path.of("root/tmp"); String socket_path; private OperationProperties props = new OperationProperties(VERSION_1); // updated in ctor @@ -86,6 +88,9 @@ public class VirtualMachineImpl extends HotSpotVirtualMachine { // Then we attempt to find the socket file again. final File socket_file = findSocketFile(pid, ns_pid); socket_path = socket_file.getPath(); + if (!validateSocketFileLength(socket_file.getPath())) { + throw new AttachNotSupportedException("Socket file path too long: " + socket_path); + } if (!socket_file.exists()) { // Keep canonical version of File, to delete, in case target process ends and /proc link has gone: File f = createAttachFile(pid, ns_pid).getCanonicalFile(); @@ -255,7 +260,8 @@ public class VirtualMachineImpl extends HotSpotVirtualMachine { } private String findTargetProcessTmpDirectory(long pid) throws IOException { - final var tmpOnProcPidRoot = PROC.resolve(Long.toString(pid)).resolve(ROOT_TMP); + final var tmpOnProcPidRoot = PROC.resolve(Long.toString(pid)).resolve("root") + .resolve(vmTemp.startsWith("/") ? vmTemp.substring(1) : vmTemp); /* We need to handle at least 4 different cases: * 1. Caller and target processes share PID namespace and root @@ -429,6 +435,8 @@ public class VirtualMachineImpl extends HotSpotVirtualMachine { static native void write(int fd, byte buf[], int off, int bufLen) throws IOException; + static native boolean validateSocketFileLength(String socketPath); + static { System.loadLibrary("attach"); } diff --git a/src/jdk.attach/linux/native/libattach/VirtualMachineImpl.c b/src/jdk.attach/linux/native/libattach/VirtualMachineImpl.c index fc9af901835..df4fc54cc96 100644 --- a/src/jdk.attach/linux/native/libattach/VirtualMachineImpl.c +++ b/src/jdk.attach/linux/native/libattach/VirtualMachineImpl.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -76,11 +76,15 @@ JNIEXPORT void JNICALL Java_sun_tools_attach_VirtualMachineImpl_connect memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_UNIX; - /* strncpy is safe because addr.sun_path was zero-initialized before. */ - strncpy(addr.sun_path, p, sizeof(addr.sun_path) - 1); + if (strlen(p) >= sizeof(addr.sun_path)) { + JNU_ThrowIOException(env, "Socket file path too long"); + } else { + /* strncpy is safe because addr.sun_path was zero-initialized before. */ + strncpy(addr.sun_path, p, sizeof(addr.sun_path) - 1); - if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) { - err = errno; + if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) { + err = errno; + } } if (isCopy) { @@ -256,3 +260,30 @@ JNIEXPORT void JNICALL Java_sun_tools_attach_VirtualMachineImpl_write } while (remaining > 0); } + +/* + * Class: sun_tools_attach_VirtualMachineImpl + * Method: validateSocketFileLength + * Signature: (Ljava/lang/String;)Z + */ +JNIEXPORT jboolean JNICALL Java_sun_tools_attach_VirtualMachineImpl_validateSocketFileLength + (JNIEnv *env, jclass cls, jstring path) +{ + jboolean isCopy; + const char* p = GetStringPlatformChars(env, path, &isCopy); + if (p == NULL) { + JNU_ThrowIOException(env, "Socket file path is null"); + return JNI_FALSE; + } + + size_t pathLength = strlen(p); + + if (isCopy) { + JNU_ReleaseStringPlatformChars(env, path, p); + } + + struct sockaddr_un addr; + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + return pathLength < sizeof(addr.sun_path); +} diff --git a/src/jdk.internal.jvmstat/linux/classes/sun/jvmstat/PlatformSupportImpl.java b/src/jdk.internal.jvmstat/linux/classes/sun/jvmstat/PlatformSupportImpl.java index d2c0fa29877..c0733e65a74 100644 --- a/src/jdk.internal.jvmstat/linux/classes/sun/jvmstat/PlatformSupportImpl.java +++ b/src/jdk.internal.jvmstat/linux/classes/sun/jvmstat/PlatformSupportImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -75,6 +75,7 @@ public class PlatformSupportImpl extends PlatformSupport { * It is important that this directory is well-known and the * same for all VM instances. It cannot be affected by configuration * variables such as java.io.tmpdir. + * It can be affected by VM option -XX:AltTempDir, however. * * Implementation Details: * @@ -170,8 +171,8 @@ public class PlatformSupportImpl extends PlatformSupport { /* - * Extract either the host PID or the NameSpace PID - * from a file path. + * Extract the VM ID (pid) from a file path, + * specifically the host pid for a container process. * * File path should be in 1 of these 2 forms: * @@ -179,6 +180,8 @@ public class PlatformSupportImpl extends PlatformSupport { * or * /tmp/hsperfdata_{user}/{pid} * + * (where /tmp may be substituted due to -XX:AltTempDir) + * * In either case we want to return {pid} and NOT {nspid} * * This function filters out host pids which do not have @@ -189,25 +192,39 @@ public class PlatformSupportImpl extends PlatformSupport { */ public int getLocalVmId(File file) throws NumberFormatException { String p = file.getAbsolutePath(); - String s[] = p.split("\\/"); + String procParts[] = p.split("\\/"); // "/proc/hostpid/root//hsperfdata_user/nsid" - // Determine if this file is from a container - if (s.length == 7 && s[1].equals("proc")) { - int hostpid = Integer.parseInt(s[2]); - int nspid = Integer.parseInt(s[6]); - if (nspid == hostpid || nspid == getNamespaceVmId(hostpid)) { - return hostpid; - } - else { - return -1; - } + int hostpid = -1; + int nspid = -1; + + // ["", "proc", "hostpid", "root", "tmpdir" .. "tmpdir", "hsperfdata_user", "nsid"] + if (procParts.length > 4 && procParts[1].equals("proc") && procParts[3].equals("root")) { + hostpid = Integer.parseInt(procParts[2]); } - else { - return Integer.parseInt(file.getName()); + + // Some invalid path. + if (procParts.length < 2) { + return -1; + } + + // Path at the end after tmp dir is: "hsperfdata_username/PID" + int end = procParts.length - 1; + if (!procParts[end-1].startsWith("hsperfdata_")) { + return -1; + } + if (hostpid == -1) { + hostpid = Integer.parseInt(procParts[end]); + } else { + nspid = Integer.parseInt(procParts[end]); + } + if (nspid == -1) { + return hostpid; + } else { + // We have both pids. + return nspid == getNamespaceVmId(hostpid) ? hostpid : -1; } } - /* * Return the inner most namespaced PID if there is one, * otherwise return the original PID. diff --git a/src/jdk.jcmd/share/man/jcmd.md b/src/jdk.jcmd/share/man/jcmd.md index 23dfa67d864..97e7385138b 100644 --- a/src/jdk.jcmd/share/man/jcmd.md +++ b/src/jdk.jcmd/share/man/jcmd.md @@ -1,5 +1,5 @@ --- -# Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2012, 2026, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -77,8 +77,12 @@ jcmd - send diagnostic command requests to a running Java Virtual Machine The `jcmd` utility is used to send diagnostic command requests to the JVM. It must be used on the same machine on which the JVM is running, and have the same -effective user and group identifiers that were used to launch the JVM. Each -diagnostic command has its own set of options and arguments. To display the description, +effective user and group identifiers that were used to launch the JVM. Both must +use the same temporary file location for communication; this is true by default +but also see the [`-XX:AltTempDir`](./java.html#-XX_AltTempDir) option that can be +set for the JVM. + +Each diagnostic command has its own set of options and arguments. To display the description, syntax, and a list of available options and arguments for a diagnostic command, use the name of the command as the argument. For example: diff --git a/src/jdk.jcmd/share/man/jinfo.md b/src/jdk.jcmd/share/man/jinfo.md index 8365c5af8a5..cc582aa9d5c 100644 --- a/src/jdk.jcmd/share/man/jinfo.md +++ b/src/jdk.jcmd/share/man/jinfo.md @@ -59,6 +59,10 @@ environment variable should contain the location of the `jvm.dll` that's used by the target process or the location from which the core dump file was produced. +If the target JVM is started with an alternate temporary file location, `jinfo` must +use the same temporary file location for communication; this is true by default +but see the [`-XX:AltTempDir`](./java.html#-XX_AltTempDir) option. + ## Options for the jinfo Command **Note:** diff --git a/src/jdk.jcmd/share/man/jmap.md b/src/jdk.jcmd/share/man/jmap.md index dd0be1b24ef..2fe766c25b5 100644 --- a/src/jdk.jcmd/share/man/jmap.md +++ b/src/jdk.jcmd/share/man/jmap.md @@ -1,5 +1,5 @@ --- -# Copyright (c) 2004, 2024, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -60,6 +60,11 @@ Debugging Tools for Windows must be installed to make these tools work. The that's used by the target process or the location from which the core dump file was produced. +If the target JVM is started with an alternate temporary file location, `jmap` must +use the same temporary file location for communication; this is true by default +but see the [`-XX:AltTempDir`](./java.html#-XX_AltTempDir) option. + + ## Options for the jmap Command [`-clstats`]{#option-clstats} *pid* diff --git a/src/jdk.jcmd/share/man/jps.md b/src/jdk.jcmd/share/man/jps.md index 2db93878801..cdd5e8e8b9d 100644 --- a/src/jdk.jcmd/share/man/jps.md +++ b/src/jdk.jcmd/share/man/jps.md @@ -1,5 +1,5 @@ --- -# Copyright (c) 2004, 2024, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -99,6 +99,9 @@ permissions granted to the principal running the command. The command lists only the JVMs for which the principal has access rights as determined by operating system-specific access control mechanisms. +The list of JVMs is also limited to those that use the same temporary file location as the `jps` +command. That is normally the case but see the [`-XX:AltTempDir`](./java.html#-XX_AltTempDir) option. + ## Host Identifier The host identifier, or `hostid`, is a string that indicates the target system. diff --git a/src/jdk.jcmd/share/man/jstack.md b/src/jdk.jcmd/share/man/jstack.md index 2e95abf36c4..b2cf02c6eb5 100644 --- a/src/jdk.jcmd/share/man/jstack.md +++ b/src/jdk.jcmd/share/man/jstack.md @@ -63,6 +63,11 @@ Debugging Tools for Windows must be installed so that these tools work. The is used by the target process, or the location from which the core dump file was produced. +If the target JVM is started with an alternate temporary file location, `jstack` must +use the same temporary file location for communication; this is true by default +but see the [`-XX:AltTempDir`](./java.html#-XX_AltTempDir) option. + + ## Options for the jstack Command `-l` diff --git a/src/jdk.jcmd/share/man/jstat.md b/src/jdk.jcmd/share/man/jstat.md index 624b675de76..4b686f73810 100644 --- a/src/jdk.jcmd/share/man/jstat.md +++ b/src/jdk.jcmd/share/man/jstat.md @@ -1,5 +1,5 @@ --- -# Copyright (c) 2004, 2024, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2004, 2026, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -85,6 +85,11 @@ statistical output. All options and their functionality are subject to change or removal in future releases. +If the target JVM is started with an alternate temporary file location, `jstat` must +use the same temporary file location for communication; this is true by default +but see the [`-XX:AltTempDir`](./java.html#-XX_AltTempDir) option. + + ## General Options If you specify one of the general options, then you can't specify any other diff --git a/test/jdk/com/sun/tools/attach/JvmTempDirTest.java b/test/jdk/com/sun/tools/attach/JvmTempDirTest.java new file mode 100644 index 00000000000..6729de149a6 --- /dev/null +++ b/test/jdk/com/sun/tools/attach/JvmTempDirTest.java @@ -0,0 +1,258 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import com.sun.tools.attach.*; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Properties; +import java.util.List; +import java.io.File; + +import jdk.test.lib.thread.ProcessThread; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +/* + * @test + * @bug 8384557 + * @summary Test to make sure attach and jvmstat work correctly when -XX:AltTempDir is set. + * + * @requires os.family == "linux" + * @library /test/lib + * @modules jdk.attach + * jdk.jartool/sun.tools.jar + * + * @run build Application RunnerUtil + * @run main/timeout=200 JvmTempDirTest + */ + +/* + * This test is similar to TempDirTest.java. The property java.io.tmpdir does not affect how + * jdk.attach works, but -XX:AltTempDir does. + * + * This test runs with an extra long timeout since it takes a really long time with -Xcomp + * when starting many processes. + */ + +import jdk.test.lib.util.FileUtils; + +public class JvmTempDirTest { + + private static long startTime; + + public static void main(String args[]) throws Throwable { + + startTime = System.currentTimeMillis(); + + Path clientTmpDir = Files.createTempDirectory(Path.of("/tmp"), "c"); + Path targetTmpDir = Files.createTempDirectory(Path.of("/tmp"), "t"); + + try { + // Run the test with all possible combinations of setting AltTempDir. + // Different setting will cause the attach mechanism to fail. + String notFound = "not found in VM list"; + runExperiment(null, null, true, null); + runExperiment(targetTmpDir, targetTmpDir, true, null); + runExperiment(clientTmpDir, clientTmpDir, true, null); + + runExperiment(clientTmpDir, null, false, notFound); + runExperiment(clientTmpDir, targetTmpDir, false, notFound); + runExperiment(null, targetTmpDir, false, notFound); + } finally { + FileUtils.deleteFileTreeWithRetry(clientTmpDir); + FileUtils.deleteFileTreeWithRetry(targetTmpDir); + } + + String name = String.valueOf('a').repeat(200); + Path veryLongDir = Files.createTempDirectory(Path.of("/tmp"), name); + try { + runExperiment(veryLongDir, veryLongDir, false, "Socket file path too long"); + } finally { + FileUtils.deleteFileTreeWithRetry(veryLongDir); + } + + // Test a directory with only proc in one part of the name. + Path procTempDir = Files.createTempDirectory(Path.of("/tmp"), "proc"); + Path procDir = Files.createDirectory(procTempDir.resolve("proc")); + try { + runExperiment(procDir, procDir, true, null); + } finally { + FileUtils.deleteFileTreeWithRetry(procDir); + FileUtils.deleteFileTreeWithRetry(procTempDir); + } + + Path hsperfDir = Files.createTempDirectory(Path.of("/tmp"), "hsperfdata_"); + try { + runExperiment(hsperfDir, hsperfDir, true, null); + } finally { + FileUtils.deleteFileTreeWithRetry(hsperfDir); + } + + // Create /tmp/tmp, and try to use /tmp/tmp/noexist + Path tmpDir = Files.createTempDirectory(Path.of("/tmp"), "tmp"); + try { + Path noExist = tmpDir.resolve("noexist"); + runNoExistTest(noExist); + } finally { + FileUtils.deleteFileTreeWithRetry(tmpDir); + } + + Path relativeDir = Files.createTempDirectory(Path.of("."), "a"); + try { + runRelativeTest(relativeDir); + } finally { + FileUtils.deleteFileTreeWithRetry(relativeDir); + } + } + + /* + * The actual test is in the nested class TestMain. + * The responsibility of this class is to: + * 1. Start the Application class in a separate process. + * 2. Find the pid and shutdown port of the running Application. + * 3. Launch the tests in nested class TestMain that will attach to the Application. + * 4. Shut down the Application. + */ + public static void runExperiment(Path clientTmpDir, Path targetTmpDir, boolean shouldPass, String message) throws Throwable { + + System.out.print("### Running tests with overridden tmpdir for"); + System.out.print(" client: " + (clientTmpDir == null ? "no" : "yes")); + System.out.print(" target: " + (targetTmpDir == null ? "no" : "yes")); + System.out.println(" ###"); + + long elapsedTime = (System.currentTimeMillis() - startTime) / 1000; + System.out.println("Started after " + elapsedTime + "s"); + + ProcessThread processThread = null; + try { + String[] tmpDirArg = null; + if (targetTmpDir != null) { + tmpDirArg = new String[] {"-XX:AltTempDir=" + targetTmpDir}; + } + processThread = RunnerUtil.startApplication(tmpDirArg); + launchTests(processThread.getPid(), clientTmpDir, shouldPass, message); + } catch (Throwable t) { + System.out.println("JvmTempDirTest got unexpected exception: " + t); + t.printStackTrace(); + throw t; + } finally { + // Make sure the Application process is stopped. + RunnerUtil.stopApplication(processThread); + } + + elapsedTime = (System.currentTimeMillis() - startTime) / 1000; + System.out.println("Completed after " + elapsedTime + "s"); + + } + + /** + * Runs the actual tests in nested class TestMain. + * The reason for running the tests in a separate process + * is that we need to modify the class path and + * the -XX:AltTempDir argument. + */ + private static void launchTests(long pid, Path clientTmpDir, boolean shouldPass, String message) throws Throwable { + + String classpath = + System.getProperty("test.class.path", ""); + + String[] tmpDirArg = null; + if (clientTmpDir != null) { + tmpDirArg = new String [] {"-XX:AltTempDir=" + clientTmpDir}; + } + + // Arguments : [-XX:AltTempDir=] -classpath cp JvmTempDirTest$TestMain pid + String[] args = RunnerUtil.concat( + tmpDirArg, + new String[] { + "-classpath", + classpath, + "JvmTempDirTest$TestMain", + Long.toString(pid) }); + OutputAnalyzer output = ProcessTools.executeTestJava(args); + if (shouldPass) { + output.shouldHaveExitValue(0); + } else { + output.shouldContain(message); + output.shouldNotHaveExitValue(0); + } + } + + /** + * This is the actual test. It will attach to the running Application + * and perform a number of basic attach tests. + */ + public static class TestMain { + public static void main(String args[]) throws Exception { + String pid = args[0]; + + // Test 1 - list method should list the target VM + System.out.println(" - Test: VirtualMachine.list"); + List l = VirtualMachine.list(); + boolean found = false; + for (VirtualMachineDescriptor vmd: l) { + if (vmd.id().equals(pid)) { + found = true; + break; + } + } + if (found) { + System.out.println(" - " + pid + " found."); + } else { + throw new RuntimeException(pid + " not found in VM list"); + } + + // Test 2 - try to attach and verify connection + + System.out.println(" - Attaching to application ..."); + VirtualMachine vm = VirtualMachine.attach(pid); + + System.out.println(" - Test: system properties in target VM"); + Properties props = vm.getSystemProperties(); + String value = props.getProperty("attach.test"); + if (value == null || !value.equals("true")) { + throw new RuntimeException("attach.test property not set"); + } + System.out.println(" - attach.test property set as expected"); + } + } + + private static void runNoExistTest(Path tmpDir) throws Throwable { + // Arguments : [-XX:AltTempDir=] -version + String[] args = new String[] { "-XX:AltTempDir=" + tmpDir, "-version" }; + OutputAnalyzer output = ProcessTools.executeTestJava(args); + output.shouldMatch("\\[warning\\]\\[os *\\] Warning: AltTempDir is not an existing or writable directory"); + // Still passes, it's just a warning. + output.shouldHaveExitValue(0); + } + + private static void runRelativeTest(Path tmpDir) throws Throwable { + // Arguments : [-XX:AltTempDir=] -version + String[] args = new String[] { "-XX:AltTempDir=" + tmpDir, "-version" }; + OutputAnalyzer output = ProcessTools.executeTestJava(args); + output.shouldMatch("\\[warning\\]\\[os *\\] Warning: AltTempDir is ignored because it must be an absolute pathname"); + // Still passes, it's just a warning. + output.shouldHaveExitValue(0); + } +} diff --git a/test/jdk/com/sun/tools/attach/TempDirTest.java b/test/jdk/com/sun/tools/attach/TempDirTest.java index e0552d15fce..14b65207da9 100644 --- a/test/jdk/com/sun/tools/attach/TempDirTest.java +++ b/test/jdk/com/sun/tools/attach/TempDirTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -64,7 +64,9 @@ public class TempDirTest { Path targetTmpDir = Files.createTempDirectory("TempDirTest-target"); targetTmpDir.toFile().deleteOnExit(); - // run the test with all possible combinations of setting java.io.tmpdir + // Run the test with all possible combinations of setting java.io.tmpdir. + // Note that the attach mechanism doesn't really use java.io.tmpdir, but this test verifies + // that different java.io.tmpdir settings for client and target don't break the attach mechanism. runExperiment(null, null); runExperiment(clientTmpDir, null); runExperiment(clientTmpDir, targetTmpDir); diff --git a/test/jdk/sun/tools/jps/TestJpsTempDir.java b/test/jdk/sun/tools/jps/TestJpsTempDir.java new file mode 100644 index 00000000000..b54fedad2b8 --- /dev/null +++ b/test/jdk/sun/tools/jps/TestJpsTempDir.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8384557 + * @summary Test to make sure jps works correctly when -XX:AltTempDir is set. + * @library /test/lib + * @requires os.family == "linux" + * @modules jdk.jartool/sun.tools.jar + * @build jdk.test.lib.apps.LingeredApp + * @run main/othervm TestJpsTempDir + */ + +// Test that jps finds hsperfdata file in -XX:AltTempDir. + +import jdk.test.lib.apps.LingeredApp; +import java.util.ArrayList; +import java.util.List; +import java.nio.file.Path; +import java.nio.file.Files; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.util.FileUtils; + +public class TestJpsTempDir { + + public static void main(java.lang.String[] unused) throws Exception { + Path clientTmpDir = Files.createTempDirectory(Path.of("/tmp"), "c"); + String tmpdirString = "-XX:AltTempDir=" + clientTmpDir.toString(); + + LingeredAppForJps app = new LingeredAppForJps(); + + try { + // Start LingeredApp with AltTempDir + List vmArgs = new ArrayList<>(List.of(JpsHelper.getVmArgs())); + vmArgs.add(tmpdirString); + LingeredApp.startApp(app, vmArgs.toArray(String[]::new)); + + // Pass to jps (adds -J) + List jpsArgs = new ArrayList<>(); + jpsArgs.add(tmpdirString); + + OutputAnalyzer output = JpsHelper.jps(jpsArgs, null); + output.shouldContain(app.getProcessName()); + output.shouldContain(Long.toString(app.getPid())); + output.shouldHaveExitValue(0); + } finally { + LingeredApp.stopApp(app); + FileUtils.deleteFileTreeWithRetry(clientTmpDir); + } + } +} From c6a068a5ee0fcd6719b85103ead1e3fee2b8b42b Mon Sep 17 00:00:00 2001 From: Naoto Sato Date: Wed, 15 Jul 2026 16:15:12 +0000 Subject: [PATCH 163/305] 8388183: Wrong example in documentation for String.toLowerCase() Reviewed-by: jlu, iris --- src/java.base/share/classes/java/lang/String.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/java.base/share/classes/java/lang/String.java b/src/java.base/share/classes/java/lang/String.java index 9f56ceb445a..e3d120c23f6 100644 --- a/src/java.base/share/classes/java/lang/String.java +++ b/src/java.base/share/classes/java/lang/String.java @@ -4054,7 +4054,7 @@ public final class String * (all) * * ΙΧΘΥΣ - * ιχθυσ + * ιχθυς * lowercased all chars in String * * From 96737cf5104d3ffc9a4c96e66e132d4b16a79c31 Mon Sep 17 00:00:00 2001 From: Justin Lu Date: Wed, 15 Jul 2026 16:18:00 +0000 Subject: [PATCH 164/305] 8387261: Locale.LanguageRange weight validation issues Reviewed-by: naoto --- .../share/classes/java/util/Locale.java | 28 +++++++++++-------- .../sun/util/locale/LocaleMatcher.java | 17 +++++------ .../java/util/Locale/LocaleMatchingTest.java | 4 ++- 3 files changed, 28 insertions(+), 21 deletions(-) diff --git a/src/java.base/share/classes/java/util/Locale.java b/src/java.base/share/classes/java/util/Locale.java index f727d301954..9cb1521ac62 100644 --- a/src/java.base/share/classes/java/util/Locale.java +++ b/src/java.base/share/classes/java/util/Locale.java @@ -3214,18 +3214,20 @@ public final class Locale implements Cloneable, Serializable { * * @param range a language range * @param weight a weight value between {@code MIN_WEIGHT} and - * {@code MAX_WEIGHT} + * {@code MAX_WEIGHT}, inclusive * @throws NullPointerException if the given {@code range} is * {@code null} * @throws IllegalArgumentException if the given {@code range} does not - * comply with the syntax of the language range mentioned in RFC 4647 - * or if the given {@code weight} is less than {@code MIN_WEIGHT} - * or greater than {@code MAX_WEIGHT} + * comply with the syntax of the language range mentioned in RFC 4647, + * or if the given {@code weight} is {@code Double.NaN}, less than {@code + * MIN_WEIGHT} or greater than {@code MAX_WEIGHT} */ public LanguageRange(String range, double weight) { Objects.requireNonNull(range); - if (weight < MIN_WEIGHT || weight > MAX_WEIGHT) { - throw new IllegalArgumentException("weight=" + weight); + if (weight < MIN_WEIGHT || weight > MAX_WEIGHT || Double.isNaN(weight)) { + throw new IllegalArgumentException( + "The weight " + weight + " must be between " + + MIN_WEIGHT + " and " + MAX_WEIGHT + ", inclusive."); } range = range.toLowerCase(Locale.ROOT); @@ -3311,9 +3313,9 @@ public final class Locale implements Cloneable, Serializable { * * * In a weighted list, each language range is given a weight value. - * The weight value is identical to the "quality value" in + * The weight value has the same numeric bounds as the "quality value" * RFC 2616, and it - * expresses how much the user prefers the language. A weight value is + * expresses how much the user prefers the language. A weight value is * specified after a corresponding language range followed by * {@code ";q="}, and the default weight value is {@code MAX_WEIGHT} * when it is omitted. @@ -3356,8 +3358,9 @@ public final class Locale implements Cloneable, Serializable { * included in the given {@code ranges} and their equivalent * language ranges if available. The list is modifiable. * @throws NullPointerException if {@code ranges} is null - * @throws IllegalArgumentException if a language range or a weight - * found in the given {@code ranges} is ill-formed + * @throws IllegalArgumentException if, in the given {@code ranges}, a + * language range is ill-formed, or a weight is out of range after + * string to double conversion by {@link Double#parseDouble(String)} * @spec https://www.rfc-editor.org/info/rfc2616 RFC 2616: Hypertext Transfer Protocol -- HTTP/1.1 */ public static List parse(String ranges) { @@ -3378,8 +3381,9 @@ public final class Locale implements Cloneable, Serializable { * @return a Language Priority List with customization. The list is * modifiable. * @throws NullPointerException if {@code ranges} is null - * @throws IllegalArgumentException if a language range or a weight - * found in the given {@code ranges} is ill-formed + * @throws IllegalArgumentException if, in the given {@code ranges}, a + * language range is ill-formed, or a weight is out of range after + * string to double conversion by {@link Double#parseDouble(String)} * @spec https://www.rfc-editor.org/info/rfc2616 RFC 2616: Hypertext Transfer Protocol -- HTTP/1.1 * @see #parse(String) * @see #mapEquivalents(List, Map) diff --git a/src/java.base/share/classes/sun/util/locale/LocaleMatcher.java b/src/java.base/share/classes/sun/util/locale/LocaleMatcher.java index bc5115e1ff1..5385a5598b6 100644 --- a/src/java.base/share/classes/sun/util/locale/LocaleMatcher.java +++ b/src/java.base/share/classes/sun/util/locale/LocaleMatcher.java @@ -467,17 +467,18 @@ public final class LocaleMatcher { try { w = Double.parseDouble(range.substring(index)); } - catch (Exception e) { - throw new IllegalArgumentException("weight=\"" + catch (NumberFormatException _) { + throw new IllegalArgumentException("The weight \"" + range.substring(index) - + "\" for language range \"" + r + "\""); + + "\" for language range \"" + r + "\"" + + " must be between " + MIN_WEIGHT + + " and " + MAX_WEIGHT + ", inclusive."); } - if (w < MIN_WEIGHT || w > MAX_WEIGHT) { - throw new IllegalArgumentException("weight=" + w - + " for language range \"" + r - + "\". It must be between " + MIN_WEIGHT - + " and " + MAX_WEIGHT + "."); + throw new IllegalArgumentException("The weight \"" + w + + "\" for language range \"" + r + "\"" + + " must be between " + MIN_WEIGHT + + " and " + MAX_WEIGHT + ", inclusive."); } } diff --git a/test/jdk/java/util/Locale/LocaleMatchingTest.java b/test/jdk/java/util/Locale/LocaleMatchingTest.java index c5d8a00d458..806229ca8eb 100644 --- a/test/jdk/java/util/Locale/LocaleMatchingTest.java +++ b/test/jdk/java/util/Locale/LocaleMatchingTest.java @@ -23,7 +23,7 @@ /* * @test - * @bug 7069824 8042360 8032842 8175539 8210443 8242010 8276302 8381644 + * @bug 7069824 8042360 8032842 8175539 8210443 8242010 8276302 8381644 8387261 * @summary Verify implementation for Locale matching. * @run junit/othervm LocaleMatchingTest */ @@ -93,6 +93,7 @@ public class LocaleMatchingTest { {"1996-de-Latn", MAX_WEIGHT}, // Testcase for 8042360 {"en-Latn-1234567890", MAX_WEIGHT}, + {"en", Double.NaN}, }; } @@ -146,6 +147,7 @@ public class LocaleMatchingTest { // Ranges {""}, {"ja;q=3"}, + {"en;q=NaN"} }; } From 41a188f1d128298cb155c40681395df83916c469 Mon Sep 17 00:00:00 2001 From: Aleksey Shipilev Date: Wed, 15 Jul 2026 16:39:12 +0000 Subject: [PATCH 165/305] 8387463: Shenandoah: Use direct oop_oop_iterate methods for known types in marking loops Reviewed-by: wkemper, xpeng, kdnilsen --- .../share/gc/shenandoah/shenandoahMark.cpp | 16 +++- .../share/gc/shenandoah/shenandoahMark.hpp | 10 +-- .../gc/shenandoah/shenandoahMark.inline.hpp | 80 ++++++++++++------- src/hotspot/share/oops/instanceRefKlass.hpp | 10 +++ 4 files changed, 80 insertions(+), 36 deletions(-) diff --git a/src/hotspot/share/gc/shenandoah/shenandoahMark.cpp b/src/hotspot/share/gc/shenandoah/shenandoahMark.cpp index fc508dddd84..9354ef25f3d 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahMark.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahMark.cpp @@ -73,11 +73,19 @@ void ShenandoahMark::mark_loop_prework(uint w, TaskTerminator *t, StringDedup::R if (update_refs) { using Closure = ShenandoahMarkUpdateRefsClosure; Closure cl(q, rp, old_q); - mark_loop_work(&cl, ld, w, t, req); + if (UseCompressedOops) { + mark_loop_work(&cl, ld, w, t, req); + } else { + mark_loop_work(&cl, ld, w, t, req); + } } else { using Closure = ShenandoahMarkRefsClosure; Closure cl(q, rp, old_q); - mark_loop_work(&cl, ld, w, t, req); + if (UseCompressedOops) { + mark_loop_work(&cl, ld, w, t, req); + } else { + mark_loop_work(&cl, ld, w, t, req); + } } heap->flush_liveness_cache(w); @@ -154,7 +162,7 @@ void ShenandoahMark::mark_drain_extra_queues(ShenandoahObjToScanQueueSet* queues } } -template +template void ShenandoahMark::mark_loop_work(T* cl, ShenandoahLiveData* live_data, uint worker_id, TaskTerminator *terminator, StringDedup::Requests* const req) { uintx stride = ShenandoahMarkLoopStride; @@ -182,7 +190,7 @@ void ShenandoahMark::mark_loop_work(T* cl, ShenandoahLiveData* live_data, uint w for (uint i = 0; i < stride; i++) { if (q->pop(t) || queues->steal(worker_id, t)) { - do_task(q, cl, live_data, req, &t, worker_id); + do_task(q, cl, live_data, req, &t, worker_id); work++; } else { break; diff --git a/src/hotspot/share/gc/shenandoah/shenandoahMark.hpp b/src/hotspot/share/gc/shenandoah/shenandoahMark.hpp index 69d792d0277..a2c363b2129 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahMark.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahMark.hpp @@ -72,17 +72,17 @@ public: private: // ---------- Marking loop and tasks - template + template ALWAYSINLINE static void do_task(ShenandoahObjToScanQueue* q, T* cl, ShenandoahLiveData* live_data, StringDedup::Requests* const req, ShenandoahMarkTask* task, uint worker_id); - template + template ALWAYSINLINE static void do_chunked_array_start(ShenandoahObjToScanQueue* q, T* cl, oop array, Klass* klass, bool weak); - template + template ALWAYSINLINE - static void do_chunked_array(ShenandoahObjToScanQueue* q, T* cl, oop array, int chunk, int pow, bool weak); + static void do_chunked_array(ShenandoahObjToScanQueue* q, T* cl, oop array, Klass* klass, int chunk, int pow, bool weak); template ALWAYSINLINE @@ -105,7 +105,7 @@ private: template void mark_loop_prework(uint worker_id, TaskTerminator *terminator, StringDedup::Requests* const req, bool update_refs); - template + template NOINLINE // Main hot loop, start inlining from here void mark_loop_work(T* cl, ShenandoahLiveData* live_data, uint worker_id, TaskTerminator *t, StringDedup::Requests* const req); diff --git a/src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp index 8a7ce7ea831..45cec71935b 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahMark.inline.hpp @@ -47,7 +47,7 @@ #include "utilities/devirtualizer.inline.hpp" #include "utilities/powerOfTwo.hpp" -template +template void ShenandoahMark::do_task(ShenandoahObjToScanQueue* q, T* cl, ShenandoahLiveData* live_data, StringDedup::Requests* const req, ShenandoahMarkTask* task, uint worker_id) { oop obj = task->obj(); @@ -55,32 +55,58 @@ void ShenandoahMark::do_task(ShenandoahObjToScanQueue* q, T* cl, ShenandoahLiveD shenandoah_assert_marked(nullptr, obj); shenandoah_assert_not_in_cset_except(nullptr, obj, ShenandoahHeap::heap()->cancelled_gc()); + Klass* klass = obj->klass(); + // Are we in weak subgraph scan? bool weak = task->is_weak(); cl->set_weak(weak); if (task->is_not_chunked()) { - Klass* klass = obj->klass(); - if (klass->is_instance_klass()) { - // Case 1: Normal oop, process as usual. - if (STRING_DEDUP && (klass == vmClasses::String_klass())) { - dedup_string(obj, req); + // Dispatch based on object type. The case order does not seem to affect performance, + // so it matches the enum order for consistency. + switch (klass->kind()) { + case Klass::InstanceKlassKind: { + // Regular instance. + if (STRING_DEDUP && (klass == vmClasses::String_klass())) { + dedup_string(obj, req); + } + InstanceKlass::cast(klass)->oop_oop_iterate(obj, cl); + break; } - if (klass->is_stack_chunk_instance_klass()) { - // Loom doesn't support mixing of weak marking and strong marking of stack chunks. + case Klass::InstanceRefKlassKind: { + // (Weak) reference instance. + InstanceRefKlass::cast(klass)->oop_oop_iterate(obj, cl); + break; + } + case Klass::InstanceMirrorKlassKind: + case Klass::InstanceClassLoaderKlassKind: { + // Remaining rare classes, dispatch generically. + obj->oop_iterate(cl); + break; + } + case Klass::InstanceStackChunkKlassKind: { + // Stack chunk. Loom doesn't support mixing of weak marking and strong marking + // of stack chunks, upgrade to strong right away. cl->set_weak(false); + InstanceStackChunkKlass::cast(klass)->oop_oop_iterate(obj, cl); + break; + } + case Klass::TypeArrayKlassKind: { + // Primitive array. Do nothing, no oops there. We use the same + // performance tweak TypeArrayKlass::oop_oop_iterate_impl is using: + // We skip iterating over the klass pointer since we know that + // Universe::TypeArrayKlass never moves. + break; + } + case Klass::ObjArrayKlassKind: { + // Object array and no chunk is set. Must be the first + // time we visit it, start the chunked processing. + do_chunked_array_start(q, cl, obj, klass, weak); + break; + } + default: { + fatal("Unknown klass kind: %d", klass->kind()); } - obj->oop_iterate(cl); - } else if (klass->is_objArray_klass()) { - // Case 2: Object array instance and no chunk is set. Must be the first - // time we visit it, start the chunked processing. - do_chunked_array_start(q, cl, obj, klass, weak); - } else { - // Case 3: Primitive array. Do nothing, no oops there. We use the same - // performance tweak TypeArrayKlass::oop_oop_iterate_impl is using: - // We skip iterating over the klass pointer since we know that - // Universe::TypeArrayKlass never moves. - assert(klass->is_typeArray_klass(), "should be type array"); } // Count liveness the last: push the outstanding work to the queues first // Avoid double-counting objects that are visited twice due to upgrade @@ -89,8 +115,8 @@ void ShenandoahMark::do_task(ShenandoahObjToScanQueue* q, T* cl, ShenandoahLiveD count_liveness(live_data, obj, klass, worker_id); } } else { - // Case 4: Array chunk, has sensible chunk id. Process it. - do_chunked_array(q, cl, obj, task->chunk(), task->pow(), weak); + // Object array chunk. Process it. + do_chunked_array(q, cl, obj, klass, task->chunk(), task->pow(), weak); } } @@ -154,7 +180,7 @@ void ShenandoahMark::count_liveness(ShenandoahLiveData* live_data, oop obj, Klas } } -template +template void ShenandoahMark::do_chunked_array_start(ShenandoahObjToScanQueue* q, T* cl, oop obj, Klass* klass, bool weak) { assert(obj->is_objArray(), "expect object array"); objArrayOop array = objArrayOop(obj); @@ -167,7 +193,7 @@ void ShenandoahMark::do_chunked_array_start(ShenandoahObjToScanQueue* q, T* cl, if (len <= (int) ObjArrayMarkingStride*2) { // A few slices only, process directly - array->oop_iterate_elements_range(cl, 0, len); + ObjArrayKlass::cast(klass)->oop_oop_iterate_elements_range(array, cl, 0, len); } else { int bits = log2i_graceful(len); // Compensate for non-power-of-two arrays, cover the array in excess: @@ -216,13 +242,13 @@ void ShenandoahMark::do_chunked_array_start(ShenandoahObjToScanQueue* q, T* cl, // Process the irregular tail, if present int from = last_idx; if (from < len) { - array->oop_iterate_elements_range(cl, from, len); + ObjArrayKlass::cast(klass)->oop_oop_iterate_elements_range(array, cl, from, len); } } } -template -void ShenandoahMark::do_chunked_array(ShenandoahObjToScanQueue* q, T* cl, oop obj, int chunk, int pow, bool weak) { +template +void ShenandoahMark::do_chunked_array(ShenandoahObjToScanQueue* q, T* cl, oop obj, Klass* klass, int chunk, int pow, bool weak) { assert(obj->is_objArray(), "expect object array"); objArrayOop array = objArrayOop(obj); @@ -246,7 +272,7 @@ void ShenandoahMark::do_chunked_array(ShenandoahObjToScanQueue* q, T* cl, oop ob assert (0 < to && to <= len, "to is sane: %d/%d", to, len); #endif - array->oop_iterate_elements_range(cl, from, to); + ObjArrayKlass::cast(klass)->oop_oop_iterate_elements_range(array, cl, from, to); } template diff --git a/src/hotspot/share/oops/instanceRefKlass.hpp b/src/hotspot/share/oops/instanceRefKlass.hpp index fc219d06739..de7ec6fce9a 100644 --- a/src/hotspot/share/oops/instanceRefKlass.hpp +++ b/src/hotspot/share/oops/instanceRefKlass.hpp @@ -58,6 +58,16 @@ class InstanceRefKlass: public InstanceKlass { public: InstanceRefKlass(); + static InstanceRefKlass* cast(Klass* k) { + return const_cast(cast(const_cast(k))); + } + + static const InstanceRefKlass* cast(const Klass* k) { + assert(k != nullptr, "k should not be null"); + assert(k->is_reference_instance_klass(), "cast to InstanceRefKlass"); + return static_cast(k); + } + // Oop fields (and metadata) iterators // // The InstanceRefKlass iterators also support reference processing. From c4a8cb23496cc6851ca71de2e2b15ec09f5cb742 Mon Sep 17 00:00:00 2001 From: Chris Plummer Date: Wed, 15 Jul 2026 17:46:57 +0000 Subject: [PATCH 166/305] 8387640: vmTestbase/nsk/jdi/EventRequestManager/threadStartRequests/thrstartreq001/TestDescription.java fails intermittently Reviewed-by: sspitsyn, lmesnik --- .../threadStartRequests/thrstartreq001.java | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/test/hotspot/jtreg/vmTestbase/nsk/jdi/EventRequestManager/threadStartRequests/thrstartreq001.java b/test/hotspot/jtreg/vmTestbase/nsk/jdi/EventRequestManager/threadStartRequests/thrstartreq001.java index 7866547177f..39dc949c233 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/jdi/EventRequestManager/threadStartRequests/thrstartreq001.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/jdi/EventRequestManager/threadStartRequests/thrstartreq001.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -201,7 +201,7 @@ public class thrstartreq001 { public void run() { try { - do { + while (true) { EventSet eventSet = vm.eventQueue().remove(1000); if (eventSet != null) { // there is not a timeout EventIterator it = eventSet.eventIterator(); @@ -219,11 +219,15 @@ public class thrstartreq001 { log.display("EventListener: following JDI event occured: " + event.toString()); } - if (isConnected) { - eventSet.resume(); - } + eventSet.resume(); + // Even if isConnected has been set false, we need to continue consuming + // events until there are no more. So do a continue here rather than + // allowing continuing to be conditional on isConnected below. + continue; } - } while (isConnected); + if (!isConnected) + break; + } } catch (InterruptedException e) { tot_res = FAILED; log.complain("FAILURE in EventListener: caught unexpected " From 2659bfe35598296f9ba1b74b87e9e34c5f229ec7 Mon Sep 17 00:00:00 2001 From: Chen Liang Date: Wed, 15 Jul 2026 19:16:54 +0000 Subject: [PATCH 167/305] 8388347: Remove enablePreview from TestEnableNativeAccessJarManifest Reviewed-by: jpai, jvernee --- .../enablenativeaccess/TestEnableNativeAccessJarManifest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/jdk/java/foreign/enablenativeaccess/TestEnableNativeAccessJarManifest.java b/test/jdk/java/foreign/enablenativeaccess/TestEnableNativeAccessJarManifest.java index 0ac7ea474a4..3522921bdd3 100644 --- a/test/jdk/java/foreign/enablenativeaccess/TestEnableNativeAccessJarManifest.java +++ b/test/jdk/java/foreign/enablenativeaccess/TestEnableNativeAccessJarManifest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2023, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,7 +29,6 @@ * @requires jdk.foreign.linker != "UNSUPPORTED" * @requires !vm.musl * - * @enablePreview * @build TestEnableNativeAccessJarManifest * panama_module/* * org.openjdk.foreigntest.unnamed.PanamaMainUnnamedModule From 399317d393869e9da54fccf55af71450c2aa36be Mon Sep 17 00:00:00 2001 From: Vladimir Ivanov Date: Wed, 15 Jul 2026 21:21:29 +0000 Subject: [PATCH 168/305] 8388357: ProblemList compiler/vectorapi/VectorStoreMaskIdentityTest.java Reviewed-by: liach, kvn --- test/hotspot/jtreg/ProblemList.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/hotspot/jtreg/ProblemList.txt b/test/hotspot/jtreg/ProblemList.txt index 0a98477be69..71e626e5b39 100644 --- a/test/hotspot/jtreg/ProblemList.txt +++ b/test/hotspot/jtreg/ProblemList.txt @@ -69,6 +69,8 @@ compiler/c2/aarch64/TestStaticCallStub.java 8359963 generic-aarch64 compiler/escapeAnalysis/TestBCEscapeAnalyzerOverflow.java 8387392 windows-aarch64 +compiler/vectorapi/VectorStoreMaskIdentityTest.java 8388281 generic-all + ############################################################################# # :hotspot_gc From bc03baed726bef0f7fde8d4de5f90b48a9cc26fa Mon Sep 17 00:00:00 2001 From: Ashutosh Mehra Date: Thu, 16 Jul 2026 00:48:42 +0000 Subject: [PATCH 169/305] 8387745: [aot] Several tests fail because a SoftReferenceKey cannot be archived Reviewed-by: iklam, kvn --- src/hotspot/share/cds/aotReferenceObjSupport.cpp | 10 ++++++++++ src/hotspot/share/classfile/vmSymbols.hpp | 1 + .../share/classes/sun/util/locale/BaseLocale.java | 6 ++++++ 3 files changed, 17 insertions(+) diff --git a/src/hotspot/share/cds/aotReferenceObjSupport.cpp b/src/hotspot/share/cds/aotReferenceObjSupport.cpp index 2d5fc8c7f21..62ed3a56b62 100644 --- a/src/hotspot/share/cds/aotReferenceObjSupport.cpp +++ b/src/hotspot/share/cds/aotReferenceObjSupport.cpp @@ -142,6 +142,16 @@ void AOTReferenceObjSupport::stabilize_cached_reference_objects(TRAPS) { vmSymbols::void_method_signature(), CHECK); } + { + TempNewSymbol method_name = SymbolTable::new_symbol("assemblySetup"); + JavaValue result(T_VOID); + Symbol* baseLocale_name = vmSymbols::sun_util_locale_BaseLocale(); + Klass* baseLocale_klass = SystemDictionary::resolve_or_fail(baseLocale_name, true, CHECK); + JavaCalls::call_static(&result, baseLocale_klass, + method_name, + vmSymbols::void_method_signature(), + CHECK); + } { Symbol* cds_name = vmSymbols::jdk_internal_misc_CDS(); diff --git a/src/hotspot/share/classfile/vmSymbols.hpp b/src/hotspot/share/classfile/vmSymbols.hpp index 0348fae28b0..4337020846f 100644 --- a/src/hotspot/share/classfile/vmSymbols.hpp +++ b/src/hotspot/share/classfile/vmSymbols.hpp @@ -731,6 +731,7 @@ class SerializeClosure; template(runtimeSetup, "runtimeSetup") \ template(toFileURL_name, "toFileURL") \ template(toFileURL_signature, "(Ljava/lang/String;)Ljava/net/URL;") \ + template(sun_util_locale_BaseLocale, "sun/util/locale/BaseLocale") \ \ /* jcmd Thread.dump_to_file */ \ template(jdk_internal_vm_ThreadDumper, "jdk/internal/vm/ThreadDumper") \ diff --git a/src/java.base/share/classes/sun/util/locale/BaseLocale.java b/src/java.base/share/classes/sun/util/locale/BaseLocale.java index 31078720ddc..295952e7896 100644 --- a/src/java.base/share/classes/sun/util/locale/BaseLocale.java +++ b/src/java.base/share/classes/sun/util/locale/BaseLocale.java @@ -275,4 +275,10 @@ public final class BaseLocale { } return h; } + + // This is called from C code, at the very end of Java code execution + // during the AOT cache assembly phase. + private static void assemblySetup() { + CACHE.get().prepareForAOTCache(); + } } From 820d28feb5043fefe353b40dae9862b6868e4f1c Mon Sep 17 00:00:00 2001 From: Ozan Cetin Date: Thu, 16 Jul 2026 10:22:01 +0000 Subject: [PATCH 170/305] 8370870: IGV: add simple regression tests for graph dumping Reviewed-by: chagedorn, shade --- .../compiler/igv/TestIdealGraphDump.java | 334 ++++++++++++++++++ 1 file changed, 334 insertions(+) create mode 100644 test/hotspot/jtreg/compiler/igv/TestIdealGraphDump.java diff --git a/test/hotspot/jtreg/compiler/igv/TestIdealGraphDump.java b/test/hotspot/jtreg/compiler/igv/TestIdealGraphDump.java new file mode 100644 index 00000000000..d950908eeaa --- /dev/null +++ b/test/hotspot/jtreg/compiler/igv/TestIdealGraphDump.java @@ -0,0 +1,334 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test TestIdealGraphDump + * @bug 8370870 + * @summary Verify that IGV graph dumping produces well-structured XML at different print levels + * @library /test/lib + * @requires vm.debug == true & vm.compiler2.enabled & vm.flagless + * @run driver ${test.main.class} + */ + +package compiler.igv; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +import jdk.test.lib.Asserts; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +public class TestIdealGraphDump { + + private static final String TEST_CLASS = TestMethods.class.getName(); + private static final String METHOD_COMPUTE = TEST_CLASS + "::compute"; + private static final String METHOD_BRANCH = TEST_CLASS + "::branchyMethod"; + + private static final Map dumpCache = new HashMap<>(); + + public static void main(String[] args) throws Exception { + testDisabled(); + testLevel0(); + testLevel1(); + testLevel2(); + testLevel3(); + testLevel4(); + testLevel5(); + testLevel6(); + testMonotonicallyIncreasingGraphCounts(); + testXmlWellFormedness(); + testMethodNameInGraph(); + testMultipleMethods(); + testIGVPrintLevelDirective(); + } + + private static void testDisabled() throws Exception { + Path xmlFile = getCachedDump(-1); + Asserts.assertTrue(Files.size(xmlFile) == 0, + "Level -1 (disabled) must produce an empty file"); + } + + private static void testLevel0() throws Exception { + Path xmlFile = getCachedDump(0); + Asserts.assertTrue(Files.size(xmlFile) == 0, + "Level 0 must produce an empty file (no system-wide dumps)"); + } + + private static void testLevel1() throws Exception { + String content = getCachedContent(1); + assertContainsPhase(content, "After Parsing", 1); + assertContainsPhase(content, "Before Matching", 1); + assertContainsPhase(content, "Final Code", 1); + assertNotContainsPhase(content, "PhaseCCP 1", 1); + } + + private static void testLevel2() throws Exception { + String content = getCachedContent(2); + assertContainsPhase(content, "After Parsing", 2); + assertContainsPhase(content, "Final Code", 2); + assertContainsPhase(content, "Iter GVN 1", 2); + assertContainsPhase(content, "PhaseCCP 1", 2); + assertNotContainsPhase(content, "Before Macro Expansion", 2); + } + + private static void testLevel3() throws Exception { + String content = getCachedContent(3); + assertContainsPhase(content, "Before Macro Expansion", 3); + assertNotContainsPhase(content, "Initial Liveness", 3); + } + + private static void testLevel4() throws Exception { + String content = getCachedContent(4); + assertContainsPhase(content, "Initial Liveness", 4); + assertNotContainsPhase(content, "After Iter GVN Step", 4); + } + + private static void testLevel5() throws Exception { + String content = getCachedContent(5); + assertContainsPhase(content, "After Iter GVN Step", 5); + assertNotContainsPhase(content, "Bytecode", 5); + } + + private static void testLevel6() throws Exception { + String content = getCachedContent(6); + Asserts.assertTrue(containsPhase(content, "Bytecode"), + "Level 6 must contain per-bytecode graphs (e.g., 'Bytecode 0: ...')"); + } + + private static void testMonotonicallyIncreasingGraphCounts() throws Exception { + int prevCount = 0; + for (int level = 1; level <= 6; level++) { + String content = getCachedContent(level); + int count = countGraphs(content); + Asserts.assertTrue(count >= prevCount, + "Level " + level + " (" + count + " graphs) must have at least as many as level " + + (level - 1) + " (" + prevCount + " graphs)"); + prevCount = count; + } + } + + private static void testXmlWellFormedness() throws Exception { + Path xmlFile = getCachedDump(2); + + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + DocumentBuilder builder = factory.newDocumentBuilder(); + try { + builder.parse(xmlFile.toFile()); + } catch (Exception e) { + Asserts.fail("IGV XML at level 2 is not well-formed: " + e.getMessage()); + } + + String content = getCachedContent(2); + Asserts.assertTrue(content.contains(""), "Must contain "); + Asserts.assertTrue(content.contains(""), "Must contain closing "); + Asserts.assertTrue(content.contains(""), "Must contain "); + Asserts.assertTrue(content.contains(""), "Must contain "); + Asserts.assertTrue(content.contains(""), "Must contain "); + Asserts.assertTrue(content.contains(" elements"); + Asserts.assertTrue(content.contains(""); + Asserts.assertTrue(content.contains(""), "Must contain "); + Asserts.assertTrue(content.contains(""), "Must contain "); + } + + private static void testMethodNameInGraph() throws Exception { + String content = getCachedContent(1); + Asserts.assertTrue(content.contains("TestMethods.compute"), + "Graph output must contain the compiled method name 'TestMethods.compute'"); + } + + private static void testMultipleMethods() throws Exception { + Path xmlFile = dumpMultipleMethods(1); + String content = Files.readString(xmlFile); + + Asserts.assertTrue(content.contains("TestMethods.compute"), + "Must contain graphs for 'compute' method"); + Asserts.assertTrue(content.contains("TestMethods.branchyMethod"), + "Must contain graphs for 'branchyMethod' method"); + + int computeFinalCode = countMethodPhase(content, "TestMethods.compute", "Final Code"); + int branchFinalCode = countMethodPhase(content, "TestMethods.branchyMethod", "Final Code"); + Asserts.assertEquals(computeFinalCode, 1, + "compute must emit exactly one 'Final Code' graph, got " + computeFinalCode); + Asserts.assertEquals(branchFinalCode, 1, + "branchyMethod must emit exactly one 'Final Code' graph, got " + branchFinalCode); + } + + private static void testIGVPrintLevelDirective() throws Exception { + Path xmlFile = Files.createTempFile("igv_directive_", ".xml"); + xmlFile.toFile().deleteOnExit(); + + List options = new ArrayList<>(); + options.add("-Xbatch"); + options.add("-XX:PrintIdealGraphLevel=0"); + options.add("-XX:PrintIdealGraphFile=" + xmlFile.toAbsolutePath()); + options.add("-XX:CompileCommand=IGVPrintLevel," + METHOD_COMPUTE + ",2"); + options.add(TEST_CLASS); + + OutputAnalyzer oa = ProcessTools.executeTestJava(options); + oa.shouldHaveExitValue(0); + oa.shouldNotContain("# A fatal error has been detected by the Java Runtime Environment"); + + String content = Files.readString(xmlFile); + Asserts.assertTrue(Files.size(xmlFile) > 0, + "Per-method IGVPrintLevel directive must produce output even with system level 0"); + Asserts.assertTrue(content.contains("TestMethods.compute"), + "Directive-based dump must contain the target method"); + Asserts.assertFalse(content.contains("TestMethods.branchyMethod"), + "Directive-based dump must NOT contain non-targeted method"); + assertContainsPhase(content, "After Parsing", 2); + } + + private static Path getCachedDump(int level) throws Exception { + if (!dumpCache.containsKey(level)) { + dumpCache.put(level, dumpAtLevel(level)); + } + return dumpCache.get(level); + } + + private static String getCachedContent(int level) throws Exception { + return Files.readString(getCachedDump(level)); + } + + private static Path dumpAtLevel(int level) throws Exception { + Path xmlFile = Files.createTempFile("igv_level" + level + "_", ".xml"); + xmlFile.toFile().deleteOnExit(); + + List options = new ArrayList<>(); + options.add("-Xbatch"); + options.add("-XX:PrintIdealGraphLevel=" + level); + options.add("-XX:PrintIdealGraphFile=" + xmlFile.toAbsolutePath()); + options.add("-XX:CompileCommand=compileonly," + METHOD_COMPUTE); + options.add(TEST_CLASS); + + OutputAnalyzer oa = ProcessTools.executeTestJava(options); + oa.shouldHaveExitValue(0); + oa.shouldNotContain("# A fatal error has been detected by the Java Runtime Environment"); + + return xmlFile; + } + + private static Path dumpMultipleMethods(int level) throws Exception { + Path xmlFile = Files.createTempFile("igv_multi_", ".xml"); + xmlFile.toFile().deleteOnExit(); + + List options = new ArrayList<>(); + options.add("-Xbatch"); + options.add("-XX:PrintIdealGraphLevel=" + level); + options.add("-XX:PrintIdealGraphFile=" + xmlFile.toAbsolutePath()); + options.add("-XX:CompileCommand=compileonly," + METHOD_COMPUTE); + options.add("-XX:CompileCommand=compileonly," + METHOD_BRANCH); + options.add(TEST_CLASS); + + OutputAnalyzer oa = ProcessTools.executeTestJava(options); + oa.shouldHaveExitValue(0); + oa.shouldNotContain("# A fatal error has been detected by the Java Runtime Environment"); + + return xmlFile; + } + + private static int countGraphs(String content) { + return countOccurrences(content, "" + phaseName + "<") || + content.contains("'" + phaseName); + } + + private static void assertContainsPhase(String content, String phaseName, int level) { + Asserts.assertTrue(containsPhase(content, phaseName), + "Level " + level + " must contain phase '" + phaseName + "'"); + } + + private static void assertNotContainsPhase(String content, String phaseName, int level) { + Asserts.assertFalse(containsPhase(content, phaseName), + "Level " + level + " must NOT contain phase '" + phaseName + "'"); + } + + private static int countMethodPhase(String content, String methodName, String phaseName) { + int count = 0; + int groupStart = 0; + while ((groupStart = content.indexOf("", groupStart)) != -1) { + int groupEnd = content.indexOf("", groupStart); + if (groupEnd == -1) { + break; + } + String group = content.substring(groupStart, groupEnd); + if (group.contains(methodName)) { + count += countOccurrences(group, ""); + } + groupStart = groupEnd; + } + return count; + } + + private static int countOccurrences(String str, String sub) { + int count = 0; + int idx = 0; + while ((idx = str.indexOf(sub, idx)) != -1) { + count++; + idx += sub.length(); + } + return count; + } + + public static class TestMethods { + public static void main(String[] args) { + int sum = 0; + for (int i = 0; i < 20_000; i++) { + sum += compute(i, i + 1); + sum += branchyMethod(i, i % 7); + } + System.out.println(sum); + } + + static int compute(int a, int b) { + int result = 0; + for (int i = 0; i < a % 10; i++) { + result += b * i; + } + return result; + } + + static int branchyMethod(int x, int y) { + if (x > y) { + return x * y + 1; + } else if (x == y) { + return x + y; + } else { + return y - x; + } + } + } +} From 80476532c0c5d4339128bd45dd85dc5cc70a6fe6 Mon Sep 17 00:00:00 2001 From: Daisuke Yamazaki Date: Thu, 16 Jul 2026 14:10:24 +0000 Subject: [PATCH 171/305] 8382269: keytool man page references to "JKS" need to be cleaned up Reviewed-by: mullan, hchao --- src/java.base/share/man/keytool.md | 70 ++++++++++++-------------- src/jdk.jartool/share/man/jarsigner.md | 18 ++----- 2 files changed, 36 insertions(+), 52 deletions(-) diff --git a/src/java.base/share/man/keytool.md b/src/java.base/share/man/keytool.md index 1d70bd2f5f8..faa2ff563a1 100644 --- a/src/java.base/share/man/keytool.md +++ b/src/java.base/share/man/keytool.md @@ -1191,14 +1191,14 @@ These options can appear for all commands operating on a keystore: [`-keystore`]{#option-keystore} *keystore* : The keystore location. - If the JKS `storetype` is used and a keystore file doesn't yet exist, then - certain `keytool` commands can result in a new keystore file being created. - For example, if `keytool -genkeypair` is called and the `-keystore` option - isn't specified, the default keystore file named `.keystore` is created in - the user's home directory if it doesn't already exist. Similarly, if the - `-keystore ks_file` option is specified but `ks_file` doesn't exist, then - it is created. For more information on the JKS `storetype`, see the - **KeyStore Implementation** section in **KeyStore aliases**. + If a keystore file doesn't yet exist, then certain `keytool` commands can + result in a new keystore file being created. For example, if + `keytool -genkeypair` is called and the `-keystore` option isn't specified, + the default keystore file named `.keystore` is created in the user's home + directory if it doesn't already exist. Similarly, if the `-keystore ks_file` + option is specified but `ks_file` doesn't exist, then it is created. For + more information on keystore types and implementations, see the + **KeyStore implementation** section in [Terms]. Note that the input stream from the `-keystore` option is passed to the `KeyStore.load` method. If `NONE` is specified as the URL, then a null @@ -1766,11 +1766,11 @@ keystore, then it prompts you for a password. If it detects alias duplication, then it asks you for a new alias, and you can specify a new alias or simply allow the `keytool` command to overwrite the existing one. -For example, import entries from a typical JKS type keystore `key.jks` into a -PKCS \#11 type hardware-based keystore, by entering the following command: +For example, import entries from a typical PKCS12 type keystore `key.p12` into +a PKCS \#11 type hardware-based keystore, by entering the following command: -> `keytool -importkeystore -srckeystore key.jks -destkeystore NONE - -srcstoretype JKS -deststoretype PKCS11 -srcstorepass` *password* +> `keytool -importkeystore -srckeystore key.p12 -destkeystore NONE + -srcstoretype PKCS12 -deststoretype PKCS11 -srcstorepass` *password* `-deststorepass` *password* The `importkeystore` command can also be used to import a single entry from a @@ -1780,8 +1780,8 @@ import. With the `-srcalias` option specified, you can also specify the destination alias name, protection password for a secret or private key, and the destination protection password you want as follows: -> `keytool -importkeystore -srckeystore key.jks -destkeystore NONE - -srcstoretype JKS -deststoretype PKCS11 -srcstorepass` *password* +> `keytool -importkeystore -srckeystore key.p12 -destkeystore NONE + -srcstoretype PKCS12 -deststoretype PKCS11 -srcstorepass` *password* `-deststorepass` *password* `-srcalias myprivatekey -destalias myoldprivatekey -srckeypass` *password* `-destkeypass` *password* `-noprompt` @@ -1800,22 +1800,22 @@ certificates for three entities: Ensure that you store all the certificates in the same keystore. ``` -keytool -genkeypair -keystore root.jks -alias root -ext bc:c -keyalg rsa -keytool -genkeypair -keystore ca.jks -alias ca -ext bc:c -keyalg rsa -keytool -genkeypair -keystore server.jks -alias server -keyalg rsa +keytool -genkeypair -keystore root.p12 -alias root -ext bc:c -keyalg rsa +keytool -genkeypair -keystore ca.p12 -alias ca -ext bc:c -keyalg rsa +keytool -genkeypair -keystore server.p12 -alias server -keyalg rsa -keytool -keystore root.jks -alias root -exportcert -rfc > root.pem +keytool -keystore root.p12 -alias root -exportcert -rfc > root.pem -keytool -storepass password -keystore ca.jks -certreq -alias ca | - keytool -storepass password -keystore root.jks +keytool -storepass password -keystore ca.p12 -certreq -alias ca | + keytool -storepass password -keystore root.p12 -gencert -alias root -ext BC=0 -rfc > ca.pem -keytool -keystore ca.jks -importcert -alias ca -file ca.pem +keytool -keystore ca.p12 -importcert -alias ca -file ca.pem -keytool -storepass password -keystore server.jks -certreq -alias server | - keytool -storepass password -keystore ca.jks -gencert -alias ca +keytool -storepass password -keystore server.p12 -certreq -alias server | + keytool -storepass password -keystore ca.p12 -gencert -alias ca -ext ku:c=dig,kE -rfc > server.pem cat root.pem ca.pem server.pem | - keytool -keystore server.jks -importcert -alias server + keytool -keystore server.p12 -importcert -alias server ``` @@ -1886,11 +1886,7 @@ Keystore implementation is a cross platform keystore based on the RSA PKCS12 Personal Information Exchange Syntax Standard. This standard is primarily meant for storing or transporting a user's private keys, certificates, and miscellaneous - secrets. There is another built-in implementation, provided by Oracle. It - implements the keystore as a file with a proprietary keystore type (format) - named `JKS`. It protects each private key with its individual password, and - also protects the integrity of the entire keystore with a (possibly - different) password. + secrets. Keystore implementations are provider-based. More specifically, the application interfaces supplied by `KeyStore` are implemented in terms of a @@ -1946,16 +1942,12 @@ Keystore implementation > `keystore.type=pkcs12` To have the tools utilize a keystore implementation other than the default, - you can change that line to specify a different keystore type. For example, - if you want to use the Oracle's `jks` keystore implementation, then change - the line to the following: - - > `keystore.type=jks` + you can change that line to specify a different keystore type. **Note:** - Case doesn't matter in keystore type designations. For example, `JKS` would - be considered the same as `jks`. + Case doesn't matter in keystore type designations. For example, `PKCS12` + would be considered the same as `pkcs12`. Certificate : A certificate (or public-key certificate) is a digitally signed statement @@ -2157,9 +2149,9 @@ cacerts Certificates File The `cacerts` file represents a system-wide keystore with CA certificates. System administrators can configure and manage that file with the `keytool` - command by specifying `jks` as the keystore type. The `cacerts` keystore - file ships with a default set of root CA certificates. For Linux, macOS, and - Windows, you can list the default certificates with the following command: + command. The `cacerts` keystore file ships with a default set of root CA + certificates. For Linux, macOS, and Windows, you can list the default + certificates with the following command: > `keytool -list -cacerts` diff --git a/src/jdk.jartool/share/man/jarsigner.md b/src/jdk.jartool/share/man/jarsigner.md index d128b9c11ff..b24382fdda5 100644 --- a/src/jdk.jartool/share/man/jarsigner.md +++ b/src/jdk.jartool/share/man/jarsigner.md @@ -1,5 +1,5 @@ --- -# Copyright (c) 1998, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 1998, 2026, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -181,11 +181,7 @@ Currently, there are two command-line tools that use keystore implementations The default keystore implementation is `PKCS12`. This is a cross platform keystore based on the RSA PKCS12 Personal Information Exchange Syntax Standard. This standard is primarily meant for storing or transporting a user's private -keys, certificates, and miscellaneous secrets. There is another built-in -implementation, provided by Oracle. It implements the keystore as a file with a -proprietary keystore type (format) named `JKS`. It protects each private key -with its individual password, and also protects the integrity of the entire -keystore with a (possibly different) password. +keys, certificates, and miscellaneous secrets. Keystore implementations are provider-based, which means the application interfaces supplied by the `KeyStore` class are implemented in terms of a @@ -237,15 +233,11 @@ specified by the following line in the security properties file: > `keystore.type=pkcs12` -Case doesn't matter in keystore type designations. For example, `JKS` is the -same as `jks`. +Case doesn't matter in keystore type designations. For example, `PKCS12` is the +same as `pkcs12`. To have the tools utilize a keystore implementation other than the default, you -can change that line to specify a different keystore type. For example, if you -want to use the Oracle's `jks` keystore implementation, then change the line to -the following: - -> `keystore.type=jks` +can change that line to specify a different keystore type. ## Supported Algorithms From 816fe33ea6136b1563cdbe0cffbd09da1f62f1c7 Mon Sep 17 00:00:00 2001 From: April Ivy Date: Thu, 16 Jul 2026 18:55:38 +0000 Subject: [PATCH 172/305] 8387991: Optimize execution of runtime/Thread/TestSpinPause.java Reviewed-by: dholmes, lmesnik --- .../jtreg/runtime/Thread/TestSpinPause.java | 93 +++++++++++++++---- 1 file changed, 73 insertions(+), 20 deletions(-) diff --git a/test/hotspot/jtreg/runtime/Thread/TestSpinPause.java b/test/hotspot/jtreg/runtime/Thread/TestSpinPause.java index 7226c8ed058..b939aceee95 100644 --- a/test/hotspot/jtreg/runtime/Thread/TestSpinPause.java +++ b/test/hotspot/jtreg/runtime/Thread/TestSpinPause.java @@ -21,38 +21,91 @@ * questions. */ -/** - * @test TestSpinPause - * @summary JVM runtime can use SpinPause function for synchronized statements. - * Check different implementations of JVM SpinPause don't crash JVM. +/* + * @test id=default + * @summary Check the default SpinPause implementation for synchronized statements. * @bug 8278241 * @library /test/lib - * * @requires os.arch=="aarch64" - * * @run main/othervm TestSpinPause - * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=none TestSpinPause - * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=nop TestSpinPause - * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=isb TestSpinPause - * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=yield TestSpinPause - * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=nop -XX:OnSpinWaitInstCount=10 TestSpinPause - * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=isb -XX:OnSpinWaitInstCount=3 TestSpinPause - * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=yield -XX:OnSpinWaitInstCount=3 TestSpinPause * @run main/othervm -Xint TestSpinPause - * @run main/othervm -Xint -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=none TestSpinPause - * @run main/othervm -Xint -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=nop TestSpinPause - * @run main/othervm -Xint -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=isb TestSpinPause - * @run main/othervm -Xint -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=yield TestSpinPause - * @run main/othervm -Xint -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=nop -XX:OnSpinWaitInstCount=10 TestSpinPause - * @run main/othervm -Xint -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=isb -XX:OnSpinWaitInstCount=3 TestSpinPause - * @run main/othervm -Xint -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=yield -XX:OnSpinWaitInstCount=3 TestSpinPause * @run main/othervm -Xcomp TestSpinPause + */ + +/* + * @test id=none + * @summary Check SpinPause for synchronized statements with OnSpinWaitInst=none. + * @bug 8278241 + * @library /test/lib + * @requires os.arch=="aarch64" + * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=none TestSpinPause + * @run main/othervm -Xint -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=none TestSpinPause * @run main/othervm -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=none TestSpinPause + */ + +/* + * @test id=nop + * @summary Check SpinPause for synchronized statements with OnSpinWaitInst=nop. + * @bug 8278241 + * @library /test/lib + * @requires os.arch=="aarch64" + * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=nop TestSpinPause + * @run main/othervm -Xint -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=nop TestSpinPause * @run main/othervm -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=nop TestSpinPause + */ + +/* + * @test id=isb + * @summary Check SpinPause for synchronized statements with OnSpinWaitInst=isb. + * @bug 8278241 + * @library /test/lib + * @requires os.arch=="aarch64" + * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=isb TestSpinPause + * @run main/othervm -Xint -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=isb TestSpinPause * @run main/othervm -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=isb TestSpinPause + */ + +/* + * @test id=yield + * @summary Check SpinPause for synchronized statements with OnSpinWaitInst=yield. + * @bug 8278241 + * @library /test/lib + * @requires os.arch=="aarch64" + * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=yield TestSpinPause + * @run main/othervm -Xint -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=yield TestSpinPause * @run main/othervm -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=yield TestSpinPause + */ + +/* + * @test id=nop-count-10 + * @summary Check SpinPause for synchronized statements with OnSpinWaitInst=nop and OnSpinWaitInstCount=10. + * @bug 8278241 + * @library /test/lib + * @requires os.arch=="aarch64" + * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=nop -XX:OnSpinWaitInstCount=10 TestSpinPause + * @run main/othervm -Xint -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=nop -XX:OnSpinWaitInstCount=10 TestSpinPause * @run main/othervm -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=nop -XX:OnSpinWaitInstCount=10 TestSpinPause + */ + +/* + * @test id=isb-count-3 + * @summary Check SpinPause for synchronized statements with OnSpinWaitInst=isb and OnSpinWaitInstCount=3. + * @bug 8278241 + * @library /test/lib + * @requires os.arch=="aarch64" + * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=isb -XX:OnSpinWaitInstCount=3 TestSpinPause + * @run main/othervm -Xint -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=isb -XX:OnSpinWaitInstCount=3 TestSpinPause * @run main/othervm -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=isb -XX:OnSpinWaitInstCount=3 TestSpinPause + */ + +/* + * @test id=yield-count-3 + * @summary Check SpinPause for synchronized statements with OnSpinWaitInst=yield and OnSpinWaitInstCount=3. + * @bug 8278241 + * @library /test/lib + * @requires os.arch=="aarch64" + * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=yield -XX:OnSpinWaitInstCount=3 TestSpinPause + * @run main/othervm -Xint -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=yield -XX:OnSpinWaitInstCount=3 TestSpinPause * @run main/othervm -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:OnSpinWaitInst=yield -XX:OnSpinWaitInstCount=3 TestSpinPause */ From 24a1532719f389f7adc7d54da8c15f2f48254296 Mon Sep 17 00:00:00 2001 From: Mark Powers Date: Thu, 16 Jul 2026 23:48:46 +0000 Subject: [PATCH 173/305] =?UTF-8?q?8359758:=20O(n=C2=B2)=20time=20complexi?= =?UTF-8?q?ty=20in=20sun.security.util.LocalizedMessage.getNonlocalized?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reviewed-by: djelinski, weijun, abarashev --- .../sun/security/util/LocalizedMessage.java | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/java.base/share/classes/sun/security/util/LocalizedMessage.java b/src/java.base/share/classes/sun/security/util/LocalizedMessage.java index 61062bf6e1a..8d79e85c8a5 100644 --- a/src/java.base/share/classes/sun/security/util/LocalizedMessage.java +++ b/src/java.base/share/classes/sun/security/util/LocalizedMessage.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -106,32 +106,33 @@ public class LocalizedMessage { // Classes like StringTokenizer may not be loaded, so parsing // is performed with String methods StringBuilder sb = new StringBuilder(); - int nextBraceIndex; - while ((nextBraceIndex = value.indexOf('{')) >= 0) { + int pos = 0; + int leftBraceIndex; + while ((leftBraceIndex = value.indexOf('{', pos)) >= 0) { - String firstPart = value.substring(0, nextBraceIndex); - sb.append(firstPart); - value = value.substring(nextBraceIndex + 1); + sb.append(value, pos, leftBraceIndex); // look for closing brace and argument index - nextBraceIndex = value.indexOf('}'); - if (nextBraceIndex < 0) { + int rightBraceIndex = value.indexOf('}', leftBraceIndex + 1); + if (rightBraceIndex < 0) { // no closing brace // MessageFormat would throw IllegalArgumentException, but // that exception class may not be loaded yet throw new RuntimeException("Unmatched braces"); } - String indexStr = value.substring(0, nextBraceIndex); try { - int index = Integer.parseInt(indexStr); + int index = Integer.parseInt(value, leftBraceIndex + 1, + rightBraceIndex, 10); sb.append(arguments[index]); } catch (NumberFormatException e) { // argument index is not an integer - throw new RuntimeException("not an integer: " + indexStr); + throw new RuntimeException("not an integer: " + + value.substring(leftBraceIndex + 1, rightBraceIndex)); } - value = value.substring(nextBraceIndex + 1); + + pos = rightBraceIndex + 1; } - sb.append(value); + sb.append(value, pos, value.length()); return sb.toString(); } From b7b29c7082db536ed03abb44ffc6e9e76960a309 Mon Sep 17 00:00:00 2001 From: Guanqiang Han Date: Fri, 17 Jul 2026 02:14:36 +0000 Subject: [PATCH 174/305] 8388186: java -XX:UseSSE=2 -XX:+EnableX86ECoreOpts -version crashes with assert(UseAVX > 0) failed: requires some form of AVX Reviewed-by: asmehra, kvn --- src/hotspot/cpu/x86/vm_version_x86.cpp | 2 +- ...estEnableX86ECoreOptsWithAVX2Disabled.java | 57 +++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 test/hotspot/jtreg/compiler/cpuflags/TestEnableX86ECoreOptsWithAVX2Disabled.java diff --git a/src/hotspot/cpu/x86/vm_version_x86.cpp b/src/hotspot/cpu/x86/vm_version_x86.cpp index 6112c280a1d..e395dd301f4 100644 --- a/src/hotspot/cpu/x86/vm_version_x86.cpp +++ b/src/hotspot/cpu/x86/vm_version_x86.cpp @@ -1343,7 +1343,7 @@ void VM_Version::get_processor_features() { } if (UseSHA && ((supports_evex() && supports_avx512vlbw()) || - (EnableX86ECoreOpts && !supports_hybrid()))) { + (supports_avx2() && EnableX86ECoreOpts && !supports_hybrid()))) { if (FLAG_IS_DEFAULT(UseSHA3Intrinsics)) { FLAG_SET_DEFAULT(UseSHA3Intrinsics, true); } diff --git a/test/hotspot/jtreg/compiler/cpuflags/TestEnableX86ECoreOptsWithAVX2Disabled.java b/test/hotspot/jtreg/compiler/cpuflags/TestEnableX86ECoreOptsWithAVX2Disabled.java new file mode 100644 index 00000000000..c31514bbba1 --- /dev/null +++ b/test/hotspot/jtreg/compiler/cpuflags/TestEnableX86ECoreOptsWithAVX2Disabled.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8388186 + * @summary Test for VM crash with -XX:+EnableX86ECoreOpts and UseAVX < 2. + * @requires vm.flagless + * @requires os.arch == "amd64" | os.arch == "x86_64" + * @library /test/lib + * @run driver ${test.main.class} + */ + +package compiler.cpuflags; + +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +public class TestEnableX86ECoreOptsWithAVX2Disabled { + static final String[] OPTIONS = { + "-XX:UseSSE=2", + "-XX:UseSSE=3", + "-XX:UseAVX=0", + "-XX:UseAVX=1" + }; + + public static void main(String[] args) throws Exception { + for (String option : OPTIONS) { + OutputAnalyzer output = ProcessTools.executeLimitedTestJava( + "-XX:+UnlockDiagnosticVMOptions", + "-XX:+EnableX86ECoreOpts", + option, + "-version"); + output.shouldHaveExitValue(0); + } + } +} From bb3baa85b8f62924230926320ca5a782f51d85e6 Mon Sep 17 00:00:00 2001 From: Benjamin Peterson Date: Fri, 17 Jul 2026 02:19:00 +0000 Subject: [PATCH 175/305] 8388360: Dead sharedRuntime.cpp stub name code Reviewed-by: dholmes --- src/hotspot/share/runtime/sharedRuntime.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/hotspot/share/runtime/sharedRuntime.cpp b/src/hotspot/share/runtime/sharedRuntime.cpp index bcb7f5488f5..919161dde2f 100644 --- a/src/hotspot/share/runtime/sharedRuntime.cpp +++ b/src/hotspot/share/runtime/sharedRuntime.cpp @@ -108,14 +108,6 @@ nmethod* SharedRuntime::_cont_doYield_stub; -#if 0 -// TODO tweak global stub name generation to match this -#define SHARED_STUB_NAME_DECLARE(name, type) "Shared Runtime " # name "_blob", -const char *SharedRuntime::_stub_names[] = { - SHARED_STUBS_DO(SHARED_STUB_NAME_DECLARE) -}; -#endif - //----------------------------generate_stubs----------------------------------- void SharedRuntime::generate_initial_stubs() { // Build this early so it's available for the interpreter. From c5c366ad0cfe33361b0c30597bc71beb4770db09 Mon Sep 17 00:00:00 2001 From: Harshit Dhiman Date: Fri, 17 Jul 2026 04:14:32 +0000 Subject: [PATCH 176/305] 8388284: [s390] resolve_jobject uses wrong branch condition after tmll Reviewed-by: amitkumar, aph --- src/hotspot/cpu/s390/gc/shared/barrierSetAssembler_s390.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hotspot/cpu/s390/gc/shared/barrierSetAssembler_s390.cpp b/src/hotspot/cpu/s390/gc/shared/barrierSetAssembler_s390.cpp index 9a401766200..d0f92cc129a 100644 --- a/src/hotspot/cpu/s390/gc/shared/barrierSetAssembler_s390.cpp +++ b/src/hotspot/cpu/s390/gc/shared/barrierSetAssembler_s390.cpp @@ -116,7 +116,7 @@ void BarrierSetAssembler::resolve_jobject(MacroAssembler* masm, Register value, __ z_bre(done); // Use null result as-is. __ z_tmll(value, JNIHandles::tag_mask); - __ z_btrue(tagged); // not zero + __ branch_optimized(Assembler::bcondNotAllZero, tagged); // not zero // Resolve Local handle __ access_load_at(T_OBJECT, IN_NATIVE | AS_RAW, Address(value, 0), value, tmp1, tmp2); @@ -124,7 +124,7 @@ void BarrierSetAssembler::resolve_jobject(MacroAssembler* masm, Register value, __ bind(tagged); __ testbit(value, exact_log2(JNIHandles::TypeTag::weak_global)); // test for weak tag - __ z_btrue(weak_tag); + __ branch_optimized(Assembler::bcondNotAllZero, weak_tag); // resolve global handle __ access_load_at(T_OBJECT, IN_NATIVE, Address(value, -JNIHandles::TypeTag::global), value, tmp1, tmp2); From 6987a3593fc7581f04992b034d3dbb0469d09f1f Mon Sep 17 00:00:00 2001 From: David CARLIER Date: Fri, 17 Jul 2026 05:02:17 +0000 Subject: [PATCH 177/305] 8387718: JVMTI GetLocal/SetLocal: slot bounds check overflows for long/double slots Reviewed-by: dholmes, sspitsyn --- src/hotspot/share/prims/jvmtiImpl.cpp | 4 +- .../GetSetLocalSlotOverflow.java | 77 ++++++++++++ .../libGetSetLocalSlotOverflow.cpp | 113 ++++++++++++++++++ 3 files changed, 192 insertions(+), 2 deletions(-) create mode 100644 test/hotspot/jtreg/serviceability/jvmti/GetLocalVariable/GetSetLocalSlotOverflow.java create mode 100644 test/hotspot/jtreg/serviceability/jvmti/GetLocalVariable/libGetSetLocalSlotOverflow.cpp diff --git a/src/hotspot/share/prims/jvmtiImpl.cpp b/src/hotspot/share/prims/jvmtiImpl.cpp index c0a4ca949c9..96366cceaff 100644 --- a/src/hotspot/share/prims/jvmtiImpl.cpp +++ b/src/hotspot/share/prims/jvmtiImpl.cpp @@ -379,7 +379,7 @@ bool VM_BaseGetOrSetLocal::check_slot_type_lvt(javaVFrame* jvf) { if (!method->has_localvariable_table()) { // Just to check index boundaries. jint extra_slot = (_type == T_LONG || _type == T_DOUBLE) ? 1 : 0; - if (_index < 0 || _index + extra_slot >= method->max_locals()) { + if (_index < 0 || _index >= method->max_locals() - extra_slot) { _result = JVMTI_ERROR_INVALID_SLOT; return false; } @@ -451,7 +451,7 @@ bool VM_BaseGetOrSetLocal::check_slot_type_no_lvt(javaVFrame* jvf) { Method* method = jvf->method(); jint extra_slot = (_type == T_LONG || _type == T_DOUBLE) ? 1 : 0; - if (_index < 0 || _index + extra_slot >= method->max_locals()) { + if (_index < 0 || _index >= method->max_locals() - extra_slot) { _result = JVMTI_ERROR_INVALID_SLOT; return false; } diff --git a/test/hotspot/jtreg/serviceability/jvmti/GetLocalVariable/GetSetLocalSlotOverflow.java b/test/hotspot/jtreg/serviceability/jvmti/GetLocalVariable/GetSetLocalSlotOverflow.java new file mode 100644 index 00000000000..09073d99bfb --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/GetLocalVariable/GetSetLocalSlotOverflow.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8387718 + * @summary VM_GetOrSetLocal slot bounds check overflows for long/double slots, + * allowing an out-of-bounds StackValueCollection access when slot == INT_MAX. + * @requires vm.jvmti + * @compile GetSetLocalSlotOverflow.java + * @run main/othervm/native -agentlib:GetSetLocalSlotOverflow GetSetLocalSlotOverflow + */ + +/* + * Regression test / reproducer for the signed-overflow in + * VM_BaseGetOrSetLocal::check_slot_type_no_lvt (jvmtiImpl.cpp). + * + * For a T_LONG/T_DOUBLE access, the bounds check is + * if (_index < 0 || _index + extra_slot >= method->max_locals()) + * with extra_slot == 1. When the agent passes slot == INT_MAX, the + * sub-expression _index + extra_slot overflows to INT_MIN, which is < max_locals(), + * so the guard passes and the code goes on to index locals->at(INT_MAX). + * + * Expected (fixed) behavior: GetLocalLong/Double and SetLocalLong/Double with + * slot == INT_MAX return JVMTI_ERROR_INVALID_SLOT. + * + * On an unfixed VM this test does not merely fail: the out-of-bounds access + * crashes the VM (assertion failure in fastdebug, SIGSEGV / silent corruption + * in product). A clean PASS is only possible once the bounds check is fixed. + */ + +public class GetSetLocalSlotOverflow { + + // Invoked from runner(); the agent inspects the runner() frame at depth 1. + // Returns false if any accessor did not return JVMTI_ERROR_INVALID_SLOT. + static native boolean testOverflow(Thread thread); + + public static void main(String[] args) throws Exception { + if (!runner()) { + throw new RuntimeException("Test GetSetLocalSlotOverflow failed"); + } + } + + // A Java frame holding a few locals. The agent targets this frame (depth 1) + // with slot == INT_MAX. The actual local contents are irrelevant: the + // overflow happens in the slot bounds check, before any local is read. + public static boolean runner() { + long l = 0xCAFEBABEL; + double d = 3.14d; + boolean ok = testOverflow(Thread.currentThread()); + // Keep locals live across the native call. + if (l == 0 && d == 0) { + throw new AssertionError("unreachable"); + } + return ok; + } +} diff --git a/test/hotspot/jtreg/serviceability/jvmti/GetLocalVariable/libGetSetLocalSlotOverflow.cpp b/test/hotspot/jtreg/serviceability/jvmti/GetLocalVariable/libGetSetLocalSlotOverflow.cpp new file mode 100644 index 00000000000..98872ddf0de --- /dev/null +++ b/test/hotspot/jtreg/serviceability/jvmti/GetLocalVariable/libGetSetLocalSlotOverflow.cpp @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include +#include +#include "jvmti.h" +#include "jvmti_common.hpp" + +#ifdef __cplusplus +extern "C" { +#endif + +// The runner() frame at depth 1; INT_MAX makes (slot + extra_slot) overflow +// for the long/double accessors. +static const jint Depth = 1; +static const jint OverflowSlot = INT_MAX; // 0x7fffffff + +static jvmtiEnv *jvmti = nullptr; + +// Each access below MUST come back as JVMTI_ERROR_INVALID_SLOT. On an unfixed +// VM the overflowing bounds check is bypassed and the subsequent +// locals->at(INT_MAX) access crashes the VM before we ever see a return code. +static bool expect_invalid_slot(const char* what, jvmtiError err) { + if (err == JVMTI_ERROR_INVALID_SLOT) { + LOG(" PASS: %s returned JVMTI_ERROR_INVALID_SLOT (%d) for slot=INT_MAX\n", what, err); + return true; + } + LOG(" FAIL: %s returned %d for slot=INT_MAX, expected JVMTI_ERROR_INVALID_SLOT (%d)\n", + what, err, JVMTI_ERROR_INVALID_SLOT); + return false; +} + +JNIEXPORT jboolean JNICALL +Java_GetSetLocalSlotOverflow_testOverflow(JNIEnv *env, jclass cls, jobject thread) { + if (jvmti == nullptr) { + LOG("JVMTI client was not properly loaded!\n"); + return JNI_FALSE; + } + + jlong lval = 0; + jdouble dval = 0; + + // T_LONG / T_DOUBLE => extra_slot == 1 => INT_MAX + 1 overflows to INT_MIN. + bool ok = true; + ok &= expect_invalid_slot("GetLocalLong", jvmti->GetLocalLong(thread, Depth, OverflowSlot, &lval)); + ok &= expect_invalid_slot("GetLocalDouble", jvmti->GetLocalDouble(thread, Depth, OverflowSlot, &dval)); + ok &= expect_invalid_slot("SetLocalLong", jvmti->SetLocalLong(thread, Depth, OverflowSlot, (jlong)0)); + ok &= expect_invalid_slot("SetLocalDouble", jvmti->SetLocalDouble(thread, Depth, OverflowSlot, (jdouble)0)); + return ok ? JNI_TRUE : JNI_FALSE; +} + +static jint Agent_Initialize(JavaVM *jvm, char *options, void *reserved) { + jint res; + jvmtiError err; + static jvmtiCapabilities caps; + + res = jvm->GetEnv((void **) &jvmti, JVMTI_VERSION_9); + if (res != JNI_OK || jvmti == nullptr) { + LOG("Wrong result of a valid call to GetEnv!\n"); + return JNI_ERR; + } + caps.can_access_local_variables = 1; + + err = jvmti->AddCapabilities(&caps); + if (err != JVMTI_ERROR_NONE) { + LOG("AddCapabilities: unexpected error: %d\n", err); + return JNI_ERR; + } + err = jvmti->GetCapabilities(&caps); + if (err != JVMTI_ERROR_NONE) { + LOG("GetCapabilities: unexpected error: %d\n", err); + return JNI_ERR; + } + if (!caps.can_access_local_variables) { + LOG("Warning: Access to local variables is not implemented\n"); + return JNI_ERR; + } + return JNI_OK; +} + +JNIEXPORT jint JNICALL +Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) { + return Agent_Initialize(jvm, options, reserved); +} + +JNIEXPORT jint JNICALL +Agent_OnAttach(JavaVM *jvm, char *options, void *reserved) { + return Agent_Initialize(jvm, options, reserved); +} + +#ifdef __cplusplus +} +#endif From 0ad1166ada90fe39e2855f21fd91db9fccfffe0b Mon Sep 17 00:00:00 2001 From: Volkan Yazici Date: Fri, 17 Jul 2026 05:55:39 +0000 Subject: [PATCH 178/305] 8387753: Improve SimpleDateFormat.set2DigitYearStart() documentation Reviewed-by: naoto --- .../classes/java/text/SimpleDateFormat.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/java.base/share/classes/java/text/SimpleDateFormat.java b/src/java.base/share/classes/java/text/SimpleDateFormat.java index 4c57214dbba..ba1ae827776 100644 --- a/src/java.base/share/classes/java/text/SimpleDateFormat.java +++ b/src/java.base/share/classes/java/text/SimpleDateFormat.java @@ -920,10 +920,15 @@ public class SimpleDateFormat extends DateFormat { } /** - * Sets the 100-year period 2-digit years will be interpreted as being in - * to begin on the date the user specifies. + * Sets the start date of the 100-year period used to interpret 2-digit years. + *

    + * For example, given a {@code SimpleDateFormat} with a {@code GregorianCalendar}, + * if the start date is set to January 1, 1950, 2-digit years are + * interpreted as falling within the 100-year range from 1950 through 2049. + * In that case, 50 is interpreted as 1950, 99 as 1999, 00 as 2000, and 49 + * as 2049. * - * @param startDate During parsing, two digit years will be placed in the range + * @param startDate During parsing, 2-digit years will be placed in the range * {@code startDate} to {@code startDate + 100 years}. * @see #get2DigitYearStart * @throws NullPointerException if {@code startDate} is {@code null}. @@ -934,11 +939,8 @@ public class SimpleDateFormat extends DateFormat { } /** - * Returns the beginning date of the 100-year period 2-digit years are interpreted - * as being within. + * {@return the start date of the 100-year period used to interpret 2-digit years} * - * @return the start of the 100-year period into which two digit years are - * parsed * @see #set2DigitYearStart * @since 1.2 */ From 3e67ebff0719e5d8e09532bf91fcc4c80bb53ad5 Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Fri, 17 Jul 2026 06:35:20 +0000 Subject: [PATCH 179/305] 8387701: TestAVXRegisterDump: guarantee(how == 0) failed: test guarantee Reviewed-by: missa, sviswanathan, kvn --- .../hotspot/jtreg/runtime/ErrorHandling/TestAVXRegisterDump.java | 1 + 1 file changed, 1 insertion(+) diff --git a/test/hotspot/jtreg/runtime/ErrorHandling/TestAVXRegisterDump.java b/test/hotspot/jtreg/runtime/ErrorHandling/TestAVXRegisterDump.java index 1f2fec74fee..59692d94d2f 100644 --- a/test/hotspot/jtreg/runtime/ErrorHandling/TestAVXRegisterDump.java +++ b/test/hotspot/jtreg/runtime/ErrorHandling/TestAVXRegisterDump.java @@ -26,6 +26,7 @@ * @summary Test that YMM and ZMM registers are correctly dumped in hs_err for different UseAVX settings * @library /test/lib * @requires os.family == "linux" & os.arch == "amd64" + * @requires vm.cpu.features ~= ".*avx.*" * @requires vm.debug == true * @modules java.base/jdk.internal.misc * @build jdk.test.whitebox.WhiteBox From 160382009f121502bee469afcae8236676502e16 Mon Sep 17 00:00:00 2001 From: Volkan Yazici Date: Fri, 17 Jul 2026 06:38:30 +0000 Subject: [PATCH 180/305] 8387598: sun.net.httpserver.maxReqTime and maxRspTime properties expect values in seconds Reviewed-by: jpai --- src/jdk.httpserver/share/classes/module-info.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/jdk.httpserver/share/classes/module-info.java b/src/jdk.httpserver/share/classes/module-info.java index 0a0e77c628f..842a7ec1c9a 100644 --- a/src/jdk.httpserver/share/classes/module-info.java +++ b/src/jdk.httpserver/share/classes/module-info.java @@ -83,7 +83,7 @@ import com.sun.net.httpserver.*; * If the value is less than or equal to zero, there is no limit. * *

  37. {@systemProperty sun.net.httpserver.maxReqTime} (default: -1)
    - * The maximum time in milliseconds allowed to receive a request headers and body. + * The maximum time in seconds allowed to receive a request headers and body. * In practice, the actual time is a function of request size, network speed, and handler * processing delays. A value less than or equal to zero means the time is not limited. * If the limit is exceeded then the connection is terminated and the handler will receive a @@ -91,7 +91,7 @@ import com.sun.net.httpserver.*; * that may mean requests are aborted later than the specified interval. *

  38. *
  39. {@systemProperty sun.net.httpserver.maxRspTime} (default: -1)
    - * The maximum time in milliseconds allowed to receive a response headers and body. + * The maximum time in seconds allowed to receive a response headers and body. * In practice, the actual time is a function of response size, network speed, and handler * processing delays. A value less than or equal to zero means the time is not limited. * If the limit is exceeded then the connection is terminated and the handler will receive a From 2278ade4e8714ee167b2267b7cdde2d5859dfdaf Mon Sep 17 00:00:00 2001 From: Matthias Baesken Date: Fri, 17 Jul 2026 07:05:51 +0000 Subject: [PATCH 181/305] 8387674: Remove isXP() function from jabswitch.cpp Reviewed-by: prr, clanger --- .../windows/native/jabswitch/jabswitch.cpp | 25 +++---------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/src/jdk.accessibility/windows/native/jabswitch/jabswitch.cpp b/src/jdk.accessibility/windows/native/jabswitch/jabswitch.cpp index fdd7ff524da..7e4f63d2363 100644 --- a/src/jdk.accessibility/windows/native/jabswitch/jabswitch.cpp +++ b/src/jdk.accessibility/windows/native/jabswitch/jabswitch.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -53,23 +53,6 @@ static LPCTSTR STR_ACCESSBRIDGE = FILE* origFile; FILE* tempFile; -bool isXP() -{ - static bool isXPFlag = false; - OSVERSIONINFO osvi; - - // Initialize the OSVERSIONINFO structure. - ZeroMemory( &osvi, sizeof( osvi ) ); - osvi.dwOSVersionInfoSize = sizeof( osvi ); - - GetVersionEx( &osvi ); - - if ( osvi.dwMajorVersion == 5 ) // For Windows XP and Windows 2000 - isXPFlag = true; - - return isXPFlag ; -} - void enableJAB() { // Copy lines from orig to temp modifying the line containing // assistive_technologies= @@ -458,16 +441,14 @@ int main(int argc, char* argv[]) { enableWasRequested = true; error = modify(true); if (error == 0) { - if( !isXP() ) - regEnable(); + regEnable(); } } else if (_stricmp(argv[1], "-disable") == 0 || _stricmp(argv[1], "/disable") == 0) { badParams = false; disableWasRequested = true; error = modify(false); if (error == 0) { - if( !isXP() ) - regDisable(); + regDisable(); } } } From 5bea309caa1e5747feca4957786e5595957316b9 Mon Sep 17 00:00:00 2001 From: Jatin Bhateja Date: Fri, 17 Jul 2026 07:07:52 +0000 Subject: [PATCH 182/305] 8387698: C2 VectorAPI: Float16Vector::indexInRange hits: fatal error: Not monotonic Reviewed-by: chagedorn, epeter --- src/hotspot/share/opto/castnode.cpp | 5 ++ .../TestFloat16VectorConvergence.java | 60 +++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 test/hotspot/jtreg/compiler/vectorapi/TestFloat16VectorConvergence.java diff --git a/src/hotspot/share/opto/castnode.cpp b/src/hotspot/share/opto/castnode.cpp index 076a95acfd8..ef7b4d5aef3 100644 --- a/src/hotspot/share/opto/castnode.cpp +++ b/src/hotspot/share/opto/castnode.cpp @@ -465,6 +465,11 @@ const Type* CheckCastPPNode::Value(PhaseGVN* phase) const { if (in_type != nullptr && my_type != nullptr) { TypePtr::PTR in_ptr = in_type->ptr(); if (in_ptr == TypePtr::Null) { + // A null input cast to a type that cannot be null (e.g. NotNull) describes + // an impossible value: the join is empty, so the result must be TOP. + if (my_type->join_ptr(TypePtr::Null) == TypePtr::TopPTR) { + return Type::TOP; + } result = in_type; } else if (in_ptr != TypePtr::Constant) { result = my_type->cast_to_ptr_type(my_type->join_ptr(in_ptr)); diff --git a/test/hotspot/jtreg/compiler/vectorapi/TestFloat16VectorConvergence.java b/test/hotspot/jtreg/compiler/vectorapi/TestFloat16VectorConvergence.java new file mode 100644 index 00000000000..9cf92af2644 --- /dev/null +++ b/test/hotspot/jtreg/compiler/vectorapi/TestFloat16VectorConvergence.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package compiler.vectorapi; + +import jdk.incubator.vector.Float16Vector; +import jdk.incubator.vector.VectorMask; + +import java.util.Random; + +/* + * @test + * @bug 8387698 + * @summary C2 VectorAPI: Float16Vector::indexInRange hits fatal error: Not monotonic. + * @modules jdk.incubator.vector + * @requires vm.debug == true + * @run main ${test.main.class} + * @run main/othervm -XX:-UncommonNullCast -Xbatch + * -XX:CompileCommand=compileonly,${test.main.class}::test + * ${test.main.class} + */ +public class TestFloat16VectorConvergence { + + static Object test(long offset, long limit) { + boolean[] array = new boolean[16]; + var v = VectorMask.fromArray(Float16Vector.SPECIES_256, array, 0); + return v.indexInRange(offset, limit); + } + + public static void main(String[] args) { + Random random = new Random(0); + for (int i = 0; i < 10_000; i++) { + // A negative offset drives indexInRange into indexPartiallyInRange, + // which is where the un-foldable Float16 mask unbox is produced. + Object mask = test(-70368744177664L, random.nextLong()); + if (mask == null) { + throw new AssertionError("Unexpected null result from indexInRange"); + } + } + } +} From a15f693cb860e0b2208b3755cee222a03956067a Mon Sep 17 00:00:00 2001 From: April Ivy Date: Fri, 17 Jul 2026 08:44:52 +0000 Subject: [PATCH 183/305] 8387467: ZGC: Use shared thread-local _nmethod_disarmed_guard_value Reviewed-by: aboldtch, ayang, eosterlund --- src/hotspot/share/gc/shared/barrierSet.cpp | 4 ++-- src/hotspot/share/gc/shared/barrierSetNMethod.cpp | 4 ++++ src/hotspot/share/gc/shared/barrierSetNMethod.hpp | 4 +++- src/hotspot/share/gc/shared/gcThreadLocalData.hpp | 4 ++-- src/hotspot/share/gc/z/zBarrierSet.cpp | 3 ++- src/hotspot/share/gc/z/zBarrierSetNMethod.cpp | 7 +------ src/hotspot/share/gc/z/zBarrierSetNMethod.hpp | 3 +-- src/hotspot/share/gc/z/zStackWatermark.cpp | 7 +++++-- src/hotspot/share/gc/z/zThreadLocalData.hpp | 12 +----------- 9 files changed, 21 insertions(+), 27 deletions(-) diff --git a/src/hotspot/share/gc/shared/barrierSet.cpp b/src/hotspot/share/gc/shared/barrierSet.cpp index a30b23ce2d9..1fd0317e8f1 100644 --- a/src/hotspot/share/gc/shared/barrierSet.cpp +++ b/src/hotspot/share/gc/shared/barrierSet.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -86,7 +86,7 @@ BarrierSet::BarrierSet(BarrierSetAssembler* barrier_set_assembler, void BarrierSet::on_thread_attach(Thread* thread) { BarrierSetNMethod* bs_nm = barrier_set_nmethod(); - thread->set_nmethod_disarmed_guard_value(bs_nm->disarmed_guard_value()); + bs_nm->set_thread_disarmed_guard_value(thread); } // Called from init.cpp diff --git a/src/hotspot/share/gc/shared/barrierSetNMethod.cpp b/src/hotspot/share/gc/shared/barrierSetNMethod.cpp index 2f7b79beab0..c36deb3446a 100644 --- a/src/hotspot/share/gc/shared/barrierSetNMethod.cpp +++ b/src/hotspot/share/gc/shared/barrierSetNMethod.cpp @@ -135,6 +135,10 @@ ByteSize BarrierSetNMethod::thread_disarmed_guard_value_offset() const { return Thread::nmethod_disarmed_guard_value_offset(); } +void BarrierSetNMethod::set_thread_disarmed_guard_value(Thread* thread) { + thread->set_nmethod_disarmed_guard_value(disarmed_guard_value()); +} + class BarrierSetNMethodArmClosure : public ThreadClosure { private: int _disarmed_guard_value; diff --git a/src/hotspot/share/gc/shared/barrierSetNMethod.hpp b/src/hotspot/share/gc/shared/barrierSetNMethod.hpp index 812763e429d..cd01ddda09c 100644 --- a/src/hotspot/share/gc/shared/barrierSetNMethod.hpp +++ b/src/hotspot/share/gc/shared/barrierSetNMethod.hpp @@ -54,9 +54,11 @@ public: bool supports_entry_barrier(nmethod* nm); virtual bool nmethod_entry_barrier(nmethod* nm); - virtual ByteSize thread_disarmed_guard_value_offset() const; virtual int* disarmed_guard_value_address() const; + ByteSize thread_disarmed_guard_value_offset() const; + void set_thread_disarmed_guard_value(Thread* thread); + int disarmed_guard_value() const; static int nmethod_stub_entry_barrier(address* return_address_ptr); diff --git a/src/hotspot/share/gc/shared/gcThreadLocalData.hpp b/src/hotspot/share/gc/shared/gcThreadLocalData.hpp index 2847cd8bf33..b0659c58390 100644 --- a/src/hotspot/share/gc/shared/gcThreadLocalData.hpp +++ b/src/hotspot/share/gc/shared/gcThreadLocalData.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -40,6 +40,6 @@ // should consider placing frequently accessed fields first in // T, so that field offsets relative to Thread are small, which // often allows for a more compact instruction encoding. -typedef uint64_t GCThreadLocalData[40]; // 320 bytes +typedef uint64_t GCThreadLocalData[39]; // 312 bytes #endif // SHARE_GC_SHARED_GCTHREADLOCALDATA_HPP diff --git a/src/hotspot/share/gc/z/zBarrierSet.cpp b/src/hotspot/share/gc/z/zBarrierSet.cpp index f6f99672886..c5d6fc5a9b1 100644 --- a/src/hotspot/share/gc/z/zBarrierSet.cpp +++ b/src/hotspot/share/gc/z/zBarrierSet.cpp @@ -251,13 +251,14 @@ void ZBarrierSet::on_thread_destroy(Thread* thread) { } void ZBarrierSet::on_thread_attach(Thread* thread) { + BarrierSet::on_thread_attach(thread); + // Set thread local masks ZThreadLocalData::set_load_bad_mask(thread, ZPointerLoadBadMask); ZThreadLocalData::set_load_good_mask(thread, ZPointerLoadGoodMask); ZThreadLocalData::set_mark_bad_mask(thread, ZPointerMarkBadMask); ZThreadLocalData::set_store_bad_mask(thread, ZPointerStoreBadMask); ZThreadLocalData::set_store_good_mask(thread, ZPointerStoreGoodMask); - ZThreadLocalData::set_nmethod_disarmed(thread, ZPointerStoreGoodMask); if (thread->is_Java_thread()) { JavaThread* const jt = JavaThread::cast(thread); StackWatermark* const watermark = new ZStackWatermark(jt); diff --git a/src/hotspot/share/gc/z/zBarrierSetNMethod.cpp b/src/hotspot/share/gc/z/zBarrierSetNMethod.cpp index a439b3a167b..6e89c5a1032 100644 --- a/src/hotspot/share/gc/z/zBarrierSetNMethod.cpp +++ b/src/hotspot/share/gc/z/zBarrierSetNMethod.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -30,7 +30,6 @@ #include "gc/z/zLock.inline.hpp" #include "gc/z/zNMethod.hpp" #include "gc/z/zResurrection.inline.hpp" -#include "gc/z/zThreadLocalData.hpp" #include "gc/z/zUncoloredRoot.inline.hpp" #include "logging/log.hpp" #include "runtime/icache.hpp" @@ -98,10 +97,6 @@ int* ZBarrierSetNMethod::disarmed_guard_value_address() const { return (int*)ZPointerStoreGoodMaskLowOrderBitsAddr; } -ByteSize ZBarrierSetNMethod::thread_disarmed_guard_value_offset() const { - return ZThreadLocalData::nmethod_disarmed_offset(); -} - oop ZBarrierSetNMethod::oop_load_no_keepalive(const nmethod* nm, int index) { return ZNMethod::oop_load_no_keepalive(nm, index); } diff --git a/src/hotspot/share/gc/z/zBarrierSetNMethod.hpp b/src/hotspot/share/gc/z/zBarrierSetNMethod.hpp index c7bbe35e17d..304be1f0a88 100644 --- a/src/hotspot/share/gc/z/zBarrierSetNMethod.hpp +++ b/src/hotspot/share/gc/z/zBarrierSetNMethod.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -36,7 +36,6 @@ protected: public: uintptr_t color(nmethod* nm); - virtual ByteSize thread_disarmed_guard_value_offset() const; virtual int* disarmed_guard_value_address() const; virtual oop oop_load_no_keepalive(const nmethod* nm, int index); diff --git a/src/hotspot/share/gc/z/zStackWatermark.cpp b/src/hotspot/share/gc/z/zStackWatermark.cpp index 4a50dea0cec..de57ea974f3 100644 --- a/src/hotspot/share/gc/z/zStackWatermark.cpp +++ b/src/hotspot/share/gc/z/zStackWatermark.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -23,6 +23,7 @@ #include "gc/z/zAddress.hpp" #include "gc/z/zBarrier.inline.hpp" +#include "gc/z/zBarrierSet.hpp" #include "gc/z/zGeneration.inline.hpp" #include "gc/z/zStackWatermark.hpp" #include "gc/z/zStoreBarrierBuffer.hpp" @@ -189,7 +190,9 @@ void ZStackWatermark::start_processing_impl(void* context) { ZThreadLocalData::set_mark_bad_mask(_jt, ZPointerMarkBadMask); ZThreadLocalData::set_store_bad_mask(_jt, ZPointerStoreBadMask); ZThreadLocalData::set_store_good_mask(_jt, ZPointerStoreGoodMask); - ZThreadLocalData::set_nmethod_disarmed(_jt, ZPointerStoreGoodMask); + + // Update thread-local nmethod disarmed guard value + BarrierSet::barrier_set()->barrier_set_nmethod()->set_thread_disarmed_guard_value(_jt); // Retire TLAB if (ZGeneration::young()->is_phase_mark() || ZGeneration::old()->is_phase_mark()) { diff --git a/src/hotspot/share/gc/z/zThreadLocalData.hpp b/src/hotspot/share/gc/z/zThreadLocalData.hpp index a141fd8f83a..297d57c2cfe 100644 --- a/src/hotspot/share/gc/z/zThreadLocalData.hpp +++ b/src/hotspot/share/gc/z/zThreadLocalData.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -39,7 +39,6 @@ private: uintptr_t _mark_bad_mask; uintptr_t _store_good_mask; uintptr_t _store_bad_mask; - uintptr_t _nmethod_disarmed; ZStoreBarrierBuffer* _store_barrier_buffer; ZMarkThreadLocalStacks _mark_stacks[2]; zaddress_unsafe* _invisible_root; @@ -50,7 +49,6 @@ private: _mark_bad_mask(0), _store_good_mask(0), _store_bad_mask(0), - _nmethod_disarmed(0), _store_barrier_buffer(new ZStoreBarrierBuffer()), _mark_stacks(), _invisible_root(nullptr) {} @@ -92,10 +90,6 @@ public: data(thread)->_store_good_mask = mask; } - static void set_nmethod_disarmed(Thread* thread, uintptr_t value) { - data(thread)->_nmethod_disarmed = value; - } - static ZMarkThreadLocalStacks* mark_stacks(Thread* thread, ZGenerationId id) { return &data(thread)->_mark_stacks[(int)id]; } @@ -134,10 +128,6 @@ public: return Thread::gc_data_offset() + byte_offset_of(ZThreadLocalData, _store_good_mask); } - static ByteSize nmethod_disarmed_offset() { - return Thread::gc_data_offset() + byte_offset_of(ZThreadLocalData, _nmethod_disarmed); - } - static ByteSize store_barrier_buffer_offset() { return Thread::gc_data_offset() + byte_offset_of(ZThreadLocalData, _store_barrier_buffer); } From 026a63d0d941abc50b4ecbecca1668a93fa6963a Mon Sep 17 00:00:00 2001 From: Harshit Dhiman Date: Fri, 17 Jul 2026 11:08:58 +0000 Subject: [PATCH 184/305] 8388285: [s390] compP_reg_mem is missing the barrier_data() == 0 predicate Reviewed-by: amitkumar, aph --- src/hotspot/cpu/s390/s390.ad | 1 + 1 file changed, 1 insertion(+) diff --git a/src/hotspot/cpu/s390/s390.ad b/src/hotspot/cpu/s390/s390.ad index 256e39b03c2..5f33cf4fa77 100644 --- a/src/hotspot/cpu/s390/s390.ad +++ b/src/hotspot/cpu/s390/s390.ad @@ -8786,6 +8786,7 @@ instruct compP_decode_reg_imm0(flagsReg cr, iRegN op1, immP0 op2) %{ instruct compP_reg_mem(iRegP dst, memory src, flagsReg cr)%{ match(Set cr (CmpP dst (LoadP src))); + predicate(n->in(2)->as_Load()->barrier_data() == 0); ins_cost(MEMORY_REF_COST); size(Z_DISP3_SIZE); format %{ "CLG $dst, $src\t # ptr" %} From 9601cfb31b7b489db10fc3523de0e5d86cd2faed Mon Sep 17 00:00:00 2001 From: Dingli Zhang Date: Fri, 17 Jul 2026 13:18:05 +0000 Subject: [PATCH 185/305] 8388399: RISC-V: Enable vector FP16 conversions with Zvfhmin Reviewed-by: fyang, wenanjian --- src/hotspot/cpu/riscv/globals_riscv.hpp | 1 + src/hotspot/cpu/riscv/riscv_v.ad | 1 + src/hotspot/cpu/riscv/vm_version_riscv.hpp | 147 +++++++++--------- .../os_cpu/linux_riscv/riscv_hwprobe.cpp | 3 + 4 files changed, 80 insertions(+), 72 deletions(-) diff --git a/src/hotspot/cpu/riscv/globals_riscv.hpp b/src/hotspot/cpu/riscv/globals_riscv.hpp index d399bc13082..a7f0da42f4e 100644 --- a/src/hotspot/cpu/riscv/globals_riscv.hpp +++ b/src/hotspot/cpu/riscv/globals_riscv.hpp @@ -120,6 +120,7 @@ define_pd_global(intx, InlineSmallCode, 1000); product(bool, UseZvbb, false, DIAGNOSTIC, "Use Zvbb instructions") \ product(bool, UseZvbc, false, EXPERIMENTAL, "Use Zvbc instructions") \ product(bool, UseZvfh, false, DIAGNOSTIC, "Use Zvfh instructions") \ + product(bool, UseZvfhmin, false, DIAGNOSTIC, "Use Zvfhmin instructions") \ product(bool, UseZvkg, false, DIAGNOSTIC, "Use Zvkg instructions") \ product(bool, UseZvkn, false, DIAGNOSTIC, \ "Use Zvkn group extension, Zvkned, Zvknhb, Zvkb, Zvkt") \ diff --git a/src/hotspot/cpu/riscv/riscv_v.ad b/src/hotspot/cpu/riscv/riscv_v.ad index a0af43364cb..2a63221de04 100644 --- a/src/hotspot/cpu/riscv/riscv_v.ad +++ b/src/hotspot/cpu/riscv/riscv_v.ad @@ -113,6 +113,7 @@ source %{ break; case Op_VectorCastHF2F: case Op_VectorCastF2HF: + return UseZvfh || UseZvfhmin; case Op_AddVHF: case Op_SubVHF: case Op_MulVHF: diff --git a/src/hotspot/cpu/riscv/vm_version_riscv.hpp b/src/hotspot/cpu/riscv/vm_version_riscv.hpp index 11a88dfedd7..e5d925d1bea 100644 --- a/src/hotspot/cpu/riscv/vm_version_riscv.hpp +++ b/src/hotspot/cpu/riscv/vm_version_riscv.hpp @@ -219,78 +219,80 @@ class VM_Version : public Abstract_VM_Version { // // Fields description in `decl`: // declaration name, extension name, bit value from linux, feature string?, mapped flag) - #define RV_EXT_FEATURE_FLAGS(decl) \ - /* A Atomic Instructions */ \ - decl(a , ('A' - 'A'), true , NO_UPDATE_DEFAULT) \ - /* C Compressed Instructions */ \ - decl(c , ('C' - 'A'), true , UPDATE_DEFAULT(UseRVC)) \ - /* D Single-Precision Floating-Point */ \ - decl(d , ('D' - 'A'), true , NO_UPDATE_DEFAULT) \ - /* F Single-Precision Floating-Point */ \ - decl(f , ('F' - 'A'), true , NO_UPDATE_DEFAULT) \ - /* H Hypervisor */ \ - decl(h , ('H' - 'A'), true , NO_UPDATE_DEFAULT) \ - /* I RV64I */ \ - decl(i , ('I' - 'A'), true , NO_UPDATE_DEFAULT) \ - /* M Integer Multiplication and Division */ \ - decl(m , ('M' - 'A'), true , NO_UPDATE_DEFAULT) \ - /* Q Quad-Precision Floating-Point */ \ - decl(q , ('Q' - 'A'), true , NO_UPDATE_DEFAULT) \ - /* V Vector */ \ - decl(v , ('V' - 'A'), true , UPDATE_DEFAULT(UseRVV)) \ - \ - /* ----------------------- Other extensions ----------------------- */ \ - \ - /* Atomic compare-and-swap (CAS) instructions */ \ - decl(Zacas , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZacas)) \ - /* Zba Address generation instructions */ \ - decl(Zba , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZba)) \ - /* Zbb Basic bit-manipulation */ \ - decl(Zbb , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZbb)) \ - /* Zbc Carry-less multiplication */ \ - decl(Zbc , RV_NO_FLAG_BIT, true , NO_UPDATE_DEFAULT) \ - /* Bitmanip instructions for Cryptography */ \ - decl(Zbkb , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZbkb)) \ - /* Zbs Single-bit instructions */ \ - decl(Zbs , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZbs)) \ - /* Zcb Simple code-size saving instructions */ \ - decl(Zcb , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZcb)) \ - /* Additional Floating-Point instructions */ \ - decl(Zfa , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZfa)) \ - /* Zfh Half-Precision Floating-Point instructions */ \ - decl(Zfh , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZfh)) \ - /* Zfhmin Minimal Half-Precision Floating-Point instructions */ \ - decl(Zfhmin , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZfhmin)) \ - /* Zicbom Cache Block Management Operations */ \ - decl(Zicbom , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZicbom)) \ - /* Zicbop Cache Block Prefetch Operations */ \ - decl(Zicbop , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZicbop)) \ - /* Zicboz Cache Block Zero Operations */ \ - decl(Zicboz , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZicboz)) \ - /* Base Counters and Timers */ \ - decl(Zicntr , RV_NO_FLAG_BIT, true , NO_UPDATE_DEFAULT) \ - /* Zicond Conditional operations */ \ - decl(Zicond , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZicond)) \ - /* Zicsr Control and Status Register (CSR) Instructions */ \ - decl(Zicsr , RV_NO_FLAG_BIT, true , NO_UPDATE_DEFAULT) \ - /* Zic64b Cache blocks must be 64 bytes in size, naturally aligned in the address space. */ \ - decl(Zic64b , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZic64b)) \ - /* Zifencei Instruction-Fetch Fence */ \ - decl(Zifencei , RV_NO_FLAG_BIT, true , NO_UPDATE_DEFAULT) \ - /* Zihintpause Pause instruction HINT */ \ - decl(Zihintpause , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZihintpause)) \ - /* Total Store Ordering */ \ - decl(Ztso , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZtso)) \ - /* Vector Basic Bit-manipulation */ \ - decl(Zvbb , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT_DEP(UseZvbb, &ext_v, nullptr)) \ - /* Vector Carryless Multiplication */ \ - decl(Zvbc , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT_DEP(UseZvbc, &ext_v, nullptr)) \ - /* Vector Extension for Half-Precision Floating-Point */ \ - decl(Zvfh , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT_DEP(UseZvfh, &ext_v, &ext_Zfh, nullptr)) \ - /* Shorthand for Zvkned + Zvknhb + Zvkb + Zvkt */ \ - decl(Zvkn , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT_DEP(UseZvkn, &ext_v, nullptr)) \ - /* Zvkg crypto extension for ghash and gcm */ \ - decl(Zvkg , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT_DEP(UseZvkg, &ext_v, nullptr)) \ + #define RV_EXT_FEATURE_FLAGS(decl) \ + /* A Atomic Instructions */ \ + decl(a , ('A' - 'A'), true , NO_UPDATE_DEFAULT) \ + /* C Compressed Instructions */ \ + decl(c , ('C' - 'A'), true , UPDATE_DEFAULT(UseRVC)) \ + /* D Single-Precision Floating-Point */ \ + decl(d , ('D' - 'A'), true , NO_UPDATE_DEFAULT) \ + /* F Single-Precision Floating-Point */ \ + decl(f , ('F' - 'A'), true , NO_UPDATE_DEFAULT) \ + /* H Hypervisor */ \ + decl(h , ('H' - 'A'), true , NO_UPDATE_DEFAULT) \ + /* I RV64I */ \ + decl(i , ('I' - 'A'), true , NO_UPDATE_DEFAULT) \ + /* M Integer Multiplication and Division */ \ + decl(m , ('M' - 'A'), true , NO_UPDATE_DEFAULT) \ + /* Q Quad-Precision Floating-Point */ \ + decl(q , ('Q' - 'A'), true , NO_UPDATE_DEFAULT) \ + /* V Vector */ \ + decl(v , ('V' - 'A'), true , UPDATE_DEFAULT(UseRVV)) \ + \ + /* ----------------------- Other extensions ----------------------- */ \ + \ + /* Atomic compare-and-swap (CAS) instructions */ \ + decl(Zacas , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZacas)) \ + /* Zba Address generation instructions */ \ + decl(Zba , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZba)) \ + /* Zbb Basic bit-manipulation */ \ + decl(Zbb , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZbb)) \ + /* Zbc Carry-less multiplication */ \ + decl(Zbc , RV_NO_FLAG_BIT, true , NO_UPDATE_DEFAULT) \ + /* Bitmanip instructions for Cryptography */ \ + decl(Zbkb , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZbkb)) \ + /* Zbs Single-bit instructions */ \ + decl(Zbs , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZbs)) \ + /* Zcb Simple code-size saving instructions */ \ + decl(Zcb , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZcb)) \ + /* Additional Floating-Point instructions */ \ + decl(Zfa , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZfa)) \ + /* Zfh Half-Precision Floating-Point instructions */ \ + decl(Zfh , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZfh)) \ + /* Zfhmin Minimal Half-Precision Floating-Point instructions */ \ + decl(Zfhmin , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZfhmin)) \ + /* Zicbom Cache Block Management Operations */ \ + decl(Zicbom , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZicbom)) \ + /* Zicbop Cache Block Prefetch Operations */ \ + decl(Zicbop , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZicbop)) \ + /* Zicboz Cache Block Zero Operations */ \ + decl(Zicboz , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZicboz)) \ + /* Base Counters and Timers */ \ + decl(Zicntr , RV_NO_FLAG_BIT, true , NO_UPDATE_DEFAULT) \ + /* Zicond Conditional operations */ \ + decl(Zicond , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZicond)) \ + /* Zicsr Control and Status Register (CSR) Instructions */ \ + decl(Zicsr , RV_NO_FLAG_BIT, true , NO_UPDATE_DEFAULT) \ + /* Zic64b Cache blocks must be 64 bytes in size, naturally aligned in the address space. */ \ + decl(Zic64b , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZic64b)) \ + /* Zifencei Instruction-Fetch Fence */ \ + decl(Zifencei , RV_NO_FLAG_BIT, true , NO_UPDATE_DEFAULT) \ + /* Zihintpause Pause instruction HINT */ \ + decl(Zihintpause , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZihintpause)) \ + /* Total Store Ordering */ \ + decl(Ztso , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT(UseZtso)) \ + /* Vector Basic Bit-manipulation */ \ + decl(Zvbb , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT_DEP(UseZvbb, &ext_v, nullptr)) \ + /* Vector Carryless Multiplication */ \ + decl(Zvbc , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT_DEP(UseZvbc, &ext_v, nullptr)) \ + /* Vector Extension for Half-Precision Floating-Point */ \ + decl(Zvfh , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT_DEP(UseZvfh, &ext_v, &ext_Zfhmin, nullptr)) \ + /* Vector Extension for Minimal Half-Precision Floating-Point */ \ + decl(Zvfhmin , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT_DEP(UseZvfhmin, &ext_v, nullptr)) \ + /* Shorthand for Zvkned + Zvknhb + Zvkb + Zvkt */ \ + decl(Zvkn , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT_DEP(UseZvkn, &ext_v, nullptr)) \ + /* Zvkg crypto extension for ghash and gcm */ \ + decl(Zvkg , RV_NO_FLAG_BIT, true , UPDATE_DEFAULT_DEP(UseZvkg, &ext_v, nullptr)) \ #define DECLARE_RV_EXT_FEATURE(PRETTY, LINUX_BIT, FSTRING, FLAGF) \ struct ext_##PRETTY##RVExtFeatureValue : public RVExtFeatureValue { \ @@ -442,6 +444,7 @@ private: RV_ENABLE_EXTENSION(UseZicboz) \ RV_ENABLE_EXTENSION(UseZicond) \ RV_ENABLE_EXTENSION(UseZihintpause) \ + RV_ENABLE_EXTENSION(UseZvfhmin) \ static void useRVA23U64Profile(); diff --git a/src/hotspot/os_cpu/linux_riscv/riscv_hwprobe.cpp b/src/hotspot/os_cpu/linux_riscv/riscv_hwprobe.cpp index fe555ec5ffb..bbefd4ba50d 100644 --- a/src/hotspot/os_cpu/linux_riscv/riscv_hwprobe.cpp +++ b/src/hotspot/os_cpu/linux_riscv/riscv_hwprobe.cpp @@ -247,6 +247,9 @@ void RiscvHwprobe::add_features_from_query_result() { if (is_set(RISCV_HWPROBE_KEY_IMA_EXT_0, RISCV_HWPROBE_EXT_ZVFH)) { VM_Version::ext_Zvfh.enable_feature(); } + if (is_set(RISCV_HWPROBE_KEY_IMA_EXT_0, RISCV_HWPROBE_EXT_ZVFHMIN)) { + VM_Version::ext_Zvfhmin.enable_feature(); + } if (is_set(RISCV_HWPROBE_KEY_IMA_EXT_0, RISCV_HWPROBE_EXT_ZVKNED) && is_set(RISCV_HWPROBE_KEY_IMA_EXT_0, RISCV_HWPROBE_EXT_ZVKNHB) && is_set(RISCV_HWPROBE_KEY_IMA_EXT_0, RISCV_HWPROBE_EXT_ZVKB) && From 9e9fae6584c8865c20542303e9349a0799888330 Mon Sep 17 00:00:00 2001 From: Thomas Stuefe Date: Fri, 17 Jul 2026 13:44:59 +0000 Subject: [PATCH 186/305] 8387652: [aarch64] Fallback mode for narrow klass decoding Reviewed-by: aph, adinn, rkennke, galder --- src/hotspot/cpu/aarch64/aarch64.ad | 8 +- .../cpu/aarch64/c1_LIRAssembler_aarch64.cpp | 42 ++-- .../cpu/aarch64/c1_MacroAssembler_aarch64.cpp | 2 +- .../cpu/aarch64/c1_Runtime1_aarch64.cpp | 6 +- .../cpu/aarch64/c2_MacroAssembler_aarch64.cpp | 2 +- .../cpu/aarch64/compressedKlass_aarch64.cpp | 8 +- .../cpu/aarch64/interp_masm_aarch64.cpp | 2 +- .../cpu/aarch64/macroAssembler_aarch64.cpp | 195 ++++++++---------- .../cpu/aarch64/macroAssembler_aarch64.hpp | 55 ++--- .../cpu/aarch64/methodHandles_aarch64.cpp | 6 +- .../cpu/aarch64/stubGenerator_aarch64.cpp | 12 +- .../cpu/aarch64/templateTable_aarch64.cpp | 26 +-- .../cpu/aarch64/vtableStubs_aarch64.cpp | 4 +- src/hotspot/share/cds/aotMetaspace.cpp | 26 +-- src/hotspot/share/memory/metaspace.cpp | 3 +- src/hotspot/share/oops/compressedKlass.cpp | 21 +- src/hotspot/share/oops/compressedKlass.hpp | 11 +- .../gtest/aarch64/test_assembler_aarch64.cpp | 139 +++++++++++++ test/hotspot/jtreg/gtest/AssemblerGtests.java | 51 +++++ ...CompressedClassPointersEncodingScheme.java | 109 ++++------ .../AccessZeroNKlassHitsProtectionZone.java | 2 +- 21 files changed, 417 insertions(+), 313 deletions(-) create mode 100644 test/hotspot/jtreg/gtest/AssemblerGtests.java diff --git a/src/hotspot/cpu/aarch64/aarch64.ad b/src/hotspot/cpu/aarch64/aarch64.ad index be9d79d03c7..37c8e0ae011 100644 --- a/src/hotspot/cpu/aarch64/aarch64.ad +++ b/src/hotspot/cpu/aarch64/aarch64.ad @@ -8228,7 +8228,7 @@ instruct encodeKlass_not_null(iRegNNoSp dst, iRegP src) %{ ins_encode %{ Register src_reg = as_Register($src$$reg); Register dst_reg = as_Register($dst$$reg); - __ encode_klass_not_null(dst_reg, src_reg); + __ encode_klass_not_null(dst_reg, src_reg, rscratch1); %} ins_pipe(ialu_reg); @@ -8243,11 +8243,7 @@ instruct decodeKlass_not_null(iRegPNoSp dst, iRegN src) %{ ins_encode %{ Register src_reg = as_Register($src$$reg); Register dst_reg = as_Register($dst$$reg); - if (dst_reg != src_reg) { - __ decode_klass_not_null(dst_reg, src_reg); - } else { - __ decode_klass_not_null(dst_reg); - } + __ decode_klass_not_null(dst_reg, src_reg, rscratch1); %} ins_pipe(ialu_reg); diff --git a/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp index 0290a200366..5b77d15457f 100644 --- a/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/c1_LIRAssembler_aarch64.cpp @@ -1324,7 +1324,7 @@ void LIR_Assembler::emit_typecheck_helper(LIR_OpTypeCheck *op, Label* success, L __ bind(not_null); Register recv = k_RInfo; - __ load_klass(recv, obj); + __ load_klass(recv, obj, rscratch1); type_profile_helper(mdo, md, data, recv); } else { __ cbz(obj, *obj_is_null); @@ -1340,15 +1340,15 @@ void LIR_Assembler::emit_typecheck_helper(LIR_OpTypeCheck *op, Label* success, L if (op->fast_check()) { // get object class // not a safepoint as obj null check happens earlier - __ load_klass(rscratch1, obj); - __ cmp( rscratch1, k_RInfo); + __ load_klass(rscratch2, obj, rscratch1); + __ cmp( rscratch2, k_RInfo); __ br(Assembler::NE, *failure_target); // successful cast, fall through to profile or jump } else { // get object class // not a safepoint as obj null check happens earlier - __ load_klass(klass_RInfo, obj); + __ load_klass(klass_RInfo, obj, rscratch1); if (k->is_loaded()) { // See if we get an immediate positive hit __ ldr(rscratch1, Address(klass_RInfo, int64_t(k->super_check_offset()))); @@ -1433,15 +1433,15 @@ void LIR_Assembler::emit_opTypeCheck(LIR_OpTypeCheck* op) { __ bind(not_null); Register recv = k_RInfo; - __ load_klass(recv, value); + __ load_klass(recv, value, rscratch1); type_profile_helper(mdo, md, data, recv); } else { __ cbz(value, done); } add_debug_info_for_null_check_here(op->info_for_exception()); - __ load_klass(k_RInfo, array); - __ load_klass(klass_RInfo, value); + __ load_klass(k_RInfo, array, rscratch1); + __ load_klass(klass_RInfo, value, rscratch1); // get instance klass (it's already uncompressed) __ ldr(k_RInfo, Address(k_RInfo, ObjArrayKlass::element_klass_offset())); @@ -2258,14 +2258,14 @@ void LIR_Assembler::emit_arraycopy(LIR_OpArrayCopy* op) { // an instance type. if (flags & LIR_OpArrayCopy::type_check) { if (!(flags & LIR_OpArrayCopy::LIR_OpArrayCopy::dst_objarray)) { - __ load_klass(tmp, dst); + __ load_klass(tmp, dst, rscratch1); __ ldrw(rscratch1, Address(tmp, in_bytes(Klass::layout_helper_offset()))); __ cmpw(rscratch1, Klass::_lh_neutral_value); __ br(Assembler::GE, *stub->entry()); } if (!(flags & LIR_OpArrayCopy::LIR_OpArrayCopy::src_objarray)) { - __ load_klass(tmp, src); + __ load_klass(tmp, src, rscratch1); __ ldrw(rscratch1, Address(tmp, in_bytes(Klass::layout_helper_offset()))); __ cmpw(rscratch1, Klass::_lh_neutral_value); __ br(Assembler::GE, *stub->entry()); @@ -2319,8 +2319,8 @@ void LIR_Assembler::emit_arraycopy(LIR_OpArrayCopy* op) { __ PUSH(src, dst); - __ load_klass(src, src); - __ load_klass(dst, dst); + __ load_klass(src, src, rscratch1); + __ load_klass(dst, dst, rscratch1); __ check_klass_subtype_fast_path(src, dst, tmp, &cont, &slow, nullptr); @@ -2344,9 +2344,9 @@ void LIR_Assembler::emit_arraycopy(LIR_OpArrayCopy* op) { assert(flags & mask, "one of the two should be known to be an object array"); if (!(flags & LIR_OpArrayCopy::src_objarray)) { - __ load_klass(tmp, src); + __ load_klass(tmp, src, rscratch1); } else if (!(flags & LIR_OpArrayCopy::dst_objarray)) { - __ load_klass(tmp, dst); + __ load_klass(tmp, dst, rscratch1); } int lh_offset = in_bytes(Klass::layout_helper_offset()); Address klass_lh_addr(tmp, lh_offset); @@ -2372,7 +2372,7 @@ void LIR_Assembler::emit_arraycopy(LIR_OpArrayCopy* op) { __ uxtw(c_rarg2, length); assert_different_registers(c_rarg2, dst); - __ load_klass(c_rarg4, dst); + __ load_klass(c_rarg4, dst, rscratch1); __ ldr(c_rarg4, Address(c_rarg4, ObjArrayKlass::element_klass_offset())); __ ldrw(c_rarg3, Address(c_rarg4, Klass::super_check_offset_offset())); __ far_call(RuntimeAddress(copyfunc_addr)); @@ -2428,12 +2428,12 @@ void LIR_Assembler::emit_arraycopy(LIR_OpArrayCopy* op) { __ mov_metadata(tmp, default_type->constant_encoding()); if (basic_type != T_OBJECT) { - __ cmp_klass(dst, tmp, rscratch1); + __ cmp_klass(dst, tmp, rscratch1, rscratch2); __ br(Assembler::NE, halt); - __ cmp_klass(src, tmp, rscratch1); + __ cmp_klass(src, tmp, rscratch1, rscratch2); __ br(Assembler::EQ, known_ok); } else { - __ cmp_klass(dst, tmp, rscratch1); + __ cmp_klass(dst, tmp, rscratch1, rscratch2); __ br(Assembler::EQ, known_ok); __ cmp(src, dst); __ br(Assembler::EQ, known_ok); @@ -2508,7 +2508,7 @@ void LIR_Assembler::emit_load_klass(LIR_OpLoadKlass* op) { add_debug_info_for_null_check_here(info); } - __ load_klass(result, obj); + __ load_klass(result, obj, rscratch1); } void LIR_Assembler::emit_profile_call(LIR_OpProfileCall* op) { @@ -2550,7 +2550,7 @@ void LIR_Assembler::emit_profile_call(LIR_OpProfileCall* op) { // Fall back to runtime helper to handle the rest at runtime. __ mov_metadata(recv, known_klass->constant_encoding()); } else { - __ load_klass(recv, recv); + __ load_klass(recv, recv, rscratch1); } type_profile_helper(mdo, md, data, recv); } else { @@ -2636,7 +2636,7 @@ void LIR_Assembler::emit_profile_type(LIR_OpProfileType* op) { #ifdef ASSERT if (exact_klass != nullptr) { Label ok; - __ load_klass(tmp, tmp); + __ load_klass(tmp, tmp, rscratch1); __ mov_metadata(rscratch1, exact_klass->constant_encoding()); __ eor(rscratch1, tmp, rscratch1); __ cbz(rscratch1, ok); @@ -2649,7 +2649,7 @@ void LIR_Assembler::emit_profile_type(LIR_OpProfileType* op) { if (exact_klass != nullptr) { __ mov_metadata(tmp, exact_klass->constant_encoding()); } else { - __ load_klass(tmp, tmp); + __ load_klass(tmp, tmp, rscratch1); } __ ldr(rscratch2, mdo_addr); diff --git a/src/hotspot/cpu/aarch64/c1_MacroAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/c1_MacroAssembler_aarch64.cpp index 89a9422ea48..f81c976d291 100644 --- a/src/hotspot/cpu/aarch64/c1_MacroAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/c1_MacroAssembler_aarch64.cpp @@ -105,7 +105,7 @@ void C1_MacroAssembler::initialize_header(Register obj, Register klass, Register } else { mov(t1, checked_cast(markWord::prototype().value())); str(t1, Address(obj, oopDesc::mark_offset_in_bytes())); - encode_klass_not_null(t1, klass); // Take care not to kill klass + encode_klass_not_null(t1, klass, t1); // Take care not to kill klass strw(t1, Address(obj, oopDesc::klass_offset_in_bytes())); } diff --git a/src/hotspot/cpu/aarch64/c1_Runtime1_aarch64.cpp b/src/hotspot/cpu/aarch64/c1_Runtime1_aarch64.cpp index 449ad4f8a4c..1745bb8aae9 100644 --- a/src/hotspot/cpu/aarch64/c1_Runtime1_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/c1_Runtime1_aarch64.cpp @@ -824,7 +824,7 @@ OopMapSet* Runtime1::generate_code_for(StubId id, StubAssembler* sasm) { // load the klass and check the has finalizer flag Label register_finalizer; Register t = r5; - __ load_klass(t, r0); + __ load_klass(t, r0, rscratch1); __ ldrb(t, Address(t, Klass::misc_flags_offset())); __ tbnz(t, exact_log2(KlassFlags::_misc_has_finalizer), register_finalizer); __ ret(lr); @@ -947,7 +947,7 @@ OopMapSet* Runtime1::generate_code_for(StubId id, StubAssembler* sasm) { __ br(Assembler::EQ, is_secondary); // Klass is a secondary superclass // Klass is a concrete class - __ load_klass(r5, obj); + __ load_klass(r5, obj, rscratch1); __ ldr(rscratch1, Address(r5, r3)); __ cmp(klass, rscratch1); __ cset(result, Assembler::EQ); @@ -955,7 +955,7 @@ OopMapSet* Runtime1::generate_code_for(StubId id, StubAssembler* sasm) { __ bind(is_secondary); - __ load_klass(obj, obj); + __ load_klass(obj, obj, rscratch1); // This is necessary because I am never in my own secondary_super list. __ cmp(obj, klass); diff --git a/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp index fe9180bda5c..3321fcc4edb 100644 --- a/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp @@ -167,7 +167,7 @@ void C2_MacroAssembler::fast_lock(Register obj, Register box, Register t1, } if (DiagnoseSyncOnValueBasedClasses != 0) { - load_klass(t1, obj); + load_klass(t1, obj, rscratch2); ldrb(t1, Address(t1, Klass::misc_flags_offset())); tst(t1, KlassFlags::_misc_is_value_based_class); br(Assembler::NE, slow_path); diff --git a/src/hotspot/cpu/aarch64/compressedKlass_aarch64.cpp b/src/hotspot/cpu/aarch64/compressedKlass_aarch64.cpp index 3874c8cd54e..7cc2a004c40 100644 --- a/src/hotspot/cpu/aarch64/compressedKlass_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/compressedKlass_aarch64.cpp @@ -120,11 +120,7 @@ char* CompressedKlassPointers::reserve_address_space_for_compressed_classes(size return result; } -bool CompressedKlassPointers::check_klass_decode_mode(address base, int shift, const size_t range) { - return MacroAssembler::check_klass_decode_mode(base, shift, range); -} - -bool CompressedKlassPointers::set_klass_decode_mode() { +void CompressedKlassPointers::initialize_pd() { const size_t range = klass_range_end() - base(); - return MacroAssembler::set_klass_decode_mode(_base, _shift, range); + MacroAssembler::initialize_klass_decode_mode(_base, _shift, range); } diff --git a/src/hotspot/cpu/aarch64/interp_masm_aarch64.cpp b/src/hotspot/cpu/aarch64/interp_masm_aarch64.cpp index 22c2383816c..0da237c133f 100644 --- a/src/hotspot/cpu/aarch64/interp_masm_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/interp_masm_aarch64.cpp @@ -1403,7 +1403,7 @@ void InterpreterMacroAssembler::profile_obj_type(Register obj, const Address& md b(next); bind(update); - load_klass(obj, obj); + load_klass(obj, obj, rscratch1); ldr(rscratch1, mdo_addr); eor(obj, obj, rscratch1); diff --git a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp index f2208aa0ad6..527e79459ec 100644 --- a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp @@ -25,6 +25,7 @@ #include "asm/assembler.hpp" #include "asm/assembler.inline.hpp" +#include "cds/archiveBuilder.hpp" #include "ci/ciEnv.hpp" #include "code/compiledIC.hpp" #include "compiler/compileTask.hpp" @@ -5115,9 +5116,9 @@ void MacroAssembler::load_narrow_klass(Register dst, Register src) { } } -void MacroAssembler::load_klass(Register dst, Register src) { +void MacroAssembler::load_klass(Register dst, Register src, Register tmp) { load_narrow_klass(dst, src); - decode_klass_not_null(dst); + decode_klass_not_null(dst, dst, tmp); } void MacroAssembler::restore_cpu_control_state_after_jni(Register tmp1, Register tmp2) { @@ -5167,8 +5168,8 @@ void MacroAssembler::load_mirror(Register dst, Register method, Register tmp1, R resolve_oop_handle(dst, tmp1, tmp2); } -void MacroAssembler::cmp_klass(Register obj, Register klass, Register tmp) { - assert_different_registers(obj, klass, tmp); +void MacroAssembler::cmp_klass(Register obj, Register klass, Register tmp, Register tmp2) { + assert_different_registers(obj, klass, tmp, tmp2); if (UseCompactObjectHeaders) { load_narrow_klass_compact(tmp, obj); } else { @@ -5184,7 +5185,7 @@ void MacroAssembler::cmp_klass(Register obj, Register klass, Register tmp) { cmpw(klass, tmp); return; } - decode_klass_not_null(tmp); + decode_klass_not_null(tmp, tmp, tmp2); cmp(klass, tmp); } @@ -5199,11 +5200,11 @@ void MacroAssembler::cmp_klasses_from_objects(Register obj1, Register obj2, Regi cmpw(tmp1, tmp2); } -void MacroAssembler::store_klass(Register dst, Register src) { +void MacroAssembler::store_klass(Register dst, Register src, Register tmp) { // FIXME: Should this be a store release? concurrent gcs assumes // klass length is valid if klass field is not null. assert(!UseCompactObjectHeaders, "not with compact headers"); - encode_klass_not_null(src); + encode_klass_not_null(src, src, tmp); strw(src, Address(dst, oopDesc::klass_offset_in_bytes())); } @@ -5356,8 +5357,6 @@ MacroAssembler::KlassDecodeMode MacroAssembler::klass_decode_mode() { } MacroAssembler::KlassDecodeMode MacroAssembler::klass_decode_mode(address base, int shift, const size_t range) { - // KlassDecodeMode shouldn't be set already. - assert(_klass_decode_mode == KlassDecodeNone, "set once"); if (base == nullptr) { return KlassDecodeZero; @@ -5377,148 +5376,128 @@ MacroAssembler::KlassDecodeMode MacroAssembler::klass_decode_mode(address base, return KlassDecodeMovk; } - // No valid encoding. - return KlassDecodeNone; + return KlassDecodeFallback; } -// Check if one of the above decoding modes will work for given base, shift and range. -bool MacroAssembler::check_klass_decode_mode(address base, int shift, const size_t range) { - return klass_decode_mode(base, shift, range) != KlassDecodeNone; -} - -bool MacroAssembler::set_klass_decode_mode(address base, int shift, const size_t range) { +void MacroAssembler::initialize_klass_decode_mode(address base, int shift, const size_t range) { + // KlassDecodeMode shouldn't be set already. + assert(_klass_decode_mode == KlassDecodeNone, "set once"); _klass_decode_mode = klass_decode_mode(base, shift, range); - return _klass_decode_mode != KlassDecodeNone; + log_info(metaspace)("Klass Decode Mode: %d", (int)_klass_decode_mode); } -static Register pick_different_tmp(Register dst, Register src) { - auto tmps = RegSet::of(r0, r1, r2) - RegSet::of(src, dst); - return *tmps.begin(); +void MacroAssembler::encode_klass_not_null(Register dst, Register src, Register tmp) { + emit_encode_klass_not_null(dst, src, tmp, CompressedKlassPointers::base(), + CompressedKlassPointers::shift(), klass_decode_mode()); } -void MacroAssembler::encode_klass_not_null_for_aot(Register dst, Register src) { - // we have to load the klass base from the AOT constants area but - // not the shift because it is not allowed to change - int shift = CompressedKlassPointers::shift(); - assert(shift >= 0 && shift <= CompressedKlassPointers::max_shift(), "unexpected compressed klass shift!"); - if (dst != src) { - // we can load the base into dst, subtract it formthe src and shift down - lea(dst, ExternalAddress(CompressedKlassPointers::base_addr())); - ldr(dst, dst); - sub(dst, src, dst); - lsr(dst, dst, shift); - } else { - // we need an extra register in order to load the coop base - Register tmp = pick_different_tmp(dst, src); - RegSet regs = RegSet::of(tmp); - push(regs, sp); +void MacroAssembler::emit_encode_klass_not_null(Register dst, Register src, Register tmp, + address base, int shift, KlassDecodeMode decode_mode) { + + assert_different_registers(tmp, src); + assert(tmp != noreg, "valid tmp required"); + + if (AOTCodeCache::is_on_for_dump()) { + // We are generating code during AOT buildup that will run in *future* processes + // with likely different encoding settings. Therefore, we have to load the + // encoding base dynamically, we cannot just bake it in as immediate. + // Note that we only need to do this for base. The encoding shift would be the + // same between build time and runtime: the standard precomputed shift. + assert(shift == ArchiveBuilder::precomputed_narrow_klass_shift(), "unexpected compressed klass shift!"); lea(tmp, ExternalAddress(CompressedKlassPointers::base_addr())); ldr(tmp, tmp); sub(dst, src, tmp); lsr(dst, dst, shift); - pop(regs, sp); - } -} - -void MacroAssembler::encode_klass_not_null(Register dst, Register src) { - if (CompressedKlassPointers::base() != nullptr && AOTCodeCache::is_on_for_dump()) { - encode_klass_not_null_for_aot(dst, src); return; } - switch (klass_decode_mode()) { + switch (decode_mode) { case KlassDecodeZero: - if (CompressedKlassPointers::shift() != 0) { - lsr(dst, src, CompressedKlassPointers::shift()); - } else { - if (dst != src) mov(dst, src); - } + lsr(dst, src, shift); break; case KlassDecodeXor: - if (CompressedKlassPointers::shift() != 0) { - eor(dst, src, (uint64_t)CompressedKlassPointers::base()); - lsr(dst, dst, CompressedKlassPointers::shift()); - } else { - eor(dst, src, (uint64_t)CompressedKlassPointers::base()); - } + eor(dst, src, (uint64_t)base); + lsr(dst, dst, shift); break; case KlassDecodeMovk: - if (CompressedKlassPointers::shift() != 0) { - ubfx(dst, src, CompressedKlassPointers::shift(), 32); + if (shift != 0) { + ubfx(dst, src, shift, 32); } else { movw(dst, src); } break; + case KlassDecodeFallback: { + mov(tmp, base); + sub(dst, src, tmp); + lsr(dst, dst, shift); + break; + } + case KlassDecodeNone: ShouldNotReachHere(); break; } + +#ifdef ASSERT + if (tmp != dst) { + mov(tmp, 0xdead); + } +#endif // ASSERT + } -void MacroAssembler::encode_klass_not_null(Register r) { - encode_klass_not_null(r, r); +void MacroAssembler::decode_klass_not_null(Register dst, Register src, Register tmp) { + emit_decode_klass_not_null(dst, src, tmp, + CompressedKlassPointers::base(), + CompressedKlassPointers::shift(), + klass_decode_mode()); } -void MacroAssembler::decode_klass_not_null_for_aot(Register dst, Register src) { - // we have to load the klass base from the AOT constants area but - // not the shift because it is not allowed to change - int shift = CompressedKlassPointers::shift(); - assert(shift >= 0 && shift <= CompressedKlassPointers::max_shift(), "unexpected compressed klass shift!"); - if (dst != src) { - // we can load the base into dst then add the offset with a suitable shift - lea(dst, ExternalAddress(CompressedKlassPointers::base_addr())); - ldr(dst, dst); - add(dst, dst, src, LSL, shift); - } else { - // we need an extra register in order to load the coop base - Register tmp = pick_different_tmp(dst, src); - RegSet regs = RegSet::of(tmp); - push(regs, sp); +void MacroAssembler::emit_decode_klass_not_null(Register dst, Register src, Register tmp, + address base, int shift, KlassDecodeMode decode_mode) { + + assert_different_registers(tmp, src); + assert(tmp != noreg, "valid tmp required"); + + if (AOTCodeCache::is_on_for_dump()) { + // We are generating code during AOT buildup that will run in *future* processes + // with likely different encoding settings. Therefore, we have to load the + // encoding base dynamically, we cannot just bake it in as immediate. + // Note that we only need to do this for base. The encoding shift would be the + // same between build time and runtime: the standard precomputed shift. + assert(shift == ArchiveBuilder::precomputed_narrow_klass_shift(), "unexpected compressed klass shift!"); lea(tmp, ExternalAddress(CompressedKlassPointers::base_addr())); ldr(tmp, tmp); add(dst, tmp, src, LSL, shift); - pop(regs, sp); - } -} - -void MacroAssembler::decode_klass_not_null(Register dst, Register src) { - if (AOTCodeCache::is_on_for_dump()) { - decode_klass_not_null_for_aot(dst, src); return; } - switch (klass_decode_mode()) { - case KlassDecodeZero: - if (CompressedKlassPointers::shift() != 0) { - lsl(dst, src, CompressedKlassPointers::shift()); - } else { - if (dst != src) mov(dst, src); - } + switch (decode_mode) { + case KlassDecodeZero: // 0-1 instructions + lsl(dst, src, shift); break; - case KlassDecodeXor: - if (CompressedKlassPointers::shift() != 0) { - lsl(dst, src, CompressedKlassPointers::shift()); - eor(dst, dst, (uint64_t)CompressedKlassPointers::base()); - } else { - eor(dst, src, (uint64_t)CompressedKlassPointers::base()); - } + case KlassDecodeXor: // 1-2 instructions + lsl(dst, src, shift); + eor(dst, dst, (uint64_t)base); break; - case KlassDecodeMovk: { + case KlassDecodeMovk: { // 1-3 instructions const uint64_t shifted_base = - (uint64_t)CompressedKlassPointers::base() >> CompressedKlassPointers::shift(); + (uint64_t)base >> shift; if (dst != src) movw(dst, src); movk(dst, shifted_base >> 32, 32); + lsl(dst, dst, shift); + break; + } - if (CompressedKlassPointers::shift() != 0) { - lsl(dst, dst, CompressedKlassPointers::shift()); - } - + case KlassDecodeFallback: { // 3-4 instructions + mov(tmp, base); + add(dst, tmp, src, LSL, shift); break; } @@ -5526,10 +5505,14 @@ void MacroAssembler::decode_klass_not_null(Register dst, Register src) { ShouldNotReachHere(); break; } -} -void MacroAssembler::decode_klass_not_null(Register r) { - decode_klass_not_null(r, r); +#ifdef ASSERT + // Always clobber tmp + if (tmp != dst) { + mov(tmp, 0xdead); + } +#endif // ASSERT + } void MacroAssembler::set_narrow_oop(Register dst, jobject obj) { @@ -7181,7 +7164,7 @@ void MacroAssembler::fast_lock(Register basic_lock, Register obj, Register t1, R } if (DiagnoseSyncOnValueBasedClasses != 0) { - load_klass(t1, obj); + load_klass(t1, obj, rscratch1); ldrb(t1, Address(t1, Klass::misc_flags_offset())); tst(t1, KlassFlags::_misc_is_value_based_class); br(Assembler::NE, slow); diff --git a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp index b39596aab53..6dfdde51ac5 100644 --- a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp +++ b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.hpp @@ -38,6 +38,7 @@ #include "utilities/powerOfTwo.hpp" class OopMap; +struct GtestFriendToMacroAssembler; // MacroAssembler extends Assembler by frequently used macros. // @@ -46,6 +47,7 @@ class OopMap; class MacroAssembler: public Assembler { friend class LIR_Assembler; + friend struct GtestFriendToMacroAssembler; public: using Assembler::mov; @@ -91,28 +93,31 @@ class MacroAssembler: public Assembler { void call_VM_helper(Register oop_result, address entry_point, int number_of_arguments, bool check_exceptions = true); + private: + enum KlassDecodeMode { KlassDecodeNone, KlassDecodeZero, KlassDecodeXor, - KlassDecodeMovk + KlassDecodeMovk, + KlassDecodeFallback }; - // Calculate decoding mode based on given parameters, used for checking then ultimately setting. - static KlassDecodeMode klass_decode_mode(address base, int shift, const size_t range); - - private: static KlassDecodeMode _klass_decode_mode; // Returns above setting with asserts static KlassDecodeMode klass_decode_mode(); - public: - // Checks the decode mode and returns false if not compatible with preferred decoding mode. - static bool check_klass_decode_mode(address base, int shift, const size_t range); + // Calculate decoding mode based on given parameters, used for checking then ultimately setting. + static KlassDecodeMode klass_decode_mode(address base, int shift, const size_t range); - // Sets the decode mode and returns false if cannot be set. - static bool set_klass_decode_mode(address base, int shift, const size_t range); + void emit_encode_klass_not_null(Register dst, Register src, Register tmp, + address base, int shift, KlassDecodeMode decode_mode); + void emit_decode_klass_not_null(Register dst, Register src, Register tmp, + address base, int shift, KlassDecodeMode decode_mode); + public: + // Determines the decode mode best suited for the given encoding parameters. + static void initialize_klass_decode_mode(address base, int shift, const size_t range); public: MacroAssembler(CodeBuffer* code) : Assembler(code) {} @@ -308,19 +313,27 @@ class MacroAssembler: public Assembler { } inline void lslw(Register Rd, Register Rn, unsigned imm) { - ubfmw(Rd, Rn, ((32 - imm) & 31), (31 - imm)); + if (imm > 0 || Rd != Rn) { + ubfmw(Rd, Rn, ((32 - imm) & 31), (31 - imm)); + } } inline void lsl(Register Rd, Register Rn, unsigned imm) { - ubfm(Rd, Rn, ((64 - imm) & 63), (63 - imm)); + if (imm > 0 || Rd != Rn) { + ubfm(Rd, Rn, ((64 - imm) & 63), (63 - imm)); + } } inline void lsrw(Register Rd, Register Rn, unsigned imm) { - ubfmw(Rd, Rn, imm, 31); + if (imm > 0 || Rd != Rn) { + ubfmw(Rd, Rn, imm, 31); + } } inline void lsr(Register Rd, Register Rn, unsigned imm) { - ubfm(Rd, Rn, imm, 63); + if (imm > 0 || Rd != Rn) { + ubfm(Rd, Rn, imm, 63); + } } inline void rorw(Register Rd, Register Rn, unsigned imm) { @@ -925,9 +938,9 @@ public: // oop manipulations void load_narrow_klass_compact(Register dst, Register src); void load_narrow_klass(Register dst, Register src); - void load_klass(Register dst, Register src); - void store_klass(Register dst, Register src); - void cmp_klass(Register obj, Register klass, Register tmp); + void load_klass(Register dst, Register src, Register tmp); + void store_klass(Register dst, Register src, Register tmp); + void cmp_klass(Register obj, Register klass, Register tmp, Register tmp2); void cmp_klasses_from_objects(Register obj1, Register obj2, Register tmp1, Register tmp2); void resolve_weak_handle(Register result, Register tmp1, Register tmp2); @@ -972,12 +985,8 @@ public: void set_narrow_oop(Register dst, jobject obj); - void decode_klass_not_null_for_aot(Register dst, Register src); - void encode_klass_not_null_for_aot(Register dst, Register src); - void encode_klass_not_null(Register r); - void decode_klass_not_null(Register r); - void encode_klass_not_null(Register dst, Register src); - void decode_klass_not_null(Register dst, Register src); + void encode_klass_not_null(Register dst, Register src, Register tmp); + void decode_klass_not_null(Register dst, Register src, Register tmp); void set_narrow_klass(Register dst, Klass* k); diff --git a/src/hotspot/cpu/aarch64/methodHandles_aarch64.cpp b/src/hotspot/cpu/aarch64/methodHandles_aarch64.cpp index cdf67e3423f..7dc74f44cdc 100644 --- a/src/hotspot/cpu/aarch64/methodHandles_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/methodHandles_aarch64.cpp @@ -76,7 +76,7 @@ void MethodHandles::verify_klass(MacroAssembler* _masm, __ verify_oop(obj); __ cbz(obj, L_bad); __ push(RegSet::of(temp, temp2), sp); - __ load_klass(temp, obj); + __ load_klass(temp, obj, temp2); __ cmpptr(temp, ExternalAddress((address) klass_addr)); __ br(Assembler::EQ, L_ok); intptr_t super_check_offset = klass->super_check_offset(); @@ -368,7 +368,7 @@ void MethodHandles::generate_method_handle_dispatch(MacroAssembler* _masm, __ null_check(receiver_reg); } else { // load receiver klass itself - __ load_klass(temp1_recv_klass, receiver_reg); + __ load_klass(temp1_recv_klass, receiver_reg, temp2); __ verify_klass_ptr(temp1_recv_klass); } BLOCK_COMMENT("check_receiver {"); @@ -376,7 +376,7 @@ void MethodHandles::generate_method_handle_dispatch(MacroAssembler* _masm, // Check the receiver against the MemberName.clazz if (VerifyMethodHandles && iid == vmIntrinsics::_linkToSpecial) { // Did not load it above... - __ load_klass(temp1_recv_klass, receiver_reg); + __ load_klass(temp1_recv_klass, receiver_reg, temp2); __ verify_klass_ptr(temp1_recv_klass); } if (VerifyMethodHandles && iid != vmIntrinsics::_linkToInterface) { diff --git a/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp b/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp index 5dfd41293fd..03eb5084eb4 100644 --- a/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp @@ -2258,7 +2258,7 @@ class StubGenerator: public StubCodeGenerator { // checked. assert_different_registers(from, to, count, ckoff, ckval, start_to, - copied_oop, r19_klass, count_save); + copied_oop, r19_klass, count_save, rscratch1); __ align(CodeEntryAlignment); StubCodeMark mark(this, stub_id); @@ -2342,7 +2342,7 @@ class StubGenerator: public StubCodeGenerator { gct1); __ cbz(copied_oop, L_store_element); - __ load_klass(r19_klass, copied_oop);// query the object klass + __ load_klass(r19_klass, copied_oop, rscratch1);// query the object klass BLOCK_COMMENT("type_check:"); generate_type_check(/*sub_klass*/r19_klass, @@ -2583,7 +2583,7 @@ class StubGenerator: public StubCodeGenerator { BLOCK_COMMENT("} assert klasses not null done"); } #endif - __ decode_klass_not_null(scratch_src_klass, scratch_src_klass); + __ decode_klass_not_null(scratch_src_klass, scratch_src_klass, rscratch1); // Load layout helper (32-bits) // @@ -2603,7 +2603,7 @@ class StubGenerator: public StubCodeGenerator { __ cbzw(rscratch2, L_objArray); // if (src->klass() != dst->klass()) return -1; - __ load_klass(rscratch2, dst); + __ load_klass(rscratch2, dst, rscratch1); __ eor(rscratch2, rscratch2, scratch_src_klass); __ cbnz(rscratch2, L_failed); @@ -2699,7 +2699,7 @@ class StubGenerator: public StubCodeGenerator { Label L_plain_copy, L_checkcast_copy; // test array classes for subtyping - __ load_klass(r15, dst); + __ load_klass(r15, dst, rscratch1); __ cmp(scratch_src_klass, r15); // usual case is exact equality __ br(Assembler::NE, L_checkcast_copy); @@ -2728,7 +2728,7 @@ class StubGenerator: public StubCodeGenerator { arraycopy_range_checks(src, src_pos, dst, dst_pos, scratch_length, r15, L_failed); - __ load_klass(dst_klass, dst); // reload + __ load_klass(dst_klass, dst, rscratch1); // reload // Marshal the base address arguments now, freeing registers. __ lea(from, Address(src, src_pos, Address::lsl(LogBytesPerHeapOop))); diff --git a/src/hotspot/cpu/aarch64/templateTable_aarch64.cpp b/src/hotspot/cpu/aarch64/templateTable_aarch64.cpp index b6cf58d6062..a0ce1d04317 100644 --- a/src/hotspot/cpu/aarch64/templateTable_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/templateTable_aarch64.cpp @@ -1123,9 +1123,9 @@ void TemplateTable::aastore() { __ cbz(r0, is_null); // Move subklass into r1 - __ load_klass(r1, r0); + __ load_klass(r1, r0, rscratch1); // Move superklass into r0 - __ load_klass(r0, r3); + __ load_klass(r0, r3, rscratch1); __ ldr(r0, Address(r0, ObjArrayKlass::element_klass_offset())); // Compress array + index*oopSize + 12 into a single register. Frees r2. @@ -1173,7 +1173,7 @@ void TemplateTable::bastore() // Need to check whether array is boolean or byte // since both types share the bastore bytecode. - __ load_klass(r2, r3); + __ load_klass(r2, r3, rscratch1); __ ldrw(r2, Address(r2, Klass::layout_helper_offset())); int diffbit_index = exact_log2(Klass::layout_helper_boolean_diffbit()); Label L_skip; @@ -2194,7 +2194,7 @@ void TemplateTable::_return(TosState state) assert(state == vtos, "only valid state"); __ ldr(c_rarg1, aaddress(0)); - __ load_klass(r3, c_rarg1); + __ load_klass(r3, c_rarg1, rscratch1); __ ldrb(r3, Address(r3, Klass::misc_flags_offset())); Label skip_register_finalizer; __ tbz(r3, exact_log2(KlassFlags::_misc_has_finalizer), skip_register_finalizer); @@ -3338,8 +3338,8 @@ void TemplateTable::invokevirtual_helper(Register index, Register recv, Register flags) { - // Uses temporary registers r0, r3 - assert_different_registers(index, recv, r0, r3); + // Uses temporary registers r0, r3, rscratch1 + assert_different_registers(index, recv, r0, r3, rscratch1); // Test for an invoke of a final method Label notFinal; __ tbz(flags, ResolvedMethodEntry::is_vfinal_shift, notFinal); @@ -3363,7 +3363,7 @@ void TemplateTable::invokevirtual_helper(Register index, __ bind(notFinal); // get receiver klass - __ load_klass(r0, recv); + __ load_klass(r0, recv, rscratch1); // profile this call __ profile_virtual_call(r0, rlocals); @@ -3464,7 +3464,7 @@ void TemplateTable::invokeinterface(int byte_no) { __ tbz(r3, ResolvedMethodEntry::is_vfinal_shift, notVFinal); // Get receiver klass into r3 - __ load_klass(r3, r2); + __ load_klass(r3, r2, rscratch1); Label subtype; __ check_klass_subtype(r3, r0, r4, subtype); @@ -3479,7 +3479,7 @@ void TemplateTable::invokeinterface(int byte_no) { __ bind(notVFinal); // Get receiver klass into r3 - __ load_klass(r3, r2); + __ load_klass(r3, r2, rscratch1); Label no_such_method; @@ -3678,7 +3678,7 @@ void TemplateTable::_new() { __ mov(rscratch1, (intptr_t)markWord::prototype().value()); __ str(rscratch1, Address(r0, oopDesc::mark_offset_in_bytes())); __ store_klass_gap(r0, zr); // zero klass gap for compressed oops - __ store_klass(r0, r4); // store klass last + __ store_klass(r0, r4, rscratch1); // store klass last } if (DTraceAllocProbes) { @@ -3759,7 +3759,7 @@ void TemplateTable::checkcast() __ load_resolved_klass_at_offset(r2, r19, r0, rscratch1); // r0 = klass __ bind(resolved); - __ load_klass(r19, r3); + __ load_klass(r19, r3, rscratch1); // Generate subtype check. Blows r2, r5. Object in r3. // Superklass in r0. Subklass in r19. @@ -3805,12 +3805,12 @@ void TemplateTable::instanceof() { __ get_vm_result_metadata(r0, rthread); __ pop(r3); // restore receiver __ verify_oop(r3); - __ load_klass(r3, r3); + __ load_klass(r3, r3, rscratch1); __ b(resolved); // Get superklass in r0 and subklass in r3 __ bind(quicked); - __ load_klass(r3, r0); + __ load_klass(r3, r0, rscratch1); __ load_resolved_klass_at_offset(r2, r19, r0, rscratch1); __ bind(resolved); diff --git a/src/hotspot/cpu/aarch64/vtableStubs_aarch64.cpp b/src/hotspot/cpu/aarch64/vtableStubs_aarch64.cpp index 714904ab3df..1b7820fc337 100644 --- a/src/hotspot/cpu/aarch64/vtableStubs_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/vtableStubs_aarch64.cpp @@ -79,7 +79,7 @@ VtableStub* VtableStubs::create_vtable_stub(int vtable_index) { // get receiver klass address npe_addr = __ pc(); - __ load_klass(r16, j_rarg0); + __ load_klass(r16, j_rarg0, rscratch1); #ifndef PRODUCT if (DebugVtables) { @@ -189,7 +189,7 @@ VtableStub* VtableStubs::create_itable_stub(int itable_index) { // get receiver klass (also an implicit null-check) address npe_addr = __ pc(); - __ load_klass(recv_klass_reg, j_rarg0); + __ load_klass(recv_klass_reg, j_rarg0, rscratch1); // Receiver subtype check against REFC. // Get selected method from declaring class and itable index diff --git a/src/hotspot/share/cds/aotMetaspace.cpp b/src/hotspot/share/cds/aotMetaspace.cpp index fbd12038c94..8106258c331 100644 --- a/src/hotspot/share/cds/aotMetaspace.cpp +++ b/src/hotspot/share/cds/aotMetaspace.cpp @@ -165,22 +165,15 @@ size_t AOTMetaspace::protection_zone_size() { } bool AOTMetaspace::shared_base_valid(char* shared_base) { - // We check user input for SharedBaseAddress at dump time. - // At CDS runtime, "shared_base" will be the (attempted) mapping start. It will also // be the encoding base, since the headers of archived base objects (and with Lilliput, // the prototype mark words) carry pre-computed narrow Klass IDs that refer to the mapping // start as base. - // - // The "shared_base" may not be later usable as encoding base, depending on the - // total size of the reserved area and the precomputed_narrow_klass_shift. This is checked - // before reserving memory. Here we weed out values already known to be invalid later. - // Since we cannot predict the range, we use the full maximum encoding range - // (4G). - constexpr size_t range = 4 * G; - address addr = (address)shared_base; - const int shift = ArchiveBuilder::precomputed_narrow_klass_shift(); - return CompressedKlassPointers::check_klass_decode_mode(addr, shift, range); + // Note that all narrowKlass inside CDS/AOT archives will be precomputed with the + // shift that, at build time, will afford us the maximum encoding range of 4GB. We do this + // since we don't know how large the class space at runtime will actually be. + return CLASS_SPACE_ONLY(is_aligned(shared_base, Metaspace::reserve_alignment())) + NOT_CLASS_SPACE(true); } class DumpClassListCLDClosure : public CLDClosure { @@ -1976,16 +1969,11 @@ char* AOTMetaspace::reserve_address_space_for_archives(FileMapInfo* static_mapin const size_t total_range_size = archive_space_size + gap_size + class_space_size; - // The code for dumping the archive ensures that the base address is valid. - // Here we validate that the base address plus shift can be decoded when - // restored. - assert(shared_base_valid((char*)base_address), - "Cannot use SharedBaseAddress " PTR_FORMAT " with precomputed shift %d.", - p2i(base_address), ArchiveBuilder::precomputed_narrow_klass_shift()); - assert(total_range_size > ccs_begin_offset, "must be"); if (use_windows_memory_mapping() && use_archive_base_addr) { if (base_address != nullptr) { + // Note: We already checked the base address for validity at dump time. + // On Windows, we cannot safely split a reserved memory space into two (see JDK-8255917). // Hence, we optimistically reserve archive space and class space side-by-side. We only // do this for use_archive_base_addr=true since for use_archive_base_addr=false case diff --git a/src/hotspot/share/memory/metaspace.cpp b/src/hotspot/share/memory/metaspace.cpp index 8b8b80cd893..43bd5e452c8 100644 --- a/src/hotspot/share/memory/metaspace.cpp +++ b/src/hotspot/share/memory/metaspace.cpp @@ -593,7 +593,8 @@ ReservedSpace Metaspace::reserve_address_space_for_compressed_classes(size_t siz optimize_for_zero_base)); if (result == nullptr) { - // Fallback: reserve anywhere + // Fallback: we let the OS decide where to place the area, but align (overallocation-and-cut) + // to metaspace reserve alignment (16MB). log_debug(metaspace, map)("Trying anywhere..."); result = os::reserve_memory_aligned(size, Metaspace::reserve_alignment(), mtClass); } diff --git a/src/hotspot/share/oops/compressedKlass.cpp b/src/hotspot/share/oops/compressedKlass.cpp index ca1c46d4095..134f5a93365 100644 --- a/src/hotspot/share/oops/compressedKlass.cpp +++ b/src/hotspot/share/oops/compressedKlass.cpp @@ -188,11 +188,7 @@ void CompressedKlassPointers::initialize_for_given_encoding(address addr, size_t calc_lowest_highest_narrow_klass_id(); - // This has already been checked for SharedBaseAddress and if this fails, it's a bug in the allocation code. - if (!set_klass_decode_mode()) { - fatal("base=" PTR_FORMAT " given with shift %d, cannot be used to encode class pointers", - p2i(_base), _shift); - } + initialize_pd(); DEBUG_ONLY(sanity_check_after_initialization();) } @@ -299,20 +295,7 @@ void CompressedKlassPointers::initialize(address addr, size_t len) { calc_lowest_highest_narrow_klass_id(); - // Initialize JIT-specific decoding settings - if (!set_klass_decode_mode()) { - - // Give fatal error if this is a specified address - if (CompressedClassSpaceBaseAddress == (size_t)_base) { - vm_exit_during_initialization( - err_msg("CompressedClassSpaceBaseAddress=" PTR_FORMAT " given with shift %d, cannot be used to encode class pointers", - CompressedClassSpaceBaseAddress, _shift)); - } else { - // If this fails, it's a bug in the allocation code. - fatal("CompressedClassSpaceBaseAddress=" PTR_FORMAT " given with shift %d, cannot be used to encode class pointers", - p2i(_base), _shift); - } - } + initialize_pd(); DEBUG_ONLY(sanity_check_after_initialization();) } diff --git a/src/hotspot/share/oops/compressedKlass.hpp b/src/hotspot/share/oops/compressedKlass.hpp index fe1ce9e07ae..ff2dd15eb75 100644 --- a/src/hotspot/share/oops/compressedKlass.hpp +++ b/src/hotspot/share/oops/compressedKlass.hpp @@ -270,15 +270,8 @@ public: // Returns true if address points into protection zone (for error reporting) static bool is_in_protection_zone(address addr); -#if defined(AARCH64) && !defined(ZERO) - // Check that with the given base, shift and range, aarch64 code can encode and decode the klass pointer. - static bool check_klass_decode_mode(address base, int shift, const size_t range); - // Called after initialization. - static bool set_klass_decode_mode(); -#else - static bool check_klass_decode_mode(address base, int shift, const size_t range) { return true; } - static bool set_klass_decode_mode() { return true; } -#endif + // platform-specific initializations + static void initialize_pd() NOT_AARCH64({}); }; #endif // SHARE_OOPS_COMPRESSEDKLASS_HPP diff --git a/test/hotspot/gtest/aarch64/test_assembler_aarch64.cpp b/test/hotspot/gtest/aarch64/test_assembler_aarch64.cpp index 63b8ddd865d..db240aeee90 100644 --- a/test/hotspot/gtest/aarch64/test_assembler_aarch64.cpp +++ b/test/hotspot/gtest/aarch64/test_assembler_aarch64.cpp @@ -30,9 +30,15 @@ #include "asm/macroAssembler.hpp" #include "compiler/disassembler.hpp" #include "memory/resourceArea.hpp" +#include "runtime/threadWXSetters.inline.hpp" +#include "utilities/powerOfTwo.hpp" #include "nativeInst_aarch64.hpp" #include "unittest.hpp" +// remove comment for debug log +//#define LOG_PLEASE +#include "testutils.hpp" + #define __ _masm. static void asm_check(const unsigned int *insns, const unsigned int *insns1, size_t len) { @@ -511,4 +517,137 @@ TEST_VM(AssemblerAArch64, native_instruction_load_predicates) { EXPECT_FALSE(ni_ldrs->is_ldrw_gpr_literal()); } +struct GtestFriendToMacroAssembler { + + typedef MacroAssembler::KlassDecodeMode Mode; + + typedef address (*decode_function)(narrowKlass encoded); + typedef narrowKlass (*encode_function)(address decoded); + + using CKP = CompressedKlassPointers; + using MA = MacroAssembler; + + static void build_and_run_encode_decode_klass(address base, int shift, + Mode expected_mode) { + + if ((shift + CKP::narrow_klass_pointer_bits()) > 32) { + return; // unsupported + } + + LOG_HERE("base " PTR_FORMAT " shift %d => mode %d: ", + p2u(base), shift, (int)expected_mode); + + // Test if the given base+shift value (with an assumed maximum Klass* range) + // yields the expected decode mode + const Mode real_mode = MA::klass_decode_mode(base, shift, CKP::max_klass_range_size()); + + ASSERT_EQ(real_mode, expected_mode) << " different mode?"; + + // Now generate encode and decode functions for this base and shift ... + BufferBlob* bb = BufferBlob::create("test_decode_klass", 512); + CodeBuffer code(bb); + address entry_encode = nullptr; + address entry_decode = nullptr; + + { + MA masm(&code); + + entry_encode = masm.pc(); + masm.emit_encode_klass_not_null(c_rarg0, // x0: dst+return + c_rarg0, // x0: src + rscratch1, // x8: tmp + base, shift, + real_mode); + masm.ret(lr); + + entry_decode = masm.pc(); + masm.emit_decode_klass_not_null(c_rarg0, // x0: dst+return + c_rarg0, // x0: src + rscratch1, // x8: tmp + base, shift, + real_mode); + masm.ret(lr); + + masm.flush(); // icache invalidate + } + + { + MACOS_AARCH64_ONLY(ThreadWXEnable wx(WXExec, Thread::current())); + + // ... and call it with some values spread over the full width of the narrowKlass range. + const narrowKlass highest = right_n_bits(CKP::narrow_klass_pointer_bits()); + + const struct { narrowKlass encoded; address decoded; } testvalues [] = { + { 0, base }, + // The highest value we can express with the current narrowKlass width + { highest, (address)(p2u(base) + ((uint64_t)highest << shift)) }, + // midpoint + { highest / 2, (address)(p2u(base) + (((uint64_t)highest / 2) << shift)) } + }; + constexpr int num_testvalues = sizeof(testvalues) / sizeof(testvalues[0]); + + for (int i = 0; i < num_testvalues; i++) { + const narrowKlass encoded = testvalues[i].encoded; + const address decoded = testvalues[i].decoded; + + const narrowKlass encoded_real = ((encode_function)entry_encode)(decoded); + LOG_HERE(" encode: " PTR_FORMAT " => " UINT32_FORMAT_X, p2u(decoded), encoded_real); + EXPECT_EQ(encoded_real, encoded) << " bad encode?"; + + const address decoded_real = ((decode_function)entry_decode)(encoded); + LOG_HERE(" decode: " UINT32_FORMAT_X " => " PTR_FORMAT, encoded, p2u(decoded_real)); + EXPECT_EQ(decoded_real, decoded) << " bad decode?"; + } + } + BufferBlob::free(bb); + } + + static void test_decode_encode_klass() { + + for (int shift = 0; shift < CKP::max_shift(); shift++) { + + // test zero-based + build_and_run_encode_decode_klass((address)nullptr, shift, MA::KlassDecodeZero); + + // test XOR-based encoding + // Base must be a valid immediate that does not intersect the highest left-shifted nKlass + const int lowest_xor_base_bit = 32; + const int highest_xor_base_bit = 51; // highest user address space bit on all our platforms + + // Highest base bit set + build_and_run_encode_decode_klass((address)nth_bit(highest_xor_base_bit), shift, MA::KlassDecodeXor); + // lowest base bit set + build_and_run_encode_decode_klass((address)nth_bit(lowest_xor_base_bit), shift, MA::KlassDecodeXor); + // all base bits set + build_and_run_encode_decode_klass((address)(right_n_bits(highest_xor_base_bit - lowest_xor_base_bit) << lowest_xor_base_bit), + shift, MA::KlassDecodeXor); + + // test movk-based + // Only bits in the third quadrant and not a valid immediate + build_and_run_encode_decode_klass((address)0x0000'A000'0000'0000ULL, 0, MA::KlassDecodeMovk); + + // test Fallback mode. + // base has low bits that intersect with nKlass, no other mode would work + build_and_run_encode_decode_klass((address)(0x5'0000'0000ULL + os::vm_page_size()), + shift, MA::KlassDecodeFallback); + build_and_run_encode_decode_klass((address)(0x5'0000'0000ULL - os::vm_page_size()), + shift, MA::KlassDecodeFallback); + + // a base that has ones in all four quadrants to trigger the full movz+3*movk path + // when loading the immediate + build_and_run_encode_decode_klass((address)right_n_bits(52), + shift, MA::KlassDecodeFallback); + + // spread over multiple 16-bit quadrants and not encodable as immediate, + // no other mode would work + build_and_run_encode_decode_klass((address)0x00AA'AAA0'0000'0000ULL, + shift, MA::KlassDecodeFallback); + } + } +}; + +// Run this with and without UseCompactObjectHeaders +TEST_VM(AssemblerAArch64, decode_encode_klass_not_null) { + GtestFriendToMacroAssembler::test_decode_encode_klass(); +} #endif // AARCH64 diff --git a/test/hotspot/jtreg/gtest/AssemblerGtests.java b/test/hotspot/jtreg/gtest/AssemblerGtests.java new file mode 100644 index 00000000000..19fb3398267 --- /dev/null +++ b/test/hotspot/jtreg/gtest/AssemblerGtests.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2026, IBM Corp. All rights reserved. + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +/* + * This runs the MacroAssembler gtests related to Klass de- and encoding + * (for now, only on aarch64) with and without COH. + */ + +/* @test id=coh + * @summary Run Assembler-related gtests + * @library /test/lib + * @modules java.base/jdk.internal.misc + * java.xml + * @requires vm.flagless + * @requires os.arch=="aarch64" + * @run main/native GTestWrapper --gtest_filter=AssemblerAArch64::decode_encode_klass* -XX:+UseCompactObjectHeaders + */ + +/* @test id=noncoh + * @summary Run Assembler-related gtests + * @library /test/lib + * @modules java.base/jdk.internal.misc + * java.xml + * @requires vm.flagless + * @requires os.arch=="aarch64" + * @run main/native GTestWrapper --gtest_filter=AssemblerAArch64::decode_encode_klass* -XX:-UseCompactObjectHeaders + */ + diff --git a/test/hotspot/jtreg/runtime/CompressedOops/CompressedClassPointersEncodingScheme.java b/test/hotspot/jtreg/runtime/CompressedOops/CompressedClassPointersEncodingScheme.java index d14dbc93245..f4ef0800a73 100644 --- a/test/hotspot/jtreg/runtime/CompressedOops/CompressedClassPointersEncodingScheme.java +++ b/test/hotspot/jtreg/runtime/CompressedOops/CompressedClassPointersEncodingScheme.java @@ -54,7 +54,7 @@ public class CompressedClassPointersEncodingScheme { "-XX:" + (COH ? "+" : "-") + "UseObjectMonitorTable", "-XX:CompressedClassSpaceBaseAddress=" + forceAddress, "-XX:CompressedClassSpaceSize=" + classSpaceSize, - "-Xmx128m", + "-Xmx64m", "-Xlog:metaspace*", "-version"); OutputAnalyzer output = new OutputAnalyzer(pb.start()); @@ -71,35 +71,6 @@ public class CompressedClassPointersEncodingScheme { output.shouldContain("Narrow klass base: " + expectedEncodingBaseString + ", Narrow klass shift: " + expectedEncodingShift); } - private static void testFailure(String forceAddressString) throws IOException { - ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder( - "-Xshare:off", // to make CompressedClassSpaceBaseAddress work - "-XX:+UnlockExperimentalVMOptions", - "-XX:+UnlockDiagnosticVMOptions", - "-XX:-UseCompactObjectHeaders", - "-XX:CompressedClassSpaceBaseAddress=" + forceAddressString, - "-Xmx128m", - "-Xlog:metaspace*", - "-version"); - OutputAnalyzer output = new OutputAnalyzer(pb.start()); - - output.reportDiagnosticSummary(); - - // We ignore cases where we were not able to map at the force address - if (!output.contains("Successfully forced class space address to " + forceAddressString)) { - throw new SkippedException("Skipping because we cannot force ccs to " + forceAddressString); - } - - if (Platform.isAArch64()) { - output.shouldHaveExitValue(1); - output.shouldContain("Error occurred during initialization of VM"); - output.shouldContain("CompressedClassSpaceBaseAddress=" + forceAddressString + - " given with shift 0, cannot be used to encode class pointers"); - } else { - output.shouldHaveExitValue(0); - } - } - final static long K = 1024; final static long M = K * 1024; final static long G = M * 1024; @@ -108,53 +79,47 @@ public class CompressedClassPointersEncodingScheme { // Expecting base=0, shift=0 test(4 * G - 128 * M, false, 128 * M, 0, 0); - // Test ccs nestling right at the end of the 32G range - // Expecting: - // - non-aarch64: base=0, shift=3 - // - aarch64: base to start of class range, shift 0 - if (Platform.isAArch64()) { - // The best we can do on aarch64 is to be *near* the end of the 32g range, since a valid encoding base - // on aarch64 must be 4G aligned, and the max. class space size is 3G. - long forceAddress = 0x7_0000_0000L; // 28g, and also a valid EOR immediate - test(forceAddress, false, 3 * G, forceAddress, 0); - } else { - test(32 * G - 128 * M, false, 128 * M, 0, 3); - } + // aarch64 does not do extended zero based encoding (shift>0) + boolean expectExtendedZeroBasedEncoding = !Platform.isAArch64(); - // Test ccs starting *below* 4G, but extending upwards beyond 4G. All platforms except aarch64 should pick - // zero based encoding. On aarch64, this test is excluded since the only valid mode would be XOR, but bit - // pattern for base and bit pattern would overlap. - if (!Platform.isAArch64()) { - test(4 * G - 128 * M, false, 2 * 128 * M, 0, 3); - } - // add more... + // Test ccs nestling right at the end of the 32G range. + // Expect all platforms but aarch64 to do shift-extended zero-based encoding; + long forceAddress = 32 * G - 128 * M; + test(forceAddress, false, 128 * M, + expectExtendedZeroBasedEncoding ? 0 : forceAddress, // expected base + expectExtendedZeroBasedEncoding ? 3 : 0 // expected shift + ); + + // Test ccs starting *below* 4G, but extending upwards beyond 4G. + // Expect all platforms but aarch64 to do shift-extended zero-based encoding; aarch64 does not do that but + // drops right to non-zero-based with shift = 0 + forceAddress = 4 * G - 128 * M; + test(forceAddress, false, 2 * 128 * M, + expectExtendedZeroBasedEncoding ? 0 : forceAddress, // expected base + expectExtendedZeroBasedEncoding ? 3 : 0 // expected shift + ); // Compact Object Header Mode: - // On aarch64 and x64 we expect the VM to chose the smallest possible shift value needed to cover - // the encoding range. We expect the encoding Base to start at the class space start - but to enforce that, - // we choose a high address. - if (Platform.isAArch64() || Platform.isX64() || Platform.isRISCV64()) { - long forceAddress = 32 * G; + // We expect the VM to chose the smallest possible shift value needed to cover the encoding range. + // We expect the encoding Base to start at the class space start - but to enforce that, + // we choose unsuited to even shift-extended zero-based mode. + forceAddress = 32 * G; - long ccsSize = 128 * M; - int expectedShift = 6; - test(forceAddress, true, ccsSize, forceAddress, expectedShift); + test(forceAddress, true, 128 * M, forceAddress, 6); + test(forceAddress, true, 256 * M, forceAddress, 7); + test(forceAddress, true, 512 * M, forceAddress, 8); + test(forceAddress, true, G, forceAddress, 9); + test(forceAddress, true, 3 * G, forceAddress, 10); - ccsSize = 512 * M; - expectedShift = 8; - test(forceAddress, true, ccsSize, forceAddress, expectedShift); - - ccsSize = G; - expectedShift = 9; - test(forceAddress, true, ccsSize, forceAddress, expectedShift); - - ccsSize = 3 * G; - expectedShift = 10; - test(forceAddress, true, ccsSize, forceAddress, expectedShift); - } - - // Test failure for -XX:CompressedClassBaseAddress and -Xshare:off - testFailure("0x0000040001000000"); + // Test a "crooked" base address: + // - just aligned enough to pass metaspace reserve alignment test of 16MB. + // - not encodable on aarch64 as logical immediate + // - sufficiently complex enough to need multiple moves on risc platforms to materialize as immediate + // - small enough to not cause test errors on small devices (e.g. arm64 39bit address space) + // - large enough to not end up with zero-based encoding + forceAddress = 0x0000000d55000000L; + test(forceAddress, true, 32 * M, forceAddress, 6); + test(forceAddress, false, 32 * M, forceAddress, 0); } } diff --git a/test/hotspot/jtreg/runtime/ErrorHandling/AccessZeroNKlassHitsProtectionZone.java b/test/hotspot/jtreg/runtime/ErrorHandling/AccessZeroNKlassHitsProtectionZone.java index 61d017d2264..4e177a6fe1d 100644 --- a/test/hotspot/jtreg/runtime/ErrorHandling/AccessZeroNKlassHitsProtectionZone.java +++ b/test/hotspot/jtreg/runtime/ErrorHandling/AccessZeroNKlassHitsProtectionZone.java @@ -126,7 +126,7 @@ public class AccessZeroNKlassHitsProtectionZone { private static void run_test(boolean COH, boolean CDS) throws IOException, SkippedException { // Notes: - // We want to enforce zero-based encoding, to test the protection page in that case. For zero-based encoding, + // We want to enforce non-zero-based encoding, to test the protection page in that case. For zero-based encoding, // protection page is at address zero, no need to test that. // If CDS is on, we never use zero-based, forceBase is ignored. // If CDS is off, we use forceBase to (somewhat) reliably force the encoding base to beyond 32G, From 5b3456ce70dc4fb0bc28ff8bacb00a0ec504400b Mon Sep 17 00:00:00 2001 From: Roland Westrelin Date: Fri, 17 Jul 2026 15:02:34 +0000 Subject: [PATCH 187/305] 8388406: [BACKOUT] C2: crash in compiled code due to zero division because of widened CastII Reviewed-by: thartmann, chagedorn --- src/hotspot/share/opto/c2_globals.hpp | 4 +- src/hotspot/share/opto/castnode.cpp | 3 + src/hotspot/share/opto/cfgnode.cpp | 14 +- src/hotspot/share/opto/classes.hpp | 1 - src/hotspot/share/opto/compile.cpp | 25 +- src/hotspot/share/opto/compile.hpp | 9 +- src/hotspot/share/opto/convertnode.cpp | 14 + src/hotspot/share/opto/divnode.cpp | 14 - src/hotspot/share/opto/divnode.hpp | 13 +- src/hotspot/share/opto/loopopts.cpp | 2 +- src/hotspot/share/opto/movenode.cpp | 5 + src/hotspot/share/opto/node.cpp | 37 +- src/hotspot/share/opto/node.hpp | 13 +- src/hotspot/share/opto/parse2.cpp | 2 +- src/hotspot/share/opto/phaseX.cpp | 123 +- src/hotspot/share/opto/phaseX.hpp | 6 +- src/hotspot/share/opto/rootnode.cpp | 45 - src/hotspot/share/opto/rootnode.hpp | 31 - src/hotspot/share/opto/vectornode.cpp | 2 +- .../c2/TestDeadPathManyDeadDataNodes.java | 1301 ----------------- .../TestDivByZeroInLiveCFGPath.java | 64 - .../TestZeroDivModWidenedCastII.java | 1122 -------------- ...yAccessAboveRCAfterRCCastIIEliminated.java | 24 +- 23 files changed, 81 insertions(+), 2793 deletions(-) delete mode 100644 test/hotspot/jtreg/compiler/c2/TestDeadPathManyDeadDataNodes.java delete mode 100644 test/hotspot/jtreg/compiler/integerArithmetic/TestDivByZeroInLiveCFGPath.java delete mode 100644 test/hotspot/jtreg/compiler/integerArithmetic/TestZeroDivModWidenedCastII.java diff --git a/src/hotspot/share/opto/c2_globals.hpp b/src/hotspot/share/opto/c2_globals.hpp index dd9288f7617..9ff88e8c310 100644 --- a/src/hotspot/share/opto/c2_globals.hpp +++ b/src/hotspot/share/opto/c2_globals.hpp @@ -922,8 +922,8 @@ "Use StoreStore barrier instead of Release barrier at the end " \ "of constructors") \ \ - develop(bool, KillPathsReachableByDeadDataNode, true, \ - "When a data node becomes top, make paths where the node is " \ + develop(bool, KillPathsReachableByDeadTypeNode, true, \ + "When a Type node becomes top, make paths where the node is " \ "used dead by replacing them with a Halt node. Turning this off " \ "could corrupt the graph in rare cases and should be used with " \ "care.") \ diff --git a/src/hotspot/share/opto/castnode.cpp b/src/hotspot/share/opto/castnode.cpp index ef7b4d5aef3..befa208a5e2 100644 --- a/src/hotspot/share/opto/castnode.cpp +++ b/src/hotspot/share/opto/castnode.cpp @@ -111,6 +111,9 @@ Node* ConstraintCastNode::Ideal(PhaseGVN* phase, bool can_reshape) { if (in(0) != nullptr && remove_dead_region(phase, can_reshape)) { return this; } + if (in(1) != nullptr && phase->type(in(1)) != Type::TOP) { + return TypeNode::Ideal(phase, can_reshape); + } return nullptr; } diff --git a/src/hotspot/share/opto/cfgnode.cpp b/src/hotspot/share/opto/cfgnode.cpp index ed5da046608..828e5bf299f 100644 --- a/src/hotspot/share/opto/cfgnode.cpp +++ b/src/hotspot/share/opto/cfgnode.cpp @@ -693,13 +693,14 @@ Node *RegionNode::Ideal(PhaseGVN *phase, bool can_reshape) { if (add_to_worklist) { igvn->add_users_to_worklist(this); // Check for further allowed opts } - uint edges_removed; - for (DUIterator_Last imin, i = last_outs(imin); i >= imin; i -= edges_removed) { - edges_removed = 1; + for (DUIterator_Last imin, i = last_outs(imin); i >= imin; --i) { Node* n = last_out(i); igvn->hash_delete(n); // Remove from worklist before modifying edges if (n->outcnt() == 0) { - edges_removed = n->replace_edge(this, phase->C->top(), igvn); + int uses_found = n->replace_edge(this, phase->C->top(), igvn); + if (uses_found > 1) { // (--i) done at the end of the loop. + i -= (uses_found - 1); + } continue; } if( n->is_Phi() ) { // Collapse all Phis @@ -718,7 +719,10 @@ Node *RegionNode::Ideal(PhaseGVN *phase, bool can_reshape) { } else if( n->is_Region() ) { // Update all incoming edges assert(n != this, "Must be removed from DefUse edges"); - edges_removed = n->replace_edge(this, parent_ctrl, igvn); + int uses_found = n->replace_edge(this, parent_ctrl, igvn); + if (uses_found > 1) { // (--i) done at the end of the loop. + i -= (uses_found - 1); + } } else { assert(n->in(0) == this, "Expect RegionNode to be control parent"); diff --git a/src/hotspot/share/opto/classes.hpp b/src/hotspot/share/opto/classes.hpp index c296237de37..53a72f979db 100644 --- a/src/hotspot/share/opto/classes.hpp +++ b/src/hotspot/share/opto/classes.hpp @@ -121,7 +121,6 @@ macro(CompareAndExchangeI) macro(CompareAndExchangeL) macro(CompareAndExchangeP) macro(CompareAndExchangeN) -macro(DeadPath) macro(GetAndAddB) macro(GetAndAddS) macro(GetAndAddI) diff --git a/src/hotspot/share/opto/compile.cpp b/src/hotspot/share/opto/compile.cpp index db43c6fb1c4..93d8e4c425d 100644 --- a/src/hotspot/share/opto/compile.cpp +++ b/src/hotspot/share/opto/compile.cpp @@ -312,8 +312,6 @@ void Compile::identify_useful_nodes(Unique_Node_List &useful) { // If 'top' is cached, declare it useful to preserve cached node if (cached_top_node()) { useful.push(cached_top_node()); } - if (dead_path()) { useful.push(dead_path()); } - // Push all useful nodes onto the list, breadthfirst for( uint next = 0; next < useful.size(); ++next ) { assert( next < unique(), "Unique useful nodes < total nodes"); @@ -390,7 +388,7 @@ void Compile::remove_useless_node(Node* dead) { // it reachable by adding use edges. So, we will NOT count Con nodes // as dead to be conservative about the dead node count at any // given time. - if (!dead->is_Con() && dead != dead_path()) { + if (!dead->is_Con()) { record_dead_node(dead->_idx); } if (dead->is_macro()) { @@ -686,7 +684,6 @@ Compile::Compile(ciEnv* ci_env, ciMethod* target, int osr_bci, _node_arena_one(mtCompiler, Arena::Tag::tag_node), _node_arena_two(mtCompiler, Arena::Tag::tag_node), _node_arena(&_node_arena_one), - _dead_path(nullptr), _mach_constant_base_node(nullptr), _Compile_types(mtCompiler, Arena::Tag::tag_type), _initial_gvn(nullptr), @@ -757,7 +754,6 @@ Compile::Compile(ciEnv* ci_env, ciMethod* target, int osr_bci, } Init(/*do_aliasing=*/ true); - set_dead_path(new DeadPathNode()); print_compile_messages(); @@ -967,7 +963,6 @@ Compile::Compile(ciEnv* ci_env, _node_arena_one(mtCompiler, Arena::Tag::tag_node), _node_arena_two(mtCompiler, Arena::Tag::tag_node), _node_arena(&_node_arena_one), - _dead_path(nullptr), _mach_constant_base_node(nullptr), _Compile_types(mtCompiler, Arena::Tag::tag_type), _initial_gvn(nullptr), @@ -2635,9 +2630,6 @@ void Compile::Optimize() { } } - // Unique DeadPath node should not be used anymore - _dead_path = nullptr; - print_method(PHASE_OPTIMIZE_FINISHED, 2); DEBUG_ONLY(set_phase_optimize_finished();) } @@ -3946,21 +3938,6 @@ void Compile::final_graph_reshaping_main_switch(Node* n, Final_Reshape_Counts& f break; } #endif - case Op_DeadPath: { - // The CFG inputs are dead paths. Replace the DeadPath with a Region and insert a Halt node. - assert(n->req() > 1, "why not removed if no input other than itself?"); - RegionNode* r = new RegionNode(n->req()); - for (uint i = 1; i < n->req(); ++i) { - r->set_req(i, n->in(i)); - } - n->disconnect_inputs(this); - Node* frame = start()->proj_out(TypeFunc::FramePtr); - stringStream ss; - ss.print("dead path discovered by data nodes during igvn"); - Node* halt = new HaltNode(r, frame, ss.as_string(comp_arena())); - root()->set_req(root()->find_edge(n), halt); - break; - } default: assert(!n->is_Call(), ""); assert(!n->is_Mem(), ""); diff --git a/src/hotspot/share/opto/compile.hpp b/src/hotspot/share/opto/compile.hpp index 73e136787f8..ab36f59a28f 100644 --- a/src/hotspot/share/opto/compile.hpp +++ b/src/hotspot/share/opto/compile.hpp @@ -57,7 +57,6 @@ class CallStaticJavaNode; class CloneMap; class CompilationFailureInfo; class ConnectionGraph; -class DeadPathNode; class IdealGraphPrinter; class InlineTree; class Matcher; @@ -428,7 +427,7 @@ public: private: RootNode* _root; // Unique root of compilation, or null after bail-out. Node* _top; // Unique top node. (Reset by various phases.) - DeadPathNode* _dead_path; // Unique DeadPath node + Node* _immutable_memory; // Initial memory state Node* _recent_alloc_obj; @@ -898,12 +897,6 @@ public: Arena* old_arena() { return (&_node_arena_one == _node_arena) ? &_node_arena_two : &_node_arena_one; } RootNode* root() const { return _root; } void set_root(RootNode* r) { _root = r; } - DeadPathNode* dead_path() const { return _dead_path; } - - void set_dead_path(DeadPathNode* dead_path) { - assert(_dead_path == nullptr, "can only set once"); - _dead_path = dead_path; - } StartNode* start() const; // (Derived from root.) void verify_start(StartNode* s) const NOT_DEBUG_RETURN; Node* immutable_memory(); diff --git a/src/hotspot/share/opto/convertnode.cpp b/src/hotspot/share/opto/convertnode.cpp index d706a13feb3..a495814da61 100644 --- a/src/hotspot/share/opto/convertnode.cpp +++ b/src/hotspot/share/opto/convertnode.cpp @@ -755,6 +755,13 @@ bool Compile::push_thru_add(PhaseGVN* phase, Node* z, const TypeInteger* tz, con //------------------------------Ideal------------------------------------------ Node* ConvI2LNode::Ideal(PhaseGVN* phase, bool can_reshape) { + if (in(1) != nullptr && phase->type(in(1)) != Type::TOP) { + Node* progress = TypeNode::Ideal(phase, can_reshape); + if (progress != nullptr) { + return progress; + } + } + const TypeLong* this_type = this->type()->is_long(); if (can_reshape && !phase->C->post_loop_opts_phase()) { // makes sure we run ::Value to potentially remove type assertion after loop opts @@ -857,6 +864,13 @@ const Type* ConvL2INode::Value(PhaseGVN* phase) const { // Return a node which is more "ideal" than the current node. // Blow off prior masking to int Node* ConvL2INode::Ideal(PhaseGVN* phase, bool can_reshape) { + if (in(1) != nullptr && phase->type(in(1)) != Type::TOP) { + Node* progress = TypeNode::Ideal(phase, can_reshape); + if (progress != nullptr) { + return progress; + } + } + Node *andl = in(1); uint andl_op = andl->Opcode(); if( andl_op == Op_AndL ) { diff --git a/src/hotspot/share/opto/divnode.cpp b/src/hotspot/share/opto/divnode.cpp index 3b51491294e..1687ff2cade 100644 --- a/src/hotspot/share/opto/divnode.cpp +++ b/src/hotspot/share/opto/divnode.cpp @@ -1031,10 +1031,6 @@ const Type* UDivINode::Value(PhaseGVN* phase) const { if( t1 == Type::TOP ) return Type::TOP; if( t2 == Type::TOP ) return Type::TOP; - if (t2 == TypeInt::ZERO) { - return Type::TOP; - } - // x/x == 1 since we always generate the dynamic divisor check for 0. if (in(1) == in(2)) { return TypeInt::ONE; @@ -1071,10 +1067,6 @@ const Type* UDivLNode::Value(PhaseGVN* phase) const { if( t1 == Type::TOP ) return Type::TOP; if( t2 == Type::TOP ) return Type::TOP; - if (t2 == TypeLong::ZERO) { - return Type::TOP; - } - // x/x == 1 since we always generate the dynamic divisor check for 0. if (in(1) == in(2)) { return TypeLong::ONE; @@ -1388,9 +1380,6 @@ Node* UModINode::Ideal(PhaseGVN* phase, bool can_reshape) { } const Type* UModINode::Value(PhaseGVN* phase) const { - if (phase->type(in(2)) == TypeInt::ZERO) { - return Type::TOP; - } return unsigned_mod_value(phase, this); } @@ -1531,9 +1520,6 @@ Node *UModLNode::Ideal(PhaseGVN *phase, bool can_reshape) { } const Type* UModLNode::Value(PhaseGVN* phase) const { - if (phase->type(in(2)) == TypeLong::ZERO) { - return Type::TOP; - } return unsigned_mod_value(phase, this); } diff --git a/src/hotspot/share/opto/divnode.hpp b/src/hotspot/share/opto/divnode.hpp index de89dcaad06..366e3fb882d 100644 --- a/src/hotspot/share/opto/divnode.hpp +++ b/src/hotspot/share/opto/divnode.hpp @@ -40,9 +40,7 @@ private: bool _pinned; protected: - DivModIntegerNode(Node* c, Node* dividend, Node* divisor) : Node(c, dividend, divisor), _pinned(false) { - init_class_id(Class_DivModInteger); - } + DivModIntegerNode(Node* c, Node* dividend, Node* divisor) : Node(c, dividend, divisor), _pinned(false) {} private: virtual uint size_of() const override { return sizeof(DivModIntegerNode); } @@ -54,15 +52,6 @@ private: res->_pinned = true; return res; } - -public: - const TypeInteger* zero() const { - if (bottom_type() == TypeInt::INT) { - return TypeInt::ZERO; - } - assert(bottom_type() == TypeLong::LONG, "should be int or long"); - return TypeLong::ZERO; - } }; //------------------------------DivINode--------------------------------------- diff --git a/src/hotspot/share/opto/loopopts.cpp b/src/hotspot/share/opto/loopopts.cpp index d525c274ef6..ccd53129a87 100644 --- a/src/hotspot/share/opto/loopopts.cpp +++ b/src/hotspot/share/opto/loopopts.cpp @@ -1725,7 +1725,7 @@ void PhaseIdealLoop::try_sink_out_of_loop(Node* n) { !n->is_OpaqueTemplateAssertionPredicate() && !is_raw_to_oop_cast && // don't extend live ranges of raw oops n->Opcode() != Op_CreateEx && - (KillPathsReachableByDeadDataNode || !n->is_Type()) + (KillPathsReachableByDeadTypeNode || !n->is_Type()) ) { Node *n_ctrl = get_ctrl(n); IdealLoopTree *n_loop = get_loop(n_ctrl); diff --git a/src/hotspot/share/opto/movenode.cpp b/src/hotspot/share/opto/movenode.cpp index 7d38238da2f..6b6becb434f 100644 --- a/src/hotspot/share/opto/movenode.cpp +++ b/src/hotspot/share/opto/movenode.cpp @@ -90,6 +90,11 @@ Node *CMoveNode::Ideal(PhaseGVN *phase, bool can_reshape) { phase->type(in(IfTrue)) == Type::TOP) { return nullptr; } + Node* progress = TypeNode::Ideal(phase, can_reshape); + if (progress != nullptr) { + return progress; + } + // Check for Min/Max patterns. This is called before constants are pushed to the right input, as that transform can // make BoolTests non-canonical. Node* minmax = Ideal_minmax(phase, this); diff --git a/src/hotspot/share/opto/node.cpp b/src/hotspot/share/opto/node.cpp index 264216ddc6d..726a3ea1b55 100644 --- a/src/hotspot/share/opto/node.cpp +++ b/src/hotspot/share/opto/node.cpp @@ -597,7 +597,6 @@ void Node::setup_is_top() { //------------------------------~Node------------------------------------------ // Fancy destructor; eagerly attempt to reclaim Node numberings and storage void Node::destruct(PhaseValues* phase) { - assert(this != Compile::current()->dead_path(), "we want to keep the unique DeadPath node around"); Compile* compile = (phase != nullptr) ? phase->C : Compile::current(); if (phase != nullptr && phase->is_IterGVN()) { phase->is_IterGVN()->_worklist.remove(this); @@ -736,14 +735,11 @@ void Node::out_grow(uint len) { //------------------------------is_dead---------------------------------------- bool Node::is_dead() const { // Mach and pinch point nodes may look like dead. - if (is_top() || is_Mach() || (Opcode() == Op_Node && _outcnt > 0) || this == Compile::current()->dead_path()) { + if( is_top() || is_Mach() || (Opcode() == Op_Node && _outcnt > 0) ) return false; - } - for (uint i = 0; i < _max; i++) { - if (_in[i] != nullptr) { + for( uint i = 0; i < _max; i++ ) + if( _in[i] != nullptr ) return false; - } - } return true; } @@ -3182,11 +3178,10 @@ uint TypeNode::ideal_reg() const { return _type->ideal_reg(); } -void Node::make_path_dead(PhaseIterGVN* igvn, PhaseIdealLoop* loop, Node* ctrl_use, uint j, const char* phase_str) { +void TypeNode::make_path_dead(PhaseIterGVN* igvn, PhaseIdealLoop* loop, Node* ctrl_use, uint j, const char* phase_str) { Node* c = ctrl_use->in(j); - Node* top = igvn->C->top(); - if (c != top) { - igvn->replace_input_of(ctrl_use, j, top); + if (igvn->type(c) != Type::TOP) { + igvn->replace_input_of(ctrl_use, j, igvn->C->top()); create_halt_path(igvn, c, loop, phase_str); } } @@ -3198,18 +3193,14 @@ void Node::make_path_dead(PhaseIterGVN* igvn, PhaseIdealLoop* loop, Node* ctrl_u // constant folds and the control flow that leads to the Type node becomes unreachable. There are cases where that // doesn't happen, however. They are handled here by following uses of the Type node until a CFG or a Phi to find dead // paths. The dead paths are then replaced by a Halt node. -void Node::make_paths_from_here_dead(PhaseIterGVN* igvn, PhaseIdealLoop* loop, const char* phase_str) { +void TypeNode::make_paths_from_here_dead(PhaseIterGVN* igvn, PhaseIdealLoop* loop, const char* phase_str) { Unique_Node_List wq; wq.push(this); for (uint i = 0; i < wq.size(); ++i) { Node* n = wq.at(i); - if (n->is_CFG()) { - n->remove_dead_region(igvn, true); - } for (DUIterator_Fast kmax, k = n->fast_outs(kmax); k < kmax; k++) { Node* u = n->fast_out(k); if (u->is_CFG()) { - wq.push(u); assert(!u->is_Region(), "Can't reach a Region without going through a Phi"); make_path_dead(igvn, loop, u, 0, phase_str); } else if (u->is_Phi()) { @@ -3229,7 +3220,7 @@ void Node::make_paths_from_here_dead(PhaseIterGVN* igvn, PhaseIdealLoop* loop, c } } -void Node::create_halt_path(PhaseIterGVN* igvn, Node* c, PhaseIdealLoop* loop, const char* phase_str) { +void TypeNode::create_halt_path(PhaseIterGVN* igvn, Node* c, PhaseIdealLoop* loop, const char* phase_str) const { Node* frame = new ParmNode(igvn->C->start(), TypeFunc::FramePtr); if (loop == nullptr) { igvn->register_new_node_with_optimizer(frame); @@ -3248,3 +3239,15 @@ void Node::create_halt_path(PhaseIterGVN* igvn, Node* c, PhaseIdealLoop* loop, c } igvn->add_input_to(igvn->C->root(), halt); } + +Node* TypeNode::Ideal(PhaseGVN* phase, bool can_reshape) { + if (KillPathsReachableByDeadTypeNode && can_reshape && Value(phase) == Type::TOP) { + PhaseIterGVN* igvn = phase->is_IterGVN(); + Node* top = igvn->C->top(); + ResourceMark rm; + make_paths_from_here_dead(igvn, nullptr, "igvn"); + return top; + } + + return Node::Ideal(phase, can_reshape); +} diff --git a/src/hotspot/share/opto/node.hpp b/src/hotspot/share/opto/node.hpp index e593822c313..b3de7498e50 100644 --- a/src/hotspot/share/opto/node.hpp +++ b/src/hotspot/share/opto/node.hpp @@ -82,7 +82,6 @@ class CountedLoopEndNode; class DecodeNarrowPtrNode; class DecodeNNode; class DecodeNKlassNode; -class DivModIntegerNode; class EncodeNarrowPtrNode; class EncodePNode; class EncodePKlassNode; @@ -830,9 +829,8 @@ public: DEFINE_CLASS_ID(LShift, Node, 21) DEFINE_CLASS_ID(Neg, Node, 22) DEFINE_CLASS_ID(ReachabilityFence, Node, 23) - DEFINE_CLASS_ID(DivModInteger, Node, 24) - _max_classes = ClassMask_DivModInteger + _max_classes = ClassMask_Neg }; #undef DEFINE_CLASS_ID @@ -949,7 +947,6 @@ public: DEFINE_CLASS_QUERY(DecodeNarrowPtr) DEFINE_CLASS_QUERY(DecodeN) DEFINE_CLASS_QUERY(DecodeNKlass) - DEFINE_CLASS_QUERY(DivModInteger) DEFINE_CLASS_QUERY(EncodeNarrowPtr) DEFINE_CLASS_QUERY(EncodeP) DEFINE_CLASS_QUERY(EncodePKlass) @@ -1504,10 +1501,6 @@ public: uint _del_tick; // Bumped when a deletion happens.. #endif #endif - void make_paths_from_here_dead(PhaseIterGVN* igvn, PhaseIdealLoop* loop, const char* phase_str); - - static void create_halt_path(PhaseIterGVN* igvn, Node* c, PhaseIdealLoop* loop, const char* phase_str); - void make_path_dead(PhaseIterGVN* igvn, PhaseIdealLoop* loop, Node* ctrl_use, uint j, const char* phase_str); }; inline bool not_a_node(const Node* n) { @@ -2205,13 +2198,17 @@ public: init_class_id(Class_Type); } virtual const Type* Value(PhaseGVN* phase) const; + virtual Node* Ideal(PhaseGVN* phase, bool can_reshape); virtual const Type *bottom_type() const; virtual uint ideal_reg() const; + void make_path_dead(PhaseIterGVN* igvn, PhaseIdealLoop* loop, Node* ctrl_use, uint j, const char* phase_str); #ifndef PRODUCT virtual void dump_spec(outputStream *st) const; virtual void dump_compact_spec(outputStream *st) const; #endif + void make_paths_from_here_dead(PhaseIterGVN* igvn, PhaseIdealLoop* loop, const char* phase_str); + void create_halt_path(PhaseIterGVN* igvn, Node* c, PhaseIdealLoop* loop, const char* phase_str) const; }; #include "opto/opcodes.hpp" diff --git a/src/hotspot/share/opto/parse2.cpp b/src/hotspot/share/opto/parse2.cpp index 6e58fae51e1..9cb20cfcd00 100644 --- a/src/hotspot/share/opto/parse2.cpp +++ b/src/hotspot/share/opto/parse2.cpp @@ -1843,7 +1843,7 @@ void Parse::sharpen_type_after_if(BoolTest::mask btest, const Type* obj_type = _gvn.type(obj); const Type* tboth = obj_type->filter_speculative(cast_type); assert(tboth->higher_equal(obj_type) && tboth->higher_equal(cast_type), "sanity"); - if (tboth == Type::TOP && KillPathsReachableByDeadDataNode) { + if (tboth == Type::TOP && KillPathsReachableByDeadTypeNode) { // Let dead type node cleaning logic prune effectively dead path for us. // CheckCastPP::Value() == TOP and it will trigger the cleanup during GVN. // Don't materialize the cast when cleanup is disabled, because diff --git a/src/hotspot/share/opto/phaseX.cpp b/src/hotspot/share/opto/phaseX.cpp index c124f940a27..a4d6a6c33d0 100644 --- a/src/hotspot/share/opto/phaseX.cpp +++ b/src/hotspot/share/opto/phaseX.cpp @@ -32,7 +32,6 @@ #include "opto/castnode.hpp" #include "opto/cfgnode.hpp" #include "opto/convertnode.hpp" -#include "opto/divnode.hpp" #include "opto/idealGraphPrinter.hpp" #include "opto/loopnode.hpp" #include "opto/machnode.hpp" @@ -2198,107 +2197,6 @@ Node *PhaseIterGVN::transform( Node *n ) { return transform_old(n); } -DeadPathNode* PhaseIterGVN::dead_path() { - DeadPathNode* dead_path_node = C->dead_path(); - if (!dead_path_node->is_active()) { - dead_path_node->activate(this); - } - assert(C->root()->find_edge(dead_path_node) > 0, "should be reachable from root"); - return dead_path_node; -} - - -// If dead_node is a data node, all CFG nodes reachable from dead_node are dead cfg paths. This method follows uses from -// dead_node until it encounters a cfg node or a phi and eagerly kills these dead cfg paths. This is needed because, in -// some corner cases, a data node dies but some data paths that use it (and are unreachable at runtime) are not proven -// dead by igvn, possibly leading to incorrect IR graphs. -// Also see comment at DeadPathNode declaration. -void PhaseIterGVN::make_dependent_paths_dead_if_top(Node* dead_node, const Type* t) { - if (t != Type::TOP) { - return; - } - if (!KillPathsReachableByDeadDataNode) { - return; - } - // dead_node is going dead, follow uses - ResourceMark rm; - Unique_Node_List wq; - wq.push(dead_node); - for (uint i = 0; i < wq.size(); i++) { - Node* n = wq.at(i); - if (n != dead_node && (n->is_Phi() || n->is_CFG())) { - continue; - } - for (DUIterator_Fast kmax, k = n->fast_outs(kmax); k < kmax; k++) { - Node* u = n->fast_out(k); - wq.push(u); - } - } - for (uint i = 0; i < wq.size(); i++) { - Node* n = wq.at(i); - if (n->is_Phi()) { - Node* region = n->in(0); - // Find out through which of the Phi's input, we reached that Phi and mark the corresponding CFG path dead - for (uint j = 1; j < n->req(); j++) { - Node* in = n->in(j); - // We don't follow uses beyond Phis so if 'in' is a Phi (unless it's dead_node), we couldn't reach this Phi through it - if (in == dead_node || (in != nullptr && !in->is_Phi() && wq.member(in))) { - if (!region->is_top() && region->in(j) != nullptr && !region->in(j)->is_top()) { - // We reached this CFG path through data nodes, record it in dead path to later insert a Halt node, if it - // doesn't die in the meantime - dead_path()->add_req(region->in(j)); - _worklist.push(dead_path()); - replace_input_of(region, j, C->top()); - } - replace_input_of(n, j, C->top()); - if (in->outcnt() == 0) { - remove_dead_node(in, NodeOrigin::Graph); - } - } - } - continue; - } - if (n == dead_node) { - continue; - } - // We don't want to follow CFG nodes but is_CFG() can return false for a cfg projection if its input is top. So - // there's no foolproof way of telling if dead_node is a cfg or not and as a consequence we can reach a Region. - if (n->is_Region()) { - // Find out through which of the Region's input, we reached that Region and mark it dead - for (uint j = 1; j < n->req(); j++) { - Node* in = n->in(j); - // We don't follow uses beyond Regions so if 'in' is a Region, we couldn't reach this Region through it - if (in != nullptr && !in->is_Region() && wq.member(in)) { - replace_input_of(n, j, C->top()); - in->remove_dead_region(this, true); - } - } - continue; - } - // If we reached this CFG node through a data input... - if (n->is_CFG()) { - Node* control_input = n->in(0); - if (control_input != nullptr && !control_input->is_top()) { - // record it in dead path to later insert a Halt node, if it doesn't die in the meantime - dead_path()->add_req(control_input); - _worklist.push(dead_path()); - replace_input_of(n, 0, C->top()); - } - n->remove_dead_region(this, true); - continue; - } - if (n->outcnt() == 0) { - remove_dead_node(n, NodeOrigin::Graph); - } - } -#ifdef ASSERT - for (uint i = 0; i < wq.size(); i++) { - Node* n = wq.at(i); - assert(n->is_Region() || n->is_Phi() || n->is_CFG() || n->outcnt() == 0, "node should be dead now"); - } -#endif -} - Node *PhaseIterGVN::transform_old(Node* n) { NOT_PRODUCT(set_transforms()); // Remove 'n' from hash table in case it gets modified @@ -2390,7 +2288,6 @@ Node *PhaseIterGVN::transform_old(Node* n) { } // If 'k' computes a constant, replace it with a constant if (t->singleton() && !k->is_Con()) { - make_dependent_paths_dead_if_top(k, t); set_progress(); Node* con = makecon(t); // Make a constant add_users_to_worklist(k); @@ -3060,14 +2957,10 @@ void PhaseCCP::analyze_step(Unique_Node_List& worklist, Node* n) { set_type(n, new_type); push_child_nodes_to_worklist(worklist, n); } - if (KillPathsReachableByDeadDataNode && n->is_Type() && new_type == Type::TOP) { + if (KillPathsReachableByDeadTypeNode && n->is_Type() && new_type == Type::TOP) { // Keep track of Type nodes to kill CFG paths that use Type // nodes that become dead. - _maybe_top_type_or_div_mod_nodes.push(n); - } - if (KillPathsReachableByDeadDataNode && new_type == Type::TOP && n->is_DivModInteger() && - type(n->in(2)) == n->as_DivModInteger()->zero()) { - _maybe_top_type_or_div_mod_nodes.push(n); + _maybe_top_type_nodes.push(n); } } @@ -3363,16 +3256,16 @@ Node *PhaseCCP::transform( Node *n ) { // track all visited nodes, so that we can remove the complement Unique_Node_List useful; - if (KillPathsReachableByDeadDataNode) { - for (uint i = 0; i < _maybe_top_type_or_div_mod_nodes.size(); ++i) { - Node* data_node = _maybe_top_type_or_div_mod_nodes.at(i); - if (type(data_node) == Type::TOP) { + if (KillPathsReachableByDeadTypeNode) { + for (uint i = 0; i < _maybe_top_type_nodes.size(); ++i) { + Node* type_node = _maybe_top_type_nodes.at(i); + if (type(type_node) == Type::TOP) { ResourceMark rm; - data_node->make_paths_from_here_dead(this, nullptr, "ccp"); + type_node->as_Type()->make_paths_from_here_dead(this, nullptr, "ccp"); } } } else { - assert(_maybe_top_type_or_div_mod_nodes.size() == 0, "we don't need type nodes"); + assert(_maybe_top_type_nodes.size() == 0, "we don't need type nodes"); } // Initialize the traversal. diff --git a/src/hotspot/share/opto/phaseX.hpp b/src/hotspot/share/opto/phaseX.hpp index 7ea7aa99142..014d16f92f6 100644 --- a/src/hotspot/share/opto/phaseX.hpp +++ b/src/hotspot/share/opto/phaseX.hpp @@ -501,10 +501,6 @@ protected: // Usually returns new_type. Returns old_type if new_type is only a slight // improvement, such that it would take many (>>10) steps to reach 2**32. - DeadPathNode* dead_path(); - - void make_dependent_paths_dead_if_top(Node* dead_node, const Type* t); - public: PhaseIterGVN(PhaseIterGVN* igvn); // Used by CCP constructor @@ -699,7 +695,7 @@ protected: // Should be replaced with combined CCP & GVN someday. class PhaseCCP : public PhaseIterGVN { Unique_Node_List _root_and_safepoints; - Unique_Node_List _maybe_top_type_or_div_mod_nodes; + Unique_Node_List _maybe_top_type_nodes; // Non-recursive. Use analysis to transform single Node. virtual Node* transform_once(Node* n); diff --git a/src/hotspot/share/opto/rootnode.cpp b/src/hotspot/share/opto/rootnode.cpp index 1e5ef29e79c..60167c5436a 100644 --- a/src/hotspot/share/opto/rootnode.cpp +++ b/src/hotspot/share/opto/rootnode.cpp @@ -90,48 +90,3 @@ const Type* HaltNode::Value(PhaseGVN* phase) const { const RegMask &HaltNode::out_RegMask() const { return RegMask::EMPTY; } - -Node* DeadPathNode::Ideal(PhaseGVN* phase, bool can_reshape) { - assert(unique_ctrl_out() == phase->C->root(), "only referenced from root"); - assert(can_reshape, "only used once igvn executes"); - bool modified = false; - for (uint i = 1; i < req(); i++) { // For all inputs - // Check for and remove dead inputs - if (phase->type(in(i)) == Type::TOP) { - del_req(i--); // Delete TOP inputs - modified = true; - } - } - if (req() == 1 && is_active()) { - assert(modified, "only if some inputs were removed"); - deactivate(); - } - return modified ? this : nullptr; -} - -const Type* DeadPathNode::Value(PhaseGVN* phase) const { - if (req() == 1) { - return Type::TOP; - } - return bottom_type(); -} - -void DeadPathNode::activate(PhaseIterGVN* igvn) { - assert(Compile::current()->root()->find_edge(this) < 0, "should be disconnected from root"); - set_req(0, this); - // If an entire subgraph died such as with Node::remove_dead_region(), some dead inputs to the DeadPath node will have - // been left behind - while (req() > 1) { - uint last = req() - 1; - assert(in(last) == nullptr || in(last)->is_top(), "only dead inputs should remain"); - del_req(last); - } - Node* root_node = Compile::current()->root(); - root_node->add_req(this); - igvn->_worklist.push(root_node); - igvn->set_type(this, bottom_type()); -} - -void DeadPathNode::deactivate() { - set_req(0, nullptr); -} diff --git a/src/hotspot/share/opto/rootnode.hpp b/src/hotspot/share/opto/rootnode.hpp index 61ad317d455..76f0ec440a9 100644 --- a/src/hotspot/share/opto/rootnode.hpp +++ b/src/hotspot/share/opto/rootnode.hpp @@ -69,35 +69,4 @@ public: virtual uint match_edge(uint idx) const { return 0; } }; - -// This node collects paths that are found dead by PhaseIterGVN::make_dependent_paths_dead_if_top() - -// There is a single DeadPath node for the lifetime of optimizations. It's initially not active (i.e. unreachable from -// the IR graph). When a cfg path becomes dead it's added as an input to the unique DeadPath node. If after some -// optimizations run, the DeadPath node gets disconnected, it's not destroyed. It becomes inactive and can possibly be -// activated again on a subsequent igvn. When optimizations are over, the DeadPath node, if it is active, is expanded to -// a Region and Halt node in Compile::final_graph_reshaping(). - -// Rather than having this dedicated node, igvn could add a Halt node everytime it finds a dead cfg path from a data -// node. What's likely, however, is that as igvn progresses, that same cfg path is found dead by following cfg edges. -// The Halt node then becomes dead. To avoid this unnecessary cycle of creation of a Halt node only to have it be found -// dead shortly after, dead cfg paths are added to the unique DeadPath node. -class DeadPathNode : public RegionNode { -public: - DeadPathNode() : RegionNode(1) { - deactivate(); - assert(Compile::current()->dead_path() == nullptr, "only one"); - } - virtual int Opcode() const; - virtual const Type* bottom_type() const { return Type::BOTTOM; } - virtual Node* Identity(PhaseGVN* phase) { return this; } - virtual Node* Ideal(PhaseGVN* phase, bool can_reshape); - virtual const Type* Value(PhaseGVN* phase) const; - bool is_active() const { - return in(0) == this; - } - void activate(PhaseIterGVN* igvn); - void deactivate(); -}; - #endif // SHARE_OPTO_ROOTNODE_HPP diff --git a/src/hotspot/share/opto/vectornode.cpp b/src/hotspot/share/opto/vectornode.cpp index 60eda1204b7..20857eed35c 100644 --- a/src/hotspot/share/opto/vectornode.cpp +++ b/src/hotspot/share/opto/vectornode.cpp @@ -2391,7 +2391,7 @@ Node* VectorMaskOpNode::Ideal(PhaseGVN* phase, bool can_reshape) { if (n != nullptr) { return n; } - return nullptr; + return TypeNode::Ideal(phase, can_reshape); } Node* VectorMaskCastNode::Identity(PhaseGVN* phase) { diff --git a/test/hotspot/jtreg/compiler/c2/TestDeadPathManyDeadDataNodes.java b/test/hotspot/jtreg/compiler/c2/TestDeadPathManyDeadDataNodes.java deleted file mode 100644 index e9c5a8f7529..00000000000 --- a/test/hotspot/jtreg/compiler/c2/TestDeadPathManyDeadDataNodes.java +++ /dev/null @@ -1,1301 +0,0 @@ -/* - * Copyright (c) 2026 IBM Corporation. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * 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 8380166 - * @summary C2: crash in compiled code due to zero division because of widened CastII - * @run main/othervm -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions - * -Xcomp -XX:CompileOnly=TestDeadPathManyDeadDataNodes::test1 - * -XX:CompileCommand=quiet - * -XX:CompileCommand=inline,TestDeadPathManyDeadDataNodes::inlined1 - * -XX:MaxRecursiveInlineLevel=1000 -XX:MaxInlineLevel=1000 - * -XX:-TieredCompilation -XX:+AlwaysIncrementalInline - * -XX:+DelayAfterInliningCutoff -XX:+IncrementalInlineForceCleanup - * -XX:NodeCountInliningCutoff=100000 -XX:+StressIGVN - * ${test.main.class} - * @run main ${test.main.class} - */ - -package compiler.c2; - -public class TestDeadPathManyDeadDataNodes { - private static int field; - private static boolean boolField2; - private static int arrayLengthField; - - public static void main(String[] args) { - Object o = new Object(); - try { - test1(false, 0); - } catch (NegativeArraySizeException nase) { - } - } - - private static int test1(boolean boolParam, int intParam) { - int length; - int res = 0; - length = -1; - for (int i = 0; i < 2; i++) { - if (boolParam) { - field = 42; - } - int[] array = new int[length]; - arrayLengthField = array.length; - while(true) { - Object o = new Object(); - int arrayLength = arrayLengthField; - arrayLengthField = 0; - switch (intParam) { - case 0: - if (boolField2) { - break; - } - field = 42; - continue; - case 1: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 2: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 3: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 4: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 5: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 6: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 7: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 8: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 9: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 10: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 11: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 12: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 13: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 14: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 15: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 16: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 17: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 18: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 19: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 20: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 21: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 22: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 23: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 24: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 25: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 26: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 27: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 28: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 29: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 30: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 31: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 32: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 33: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 34: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 35: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 36: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 37: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 38: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 39: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 40: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 41: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 42: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 43: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 44: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 45: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 46: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 47: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 48: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 49: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 50: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 51: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 52: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 53: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 54: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 55: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 56: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 57: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 58: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 59: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 60: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 61: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 62: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 63: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 64: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 65: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 66: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 67: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 68: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 69: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 70: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 71: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 72: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 73: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 74: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 75: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 76: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 77: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 78: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 79: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 80: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 81: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 82: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 83: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 84: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 85: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 86: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 87: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 88: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 89: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 90: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 91: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 92: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 93: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 94: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 95: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 96: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 97: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - case 98: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - continue; - case 99: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - continue; - default: - res += inlined1(boolParam, intParam/100, arrayLength, 92); - continue; - } - field = 42; - break; - } - length = lastInlined(); - } - return res; - } - - static int lastInlined() { - return -1; - } - - static int inlined1(boolean boolParam, int intParam, int arrayLength, int count) { - int res = 0; - switch (intParam) { - case 0: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 1: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 2: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 3: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 4: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 5: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 6: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 7: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 8: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 9: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 10: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 11: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 12: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 13: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 14: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 15: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 16: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 17: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 18: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 19: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 20: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 21: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 22: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 23: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 24: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 25: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 26: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 27: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 28: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 29: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 30: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 31: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 32: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 33: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 34: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 35: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 36: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 37: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 38: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 39: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 40: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 41: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 42: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 43: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 44: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 45: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 46: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 47: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 48: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 49: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 50: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 51: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 52: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 53: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 54: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 55: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 56: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 57: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 58: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 59: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 60: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 61: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 62: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 63: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 64: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 65: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 66: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 67: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 68: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 69: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 70: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 71: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 72: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 73: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 74: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 75: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 76: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 77: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 78: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 79: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 80: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 81: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 82: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 83: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 84: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 85: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 86: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 87: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 88: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 89: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 90: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 91: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 92: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 93: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 94: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 95: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 96: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 97: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - case 98: - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - case 99: - if (boolParam) { - res += arrayLength * 2; - } - field = 42; - return res; - default: - if (count == 0) { - if (boolParam) { - res += arrayLength * 1; - } - field = 42; - return res; - } else { - return inlined1(boolParam, intParam / 100, arrayLength, count-1); - } - } - } -} diff --git a/test/hotspot/jtreg/compiler/integerArithmetic/TestDivByZeroInLiveCFGPath.java b/test/hotspot/jtreg/compiler/integerArithmetic/TestDivByZeroInLiveCFGPath.java deleted file mode 100644 index 6eaf3d86e71..00000000000 --- a/test/hotspot/jtreg/compiler/integerArithmetic/TestDivByZeroInLiveCFGPath.java +++ /dev/null @@ -1,64 +0,0 @@ - -/* - * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -/** - * @test - * @bug 8383815 - * @summary C2: assert(false) failed: malformed IfNode with 1 outputs - * @run main/othervm -XX:CompileCommand=compileonly,${test.main.class}*::* -XX:-TieredCompilation -Xbatch -XX:PerMethodTrapLimit=0 ${test.main.class} - * @run main ${test.main.class} - */ - -package compiler.integerArithmetic; - -public class TestDivByZeroInLiveCFGPath { - static long lFld; - static int iArr[] = new int[400]; - - public static void main(String[] strArr) { - for (int i = 0; i < 10; i++) { - test(); - } - } - - static void test() { - int x; - for (int i = 9; i < 100; ++i) { - int j = 100; - while (--j > 0) { - iArr[1] = (int) lFld; - } - try { - iArr[1] = (5 / j); - x = (i / iArr[8]); - } catch (ArithmeticException a_e) { - } - } - - for (int i = 18; i < 50; i++) { - iArr[2] += lFld; - } - } -} - diff --git a/test/hotspot/jtreg/compiler/integerArithmetic/TestZeroDivModWidenedCastII.java b/test/hotspot/jtreg/compiler/integerArithmetic/TestZeroDivModWidenedCastII.java deleted file mode 100644 index a5bc8fc9287..00000000000 --- a/test/hotspot/jtreg/compiler/integerArithmetic/TestZeroDivModWidenedCastII.java +++ /dev/null @@ -1,1122 +0,0 @@ -/* - * Copyright (c) 2026 IBM Corporation. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * 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 8380166 - * @summary C2: crash in compiled code due to zero division because of widened CastII - * - * @run main/othervm -XX:-TieredCompilation -XX:-UseOnStackReplacement -XX:-BackgroundCompilation - * ${test.main.class} - * @run main ${test.main.class} - * - */ - -package compiler.integerArithmetic; - -public class TestZeroDivModWidenedCastII { - private static int intField; - private static long longField; - private static volatile int volatileField; - - public static void main(String[] args) { - for (int i = 0; i < 20_000; i++) { - test1(0, 9, 1, true, false); - test1(0, 9, 1, false, false); - inlined1_2(9, 1, 1, true, 0); - inlined1_3(0, 0); - test2(0, 9, 1, true, false); - test2(0, 9, 1, false, false); - inlined2_2(9, 1, 1, true, 0); - inlined2_3(0, 0); - test3(0, 9, 1, true, false); - test3(0, 9, 1, false, false); - inlined3_2(9, 1, 1, true, 0); - inlined3_3(0, 0); - test4(0, 9, 1, true, false); - test4(0, 9, 1, false, false); - inlined4_2(9, 1, 1, true, 0); - inlined4_3(0, 0); - test5(0, 9, 1, true, false); - test5(0, 9, 1, false, false); - inlined5_2(9, 1, 1, true, 0); - inlined5_3(0, 0); - test6(0, 9, 1, true, false); - test6(0, 9, 1, false, false); - inlined6_2(9, 1, 1, true, 0); - inlined6_3(0, 0); - test7(0, 9, 1, true, false); - test7(0, 9, 1, false, false); - inlined7_2(9, 1, 1, true, 0); - inlined7_3(0, 0); - test8(0, 9, 1, true, false); - test8(0, 9, 1, false, false); - inlined8_2(9, 1, 1, true, 0); - inlined8_3(0, 0); - test9(0, 9, 1, true, false); - test9(0, 9, 1, false, false); - inlined9_2(9, 1, 1, true, 0); - inlined9_3(0, 0); - test10(0, 9, 1, false); - inlined10_2(9, 1, 1, true, 0); - inlined10_3(0, 0); - test11(0, 9, 1, false); - inlined11_2(9, 1, 1, true, 0); - inlined11_3(0, 0); - test12(0, 9, 1, false); - inlined12_2(9, 1, 1, true, 0); - inlined12_3(0, 0); - test13(0, 9, 1, false); - inlined13_2(9, 1, 1, true, 0); - inlined13_3(0, 0); - test14(0, 9, 1, false); - inlined14_2(9, 1, 1, true, 0); - inlined14_3(0, 0); - test15(0, 9, 1, false); - inlined15_2(9, 1, 1, true, 0); - inlined15_3(0, 0); - test16(0, 9, 1, false); - inlined16_2(9, 1, 1, true, 0); - inlined16_3(0, 0); - test17(0, 9, 1, false); - inlined17_2(9, 1, 1, true, 0); - inlined17_3(0, 0); - } - } - - private static void test1(int k, int j, int flag, boolean flag2, boolean flag3) { - int l = 0; - for (; l < 10; l++); - int m = inlined1_3(j, l); - - int i = inlined1(k, flag2); - j = Integer.min(j, 9); - int[] array = new int[10]; - if (flag == 0) { - throw new RuntimeException("never taken"); - } - if (flag2) { - inlined1_2(j, flag, i, flag3, m); - } else { - inlined1_2(j, flag, i, flag3, m); - } - } - - private static int inlined1_3(int j, int l) { - if (l == 10) { - j = 1; - } - return j; - } - - private static void inlined1_2(int j, int flag, int i, boolean flag3, int m) { - if (flag3) { - float[] newArray = new float[j + 1]; // j + 1 in [0..10] - // RC i Date: Fri, 17 Jul 2026 15:51:06 +0000 Subject: [PATCH 188/305] 8388358: HotCodeHeap should throw warning when enabled without C2 Reviewed-by: kvn, eastigeevich --- src/hotspot/share/compiler/compilerDefinitions.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/hotspot/share/compiler/compilerDefinitions.cpp b/src/hotspot/share/compiler/compilerDefinitions.cpp index 5bb96f8d031..1ad667e51f1 100644 --- a/src/hotspot/share/compiler/compilerDefinitions.cpp +++ b/src/hotspot/share/compiler/compilerDefinitions.cpp @@ -272,7 +272,11 @@ void CompilerConfig::set_compilation_policy_flags() { } #ifdef COMPILER2 - if (HotCodeHeap) { + if (HotCodeHeap && !is_c2_enabled()) { + warning("HotCodeHeap disabled because C2 is disabled."); + FLAG_SET_ERGO(HotCodeHeap, false); + FLAG_SET_ERGO(HotCodeHeapSize, 0); + } else if (HotCodeHeap) { if (FLAG_IS_DEFAULT(SegmentedCodeCache)) { FLAG_SET_ERGO(SegmentedCodeCache, true); } else if (!SegmentedCodeCache) { @@ -285,10 +289,6 @@ void CompilerConfig::set_compilation_policy_flags() { vm_exit_during_initialization("HotCodeHeap requires NMethodRelocation enabled"); } - if (!is_c2_enabled()) { - vm_exit_during_initialization("HotCodeHeap requires C2 enabled"); - } - if (HotCodeMinSamplingMs > HotCodeMaxSamplingMs) { vm_exit_during_initialization("HotCodeMinSamplingMs cannot be larger than HotCodeMaxSamplingMs"); } From 2a83c509772d1645eb4ca1ad52a112d1ac57daa3 Mon Sep 17 00:00:00 2001 From: Artur Barashev Date: Thu, 8 Jan 2026 19:08:20 +0000 Subject: [PATCH 189/305] 8373275: Improve DTLS handshaking Reviewed-by: rhalade, pkumaraswamy, ahgross, jnibedita, jnimeh, mullan --- .../sun/security/ssl/HelloCookieManager.java | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/java.base/share/classes/sun/security/ssl/HelloCookieManager.java b/src/java.base/share/classes/sun/security/ssl/HelloCookieManager.java index b3155f5170a..4268b9779bd 100644 --- a/src/java.base/share/classes/sun/security/ssl/HelloCookieManager.java +++ b/src/java.base/share/classes/sun/security/ssl/HelloCookieManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,6 +26,7 @@ package sun.security.ssl; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; @@ -121,6 +122,7 @@ abstract class HelloCookieManager { private static final class D10HelloCookieManager extends HelloCookieManager { + private static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; final SecureRandom secureRandom; private int cookieVersion; // allow to wrap, version + sequence private final byte[] cookieSecret; @@ -170,6 +172,7 @@ abstract class HelloCookieManager { } byte[] helloBytes = clientHello.getHelloCookieBytes(); md.update(helloBytes); + md.update(getHostPortBytes(context)); byte[] cookie = md.digest(secret); // 32 bytes cookie[0] = (byte)((version >> 24) & 0xFF); @@ -205,11 +208,30 @@ abstract class HelloCookieManager { } byte[] helloBytes = clientHello.getHelloCookieBytes(); md.update(helloBytes); + md.update(getHostPortBytes(context)); byte[] target = md.digest(secret); // 32 bytes target[0] = cookie[0]; return MessageDigest.isEqual(target, cookie); } + + /** + * Returns host and port bytes if those are set. + * Using ASCII unit separator character to separate host and port so we + * can differentiate between otherwise identical host and port string + * concatenations, for example host 172.0.0.1 with port 25 and host + * 172.0.0.12 with port 5. + */ + private static byte[] getHostPortBytes(ServerHandshakeContext context) { + final String host = context.conContext.transport.getPeerHost(); + final int port = context.conContext.transport.getPeerPort(); + final String hostStr = host != null ? host : ""; + final String portStr = port > -1 ? Integer.toString(port) : ""; + return hostStr.isEmpty() && portStr.isEmpty() ? + EMPTY_BYTE_ARRAY : + (hostStr + '\u001F' + portStr).getBytes( + StandardCharsets.UTF_8); + } } private static final From b84cef8064de569d6f728f5291662bc9bb4cd8a9 Mon Sep 17 00:00:00 2001 From: Bradford Wetmore Date: Wed, 14 Jan 2026 20:54:56 +0000 Subject: [PATCH 190/305] 8368041: Enhance TLS certificate handling Reviewed-by: jnimeh, abarashev, hchao, djelinski, ksreenath, ahgross, rhalade --- .../share/classes/sun/security/ssl/Alert.java | 60 ++++++++++++------- 1 file changed, 37 insertions(+), 23 deletions(-) diff --git a/src/java.base/share/classes/sun/security/ssl/Alert.java b/src/java.base/share/classes/sun/security/ssl/Alert.java index e9588a09b3d..c081ec5f747 100644 --- a/src/java.base/share/classes/sun/security/ssl/Alert.java +++ b/src/java.base/share/classes/sun/security/ssl/Alert.java @@ -181,11 +181,13 @@ public enum Alert { AlertMessage(TransportContext context, ByteBuffer m) throws IOException { - // From RFC 8446 "Implementations - // MUST NOT send Handshake and Alert records that have a zero-length - // TLSInnerPlaintext.content; if such a message is received, the - // receiving implementation MUST terminate the connection with an - // "unexpected_message" alert." + + // From RFC 8446: TLSv1.3 + // + // Implementations MUST NOT send Handshake and Alert records that + // have a zero-length TLSInnerPlaintext.content; if such a message + // is received, the receiving implementation MUST terminate the + // connection with an "unexpected_message" alert. if (m.remaining() == 0) { throw context.fatal(Alert.UNEXPECTED_MESSAGE, "Alert fragments must not be zero length."); @@ -264,27 +266,39 @@ public enum Alert { } else if ((level == Level.WARNING) && (alert != null)) { // Terminate the connection if an alert with a level of warning // is received during handshaking, except the no_certificate - // warning. - if (alert.handshakeOnly && (tc.handshakeContext != null)) { - // It's OK to get a no_certificate alert from a client of - // which we requested client authentication. However, - // if we required it, then this is not acceptable. - if (tc.sslConfig.isClientMode || - alert != Alert.NO_CERTIFICATE || - (tc.sslConfig.clientAuthType != + // warning for SSLv3. + HandshakeContext hc = tc.handshakeContext; + if (alert.handshakeOnly && (hc != null)) { + // In SSLv3, it's OK to get a no_certificate alert from a + // client where we requested (want) client authentication. + // If we required it (need), this is not acceptable + // and must fail. + // + // no_certificate alerts are not acceptable in TLSv1.*. + // + if (!tc.sslConfig.isClientMode && + (hc.negotiatedProtocol == ProtocolVersion.SSL30) && + (alert == Alert.NO_CERTIFICATE) && + (tc.sslConfig.clientAuthType == ClientAuthType.CLIENT_AUTH_REQUESTED)) { - throw tc.fatal(Alert.HANDSHAKE_FAILURE, - "received handshake warning: " + alert.description); - } else { - // Otherwise, ignore the warning but remove the - // Certificate and CertificateVerify handshake - // consumer so the state machine doesn't expect it. - tc.handshakeContext.handshakeConsumers.remove( - SSLHandshake.CERTIFICATE.id); - tc.handshakeContext.handshakeConsumers.remove( + + // We'll ignore the warning and remove the Certificate, + // CompressedCertificate and CertificateVerify handshake + // consumers so the state machine isn't expecting them. + if (hc.handshakeConsumers.remove( + SSLHandshake.CERTIFICATE.id) != null) { + hc.handshakeConsumers.remove( SSLHandshake.COMPRESSED_CERTIFICATE.id); - tc.handshakeContext.handshakeConsumers.remove( + hc.handshakeConsumers.remove( SSLHandshake.CERTIFICATE_VERIFY.id); + } else { + throw tc.fatal(Alert.HANDSHAKE_FAILURE, + "NO_CERTIFICATE alert received when certs" + + " were not expected or already received"); + } + } else { + throw tc.fatal(Alert.HANDSHAKE_FAILURE, + "Received handshake warning: " + alert.description); } } // Otherwise, ignore the warning } else { // fatal or unknown From 1b0aeb09407fd9ff3d1c0864c812b479b228609c Mon Sep 17 00:00:00 2001 From: Volkan Yazici Date: Mon, 2 Mar 2026 10:00:34 +0000 Subject: [PATCH 191/305] 8377498: Improve HttpServer handling Reviewed-by: dfuchs --- .../sun/net/httpserver/ServerImpl.java | 11 +++++++++- .../simpleserver/FileServerHandler.java | 22 ++++++++++--------- 2 files changed, 22 insertions(+), 11 deletions(-) diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java index 3d77a61c0be..f0a8efe1a6b 100644 --- a/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java +++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/ServerImpl.java @@ -770,15 +770,24 @@ class ServerImpl { requestLine, "Bad request line"); return; } + + // Read the request URI String uriStr = requestLine.substring(start, space); + // Reject ambiguous URIs + if (uriStr.startsWith("//")) { + reject(Code.HTTP_BAD_REQUEST, + requestLine, "Bad request URI"); + return; + } URI uri; try { uri = new URI(uriStr); } catch (URISyntaxException e3) { reject(Code.HTTP_BAD_REQUEST, - requestLine, "URISyntaxException thrown"); + requestLine, "Bad request URI"); return; } + start = space+1; String version = requestLine.substring(start); Headers headers = req.headers(); diff --git a/src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/FileServerHandler.java b/src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/FileServerHandler.java index cbf032e8398..08ea357b7f4 100644 --- a/src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/FileServerHandler.java +++ b/src/jdk.httpserver/share/classes/sun/net/httpserver/simpleserver/FileServerHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -106,16 +106,22 @@ public final class FileServerHandler implements HttpHandler { private void handleSupportedMethod(HttpExchange exchange, Path path, boolean writeBody) throws IOException { + boolean requestURIEndsWithSlash = pathEndsWithSlash(exchange); if (Files.isDirectory(path)) { - if (missingSlash(exchange)) { + if (!requestURIEndsWithSlash) { handleMovedPermanently(exchange); return; } - if (indexFile(path) != null) { - serveFile(exchange, indexFile(path), writeBody); + Path indexFile = indexFile(path); + if (indexFile != null) { + serveFile(exchange, indexFile, writeBody); } else { listFiles(exchange, path, writeBody); } + } + // Disallow non-directory paths ending with slash + else if (requestURIEndsWithSlash) { + handleNotFound(exchange); } else { serveFile(exchange, path, writeBody); } @@ -126,10 +132,6 @@ public final class FileServerHandler implements HttpHandler { exchange.sendResponseHeaders(301, RSPBODY_EMPTY); } - private void handleForbidden(HttpExchange exchange) throws IOException { - exchange.sendResponseHeaders(403, RSPBODY_EMPTY); - } - private void handleNotFound(HttpExchange exchange) throws IOException { String fileNotFound = ResourceBundleHelper.getMessage("html.not.found"); var bytes = (openHTML @@ -161,8 +163,8 @@ public final class FileServerHandler implements HttpHandler { return query == null ? redirectPath : redirectPath + "?" + query; } - private static boolean missingSlash(HttpExchange exchange) { - return !exchange.getRequestURI().getPath().endsWith("/"); + private static boolean pathEndsWithSlash(HttpExchange exchange) { + return exchange.getRequestURI().getPath().endsWith("/"); } private static String contextPath(HttpExchange exchange) { From d72538f9a59944ca977d3196e019bcede1195ff8 Mon Sep 17 00:00:00 2001 From: Jayathirth D V Date: Wed, 4 Mar 2026 17:01:12 +0000 Subject: [PATCH 192/305] 8374058: Enhance JPEG handling Reviewed-by: mschoene, rhalade, psadhukhan, prr --- .../share/native/libjavajpeg/imageioJPEG.c | 395 ++++++++---------- .../jpeg/LargeJpegReadWithProgressBench.java | 166 ++++++++ .../plugins/jpeg/LargeJpegReadWriteBench.java | 141 +++++++ 3 files changed, 472 insertions(+), 230 deletions(-) create mode 100644 test/micro/org/openjdk/bench/javax/imageio/plugins/jpeg/LargeJpegReadWithProgressBench.java create mode 100644 test/micro/org/openjdk/bench/javax/imageio/plugins/jpeg/LargeJpegReadWriteBench.java diff --git a/src/java.desktop/share/native/libjavajpeg/imageioJPEG.c b/src/java.desktop/share/native/libjavajpeg/imageioJPEG.c index a764eb1ae3b..ac37ad8eab6 100644 --- a/src/java.desktop/share/native/libjavajpeg/imageioJPEG.c +++ b/src/java.desktop/share/native/libjavajpeg/imageioJPEG.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -120,6 +120,7 @@ typedef struct streamBufferStruct { size_t bufferLength; // Allocated, nut just used int suspendable; // Set to true to suspend input long remaining_skip; // Used only on input + jboolean isCopy; // GetByteArrayElements copied/pinned the Java array } streamBuffer, *streamBufferPtr; /* @@ -200,7 +201,8 @@ static void destroyStreamBuffer(JNIEnv *env, streamBufferPtr sb) { // Forward reference static void unpinStreamBuffer(JNIEnv *env, streamBufferPtr sb, - const JOCTET *next_byte); + const JOCTET *next_byte, + int streamReleaseMode); /* * Resets the state of a streamBuffer object that has been in use. * The global reference to the stream is released, but the reference @@ -212,15 +214,16 @@ static void resetStreamBuffer(JNIEnv *env, streamBufferPtr sb) { (*env)->DeleteWeakGlobalRef(env, sb->ioRef); sb->ioRef = NULL; } - unpinStreamBuffer(env, sb, NULL); + unpinStreamBuffer(env, sb, NULL, JNI_ABORT); sb->bufferOffset = NO_DATA; sb->suspendable = FALSE; sb->remaining_skip = 0; } /* - * Pins the data buffer associated with this stream. Returns OK on - * success, NOT_OK on failure, as GetPrimitiveArrayCritical may fail. + * Pins/copies the data buffer associated with this stream. Returns OK on + * success, NOT_OK on failure, as GetByteArrayElements + * may fail. */ static int pinStreamBuffer(JNIEnv *env, streamBufferPtr sb, @@ -228,9 +231,9 @@ static int pinStreamBuffer(JNIEnv *env, if (sb->hstreamBuffer != NULL) { assert(sb->buf == NULL); sb->buf = - (JOCTET *)(*env)->GetPrimitiveArrayCritical(env, - sb->hstreamBuffer, - NULL); + (JOCTET *)(*env)->GetByteArrayElements(env, + sb->hstreamBuffer, + &sb->isCopy); if (sb->buf == NULL) { return NOT_OK; } @@ -242,11 +245,12 @@ static int pinStreamBuffer(JNIEnv *env, } /* - * Unpins the data buffer associated with this stream. + * Unpins/releases the data buffer associated with this stream. */ static void unpinStreamBuffer(JNIEnv *env, streamBufferPtr sb, - const JOCTET *next_byte) { + const JOCTET *next_byte, + int streamReleaseMode) { if (sb->buf != NULL) { assert(sb->hstreamBuffer != NULL); if (next_byte == NULL) { @@ -254,11 +258,13 @@ static void unpinStreamBuffer(JNIEnv *env, } else { sb->bufferOffset = next_byte - sb->buf; } - (*env)->ReleasePrimitiveArrayCritical(env, - sb->hstreamBuffer, - sb->buf, - 0); - sb->buf = NULL; + (*env)->ReleaseByteArrayElements(env, + sb->hstreamBuffer, + (jbyte *)sb->buf, + streamReleaseMode); + if (streamReleaseMode != JNI_COMMIT) { + sb->buf = NULL; + } } } @@ -276,6 +282,7 @@ static void clearStreamBuffer(streamBufferPtr sb) { typedef struct pixelBufferStruct { jobject hpixelObject; // Usually a DataBuffer bank as a byte array unsigned int byteBufferLength; + jboolean isCopy; // GetByteArrayElements copied/pinned the Java array union pixptr { INT32 *ip; // Pinned buffer pointer, as 32-bit ints unsigned char *bp; // Pinned buffer pointer, as bytes @@ -309,7 +316,7 @@ static int setPixelBuffer(JNIEnv *env, pixelBufferPtr pb, jobject obj) { } // Forward reference -static void unpinPixelBuffer(JNIEnv *env, pixelBufferPtr pb); +static void unpinPixelBuffer(JNIEnv *env, pixelBufferPtr pb, int pixelReleaseMode); /* * Resets a pixel buffer to its initial state. Unpins any pixel buffer, @@ -318,7 +325,7 @@ static void unpinPixelBuffer(JNIEnv *env, pixelBufferPtr pb); */ static void resetPixelBuffer(JNIEnv *env, pixelBufferPtr pb) { if (pb->hpixelObject != NULL) { - unpinPixelBuffer(env, pb); + unpinPixelBuffer(env, pb, JNI_ABORT); (*env)->DeleteGlobalRef(env, pb->hpixelObject); pb->hpixelObject = NULL; pb->byteBufferLength = 0; @@ -326,13 +333,13 @@ static void resetPixelBuffer(JNIEnv *env, pixelBufferPtr pb) { } /* - * Pins the data buffer. Returns OK on success, NOT_OK on failure. + * Pins/copies the data buffer. Returns OK on success, NOT_OK on failure. */ static int pinPixelBuffer(JNIEnv *env, pixelBufferPtr pb) { if (pb->hpixelObject != NULL) { assert(pb->buf.ip == NULL); - pb->buf.bp = (unsigned char *)(*env)->GetPrimitiveArrayCritical - (env, pb->hpixelObject, NULL); + pb->buf.bp = (unsigned char *)(*env)->GetByteArrayElements + (env, pb->hpixelObject, &pb->isCopy); if (pb->buf.bp == NULL) { return NOT_OK; } @@ -341,17 +348,19 @@ static int pinPixelBuffer(JNIEnv *env, pixelBufferPtr pb) { } /* - * Unpins the data buffer. + * Unpins/releases the pixel buffer. */ -static void unpinPixelBuffer(JNIEnv *env, pixelBufferPtr pb) { +static void unpinPixelBuffer(JNIEnv *env, pixelBufferPtr pb, int pixelReleaseMode) { if (pb->buf.ip != NULL) { assert(pb->hpixelObject != NULL); - (*env)->ReleasePrimitiveArrayCritical(env, - pb->hpixelObject, - pb->buf.ip, - 0); - pb->buf.ip = NULL; + (*env)->ReleaseByteArrayElements(env, + pb->hpixelObject, + (jbyte *)pb->buf.ip, + pixelReleaseMode); + if (pixelReleaseMode != JNI_COMMIT) { + pb->buf.ip = NULL; + } } } @@ -468,34 +477,28 @@ static j_common_ptr destroyImageioData(JNIEnv *env, imageIODataPtr data) { /******************** Java array pinning and unpinning *****************/ -/* We use Get/ReleasePrimitiveArrayCritical functions to avoid - * the need to copy array elements for the above two objects. - * - * MAKE SURE TO: - * - * - carefully insert pairs of RELEASE_ARRAYS and GET_ARRAYS around - * callbacks to Java. - * - call RELEASE_ARRAYS before returning to Java. - * - * Otherwise things will go horribly wrong. There may be memory leaks, - * excessive pinning, or even VM crashes! - * - * Note that GetPrimitiveArrayCritical may fail! +/* + * We use Get/ReleaseByteArrayElements functions for access stream + * and pixel information from Java level arrays. + * If we receive reference to copy of Java array make sure you update + * Java array also when the latest information is needed at Java level. + * Also we use specific release modes for performance optimizations. */ /* - * Release (unpin) all the arrays in use during a read. + * Release (unpin) both stream and pixel arrays. */ -static void RELEASE_ARRAYS(JNIEnv *env, imageIODataPtr data, const JOCTET *next_byte) +static void RELEASE_ARRAYS(JNIEnv *env, imageIODataPtr data, const JOCTET *next_byte, + int streamReleaseMode, int pixelReleaseMode) { - unpinStreamBuffer(env, &data->streamBuf, next_byte); + unpinStreamBuffer(env, &data->streamBuf, next_byte, streamReleaseMode); - unpinPixelBuffer(env, &data->pixelBuf); + unpinPixelBuffer(env, &data->pixelBuf, pixelReleaseMode); } /* - * Get (pin) all the arrays in use during a read. + * Get (pin) both stream and pixel arrays. */ static int GET_ARRAYS(JNIEnv *env, imageIODataPtr data, const JOCTET **next_byte) { if (pinStreamBuffer(env, &data->streamBuf, next_byte) == NOT_OK) { @@ -503,7 +506,7 @@ static int GET_ARRAYS(JNIEnv *env, imageIODataPtr data, const JOCTET **next_byte } if (pinPixelBuffer(env, &data->pixelBuf) == NOT_OK) { - RELEASE_ARRAYS(env, data, *next_byte); + RELEASE_ARRAYS(env, data, *next_byte, JNI_ABORT, JNI_ABORT); return NOT_OK; } return OK; @@ -570,26 +573,16 @@ sun_jpeg_output_message (j_common_ptr cinfo) theObject = data->imageIOobj; if (cinfo->is_decompressor) { - struct jpeg_source_mgr *src = ((j_decompress_ptr)cinfo)->src; - RELEASE_ARRAYS(env, data, src->next_input_byte); (*env)->CallVoidMethod(env, theObject, JPEGImageReader_warningWithMessageID, string); - if ((*env)->ExceptionCheck(env) - || !GET_ARRAYS(env, data, &(src->next_input_byte))) { - cinfo->err->error_exit(cinfo); - } } else { - struct jpeg_destination_mgr *dest = ((j_compress_ptr)cinfo)->dest; - RELEASE_ARRAYS(env, data, (const JOCTET *)(dest->next_output_byte)); (*env)->CallVoidMethod(env, theObject, JPEGImageWriter_warningWithMessageID, string); - if ((*env)->ExceptionCheck(env) - || !GET_ARRAYS(env, data, - (const JOCTET **)(&dest->next_output_byte))) { - cinfo->err->error_exit(cinfo); - } + } + if ((*env)->ExceptionCheck(env)) { + cinfo->err->error_exit(cinfo); } } @@ -941,7 +934,7 @@ imageio_fill_input_buffer(j_decompress_ptr cinfo) #ifdef DEBUG_IIO_JPEG printf("Filling input buffer, remaining skip is %ld, ", sb->remaining_skip); - printf("Buffer length is %d\n", sb->bufferLength); + printf("Buffer length is %zu\n", sb->bufferLength); #endif /* @@ -956,8 +949,15 @@ imageio_fill_input_buffer(j_decompress_ptr cinfo) /* * Now fill a complete buffer, or as much of one as the stream * will give us if we are near the end. + * + * The native copy of java array is not valid anymore so we just + * release it and get new copy, if we don't have native copy we rely + * on JVM to maintain the pinned handle of java array. */ - RELEASE_ARRAYS(env, data, src->next_input_byte); + jboolean isCopy = sb->isCopy; + if (isCopy) { + unpinStreamBuffer(env, &data->streamBuf, src->next_input_byte, JNI_ABORT); + } GET_IO_REF(input); @@ -969,9 +969,12 @@ imageio_fill_input_buffer(j_decompress_ptr cinfo) if ((ret > 0) && ((unsigned int)ret > sb->bufferLength)) { ret = (int)sb->bufferLength; } - if ((*env)->ExceptionCheck(env) - || !GET_ARRAYS(env, data, &(src->next_input_byte))) { - cinfo->err->error_exit((j_common_ptr) cinfo); + + if ((*env)->ExceptionCheck(env) || + (isCopy && (pinStreamBuffer(env, + &data->streamBuf, + &(src->next_input_byte)) == NOT_OK))) { + cinfo->err->error_exit((j_common_ptr) cinfo); } #ifdef DEBUG_IIO_JPEG @@ -988,12 +991,10 @@ imageio_fill_input_buffer(j_decompress_ptr cinfo) #ifdef DEBUG_IIO_JPEG printf("YO! Early EOI! ret = %d\n", ret); #endif - RELEASE_ARRAYS(env, data, src->next_input_byte); (*env)->CallVoidMethod(env, reader, JPEGImageReader_warningOccurredID, READ_NO_EOI); - if ((*env)->ExceptionCheck(env) - || !GET_ARRAYS(env, data, &(src->next_input_byte))) { + if ((*env)->ExceptionCheck(env)) { cinfo->err->error_exit((j_common_ptr) cinfo); } @@ -1008,97 +1009,6 @@ imageio_fill_input_buffer(j_decompress_ptr cinfo) return TRUE; } -/* - * With I/O suspension turned on, the JPEG library requires that all - * buffer filling be done at the top application level, using this - * function. Due to the way that backtracking works, this procedure - * saves all of the data that was left in the buffer when suspension - * occurred and read new data only at the end. - */ - -GLOBAL(void) -imageio_fill_suspended_buffer(j_decompress_ptr cinfo) -{ - struct jpeg_source_mgr *src = cinfo->src; - imageIODataPtr data = (imageIODataPtr) cinfo->client_data; - streamBufferPtr sb = &data->streamBuf; - JNIEnv *env = (JNIEnv *)JNU_GetEnv(the_jvm, JNI_VERSION_1_2); - jint ret; - size_t offset, buflen; - jobject input = NULL; - - /* - * The original (jpegdecoder.c) had code here that called - * InputStream.available and just returned if the number of bytes - * available was less than any remaining skip. Presumably this was - * to avoid blocking, although the benefit was unclear, as no more - * decompression can take place until more data is available, so - * the code would block on input a little further along anyway. - * ImageInputStreams don't have an available method, so we'll just - * block in the skip if we have to. - */ - - if (sb->remaining_skip) { - src->skip_input_data(cinfo, 0); - } - - /* Save the data currently in the buffer */ - offset = src->bytes_in_buffer; - if (src->next_input_byte > sb->buf) { - memcpy(sb->buf, src->next_input_byte, offset); - } - - - RELEASE_ARRAYS(env, data, src->next_input_byte); - - GET_IO_REF(input); - - buflen = sb->bufferLength - offset; - if (buflen <= 0) { - if (!GET_ARRAYS(env, data, &(src->next_input_byte))) { - cinfo->err->error_exit((j_common_ptr) cinfo); - } - RELEASE_ARRAYS(env, data, src->next_input_byte); - return; - } - - ret = (*env)->CallIntMethod(env, input, - JPEGImageReader_readInputDataID, - sb->hstreamBuffer, - offset, buflen); - if ((ret > 0) && ((unsigned int)ret > buflen)) ret = (int)buflen; - if ((*env)->ExceptionCheck(env) - || !GET_ARRAYS(env, data, &(src->next_input_byte))) { - cinfo->err->error_exit((j_common_ptr) cinfo); - } - /* - * If we have reached the end of the stream, then the EOI marker - * is missing. We accept such streams but generate a warning. - * The image is likely to be corrupted, though everything through - * the end of the last complete MCU should be usable. - */ - if (ret <= 0) { - jobject reader = data->imageIOobj; - RELEASE_ARRAYS(env, data, src->next_input_byte); - (*env)->CallVoidMethod(env, reader, - JPEGImageReader_warningOccurredID, - READ_NO_EOI); - if ((*env)->ExceptionCheck(env) - || !GET_ARRAYS(env, data, &(src->next_input_byte))) { - cinfo->err->error_exit((j_common_ptr) cinfo); - } - - sb->buf[offset] = (JOCTET) 0xFF; - sb->buf[offset + 1] = (JOCTET) JPEG_EOI; - ret = 2; - } - - src->next_input_byte = sb->buf; - src->bytes_in_buffer = ret + offset; - - return; -} - /* * Skip num_bytes worth of data. The buffer pointer and count are * advanced over num_bytes input bytes, using the input stream @@ -1160,16 +1070,13 @@ imageio_skip_input_data(j_decompress_ptr cinfo, long num_bytes) return; } - RELEASE_ARRAYS(env, data, src->next_input_byte); - GET_IO_REF(input); ret = (*env)->CallLongMethod(env, input, JPEGImageReader_skipInputBytesID, (jlong) num_bytes); - if ((*env)->ExceptionCheck(env) - || !GET_ARRAYS(env, data, &(src->next_input_byte))) { + if ((*env)->ExceptionCheck(env)) { cinfo->err->error_exit((j_common_ptr) cinfo); } @@ -1181,15 +1088,12 @@ imageio_skip_input_data(j_decompress_ptr cinfo, long num_bytes) */ if (ret <= 0) { reader = data->imageIOobj; - RELEASE_ARRAYS(env, data, src->next_input_byte); (*env)->CallVoidMethod(env, reader, JPEGImageReader_warningOccurredID, READ_NO_EOI); - - if ((*env)->ExceptionCheck(env) - || !GET_ARRAYS(env, data, &(src->next_input_byte))) { - cinfo->err->error_exit((j_common_ptr) cinfo); + if ((*env)->ExceptionCheck(env)) { + cinfo->err->error_exit((j_common_ptr) cinfo); } sb->buf[0] = (JOCTET) 0xFF; sb->buf[1] = (JOCTET) JPEG_EOI; @@ -1215,7 +1119,7 @@ imageio_term_source(j_decompress_ptr cinfo) JNIEnv *env = (JNIEnv *)JNU_GetEnv(the_jvm, JNI_VERSION_1_2); jobject reader = data->imageIOobj; if (src->bytes_in_buffer > 0) { - RELEASE_ARRAYS(env, data, src->next_input_byte); + RELEASE_ARRAYS(env, data, src->next_input_byte, JNI_ABORT, 0); (*env)->CallVoidMethod(env, reader, JPEGImageReader_pushBackID, @@ -1659,7 +1563,7 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImageHeader if (setjmp(jerr->setjmp_buffer)) { /* If we get here, the JPEG code has signaled an error while reading the header. */ - RELEASE_ARRAYS(env, data, src->next_input_byte); + RELEASE_ARRAYS(env, data, src->next_input_byte, JNI_ABORT, JNI_ABORT); if (!(*env)->ExceptionCheck(env)) { char buffer[JMSG_LENGTH_MAX]; (*cinfo->err->format_message) ((struct jpeg_common_struct *) cinfo, @@ -1678,7 +1582,7 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImageHeader (*env)->ExceptionClear(env); JNU_ThrowByName(env, "javax/imageio/IIOException", - "Array pin failed"); + "Get array elements failed"); return retval; } @@ -1701,7 +1605,11 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImageHeader printf("just read tables-only image; q table 0 at %p\n", cinfo->quant_tbl_ptrs[0]); #endif - RELEASE_ARRAYS(env, data, src->next_input_byte); + /* + * readImageHeader can be called independently, so + * we release the arrays when we return back. + */ + RELEASE_ARRAYS(env, data, src->next_input_byte, JNI_ABORT, 0); } else { /* * Now adjust the jpeg_color_space variable, which was set in @@ -1802,7 +1710,6 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImageHeader /* Leave the output space as CMYK */ } } - RELEASE_ARRAYS(env, data, src->next_input_byte); /* read icc profile data */ profileData = read_icc_profile(env, cinfo); @@ -1819,14 +1726,17 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImageHeader cinfo->out_color_space, cinfo->num_components, profileData); - if ((*env)->ExceptionCheck(env) - || !GET_ARRAYS(env, data, &(src->next_input_byte))) { + if ((*env)->ExceptionCheck(env)) { cinfo->err->error_exit((j_common_ptr) cinfo); } if (reset) { jpeg_abort_decompress(cinfo); } - RELEASE_ARRAYS(env, data, src->next_input_byte); + /* + * readImageHeader can be called independently, so + * we release the arrays when we return back. + */ + RELEASE_ARRAYS(env, data, src->next_input_byte, JNI_ABORT, 0); } return retval; @@ -1987,7 +1897,7 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImage if (setjmp(jerr->setjmp_buffer)) { /* If we get here, the JPEG code has signaled an error while reading. */ - RELEASE_ARRAYS(env, data, src->next_input_byte); + RELEASE_ARRAYS(env, data, src->next_input_byte, JNI_ABORT, JNI_ABORT); if (!(*env)->ExceptionCheck(env)) { char buffer[JMSG_LENGTH_MAX]; (*cinfo->err->format_message) ((struct jpeg_common_struct *) cinfo, @@ -2005,7 +1915,7 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImage (*env)->ExceptionClear(env); JNU_ThrowByName(env, "javax/imageio/IIOException", - "Array pin failed"); + "Get array elements failed"); return data->abortFlag; } @@ -2037,7 +1947,7 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImage jpeg_start_decompress(cinfo); if (numBands != cinfo->output_components) { - RELEASE_ARRAYS(env, data, src->next_input_byte); + RELEASE_ARRAYS(env, data, src->next_input_byte, JNI_ABORT, JNI_ABORT); JNU_ThrowByName(env, "javax/imageio/IIOException", "Invalid argument to native readImage"); return data->abortFlag; @@ -2046,7 +1956,7 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImage if (cinfo->output_components <= 0 || cinfo->image_width > (0xffffffffu / (unsigned int)cinfo->output_components)) { - RELEASE_ARRAYS(env, data, src->next_input_byte); + RELEASE_ARRAYS(env, data, src->next_input_byte, JNI_ABORT, JNI_ABORT); JNU_ThrowByName(env, "javax/imageio/IIOException", "Invalid number of output components"); return data->abortFlag; @@ -2055,7 +1965,7 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImage // Allocate a 1-scanline buffer scanLinePtr = (JSAMPROW)malloc(cinfo->image_width*cinfo->output_components); if (scanLinePtr == NULL) { - RELEASE_ARRAYS(env, data, src->next_input_byte); + RELEASE_ARRAYS(env, data, src->next_input_byte, JNI_ABORT, JNI_ABORT); JNU_ThrowByName( env, "java/lang/OutOfMemoryError", "Reading JPEG Stream"); @@ -2070,22 +1980,18 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImage // the first interesting pass. jpeg_start_output(cinfo, cinfo->input_scan_number); if (wantUpdates) { - RELEASE_ARRAYS(env, data, src->next_input_byte); (*env)->CallVoidMethod(env, this, JPEGImageReader_passStartedID, cinfo->input_scan_number-1); - if ((*env)->ExceptionCheck(env) - || !GET_ARRAYS(env, data, &(src->next_input_byte))) { + if ((*env)->ExceptionCheck(env)) { cinfo->err->error_exit((j_common_ptr) cinfo); } } } else if (wantUpdates) { - RELEASE_ARRAYS(env, data, src->next_input_byte); (*env)->CallVoidMethod(env, this, JPEGImageReader_passStartedID, 0); - if ((*env)->ExceptionCheck(env) - || !GET_ARRAYS(env, data, &(src->next_input_byte))) { + if ((*env)->ExceptionCheck(env)) { cinfo->err->error_exit((j_common_ptr) cinfo); } } @@ -2136,16 +2042,20 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImage } } - // And call it back to Java - RELEASE_ARRAYS(env, data, src->next_input_byte); + /* + * Optimisation to just commit the native pixel buffer + * content back to java array without releasing the + * native buffer. + */ + if (pb->isCopy) { + unpinPixelBuffer(env, pb, JNI_COMMIT); + } (*env)->CallVoidMethod(env, this, JPEGImageReader_acceptPixelsID, targetLine++, progressive); - - if ((*env)->ExceptionCheck(env) - || !GET_ARRAYS(env, data, &(src->next_input_byte))) { + if ((*env)->ExceptionCheck(env)) { cinfo->err->error_exit((j_common_ptr) cinfo); } @@ -2175,11 +2085,9 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImage done = TRUE; } if (wantUpdates) { - RELEASE_ARRAYS(env, data, src->next_input_byte); (*env)->CallVoidMethod(env, this, JPEGImageReader_passCompleteID); - if ((*env)->ExceptionCheck(env) - || !GET_ARRAYS(env, data, &(src->next_input_byte))) { + if ((*env)->ExceptionCheck(env)) { cinfo->err->error_exit((j_common_ptr) cinfo); } } @@ -2204,13 +2112,16 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageReader_readImage this, JPEGImageReader_skipPastImageID, imageIndex); + if ((*env)->ExceptionCheck(env)) { + cinfo->err->error_exit((j_common_ptr) cinfo); + } } else { jpeg_finish_decompress(cinfo); } free(scanLinePtr); - RELEASE_ARRAYS(env, data, src->next_input_byte); + RELEASE_ARRAYS(env, data, src->next_input_byte, JNI_ABORT, 0); return data->abortFlag; } @@ -2405,8 +2316,16 @@ imageio_empty_output_buffer (j_compress_ptr cinfo) JNIEnv *env = (JNIEnv *)JNU_GetEnv(the_jvm, JNI_VERSION_1_2); jobject output = NULL; - RELEASE_ARRAYS(env, data, (const JOCTET *)(dest->next_output_byte)); - + /* + * Optimization to not delete the native copy of stream buffer, + * but just commit the content back to the Java array. + * In case where we don't have a copy, we rely on JVM to maintain + * the native reference of Java array. + */ + jboolean isCopy = sb->isCopy; + if (isCopy) { + unpinStreamBuffer(env, sb, dest->next_output_byte, JNI_COMMIT); + } GET_IO_REF(output); (*env)->CallVoidMethod(env, @@ -2415,10 +2334,8 @@ imageio_empty_output_buffer (j_compress_ptr cinfo) sb->hstreamBuffer, 0, sb->bufferLength); - if ((*env)->ExceptionCheck(env) - || !GET_ARRAYS(env, data, - (const JOCTET **)(&dest->next_output_byte))) { - cinfo->err->error_exit((j_common_ptr) cinfo); + if ((*env)->ExceptionCheck(env)) { + cinfo->err->error_exit((j_common_ptr) cinfo); } dest->next_output_byte = sb->buf; @@ -2447,7 +2364,16 @@ imageio_term_destination (j_compress_ptr cinfo) if (datacount != 0) { jobject output = NULL; - RELEASE_ARRAYS(env, data, (const JOCTET *)(dest->next_output_byte)); + /* + * Optimization to not delete the native copy of stream buffer, + * but just commit the content back to the Java array. + * In case where we don't have a copy, we rely on JVM to maintain + * the native reference of Java array. + */ + jboolean isCopy = sb->isCopy; + if (isCopy) { + unpinStreamBuffer(env, sb, dest->next_output_byte, JNI_COMMIT); + } GET_IO_REF(output); @@ -2457,17 +2383,13 @@ imageio_term_destination (j_compress_ptr cinfo) sb->hstreamBuffer, 0, datacount); - - if ((*env)->ExceptionCheck(env) - || !GET_ARRAYS(env, data, - (const JOCTET **)(&dest->next_output_byte))) { + if ((*env)->ExceptionCheck(env)) { cinfo->err->error_exit((j_common_ptr) cinfo); } } dest->next_output_byte = NULL; dest->free_in_buffer = 0; - } /* @@ -2668,7 +2590,7 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_writeTables if (setjmp(jerr->setjmp_buffer)) { /* If we get here, the JPEG code has signaled an error while writing. */ - RELEASE_ARRAYS(env, data, (const JOCTET *)(dest->next_output_byte)); + RELEASE_ARRAYS(env, data, (const JOCTET *)(dest->next_output_byte), JNI_ABORT, JNI_ABORT); if (!(*env)->ExceptionCheck(env)) { char buffer[JMSG_LENGTH_MAX]; (*cinfo->err->format_message) ((j_common_ptr) cinfo, @@ -2683,7 +2605,7 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_writeTables (*env)->ExceptionClear(env); JNU_ThrowByName(env, "javax/imageio/IIOException", - "Array pin failed"); + "Get array elements failed"); return; } @@ -2703,7 +2625,15 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_writeTables } jpeg_write_tables(cinfo); // Flushes the buffer for you - RELEASE_ARRAYS(env, data, NULL); + /* + * writeTables can be called independently, so + * we release the arrays when we return back. + * Also the table content in output_buffer is + * already flushed, so no need to commit the + * native copy of stream content back to the + * Java array. + */ + RELEASE_ARRAYS(env, data, NULL, JNI_ABORT, 0); } static void freeArray(UINT8** arr, jint size) { @@ -2766,7 +2696,6 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_writeImage UINT8** scale = NULL; boolean success = TRUE; - /* verify the inputs */ if (data == NULL) { @@ -2891,7 +2820,7 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_writeImage if (setjmp(jerr->setjmp_buffer)) { /* If we get here, the JPEG code has signaled an error while writing. */ - RELEASE_ARRAYS(env, data, (const JOCTET *)(dest->next_output_byte)); + RELEASE_ARRAYS(env, data, (const JOCTET *)(dest->next_output_byte), JNI_ABORT, JNI_ABORT); if (!(*env)->ExceptionCheck(env)) { char buffer[JMSG_LENGTH_MAX]; (*cinfo->err->format_message) ((j_common_ptr) cinfo, @@ -2973,7 +2902,7 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_writeImage free(scanLinePtr); JNU_ThrowByName(env, "javax/imageio/IIOException", - "Array pin failed"); + "Get array elements failed"); return data->abortFlag; } @@ -3006,7 +2935,7 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_writeImage scanptr = (int *) cinfo->script_space; scanData = (*env)->GetIntArrayElements(env, scanInfo, NULL); if (scanData == NULL) { - RELEASE_ARRAYS(env, data, (const JOCTET *)(dest->next_output_byte)); + RELEASE_ARRAYS(env, data, (const JOCTET *)(dest->next_output_byte), JNI_ABORT, JNI_ABORT); freeArray(scale, numBands); free(scanLinePtr); return data->abortFlag; @@ -3034,16 +2963,13 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_writeImage if (haveMetadata) { // Flush the buffer imageio_flush_destination(cinfo); - // Call Java to write the metadata - RELEASE_ARRAYS(env, data, (const JOCTET *)(dest->next_output_byte)); + // Call Java to write the metadata. (*env)->CallVoidMethod(env, this, JPEGImageWriter_writeMetadataID); - if ((*env)->ExceptionCheck(env) - || !GET_ARRAYS(env, data, - (const JOCTET **)(&dest->next_output_byte))) { - cinfo->err->error_exit((j_common_ptr) cinfo); - } + if ((*env)->ExceptionCheck(env)) { + cinfo->err->error_exit((j_common_ptr) cinfo); + } } targetLine = 0; @@ -3053,20 +2979,29 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_writeImage // for each line in destHeight while ((data->abortFlag == JNI_FALSE) && (cinfo->next_scanline < cinfo->image_height)) { - // get the line from Java - RELEASE_ARRAYS(env, data, (const JOCTET *)(dest->next_output_byte)); + /* + * Get a line of pixel data from Java. + * In case where we have native copy of Java pixel array, + * we need to just use JNI_ABORT to exclude any copy operation + * and then get new copy for next scanline. + * + * If we have direct reference to Java array, we rely on + * JVM to maintain the reference appropriately. + */ + jboolean isCopy = pb->isCopy; + if (isCopy) { + unpinPixelBuffer(env, pb, JNI_ABORT); + } (*env)->CallVoidMethod(env, this, JPEGImageWriter_grabPixelsID, targetLine); - if ((*env)->ExceptionCheck(env) - || !GET_ARRAYS(env, data, - (const JOCTET **)(&dest->next_output_byte))) { - cinfo->err->error_exit((j_common_ptr) cinfo); - } + if ((*env)->ExceptionCheck(env) || + (isCopy && (pinPixelBuffer(env, pb) == NOT_OK))) { + cinfo->err->error_exit((j_common_ptr) cinfo); + } // subsample it into our buffer - in = data->pixelBuf.buf.bp; out = scanLinePtr; pixelLimit = in + ((pixelBufferSize > data->pixelBuf.byteBufferLength) ? @@ -3108,7 +3043,7 @@ Java_com_sun_imageio_plugins_jpeg_JPEGImageWriter_writeImage freeArray(scale, numBands); free(scanLinePtr); - RELEASE_ARRAYS(env, data, NULL); + RELEASE_ARRAYS(env, data, NULL, 0, JNI_ABORT); return data->abortFlag; } diff --git a/test/micro/org/openjdk/bench/javax/imageio/plugins/jpeg/LargeJpegReadWithProgressBench.java b/test/micro/org/openjdk/bench/javax/imageio/plugins/jpeg/LargeJpegReadWithProgressBench.java new file mode 100644 index 00000000000..70f8020f358 --- /dev/null +++ b/test/micro/org/openjdk/bench/javax/imageio/plugins/jpeg/LargeJpegReadWithProgressBench.java @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.javax.imageio.plugins.jpeg; + +import java.awt.Color; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.util.Iterator; + +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +import javax.imageio.ImageIO; +import javax.imageio.ImageReader; +import javax.imageio.event.IIOReadProgressListener; +import javax.imageio.stream.ImageInputStream; + +/** + * Measure time taken to read large jpeg image + * make test TEST="micro:javax.imageio.plugins.jpeg.LargeJpegReadWithProgressBench" + */ +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@Warmup(iterations = 5, time = 1) +@Measurement(iterations = 5, time = 1) +@Fork(3) +@State(Scope.Benchmark) +public class LargeJpegReadWithProgressBench { + + private static final File pwd = new File("."); + private static ImageReader reader; + + @Setup + public void setup() throws IOException { + BufferedImage src = createSource(); + ImageInputStream iis = prepareInput(src); + reader = null; + Iterator it = ImageIO.getImageReadersByFormatName("jpeg"); + if (it.hasNext()) { + reader = (ImageReader)it.next(); + } else { + throw new RuntimeException("Could not find JPEG reader"); + } + reader.setInput(iis); + ImageReadProgressListener listener = new ImageReadProgressListener(); + reader.addIIOReadProgressListener(listener); + } + + @Benchmark + public void readLargeJpegImage(Blackhole bh) throws IOException { + reader.read(0); + } + + private static BufferedImage createSource() { + int width = 2000; + int height = 2000; + int squareSize = 20; + + Color red = Color.RED; + Color green = Color.GREEN; + BufferedImage image = new BufferedImage(width, height, + BufferedImage.TYPE_INT_RGB); + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + if (((x / squareSize) + (y / squareSize)) % 2 == 0) { + image.setRGB(x, y, red.getRGB()); + } else { + image.setRGB(x, y, green.getRGB()); + } + } + } + return image; + } + + private static ImageInputStream prepareInput(BufferedImage src) + throws IOException { + File f = File.createTempFile("src_", ".jpeg", pwd); + if (ImageIO.write(src, "jpeg", f)) { + ImageInputStream iis = ImageIO.createImageInputStream(f); + f.deleteOnExit(); + return iis; + } else { + throw new RuntimeException("Unable to write jpeg image"); + } + } +} + +class ImageReadProgressListener implements IIOReadProgressListener { + // This class is a no-op, it is added just to have a progress listener + @Override + public void sequenceStarted(ImageReader source, int minIndex) { + + } + + @Override + public void sequenceComplete(ImageReader source) { + + } + + @Override + public void imageStarted(ImageReader source, int imageIndex) { + + } + + @Override + public void imageProgress(ImageReader source, float percentageDone) { + + } + + @Override + public void imageComplete(ImageReader source) { + + } + + @Override + public void thumbnailStarted(ImageReader source, int imageIndex, int thumbnailIndex) { + + } + + @Override + public void thumbnailProgress(ImageReader source, float percentageDone) { + + } + + @Override + public void thumbnailComplete(ImageReader source) { + + } + + @Override + public void readAborted(ImageReader source) { + + } +} diff --git a/test/micro/org/openjdk/bench/javax/imageio/plugins/jpeg/LargeJpegReadWriteBench.java b/test/micro/org/openjdk/bench/javax/imageio/plugins/jpeg/LargeJpegReadWriteBench.java new file mode 100644 index 00000000000..8a84eab4da7 --- /dev/null +++ b/test/micro/org/openjdk/bench/javax/imageio/plugins/jpeg/LargeJpegReadWriteBench.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package org.openjdk.bench.javax.imageio.plugins.jpeg; + +import java.awt.Color; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.util.Iterator; + +import java.util.concurrent.TimeUnit; + +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; + +import javax.imageio.ImageIO; +import javax.imageio.ImageReader; +import javax.imageio.ImageWriter; +import javax.imageio.stream.ImageInputStream; +import javax.imageio.stream.ImageOutputStream; + +/** + * Measure time taken to read large jpeg image + * make test TEST="micro:javax.imageio.plugins.jpeg.LargeJpegReadWriteBench" + */ +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +@Warmup(iterations = 5, time = 1) +@Measurement(iterations = 5, time = 1) +@Fork(3) +@State(Scope.Benchmark) +public class LargeJpegReadWriteBench { + + private static final File pwd = new File("."); + private static ImageReader reader; + private static ImageWriter writer; + private static BufferedImage src; + + @Setup + public void setup() throws IOException { + src = createSource(); + ImageInputStream iis = prepareInput(src); + reader = null; + Iterator readerIterator = ImageIO.getImageReadersByFormatName("jpeg"); + if (readerIterator.hasNext()) { + reader = readerIterator.next(); + } else { + throw new RuntimeException("Could not find JPEG reader"); + } + reader.setInput(iis); + + ImageOutputStream ios = prepareOutput(src); + writer = null; + Iterator writerIterator = ImageIO.getImageWritersByFormatName("jpeg"); + if (writerIterator.hasNext()) { + writer = writerIterator.next(); + } else { + throw new RuntimeException("Could not find JPEG writer"); + } + writer.setOutput(ios); + } + + @Benchmark + public void readLargeJpegImage(Blackhole bh) throws IOException { + reader.read(0); + } + + @Benchmark + public void writeLargeJpegImage(Blackhole bh) throws IOException { + writer.write(src); + } + + private static BufferedImage createSource() { + int width = 2000; + int height = 2000; + int squareSize = 20; + + Color red = Color.RED; + Color green = Color.GREEN; + BufferedImage image = new BufferedImage(width, height, + BufferedImage.TYPE_INT_RGB); + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + if (((x / squareSize) + (y / squareSize)) % 2 == 0) { + image.setRGB(x, y, red.getRGB()); + } else { + image.setRGB(x, y, green.getRGB()); + } + } + } + return image; + } + + private static ImageInputStream prepareInput(BufferedImage src) + throws IOException { + File f = File.createTempFile("src_", ".jpeg", pwd); + if (ImageIO.write(src, "jpeg", f)) { + ImageInputStream iis = ImageIO.createImageInputStream(f); + f.deleteOnExit(); + return iis; + } else { + throw new RuntimeException("Unable to write jpeg image"); + } + } + + private static ImageOutputStream prepareOutput(BufferedImage src) throws IOException { + File f = File.createTempFile("dest_", ".jpeg", pwd); + ImageOutputStream ios = ImageIO.createImageOutputStream(f); + f.deleteOnExit(); + return ios; + } +} From a8834d6dd076880202f553414d4fb1cc468e7891 Mon Sep 17 00:00:00 2001 From: Daniel Fuchs Date: Fri, 6 Mar 2026 09:28:51 +0000 Subject: [PATCH 193/305] 8378687: Improve delegation of HttpURLConnection Reviewed-by: rhalade, jpai, michaelm, skoivu --- .../classes/sun/net/www/protocol/http/HttpURLConnection.java | 4 ++-- .../protocol/https/AbstractDelegateHttpsURLConnection.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/java.base/share/classes/sun/net/www/protocol/http/HttpURLConnection.java b/src/java.base/share/classes/sun/net/www/protocol/http/HttpURLConnection.java index 480553e9a62..45e641f11ee 100644 --- a/src/java.base/share/classes/sun/net/www/protocol/http/HttpURLConnection.java +++ b/src/java.base/share/classes/sun/net/www/protocol/http/HttpURLConnection.java @@ -571,8 +571,8 @@ public class HttpURLConnection extends java.net.HttpURLConnection { throws ProtocolException { lock(); try { - if (connecting) { - throw new IllegalStateException("connect in progress"); + if (connected || connecting) { + throw new IllegalStateException("Already connected"); } super.setRequestMethod(method); } finally { diff --git a/src/java.base/share/classes/sun/net/www/protocol/https/AbstractDelegateHttpsURLConnection.java b/src/java.base/share/classes/sun/net/www/protocol/https/AbstractDelegateHttpsURLConnection.java index 1415658e34d..88449caaf09 100644 --- a/src/java.base/share/classes/sun/net/www/protocol/https/AbstractDelegateHttpsURLConnection.java +++ b/src/java.base/share/classes/sun/net/www/protocol/https/AbstractDelegateHttpsURLConnection.java @@ -178,7 +178,7 @@ public abstract class AbstractDelegateHttpsURLConnection extends public void connect() throws IOException { if (connected) return; - plainConnect(); + super.connect(); if (cachedResponse != null) { // using cached response return; From 0203dcff4b14675df9f4f9b8dc31b33b9c4097d2 Mon Sep 17 00:00:00 2001 From: Weijun Wang Date: Tue, 17 Mar 2026 21:03:49 +0000 Subject: [PATCH 194/305] 8377833: Enhance Jar file processing Reviewed-by: ahgross, rhalade, hchao, mullan --- .../share/classes/java/util/jar/JarVerifier.java | 6 +++--- .../sun/security/util/SignatureFileVerifier.java | 15 +++++++++++---- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/java.base/share/classes/java/util/jar/JarVerifier.java b/src/java.base/share/classes/java/util/jar/JarVerifier.java index d73231a4c61..e3bdb0307b9 100644 --- a/src/java.base/share/classes/java/util/jar/JarVerifier.java +++ b/src/java.base/share/classes/java/util/jar/JarVerifier.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -68,7 +68,7 @@ class JarVerifier { private ArrayList pendingBlocks; /* cache of CodeSigner objects */ - private ArrayList signerCache; + private List signerCache; /* Are we parsing a block? */ private boolean parsingBlockOrSF = false; @@ -288,7 +288,7 @@ class JarVerifier { String key = uname.substring(0, uname.lastIndexOf('.')); if (signerCache == null) - signerCache = new ArrayList<>(); + signerCache = new LinkedList<>(); if (manDig == null) { synchronized(manifestRawBytes) { diff --git a/src/java.base/share/classes/sun/security/util/SignatureFileVerifier.java b/src/java.base/share/classes/sun/security/util/SignatureFileVerifier.java index d7e65b6aef0..0b21ccbd294 100644 --- a/src/java.base/share/classes/sun/security/util/SignatureFileVerifier.java +++ b/src/java.base/share/classes/sun/security/util/SignatureFileVerifier.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -46,7 +46,12 @@ public class SignatureFileVerifier { /* Are we debugging ? */ private static final Debug debug = Debug.getInstance("jar"); - private final ArrayList signerCache; + private final List signerCache; + + // The maximum size of the signerCache. This is for debug only + // and not intended to be adjusted by users. + private static int SIGNER_CACHE_SIZE + = Integer.getInteger("sun.security.util.jar.signer.cache.size", 5); private static final String ATTR_DIGEST = "-DIGEST-" + ManifestDigester.MF_MAIN_ATTRS.toUpperCase(Locale.ENGLISH); @@ -97,7 +102,7 @@ public class SignatureFileVerifier { * * @param rawBytes the raw bytes of the signature block file */ - public SignatureFileVerifier(ArrayList signerCache, + public SignatureFileVerifier(List signerCache, ManifestDigester md, String name, byte[] rawBytes) @@ -282,7 +287,6 @@ public class SignatureFileVerifier { } finally { Providers.stopJarVerification(obj); } - } private void processImpl(Hashtable signers, @@ -850,6 +854,9 @@ public class SignatureFileVerifier { newSigners.length); } signerCache.add(cachedSigners); + if (signerCache.size() > SIGNER_CACHE_SIZE) { + signerCache.remove(0); + } signers.put(name, cachedSigners); } From 404a4dd17762c34ea4a5084acd6f07d9a8f21701 Mon Sep 17 00:00:00 2001 From: Artur Barashev Date: Wed, 8 Apr 2026 12:08:39 +0000 Subject: [PATCH 195/305] 8380672: Improve certification checking Reviewed-by: ahgross, jnibedita, pkumaraswamy, rhalade, weijun, mullan --- .../sun/security/util/HostnameChecker.java | 4 ++-- .../classes/sun/security/x509/DNSName.java | 8 +++++++- .../test/lib/security/CertificateBuilder.java | 20 ++++++++++++++++--- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/src/java.base/share/classes/sun/security/util/HostnameChecker.java b/src/java.base/share/classes/sun/security/util/HostnameChecker.java index 65115c9aeaf..b5a6e48e570 100644 --- a/src/java.base/share/classes/sun/security/util/HostnameChecker.java +++ b/src/java.base/share/classes/sun/security/util/HostnameChecker.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2002, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -263,7 +263,7 @@ public class HostnameChecker { * The name parameter should represent a DNS name. The * template parameter may contain the wildcard character '*'. */ - private boolean isMatched(String name, String template, + public boolean isMatched(String name, String template, boolean chainsToPublicCA) { // Normalize to Unicode, because PSL is in Unicode. diff --git a/src/java.base/share/classes/sun/security/x509/DNSName.java b/src/java.base/share/classes/sun/security/x509/DNSName.java index ce903a3d16c..17820d279a5 100644 --- a/src/java.base/share/classes/sun/security/x509/DNSName.java +++ b/src/java.base/share/classes/sun/security/x509/DNSName.java @@ -52,6 +52,8 @@ import sun.security.util.*; public class DNSName implements GeneralNameInterface { private final String name; + private static final HostnameChecker HOSTNAME_CHECKER = + HostnameChecker.getInstance(HostnameChecker.TYPE_TLS); private static final String DNS_ALLOWED = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-"; @@ -218,6 +220,9 @@ public class DNSName implements GeneralNameInterface { * For example, www.host.example.com would satisfy the constraint but * host1.example.com would not. *

    + * RFC6125: Match wildcard pattern in the input name being constrained, + * any wildcard in this name will be matched as a literal character. + *

    * RFC1034: By convention, domain names can be stored with arbitrary case, but * domain name comparisons for all present domain functions are done in a * case-insensitive manner, assuming an ASCII character set, and a high @@ -238,7 +243,8 @@ public class DNSName implements GeneralNameInterface { String inName = (((DNSName)inputName).getName()).toLowerCase(Locale.ENGLISH); String thisName = name.toLowerCase(Locale.ENGLISH); - if (inName.equals(thisName)) + + if (HOSTNAME_CHECKER.isMatched(thisName, inName, false)) constraintType = NAME_MATCH; else if (thisName.endsWith(inName)) { int inNdx = thisName.lastIndexOf(inName); diff --git a/test/lib/jdk/test/lib/security/CertificateBuilder.java b/test/lib/jdk/test/lib/security/CertificateBuilder.java index 6bf554c3517..a2d2a7d9eb1 100644 --- a/test/lib/jdk/test/lib/security/CertificateBuilder.java +++ b/test/lib/jdk/test/lib/security/CertificateBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,6 +24,7 @@ package jdk.test.lib.security; import java.io.*; +import java.net.IDN; import java.security.cert.*; import java.security.cert.Extension; import java.util.*; @@ -41,7 +42,9 @@ import sun.security.x509.AccessDescription; import sun.security.x509.AlgorithmId; import sun.security.x509.AuthorityInfoAccessExtension; import sun.security.x509.AuthorityKeyIdentifierExtension; +import sun.security.x509.GeneralSubtrees; import sun.security.x509.IPAddressName; +import sun.security.x509.NameConstraintsExtension; import sun.security.x509.SubjectKeyIdentifierExtension; import sun.security.x509.BasicConstraintsExtension; import sun.security.x509.CertificateSerialNumber; @@ -326,7 +329,6 @@ public class CertificateBuilder { * Helper method to add DNSName types for the SAN extension * * @param dnsNames A {@code List} of names to add as DNSName types - * * @throws IOException if an encoding error occurs. */ public CertificateBuilder addSubjectAltNameDNSExt(List dnsNames) @@ -334,7 +336,8 @@ public class CertificateBuilder { if (!dnsNames.isEmpty()) { GeneralNames gNames = new GeneralNames(); for (String name : dnsNames) { - gNames.add(new GeneralName(new DNSName(name))); + gNames.add(new GeneralName(new DNSName(new DerValue( + DerValue.tag_IA5String, IDN.toASCII(name))))); } addExtension(new SubjectAlternativeNameExtension(false, gNames)); @@ -437,6 +440,17 @@ public class CertificateBuilder { maxPathLen)); } + /** + * Set the Name Constraints Extension for a certificate. + * + * @param permitted permitted names + * @param excluded excluded names + */ + public CertificateBuilder addNameConstraintsExt( + GeneralSubtrees permitted, GeneralSubtrees excluded) { + return addExtension(new NameConstraintsExtension(permitted, excluded)); + } + /** * Add the Authority Key Identifier extension. * From 33e220059bace41b02017c035505fe6fd81844da Mon Sep 17 00:00:00 2001 From: Jayathirth D V Date: Fri, 10 Apr 2026 12:14:14 +0000 Subject: [PATCH 196/305] 8381039: Enhance AWT ImagingLib Reviewed-by: mschoene, rhalade, azvegint, prr --- .../libawt/awt/medialib/awt_ImagingLib.c | 39 ++++++++++++++++--- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/src/java.desktop/share/native/libawt/awt/medialib/awt_ImagingLib.c b/src/java.desktop/share/native/libawt/awt/medialib/awt_ImagingLib.c index bb93108f111..b6e10617cc3 100644 --- a/src/java.desktop/share/native/libawt/awt/medialib/awt_ImagingLib.c +++ b/src/java.desktop/share/native/libawt/awt/medialib/awt_ImagingLib.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -2218,7 +2218,11 @@ allocateArray(JNIEnv *env, BufImageS_t *imageP, /* Means we need to fill in alpha */ if (!cvtToDefault && addAlpha) { *mlibImagePP = (*sMlibSysFns.createFP)(MLIB_BYTE, 4, width, height); - if (*mlibImagePP != NULL) { + if (*mlibImagePP == NULL) { + (*env)->ReleasePrimitiveArrayCritical(env, rasterP->jdata, dataP, + JNI_ABORT); + return -1; + } else { unsigned int *dstP = (unsigned int *) mlib_ImageGetData(*mlibImagePP); int dstride = (*mlibImagePP)->stride>>2; @@ -2234,10 +2238,10 @@ allocateArray(JNIEnv *env, BufImageS_t *imageP, dP[x] = sP[x] | 0xff000000; } } + (*env)->ReleasePrimitiveArrayCritical(env, rasterP->jdata, dataP, + JNI_ABORT); + return 0; } - (*env)->ReleasePrimitiveArrayCritical(env, rasterP->jdata, dataP, - JNI_ABORT); - return 0; } else if ((hintP->packing & BYTE_INTERLEAVED) == BYTE_INTERLEAVED) { int nChans = (cmP->isDefaultCompatCM ? 4 : hintP->numChans); @@ -2252,6 +2256,11 @@ allocateArray(JNIEnv *env, BufImageS_t *imageP, hintP->sStride, (unsigned char *)dataP + hintP->dataOffset); + if (*mlibImagePP == NULL) { + (*env)->ReleasePrimitiveArrayCritical(env, rasterP->jdata, dataP, + JNI_ABORT); + return -1; + } } else if ((hintP->packing & SHORT_INTERLEAVED) == SHORT_INTERLEAVED) { *mlibImagePP = (*sMlibSysFns.createStructFP)(MLIB_SHORT, @@ -2261,6 +2270,11 @@ allocateArray(JNIEnv *env, BufImageS_t *imageP, imageP->raster.scanlineStride*2, (unsigned short *)dataP + hintP->channelOffset); + if (*mlibImagePP == NULL) { + (*env)->ReleasePrimitiveArrayCritical(env, rasterP->jdata, dataP, + JNI_ABORT); + return -1; + } } else { /* Release the data array */ @@ -2360,6 +2374,11 @@ allocateRasterArray(JNIEnv *env, RasterS_t *rasterP, width, height, rasterP->scanlineStride*4, (unsigned char *)dataP + offset); + if (*mlibImagePP == NULL) { + (*env)->ReleasePrimitiveArrayCritical(env, rasterP->jdata, dataP, + JNI_ABORT); + return -1; + } *dataPP = dataP; return 0; case sun_awt_image_IntegerComponentRaster_TYPE_BYTE_SAMPLES: @@ -2388,6 +2407,11 @@ allocateRasterArray(JNIEnv *env, RasterS_t *rasterP, width, height, rasterP->scanlineStride, (unsigned char *)dataP + offset); + if (*mlibImagePP == NULL) { + (*env)->ReleasePrimitiveArrayCritical(env, rasterP->jdata, dataP, + JNI_ABORT); + return -1; + } *dataPP = dataP; return 0; case sun_awt_image_IntegerComponentRaster_TYPE_USHORT_SAMPLES: @@ -2418,6 +2442,11 @@ allocateRasterArray(JNIEnv *env, RasterS_t *rasterP, width, height, rasterP->scanlineStride*2, (unsigned char *)dataP + offset); + if (*mlibImagePP == NULL) { + (*env)->ReleasePrimitiveArrayCritical(env, rasterP->jdata, dataP, + JNI_ABORT); + return -1; + } *dataPP = dataP; return 0; From 48d32601cde66fb211f89885bee8a0bd182cbea0 Mon Sep 17 00:00:00 2001 From: Anthony Scarpino Date: Thu, 16 Apr 2026 17:15:33 +0000 Subject: [PATCH 197/305] 8381519: Enhance Der Value Handling Reviewed-by: mschoene, jnimeh, valeriep --- .../share/classes/sun/security/util/DerValue.java | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/java.base/share/classes/sun/security/util/DerValue.java b/src/java.base/share/classes/sun/security/util/DerValue.java index ec8b482b07d..8d86c8dd143 100644 --- a/src/java.base/share/classes/sun/security/util/DerValue.java +++ b/src/java.base/share/classes/sun/security/util/DerValue.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -157,6 +157,9 @@ public class DerValue { */ public static final byte tag_SetOf = 0x31; + // Max nested depth for constructed data + private static final int MAX_CONSTRUCTED_NEST = 30; + // This class is mostly immutable except that: // // 1. resetTag() modifies the tag @@ -564,6 +567,14 @@ public class DerValue { * @return the octet string held in this DER value */ public byte[] getOctetString() throws IOException { + return getOctetString(0); + } + + private byte[] getOctetString(int limit) throws IOException { + if (++limit > MAX_CONSTRUCTED_NEST) { + throw new IOException("Nested OctetString limit reached (" + + MAX_CONSTRUCTED_NEST + ")."); + } if (tag != tag_OctetString && !isConstructed(tag_OctetString)) { throw new IOException( @@ -582,7 +593,7 @@ public class DerValue { ByteArrayOutputStream bout = new ByteArrayOutputStream(); DerInputStream dis = data(); while (dis.available() > 0) { - bout.write(dis.getDerValue().getOctetString()); + bout.write(dis.getDerValue().getOctetString(limit)); } return bout.toByteArray(); } From 7e17c402e4fb0a3cffabef43352d91a62f0f94b8 Mon Sep 17 00:00:00 2001 From: Jamil Nimeh Date: Thu, 23 Apr 2026 00:55:06 +0000 Subject: [PATCH 198/305] 8381796: Enhance Certificate parsing Reviewed-by: ascarpino, abarashev, rhalade, mdonovan --- .../provider/certpath/URICertStore.java | 89 +++++++++++++- .../sun/security/util/SecurityProperties.java | 32 ++++- .../share/conf/security/java.security | 16 +++ .../certpath/ldap/LDAPCertStoreImpl.java | 45 ++++++- .../test/lib/security/CertificateBuilder.java | 114 +++++++++++++----- 5 files changed, 261 insertions(+), 35 deletions(-) diff --git a/src/java.base/share/classes/sun/security/provider/certpath/URICertStore.java b/src/java.base/share/classes/sun/security/provider/certpath/URICertStore.java index 3e1fc8db164..6eb95f92246 100644 --- a/src/java.base/share/classes/sun/security/provider/certpath/URICertStore.java +++ b/src/java.base/share/classes/sun/security/provider/certpath/URICertStore.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2006, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2006, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,6 +25,7 @@ package sun.security.provider.certpath; +import java.io.FilterInputStream; import java.io.InputStream; import java.io.IOException; import java.net.HttpURLConnection; @@ -188,6 +189,16 @@ class URICertStore extends CertStoreSpi { return timeoutVal; } + /** + * Maximum size for a CRL downloaded through a URICertStore + * in bytes. This can be controlled by the com.sun.security.crl.maxSize + * Security or System property. The System property, if set, overrides + * the Security property. The default size is 20MiB. + */ + private static final long MAX_CRL_DOWNLOAD_SIZE = + SecurityProperties.getOverridableLongProp( + "com.sun.security.crl.maxSize", 20971520, debug); + /** * Enumeration for the allowed schemes we support when following a * URI from an authorityInfoAccess extension on a certificate. @@ -228,6 +239,13 @@ class URICertStore extends CertStoreSpi { private static final boolean CA_ISS_ALLOW_ANY; static { + // Add a debug message for the configured CRL download limit + if (debug != null) { + debug.println("Maximum downloadable CRL size: " + + MAX_CRL_DOWNLOAD_SIZE + + ((MAX_CRL_DOWNLOAD_SIZE < 0) ? " (DISABLED)" : "")); + } + boolean allowAny = false; try { if (Builder.USE_AIA) { @@ -623,7 +641,19 @@ class URICertStore extends CertStoreSpi { if (debug != null) { debug.println("Downloading new CRL..."); } - crl = (X509CRL) factory.generateCRL(in); + InputStream crlIn = (MAX_CRL_DOWNLOAD_SIZE > -1) ? + new SizeLimitedInputStream(in, MAX_CRL_DOWNLOAD_SIZE) : + in; + try { + crl = (X509CRL) factory.generateCRL(crlIn); + } catch (IllegalArgumentException iae) { + // IAE should only be thrown when the CRL exceeds a + // configured maximum length. + if (debug != null) { + debug.println("Discarding CRL: " + iae.getMessage()); + crl = null; + } + } } return getMatchingCRLs(crl, selector); } catch (IOException | CRLException e) { @@ -816,4 +846,59 @@ class URICertStore extends CertStoreSpi { return true; } } + + /** + * Stream wrapper used when an InputStream passed into a CertificateFactory + * needs to be size limited. It will throw IllegalArgumentException when + * the downloaded resource via the underlying stream exceeds the maximum + * limit. + */ + private static class SizeLimitedInputStream extends FilterInputStream { + + private final long maxBytes; + private long bytesRead = 0; + + private SizeLimitedInputStream(InputStream in, long maxBytes) { + super(in); + this.maxBytes = maxBytes; + } + + @Override + public int read() throws IOException { + if (bytesRead >= maxBytes) { + // We will use IAE here to differentiate this special case + // from other IOEs that the underlying input stream might + // legitimately throw. + throw new IllegalArgumentException("InputStream exceeded max " + + "size of " + maxBytes); + } + + int b = super.read(); + if (b != -1) { + bytesRead++; + } + return b; + } + + @Override + public int read(byte[] b, int off, int len) throws IOException { + + if (bytesRead >= maxBytes) { + // We will use IAE here to differentiate this special case + // from other IOEs that the underlying input stream might + // legitimately throw. + throw new IllegalArgumentException("InputStream exceeded max " + + "size of " + maxBytes); + } + + long remaining = maxBytes - bytesRead; + int toRead = (int) Math.min(len, remaining); + + int n = super.read(b, off, toRead); + if (n != -1) { + bytesRead += n; + } + return n; + } + } } diff --git a/src/java.base/share/classes/sun/security/util/SecurityProperties.java b/src/java.base/share/classes/sun/security/util/SecurityProperties.java index 98bc71d829b..da69ecbf5d6 100644 --- a/src/java.base/share/classes/sun/security/util/SecurityProperties.java +++ b/src/java.base/share/classes/sun/security/util/SecurityProperties.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2018 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -139,6 +139,36 @@ public class SecurityProperties { } } + /** + * A convenience routine for fetching a numeric value from a Security + * or System property and returning it as a long. The value from the + * property is obtained according to the logic in + * {@link SecurityProperties#getOverridableProperty(String)} + * + * @param prop the property to query + * @param defaultValue the default value + * @param dbg a Debug object, if null no debug messages will be sent + * @return the value of the property as a {@code long}. If a non-numeric + * value is supplied, the default value will be returned. + */ + public static long getOverridableLongProp(String prop, long defaultValue, + Debug dbg) { + long longVal = defaultValue; + try { + String propVal = SecurityProperties.getOverridableProperty(prop); + if (propVal != null) { + longVal = Long.parseLong(propVal); + } + } catch (NumberFormatException nfe) { + // We will use the default, but add a warning debug message + if (dbg != null) { + dbg.println("Warning: Non-numeric value found in property " + + prop + ", using default value of " + defaultValue); + } + } + return longVal; + } + /** * Convenience method for fetching System property values that are booleans. * diff --git a/src/java.base/share/conf/security/java.security b/src/java.base/share/conf/security/java.security index 26842d0c845..2fc908c6bf9 100644 --- a/src/java.base/share/conf/security/java.security +++ b/src/java.base/share/conf/security/java.security @@ -1714,6 +1714,22 @@ jdk.epkcs8.defaultAlgorithm=PBEWithHmacSHA256AndAES_128 # ldap://ldap.company.com/dc=company,dc=com?caCertificate;binary com.sun.security.allowedAIALocations= +# +# Certificate Revocation List (CRL) Download Size Limitation +# +# This property sets a size limit for CRLs downloaded via URIs provided +# in the CRL Distribution Points certificate extension. This property +# must be a numeric value that is the size in bytes of the DER-encoded CRL. +# For protocols that can return multi-value responses, such as LDAP, the +# size threshold is the sum of all CRLs downloaded from a single search +# query. CRLs that exceed this length will not be processed during certificate +# path validation. This size limit does not apply to CRLs that are imported +# through non-network-based means. A negative value will disable this size +# limitation. A non-numeric value will be ignored and the default size will +# be used instead. The default size limit is 20MiB. +# This property may be overridden by a System property of the same name. +com.sun.security.crl.maxSize = 20971520 + # # PKCS #8 encoding format for newly created ML-KEM and ML-DSA private keys # diff --git a/src/java.naming/share/classes/sun/security/provider/certpath/ldap/LDAPCertStoreImpl.java b/src/java.naming/share/classes/sun/security/provider/certpath/ldap/LDAPCertStoreImpl.java index 8f18e04760a..ebf09e57bd1 100644 --- a/src/java.naming/share/classes/sun/security/provider/certpath/ldap/LDAPCertStoreImpl.java +++ b/src/java.naming/share/classes/sun/security/provider/certpath/ldap/LDAPCertStoreImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -52,6 +52,7 @@ import sun.security.util.HexDumpEncoder; import sun.security.provider.certpath.X509CertificatePair; import sun.security.util.Cache; import sun.security.util.Debug; +import sun.security.util.SecurityProperties; /** * Core implementation of a LDAP Cert Store. @@ -96,6 +97,16 @@ final class LDAPCertStoreImpl { private static final String PROP_DISABLE_APP_RESOURCE_FILES = "sun.security.certpath.ldap.disable.app.resource.files"; + /** + * Maximum size for a CRL downloaded through an LDAPCertStoreImpl + * in bytes. This can be controlled by the com.sun.security.crl.maxSize + * Security or System property. The System property, if set, overrides + * the Security property. The default size is 20MiB. + */ + private static final long MAX_CRL_DOWNLOAD_SIZE = + SecurityProperties.getOverridableLongProp( + "com.sun.security.crl.maxSize", 20971520, debug); + static { String s = System.getProperty(PROP_LIFETIME); if (s != null) { @@ -103,6 +114,13 @@ final class LDAPCertStoreImpl { } else { LIFETIME = DEFAULT_CACHE_LIFETIME; } + + // Add a debug message for the configured CRL download limit + if (debug != null) { + debug.println("Maximum downloadable CRL size: " + + MAX_CRL_DOWNLOAD_SIZE + + ((MAX_CRL_DOWNLOAD_SIZE < 0) ? " (DISABLED)" : "")); + } } /** @@ -672,12 +690,12 @@ final class LDAPCertStoreImpl { return certs; } - /* + /** * Gets CRLs from an attribute id and location in the LDAP directory. * Returns a Collection containing only the CRLs that match the * specified X509CRLSelector. * - * @param name the location holding the attribute + * @param request the LDAP request used for this CRL fetch operation * @param id the attribute identifier * @param sel a X509CRLSelector that the CRLs must match * @return a Collection of CRLs found @@ -689,7 +707,26 @@ final class LDAPCertStoreImpl { /* fetch the encoded crls from storage */ byte[][] encodedCRL; try { - encodedCRL = request.getValues(id); + byte[][] tmpCrls = request.getValues(id); + if (MAX_CRL_DOWNLOAD_SIZE > -1) { + int totalSize = 0; + for (byte[] tCrl : tmpCrls) { + totalSize += tCrl.length; + } + if (totalSize <= MAX_CRL_DOWNLOAD_SIZE) { + encodedCRL = tmpCrls; + } else { + if (debug != null) { + debug.println("Received " + tmpCrls.length + + " CRL(s). Combined length of " + totalSize + + " exceeds configured maximum. Discarding."); + } + encodedCRL = new byte[0][]; + } + } else { + // Download limits disabled + encodedCRL = tmpCrls; + } } catch (NamingException namingEx) { throw new CertStoreException(namingEx); } diff --git a/test/lib/jdk/test/lib/security/CertificateBuilder.java b/test/lib/jdk/test/lib/security/CertificateBuilder.java index a2d2a7d9eb1..86aaba2a0b1 100644 --- a/test/lib/jdk/test/lib/security/CertificateBuilder.java +++ b/test/lib/jdk/test/lib/security/CertificateBuilder.java @@ -42,6 +42,7 @@ import sun.security.x509.AccessDescription; import sun.security.x509.AlgorithmId; import sun.security.x509.AuthorityInfoAccessExtension; import sun.security.x509.AuthorityKeyIdentifierExtension; +import sun.security.x509.CRLDistributionPointsExtension; import sun.security.x509.GeneralSubtrees; import sun.security.x509.IPAddressName; import sun.security.x509.NameConstraintsExtension; @@ -49,6 +50,7 @@ import sun.security.x509.SubjectKeyIdentifierExtension; import sun.security.x509.BasicConstraintsExtension; import sun.security.x509.CertificateSerialNumber; import sun.security.x509.ExtendedKeyUsageExtension; +import sun.security.x509.DistributionPoint; import sun.security.x509.DNSName; import sun.security.x509.GeneralName; import sun.security.x509.GeneralNames; @@ -61,13 +63,13 @@ import sun.security.x509.X500Name; /** * Helper class that builds and signs X.509 certificates. - * + *

    * A CertificateBuilder is created with a default constructor, and then * uses additional public methods to set the public key, desired validity * dates, serial number and extensions. It is expected that the caller will * have generated the necessary key pairs prior to using a CertificateBuilder * to generate certificates. - * + *

    * The following methods are mandatory before calling build(): *