diff --git a/make/autoconf/flags-ldflags.m4 b/make/autoconf/flags-ldflags.m4 index 7876511328b9..1da98f5cdebf 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 diff --git a/make/jdk/src/classes/build/tools/cldrconverter/ResourceBundleGenerator.java b/make/jdk/src/classes/build/tools/cldrconverter/ResourceBundleGenerator.java index 84657ae94f00..8e9635ab519e 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.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.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 @@ public void generateBundle(String packageName, String baseName, String localeID, 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 @@ public void generateMetaInfo(Map> metaInfo) throws IOE 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 @@ public class %s implements LocaleDataMetaInfo { 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 @@ public class %s implements LocaleDataMetaInfo { 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 @@ public Map parentLocales() { return parentLocalesMap; } + public Set baseModuleLocales() { + return baseModuleLocales; + } + // package access from CLDRLocaleProviderAdapter Map likelyScriptMap() { return CLDRMapHolder.likelyScriptMap; diff --git a/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/c2_MacroAssembler_aarch64.cpp index eacfef9618a0..fe9180bda5cc 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/src/hotspot/cpu/aarch64/frame_aarch64.hpp b/src/hotspot/cpu/aarch64/frame_aarch64.hpp index ac4740645b84..55a6dde38f59 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/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/gc/shenandoah/shenandoahBarrierSetAssembler_aarch64.cpp index 7406aa0c1c4a..19c82ed77efe 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 d25dd8871f9e..62273a44da2c 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 @@ class ShenandoahBarrierSetAssembler: public BarrierSetAssembler { 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/arm/frame_arm.hpp b/src/hotspot/cpu/arm/frame_arm.hpp index 2ef44414e1c0..8ca7a555b933 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 bf49bbb7e018..43d5fd410682 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/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.cpp b/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.cpp index b17f0f924aeb..7dbb01822661 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 8d741e6104b4..0784c8b7148f 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 @@ class ShenandoahBarrierSetAssembler: public BarrierSetAssembler { 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/frame_riscv.hpp b/src/hotspot/cpu/riscv/frame_riscv.hpp index d5f04ee3ff70..5cf341aa21b9 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/riscv/gc/shenandoah/shenandoahBarrierSetAssembler_riscv.cpp b/src/hotspot/cpu/riscv/gc/shenandoah/shenandoahBarrierSetAssembler_riscv.cpp index 574c70c8ea4b..d7cfcb112057 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 ecb63e68a016..eb8ac653e2ea 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 @@ class ShenandoahBarrierSetAssembler: public BarrierSetAssembler { 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/riscv/globals_riscv.hpp b/src/hotspot/cpu/riscv/globals_riscv.hpp index dc3915aa3988..d399bc13082f 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/cpu/s390/abstractInterpreter_s390.cpp b/src/hotspot/cpu/s390/abstractInterpreter_s390.cpp index 96990f0ce943..c54f1a4b0104 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 c0cee5bd5553..c834a71ec0c9 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 *(uint8_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 08f922a0b9ae..db3f2f6218fb 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 e78b04fe9110..d26db67d0780 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 e4e611d2b152..15b1347ce0a3 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 1d4e3c2439d9..58ff8f0d1940 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 1102a745ac06..2f7660052c07 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 fb7d998c458d..11944a8f0405 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 b602d0adce57..af4c670132a0 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 664a49fdd21b..8a99bdb8df58 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,17 @@ 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 6fcd36c57d17..e31b0d5a4263 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 80ed6d1acc89..745a6171ca21 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 d50cb833e683..5d86a0c3182e 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 a210588d0624..6921fd05ff04 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 ea75d483e5f0..6eb144524016 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. @@ -6149,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. @@ -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 8e2834ba9b70..9dd4054a36c1 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 72724fb66d11..24bec32f8b4c 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 546f8b133975..3520e9a34935 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 9852bc410b1d..0ba97830bb70 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 c0e51bd2bfd3..256e39b03c2b 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) %{ @@ -2614,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]); %} %} @@ -5557,6 +5563,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 e5a27e66968b..1a13e76e9308 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 f338fb192ad4..630a9516831a 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 @@ class SmallRegisterMapType { 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 @@ class SmallRegisterMapType { #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 e598117fe7d0..3a5b860b7a73 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 dfd3562c9d96..c97751d0d1eb 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 d0e26beedab9..d773b6ce7596 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 5309158fc742..381d1c02277f 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,182 @@ 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); + +#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); + __ 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 +3486,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 dba04fc0e852..03470597ab5c 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 3b0929608a38..1db9c54aef5c 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() { @@ -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); @@ -3504,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(); @@ -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 23ac80ddf482..de57e5e0cc49 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/cpu/x86/frame_x86.hpp b/src/hotspot/cpu/x86/frame_x86.hpp index d97e6b847b44..50f0c6b7eb6f 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/x86/gc/shenandoah/shenandoahBarrierSetAssembler_x86.cpp b/src/hotspot/cpu/x86/gc/shenandoah/shenandoahBarrierSetAssembler_x86.cpp index bdb98d4b4c02..480e484f4b1f 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 @@ -679,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); @@ -703,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); diff --git a/src/hotspot/cpu/x86/gc/shenandoah/shenandoahBarrierSetAssembler_x86.hpp b/src/hotspot/cpu/x86/gc/shenandoah/shenandoahBarrierSetAssembler_x86.hpp index 7f417d3c262c..7c1a89b74f53 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 @@ class ShenandoahBarrierSetAssembler: public BarrierSetAssembler { 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); diff --git a/src/hotspot/cpu/x86/macroAssembler_x86.cpp b/src/hotspot/cpu/x86/macroAssembler_x86.cpp index d1250f0820f5..6c0b1178b0e6 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 afd9c126a213..2b37e39ec86d 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/src/hotspot/cpu/x86/stubGenerator_x86_64_kyber.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64_kyber.cpp index c35a2a1bba6d..840f848d3baf 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 53696ee6ef32..6112c280a1dc 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; } diff --git a/src/hotspot/cpu/zero/frame_zero.hpp b/src/hotspot/cpu/zero/frame_zero.hpp index 45d1cb82e829..514b134e36b7 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/os_cpu/linux_riscv/riscv_hwprobe.cpp b/src/hotspot/os_cpu/linux_riscv/riscv_hwprobe.cpp index 3ede62e14cd2..fe555ec5ffb3 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(); } 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 648131b94a39..c9556d32cc50 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(); diff --git a/src/hotspot/share/classfile/classLoaderData.cpp b/src/hotspot/share/classfile/classLoaderData.cpp index d1ea9c09d4cc..95e1ef3b8774 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 64fcfb7519f6..3a0a05126af4 100644 --- a/src/hotspot/share/classfile/classLoaderData.hpp +++ b/src/hotspot/share/classfile/classLoaderData.hpp @@ -242,7 +242,7 @@ class ClassLoaderData : public CHeapObj { 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 @@ class ClassLoaderData : public CHeapObj { 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 4c4427b19e17..df29dca053b9 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 diff --git a/src/hotspot/share/code/codeCache.cpp b/src/hotspot/share/code/codeCache.cpp index 94cf8ebdec1d..efe5d6549eb5 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; } } diff --git a/src/hotspot/share/compiler/compiler_globals.hpp b/src/hotspot/share/compiler/compiler_globals.hpp index e1f8d9f8922d..effe6cc0725d 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/gc/g1/g1CollectedHeap.cpp b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp index 3c41133e572d..f60ce9b15b4b 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 cb466a5e120d..672dea9b7b0e 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.hpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.hpp @@ -953,7 +953,6 @@ class G1CollectedHeap : public CollectedHeap { 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 233901c30f88..73a697f8c514 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 c5af4a8220b0..1e838b344b7f 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 ff757f205a26..777b734c59ec 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 13532dea07d4..a88a08783054 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 3de562e886d6..eb2bed109b59 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/src/hotspot/share/gc/shenandoah/shenandoahAllocRate.hpp b/src/hotspot/share/gc/shenandoah/shenandoahAllocRate.hpp index 24221e504fde..ca94b91200ad 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 @@ class ShenandoahAllocRate { 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 @@ class ShenandoahAllocRate { } 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 9ffad0d312c8..e317721cd7b7 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,38 +58,54 @@ 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.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; - take_sample(now, elapsed, unsampled); + const uint64_t params = _sample_params.load_relaxed(); + const uint32_t log_per_stripe_threshold = decode_log_per_stripe_threshold(params); - _sample_lock.unlock(); + // 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 @@ -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/shenandoahBarrierSet.hpp b/src/hotspot/share/gc/shenandoah/shenandoahBarrierSet.hpp index 51b355e70420..ac896f247396 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 @@ class ShenandoahBarrierSet: public BarrierSet { 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 @@ class ShenandoahBarrierSet: public BarrierSet { 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 f4b859afc448..af0622693fb4 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); + } + } } } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahHeap.inline.hpp b/src/hotspot/share/gc/shenandoah/shenandoahHeap.inline.hpp index 69eaf1589d24..b3c847cadaf6 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); - - 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); - } + 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"); + + // 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/shenandoahStripedCounter.cpp b/src/hotspot/share/gc/shenandoah/shenandoahStripedCounter.cpp new file mode 100644 index 000000000000..d6e55d062487 --- /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 000000000000..ad1086005a65 --- /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 000000000000..58c400893247 --- /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/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp b/src/hotspot/share/gc/shenandoah/shenandoah_globals.hpp index 793b2f3b6d16..d76348b030aa 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 "\ diff --git a/src/hotspot/share/interpreter/abstractInterpreter.hpp b/src/hotspot/share/interpreter/abstractInterpreter.hpp index 23618cb037ec..6c555f0c008d 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/nmt/regionsTree.inline.hpp b/src/hotspot/share/nmt/regionsTree.inline.hpp index 793a5c5f1fab..7f1714fb939a 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) { diff --git a/src/hotspot/share/nmt/virtualMemoryTracker.cpp b/src/hotspot/share/nmt/virtualMemoryTracker.cpp index e23076a12bff..08ea3699bc29 100644 --- a/src/hotspot/share/nmt/virtualMemoryTracker.cpp +++ b/src/hotspot/share/nmt/virtualMemoryTracker.cpp @@ -281,7 +281,6 @@ class RegionIterator : public StackObj { 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"); diff --git a/src/hotspot/share/oops/stackChunkOop.inline.hpp b/src/hotspot/share/oops/stackChunkOop.inline.hpp index d0ddbe8dfe6f..3ad28190d026 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/opto/c2_globals.hpp b/src/hotspot/share/opto/c2_globals.hpp index cbe149fd01a0..dd9288f76179 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") \ \ @@ -914,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/callGenerator.cpp b/src/hotspot/share/opto/callGenerator.cpp index d0b48982b0f0..10df25abcb8b 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/castnode.cpp b/src/hotspot/share/opto/castnode.cpp index 7bb6b1dcb77d..076a95acfd88 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 828e5bf299f7..ed5da0466089 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 4d06e20875ab..c296237de37b 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) @@ -288,6 +289,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 e5f91875516e..db43c6fb1c43 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), @@ -748,11 +751,13 @@ 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); } Init(/*do_aliasing=*/ true); + set_dead_path(new DeadPathNode()); print_compile_messages(); @@ -962,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), @@ -2051,10 +2057,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 +2079,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); @@ -2623,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();) } @@ -2647,12 +2662,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); @@ -3929,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 ab36f59a28fc..73e136787f82 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 @@ class Compile : public Phase { 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 @@ class Compile : public Phase { 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 a495814da615..d706a13feb3a 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 1687ff2cade4..3b51491294e3 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 366e3fb882db..de89dcaad06e 100644 --- a/src/hotspot/share/opto/divnode.hpp +++ b/src/hotspot/share/opto/divnode.hpp @@ -40,7 +40,9 @@ class DivModIntegerNode : public Node { 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 @@ class DivModIntegerNode : public Node { 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/graphKit.hpp b/src/hotspot/share/opto/graphKit.hpp index d371dfb2e32e..ef160ac6f1a6 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/loopopts.cpp b/src/hotspot/share/opto/loopopts.cpp index ccd53129a87c..d525c274ef63 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/macro.cpp b/src/hotspot/share/opto/macro.cpp index 0ee073e8b065..a4d03970fcf8 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/src/hotspot/share/opto/memnode.cpp b/src/hotspot/share/opto/memnode.cpp index 00ccb3e3dbc1..165eb9e430c5 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/src/hotspot/share/opto/movenode.cpp b/src/hotspot/share/opto/movenode.cpp index 6b6becb434f0..7d38238da2f2 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 726a3ea1b555..264216ddc6de 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 b3de7498e50c..e593822c3130 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 @@ class Node { 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 @@ class Node { 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 @@ class Node { 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 @@ class TypeNode : public Node { 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/opaquenode.cpp b/src/hotspot/share/opto/opaquenode.cpp index 428379e84ae1..a3b50d269fbd 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 bb3da2aa65f6..7ec7e23144a3 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/src/hotspot/share/opto/parse.hpp b/src/hotspot/share/opto/parse.hpp index 5118019fc313..426720b5bba7 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 6a400631bffb..2d74866e5708 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/src/hotspot/share/opto/parse2.cpp b/src/hotspot/share/opto/parse2.cpp index 8ac4cf47558e..6e58fae51e1f 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); - - if ((btest == BoolTest::eq && vcon == con) || (btest == BoolTest::ne && vcon != con)) { - assert(val->find_edge(con) > 0, "mismatch"); + int success_idx = diamond->find_edge(success_proj); + assert(success_idx == 1 || success_idx == 2, ""); + assert(val->req() == 3, "not a diamond"); + + // 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); @@ -1835,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 a4d6a6c33d08..c124f940a27b 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 014d16f92f6d..7ea7aa991426 100644 --- a/src/hotspot/share/opto/phaseX.hpp +++ b/src/hotspot/share/opto/phaseX.hpp @@ -501,6 +501,10 @@ class PhaseIterGVN : public PhaseGVN { // 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 @@ class PhaseIterGVN : public PhaseGVN { // 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 60167c5436a1..1e5ef29e79c7 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 76f0ec440a9b..61ad317d455f 100644 --- a/src/hotspot/share/opto/rootnode.hpp +++ b/src/hotspot/share/opto/rootnode.hpp @@ -69,4 +69,35 @@ class HaltNode : public Node { 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 20857eed35c3..60eda1204b73 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/src/hotspot/share/runtime/arguments.cpp b/src/hotspot/share/runtime/arguments.cpp index 2804224ed01f..269a8b39e6b6 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/src/hotspot/share/runtime/continuation.cpp b/src/hotspot/share/runtime/continuation.cpp index f8af2545c371..e80720072c58 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 d76652edf36a..e9b6325d03ba 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 ae04d398043c..2b0dd59debad 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); } } @@ -1685,13 +1672,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 5489735da39b..bcb7f5488f5e 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/src/hotspot/share/runtime/vframe.cpp b/src/hotspot/share/runtime/vframe.cpp index c9628255e454..02386537004a 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 6810d7bb8d39..0885262eefb5 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); diff --git a/src/java.base/share/classes/java/net/HttpCookie.java b/src/java.base/share/classes/java/net/HttpCookie.java index 3c633522bdfd..2b3a5cbb6a56 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 @@ private static void assignMaxAgeAttribute(HttpCookie cookie, } } 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 @@ private String toRFC2965HeaderString() { * @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 @@ private long expiryDate2DeltaSeconds(String dateString) { } 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/src/java.base/share/classes/java/security/KeyStore.java b/src/java.base/share/classes/java/security/KeyStore.java index 434aa57e3ac0..f7adfcbcd626 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.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 @@ private String getProviderName() { *
  • 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. *
  • * * @@ -876,6 +889,7 @@ private String getProviderName() { * * @see Provider */ + @CallerSensitive public static KeyStore getInstance(String type) throws KeyStoreException { @@ -885,6 +899,11 @@ public static KeyStore getInstance(String type) 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 static KeyStore getInstance(String type) * * @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 + *
  • 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
    + *
  • 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 password the keystore password, which may be {@code null} @@ -1785,10 +1856,12 @@ public final void setEntry(String alias, Entry entry, * * @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 static final KeyStore getInstance(File file, char[] password) * * @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 static final KeyStore getInstance(File file, char[] password) * * @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 @@ private static final KeyStore getInstance(File file, char[] password, 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 6e8f64f7ebe6..943459b4bf7b 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.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 @@ private MessageDigest(String algorithm, Provider p) { *
  • 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. *
  • * * @@ -191,6 +204,7 @@ private MessageDigest(String algorithm, Provider p) { * * @see Provider */ + @CallerSensitive public static MessageDigest getInstance(String algorithm) throws NoSuchAlgorithmException { @@ -200,6 +214,11 @@ public static MessageDigest getInstance(String algorithm) 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 static MessageDigest getInstance(String algorithm) * * @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
    + *
  • 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
    0) { + attrName = attrName.substring(pos); + } + attrName = attrName.intern(); ServiceKey stdKey = new ServiceKey(type, stdAlg, true); Service stdService = legacyMap.get(stdKey); diff --git a/src/java.base/share/classes/java/security/Signature.java b/src/java.base/share/classes/java/security/Signature.java index 228d6fff82ba..6606752dde84 100644 --- a/src/java.base/share/classes/java/security/Signature.java +++ b/src/java.base/share/classes/java/security/Signature.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 @@ -38,6 +38,8 @@ import jdk.internal.access.JavaSecuritySignatureAccess; import jdk.internal.access.SharedSecrets; +import jdk.internal.reflect.CallerSensitive; +import jdk.internal.reflect.Reflection; import sun.security.util.Debug; import sun.security.util.CryptoAlgorithmConstraints; @@ -237,8 +239,18 @@ protected Signature(String algorithm) { *
  • 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. *
  • * * @@ -259,6 +271,7 @@ protected Signature(String algorithm) { * * @see Provider */ + @CallerSensitive public static Signature getInstance(String algorithm) throws NoSuchAlgorithmException { Objects.requireNonNull(algorithm, "null algorithm name"); @@ -267,6 +280,11 @@ public static Signature getInstance(String algorithm) 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 @@ private static boolean isSpi(Service s) { * * @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
    + *
  • 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
    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 String toString() { *
  • Deprecated ISO language codes "iw", "ji", and "in" are * converted to "he", "yi", and "id", respectively. * - *
  • A locale with language "no", country "NO", and variant - * "NY", representing Norwegian Nynorsk (Norway), is converted - * to a language tag "nn-NO".
  • + *
  • 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".
  • * *

    Note: Although the language tag obtained by this * method is well-formed (satisfies the syntax requirements @@ -2693,6 +2692,13 @@ public Builder() { *

  • Locale("th", "TH", "TH") is treated as "th-TH-u-nu-thai" *
  • 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 f91db79891b1..2483e184e2ec 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 @@ private static List createCandidateList(BaseLocale base) { 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/javax/crypto/Cipher.java b/src/java.base/share/classes/javax/crypto/Cipher.java index 6ff5a4e00ac5..27a5a0755448 100644 --- a/src/java.base/share/classes/javax/crypto/Cipher.java +++ b/src/java.base/share/classes/javax/crypto/Cipher.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 @@ -42,6 +42,8 @@ import java.nio.ByteBuffer; import java.nio.ReadOnlyBufferException; +import jdk.internal.reflect.CallerSensitive; +import jdk.internal.reflect.Reflection; import sun.security.util.Debug; import sun.security.jca.*; import sun.security.util.KnownOIDs; @@ -515,8 +517,18 @@ private static Transform getTransform(Service s, *

  • 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. *
  • * * @@ -541,6 +553,7 @@ private static Transform getTransform(Service s, * * @see java.security.Provider */ + @CallerSensitive public static final Cipher getInstance(String transformation) throws NoSuchAlgorithmException, NoSuchPaddingException { @@ -554,6 +567,11 @@ public static final Cipher getInstance(String transformation) " 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 static final Cipher getInstance(String transformation) * * @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 static final Cipher getInstance(String transformation) * * @see java.security.Provider */ + @CallerSensitive public static final Cipher getInstance(String transformation, String provider) throws NoSuchAlgorithmException, NoSuchProviderException, @@ -676,7 +708,7 @@ public static final Cipher getInstance(String transformation, throw new NoSuchProviderException("No such provider: " + provider); } - return getInstance(transformation, p); + return getInstance(transformation, p, Reflection.getCallerClass()); } private String getProviderName() { @@ -705,11 +737,24 @@ private String getProviderName() { * * @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 @@ private String getProviderName() { * * @see java.security.Provider */ + @CallerSensitive public static final Cipher getInstance(String transformation, Provider provider) throws NoSuchAlgorithmException, NoSuchPaddingException @@ -750,12 +796,27 @@ public static final Cipher getInstance(String transformation, 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/reflect/annotation/AnnotationParser.java b/src/java.base/share/classes/sun/reflect/annotation/AnnotationParser.java index b40ed946648a..ba804757e459 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 static Map, Annotation> parseAnnotations( * 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, 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 ad3beab350fa..781c1ab2cd2a 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 @@ /** * 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 @@ private static void debug(String msg) { } 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 static boolean permits(String service, String algo) { * {@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 final boolean permits(Set primitives, 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 @@ private boolean cachedCheckAlgorithm(String serviceDesc) { 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/classes/sun/util/cldr/CLDRLocaleProviderAdapter.java b/src/java.base/share/classes/sun/util/cldr/CLDRLocaleProviderAdapter.java index 573187ba3d0c..1e80bce38399 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 boolean isSupportedProviderLocale(Locale locale, Set langtags) { || 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/InternalLocaleBuilder.java b/src/java.base/share/classes/sun/util/locale/InternalLocaleBuilder.java index 5da725d59c8c..499cb757125e 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 InternalLocaleBuilder setLocale(BaseLocale base, LocaleExtensions localeE 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 5ce62a275cc8..485fb7f5ca6f 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 static LanguageTag parseLocale(BaseLocale baseLocale, LocaleExtensions lo } // 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/JRELocaleProviderAdapter.java b/src/java.base/share/classes/sun/util/locale/provider/JRELocaleProviderAdapter.java index 7b8b3b06eb37..2d6d95b509f9 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 boolean isSupportedProviderLocale(Locale locale, Set langtags) { "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/LocaleServiceProviderPool.java b/src/java.base/share/classes/sun/util/locale/provider/LocaleServiceProviderPool.java index cc9a805fe0de..f3d0990429d5 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 @@ static Locale getLookupLocale(Locale locale) { 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/src/java.base/share/classes/sun/util/locale/provider/ResourceBundleBasedAdapter.java b/src/java.base/share/classes/sun/util/locale/provider/ResourceBundleBasedAdapter.java index 613b1ee5158b..48d9b832b13f 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 @@ 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 20e8e0f8fe9e..884f9610ca7c 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.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 @@ protected String toOtherBundleName(String baseName, String bundleName, Locale lo 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 List getCandidateLocales(String baseName, Locale locale) { 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 List getCandidateLocales(String baseName, Locale locale) { } 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 diff --git a/src/java.base/share/conf/security/java.security b/src/java.base/share/conf/security/java.security index 976604b5cbcc..26842d0c8452 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/src/java.desktop/windows/classes/sun/awt/Win32GraphicsEnvironment.java b/src/java.desktop/windows/classes/sun/awt/Win32GraphicsEnvironment.java index 9d09f13e525d..8bb7f04420c5 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 static boolean isDWMCompositionEnabled() { 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 00ad60c8bb3f..b288d5beb07f 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 void setBoundsOperation(int operation) { */ 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 9c1c7665f4b0..3c8e4de23cbc 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 void setOpacity(float opacity) { 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 void setOpaque(boolean isOpaque) { } } - 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 void setOpaque(boolean isOpaque) { } } - 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 189525c39a1a..c294303f4c11 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 c367471afa99..8a09d6af9944 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 d5ad022c1e0e..a00938f764f5 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 ff7e01df3e86..d1a4fc68d030 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 6dc21c5addae..282bd194f2f9 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 5518ab91145a..c1b682ffa46f 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 cc53f4a33224..5aa7731c8a6b 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; -} 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 ddbcce661aa1..268705f6c1fa 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 String toString() { @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/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/Float16Vector.java b/src/jdk.incubator.vector/share/classes/jdk/incubator/vector/Float16Vector.java index ce3a67357f92..a42fb44dd020 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 final ShortVector viewAsIntegralLanes() { * 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 final ShortVector viewAsIntegralLanes() { @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 f11c62836858..00445cc8ac52 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/src/jdk.jcmd/share/man/jinfo.md b/src/jdk.jcmd/share/man/jinfo.md index b70bc4c45eee..8365c5af8a56 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 15849502d8cc..2e95abf36c4d 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:** diff --git a/test/hotspot/gtest/gc/shenandoah/test_shenandoahAllocationRate.cpp b/test/hotspot/gtest/gc/shenandoah/test_shenandoahAllocationRate.cpp index af0666573778..a4169ff6ba6c 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 000000000000..db4933a92643 --- /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); +} diff --git a/test/hotspot/gtest/runtime/test_committed_virtualmemory.cpp b/test/hotspot/gtest/runtime/test_committed_virtualmemory.cpp index 5d475d2f955e..fcc1354c773f 100644 --- a/test/hotspot/gtest/runtime/test_committed_virtualmemory.cpp +++ b/test/hotspot/gtest/runtime/test_committed_virtualmemory.cpp @@ -58,16 +58,15 @@ class CommittedVirtualMemoryTest { 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 @@ class CommittedVirtualMemoryTest { }); } - // 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; 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 000000000000..2e677508a8da --- /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 + diff --git a/test/hotspot/jtreg/ProblemList.txt b/test/hotspot/jtreg/ProblemList.txt index a9f70fc97a49..0a98477be69f 100644 --- a/test/hotspot/jtreg/ProblemList.txt +++ b/test/hotspot/jtreg/ProblemList.txt @@ -64,12 +64,9 @@ 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 -compiler/unsafe/AlignmentGapAccess.java 8373487 generic-all - compiler/escapeAnalysis/TestBCEscapeAnalyzerOverflow.java 8387392 windows-aarch64 ############################################################################# @@ -105,7 +102,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/hotspot/jtreg/compiler/arguments/TestStressOptions.java b/test/hotspot/jtreg/compiler/arguments/TestStressOptions.java index 534ec9d2d97f..99cf06110d67 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/c2/TestDeadPathManyDeadDataNodes.java b/test/hotspot/jtreg/compiler/c2/TestDeadPathManyDeadDataNodes.java new file mode 100644 index 000000000000..e9c5a8f75290 --- /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/cpuflags/TestUseBMI2Instructions.java b/test/hotspot/jtreg/compiler/cpuflags/TestUseBMI2Instructions.java new file mode 100644 index 000000000000..df595a5ff267 --- /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 + } +} diff --git a/test/hotspot/jtreg/compiler/escapeAnalysis/StressEliminateAllocationsIRTest.java b/test/hotspot/jtreg/compiler/escapeAnalysis/StressEliminateAllocationsIRTest.java new file mode 100644 index 000000000000..b97847b9b28a --- /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() {} +} diff --git a/test/hotspot/jtreg/compiler/inlining/TestLateInliningWithSliceNarrowing.java b/test/hotspot/jtreg/compiler/inlining/TestLateInliningWithSliceNarrowing.java new file mode 100644 index 000000000000..fbae0454d078 --- /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); + } + } + } +} diff --git a/test/hotspot/jtreg/compiler/integerArithmetic/TestDivByZeroInLiveCFGPath.java b/test/hotspot/jtreg/compiler/integerArithmetic/TestDivByZeroInLiveCFGPath.java new file mode 100644 index 000000000000..6eaf3d86e71f --- /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 000000000000..a5bc8fc9287c --- /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 { + 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() {} +} diff --git a/test/hotspot/jtreg/compiler/parsing/TestTypeUnsafeFieldStore.java b/test/hotspot/jtreg/compiler/parsing/TestTypeUnsafeFieldStore.java new file mode 100644 index 000000000000..d6bd097eec00 --- /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() {} +} diff --git a/test/hotspot/jtreg/compiler/rangechecks/TestArrayAccessAboveRCAfterRCCastIIEliminated.java b/test/hotspot/jtreg/compiler/rangechecks/TestArrayAccessAboveRCAfterRCCastIIEliminated.java index b31c14257289..94b1f3b8eee3 100644 --- a/test/hotspot/jtreg/compiler/rangechecks/TestArrayAccessAboveRCAfterRCCastIIEliminated.java +++ b/test/hotspot/jtreg/compiler/rangechecks/TestArrayAccessAboveRCAfterRCCastIIEliminated.java @@ -27,17 +27,19 @@ * @summary [REDO] C2: crash in compiled code because of dependency on removed range check CastIIs * * @run main/othervm -XX:-TieredCompilation -XX:-UseOnStackReplacement -XX:-BackgroundCompilation - * -XX:CompileCommand=dontinline,TestArrayAccessAboveRCAfterRCCastIIEliminated::notInlined - * TestArrayAccessAboveRCAfterRCCastIIEliminated + * -XX:CompileCommand=dontinline,${test.main.class}::notInlined + * ${test.main.class} * @run main/othervm -XX:-TieredCompilation -XX:-UseOnStackReplacement -XX:-BackgroundCompilation - * -XX:CompileCommand=dontinline,TestArrayAccessAboveRCAfterRCCastIIEliminated::notInlined - * -XX:+UnlockDiagnosticVMOptions -XX:+StressGCM TestArrayAccessAboveRCAfterRCCastIIEliminated - * @run main TestArrayAccessAboveRCAfterRCCastIIEliminated - * @run main/othervm -XX:CompileCommand=dontinline,TestArrayAccessAboveRCAfterRCCastIIEliminated::notInlined - * TestArrayAccessAboveRCAfterRCCastIIEliminated + * -XX:CompileCommand=dontinline,${test.main.class}::notInlined + * -XX:+UnlockDiagnosticVMOptions -XX:+StressGCM ${test.main.class} + * @run main ${test.main.class} + * @run main/othervm -XX:CompileCommand=dontinline,${test.main.class}::notInlined + * ${test.main.class} * */ +package compiler.rangechecks; + public class TestArrayAccessAboveRCAfterRCCastIIEliminated { private static int intField; private static long longField; @@ -482,7 +484,7 @@ private static void test13(int i, int j, int flag, boolean flag2) { } } - // Range check cast type widen after loop opts causes control dependency to be lost + // Widened range check cast type after loop opts causes control dependency to be lost private static void test14(int i, int j, int flag, boolean flag2) { int l = 0; for (; l < 10; l++); @@ -490,12 +492,14 @@ private static void test14(int i, int j, int flag, boolean flag2) { int[] array = new int[10]; notInlined(array); if (flag == 0) { + throw new RuntimeException("never taken"); } if (flag2) { float[] newArray = new float[10]; newArray[i+j] = 42; // i+j in [0, 9] float[] otherArray = new float[i+j]; // i+j in [0, max] if (flag == 0) { + throw new RuntimeException("never taken"); } intField = array[otherArray.length]; } else { @@ -503,6 +507,7 @@ private static void test14(int i, int j, int flag, boolean flag2) { newArray[i+j] = 42; // i+j in [0, 9] float[] otherArray = new float[i+j]; // i+j in [0, max] if (flag == 0) { + throw new RuntimeException("never taken"); } intField = array[otherArray.length]; } @@ -523,12 +528,14 @@ private static void test15(int i, int j, int flag, boolean flag2) { int[] array = new int[10]; notInlined(array); if (flag == 0) { + throw new RuntimeException("never taken"); } if (flag2) { float[] newArray = new float[10]; newArray[i+j] = 42; // i+j in [0, 9] float[] otherArray = new float[i+j]; // i+j in [0, max] if (flag == 0) { + throw new RuntimeException("never taken"); } intField = array[otherArray.length]; } else { @@ -536,6 +543,7 @@ private static void test15(int i, int j, int flag, boolean flag2) { newArray[i+j] = 42; // i+j in [0, 9] float[] otherArray = new float[i+j]; // i+j in [0, max] if (flag == 0) { + throw new RuntimeException("never taken"); } intField = array[otherArray.length]; } diff --git a/test/hotspot/jtreg/compiler/types/TestSubTypeCheckConstantCastII.java b/test/hotspot/jtreg/compiler/types/TestSubTypeCheckConstantCastII.java new file mode 100644 index 000000000000..d3ce317ed30d --- /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()); + } +} diff --git a/test/hotspot/jtreg/compiler/unsafe/AlignmentGapAccess.java b/test/hotspot/jtreg/compiler/unsafe/AlignmentGapAccess.java index ac3c4b0278ae..8b2ee0671408 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"); } diff --git a/test/hotspot/jtreg/compiler/vectorapi/TestSelectFromTwoVectorSameOperand.java b/test/hotspot/jtreg/compiler/vectorapi/TestSelectFromTwoVectorSameOperand.java new file mode 100644 index 000000000000..7c30e4395153 --- /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]); + } + } +} diff --git a/test/hotspot/jtreg/gc/TestCodeCacheUnload.java b/test/hotspot/jtreg/gc/TestCodeCacheUnload.java new file mode 100644 index 000000000000..03c857dba1e6 --- /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 000000000000..0c2d473b021e --- /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"); + } + } +} diff --git a/test/hotspot/jtreg/gc/shenandoah/generational/TestOldGrowthTriggers.java b/test/hotspot/jtreg/gc/shenandoah/generational/TestOldGrowthTriggers.java index 2af784fd034b..fe3c8a5a4763 100644 --- a/test/hotspot/jtreg/gc/shenandoah/generational/TestOldGrowthTriggers.java +++ b/test/hotspot/jtreg/gc/shenandoah/generational/TestOldGrowthTriggers.java @@ -41,9 +41,11 @@ 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 static void makeOldAllocations() { 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 static void makeOldAllocations() { } 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 static void main(String[] args) throws Exception { "-XX:ShenandoahMinOldGenGrowthRemainingHeapPercent=100", "-XX:ShenandoahGuaranteedYoungGCInterval=0", "-XX:ShenandoahGuaranteedOldGCInterval=0", + "-XX:ShenandoahGenerationalMinTenuringAge=2", + "-XX:ShenandoahGenerationalMaxTenuringAge=2", "-XX:-UseCompactObjectHeaders" ); @@ -127,6 +133,8 @@ public static void main(String[] args) throws Exception { "-XX:ShenandoahMinOldGenGrowthRemainingHeapPercent=100", "-XX:ShenandoahGuaranteedYoungGCInterval=0", "-XX:ShenandoahGuaranteedOldGCInterval=0", + "-XX:ShenandoahGenerationalMinTenuringAge=2", + "-XX:ShenandoahGenerationalMaxTenuringAge=2", "-XX:+UseCompactObjectHeaders" ); } diff --git a/test/hotspot/jtreg/runtime/CommandLine/VMDeprecatedOptions.java b/test/hotspot/jtreg/runtime/CommandLine/VMDeprecatedOptions.java index 99c2d27f8d3e..8c5309360656 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"} diff --git a/test/jdk/ProblemList.txt b/test/jdk/ProblemList.txt index fcde1d9c01d9..8879aa2e5b6d 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/awt/Frame/PackTwiceTest.java b/test/jdk/java/awt/Frame/PackTwiceTest.java index 63cd20612f0d..ee948665d15f 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 @@ 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 diff --git a/test/jdk/java/net/CookieHandler/B6791927.java b/test/jdk/java/net/CookieHandler/B6791927.java index bc5374b2a985..ceeff260665c 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 5cf7208d2ca7..e2769d8dd61c 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.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 static void main(String[] args) throws Exception { 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 6704a2908364..79139d69e503 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.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 void test2() { 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"); + } } diff --git a/test/jdk/java/net/httpclient/CancelRequestTest.java b/test/jdk/java/net/httpclient/CancelRequestTest.java index f21d13d5e987..418127c77352 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 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 void testGetSendAsync(String uri, boolean sameClient, boolean mayInterrup 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 void testGetSendAsync(String uri, boolean sameClient, boolean mayInterrup 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 Iterator iterator() { 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 Iterator iterator() { 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 2c33f6f00188..ccac8fcf4398 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.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 void test(CompletableFuture mf) { } } + @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()); } diff --git a/test/jdk/java/security/KeyStore/TestLegacyAlgorithms.java b/test/jdk/java/security/KeyStore/TestLegacyAlgorithms.java new file mode 100644 index 000000000000..516191cfa016 --- /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 000000000000..61275b03f5db --- /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/Provider/SupportsParameter.java b/test/jdk/java/security/Provider/SupportsParameter.java index 039fb3d07975..3325ad9680a8 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 @@ private static class MyProvider extends Provider { 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"); diff --git a/test/jdk/java/security/Signature/TestLegacyAlgorithms.java b/test/jdk/java/security/Signature/TestLegacyAlgorithms.java new file mode 100644 index 000000000000..bf745fba3e1e --- /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/java/util/Locale/LocaleEnhanceTest.java b/test/jdk/java/util/Locale/LocaleEnhanceTest.java index 3fe3745034d6..a4c056d3a77b 100644 --- a/test/jdk/java/util/Locale/LocaleEnhanceTest.java +++ b/test/jdk/java/util/Locale/LocaleEnhanceTest.java @@ -58,7 +58,7 @@ * @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 void testToLanguageTag() { // 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 void testBuilderSetLocale() { 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 b8b49406c053..a70ebf9e527a 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 @@ private static void testGetCandidateLocales() { 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 @@ private static void testGetCandidateLocales() { 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); diff --git a/test/jdk/java/util/concurrent/tck/JSR166TestCase.java b/test/jdk/java/util/concurrent/tck/JSR166TestCase.java index 641fbf2e4954..f1f32bee3109 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.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 static Test suite() { "SynchronousQueue20Test", "ReentrantReadWriteLock20Test" }; - - if (Platform.isS390x()) { - java20TestClassNames = new String[] { - "ForkJoinPool20Test", - }; - } - addNamedTestClasses(suite, java20TestClassNames); } diff --git a/test/jdk/javax/crypto/Cipher/TestLegacyAlgorithms.java b/test/jdk/javax/crypto/Cipher/TestLegacyAlgorithms.java new file mode 100644 index 000000000000..f4336646cbdd --- /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()); + } + } +} diff --git a/test/jdk/jdk/incubator/vector/Float16Vector128Tests.java b/test/jdk/jdk/incubator/vector/Float16Vector128Tests.java index a33e83d14eac..215f6ef9c476 100644 --- a/test/jdk/jdk/incubator/vector/Float16Vector128Tests.java +++ b/test/jdk/jdk/incubator/vector/Float16Vector128Tests.java @@ -5412,7 +5412,8 @@ static void toStringFloat16Vector128TestsSmokeTest(IntFunction fa) { 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 99b167d40242..f198a4a970f5 100644 --- a/test/jdk/jdk/incubator/vector/Float16Vector256Tests.java +++ b/test/jdk/jdk/incubator/vector/Float16Vector256Tests.java @@ -5412,7 +5412,8 @@ static void toStringFloat16Vector256TestsSmokeTest(IntFunction fa) { 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 1c3914970153..3d2b7de23f9a 100644 --- a/test/jdk/jdk/incubator/vector/Float16Vector512Tests.java +++ b/test/jdk/jdk/incubator/vector/Float16Vector512Tests.java @@ -5412,7 +5412,8 @@ static void toStringFloat16Vector512TestsSmokeTest(IntFunction fa) { 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 6ef651860ad0..c44f0f7c5760 100644 --- a/test/jdk/jdk/incubator/vector/Float16Vector64Tests.java +++ b/test/jdk/jdk/incubator/vector/Float16Vector64Tests.java @@ -5412,7 +5412,8 @@ static void toStringFloat16Vector64TestsSmokeTest(IntFunction fa) { 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 61efa3de9a0a..92945d74ceba 100644 --- a/test/jdk/jdk/incubator/vector/Float16VectorMaxTests.java +++ b/test/jdk/jdk/incubator/vector/Float16VectorMaxTests.java @@ -5418,7 +5418,8 @@ static void toStringFloat16VectorMaxTestsSmokeTest(IntFunction fa) { 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 0ae9342539fc..b673e5e1fabd 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); } } diff --git a/test/jdk/sun/security/ssl/CertificateCompression/CompressedCertMsgCache.java b/test/jdk/sun/security/ssl/CertificateCompression/CompressedCertMsgCache.java index 42e9701c6d0d..be0f0154e323 100644 --- a/test/jdk/sun/security/ssl/CertificateCompression/CompressedCertMsgCache.java +++ b/test/jdk/sun/security/ssl/CertificateCompression/CompressedCertMsgCache.java @@ -52,7 +52,7 @@ * 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 { diff --git a/test/jdk/sun/security/ssl/SSLSessionImpl/ResumeChecksServer.java b/test/jdk/sun/security/ssl/SSLSessionImpl/ResumeChecksServer.java index d1918aab7f1d..87c032728dca 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 @@ private void test() throws Exception { 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 @@ private void test() throws Exception { 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"); diff --git a/test/jdk/sun/tools/jstat/jstatGcCapacityOutput1.sh b/test/jdk/sun/tools/jstat/jstatGcCapacityOutput1.sh index c1908855ea76..6e184e9dc310 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 b16d0e38d02c..b5caccb17681 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 64ce2efd455e..96f0722a9e13 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 b15ec02d2b0a..96e2db61488e 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 1c13d6f916d3..0c5e2e198945 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 7f505228b12a..0f857ccb1e68 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 dfffa2d1a550..5862fda3fd78 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 97338b8e793d..ca6adce96a5c 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 eab19f3931e8..a668df72e0ec 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 9a769a924648..bffffc8a38e3 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 817c3b14f621..9ad1f57a5d50 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 db71314fcf95..4e4cb8df4266 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 diff --git a/test/jdk/tools/jimage/JImageExtractTest.java b/test/jdk/tools/jimage/JImageExtractTest.java index eec10439cf55..e5f62232097a 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 void testExtractToReadOnlyDir() throws IOException { 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();