diff --git a/make/modules/jdk.crypto.cryptoki/Lib.gmk b/make/modules/jdk.crypto.cryptoki/Lib.gmk index 29d1422cd78..2d7904e6016 100644 --- a/make/modules/jdk.crypto.cryptoki/Lib.gmk +++ b/make/modules/jdk.crypto.cryptoki/Lib.gmk @@ -1,5 +1,5 @@ # -# Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2011, 2026, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -35,8 +35,6 @@ $(eval $(call SetupJdkLibrary, BUILD_LIBJ2PKCS11, \ NAME := j2pkcs11, \ OPTIMIZATION := LOW, \ EXTRA_HEADER_DIRS := java.base:libjava, \ - DISABLED_WARNINGS_gcc_p11_md.c := unused-variable, \ - DISABLED_WARNINGS_clang_p11_md.c := unused-variable, \ DISABLED_WARNINGS_clang_p11_util.c := format-nonliteral, \ LIBS_unix := $(LIBDL), \ )) diff --git a/src/hotspot/cpu/aarch64/aarch64.ad b/src/hotspot/cpu/aarch64/aarch64.ad index 841b4dff449..b31f7a0b2e8 100644 --- a/src/hotspot/cpu/aarch64/aarch64.ad +++ b/src/hotspot/cpu/aarch64/aarch64.ad @@ -14212,7 +14212,7 @@ instruct clearArray_reg_reg_immL0(iRegL_R11 cnt, iRegP_R10 base, immL0 zero, Uni instruct clearArray_reg_reg(iRegL_R11 cnt, iRegP_R10 base, iRegL val, Universe dummy, rFlagsReg cr) %{ - predicate(((ClearArrayNode*)n)->word_copy_only()); + predicate(((ClearArrayNode*)n)->requires_word_fill()); match(Set dummy (ClearArray (Binary cnt base) val)); effect(USE_KILL cnt, USE_KILL base, KILL cr); @@ -14230,7 +14230,7 @@ instruct clearArray_imm_reg(immL cnt, iRegP_R10 base, iRegL_R11 temp, immL0 zero %{ predicate((uint64_t)n->in(2)->in(1)->get_long() < (uint64_t)(BlockZeroingLowLimit >> LogBytesPerWord) - && !((ClearArrayNode*)n)->word_copy_only()); + && !((ClearArrayNode*)n)->requires_word_fill()); match(Set dummy (ClearArray (Binary cnt base) zero)); effect(TEMP temp, USE_KILL base, KILL cr); diff --git a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp index c95762dc4e6..6d17239c073 100644 --- a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp @@ -7911,8 +7911,10 @@ void MacroAssembler::fast_lock(Register basic_lock, Register obj, Register t1, R // Try to lock. Transition lock bits 0b01 => 0b00 assert(oopDesc::mark_offset_in_bytes() == 0, "required to avoid lea"); orr(mark, mark, markWord::unlocked_value); - // Mask inline_type bit such that we go to the slow path if object is an inline type - andr(mark, mark, ~((int) markWord::inline_type_bit_in_place)); + if (Arguments::is_valhalla_enabled()) { + // Mask inline_type bit such that we go to the slow path if object is an inline type + andr(mark, mark, ~((int) markWord::inline_type_bit_in_place)); + } eor(t, mark, markWord::unlocked_value); cmpxchg(/*addr*/ obj, /*expected*/ mark, /*new*/ t, Assembler::xword, memory_order_acquire); diff --git a/src/hotspot/cpu/aarch64/sharedRuntime_aarch64.cpp b/src/hotspot/cpu/aarch64/sharedRuntime_aarch64.cpp index fce08ff982d..cb14d3eada1 100644 --- a/src/hotspot/cpu/aarch64/sharedRuntime_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/sharedRuntime_aarch64.cpp @@ -2922,11 +2922,10 @@ RuntimeStub* SharedRuntime::generate_resolve_blob(StubId id, address destination } BufferedInlineTypeBlob* SharedRuntime::generate_buffered_inline_type_adapter(const InlineKlass* vk) { - BufferBlob* buf = BufferBlob::create("inline types pack/unpack", 16 * K); - if (buf == nullptr) { + CodeBuffer buffer("inline types pack/unpack", 16 * K, 0); + if (buffer.blob() == nullptr) { return nullptr; } - CodeBuffer buffer(buf); short buffer_locs[20]; buffer.insts()->initialize_shared_locs((relocInfo*)buffer_locs, sizeof(buffer_locs)/sizeof(relocInfo)); diff --git a/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp b/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp index c63f456e521..3341acf057a 100644 --- a/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/stubGenerator_aarch64.cpp @@ -2623,11 +2623,13 @@ class StubGenerator: public StubCodeGenerator { __ eor(rscratch2, rscratch2, scratch_src_klass); __ cbnz(rscratch2, L_failed); - // Check for flat inline type array -> return -1 - __ test_flat_array_oop(src, rscratch2, L_failed); + if (Arguments::is_valhalla_enabled()) { + // Check for flat inline type array -> return -1 + __ test_flat_array_oop(src, rscratch2, L_failed); - // Check for null-free (non-flat) inline type array -> handle as object array - __ test_null_free_array_oop(src, rscratch2, L_objArray); + // Check for null-free (non-flat) inline type array -> handle as object array + __ test_null_free_array_oop(src, rscratch2, L_objArray); + } // if (!src->is_Array()) return -1; __ tbz(lh, 31, L_failed); // i.e. (lh >= 0) diff --git a/src/hotspot/cpu/ppc/ppc.ad b/src/hotspot/cpu/ppc/ppc.ad index b54bbf97ba6..8d9bf3ab0b2 100644 --- a/src/hotspot/cpu/ppc/ppc.ad +++ b/src/hotspot/cpu/ppc/ppc.ad @@ -11096,8 +11096,8 @@ instruct inlineCallClearArray(rarg1RegL cnt, rarg2RegP base, immL_0 zero, Univer %} // Clear-array with dynamic array length and non-zero value. -instruct inlineCallClearArrayWordCopy(rarg1RegL cnt, rarg2RegP base, iRegLdst val, Universe dummy, regCTR ctr) %{ - predicate(((ClearArrayNode*)n)->word_copy_only()); +instruct inlineCallClearArrayWordFill(rarg1RegL cnt, rarg2RegP base, iRegLdst val, Universe dummy, regCTR ctr) %{ + predicate(((ClearArrayNode*)n)->requires_word_fill()); match(Set dummy (ClearArray (Binary cnt base) val)); effect(USE_KILL base, KILL ctr); ins_cost(8 * MEMORY_REF_COST); diff --git a/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp b/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp index 116332c1add..61b9633a4ef 100644 --- a/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp +++ b/src/hotspot/cpu/riscv/macroAssembler_riscv.cpp @@ -2174,12 +2174,12 @@ void MacroAssembler::vector_update_crc32(Register crc, Register buf, Register le mv(tmp5, 0xff); if (MaxVectorSize == 16) { - vsetivli(zr, N, Assembler::e32, Assembler::m4, Assembler::ma, Assembler::ta); + vsetivli(zr, N, Assembler::e32, Assembler::m4, Assembler::mu, Assembler::tu); } else if (MaxVectorSize == 32) { - vsetivli(zr, N, Assembler::e32, Assembler::m2, Assembler::ma, Assembler::ta); + vsetivli(zr, N, Assembler::e32, Assembler::m2, Assembler::mu, Assembler::tu); } else { assert(MaxVectorSize > 32, "sanity"); - vsetivli(zr, N, Assembler::e32, Assembler::m1, Assembler::ma, Assembler::ta); + vsetivli(zr, N, Assembler::e32, Assembler::m1, Assembler::mu, Assembler::tu); } vmv_v_x(vcrc, zr); @@ -7064,8 +7064,11 @@ void MacroAssembler::fast_lock(Register basic_lock, Register obj, Register tmp1, // Try to lock. Transition lock-bits 0b01 => 0b00 assert(oopDesc::mark_offset_in_bytes() == 0, "required to avoid a la"); ori(mark, mark, markWord::unlocked_value); - // Mask inline_type bit such that we go to the slow path if object is an inline type - andi(mark, mark, ~((int) markWord::inline_type_bit_in_place)); + if (Arguments::is_valhalla_enabled()) { + // Mask inline_type bit such that we go to the slow path if object is an inline type + andi(mark, mark, ~((int) markWord::inline_type_bit_in_place)); + } + xori(t, mark, markWord::unlocked_value); cmpxchg(/*addr*/ obj, /*expected*/ mark, /*new*/ t, Assembler::int64, /*acquire*/ Assembler::aq, /*release*/ Assembler::relaxed, /*result*/ t); diff --git a/src/hotspot/cpu/riscv/riscv.ad b/src/hotspot/cpu/riscv/riscv.ad index 4743c027676..3bb4094a816 100644 --- a/src/hotspot/cpu/riscv/riscv.ad +++ b/src/hotspot/cpu/riscv/riscv.ad @@ -11376,7 +11376,7 @@ instruct clearArray_reg_reg(iRegL_R29 cnt, iRegP_R28 base, iRegL val, Universe dummy) %{ // temp registers must match the one used in StubGenerator::generate_zero_blocks() - predicate(((ClearArrayNode*)n)->word_copy_only()); + predicate(((ClearArrayNode*)n)->requires_word_fill()); match(Set dummy (ClearArray (Binary cnt base) val)); effect(USE_KILL cnt, USE_KILL base, TEMP tmp1, TEMP tmp2, KILL cr); @@ -11395,7 +11395,7 @@ instruct clearArray_imm_reg(immL cnt, iRegP_R28 base, immL0 zero, Universe dummy predicate(!UseRVV && (uint64_t)n->in(2)->in(1)->get_long() < (uint64_t)(BlockZeroingLowLimit >> LogBytesPerWord) - && !((ClearArrayNode*)n)->word_copy_only()); + && !((ClearArrayNode*)n)->requires_word_fill()); match(Set dummy (ClearArray (Binary cnt base) zero)); effect(USE_KILL base, KILL cr); diff --git a/src/hotspot/cpu/riscv/stubGenerator_riscv.cpp b/src/hotspot/cpu/riscv/stubGenerator_riscv.cpp index 3f2fcb1055c..7f43a1dba69 100644 --- a/src/hotspot/cpu/riscv/stubGenerator_riscv.cpp +++ b/src/hotspot/cpu/riscv/stubGenerator_riscv.cpp @@ -1925,15 +1925,16 @@ class StubGenerator: public StubCodeGenerator { __ load_klass(t1, dst); __ bne(t1, scratch_src_klass, L_failed); - // Check for flat inline type array -> return -1 - __ test_flat_array_oop(src, t1, L_failed); + if (Arguments::is_valhalla_enabled()) { + // Check for flat inline type array -> return -1 + __ test_flat_array_oop(src, t1, L_failed); - // Check for null-free (non-flat) inline type array -> handle as object array - __ test_null_free_array_oop(src, t1, L_objArray); + // Check for null-free (non-flat) inline type array -> handle as object array + __ test_null_free_array_oop(src, t1, L_objArray); + } - // if src->is_Array() isn't null then return -1 - // i.e. (lh >= 0) - __ bgez(lh, L_failed); + // if (!src->is_Array()) return -1; + __ bgez(lh, L_failed); // i.e. (lh >= 0) // At this point, it is known to be a typeArray (array_tag 0x3). #ifdef ASSERT diff --git a/src/hotspot/cpu/s390/continuationEntry_s390.inline.hpp b/src/hotspot/cpu/s390/continuationEntry_s390.inline.hpp index 58ff8f0d194..6eb46ab2eba 100644 --- a/src/hotspot/cpu/s390/continuationEntry_s390.inline.hpp +++ b/src/hotspot/cpu/s390/continuationEntry_s390.inline.hpp @@ -26,6 +26,7 @@ #ifndef CPU_S390_CONTINUATIONENTRY_S390_INLINE_HPP #define CPU_S390_CONTINUATIONENTRY_S390_INLINE_HPP +#include "code/codeCache.hpp" #include "oops/method.inline.hpp" #include "runtime/frame.inline.hpp" #include "runtime/registerMap.hpp" diff --git a/src/hotspot/cpu/x86/macroAssembler_x86.cpp b/src/hotspot/cpu/x86/macroAssembler_x86.cpp index aaea4173c0e..07dc498c48b 100644 --- a/src/hotspot/cpu/x86/macroAssembler_x86.cpp +++ b/src/hotspot/cpu/x86/macroAssembler_x86.cpp @@ -6506,11 +6506,12 @@ void MacroAssembler::remove_frame(int initial_framesize, bool needs_stack_repair #ifdef COMPILER2 -// clear memory of size 'cnt' qwords, starting at 'base' using XMM/YMM/ZMM registers -void MacroAssembler::xmm_clear_mem(Register base, Register cnt, Register val, XMMRegister xtmp, KRegister mask) { +// Fill memory with 'val', for 'cnt' qwords starting at 'base', using XMM/YMM/ZMM registers. +void MacroAssembler::xmm_fill_mem(Register base, Register cnt, Register val, XMMRegister xtmp, KRegister mask) { // 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; + // val - qword pattern to fill. + Label L_fill_64_bytes, L_loop, L_sloop, L_tail, L_end; bool use64byteVector = (MaxVectorSize == 64) && (CopyAVX3Threshold == 0) && VM_Version::supports_bmi2(); if (use64byteVector) { evpbroadcastq(xtmp, val, AVX_512bit); @@ -6522,7 +6523,7 @@ void MacroAssembler::xmm_clear_mem(Register base, Register cnt, Register val, XM movdq(xtmp, val); punpcklqdq(xtmp, xtmp); } - jmp(L_zero_64_bytes); + jmp(L_fill_64_bytes); BIND(L_loop); if (MaxVectorSize >= 32) { @@ -6535,11 +6536,11 @@ void MacroAssembler::xmm_clear_mem(Register base, Register cnt, Register val, XM } addptr(base, 64); - BIND(L_zero_64_bytes); + BIND(L_fill_64_bytes); subptr(cnt, 8); jccb(Assembler::greaterEqual, L_loop); - // Copy trailing 64 bytes + // Fill trailing 64 bytes. if (use64byteVector) { addptr(cnt, 8); jccb(Assembler::equal, L_end); @@ -6665,10 +6666,12 @@ void MacroAssembler::clear_mem(Register base, int cnt, Register rtmp, XMMRegiste } void MacroAssembler::clear_mem(Register base, Register cnt, Register val, XMMRegister xtmp, - bool is_large, bool word_copy_only, KRegister mask) { + bool is_large, bool requires_word_fill, KRegister mask) { // cnt - number of qwords (8-byte words). // base - start address, qword aligned. // is_large - if optimizers know cnt is larger than InitArrayShortSize + // requires_word_fill - if true, val contains the qword pattern to fill; if + // false, val is scratch and this method creates zero assert(base==rdi, "base register must be edi for rep stos"); assert(val==rax, "val register must be eax for rep stos"); assert(cnt==rcx, "cnt register must be ecx for rep stos"); @@ -6677,6 +6680,10 @@ void MacroAssembler::clear_mem(Register base, Register cnt, Register val, XMMReg Label DONE; + if (!requires_word_fill) { + xorptr(val, val); + } + if (!is_large) { Label LOOP, LONG; cmpptr(cnt, InitArrayShortSize/BytesPerLong); @@ -6695,12 +6702,13 @@ void MacroAssembler::clear_mem(Register base, Register cnt, Register val, XMMReg BIND(LONG); } - // Use longer rep-prefixed ops for non-small counts: - if (UseFastStosb && !word_copy_only) { + // Use longer rep-prefixed ops for non-small counts. rep stosb is valid only + // for zeroing; an arbitrary qword pattern must be copied in full. + if (UseFastStosb && !requires_word_fill) { shlptr(cnt, 3); // convert to number of bytes rep_stosb(); } else if (UseXMMForObjInit) { - xmm_clear_mem(base, cnt, val, xtmp, mask); + xmm_fill_mem(base, cnt, val, xtmp, mask); } else { rep_stos(); } @@ -10617,8 +10625,10 @@ void MacroAssembler::fast_lock(Register basic_lock, Register obj, Register reg_r movptr(tmp, reg_rax); andptr(tmp, ~(int32_t)markWord::unlocked_value); orptr(reg_rax, markWord::unlocked_value); - // Mask inline_type bit such that we go to the slow path if object is an inline type - andptr(reg_rax, ~((int) markWord::inline_type_bit_in_place)); + if (Arguments::is_valhalla_enabled()) { + // Mask inline_type bit such that we go to the slow path if object is an inline type + andptr(reg_rax, ~((int) markWord::inline_type_bit_in_place)); + } lock(); cmpxchgptr(tmp, Address(obj, oopDesc::mark_offset_in_bytes())); jcc(Assembler::notEqual, slow); diff --git a/src/hotspot/cpu/x86/macroAssembler_x86.hpp b/src/hotspot/cpu/x86/macroAssembler_x86.hpp index 35714d85e47..7f424966468 100644 --- a/src/hotspot/cpu/x86/macroAssembler_x86.hpp +++ b/src/hotspot/cpu/x86/macroAssembler_x86.hpp @@ -1970,15 +1970,16 @@ public: // Inline type specific methods #include "asm/macroAssembler_common.hpp" - // clear memory of size 'cnt' qwords, starting at 'base'; - // if 'is_large' is set, do not try to produce short loop - void clear_mem(Register base, Register cnt, Register val, XMMRegister xtmp, bool is_large, bool word_copy_only, KRegister mask=knoreg); + // Clear or fill 'cnt' qwords starting at 'base'. If 'requires_word_fill' is + // set, use 'val' as the fill value; otherwise, create zero in 'val'. If + // 'is_large' is set, do not try to produce a short loop. + void clear_mem(Register base, Register cnt, Register val, XMMRegister xtmp, bool is_large, bool requires_word_fill, KRegister mask=knoreg); // clear memory initialization sequence for constant size; void clear_mem(Register base, int cnt, Register rtmp, XMMRegister xtmp, KRegister mask=knoreg); - // clear memory of size 'cnt' qwords, starting at 'base' using XMM/YMM registers - void xmm_clear_mem(Register base, Register cnt, Register rtmp, XMMRegister xtmp, KRegister mask=knoreg); + // Fill memory with 'val', for 'cnt' qwords starting at 'base', using XMM/YMM/ZMM registers. + void xmm_fill_mem(Register base, Register cnt, Register val, XMMRegister xtmp, KRegister mask=knoreg); // Fill primitive arrays void generate_fill(BasicType t, bool aligned, diff --git a/src/hotspot/cpu/x86/sharedRuntime_x86_64.cpp b/src/hotspot/cpu/x86/sharedRuntime_x86_64.cpp index d52b03650ca..37df7acf942 100644 --- a/src/hotspot/cpu/x86/sharedRuntime_x86_64.cpp +++ b/src/hotspot/cpu/x86/sharedRuntime_x86_64.cpp @@ -3731,11 +3731,10 @@ void SharedRuntime::montgomery_square(jint *a_ints, jint *n_ints, } BufferedInlineTypeBlob* SharedRuntime::generate_buffered_inline_type_adapter(const InlineKlass* vk) { - BufferBlob* buf = BufferBlob::create("inline types pack/unpack", 16 * K); - if (buf == nullptr) { + CodeBuffer buffer("inline types pack/unpack", 16 * K, 0); + if (buffer.blob() == nullptr) { return nullptr; } - CodeBuffer buffer(buf); short buffer_locs[20]; buffer.insts()->initialize_shared_locs((relocInfo*)buffer_locs, sizeof(buffer_locs)/sizeof(relocInfo)); diff --git a/src/hotspot/cpu/x86/stubGenerator_x86_64_arraycopy.cpp b/src/hotspot/cpu/x86/stubGenerator_x86_64_arraycopy.cpp index 13ce582acaa..c2db4a69b23 100644 --- a/src/hotspot/cpu/x86/stubGenerator_x86_64_arraycopy.cpp +++ b/src/hotspot/cpu/x86/stubGenerator_x86_64_arraycopy.cpp @@ -26,6 +26,7 @@ #include "gc/shared/barrierSet.hpp" #include "gc/shared/barrierSetAssembler.hpp" #include "oops/objArrayKlass.hpp" +#include "runtime/arguments.hpp" #include "runtime/sharedRuntime.hpp" #include "runtime/stubRoutines.hpp" #include "stubGenerator_x86_64.hpp" @@ -3599,11 +3600,13 @@ address StubGenerator::generate_generic_copy(address byte_copy_entry, address sh __ cmpq(r10_src_klass, rax); __ jcc(Assembler::notEqual, L_failed); - // Check for flat inline type array -> return -1 - __ test_flat_array_oop(src, rax, L_failed); + if (Arguments::is_valhalla_enabled()) { + // Check for flat inline type array -> return -1 + __ test_flat_array_oop(src, rax, L_failed); - // Check for null-free (non-flat) inline type array -> handle as object array - __ test_null_free_array_oop(src, rax, L_objArray); + // Check for null-free (non-flat) inline type array -> handle as object array + __ test_null_free_array_oop(src, rax, L_objArray); + } const Register rax_lh = rax; // layout helper __ movl(rax_lh, Address(r10_src_klass, lh_offset)); diff --git a/src/hotspot/cpu/x86/x86.ad b/src/hotspot/cpu/x86/x86.ad index f26ace22e47..b7e0c85030c 100644 --- a/src/hotspot/cpu/x86/x86.ad +++ b/src/hotspot/cpu/x86/x86.ad @@ -14774,16 +14774,18 @@ instruct MoveL2D_reg_reg(regD dst, rRegL src) %{ %} -// Fast clearing of an array -// Small non-constant lenght ClearArray for non-AVX512 targets. -instruct rep_stos(rcx_RegL cnt, rdi_RegP base, regD tmp, rax_RegL val, +// Small zero fill for non-AVX512 targets. +instruct rep_stos(rcx_RegL cnt, rdi_RegP base, regD tmp, immL0 zero, rax_RegL val, Universe dummy, rFlagsReg cr) %{ - predicate(!((ClearArrayNode*)n)->is_large() && !((ClearArrayNode*)n)->word_copy_only() && (UseAVX <= 2)); - match(Set dummy (ClearArray (Binary cnt base) val)); - effect(USE_KILL cnt, USE_KILL base, TEMP tmp, USE_KILL val, KILL cr); + predicate((UseAVX <= 2) && + !((ClearArrayNode*)n)->is_large() && + ((ClearArrayNode*)n)->is_zero_fill()); + match(Set dummy (ClearArray (Binary cnt base) zero)); + effect(USE_KILL cnt, USE_KILL base, TEMP tmp, KILL val, KILL cr); format %{ $$template + $$emit$$"xorq rax, rax\t# ClearArray:\n\t" $$emit$$"cmp InitArrayShortSize,rcx\n\t" $$emit$$"jg LARGE\n\t" $$emit$$"dec rcx\n\t" @@ -14802,24 +14804,24 @@ instruct rep_stos(rcx_RegL cnt, rdi_RegP base, regD tmp, rax_RegL val, $$emit$$"vinserti128_high $tmp, $tmp\n\t" $$emit$$"jmpq L_zero_64_bytes\n\t" $$emit$$"# L_loop:\t# 64-byte LOOP\n\t" - $$emit$$"vmovdqu $tmp,(rax)\n\t" - $$emit$$"vmovdqu $tmp,0x20(rax)\n\t" - $$emit$$"add 0x40,rax\n\t" + $$emit$$"vmovdqu $tmp,($base)\n\t" + $$emit$$"vmovdqu $tmp,0x20($base)\n\t" + $$emit$$"add 0x40,$base\n\t" $$emit$$"# L_zero_64_bytes:\n\t" $$emit$$"sub 0x8,rcx\n\t" $$emit$$"jge L_loop\n\t" $$emit$$"add 0x4,rcx\n\t" $$emit$$"jl L_tail\n\t" - $$emit$$"vmovdqu $tmp,(rax)\n\t" - $$emit$$"add 0x20,rax\n\t" + $$emit$$"vmovdqu $tmp,($base)\n\t" + $$emit$$"add 0x20,$base\n\t" $$emit$$"sub 0x4,rcx\n\t" $$emit$$"# L_tail:\t# Clearing tail bytes\n\t" $$emit$$"add 0x4,rcx\n\t" $$emit$$"jle L_end\n\t" $$emit$$"dec rcx\n\t" $$emit$$"# L_sloop:\t# 8-byte short loop\n\t" - $$emit$$"vmovq xmm0,(rax)\n\t" - $$emit$$"add 0x8,rax\n\t" + $$emit$$"vmovq $tmp,($base)\n\t" + $$emit$$"add 0x8,$base\n\t" $$emit$$"dec rcx\n\t" $$emit$$"jge L_sloop\n\t" $$emit$$"# L_end:\n\t" @@ -14835,10 +14837,13 @@ instruct rep_stos(rcx_RegL cnt, rdi_RegP base, regD tmp, rax_RegL val, ins_pipe(pipe_slow); %} -instruct rep_stos_word_copy(rcx_RegL cnt, rdi_RegP base, regD tmp, rax_RegL val, +// Small word fill for non-AVX512 targets. +instruct rep_stos_word_fill(rcx_RegL cnt, rdi_RegP base, regD tmp, rax_RegL val, Universe dummy, rFlagsReg cr) %{ - predicate(!((ClearArrayNode*)n)->is_large() && ((ClearArrayNode*)n)->word_copy_only() && (UseAVX <= 2)); + predicate((UseAVX <= 2) && + !((ClearArrayNode*)n)->is_large() && + ((ClearArrayNode*)n)->requires_word_fill()); match(Set dummy (ClearArray (Binary cnt base) val)); effect(USE_KILL cnt, USE_KILL base, TEMP tmp, USE_KILL val, KILL cr); @@ -14856,26 +14861,26 @@ instruct rep_stos_word_copy(rcx_RegL cnt, rdi_RegP base, regD tmp, rax_RegL val, $$emit$$"movdq $tmp, $val\n\t" $$emit$$"punpcklqdq $tmp, $tmp\n\t" $$emit$$"vinserti128_high $tmp, $tmp\n\t" - $$emit$$"jmpq L_zero_64_bytes\n\t" + $$emit$$"jmpq L_fill_64_bytes\n\t" $$emit$$"# L_loop:\t# 64-byte LOOP\n\t" - $$emit$$"vmovdqu $tmp,(rax)\n\t" - $$emit$$"vmovdqu $tmp,0x20(rax)\n\t" - $$emit$$"add 0x40,rax\n\t" - $$emit$$"# L_zero_64_bytes:\n\t" + $$emit$$"vmovdqu $tmp,($base)\n\t" + $$emit$$"vmovdqu $tmp,0x20($base)\n\t" + $$emit$$"add 0x40,$base\n\t" + $$emit$$"# L_fill_64_bytes:\n\t" $$emit$$"sub 0x8,rcx\n\t" $$emit$$"jge L_loop\n\t" $$emit$$"add 0x4,rcx\n\t" $$emit$$"jl L_tail\n\t" - $$emit$$"vmovdqu $tmp,(rax)\n\t" - $$emit$$"add 0x20,rax\n\t" + $$emit$$"vmovdqu $tmp,($base)\n\t" + $$emit$$"add 0x20,$base\n\t" $$emit$$"sub 0x4,rcx\n\t" - $$emit$$"# L_tail:\t# Clearing tail bytes\n\t" + $$emit$$"# L_tail:\t# Filling tail bytes\n\t" $$emit$$"add 0x4,rcx\n\t" $$emit$$"jle L_end\n\t" $$emit$$"dec rcx\n\t" $$emit$$"# L_sloop:\t# 8-byte short loop\n\t" - $$emit$$"vmovq xmm0,(rax)\n\t" - $$emit$$"add 0x8,rax\n\t" + $$emit$$"vmovq $tmp,($base)\n\t" + $$emit$$"add 0x8,$base\n\t" $$emit$$"dec rcx\n\t" $$emit$$"jge L_sloop\n\t" $$emit$$"# L_end:\n\t" @@ -14891,14 +14896,16 @@ instruct rep_stos_word_copy(rcx_RegL cnt, rdi_RegP base, regD tmp, rax_RegL val, ins_pipe(pipe_slow); %} -// Small non-constant length ClearArray for AVX512 targets. -instruct rep_stos_evex(rcx_RegL cnt, rdi_RegP base, legRegD tmp, kReg ktmp, rax_RegL val, +// Small zero fill for AVX512 targets. +instruct rep_stos_evex(rcx_RegL cnt, rdi_RegP base, legRegD tmp, kReg ktmp, immL0 zero, rax_RegL val, Universe dummy, rFlagsReg cr) %{ - predicate(!((ClearArrayNode*)n)->is_large() && !((ClearArrayNode*)n)->word_copy_only() && (UseAVX > 2)); - match(Set dummy (ClearArray (Binary cnt base) val)); + predicate((UseAVX > 2) && + !((ClearArrayNode*)n)->is_large() && + ((ClearArrayNode*)n)->is_zero_fill()); + match(Set dummy (ClearArray (Binary cnt base) zero)); ins_cost(125); - effect(USE_KILL cnt, USE_KILL base, TEMP tmp, TEMP ktmp, USE_KILL val, KILL cr); + effect(USE_KILL cnt, USE_KILL base, TEMP tmp, TEMP ktmp, KILL val, KILL cr); format %{ $$template $$emit$$"xorq rax, rax\t# ClearArray:\n\t" @@ -14915,28 +14922,29 @@ instruct rep_stos_evex(rcx_RegL cnt, rdi_RegP base, legRegD tmp, kReg ktmp, rax_ $$emit$$"shlq rcx,3\t# Convert doublewords to bytes\n\t" $$emit$$"rep stosb\t# Store rax to *rdi++ while rcx--\n\t" } else if (UseXMMForObjInit) { - $$emit$$"mov rdi,rax\n\t" - $$emit$$"vpxor ymm0,ymm0,ymm0\n\t" + $$emit$$"movdq $tmp, $val\n\t" + $$emit$$"punpcklqdq $tmp, $tmp\n\t" + $$emit$$"vinserti128_high $tmp, $tmp\n\t" $$emit$$"jmpq L_zero_64_bytes\n\t" $$emit$$"# L_loop:\t# 64-byte LOOP\n\t" - $$emit$$"vmovdqu ymm0,(rax)\n\t" - $$emit$$"vmovdqu ymm0,0x20(rax)\n\t" - $$emit$$"add 0x40,rax\n\t" + $$emit$$"vmovdqu $tmp,($base)\n\t" + $$emit$$"vmovdqu $tmp,0x20($base)\n\t" + $$emit$$"add 0x40,$base\n\t" $$emit$$"# L_zero_64_bytes:\n\t" $$emit$$"sub 0x8,rcx\n\t" $$emit$$"jge L_loop\n\t" $$emit$$"add 0x4,rcx\n\t" $$emit$$"jl L_tail\n\t" - $$emit$$"vmovdqu ymm0,(rax)\n\t" - $$emit$$"add 0x20,rax\n\t" + $$emit$$"vmovdqu $tmp,($base)\n\t" + $$emit$$"add 0x20,$base\n\t" $$emit$$"sub 0x4,rcx\n\t" $$emit$$"# L_tail:\t# Clearing tail bytes\n\t" $$emit$$"add 0x4,rcx\n\t" $$emit$$"jle L_end\n\t" $$emit$$"dec rcx\n\t" $$emit$$"# L_sloop:\t# 8-byte short loop\n\t" - $$emit$$"vmovq xmm0,(rax)\n\t" - $$emit$$"add 0x8,rax\n\t" + $$emit$$"vmovq $tmp,($base)\n\t" + $$emit$$"add 0x8,$base\n\t" $$emit$$"dec rcx\n\t" $$emit$$"jge L_sloop\n\t" $$emit$$"# L_end:\n\t" @@ -14952,16 +14960,18 @@ instruct rep_stos_evex(rcx_RegL cnt, rdi_RegP base, legRegD tmp, kReg ktmp, rax_ ins_pipe(pipe_slow); %} -instruct rep_stos_evex_word_copy(rcx_RegL cnt, rdi_RegP base, legRegD tmp, kReg ktmp, rax_RegL val, +// Small word fill for AVX512 targets. +instruct rep_stos_evex_word_fill(rcx_RegL cnt, rdi_RegP base, legRegD tmp, kReg ktmp, rax_RegL val, Universe dummy, rFlagsReg cr) %{ - predicate(!((ClearArrayNode*)n)->is_large() && ((ClearArrayNode*)n)->word_copy_only() && (UseAVX > 2)); + predicate((UseAVX > 2) && + !((ClearArrayNode*)n)->is_large() && + ((ClearArrayNode*)n)->requires_word_fill()); match(Set dummy (ClearArray (Binary cnt base) val)); ins_cost(125); effect(USE_KILL cnt, USE_KILL base, TEMP tmp, TEMP ktmp, USE_KILL val, KILL cr); format %{ $$template - $$emit$$"xorq rax, rax\t# ClearArray:\n\t" $$emit$$"cmp InitArrayShortSize,rcx\n\t" $$emit$$"jg LARGE\n\t" $$emit$$"dec rcx\n\t" @@ -14971,32 +14981,30 @@ instruct rep_stos_evex_word_copy(rcx_RegL cnt, rdi_RegP base, legRegD tmp, kReg $$emit$$"jge LOOP\n\t" $$emit$$"jmp DONE\n\t" $$emit$$"# LARGE:\n\t" - if (UseFastStosb) { - $$emit$$"shlq rcx,3\t# Convert doublewords to bytes\n\t" - $$emit$$"rep stosb\t# Store rax to *rdi++ while rcx--\n\t" - } else if (UseXMMForObjInit) { - $$emit$$"mov rdi,rax\n\t" - $$emit$$"vpxor ymm0,ymm0,ymm0\n\t" - $$emit$$"jmpq L_zero_64_bytes\n\t" + if (UseXMMForObjInit) { + $$emit$$"movdq $tmp, $val\n\t" + $$emit$$"punpcklqdq $tmp, $tmp\n\t" + $$emit$$"vinserti128_high $tmp, $tmp\n\t" + $$emit$$"jmpq L_fill_64_bytes\n\t" $$emit$$"# L_loop:\t# 64-byte LOOP\n\t" - $$emit$$"vmovdqu ymm0,(rax)\n\t" - $$emit$$"vmovdqu ymm0,0x20(rax)\n\t" - $$emit$$"add 0x40,rax\n\t" - $$emit$$"# L_zero_64_bytes:\n\t" + $$emit$$"vmovdqu $tmp,($base)\n\t" + $$emit$$"vmovdqu $tmp,0x20($base)\n\t" + $$emit$$"add 0x40,$base\n\t" + $$emit$$"# L_fill_64_bytes:\n\t" $$emit$$"sub 0x8,rcx\n\t" $$emit$$"jge L_loop\n\t" $$emit$$"add 0x4,rcx\n\t" $$emit$$"jl L_tail\n\t" - $$emit$$"vmovdqu ymm0,(rax)\n\t" - $$emit$$"add 0x20,rax\n\t" + $$emit$$"vmovdqu $tmp,($base)\n\t" + $$emit$$"add 0x20,$base\n\t" $$emit$$"sub 0x4,rcx\n\t" - $$emit$$"# L_tail:\t# Clearing tail bytes\n\t" + $$emit$$"# L_tail:\t# Filling tail bytes\n\t" $$emit$$"add 0x4,rcx\n\t" $$emit$$"jle L_end\n\t" $$emit$$"dec rcx\n\t" $$emit$$"# L_sloop:\t# 8-byte short loop\n\t" - $$emit$$"vmovq xmm0,(rax)\n\t" - $$emit$$"add 0x8,rax\n\t" + $$emit$$"vmovq $tmp,($base)\n\t" + $$emit$$"add 0x8,$base\n\t" $$emit$$"dec rcx\n\t" $$emit$$"jge L_sloop\n\t" $$emit$$"# L_end:\n\t" @@ -15012,15 +15020,18 @@ instruct rep_stos_evex_word_copy(rcx_RegL cnt, rdi_RegP base, legRegD tmp, kReg ins_pipe(pipe_slow); %} -// Large non-constant length ClearArray for non-AVX512 targets. -instruct rep_stos_large(rcx_RegL cnt, rdi_RegP base, regD tmp, rax_RegL val, +// Large zero fill for non-AVX512 targets. +instruct rep_stos_large(rcx_RegL cnt, rdi_RegP base, regD tmp, immL0 zero, rax_RegL val, Universe dummy, rFlagsReg cr) %{ - predicate(((ClearArrayNode*)n)->is_large() && !((ClearArrayNode*)n)->word_copy_only() && (UseAVX <= 2)); - match(Set dummy (ClearArray (Binary cnt base) val)); - effect(USE_KILL cnt, USE_KILL base, TEMP tmp, USE_KILL val, KILL cr); + predicate((UseAVX <= 2) && + ((ClearArrayNode*)n)->is_large() && + ((ClearArrayNode*)n)->is_zero_fill()); + match(Set dummy (ClearArray (Binary cnt base) zero)); + effect(USE_KILL cnt, USE_KILL base, TEMP tmp, KILL val, KILL cr); format %{ $$template + $$emit$$"xorq rax, rax\t# ClearArray:\n\t" if (UseFastStosb) { $$emit$$"shlq rcx,3\t# Convert doublewords to bytes\n\t" $$emit$$"rep stosb\t# Store rax to *rdi++ while rcx--" @@ -15030,24 +15041,24 @@ instruct rep_stos_large(rcx_RegL cnt, rdi_RegP base, regD tmp, rax_RegL val, $$emit$$"vinserti128_high $tmp, $tmp\n\t" $$emit$$"jmpq L_zero_64_bytes\n\t" $$emit$$"# L_loop:\t# 64-byte LOOP\n\t" - $$emit$$"vmovdqu $tmp,(rax)\n\t" - $$emit$$"vmovdqu $tmp,0x20(rax)\n\t" - $$emit$$"add 0x40,rax\n\t" + $$emit$$"vmovdqu $tmp,($base)\n\t" + $$emit$$"vmovdqu $tmp,0x20($base)\n\t" + $$emit$$"add 0x40,$base\n\t" $$emit$$"# L_zero_64_bytes:\n\t" $$emit$$"sub 0x8,rcx\n\t" $$emit$$"jge L_loop\n\t" $$emit$$"add 0x4,rcx\n\t" $$emit$$"jl L_tail\n\t" - $$emit$$"vmovdqu $tmp,(rax)\n\t" - $$emit$$"add 0x20,rax\n\t" + $$emit$$"vmovdqu $tmp,($base)\n\t" + $$emit$$"add 0x20,$base\n\t" $$emit$$"sub 0x4,rcx\n\t" $$emit$$"# L_tail:\t# Clearing tail bytes\n\t" $$emit$$"add 0x4,rcx\n\t" $$emit$$"jle L_end\n\t" $$emit$$"dec rcx\n\t" $$emit$$"# L_sloop:\t# 8-byte short loop\n\t" - $$emit$$"vmovq xmm0,(rax)\n\t" - $$emit$$"add 0x8,rax\n\t" + $$emit$$"vmovq $tmp,($base)\n\t" + $$emit$$"add 0x8,$base\n\t" $$emit$$"dec rcx\n\t" $$emit$$"jge L_sloop\n\t" $$emit$$"# L_end:\n\t" @@ -15062,10 +15073,13 @@ instruct rep_stos_large(rcx_RegL cnt, rdi_RegP base, regD tmp, rax_RegL val, ins_pipe(pipe_slow); %} -instruct rep_stos_large_word_copy(rcx_RegL cnt, rdi_RegP base, regD tmp, rax_RegL val, +// Large word fill for non-AVX512 targets. +instruct rep_stos_large_word_fill(rcx_RegL cnt, rdi_RegP base, regD tmp, rax_RegL val, Universe dummy, rFlagsReg cr) %{ - predicate(((ClearArrayNode*)n)->is_large() && ((ClearArrayNode*)n)->word_copy_only() && (UseAVX <= 2)); + predicate((UseAVX <= 2) && + ((ClearArrayNode*)n)->is_large() && + ((ClearArrayNode*)n)->requires_word_fill()); match(Set dummy (ClearArray (Binary cnt base) val)); effect(USE_KILL cnt, USE_KILL base, TEMP tmp, USE_KILL val, KILL cr); @@ -15074,26 +15088,26 @@ instruct rep_stos_large_word_copy(rcx_RegL cnt, rdi_RegP base, regD tmp, rax_Reg $$emit$$"movdq $tmp, $val\n\t" $$emit$$"punpcklqdq $tmp, $tmp\n\t" $$emit$$"vinserti128_high $tmp, $tmp\n\t" - $$emit$$"jmpq L_zero_64_bytes\n\t" + $$emit$$"jmpq L_fill_64_bytes\n\t" $$emit$$"# L_loop:\t# 64-byte LOOP\n\t" - $$emit$$"vmovdqu $tmp,(rax)\n\t" - $$emit$$"vmovdqu $tmp,0x20(rax)\n\t" - $$emit$$"add 0x40,rax\n\t" - $$emit$$"# L_zero_64_bytes:\n\t" + $$emit$$"vmovdqu $tmp,($base)\n\t" + $$emit$$"vmovdqu $tmp,0x20($base)\n\t" + $$emit$$"add 0x40,$base\n\t" + $$emit$$"# L_fill_64_bytes:\n\t" $$emit$$"sub 0x8,rcx\n\t" $$emit$$"jge L_loop\n\t" $$emit$$"add 0x4,rcx\n\t" $$emit$$"jl L_tail\n\t" - $$emit$$"vmovdqu $tmp,(rax)\n\t" - $$emit$$"add 0x20,rax\n\t" + $$emit$$"vmovdqu $tmp,($base)\n\t" + $$emit$$"add 0x20,$base\n\t" $$emit$$"sub 0x4,rcx\n\t" - $$emit$$"# L_tail:\t# Clearing tail bytes\n\t" + $$emit$$"# L_tail:\t# Filling tail bytes\n\t" $$emit$$"add 0x4,rcx\n\t" $$emit$$"jle L_end\n\t" $$emit$$"dec rcx\n\t" $$emit$$"# L_sloop:\t# 8-byte short loop\n\t" - $$emit$$"vmovq xmm0,(rax)\n\t" - $$emit$$"add 0x8,rax\n\t" + $$emit$$"vmovq $tmp,($base)\n\t" + $$emit$$"add 0x8,$base\n\t" $$emit$$"dec rcx\n\t" $$emit$$"jge L_sloop\n\t" $$emit$$"# L_end:\n\t" @@ -15108,47 +15122,49 @@ instruct rep_stos_large_word_copy(rcx_RegL cnt, rdi_RegP base, regD tmp, rax_Reg ins_pipe(pipe_slow); %} -// Large non-constant length ClearArray for AVX512 targets. -instruct rep_stos_large_evex(rcx_RegL cnt, rdi_RegP base, legRegD tmp, kReg ktmp, rax_RegL val, +// Large zero fill for AVX512 targets. +instruct rep_stos_large_evex(rcx_RegL cnt, rdi_RegP base, legRegD tmp, kReg ktmp, immL0 zero, rax_RegL val, Universe dummy, rFlagsReg cr) %{ - predicate(((ClearArrayNode*)n)->is_large() && !((ClearArrayNode*)n)->word_copy_only() && (UseAVX > 2)); - match(Set dummy (ClearArray (Binary cnt base) val)); - effect(USE_KILL cnt, USE_KILL base, TEMP tmp, TEMP ktmp, USE_KILL val, KILL cr); + predicate((UseAVX > 2) && + ((ClearArrayNode*)n)->is_large() && + ((ClearArrayNode*)n)->is_zero_fill()); + match(Set dummy (ClearArray (Binary cnt base) zero)); + effect(USE_KILL cnt, USE_KILL base, TEMP tmp, TEMP ktmp, KILL val, KILL cr); format %{ $$template + $$emit$$"xorq $val, $val\t# ClearArray:\n\t" if (UseFastStosb) { - $$emit$$"xorq rax, rax\t# ClearArray:\n\t" $$emit$$"shlq rcx,3\t# Convert doublewords to bytes\n\t" $$emit$$"rep stosb\t# Store rax to *rdi++ while rcx--" } else if (UseXMMForObjInit) { - $$emit$$"mov rdi,rax\t# ClearArray:\n\t" - $$emit$$"vpxor ymm0,ymm0,ymm0\n\t" + $$emit$$"movdq $tmp, $val\n\t" + $$emit$$"punpcklqdq $tmp, $tmp\n\t" + $$emit$$"vinserti128_high $tmp, $tmp\n\t" $$emit$$"jmpq L_zero_64_bytes\n\t" $$emit$$"# L_loop:\t# 64-byte LOOP\n\t" - $$emit$$"vmovdqu ymm0,(rax)\n\t" - $$emit$$"vmovdqu ymm0,0x20(rax)\n\t" - $$emit$$"add 0x40,rax\n\t" + $$emit$$"vmovdqu $tmp,($base)\n\t" + $$emit$$"vmovdqu $tmp,0x20($base)\n\t" + $$emit$$"add 0x40,$base\n\t" $$emit$$"# L_zero_64_bytes:\n\t" $$emit$$"sub 0x8,rcx\n\t" $$emit$$"jge L_loop\n\t" $$emit$$"add 0x4,rcx\n\t" $$emit$$"jl L_tail\n\t" - $$emit$$"vmovdqu ymm0,(rax)\n\t" - $$emit$$"add 0x20,rax\n\t" + $$emit$$"vmovdqu $tmp,($base)\n\t" + $$emit$$"add 0x20,$base\n\t" $$emit$$"sub 0x4,rcx\n\t" $$emit$$"# L_tail:\t# Clearing tail bytes\n\t" $$emit$$"add 0x4,rcx\n\t" $$emit$$"jle L_end\n\t" $$emit$$"dec rcx\n\t" $$emit$$"# L_sloop:\t# 8-byte short loop\n\t" - $$emit$$"vmovq xmm0,(rax)\n\t" - $$emit$$"add 0x8,rax\n\t" + $$emit$$"vmovq $tmp,($base)\n\t" + $$emit$$"add 0x8,$base\n\t" $$emit$$"dec rcx\n\t" $$emit$$"jge L_sloop\n\t" $$emit$$"# L_end:\n\t" } else { - $$emit$$"xorq rax, rax\t# ClearArray:\n\t" $$emit$$"rep stosq\t# Store rax to *rdi++ while rcx--" } %} @@ -15159,46 +15175,45 @@ instruct rep_stos_large_evex(rcx_RegL cnt, rdi_RegP base, legRegD tmp, kReg ktmp ins_pipe(pipe_slow); %} -instruct rep_stos_large_evex_word_copy(rcx_RegL cnt, rdi_RegP base, legRegD tmp, kReg ktmp, rax_RegL val, +// Large word fill for AVX512 targets. +instruct rep_stos_large_evex_word_fill(rcx_RegL cnt, rdi_RegP base, legRegD tmp, kReg ktmp, rax_RegL val, Universe dummy, rFlagsReg cr) %{ - predicate(((ClearArrayNode*)n)->is_large() && ((ClearArrayNode*)n)->word_copy_only() && (UseAVX > 2)); + predicate((UseAVX > 2) && + ((ClearArrayNode*)n)->is_large() && + ((ClearArrayNode*)n)->requires_word_fill()); match(Set dummy (ClearArray (Binary cnt base) val)); effect(USE_KILL cnt, USE_KILL base, TEMP tmp, TEMP ktmp, USE_KILL val, KILL cr); format %{ $$template - if (UseFastStosb) { - $$emit$$"xorq rax, rax\t# ClearArray:\n\t" - $$emit$$"shlq rcx,3\t# Convert doublewords to bytes\n\t" - $$emit$$"rep stosb\t# Store rax to *rdi++ while rcx--" - } else if (UseXMMForObjInit) { - $$emit$$"mov rdi,rax\t# ClearArray:\n\t" - $$emit$$"vpxor ymm0,ymm0,ymm0\n\t" - $$emit$$"jmpq L_zero_64_bytes\n\t" + if (UseXMMForObjInit) { + $$emit$$"movdq $tmp, $val\t# ClearArray:\n\t" + $$emit$$"punpcklqdq $tmp, $tmp\n\t" + $$emit$$"vinserti128_high $tmp, $tmp\n\t" + $$emit$$"jmpq L_fill_64_bytes\n\t" $$emit$$"# L_loop:\t# 64-byte LOOP\n\t" - $$emit$$"vmovdqu ymm0,(rax)\n\t" - $$emit$$"vmovdqu ymm0,0x20(rax)\n\t" - $$emit$$"add 0x40,rax\n\t" - $$emit$$"# L_zero_64_bytes:\n\t" + $$emit$$"vmovdqu $tmp,($base)\n\t" + $$emit$$"vmovdqu $tmp,0x20($base)\n\t" + $$emit$$"add 0x40,$base\n\t" + $$emit$$"# L_fill_64_bytes:\n\t" $$emit$$"sub 0x8,rcx\n\t" $$emit$$"jge L_loop\n\t" $$emit$$"add 0x4,rcx\n\t" $$emit$$"jl L_tail\n\t" - $$emit$$"vmovdqu ymm0,(rax)\n\t" - $$emit$$"add 0x20,rax\n\t" + $$emit$$"vmovdqu $tmp,($base)\n\t" + $$emit$$"add 0x20,$base\n\t" $$emit$$"sub 0x4,rcx\n\t" - $$emit$$"# L_tail:\t# Clearing tail bytes\n\t" + $$emit$$"# L_tail:\t# Filling tail bytes\n\t" $$emit$$"add 0x4,rcx\n\t" $$emit$$"jle L_end\n\t" $$emit$$"dec rcx\n\t" $$emit$$"# L_sloop:\t# 8-byte short loop\n\t" - $$emit$$"vmovq xmm0,(rax)\n\t" - $$emit$$"add 0x8,rax\n\t" + $$emit$$"vmovq $tmp,($base)\n\t" + $$emit$$"add 0x8,$base\n\t" $$emit$$"dec rcx\n\t" $$emit$$"jge L_sloop\n\t" $$emit$$"# L_end:\n\t" } else { - $$emit$$"xorq rax, rax\t# ClearArray:\n\t" $$emit$$"rep stosq\t# Store rax to *rdi++ while rcx--" } %} @@ -15209,14 +15224,16 @@ instruct rep_stos_large_evex_word_copy(rcx_RegL cnt, rdi_RegP base, legRegD tmp, ins_pipe(pipe_slow); %} -// Small constant length ClearArray for AVX512 targets. -instruct rep_stos_im(immL cnt, rRegP base, regD tmp, rax_RegL val, kReg ktmp, Universe dummy, rFlagsReg cr) +// Small constant-count zero fill for AVX512 targets. +instruct rep_stos_im(immL cnt, rRegP base, regD tmp, immL0 zero, rRegI val, kReg ktmp, Universe dummy, rFlagsReg cr) %{ - predicate(!((ClearArrayNode*)n)->is_large() && !((ClearArrayNode*)n)->word_copy_only() && - ((MaxVectorSize >= 32) && VM_Version::supports_avx512vl())); - match(Set dummy (ClearArray (Binary cnt base) val)); + predicate((MaxVectorSize >= 32) && + VM_Version::supports_avx512vl() && + !((ClearArrayNode*)n)->is_large() && + ((ClearArrayNode*)n)->is_zero_fill()); + match(Set dummy (ClearArray (Binary cnt base) zero)); ins_cost(100); - effect(TEMP tmp, USE_KILL val, TEMP ktmp, KILL cr); + effect(TEMP tmp, TEMP val, TEMP ktmp, KILL cr); format %{ "clear_mem_imm $base , $cnt \n\t" %} ins_encode %{ __ clear_mem($base$$Register, $cnt$$constant, $val$$Register, $tmp$$XMMRegister, $ktmp$$KRegister); diff --git a/src/hotspot/os/windows/os_windows.cpp b/src/hotspot/os/windows/os_windows.cpp index f62e9c298e8..7c7e61098f5 100644 --- a/src/hotspot/os/windows/os_windows.cpp +++ b/src/hotspot/os/windows/os_windows.cpp @@ -2671,14 +2671,6 @@ LONG Handle_IDiv_Exception(struct _EXCEPTION_POINTERS* exceptionInfo) { return EXCEPTION_CONTINUE_EXECUTION; } -static inline void report_error(Thread* t, DWORD exception_code, - address addr, void* siginfo, void* context) { - VMError::report_and_die(t, exception_code, addr, siginfo, context); - - // If UseOSErrorReporting, this will return here and save the error file - // somewhere where we can find it in the minidump. -} - //----------------------------------------------------------------------------- JNIEXPORT LONG WINAPI topLevelExceptionFilter(struct _EXCEPTION_POINTERS* exceptionInfo) { @@ -2750,9 +2742,8 @@ LONG WINAPI topLevelExceptionFilter(struct _EXCEPTION_POINTERS* exceptionInfo) { // Fatal red zone violation. overflow_state->disable_stack_red_zone(); tty->print_raw_cr("An unrecoverable stack overflow has occurred."); - report_error(t, exception_code, pc, exception_record, - exceptionInfo->ContextRecord); - return EXCEPTION_CONTINUE_SEARCH; + VMError::report_and_die(t, exception_code, pc, exception_record, + exceptionInfo->ContextRecord); } } else if (exception_code == EXCEPTION_ACCESS_VIOLATION) { if (in_java) { @@ -2789,9 +2780,8 @@ LONG WINAPI topLevelExceptionFilter(struct _EXCEPTION_POINTERS* exceptionInfo) { address stub = SharedRuntime::continuation_for_implicit_exception(thread, pc, SharedRuntime::IMPLICIT_NULL); if (stub != nullptr) return Handle_Exception(exceptionInfo, stub); } - report_error(t, exception_code, pc, exception_record, - exceptionInfo->ContextRecord); - return EXCEPTION_CONTINUE_SEARCH; + VMError::report_and_die(t, exception_code, pc, exception_record, + exceptionInfo->ContextRecord); } // Special care for fast JNI field accessors. @@ -2803,9 +2793,8 @@ LONG WINAPI topLevelExceptionFilter(struct _EXCEPTION_POINTERS* exceptionInfo) { } // Stack overflow or null pointer exception in native code. - report_error(t, exception_code, pc, exception_record, - exceptionInfo->ContextRecord); - return EXCEPTION_CONTINUE_SEARCH; + VMError::report_and_die(t, exception_code, pc, exception_record, + exceptionInfo->ContextRecord); } // /EXCEPTION_ACCESS_VIOLATION // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -2873,8 +2862,8 @@ LONG WINAPI topLevelExceptionFilter(struct _EXCEPTION_POINTERS* exceptionInfo) { #endif if (should_report_error) { - report_error(t, exception_code, pc, exception_record, - exceptionInfo->ContextRecord); + VMError::report_and_die(t, exception_code, pc, exception_record, + exceptionInfo->ContextRecord); } return EXCEPTION_CONTINUE_SEARCH; @@ -2894,8 +2883,8 @@ LONG WINAPI topLevelUnhandledExceptionFilter(struct _EXCEPTION_POINTERS* excepti Thread* thread = Thread::current_or_null_safe(); if (exceptionCode != EXCEPTION_BREAKPOINT) { - report_error(thread, exceptionCode, pc, exceptionInfo->ExceptionRecord, - exceptionInfo->ContextRecord); + VMError::report_and_die(thread, exceptionCode, pc, exceptionInfo->ExceptionRecord, + exceptionInfo->ContextRecord); } } diff --git a/src/hotspot/os_cpu/windows_aarch64/os_windows_aarch64.cpp b/src/hotspot/os_cpu/windows_aarch64/os_windows_aarch64.cpp index 2c2afb168fd..679e376ec98 100644 --- a/src/hotspot/os_cpu/windows_aarch64/os_windows_aarch64.cpp +++ b/src/hotspot/os_cpu/windows_aarch64/os_windows_aarch64.cpp @@ -291,6 +291,19 @@ int os::extra_bang_size_in_bytes() { extern "C" { int SpinPause() { - return 0; + using spin_wait_func_ptr_t = void (*)(); + spin_wait_func_ptr_t func = CAST_TO_FN_PTR(spin_wait_func_ptr_t, StubRoutines::aarch64::spin_wait()); + assert(func != nullptr, "StubRoutines::aarch64::spin_wait must not be null."); + (*func)(); + // If StubRoutines::aarch64::spin_wait consists of only a RET, + // SpinPause can be considered as implemented. There will be a sequence + // of instructions for: + // - call of SpinPause + // - load of StubRoutines::aarch64::spin_wait stub pointer + // - indirect call of the stub + // - return from the stub + // - return from SpinPause + // So '1' always is returned. + return 1; } }; diff --git a/src/hotspot/share/c1/c1_GraphBuilder.cpp b/src/hotspot/share/c1/c1_GraphBuilder.cpp index bb7c10f99df..ce5e9a2c10f 100644 --- a/src/hotspot/share/c1/c1_GraphBuilder.cpp +++ b/src/hotspot/share/c1/c1_GraphBuilder.cpp @@ -1092,12 +1092,15 @@ void GraphBuilder::load_indexed(BasicType type) { bool is_null_free = array_klass->is_elem_null_free(); bool will_link; ciField* next_field = s.get_field(will_link); - bool next_needs_patching = !next_field->holder()->is_initialized() || + ciInstanceKlass* next_holder = next_field->holder(); + bool next_needs_patching = !next_holder->is_initialized() || !next_field->will_link(method(), Bytecodes::_getfield) || PatchALot; bool needs_atomic_access = array_klass->is_elem_atomic(); + // Offset adjustment for delayed reads requires a concrete inline holder + bool next_holder_is_inlinetype = next_holder->is_inlinetype(); can_delay_access = is_null_free && C1UseDelayedFlattenedFieldReads && - !next_needs_patching && !needs_atomic_access; + !next_needs_patching && !needs_atomic_access && next_holder_is_inlinetype; } if (can_delay_access) { // potentially optimizable array access, storing information for delayed decision @@ -1107,16 +1110,20 @@ void GraphBuilder::load_indexed(BasicType type) { set_pending_load_indexed(dli); return; // Nothing else to do for now } else { - NewInstance* buffer = new NewInstance(elem_klass, state_before, false, true); - buffer->set_null_free(true); - _memory->new_instance(buffer); - result = append_split(buffer); load_indexed = new LoadIndexed(array, index, length, type, state_before); - load_indexed->set_buffer(buffer); - // The LoadIndexed node will initialize this instance by copying from - // the flat field. Ensure these stores are visible before any - // subsequent store that publishes this reference. - need_membar = true; + // Deoptimize on non-null because buffering requires the value class to be initialized + bool assert_null = !array_klass->is_elem_null_free() && !elem_klass->is_initialized(); + if (!assert_null) { + NewInstance* buffer = new NewInstance(elem_klass, state_before, false, true); + buffer->set_null_free(true); + _memory->new_instance(buffer); + result = append_split(buffer); + load_indexed->set_buffer(buffer); + // The LoadIndexed node will initialize this instance by copying from + // the flat field. Ensure these stores are visible before any + // subsequent store that publishes this reference. + need_membar = true; + } } } else { load_indexed = new LoadIndexed(array, index, length, type, state_before); @@ -1998,12 +2005,16 @@ void GraphBuilder::access_field(Bytecodes::Code code) { s.next(); if (s.cur_bc() == Bytecodes::_getfield && !needs_patching) { ciField* next_field = s.get_field(will_link); - bool next_needs_patching = !next_field->holder()->is_loaded() || + ciInstanceKlass* next_holder = next_field->holder(); + bool next_needs_patching = !next_holder->is_loaded() || !next_field->will_link(method(), Bytecodes::_getfield) || PatchALot; // We can't update the offset for atomic accesses bool next_needs_atomic_access = next_field->is_flat() && next_field->is_atomic(); - can_delay_access = C1UseDelayedFlattenedFieldReads && !next_needs_patching && !next_needs_atomic_access && next_field->is_null_free(); + // Offset adjustment for delayed reads requires a concrete inline holder + bool next_holder_is_inlinetype = next_holder->is_inlinetype(); + can_delay_access = C1UseDelayedFlattenedFieldReads && !next_needs_patching && !next_needs_atomic_access && + next_field->is_null_free() && next_holder_is_inlinetype; } } diff --git a/src/hotspot/share/c1/c1_LIRGenerator.cpp b/src/hotspot/share/c1/c1_LIRGenerator.cpp index 0227615241c..52bb4e224b4 100644 --- a/src/hotspot/share/c1/c1_LIRGenerator.cpp +++ b/src/hotspot/share/c1/c1_LIRGenerator.cpp @@ -772,9 +772,9 @@ void LIRGenerator::arraycopy_helper(Intrinsic* x, int* flagsp, ciArrayKlass** ex if (expected_type == nullptr) expected_type = src_declared_type; if (expected_type == nullptr) expected_type = dst_declared_type; - if (expected_type != nullptr && expected_type->is_obj_array_klass()) { + if (expected_type != nullptr && expected_type->is_obj_array_klass() && !expected_type->is_refined()) { // For a direct pointer comparison, we need the refined array klass pointer - expected_type = ciObjArrayKlass::make(expected_type->as_array_klass()->element_klass()); + expected_type = ciObjArrayKlass::make(expected_type->as_array_klass()->element_klass(), true /* refined_type */); } src_objarray = (src_exact_type && src_exact_type->is_obj_array_klass()) || (src_declared_type && src_declared_type->is_obj_array_klass()); @@ -1489,7 +1489,20 @@ LIR_Opr LIRGenerator::load_constant(Constant* x) { LIR_Opr LIRGenerator::load_constant(LIR_Const* c) { BasicType t = c->type(); - for (int i = 0; i < _constants.length() && !in_conditional_code(); i++) { + if (in_conditional_code()) { + // TODO 8353851: Control flow introduced by check_flat_array() is currently opaque to the register allocator. + // Do not use or update the constant -> register cache in such conditional code because the register allocator could + // spill a constant and only rematerialize it into a register in one branch of check_flat_array() but not the other. + // Since the control flow is opaque to the register allocator, it assumes the rematerialized constant in the register + // dominates all subsequent uses in the block and does not insert another rematerialization. When taking the + // non-rematerialized branch of check_flat_array() at runtime, the register contains garbage potentially causing + // a crash. + LIR_Opr result = new_register(t); + __ move(c, result); + return result; + } + + for (int i = 0; i < _constants.length(); i++) { LIR_Const* other = _constants.at(i); if (t == other->type()) { switch (t) { @@ -1513,11 +1526,9 @@ LIR_Opr LIRGenerator::load_constant(LIR_Const* c) { } LIR_Opr result = new_register(t); - __ move((LIR_Opr)c, result); - if (!in_conditional_code()) { - _constants.append(c); - _reg_for_constants.append(result); - } + __ move(c, result); + _constants.append(c); + _reg_for_constants.append(result); return result; } @@ -2156,21 +2167,41 @@ void LIRGenerator::do_LoadField(LoadField* x) { ciInlineKlass* vk = field->type()->as_inline_klass(); #ifdef ASSERT assert(field->is_atomic(), "No atomic access required"); + assert(!is_volatile, "Flat fields cannot be volatile"); assert(x->state_before() != nullptr, "Needs state before"); #endif - // Allocate buffer (we can't easily do this conditionally on the null check below - // because branches added in the LIR are opaque to the register allocator). - NewInstance* buffer = new NewInstance(vk, x->state_before(), false, true); - do_NewInstance(buffer); - LIRItem dest(buffer, this); + NewInstance* buffer = nullptr; + bool assert_null = !field->is_null_free() && !vk->is_initialized(); + if (!assert_null) { + // Allocate the buffer before loading the payload because allocation may safepoint + // and a payload may contain oops represented as raw bits and thus invisible to the GC. + // We can't easily allocate conditionally on the null check below because branches + // added in the LIR are opaque to the register allocator. + buffer = new NewInstance(vk, x->state_before(), false, true); + do_NewInstance(buffer); + } - // Copy the payload to the buffer BasicType bt = vk->atomic_size_to_basic_type(field->is_null_free()); LIR_Opr payload = new_register((bt == T_LONG) ? bt : T_INT); access_load_at(decorators, bt, object, LIR_OprFact::intConst(field->offset_in_bytes()), payload, // Make sure to emit an implicit null check info ? new CodeEmitInfo(info) : nullptr, info); + + if (assert_null) { + // Deoptimize on non-null because buffering requires the value class to be initialized + CodeEmitInfo* null_assert_info = state_for(x, x->state_before()); + __ logical_and(payload, null_marker_mask(bt, field), payload); + __ cmp(lir_cond_notEqual, payload, (bt == T_LONG) ? LIR_OprFact::longConst(0) : LIR_OprFact::intConst(0)); + __ branch(lir_cond_notEqual, new DeoptimizeStub(null_assert_info, Deoptimization::Reason_null_assert, + Deoptimization::Action_make_not_entrant)); + __ move(LIR_OprFact::oopConst(nullptr), rlock_result(x)); + return; + } + + // Copy the payload to the buffer + assert(buffer != nullptr, "buffer required"); + LIRItem dest(buffer, this); access_store_at(decorators, bt, dest, LIR_OprFact::intConst(vk->payload_offset()), payload); if (field->is_null_free()) { @@ -2353,6 +2384,30 @@ void LIRGenerator::do_LoadIndexed(LoadIndexed* x) { } } + ciFlatArrayKlass* flat_array_klass = x->array()->is_loaded_flat_array() ? + x->array()->declared_type()->as_flat_array_klass() : nullptr; + bool assert_null = flat_array_klass != nullptr && !flat_array_klass->is_elem_null_free() && + !flat_array_klass->element_klass()->as_inline_klass()->is_initialized(); + if (assert_null) { + // Deoptimize on non-null because buffering requires the value class to be initialized + assert(x->buffer() == nullptr && x->delayed() == nullptr, "null assertion should not buffer"); + assert(flat_array_klass->is_elem_atomic(), "nullable flat arrays must use an atomic layout"); + ciInlineKlass* elem_klass = flat_array_klass->element_klass()->as_inline_klass(); + CodeEmitInfo* null_assert_info = state_for(x, x->state_before()); + BasicType bt = elem_klass->atomic_size_to_basic_type(false); + LIR_Opr elm_op = get_and_load_element_address(array, index); + ComputedAddressValue* elm_resolved_addr = new ComputedAddressValue(as_ValueType(bt), elm_op); + LIRItem elm_item(elm_resolved_addr, this); + LIR_Opr payload = new_register((bt == T_LONG) ? bt : T_INT); + access_load_at(IN_HEAP, bt, elm_item, LIR_OprFact::intConst(0), payload, nullptr, nullptr); + __ logical_and(payload, null_marker_mask(bt, elem_klass->null_marker_offset_in_payload()), payload); + __ cmp(lir_cond_notEqual, payload, (bt == T_LONG) ? LIR_OprFact::longConst(0) : LIR_OprFact::intConst(0)); + __ branch(lir_cond_notEqual, new DeoptimizeStub(null_assert_info, Deoptimization::Reason_null_assert, + Deoptimization::Action_make_not_entrant)); + __ move(LIR_OprFact::oopConst(nullptr), rlock_result(x)); + return; + } + Value element = nullptr; if (x->buffer() != nullptr) { assert(x->array()->is_loaded_flat_array(), "must be"); diff --git a/src/hotspot/share/c1/c1_Runtime1.cpp b/src/hotspot/share/c1/c1_Runtime1.cpp index bcf55b454b6..e91691f96e2 100644 --- a/src/hotspot/share/c1/c1_Runtime1.cpp +++ b/src/hotspot/share/c1/c1_Runtime1.cpp @@ -383,10 +383,10 @@ const char* Runtime1::name_for_address(address entry) { return pd_name_for_address(entry); } -static void allocate_instance(JavaThread* current, Klass* klass, TRAPS) { +JRT_ENTRY(void, Runtime1::new_instance(JavaThread* current, Klass* klass)) #ifndef PRODUCT if (PrintC1Statistics) { - Runtime1::_new_instance_slowcase_cnt++; + _new_instance_slowcase_cnt++; } #endif assert(klass->is_klass(), "not a class"); @@ -400,10 +400,6 @@ static void allocate_instance(JavaThread* current, Klass* klass, TRAPS) { current->set_vm_result_oop(obj); JRT_END -JRT_ENTRY(void, Runtime1::new_instance(JavaThread* current, Klass* klass)) - allocate_instance(current, klass, CHECK); -JRT_END - JRT_ENTRY(void, Runtime1::new_type_array(JavaThread* current, Klass* klass, jint length)) #ifndef PRODUCT if (PrintC1Statistics) { @@ -1188,7 +1184,7 @@ JRT_ENTRY(void, Runtime1::patch_code(JavaThread* current, StubId stub_id )) { Bytecode_anewarray anew(caller_method(), caller_method->bcp_from(bci)); Klass* ek = caller_method->constants()->klass_at(anew.index(), CHECK); k = ek->array_klass(CHECK); - if (!k->is_typeArray_klass() && !k->is_refArray_klass() && !k->is_flatArray_klass()) { + if (k->is_unrefined_objArray_klass()) { k = ObjArrayKlass::cast(k)->klass_with_properties(ArrayProperties::Default(), THREAD); } if (k->is_flatArray_klass()) { diff --git a/src/hotspot/share/cds/aotMetaspace.cpp b/src/hotspot/share/cds/aotMetaspace.cpp index c933bde2c8f..a22e9bc6bf3 100644 --- a/src/hotspot/share/cds/aotMetaspace.cpp +++ b/src/hotspot/share/cds/aotMetaspace.cpp @@ -1221,8 +1221,8 @@ void AOTMetaspace::dump_static_archive_impl(StaticArchiveBuilder& builder, TRAPS assert(!_output_mapinfo->is_open(), "Must be closed already"); _output_mapinfo = nullptr; if (status && CDSConfig::is_dumping_preimage_static_archive()) { - tty->print_cr("%s AOTConfiguration recorded: %s", - CDSConfig::has_temp_aot_config_file() ? "Temporary" : "", AOTConfiguration); + tty->print_cr("%sAOTConfiguration recorded: %s", + CDSConfig::has_temp_aot_config_file() ? "Temporary " : "", AOTConfiguration); if (CDSConfig::is_single_command_training()) { fork_and_dump_final_static_archive(CHECK); } @@ -1359,8 +1359,19 @@ void AOTMetaspace::fork_and_dump_final_static_archive(TRAPS) { tty->print_cr("Launching child process %s to assemble AOT cache %s using configuration %s", cmd, AOTCacheOutput, AOTConfiguration); int status = exec_jvm_with_java_tool_options(cmd, CHECK); if (status != 0) { + // We do this in all cases when the child process is launched because: + // - the AOT training process is about to exit; or + // - jcmd or AOTCacheMXBean is used to end AOT training. + // + // The child process is just a convenient way to get a fresh JVM state to + // assemble the AOT cache. Logically, we consider the AOT assembly to be + // executed as part of the current JVM. If the child process has failed, + // we should exit the current JVM as well. + // + // To help debugging, if we have created a temporary AOT config file, do not + // delete it. log_error(aot)("Child process failed; status = %d", status); - // We leave the temp config file for debugging + vm_exit(status); } else if (CDSConfig::has_temp_aot_config_file()) { const char* tmp_config = AOTConfiguration; // On Windows, need WRITE permission to remove the file. diff --git a/src/hotspot/share/ci/bcEscapeAnalyzer.cpp b/src/hotspot/share/ci/bcEscapeAnalyzer.cpp index d9f2d54427e..c4c516002dd 100644 --- a/src/hotspot/share/ci/bcEscapeAnalyzer.cpp +++ b/src/hotspot/share/ci/bcEscapeAnalyzer.cpp @@ -189,14 +189,25 @@ void BCEscapeAnalyzer::set_global_escape(ArgumentMap vars, bool merge) { } void BCEscapeAnalyzer::set_modified(ArgumentMap vars, int offs, int size) { - for (int i = 0; i < _arg_size; i++) { if (vars.contains(i)) { set_arg_modified(i, offs, size); } } - if (vars.contains_unknown()) + if (vars.contains_unknown()) { _unknown_modified = true; + } +} + +void BCEscapeAnalyzer::set_modified_any_offset(ArgumentMap vars) { + for (int i = 0; i < _arg_size; i++) { + if (vars.contains(i)) { + _arg_modified[i] = (uint)-1; + } + } + if (vars.contains_unknown()) { + _unknown_modified = true; + } } bool BCEscapeAnalyzer::is_recursive_call(ciMethod* callee) { @@ -227,7 +238,7 @@ bool BCEscapeAnalyzer::is_arg_modified(int arg, int offset, int size_in_bytes) { void BCEscapeAnalyzer::set_arg_modified(int arg, int offset, int size_in_bytes) { if (offset == OFFSET_ANY) { - _arg_modified[arg] = (uint) -1; + _arg_modified[arg] = (uint)-1; return; } assert(arg >= 0 && arg < _arg_size, "must be an argument."); @@ -537,7 +548,7 @@ void BCEscapeAnalyzer::iterate_one_block(ciBlock *blk, StateInfo &state, Growabl state.spop(); ArgumentMap arr = state.apop(); set_method_escape(arr); - set_modified(arr, OFFSET_ANY, type2size[T_INT]*HeapWordSize); + set_modified_any_offset(arr); break; } case Bytecodes::_lastore: @@ -547,7 +558,7 @@ void BCEscapeAnalyzer::iterate_one_block(ciBlock *blk, StateInfo &state, Growabl state.spop(); ArgumentMap arr = state.apop(); set_method_escape(arr); - set_modified(arr, OFFSET_ANY, type2size[T_LONG]*HeapWordSize); + set_modified_any_offset(arr); break; } case Bytecodes::_aastore: @@ -555,10 +566,8 @@ void BCEscapeAnalyzer::iterate_one_block(ciBlock *blk, StateInfo &state, Growabl set_global_escape(state.apop()); state.spop(); ArgumentMap arr = state.apop(); - // If the array is a flat array, a larger part of it is modified than - // the size of a reference. However, if OFFSET_ANY is given as - // parameter to set_modified(), size is not taken into account. - set_modified(arr, OFFSET_ANY, type2size[T_OBJECT]*HeapWordSize); + // If the array is a flat array, a larger part of it is modified than the size of a reference. + set_modified_any_offset(arr); break; } case Bytecodes::_pop: diff --git a/src/hotspot/share/ci/bcEscapeAnalyzer.hpp b/src/hotspot/share/ci/bcEscapeAnalyzer.hpp index 7bdd4a58146..e7e24a067fe 100644 --- a/src/hotspot/share/ci/bcEscapeAnalyzer.hpp +++ b/src/hotspot/share/ci/bcEscapeAnalyzer.hpp @@ -84,6 +84,7 @@ class BCEscapeAnalyzer : public ArenaObj { void set_method_escape(ArgumentMap vars); void set_global_escape(ArgumentMap vars, bool merge = false); void set_modified(ArgumentMap vars, int offs, int size); + void set_modified_any_offset(ArgumentMap vars); bool is_recursive_call(ciMethod* callee); void invoke(StateInfo &state, Bytecodes::Code code, ciMethod* target, ciKlass* holder); diff --git a/src/hotspot/share/ci/ciArrayKlass.cpp b/src/hotspot/share/ci/ciArrayKlass.cpp index a1e8adf7f4b..4be808cb2bc 100644 --- a/src/hotspot/share/ci/ciArrayKlass.cpp +++ b/src/hotspot/share/ci/ciArrayKlass.cpp @@ -60,7 +60,7 @@ ciType* ciArrayKlass::element_type() { if (is_type_array_klass()) { return ciType::make(as_type_array_klass()->element_type()); } else { - return element_klass()->as_klass(); + return as_obj_array_klass()->element_klass()->as_klass(); } } diff --git a/src/hotspot/share/ci/ciClassList.hpp b/src/hotspot/share/ci/ciClassList.hpp index 04b13d5cae9..5e21c2c0705 100644 --- a/src/hotspot/share/ci/ciClassList.hpp +++ b/src/hotspot/share/ci/ciClassList.hpp @@ -117,7 +117,7 @@ friend class ciReplay; \ friend class ciTypeArray; \ friend class ciType; \ friend class ciReturnAddress; \ -friend class ciWrapper; \ +friend class ciWrapper; \ friend class ciKlass; \ friend class ciInstanceKlass; \ friend class ciInlineKlass; \ diff --git a/src/hotspot/share/ci/ciEnv.cpp b/src/hotspot/share/ci/ciEnv.cpp index 02fcc3ea72e..8123d21dd98 100644 --- a/src/hotspot/share/ci/ciEnv.cpp +++ b/src/hotspot/share/ci/ciEnv.cpp @@ -187,9 +187,9 @@ ciEnv::ciEnv(CompileTask* task) // { // RecordLocation fp(this, "field1"); // // location: "field1" -// { RecordLocation fp(this, " field2"); // location: "field1 field2" } +// { RecordLocation fp(this, "field2"); // location: "field1 field2" } // // location: "field1" -// { RecordLocation fp(this, " field3"); // location: "field1 field3" } +// { RecordLocation fp(this, "field3"); // location: "field1 field3" } // // location: "field1" // } // // location: "" @@ -225,10 +225,13 @@ public: // append a new component ATTRIBUTE_PRINTF(3, 4) RecordLocation(ciEnv* ci, const char* fmt, ...) { - end = ci->_dyno_name + strlen(ci->_dyno_name); + size_t len = strlen(ci->_dyno_name); + end = ci->_dyno_name + len; va_list args; va_start(args, fmt); - push(ci, " "); + if (len > 0) { + push(ci, " "); + } push_va(ci, fmt, args); va_end(args); } @@ -490,7 +493,7 @@ ciKlass* ciEnv::get_klass_by_name_impl(ciKlass* accessing_klass, require_local); if (elem_klass != nullptr && elem_klass->is_loaded()) { // Now make an array for it - return ciArrayKlass::make(elem_klass); + return ciObjArrayKlass::make_impl(elem_klass); } } diff --git a/src/hotspot/share/ci/ciFlatArray.hpp b/src/hotspot/share/ci/ciFlatArray.hpp index b1ffdd50c2c..d159c984434 100644 --- a/src/hotspot/share/ci/ciFlatArray.hpp +++ b/src/hotspot/share/ci/ciFlatArray.hpp @@ -31,6 +31,7 @@ // ciFlatArray // // This class represents a flatArrayOop in the HotSpot virtual machine. +// TODO 8388127: Sync ciArray class hierarchy with arrayOopDesc class hierarchy. class ciFlatArray : public ciArray { CI_PACKAGE_ACCESS diff --git a/src/hotspot/share/ci/ciInstanceKlass.cpp b/src/hotspot/share/ci/ciInstanceKlass.cpp index 41adc7de96b..596a0a8415a 100644 --- a/src/hotspot/share/ci/ciInstanceKlass.cpp +++ b/src/hotspot/share/ci/ciInstanceKlass.cpp @@ -838,8 +838,8 @@ public: StaticFieldPrinter(out), _obj(obj) { } void do_field(fieldDescriptor* fd) { - do_field_helper(fd, _obj, true); _out->print(" "); + do_field_helper(fd, _obj, true); } }; @@ -865,27 +865,48 @@ void StaticFieldPrinter::do_field_helper(fieldDescriptor* fd, oop mirror, bool i case T_ARRAY: // fall-through case T_OBJECT: if (!fd->is_null_free_inline_type()) { - _out->print("%s ", fd->signature()->as_quoted_ascii()); + _out->print("%s", fd->signature()->as_quoted_ascii()); oop value = mirror->obj_field_acquire(fd->offset()); if (value == nullptr) { if (field_type == T_ARRAY) { - _out->print("%d", -1); + _out->print(" %d", -1); } - _out->cr(); } else if (value->is_instance()) { assert(field_type == T_OBJECT, ""); if (value->is_a(vmClasses::String_klass())) { const char* ascii_value = java_lang_String::as_quoted_ascii(value); - _out->print("\"%s\"", (ascii_value != nullptr) ? ascii_value : ""); + _out->print(" \"%s\"", (ascii_value != nullptr) ? ascii_value : ""); } else { const char* klass_name = value->klass()->name()->as_quoted_ascii(); - _out->print("%s", klass_name); + _out->print(" %s", klass_name); } } else if (value->is_array()) { arrayOop a = (arrayOop)value; - _out->print("%d", a->length()); + _out->print(" %d", a->length()); if (value->is_objArray()) { objArrayOop oa = (objArrayOop)value; + if (value->is_flatArray()) { + FlatArrayKlass* klass = ((flatArrayOop)oa)->klass(); + LayoutKind lk = klass->layout_kind(); + _out->print(" flat"); + if (LayoutKindHelper::is_nullable_flat(lk)) { + _out->print(" nullable"); + } else { + _out->print(" null-free"); + } + if (LayoutKindHelper::is_atomic_flat(lk)) { + _out->print(" atomic"); + } else { + _out->print(" non-atomic"); + } + } else { + _out->print(" ref"); + if (oa->klass()->is_null_free_array_klass()) { + _out->print(" null-free"); + } else { + _out->print(" nullable"); + } + } const char* klass_name = value->klass()->name()->as_quoted_ascii(); _out->print(" %s", klass_name); } @@ -895,6 +916,7 @@ void StaticFieldPrinter::do_field_helper(fieldDescriptor* fd, oop mirror, bool i break; } else { // handling of null free inline type + _out->print("%s", fd->signature()->as_quoted_ascii()); ResetNoHandleMark rnhm; Thread* THREAD = Thread::current(); SignatureStream ss(fd->signature(), false); diff --git a/src/hotspot/share/ci/ciMethod.cpp b/src/hotspot/share/ci/ciMethod.cpp index 7b4220ec636..c89be0b7239 100644 --- a/src/hotspot/share/ci/ciMethod.cpp +++ b/src/hotspot/share/ci/ciMethod.cpp @@ -1044,10 +1044,7 @@ bool ciMethod::is_compiled_lambda_form() const { // ciMethod::is_object_constructor // bool ciMethod::is_object_constructor() const { - return (name() == ciSymbols::object_initializer_name() - && signature()->return_type()->is_void()); - // Note: We can't test is_static, because that would - // require the method to be loaded. Sometimes it isn't. + return name() == ciSymbols::object_initializer_name(); } // ------------------------------------------------------------------ diff --git a/src/hotspot/share/ci/ciObjArrayKlass.cpp b/src/hotspot/share/ci/ciObjArrayKlass.cpp index 684322ad810..a90b23b558d 100644 --- a/src/hotspot/share/ci/ciObjArrayKlass.cpp +++ b/src/hotspot/share/ci/ciObjArrayKlass.cpp @@ -190,12 +190,12 @@ ciObjArrayKlass* ciObjArrayKlass::make(ciKlass* element_klass, bool refined_type GUARDED_VM_ENTRY(return make_impl(element_klass, refined_type, null_free, atomic);) } -ciArrayKlass* ciObjArrayKlass::make(ciKlass* element_klass, int dims) { +ciObjArrayKlass* ciObjArrayKlass::make(ciKlass* element_klass, int dims) { ciKlass* klass = element_klass; for (int i = 0; i < dims; i++) { - klass = ciObjArrayKlass::make(klass, /* refined_type = */ false); + klass = make(klass, /* refined_type = */ false); } - return klass->as_array_klass(); + return klass->as_obj_array_klass(); } ciKlass* ciObjArrayKlass::exact_klass() { diff --git a/src/hotspot/share/ci/ciObjArrayKlass.hpp b/src/hotspot/share/ci/ciObjArrayKlass.hpp index b2f14bd8ed0..b79e65afe8d 100644 --- a/src/hotspot/share/ci/ciObjArrayKlass.hpp +++ b/src/hotspot/share/ci/ciObjArrayKlass.hpp @@ -70,7 +70,7 @@ public: bool is_obj_array_klass() const { return true; } static ciObjArrayKlass* make(ciKlass* element_klass, bool refined_type = true, bool null_free = false, bool atomic = true); - static ciArrayKlass* make(ciKlass* element_klass, int dims); + static ciObjArrayKlass* make(ciKlass* element_klass, int dims); virtual ciKlass* exact_klass(); diff --git a/src/hotspot/share/ci/ciReplay.cpp b/src/hotspot/share/ci/ciReplay.cpp index be55687cca4..733748735fe 100644 --- a/src/hotspot/share/ci/ciReplay.cpp +++ b/src/hotspot/share/ci/ciReplay.cpp @@ -514,15 +514,14 @@ class CompileReplay : public StackObj { return k; } obj = ciReplay::obj_field(obj, field); - // TODO 8350865 I think we need to handle null-free/flat arrays here - if (obj != nullptr && obj->is_refArray()) { - refArrayOop arr = oop_cast(obj); + if (obj != nullptr && obj->is_objArray()) { + objArrayOop arr = oop_cast(obj); int index = parse_int("index"); if (index >= arr->length()) { report_error("bad array index"); return nullptr; } - obj = arr->obj_at(index); + obj = arr->obj_at(index, THREAD); } } while (obj != nullptr); if (obj == nullptr) { @@ -825,7 +824,7 @@ class CompileReplay : public StackObj { rec->_instructions_size = parse_int("instructions_size"); } - // ciMethodData orig * data * oops ( )* methods ( )* + // ciMethodData orig * data * oops ( ?)* methods ( )* void process_ciMethodData(TRAPS) { Method* method = parse_method(CHECK); if (had_error()) return; @@ -1139,12 +1138,26 @@ class CompileReplay : public StackObj { value = oopFactory::new_longArray(length, CHECK_(true)); } else if (field_signature[0] == JVM_SIGNATURE_ARRAY && field_signature[1] == JVM_SIGNATURE_CLASS) { - Klass* actual_array_klass = parse_klass(CHECK_(true)); - // TODO 8350865 I think we need to handle null-free/flat arrays here - // This handling will change the array property argument passed to the - // factory below - Klass* kelem = ObjArrayKlass::cast(actual_array_klass)->element_klass(); - value = oopFactory::new_objArray(kelem, length, CHECK_(true)); + const char* flatness = parse_string(); + if (strcmp(flatness, "ref") == 0) { + const char* nullability = parse_string(); + bool null_restricted = (strcmp(nullability, "null-free") == 0); + Klass* actual_array_klass = parse_klass(CHECK_(true)); + Klass* kelem = ObjArrayKlass::cast(actual_array_klass)->element_klass(); + ArrayProperties props = ArrayProperties::Default().with_non_atomic(false).with_null_restricted(null_restricted); + value = oopFactory::new_refArray(kelem, length, props, CHECK_(true)); + } else if (strcmp(flatness, "flat") == 0) { + const char* nullability = parse_string(); + const char* atomicity = parse_string(); + bool null_restricted = (strcmp(nullability, "null-free") == 0); + bool non_atomic = (strcmp(atomicity, "non-atomic") == 0); + Klass* actual_array_klass = parse_klass(CHECK_(true)); + Klass* kelem = ObjArrayKlass::cast(actual_array_klass)->element_klass(); + ArrayProperties props = ArrayProperties::Default().with_non_atomic(non_atomic).with_null_restricted(null_restricted); + value = oopFactory::new_flatArray(InlineKlass::cast(kelem), length, props, CHECK_(true)); + } else { + report_error("unrecognized array kind"); + } } else { report_error("unhandled array staticfield"); } @@ -1190,7 +1203,7 @@ class CompileReplay : public StackObj { fieldDescriptor fd; Symbol* name = SymbolTable::new_symbol(field_name); Symbol* sig = SymbolTable::new_symbol(field_signature); - if (!k->find_local_field(name, sig, &fd) || + if (!k->find_local_field(name, sig, &fd, _version >= 3) || !fd.is_static() || fd.has_initial_value()) { report_error(field_name); diff --git a/src/hotspot/share/ci/ciReplay.hpp b/src/hotspot/share/ci/ciReplay.hpp index f8c3e73b120..6013c284510 100644 --- a/src/hotspot/share/ci/ciReplay.hpp +++ b/src/hotspot/share/ci/ciReplay.hpp @@ -134,7 +134,7 @@ class ciReplay { // 1: first instanceKlass sets protection domain (8275868) // replace current_mileage with invocation_count (8276095) // 2: incremental inlining support (8254108) -// 3: value class array support (8375548) +// 3: value class array support (8375548 & 8388709) #define REPLAY_VERSION 3 // current version, bump up for incompatible changes #endif // SHARE_CI_CIREPLAY_HPP diff --git a/src/hotspot/share/ci/ciSignature.cpp b/src/hotspot/share/ci/ciSignature.cpp index 973e96b93ba..5f764fa9d32 100644 --- a/src/hotspot/share/ci/ciSignature.cpp +++ b/src/hotspot/share/ci/ciSignature.cpp @@ -56,6 +56,9 @@ ciSignature::ciSignature(ciKlass* accessing_klass, const constantPoolHandle& cpo } else { type = ciType::make(ss.type()); } + + assert(type == type->unwrap(), "signature type should not be wrapped"); + if (ss.at_return_type()) { // don't include return type in size calculation _return_type = type; diff --git a/src/hotspot/share/ci/ciSignature.hpp b/src/hotspot/share/ci/ciSignature.hpp index e0e4c322e89..ec57f2bc8e8 100644 --- a/src/hotspot/share/ci/ciSignature.hpp +++ b/src/hotspot/share/ci/ciSignature.hpp @@ -57,7 +57,11 @@ public: ciKlass* accessing_klass() const { return _accessing_klass; } ciType* return_type() const { return _return_type; } - ciType* type_at(int index) const { return _types.at(index)->unwrap(); } + ciType* type_at(int index) const { + ciType* type = _types.at(index); + assert(type == type->unwrap(), "signature type should not be wrapped"); + return _types.at(index)->unwrap(); + } int size() const { return _size; } int count() const { return _types.length(); } diff --git a/src/hotspot/share/ci/ciTypeFlow.cpp b/src/hotspot/share/ci/ciTypeFlow.cpp index 073e2788ba2..a045623ba41 100644 --- a/src/hotspot/share/ci/ciTypeFlow.cpp +++ b/src/hotspot/share/ci/ciTypeFlow.cpp @@ -576,11 +576,9 @@ void ciTypeFlow::StateVector::push_translate(ciType* type) { } } -// ------------------------------------------------------------------ -// ciTypeFlow::StateVector::do_aload -void ciTypeFlow::StateVector::do_aload(ciBytecodeStream* str) { +void ciTypeFlow::StateVector::do_aaload(ciBytecodeStream* str) { pop_int(); - ciArrayKlass* array_klass = pop_objOrFlatArray(); + ciObjArrayKlass* array_klass = pop_objArray(); if (array_klass == nullptr) { // Did aload on a null reference; push a null and ignore the exception. // This instruction will never continue normally. All we have to do @@ -955,13 +953,13 @@ bool ciTypeFlow::StateVector::apply_one_bytecode(ciBytecodeStream* str) { } switch(str->cur_bc()) { - case Bytecodes::_aaload: do_aload(str); break; + case Bytecodes::_aaload: do_aaload(str); break; case Bytecodes::_aastore: { pop_object(); pop_int(); - pop_objOrFlatArray(); + pop_objArray(); break; } case Bytecodes::_aconst_null: @@ -983,7 +981,7 @@ bool ciTypeFlow::StateVector::apply_one_bytecode(ciBytecodeStream* str) { if (!will_link) { trap(str, element_klass, str->get_klass_index()); } else { - push_object(ciArrayKlass::make(element_klass)); + push_object(ciObjArrayKlass::make(element_klass,/* refined_type = */ false)); } break; } @@ -3225,7 +3223,7 @@ void ciTypeFlow::record_failure(const char* reason) { } ciType* ciTypeFlow::mark_as_early_larval(ciType* type) { - // Wrap the type to carry the information that it is null-free + // Wrap the type to carry the information that it is "early larval" return env()->make_early_larval_wrapper(type); } diff --git a/src/hotspot/share/ci/ciTypeFlow.hpp b/src/hotspot/share/ci/ciTypeFlow.hpp index 6df8db4ca74..bab7100e908 100644 --- a/src/hotspot/share/ci/ciTypeFlow.hpp +++ b/src/hotspot/share/ci/ciTypeFlow.hpp @@ -334,14 +334,15 @@ public: type_at_tos()->is_array_klass(), "must be array type"); pop(); } - // pop_objOrFlatArray and pop_typeArray narrow the tos to ciObjArrayKlass, - // ciFlatArrayKlass or ciTypeArrayKlass (resp.). In the rare case that an explicit - // null is popped from the stack, we return null. Caller beware. - ciArrayKlass* pop_objOrFlatArray() { + // pop_objArray and pop_typeArray narrow the tos to ciObjArrayKlass + // or ciTypeArrayKlass (resp.). In the rare case that an explicit + // null is popped from the stack, we return null. Caller beware. + ciObjArrayKlass* pop_objArray() { ciType* array = pop_value(); - if (array == null_type()) return nullptr; - assert(array->is_obj_array_klass(), "must be an object array type"); - return array->as_array_klass(); + if (array == null_type()) { + return nullptr; + } + return array->as_obj_array_klass(); } ciTypeArrayKlass* pop_typeArray() { ciType* array = pop_value(); @@ -355,7 +356,7 @@ public: void do_null_assert(ciKlass* unloaded_klass); // Helper convenience routines. - void do_aload(ciBytecodeStream* str); + void do_aaload(ciBytecodeStream* str); void do_checkcast(ciBytecodeStream* str); void do_getfield(ciBytecodeStream* str); void do_getstatic(ciBytecodeStream* str); diff --git a/src/hotspot/share/code/scopeDesc.hpp b/src/hotspot/share/code/scopeDesc.hpp index c7ae35000b2..25bd333f620 100644 --- a/src/hotspot/share/code/scopeDesc.hpp +++ b/src/hotspot/share/code/scopeDesc.hpp @@ -110,7 +110,8 @@ class ScopeDesc : public ResourceObj { bool _has_ea_local_in_scope; // One or more NoEscape or ArgEscape objects exist in // any of the scopes at compiled pc. bool _arg_escape; // Compiled Java call in youngest scope passes ArgEscape - // Decoding offsets + + // Decoding offsets int _decode_offset; int _sender_decode_offset; int _locals_decode_offset; diff --git a/src/hotspot/share/code/vtableStubs.cpp b/src/hotspot/share/code/vtableStubs.cpp index a8dc2ad7ea0..c2b57dc80b1 100644 --- a/src/hotspot/share/code/vtableStubs.cpp +++ b/src/hotspot/share/code/vtableStubs.cpp @@ -253,6 +253,7 @@ inline uint VtableStubs::hash(bool is_vtable_stub, int vtable_index, bool caller // Assumption: receiver_location < 4 in most cases. int hash = ((vtable_index << 2) ^ VtableStub::receiver_location()->value()) + vtable_index; if (caller_is_c1) { + // We have different vtable stubs for C1 and C2. We therefore make sure to get different hashes. hash = 7 - hash; } return (is_vtable_stub ? ~hash : hash) & mask; diff --git a/src/hotspot/share/compiler/methodMatcher.cpp b/src/hotspot/share/compiler/methodMatcher.cpp index 57c77b6516a..1f401d2e409 100644 --- a/src/hotspot/share/compiler/methodMatcher.cpp +++ b/src/hotspot/share/compiler/methodMatcher.cpp @@ -304,7 +304,7 @@ void MethodMatcher::parse_method_pattern(char*& line, const char*& error_msg, Me (strchr(method_name, JVM_SIGNATURE_ENDSPECIAL) != nullptr)) { if (!vmSymbols::object_initializer_name()->equals(method_name) && !vmSymbols::class_initializer_name()->equals(method_name)) { - error_msg = "Chars '<' and '>' only allowed in , "; + error_msg = "Chars '<' and '>' only allowed in and "; return; } } diff --git a/src/hotspot/share/gc/g1/g1ConcurrentMark.hpp b/src/hotspot/share/gc/g1/g1ConcurrentMark.hpp index f1f84bf246e..925d250ab0a 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentMark.hpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentMark.hpp @@ -144,7 +144,7 @@ private: Atomic* _buckets; char _pad0[DEFAULT_PADDING_SIZE]; Atomic _size; - char _pad4[DEFAULT_PADDING_SIZE - sizeof(size_t)]; + char _pad4[DEFAULT_PADDING_SIZE - sizeof(_size)]; size_t bucket_size(size_t bucket) { return (bucket == 0) ? @@ -211,10 +211,10 @@ private: char _pad0[DEFAULT_PADDING_SIZE]; Atomic _free_list; // Linked list of free chunks that can be allocated by users. - char _pad1[DEFAULT_PADDING_SIZE - sizeof(TaskQueueEntryChunk*)]; + char _pad1[DEFAULT_PADDING_SIZE - sizeof(_free_list)]; Atomic _chunk_list; // List of chunks currently containing data. Atomic _chunks_in_chunk_list; - char _pad2[DEFAULT_PADDING_SIZE - sizeof(TaskQueueEntryChunk*) - sizeof(_chunks_in_chunk_list)]; + char _pad2[DEFAULT_PADDING_SIZE - sizeof(_chunk_list) - sizeof(_chunks_in_chunk_list)]; // Atomically add the given chunk to the list. void add_chunk_to_list(Atomic* list, TaskQueueEntryChunk* elem); diff --git a/src/hotspot/share/gc/g1/g1ConcurrentRefineThread.hpp b/src/hotspot/share/gc/g1/g1ConcurrentRefineThread.hpp index 2ecbdc668eb..93f64fc80ed 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentRefineThread.hpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentRefineThread.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -40,8 +40,6 @@ class G1ConcurrentRefineThread: public ConcurrentGCThread { Monitor _notifier; bool _requested_active; - uint _worker_id; - G1ConcurrentRefine* _cr; NONCOPYABLE(G1ConcurrentRefineThread); diff --git a/src/hotspot/share/gc/g1/g1FullGCAdjustTask.cpp b/src/hotspot/share/gc/g1/g1FullGCAdjustTask.cpp index c02b028112b..6ae2182847b 100644 --- a/src/hotspot/share/gc/g1/g1FullGCAdjustTask.cpp +++ b/src/hotspot/share/gc/g1/g1FullGCAdjustTask.cpp @@ -52,12 +52,11 @@ public: class G1AdjustRegionClosure : public G1HeapRegionClosure { G1FullCollector* _collector; G1CMBitMap* _bitmap; - uint _worker_id; - public: - G1AdjustRegionClosure(G1FullCollector* collector, uint worker_id) : + +public: + G1AdjustRegionClosure(G1FullCollector* collector) : _collector(collector), - _bitmap(collector->mark_bitmap()), - _worker_id(worker_id) { } + _bitmap(collector->mark_bitmap()) { } bool do_heap_region(G1HeapRegion* r) { G1AdjustClosure cl(_collector); @@ -103,7 +102,7 @@ void G1FullGCAdjustTask::work(uint worker_id) { _root_processor.process_all_roots(&_adjust, &adjust_cld, &adjust_code); // Now adjust pointers region by region - G1AdjustRegionClosure blk(collector(), worker_id); + G1AdjustRegionClosure blk(collector()); G1CollectedHeap::heap()->heap_region_par_iterate_from_worker_offset(&blk, &_hrclaimer, worker_id); log_task("Adjust task", worker_id, start); } diff --git a/src/hotspot/share/gc/g1/g1FullGCMarker.cpp b/src/hotspot/share/gc/g1/g1FullGCMarker.cpp index cd1d69e96fd..6e0ef06da7d 100644 --- a/src/hotspot/share/gc/g1/g1FullGCMarker.cpp +++ b/src/hotspot/share/gc/g1/g1FullGCMarker.cpp @@ -40,7 +40,7 @@ G1FullGCMarker::G1FullGCMarker(G1FullCollector* collector, _bitmap(collector->mark_bitmap()), _task_queue(), _partial_array_splitter(collector->partial_array_state_manager(), collector->workers()), - _mark_closure(worker_id, this, ClassLoaderData::_claim_stw_fullgc_mark, G1CollectedHeap::heap()->ref_processor_stw()), + _mark_closure(this, ClassLoaderData::_claim_stw_fullgc_mark, G1CollectedHeap::heap()->ref_processor_stw()), _stack_closure(this), _cld_closure(mark_closure(), ClassLoaderData::_claim_stw_fullgc_mark), _mark_stats_cache(mark_stats, G1RegionMarkStatsCache::RegionMarkStatsCacheSize) { diff --git a/src/hotspot/share/gc/g1/g1FullGCOopClosures.hpp b/src/hotspot/share/gc/g1/g1FullGCOopClosures.hpp index 08ed5f982e1..6b54fffdf3c 100644 --- a/src/hotspot/share/gc/g1/g1FullGCOopClosures.hpp +++ b/src/hotspot/share/gc/g1/g1FullGCOopClosures.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -60,13 +60,11 @@ public: class G1MarkAndPushClosure : public ClaimMetadataVisitingOopIterateClosure { G1FullGCMarker* _marker; - uint _worker_id; public: - G1MarkAndPushClosure(uint worker_id, G1FullGCMarker* marker, int claim, ReferenceDiscoverer* ref) : + G1MarkAndPushClosure(G1FullGCMarker* marker, int claim, ReferenceDiscoverer* ref) : ClaimMetadataVisitingOopIterateClosure(claim, ref), - _marker(marker), - _worker_id(worker_id) { } + _marker(marker) { } template inline void do_oop_work(T* p); virtual void do_oop(oop* p); diff --git a/src/hotspot/share/gc/g1/g1GCParPhaseTimesTracker.hpp b/src/hotspot/share/gc/g1/g1GCParPhaseTimesTracker.hpp index af626e99fd4..da9a11dd3c7 100644 --- a/src/hotspot/share/gc/g1/g1GCParPhaseTimesTracker.hpp +++ b/src/hotspot/share/gc/g1/g1GCParPhaseTimesTracker.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2020, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -49,7 +49,7 @@ class G1EvacPhaseTimesTracker : public G1GCParPhaseTimesTracker { G1EvacPhaseWithTrimTimeTracker _trim_tracker; public: - G1EvacPhaseTimesTracker(G1GCPhaseTimes* phase_times, G1ParScanThreadState* pss, G1GCPhaseTimes::GCParPhases phase, uint worker_id); + G1EvacPhaseTimesTracker(G1GCPhaseTimes* phase_times, G1ParScanThreadState* par_scan_state, G1GCPhaseTimes::GCParPhases phase); virtual ~G1EvacPhaseTimesTracker(); }; diff --git a/src/hotspot/share/gc/g1/g1GCPhaseTimes.cpp b/src/hotspot/share/gc/g1/g1GCPhaseTimes.cpp index e5bf8137811..bf37d1b6b88 100644 --- a/src/hotspot/share/gc/g1/g1GCPhaseTimes.cpp +++ b/src/hotspot/share/gc/g1/g1GCPhaseTimes.cpp @@ -581,14 +581,14 @@ const char* G1GCPhaseTimes::phase_name(GCParPhases phase) { return phase_times->_gc_par_phases[phase]->short_name(); } -G1EvacPhaseWithTrimTimeTracker::G1EvacPhaseWithTrimTimeTracker(G1ParScanThreadState* pss, Tickspan& total_time, Tickspan& trim_time) : - _pss(pss), +G1EvacPhaseWithTrimTimeTracker::G1EvacPhaseWithTrimTimeTracker(G1ParScanThreadState* par_scan_state, Tickspan& total_time, Tickspan& trim_time) : + _par_scan_state(par_scan_state), _start(Ticks::now()), _total_time(total_time), _trim_time(trim_time), _stopped(false) { - assert(_pss->trim_ticks().value() == 0, "Possibly remaining trim ticks left over from previous use"); + assert(_par_scan_state->trim_ticks().value() == 0, "Possibly remaining trim ticks left over from previous use"); } G1EvacPhaseWithTrimTimeTracker::~G1EvacPhaseWithTrimTimeTracker() { @@ -599,9 +599,9 @@ G1EvacPhaseWithTrimTimeTracker::~G1EvacPhaseWithTrimTimeTracker() { void G1EvacPhaseWithTrimTimeTracker::stop() { assert(!_stopped, "Should only be called once"); - _total_time += (Ticks::now() - _start) - _pss->trim_ticks(); - _trim_time += _pss->trim_ticks(); - _pss->reset_trim_ticks(); + _total_time += (Ticks::now() - _start) - _par_scan_state->trim_ticks(); + _trim_time += _par_scan_state->trim_ticks(); + _par_scan_state->reset_trim_ticks(); _stopped = true; } @@ -625,9 +625,8 @@ G1GCParPhaseTimesTracker::~G1GCParPhaseTimesTracker() { G1EvacPhaseTimesTracker::G1EvacPhaseTimesTracker(G1GCPhaseTimes* phase_times, G1ParScanThreadState* pss, - G1GCPhaseTimes::GCParPhases phase, - uint worker_id) : - G1GCParPhaseTimesTracker(phase_times, phase, worker_id), + G1GCPhaseTimes::GCParPhases phase) : + G1GCParPhaseTimesTracker(phase_times, phase, pss->worker_id()), _total_time(), _trim_time(), _trim_tracker(pss, _total_time, _trim_time) { diff --git a/src/hotspot/share/gc/g1/g1GCPhaseTimes.hpp b/src/hotspot/share/gc/g1/g1GCPhaseTimes.hpp index 078a819986c..81e9a8b9648 100644 --- a/src/hotspot/share/gc/g1/g1GCPhaseTimes.hpp +++ b/src/hotspot/share/gc/g1/g1GCPhaseTimes.hpp @@ -413,7 +413,7 @@ class G1GCPhaseTimes : public CHeapObj { }; class G1EvacPhaseWithTrimTimeTracker : public StackObj { - G1ParScanThreadState* _pss; + G1ParScanThreadState* _par_scan_state; Ticks _start; Tickspan& _total_time; @@ -421,7 +421,7 @@ class G1EvacPhaseWithTrimTimeTracker : public StackObj { bool _stopped; public: - G1EvacPhaseWithTrimTimeTracker(G1ParScanThreadState* pss, Tickspan& total_time, Tickspan& trim_time); + G1EvacPhaseWithTrimTimeTracker(G1ParScanThreadState* par_scan_state, Tickspan& total_time, Tickspan& trim_time); ~G1EvacPhaseWithTrimTimeTracker(); void stop(); diff --git a/src/hotspot/share/gc/g1/g1NMethodClosure.cpp b/src/hotspot/share/gc/g1/g1NMethodClosure.cpp index d7dcbeb87fb..340ab4eed6d 100644 --- a/src/hotspot/share/gc/g1/g1NMethodClosure.cpp +++ b/src/hotspot/share/gc/g1/g1NMethodClosure.cpp @@ -28,11 +28,17 @@ #include "gc/g1/g1HeapRegion.hpp" #include "gc/g1/g1HeapRegionRemSet.inline.hpp" #include "gc/g1/g1NMethodClosure.hpp" +#include "gc/g1/g1ParScanThreadState.inline.hpp" #include "gc/shared/barrierSetNMethod.hpp" #include "oops/access.inline.hpp" #include "oops/compressedOops.inline.hpp" #include "oops/oop.inline.hpp" +G1NMethodClosure::G1NMethodClosure(OopClosure* oc, bool strong, G1ParScanThreadState* par_scan_state) : + _oc(oc, par_scan_state), + _marking_oc(par_scan_state->worker_id()), + _strong(strong) { } + template void G1NMethodClosure::HeapRegionGatheringOopClosure::do_oop_work(T* p) { T old_oop_or_narrowoop = RawAccess<>::oop_load(p); @@ -65,17 +71,17 @@ void G1NMethodClosure::HeapRegionGatheringOopClosure::do_oop_work(T* p) { } } -G1NMethodClosure::HeapRegionGatheringOopClosure::HeapRegionGatheringOopClosure(OopClosure* oc, G1ParScanThreadState* pss) : +G1NMethodClosure::HeapRegionGatheringOopClosure::HeapRegionGatheringOopClosure(OopClosure* oc, G1ParScanThreadState* par_scan_state) : _g1h(G1CollectedHeap::heap()), _work(oc), - _pss(pss), + _par_scan_state(par_scan_state), _nm(nullptr), _affected_regions(5) { } void G1NMethodClosure::HeapRegionGatheringOopClosure::add_to_remsets() { while (!_affected_regions.is_empty()) { - _pss->remember_nmethod_into_region(_affected_regions.pop(), _nm); + _par_scan_state->remember_nmethod_into_region(_affected_regions.pop(), _nm); } } diff --git a/src/hotspot/share/gc/g1/g1NMethodClosure.hpp b/src/hotspot/share/gc/g1/g1NMethodClosure.hpp index 95d0ee1942d..2dcf02a32b7 100644 --- a/src/hotspot/share/gc/g1/g1NMethodClosure.hpp +++ b/src/hotspot/share/gc/g1/g1NMethodClosure.hpp @@ -38,7 +38,7 @@ class G1NMethodClosure : public NMethodClosure { class HeapRegionGatheringOopClosure : public OopClosure { G1CollectedHeap* _g1h; OopClosure* _work; - G1ParScanThreadState* _pss; + G1ParScanThreadState* _par_scan_state; nmethod* _nm; GrowableArrayCHeap _affected_regions; @@ -47,7 +47,7 @@ class G1NMethodClosure : public NMethodClosure { void do_oop_work(T* p); public: - HeapRegionGatheringOopClosure(OopClosure* oc, G1ParScanThreadState* pss); + HeapRegionGatheringOopClosure(OopClosure* oc, G1ParScanThreadState* par_scan_state); ~HeapRegionGatheringOopClosure() = default; void do_oop(oop* o); @@ -81,8 +81,7 @@ class G1NMethodClosure : public NMethodClosure { bool _strong; public: - G1NMethodClosure(uint worker_id, OopClosure* oc, bool strong, G1ParScanThreadState* pss) : - _oc(oc, pss), _marking_oc(worker_id), _strong(strong) { } + G1NMethodClosure(OopClosure* oc, bool strong, G1ParScanThreadState* par_scan_state); void do_evacuation_and_fixup(nmethod* nm); void do_marking(nmethod* nm); diff --git a/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp b/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp index 1636deaa914..3a7ff7adc4f 100644 --- a/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp +++ b/src/hotspot/share/gc/g1/g1ParScanThreadState.cpp @@ -55,20 +55,12 @@ // Explicit NOINLINE to block ATTRIBUTE_FLATTENing. #define MAYBE_INLINE_EVACUATION NOT_DEBUG(inline) DEBUG_ONLY(NOINLINE) -// Good estimate for the initial table size. -static uint initial_nmethod_table_size(G1CollectedHeap* g1h) { - // The +1 is both to consider the retained old region likely to be added, and avoid zero-sized initial tables. - return MIN3(g1h->collection_set()->num_regions(), g1h->max_num_regions() / 2, g1h->num_available_regions()) + 1; -} - G1ParScanThreadState::G1ParScanThreadState(G1CollectedHeap* g1h, - G1ParScanThreadStateSet* per_thread_states, uint worker_id, uint num_workers, G1CollectionSet* collection_set, G1EvacFailureRegions* evac_failure_regions) : _g1h(g1h), - _per_thread_states(per_thread_states), _task_queue(g1h->task_queue(worker_id)), _ct(g1h->refinement_table()), _closures(nullptr), @@ -91,10 +83,7 @@ G1ParScanThreadState::G1ParScanThreadState(G1CollectedHeap* g1h, _max_num_optional_regions(collection_set->num_optional_regions()), _numa(g1h->numa()), _obj_alloc_stat(nullptr), - // The initial size estimate is relatively conservative, assuming that all regions - // in the collection set get evacuated into the same amount of new regions. - _nmethods_to_add(initial_nmethod_table_size(g1h), - MAX2(initial_nmethod_table_size(g1h), _g1h->max_num_regions() / 2)), + _code_root_pairs(32), ALLOCATION_FAILURE_INJECTOR_ONLY(_allocation_failure_inject_counter(0) COMMA) _evacuation_failed_info(), _evac_failure_regions(evac_failure_regions), @@ -141,12 +130,6 @@ size_t G1ParScanThreadState::flush_stats(size_t* surviving_young_words, uint num } G1ParScanThreadState::~G1ParScanThreadState() { - auto delete_all = [&] (uint region, G1NmethodSet* nmethods) -> bool { - delete nmethods; - return true; - }; - _nmethods_to_add.iterate(delete_all); - delete _plab_allocator; delete _closures; FREE_C_HEAP_ARRAY(_surviving_young_words_base); @@ -597,7 +580,6 @@ G1ParScanThreadState* G1ParScanThreadStateSet::state_for_worker(uint worker_id) if (_states[worker_id] == nullptr) { _states[worker_id] = new G1ParScanThreadState(_g1h, - this, worker_id, _num_workers, _collection_set, @@ -649,51 +631,18 @@ void G1ParScanThreadStateSet::destroy_worker_states() { } } -void G1ParScanThreadStateSet::update_nmethod_regions_to_add(G1NmethodsToAdd* nmethods) { - if (nmethods->number_of_entries() == 0) { - return; - } - - // Take the key set, look which are not yet in the global set, and update the necessary ones. - ResourceMark rm; - GrowableArray regions_to_add = GrowableArray(nmethods->table_size()); - - nmethods->iterate_all([&] (uint& region, void*) { - if (_has_nmethods_to_add.par_set_bit(region, memory_order_relaxed)) { - regions_to_add.push(region); - } - }); - - uint num_regions_to_add = (uint)regions_to_add.length(); - - if (num_regions_to_add == 0) { - return; - } - - uint first_index = _num_nmethod_regions_to_add.fetch_then_add(num_regions_to_add, memory_order_relaxed); - guarantee(first_index + num_regions_to_add <= _g1h->max_num_regions(), "must be"); - - memcpy(&_nmethod_regions_to_add[first_index], regions_to_add.adr_at(0), num_regions_to_add * sizeof(uint)); -} - -void G1ParScanThreadStateSet::par_iterate_nmethod_regions_to_add(G1HeapRegionClosure* cl, - G1HeapRegionClaimer* claimer, - uint worker_id) { - _g1h->par_iterate_regions_array(cl, claimer, _nmethod_regions_to_add, num_nmethod_regions_to_add(), worker_id); -} - void G1ParScanThreadStateSet::record_unused_optional_region(G1HeapRegion* hr) { - for (uint worker_index = 0; worker_index < _num_workers; ++worker_index) { - G1ParScanThreadState* pss = _states[worker_index]; + for (uint worker_id = 0; worker_id < _num_workers; ++worker_id) { + G1ParScanThreadState* pss = _states[worker_id]; assert(pss != nullptr, "must be initialized"); size_t used_memory = pss->oops_into_optional_region(hr)->used_memory(); - _g1h->phase_times()->record_or_add_thread_work_item(G1GCPhaseTimes::OptScanHR, worker_index, used_memory, G1GCPhaseTimes::ScanHRUsedMemory); + _g1h->phase_times()->record_or_add_thread_work_item(G1GCPhaseTimes::OptScanHR, worker_id, used_memory, G1GCPhaseTimes::ScanHRUsedMemory); } } -void G1ParScanThreadState::record_evacuation_failed_region(G1HeapRegion* r, uint worker_id, bool cause_pinned) { - if (_evac_failure_regions->record(worker_id, r->hrm_index(), cause_pinned)) { +void G1ParScanThreadState::record_evacuation_failed_region(G1HeapRegion* r, bool cause_pinned) { + if (_evac_failure_regions->record(worker_id(), r->hrm_index(), cause_pinned)) { G1HeapRegionPrinter::evac_failure(r); } } @@ -707,7 +656,7 @@ oop G1ParScanThreadState::handle_evacuation_failure_par(oop old, markWord m, Kla // Forward-to-self succeeded. We are the "owner" of the object. G1HeapRegion* r = _g1h->heap_region_containing(old); - record_evacuation_failed_region(r, _worker_id, cause_pinned); + record_evacuation_failed_region(r, cause_pinned); // Mark the failing object in the marking bitmap and later use the bitmap to handle // evacuation failure recovery. @@ -737,10 +686,6 @@ oop G1ParScanThreadState::handle_evacuation_failure_par(oop old, markWord m, Kla } } -void G1ParScanThreadState::update_nmethod_regions_to_add() { - _per_thread_states->update_nmethod_regions_to_add(&_nmethods_to_add); -} - void G1ParScanThreadState::initialize_numa_stats() { if (_numa->is_enabled()) { LogTarget(Info, gc, heap, numa) lt; @@ -785,10 +730,7 @@ G1ParScanThreadStateSet::G1ParScanThreadStateSet(G1CollectedHeap* g1h, _surviving_young_words_total(NEW_C_HEAP_ARRAY(size_t, collection_set->num_young_regions() + 1, mtGC)), _num_workers(num_workers), _flushed(false), - _evac_failure_regions(evac_failure_regions), - _has_nmethods_to_add(g1h->max_num_regions(), mtGC), - _num_nmethod_regions_to_add(0), - _nmethod_regions_to_add(NEW_C_HEAP_ARRAY(uint, g1h->max_num_regions(), mtGC)) // Conservative length estimation. + _evac_failure_regions(evac_failure_regions) { for (uint i = 0; i < num_workers; ++i) { _states[i] = nullptr; @@ -800,7 +742,6 @@ G1ParScanThreadStateSet::~G1ParScanThreadStateSet() { for (uint i = 0; i < _num_workers; i++) { assert(_states[i] == nullptr, "must be"); } - FREE_C_HEAP_ARRAY(_nmethod_regions_to_add); FREE_C_HEAP_ARRAY(_states); FREE_C_HEAP_ARRAY(_surviving_young_words_total); } diff --git a/src/hotspot/share/gc/g1/g1ParScanThreadState.hpp b/src/hotspot/share/gc/g1/g1ParScanThreadState.hpp index 4116874f947..65859c5ade7 100644 --- a/src/hotspot/share/gc/g1/g1ParScanThreadState.hpp +++ b/src/hotspot/share/gc/g1/g1ParScanThreadState.hpp @@ -37,9 +37,7 @@ #include "gc/shared/taskqueue.hpp" #include "memory/allocation.hpp" #include "oops/oop.hpp" -#include "runtime/atomic.hpp" #include "utilities/growableArray.hpp" -#include "utilities/resizableHashTable.hpp" #include "utilities/ticks.hpp" class G1CardTable; @@ -52,11 +50,13 @@ class G1PLABAllocator; class G1HeapRegion; class outputStream; -typedef GrowableArrayCHeap G1NmethodSet; -typedef ResizeableHashTable G1NmethodsToAdd; +// A code root pair gathered during code root scanning. +struct G1CodeRootPair { + uint _region_idx; + nmethod* _nmethod; +}; class G1ParScanThreadState : public CHeapObj { G1CollectedHeap* _g1h; - G1ParScanThreadStateSet* _per_thread_states; G1ScannerTasksQueue* _task_queue; G1CardTable* _ct; G1EvacuationRootClosures* _closures; @@ -103,8 +103,8 @@ class G1ParScanThreadState : public CHeapObj { // transferred when flushed. size_t* _obj_alloc_stat; - // The nmethods that were found during code root scan that need to be redistributed. - G1NmethodsToAdd _nmethods_to_add; + // Code root pairs to add after evacuation. + GrowableArrayCHeap _code_root_pairs; // Per-thread evacuation failure data structures. ALLOCATION_FAILURE_INJECTOR_ONLY(size_t _allocation_failure_inject_counter;) @@ -124,7 +124,6 @@ class G1ParScanThreadState : public CHeapObj { public: G1ParScanThreadState(G1CollectedHeap* g1h, - G1ParScanThreadStateSet* per_thread_states, uint worker_id, uint num_workers, G1CollectionSet* collection_set, @@ -251,19 +250,13 @@ public: Tickspan trim_ticks() const; void reset_trim_ticks(); - void record_evacuation_failed_region(G1HeapRegion* r, uint worker_id, bool cause_pinned); + void record_evacuation_failed_region(G1HeapRegion* r, bool cause_pinned); // An attempt to evacuate "obj" has failed; take necessary steps. oop handle_evacuation_failure_par(oop obj, markWord m, Klass* klass, G1HeapRegionAttr attr, size_t word_sz, bool cause_pinned); inline void remember_nmethod_into_region(G1HeapRegion* r, nmethod* nm); - // Updates the global set of regions that need updates to the code root set - // later with the ones gathered so far. - void update_nmethod_regions_to_add(); - inline size_t num_nmethods(uint index) const; - // Iterate nmethods stored for the given region index. - template - inline void iterate_nmethods(uint index, Function fn); + const GrowableArrayCHeap& code_root_pairs() const { return _code_root_pairs; } template inline void remember_root_into_optional_region(T* p); @@ -282,10 +275,6 @@ class G1ParScanThreadStateSet : public StackObj { bool _flushed; G1EvacFailureRegions* _evac_failure_regions; - CHeapBitMap _has_nmethods_to_add; - Atomic _num_nmethod_regions_to_add; - uint* _nmethod_regions_to_add; - public: G1ParScanThreadStateSet(G1CollectedHeap* g1h, uint num_workers, @@ -296,13 +285,6 @@ class G1ParScanThreadStateSet : public StackObj { void flush_stats(); void destroy_worker_states(); - // Updates the region set that has code root updates with the regions in the given set. - void update_nmethod_regions_to_add(G1NmethodsToAdd* nmethods); - void par_iterate_nmethod_regions_to_add(G1HeapRegionClosure* cl, - G1HeapRegionClaimer* claimer, - uint worker_id); - uint num_nmethod_regions_to_add() const { return _num_nmethod_regions_to_add.load_relaxed(); } - void record_unused_optional_region(G1HeapRegion* hr); #if TASKQUEUE_STATS void print_partial_array_task_stats(); diff --git a/src/hotspot/share/gc/g1/g1ParScanThreadState.inline.hpp b/src/hotspot/share/gc/g1/g1ParScanThreadState.inline.hpp index c42f5f4c4f6..6d6a18f97a7 100644 --- a/src/hotspot/share/gc/g1/g1ParScanThreadState.inline.hpp +++ b/src/hotspot/share/gc/g1/g1ParScanThreadState.inline.hpp @@ -71,34 +71,7 @@ inline void G1ParScanThreadState::reset_trim_ticks() { } inline void G1ParScanThreadState::remember_nmethod_into_region(G1HeapRegion* r, nmethod* nm) { - uint index = r->hrm_index(); - - G1NmethodSet** nmethods = _nmethods_to_add.get(index); - if (nmethods != nullptr) { - (*nmethods)->push(nm); - } else { - G1NmethodSet* new_set = new G1NmethodSet(3); - new_set->push(nm); - bool put_result = _nmethods_to_add.put(index, new_set); - assert(put_result, "must be"); - _nmethods_to_add.maybe_grow(3 /* load_factor */); - } -} - -inline size_t G1ParScanThreadState::num_nmethods(uint region) const { - G1NmethodSet** nmethods = _nmethods_to_add.get(region); - return nmethods != nullptr ? (size_t)(*nmethods)->length() : 0; -} - -template -inline void G1ParScanThreadState::iterate_nmethods(uint index, Function fn) { - G1NmethodSet** nmethods = _nmethods_to_add.get(index); - if (nmethods == nullptr) { - return; - } - for (nmethod* nm : **nmethods) { - fn(nm); - } + _code_root_pairs.push(G1CodeRootPair{r->hrm_index(), nm}); } template diff --git a/src/hotspot/share/gc/g1/g1RemSet.cpp b/src/hotspot/share/gc/g1/g1RemSet.cpp index 5f58ca2e053..149f1da1a8b 100644 --- a/src/hotspot/share/gc/g1/g1RemSet.cpp +++ b/src/hotspot/share/gc/g1/g1RemSet.cpp @@ -398,13 +398,9 @@ class G1ScanHRForRegionClosure : public G1HeapRegionClosure { G1CollectedHeap* _g1h; G1CardTable* _ct; - G1ParScanThreadState* _pss; - G1RemSetScanState* _scan_state; - G1GCPhaseTimes::GCParPhases _phase; - - uint _worker_id; + G1ParScanThreadState* _pss; size_t _cards_pending; size_t _cards_empty; @@ -493,15 +489,11 @@ class G1ScanHRForRegionClosure : public G1HeapRegionClosure { public: G1ScanHRForRegionClosure(G1RemSetScanState* scan_state, G1ParScanThreadState* pss, - uint worker_id, - G1GCPhaseTimes::GCParPhases phase, bool remember_already_scanned_cards) : _g1h(G1CollectedHeap::heap()), _ct(_g1h->card_table()), - _pss(pss), _scan_state(scan_state), - _phase(phase), - _worker_id(worker_id), + _pss(pss), _cards_pending(0), _cards_empty(0), _cards_scanned(0), @@ -540,12 +532,13 @@ public: }; void G1RemSet::scan_heap_roots(G1ParScanThreadState* pss, - uint worker_id, G1GCPhaseTimes::GCParPhases scan_phase, G1GCPhaseTimes::GCParPhases objcopy_phase, bool remember_already_scanned_cards) { + uint worker_id = pss->worker_id(); + EventGCPhaseParallel event; - G1ScanHRForRegionClosure cl(_scan_state, pss, worker_id, scan_phase, remember_already_scanned_cards); + G1ScanHRForRegionClosure cl(_scan_state, pss, remember_already_scanned_cards); _scan_state->iterate_dirty_regions_from(&cl, worker_id); event.commit(GCId::current(), worker_id, G1GCPhaseTimes::phase_name(scan_phase)); @@ -587,19 +580,12 @@ public: // increment to fix up non-card related roots. class G1ScanCodeRootsClosure : public G1HeapRegionClosure { G1ParScanThreadState* _pss; - G1RemSetScanState* _scan_state; - - uint _worker_id; size_t _code_roots_scanned; public: - G1ScanCodeRootsClosure(G1RemSetScanState* scan_state, - G1ParScanThreadState* pss, - uint worker_id) : + G1ScanCodeRootsClosure(G1ParScanThreadState* pss) : _pss(pss), - _scan_state(scan_state), - _worker_id(worker_id), _code_roots_scanned(0) { } bool do_heap_region(G1HeapRegion* r) { @@ -614,7 +600,6 @@ public: }; void G1RemSet::scan_collection_set_code_roots(G1ParScanThreadState* pss, - uint worker_id, G1GCPhaseTimes::GCParPhases coderoots_phase, G1GCPhaseTimes::GCParPhases objcopy_phase) { EventGCPhaseParallel event; @@ -622,16 +607,15 @@ void G1RemSet::scan_collection_set_code_roots(G1ParScanThreadState* pss, Tickspan code_root_trim_partially_time; G1GCPhaseTimes* p = _g1h->phase_times(); + uint worker_id = pss->worker_id(); { G1EvacPhaseWithTrimTimeTracker timer(pss, code_root_scan_time, code_root_trim_partially_time); - G1ScanCodeRootsClosure cl(_scan_state, pss, worker_id); + G1ScanCodeRootsClosure cl(pss); // Code roots work distribution occurs inside the iteration method. So scan all collection // set regions for all threads. _g1h->collection_set_iterate_increment_from(&cl, worker_id); - pss->update_nmethod_regions_to_add(); - p->record_or_add_thread_work_item(coderoots_phase, worker_id, cl.code_roots_scanned(), G1GCPhaseTimes::CodeRootsScannedNMethods); } @@ -644,10 +628,6 @@ void G1RemSet::scan_collection_set_code_roots(G1ParScanThreadState* pss, class G1ScanOptionalRemSetRootsClosure : public G1HeapRegionClosure { G1ParScanThreadState* _pss; - uint _worker_id; - - G1GCPhaseTimes::GCParPhases _scan_phase; - size_t _opt_roots_scanned; size_t _opt_refs_scanned; @@ -663,12 +643,8 @@ class G1ScanOptionalRemSetRootsClosure : public G1HeapRegionClosure { } public: - G1ScanOptionalRemSetRootsClosure(G1ParScanThreadState* pss, - uint worker_id, - G1GCPhaseTimes::GCParPhases scan_phase) : + G1ScanOptionalRemSetRootsClosure(G1ParScanThreadState* pss) : _pss(pss), - _worker_id(worker_id), - _scan_phase(scan_phase), _opt_roots_scanned(0), _opt_refs_scanned(0), _opt_refs_memory_used(0) { } @@ -686,7 +662,6 @@ public: }; void G1RemSet::scan_collection_set_optional_roots(G1ParScanThreadState* pss, - uint worker_id, G1GCPhaseTimes::GCParPhases scan_phase, G1GCPhaseTimes::GCParPhases objcopy_phase) { assert(scan_phase == G1GCPhaseTimes::OptScanHR, "must be"); @@ -699,7 +674,8 @@ void G1RemSet::scan_collection_set_optional_roots(G1ParScanThreadState* pss, G1GCPhaseTimes* p = _g1h->phase_times(); - G1ScanOptionalRemSetRootsClosure cl(pss, worker_id, scan_phase); + G1ScanOptionalRemSetRootsClosure cl(pss); + uint worker_id = pss->worker_id(); // The individual references for the optional remembered set are per-worker, so every worker // always need to scan all regions (no claimer). _g1h->collection_set_iterate_increment_from(&cl, worker_id); diff --git a/src/hotspot/share/gc/g1/g1RemSet.hpp b/src/hotspot/share/gc/g1/g1RemSet.hpp index 8b2353cdbb3..4893e0839d0 100644 --- a/src/hotspot/share/gc/g1/g1RemSet.hpp +++ b/src/hotspot/share/gc/g1/g1RemSet.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -80,7 +80,6 @@ public: // Scan all cards in the non-collection set regions that potentially contain // references into the current whole collection set. void scan_heap_roots(G1ParScanThreadState* pss, - uint worker_id, G1GCPhaseTimes::GCParPhases scan_phase, G1GCPhaseTimes::GCParPhases objcopy_phase, bool remember_already_scanned_cards); @@ -109,12 +108,10 @@ public: // Do work for regions in the current increment of the collection set, scanning // non-card based (heap) roots. void scan_collection_set_code_roots(G1ParScanThreadState* pss, - uint worker_id, G1GCPhaseTimes::GCParPhases coderoots_phase, G1GCPhaseTimes::GCParPhases objcopy_phase); void scan_collection_set_optional_roots(G1ParScanThreadState* pss, - uint worker_id, G1GCPhaseTimes::GCParPhases scan_phase, G1GCPhaseTimes::GCParPhases objcopy_phase); diff --git a/src/hotspot/share/gc/g1/g1RootProcessor.cpp b/src/hotspot/share/gc/g1/g1RootProcessor.cpp index a534eefb428..a8fc55806dd 100644 --- a/src/hotspot/share/gc/g1/g1RootProcessor.cpp +++ b/src/hotspot/share/gc/g1/g1RootProcessor.cpp @@ -52,12 +52,13 @@ G1RootProcessor::G1RootProcessor(G1CollectedHeap* g1h, bool is_parallel) : _threads_claim_token_scope(), _is_parallel(is_parallel) {} -void G1RootProcessor::evacuate_roots(G1ParScanThreadState* pss, uint worker_id) { +void G1RootProcessor::evacuate_roots(G1ParScanThreadState* pss) { G1GCPhaseTimes* phase_times = _g1h->phase_times(); - G1EvacPhaseTimesTracker timer(phase_times, pss, G1GCPhaseTimes::ExtRootScan, worker_id); + G1EvacPhaseTimesTracker timer(phase_times, pss, G1GCPhaseTimes::ExtRootScan); G1EvacuationRootClosures* closures = pss->closures(); + uint worker_id = pss->worker_id(); process_java_roots(closures, phase_times, worker_id); process_vm_roots(closures, phase_times, worker_id); diff --git a/src/hotspot/share/gc/g1/g1RootProcessor.hpp b/src/hotspot/share/gc/g1/g1RootProcessor.hpp index 95267dc3c46..c3e1d0348e0 100644 --- a/src/hotspot/share/gc/g1/g1RootProcessor.hpp +++ b/src/hotspot/share/gc/g1/g1RootProcessor.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -80,7 +80,7 @@ public: // Apply correct closures from pss to the strongly and weakly reachable roots in the system // in a single pass. // Record and report timing measurements for sub phases using worker_id. - void evacuate_roots(G1ParScanThreadState* pss, uint worker_id); + void evacuate_roots(G1ParScanThreadState* pss); // Apply oops, clds and blobs to all strongly reachable roots in the system void process_strong_roots(OopClosure* oops, diff --git a/src/hotspot/share/gc/g1/g1SharedClosures.hpp b/src/hotspot/share/gc/g1/g1SharedClosures.hpp index dc6ff646271..4e1c3c65b6f 100644 --- a/src/hotspot/share/gc/g1/g1SharedClosures.hpp +++ b/src/hotspot/share/gc/g1/g1SharedClosures.hpp @@ -55,7 +55,7 @@ public: _oops_in_cld(g1h, pss), _oops_in_nmethod(g1h, pss), _clds(&_oops_in_cld, process_only_dirty), - _nmethods(pss->worker_id(), &_oops_in_nmethod, should_mark, pss) {} + _nmethods(&_oops_in_nmethod, should_mark, pss) {} }; #endif // SHARE_GC_G1_G1SHAREDCLOSURES_HPP diff --git a/src/hotspot/share/gc/g1/g1YoungCollector.cpp b/src/hotspot/share/gc/g1/g1YoungCollector.cpp index 0a26c778417..359ed4586c1 100644 --- a/src/hotspot/share/gc/g1/g1YoungCollector.cpp +++ b/src/hotspot/share/gc/g1/g1YoungCollector.cpp @@ -580,7 +580,6 @@ class G1ParEvacuateFollowersClosure : public VoidClosure { void start_term_time() { _term_attempts++; _start_term = os::elapsedTime(); } void end_term_time() { _term_time += (os::elapsedTime() - _start_term); } - G1CollectedHeap* _g1h; G1ParScanThreadState* _par_scan_state; G1ScannerTasksQueueSet* _queues; TaskTerminator* _terminator; @@ -592,22 +591,20 @@ class G1ParEvacuateFollowersClosure : public VoidClosure { inline bool offer_termination() { EventGCPhaseParallel event; - G1ParScanThreadState* const pss = par_scan_state(); start_term_time(); const bool res = (terminator() == nullptr) ? true : terminator()->offer_termination(); end_term_time(); - event.commit(GCId::current(), pss->worker_id(), G1GCPhaseTimes::phase_name(G1GCPhaseTimes::Termination)); + event.commit(GCId::current(), par_scan_state()->worker_id(), G1GCPhaseTimes::phase_name(G1GCPhaseTimes::Termination)); return res; } public: - G1ParEvacuateFollowersClosure(G1CollectedHeap* g1h, - G1ParScanThreadState* par_scan_state, + G1ParEvacuateFollowersClosure(G1ParScanThreadState* par_scan_state, G1ScannerTasksQueueSet* queues, TaskTerminator* terminator, G1GCPhaseTimes::GCParPhases phase) : _start_term(0.0), _term_time(0.0), _term_attempts(0), - _g1h(g1h), _par_scan_state(par_scan_state), + _par_scan_state(par_scan_state), _queues(queues), _terminator(terminator), _phase(phase) {} void do_void() { @@ -632,23 +629,22 @@ class G1EvacuateRegionsBaseTask : public WorkerTask { // regions as there is no guarantee that there is a reference reachable by // Java code (i.e. only by native code) that adds it to the evacuation failed // regions. - void record_pinned_regions(G1ParScanThreadState* pss, uint worker_id) { + void record_pinned_regions(G1ParScanThreadState* pss) { class RecordPinnedRegionClosure : public G1HeapRegionClosure { G1ParScanThreadState* _pss; - uint _worker_id; public: - RecordPinnedRegionClosure(G1ParScanThreadState* pss, uint worker_id) : _pss(pss), _worker_id(worker_id) { } + RecordPinnedRegionClosure(G1ParScanThreadState* pss) : _pss(pss) { } bool do_heap_region(G1HeapRegion* r) { if (r->has_pinned_objects()) { - _pss->record_evacuation_failed_region(r, _worker_id, true /* cause_pinned */); + _pss->record_evacuation_failed_region(r, true /* cause_pinned */); } return false; } - } cl(pss, worker_id); + } cl(pss); - _g1h->collection_set_iterate_increment_from(&cl, worker_id); + _g1h->collection_set_iterate_increment_from(&cl, pss->worker_id()); } protected: @@ -659,18 +655,18 @@ protected: TaskTerminator _terminator; void evacuate_live_objects(G1ParScanThreadState* pss, - uint worker_id, G1GCPhaseTimes::GCParPhases objcopy_phase, G1GCPhaseTimes::GCParPhases termination_phase) { G1GCPhaseTimes* p = _g1h->phase_times(); Ticks start = Ticks::now(); - G1ParEvacuateFollowersClosure cl(_g1h, pss, _task_queues, &_terminator, objcopy_phase); + G1ParEvacuateFollowersClosure cl(pss, _task_queues, &_terminator, objcopy_phase); cl.do_void(); assert(pss->queue_is_empty(), "should be empty"); Tickspan evac_time = (Ticks::now() - start); + uint worker_id = pss->worker_id(); p->record_or_add_time_secs(objcopy_phase, worker_id, evac_time.seconds() - cl.term_time()); if (termination_phase == G1GCPhaseTimes::Termination) { @@ -689,9 +685,9 @@ protected: virtual void end_work(uint worker_id) { } - virtual void scan_roots(G1ParScanThreadState* pss, uint worker_id) = 0; + virtual void scan_roots(G1ParScanThreadState* pss) = 0; - virtual void evacuate_live_objects(G1ParScanThreadState* pss, uint worker_id) = 0; + virtual void evacuate_live_objects(G1ParScanThreadState* pss) = 0; private: Atomic _pinned_regions_recorded; @@ -719,10 +715,10 @@ public: pss->set_ref_discoverer(_g1h->ref_processor_stw()); if (_pinned_regions_recorded.compare_set(false, true)) { - record_pinned_regions(pss, worker_id); + record_pinned_regions(pss); } - scan_roots(pss, worker_id); - evacuate_live_objects(pss, worker_id); + scan_roots(pss); + evacuate_live_objects(pss); } end_work(worker_id); @@ -733,29 +729,26 @@ class G1EvacuateRegionsTask : public G1EvacuateRegionsBaseTask { G1RootProcessor* _root_processor; bool _has_optional_evacuation_work; - void scan_roots(G1ParScanThreadState* pss, uint worker_id) { - _root_processor->evacuate_roots(pss, worker_id); - _g1h->rem_set()->scan_heap_roots(pss, worker_id, G1GCPhaseTimes::ScanHR, G1GCPhaseTimes::ObjCopy, _has_optional_evacuation_work); - _g1h->rem_set()->scan_collection_set_code_roots(pss, worker_id, G1GCPhaseTimes::CodeRoots, G1GCPhaseTimes::ObjCopy); + void scan_roots(G1ParScanThreadState* pss) { + _root_processor->evacuate_roots(pss); + _g1h->rem_set()->scan_heap_roots(pss, G1GCPhaseTimes::ScanHR, G1GCPhaseTimes::ObjCopy, _has_optional_evacuation_work); + _g1h->rem_set()->scan_collection_set_code_roots(pss, G1GCPhaseTimes::CodeRoots, G1GCPhaseTimes::ObjCopy); // There are no optional roots to scan right now. #ifdef ASSERT class VerifyOptionalCollectionSetRootsEmptyClosure : public G1HeapRegionClosure { - G1ParScanThreadState* _pss; - public: - VerifyOptionalCollectionSetRootsEmptyClosure(G1ParScanThreadState* pss) : _pss(pss) { } bool do_heap_region(G1HeapRegion* r) override { assert(!r->has_index_in_opt_cset(), "must be"); return false; } - } cl(pss); - _g1h->collection_set_iterate_increment_from(&cl, worker_id); + } cl; + _g1h->collection_set_iterate_increment_from(&cl, pss->worker_id()); #endif } - void evacuate_live_objects(G1ParScanThreadState* pss, uint worker_id) { - G1EvacuateRegionsBaseTask::evacuate_live_objects(pss, worker_id, G1GCPhaseTimes::ObjCopy, G1GCPhaseTimes::Termination); + void evacuate_live_objects(G1ParScanThreadState* pss) { + G1EvacuateRegionsBaseTask::evacuate_live_objects(pss, G1GCPhaseTimes::ObjCopy, G1GCPhaseTimes::Termination); } void start_work(uint worker_id) { @@ -767,8 +760,7 @@ class G1EvacuateRegionsTask : public G1EvacuateRegionsBaseTask { } public: - G1EvacuateRegionsTask(G1CollectedHeap* g1h, - G1ParScanThreadStateSet* per_thread_states, + G1EvacuateRegionsTask(G1ParScanThreadStateSet* per_thread_states, G1ScannerTasksQueueSet* task_queues, G1RootProcessor* root_processor, uint num_workers, @@ -791,8 +783,7 @@ void G1YoungCollector::evacuate_initial_collection_set(G1ParScanThreadStateSet* Ticks start_processing = Ticks::now(); { G1RootProcessor root_processor(_g1h, num_workers > 1 /* is_parallel */); - G1EvacuateRegionsTask g1_par_task(_g1h, - per_thread_states, + G1EvacuateRegionsTask g1_par_task(per_thread_states, task_queues(), &root_processor, num_workers, @@ -814,14 +805,14 @@ void G1YoungCollector::evacuate_initial_collection_set(G1ParScanThreadStateSet* class G1EvacuateOptionalRegionsTask : public G1EvacuateRegionsBaseTask { - void scan_roots(G1ParScanThreadState* pss, uint worker_id) { - _g1h->rem_set()->scan_heap_roots(pss, worker_id, G1GCPhaseTimes::OptScanHR, G1GCPhaseTimes::OptObjCopy, true /* remember_already_scanned_cards */); - _g1h->rem_set()->scan_collection_set_code_roots(pss, worker_id, G1GCPhaseTimes::OptCodeRoots, G1GCPhaseTimes::OptObjCopy); - _g1h->rem_set()->scan_collection_set_optional_roots(pss, worker_id, G1GCPhaseTimes::OptScanHR, G1GCPhaseTimes::ObjCopy); + void scan_roots(G1ParScanThreadState* pss) { + _g1h->rem_set()->scan_heap_roots(pss, G1GCPhaseTimes::OptScanHR, G1GCPhaseTimes::OptObjCopy, true /* remember_already_scanned_cards */); + _g1h->rem_set()->scan_collection_set_code_roots(pss, G1GCPhaseTimes::OptCodeRoots, G1GCPhaseTimes::OptObjCopy); + _g1h->rem_set()->scan_collection_set_optional_roots(pss, G1GCPhaseTimes::OptScanHR, G1GCPhaseTimes::ObjCopy); } - void evacuate_live_objects(G1ParScanThreadState* pss, uint worker_id) { - G1EvacuateRegionsBaseTask::evacuate_live_objects(pss, worker_id, G1GCPhaseTimes::OptObjCopy, G1GCPhaseTimes::OptTermination); + void evacuate_live_objects(G1ParScanThreadState* pss) { + G1EvacuateRegionsBaseTask::evacuate_live_objects(pss, G1GCPhaseTimes::OptObjCopy, G1GCPhaseTimes::OptTermination); } public: @@ -982,7 +973,7 @@ public: G1STWIsAliveClosure is_alive(&_g1h); G1CopyingKeepAliveClosure keep_alive(&_g1h, pss); G1EnqueueDiscoveredFieldClosure enqueue(&_g1h, pss); - G1ParEvacuateFollowersClosure complete_gc(&_g1h, pss, &_task_queues, _tm == RefProcThreadModel::Single ? nullptr : &_terminator, G1GCPhaseTimes::ObjCopy); + G1ParEvacuateFollowersClosure complete_gc(pss, &_task_queues, _tm == RefProcThreadModel::Single ? nullptr : &_terminator, G1GCPhaseTimes::ObjCopy); _rp_task->rp_work(worker_id, &is_alive, &keep_alive, &enqueue, &complete_gc); // We have completed copying any necessary live referent objects. diff --git a/src/hotspot/share/gc/g1/g1YoungGCPostEvacuateTasks.cpp b/src/hotspot/share/gc/g1/g1YoungGCPostEvacuateTasks.cpp index 5a10ab7ae3b..6207480a50e 100644 --- a/src/hotspot/share/gc/g1/g1YoungGCPostEvacuateTasks.cpp +++ b/src/hotspot/share/gc/g1/g1YoungGCPostEvacuateTasks.cpp @@ -122,52 +122,49 @@ public: class G1PostEvacuateCollectionSetCleanupTask1::UpdateCodeRootsTask : public G1AbstractSubTask { - class ProcessRegionClosure : public G1HeapRegionClosure { - G1ParScanThreadStateSet* _psss; - - public: - ProcessRegionClosure(G1ParScanThreadStateSet* psss) : _psss(psss) { } - - bool do_heap_region(G1HeapRegion* r) override { - uint index = r->hrm_index(); - - size_t num_nmethods = 0; - for (uint i = 0; i < _psss->num_workers(); i++) { - G1ParScanThreadState* pss = _psss->state_for_worker(i); - num_nmethods += pss->num_nmethods(index); - } - if (num_nmethods != 0) { - // Notify the code root sets that we are going to add code roots. - r->rem_set()->prepare_for_adding_code_roots(num_nmethods); - - // Add roots. - for (uint i = 0; i < _psss->num_workers(); i++) { - G1ParScanThreadState* pss = _psss->state_for_worker(i); - pss->iterate_nmethods(index, [&] (nmethod* nm) { r->add_code_root(nm); }); - } - } - return false; - } - }; - G1ParScanThreadStateSet* _psss; - G1HeapRegionClaimer _claimer; public: UpdateCodeRootsTask(G1ParScanThreadStateSet* per_thread_states) - : G1AbstractSubTask(G1GCPhaseTimes::UpdateCodeRoots), _psss(per_thread_states), _claimer(0) { } + : G1AbstractSubTask(G1GCPhaseTimes::UpdateCodeRoots), _psss(per_thread_states) { } - double worker_cost() const override { - return _psss->num_nmethod_regions_to_add(); - } - - void set_max_workers(uint max_workers) override { - _claimer.set_n_workers(max_workers); - } + double worker_cost() const override { return 1.0; } + // Add code roots serially to avoid lock and resize contention. void do_work(uint worker_id) override { - ProcessRegionClosure cl(_psss); - _psss->par_iterate_nmethod_regions_to_add(&cl, &_claimer, worker_id); + G1CollectedHeap* g1h = G1CollectedHeap::heap(); + uint max_regions = g1h->max_num_regions(); + + uint* counts = NEW_C_HEAP_ARRAY(uint, max_regions, mtGC); + memset(counts, 0, max_regions * sizeof(uint)); + + // Pass 1: count the number of nmethods to add per region across all workers. + for (uint i = 0; i < _psss->num_workers(); i++) { + G1ParScanThreadState* pss = _psss->state_for_worker(i); + const GrowableArrayCHeap& pairs = pss->code_root_pairs(); + for (const G1CodeRootPair& pair : pairs) { + counts[pair._region_idx]++; + } + } + + // Pass 2: pre-size each region's code root set once, then add all nmethods. + for (uint i = 0; i < _psss->num_workers(); i++) { + G1ParScanThreadState* pss = _psss->state_for_worker(i); + const GrowableArrayCHeap& pairs = pss->code_root_pairs(); + for (const G1CodeRootPair& pair : pairs) { + uint region_idx = pair._region_idx; + G1HeapRegion* region = g1h->region_at(region_idx); + if (counts[region_idx] > 0) { + // First occurrence of this region: pre-size its code root set to the + // final size so it never needs to grow under the (single-threaded) add. + region->rem_set()->prepare_for_adding_code_roots(counts[region_idx]); + counts[region_idx] = 0; + } + region->add_code_root(pair._nmethod); + } + } + + FREE_C_HEAP_ARRAY(counts); } }; @@ -384,7 +381,7 @@ G1PostEvacuateCollectionSetCleanupTask1::G1PostEvacuateCollectionSetCleanupTask1 if (SampleCollectionSetCandidatesTask::should_execute()) { add_serial_task(new SampleCollectionSetCandidatesTask()); } - add_parallel_task(new UpdateCodeRootsTask(per_thread_states)); + add_serial_task(new UpdateCodeRootsTask(per_thread_states)); add_parallel_task(G1CollectedHeap::heap()->rem_set()->create_cleanup_after_scan_heap_roots_task()); if (evac_failed) { diff --git a/src/hotspot/share/gc/shared/c1/cardTableBarrierSetC1.cpp b/src/hotspot/share/gc/shared/c1/cardTableBarrierSetC1.cpp index 9effc0ad349..d478840578f 100644 --- a/src/hotspot/share/gc/shared/c1/cardTableBarrierSetC1.cpp +++ b/src/hotspot/share/gc/shared/c1/cardTableBarrierSetC1.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -22,6 +22,7 @@ * */ +#include "c1/c1_IR.hpp" #include "ci/ciInlineKlass.hpp" #include "code/aotCodeCache.hpp" #include "gc/shared/c1/cardTableBarrierSetC1.hpp" @@ -49,7 +50,12 @@ void CardTableBarrierSetC1::store_at_resolved(LIRAccess& access, LIR_Opr value) ciField* field = vk->nonstatic_field_at(i); if (!field->type()->is_primitive_type()) { int off = access.offset().opr().as_jint() + field->offset_in_bytes() - vk->payload_offset(); - LIRAccess inner_access(access.gen(), decorators, access.base(), LIR_OprFact::intConst(off), field->type()->basic_type(), access.patch_emit_info(), access.access_emit_info()); + // Each pre-barrier needs its own CodeEmitInfo + CodeEmitInfo* info = access.patch_emit_info(); + if (info != nullptr) { + info = new CodeEmitInfo(info); + } + LIRAccess inner_access(access.gen(), decorators, access.base(), LIR_OprFact::intConst(off), field->type()->basic_type(), info, access.access_emit_info()); pre_barrier(inner_access, resolve_address(inner_access, false), LIR_OprFact::illegalOpr /* pre_val */, inner_access.patch_emit_info()); } diff --git a/src/hotspot/share/gc/z/c2/zBarrierSetC2.cpp b/src/hotspot/share/gc/z/c2/zBarrierSetC2.cpp index 6117754fde0..6c82f388aae 100644 --- a/src/hotspot/share/gc/z/c2/zBarrierSetC2.cpp +++ b/src/hotspot/share/gc/z/c2/zBarrierSetC2.cpp @@ -444,7 +444,7 @@ void ZBarrierSetC2::clone_at_expansion(PhaseMacroExpand* phase, ArrayCopyNode* a if (offset != arrayOopDesc::base_offset_in_bytes(T_OBJECT)) { assert(UseCompactObjectHeaders, "should only happen with COH"); assert((arrayOopDesc::base_offset_in_bytes(T_OBJECT) - offset) == BytesPerLong, "unexpected offset"); - length = phase->transform_later(new SubXNode(length, phase->longcon(1))); // Size is in longs + length = phase->transform_later(new SubXNode(length, phase->MakeConX(1))); // Size is in longs src_offset = phase->longcon(arrayOopDesc::base_offset_in_bytes(T_OBJECT)); dest_offset = src_offset; } diff --git a/src/hotspot/share/memory/oopFactory.cpp b/src/hotspot/share/memory/oopFactory.cpp index 428c0a10be8..178878607d3 100644 --- a/src/hotspot/share/memory/oopFactory.cpp +++ b/src/hotspot/share/memory/oopFactory.cpp @@ -79,7 +79,8 @@ typeArrayOop oopFactory::new_longArray(int length, TRAPS) { // create java.lang.Object[] refArrayOop oopFactory::new_objectArray(int length, TRAPS) { - return Universe::objectArrayKlass()->allocate_instance(length, THREAD); + objArrayOop array = Universe::objectArrayKlass()->allocate_instance(length, CHECK_NULL); + return oop_cast(array); } typeArrayOop oopFactory::new_charArray(const char* utf8_str, TRAPS) { @@ -117,7 +118,7 @@ objArrayOop oopFactory::new_objArray(Klass* klass, int length, ArrayProperties p } objArrayOop oopFactory::new_objArray(Klass* klass, int length, TRAPS) { - return new_objArray(klass, length, ArrayProperties::Default(), THREAD); + return new_objArray(klass, length, ArrayProperties::Default(), THREAD); } refArrayOop oopFactory::new_refArray(Klass* klass, int length, ArrayProperties properties, TRAPS) { @@ -126,7 +127,8 @@ refArrayOop oopFactory::new_refArray(Klass* klass, int length, ArrayProperties p ObjArrayKlass* oak = ObjArrayKlass::cast(ak)->klass_from_description(ad, CHECK_NULL); // Cast below must pass because the array description required a RefArrayKlass RefArrayKlass* rak = RefArrayKlass::cast(oak); - return rak->allocate_instance(length, THREAD); + objArrayOop array = rak->allocate_instance(length, CHECK_NULL); + return oop_cast(array); } refArrayOop oopFactory::new_refArray(Klass* klass, int length, TRAPS) { @@ -138,7 +140,8 @@ flatArrayOop oopFactory::new_flatArray(InlineKlass* ik, int length, ArrayPropert ObjArrayKlass* oak = ObjArrayKlass::cast(ak)->klass_with_properties(props, CHECK_NULL); FlatArrayKlass* fak = FlatArrayKlass::cast(oak); - return fak->allocate_instance(length, THREAD); + objArrayOop array = fak->allocate_instance(length, CHECK_NULL); + return oop_cast(array); } refArrayHandle oopFactory::new_refArray_handle(Klass* klass, int length, TRAPS) { diff --git a/src/hotspot/share/oops/flatArrayKlass.cpp b/src/hotspot/share/oops/flatArrayKlass.cpp index b1d99768a75..dd33074f656 100644 --- a/src/hotspot/share/oops/flatArrayKlass.cpp +++ b/src/hotspot/share/oops/flatArrayKlass.cpp @@ -146,12 +146,12 @@ void FlatArrayKlass::metaspace_pointers_do(MetaspaceClosure* it) { } // Oops allocation... -flatArrayOop FlatArrayKlass::allocate_instance(int length, TRAPS) { +objArrayOop FlatArrayKlass::allocate_instance(int length, TRAPS) { assert(UseArrayFlattening, "Must be enabled"); check_array_allocation_length(length, max_elements(), CHECK_NULL); int size = flatArrayOopDesc::object_size(layout_helper(), length); oop array = Universe::heap()->array_allocate(this, size, length, true, CHECK_NULL); - return oop_cast(array); + return oop_cast(array); } oop FlatArrayKlass::multi_allocate(int rank, jint* last_size, TRAPS) { diff --git a/src/hotspot/share/oops/flatArrayKlass.hpp b/src/hotspot/share/oops/flatArrayKlass.hpp index 90c4552db79..c2fa4c56f11 100644 --- a/src/hotspot/share/oops/flatArrayKlass.hpp +++ b/src/hotspot/share/oops/flatArrayKlass.hpp @@ -98,7 +98,7 @@ class FlatArrayKlass : public ObjArrayKlass { size_t oop_size(oop obj) const override; // Oop Allocation - flatArrayOop allocate_instance(int length, TRAPS); + objArrayOop allocate_instance(int length, TRAPS) override final; oop multi_allocate(int rank, jint* sizes, TRAPS) override; diff --git a/src/hotspot/share/oops/instanceKlass.cpp b/src/hotspot/share/oops/instanceKlass.cpp index d6f41117213..2a769f344cc 100644 --- a/src/hotspot/share/oops/instanceKlass.cpp +++ b/src/hotspot/share/oops/instanceKlass.cpp @@ -1583,7 +1583,7 @@ void InstanceKlass::initialize_impl(TRAPS) { call_class_initializer(THREAD); } - if (has_strict_static_fields() && !HAS_PENDING_EXCEPTION) { + if (has_strict_static_fields() && !HAS_PENDING_EXCEPTION && !ReplayCompiles) { // Step 9 also verifies that strict static fields have been initialized. // Status bits were set in ClassFileParser::post_process_parsed_stream. // After , bits must all be clear, or else we must throw an error. @@ -2134,12 +2134,25 @@ bool InstanceKlass::find_local_field(Symbol* name, Symbol* sig, fieldDescriptor* if (fs.lookup(name, sig)) { assert(fs.name() == name, "name must match"); assert(fs.signature() == sig, "signature must match"); - fd->reinitialize(const_cast(this), fs.to_FieldInfo()); + fd->reinitialize(this, fs.to_FieldInfo()); return true; } return false; } +bool InstanceKlass::find_local_field(Symbol* name, Symbol* sig, fieldDescriptor* fd, bool also_internal) const { + if (!also_internal) { + return find_local_field( name, sig, fd); + } + + for (AllFieldStream fs(this); !fs.done(); fs.next()) { + if (fs.name() == name && fs.signature() == sig) { + fd->reinitialize(this, fs.to_FieldInfo()); + return true; + } + } + return false; +} Klass* InstanceKlass::find_interface_field(Symbol* name, Symbol* sig, fieldDescriptor* fd) const { const int n = local_interfaces()->length(); diff --git a/src/hotspot/share/oops/instanceKlass.hpp b/src/hotspot/share/oops/instanceKlass.hpp index 8ddf3893fd5..aba81900985 100644 --- a/src/hotspot/share/oops/instanceKlass.hpp +++ b/src/hotspot/share/oops/instanceKlass.hpp @@ -669,6 +669,8 @@ public: // find local field, returns true if found bool find_local_field(Symbol* name, Symbol* sig, fieldDescriptor* fd) const; + // find local field, returns true if found + bool find_local_field(Symbol* name, Symbol* sig, fieldDescriptor* fd, bool also_internal) const; // find field in direct superinterfaces, returns the interface in which the field is defined Klass* find_interface_field(Symbol* name, Symbol* sig, fieldDescriptor* fd) const; // find field according to JVM spec 5.4.3.2, returns the klass in which the field is defined diff --git a/src/hotspot/share/oops/objArrayKlass.cpp b/src/hotspot/share/oops/objArrayKlass.cpp index 3b37e7ac2bc..8a5c93cc57a 100644 --- a/src/hotspot/share/oops/objArrayKlass.cpp +++ b/src/hotspot/share/oops/objArrayKlass.cpp @@ -237,18 +237,12 @@ ObjArrayKlass* ObjArrayKlass::allocate_klass_from_description(ArrayDescription a } objArrayOop ObjArrayKlass::allocate_instance(int length, ArrayProperties props, TRAPS) { - check_array_allocation_length(length, arrayOopDesc::max_array_length(T_OBJECT), CHECK_NULL); ObjArrayKlass* ak = klass_with_properties(props, CHECK_NULL); - switch (ak->kind()) { - case Klass::RefArrayKlassKind: - return RefArrayKlass::cast(ak)->allocate_instance(length, THREAD); + return ak->allocate_instance(length, THREAD); +} - case Klass::FlatArrayKlassKind: - return FlatArrayKlass::cast(ak)->allocate_instance(length, THREAD); - - default: - ShouldNotReachHere(); - } +objArrayOop ObjArrayKlass::allocate_instance(int length, TRAPS) { + ShouldNotReachHere(); } oop ObjArrayKlass::multi_allocate(int rank, jint* sizes, TRAPS) { diff --git a/src/hotspot/share/oops/objArrayKlass.hpp b/src/hotspot/share/oops/objArrayKlass.hpp index 846a05fb641..add6d86c14c 100644 --- a/src/hotspot/share/oops/objArrayKlass.hpp +++ b/src/hotspot/share/oops/objArrayKlass.hpp @@ -100,6 +100,7 @@ class ObjArrayKlass : public ArrayKlass { int n, Klass* element_klass, TRAPS); oop multi_allocate(int rank, jint* sizes, TRAPS) override; + virtual objArrayOop allocate_instance(int length, TRAPS); // Copying void copy_array(arrayOop s, int src_pos, arrayOop d, int dst_pos, int length, TRAPS) override; diff --git a/src/hotspot/share/oops/refArrayKlass.cpp b/src/hotspot/share/oops/refArrayKlass.cpp index 698670273c1..4c55f8e936b 100644 --- a/src/hotspot/share/oops/refArrayKlass.cpp +++ b/src/hotspot/share/oops/refArrayKlass.cpp @@ -111,12 +111,12 @@ size_t RefArrayKlass::oop_size(oop obj) const { return refArrayOop(obj)->object_size(); } -refArrayOop RefArrayKlass::allocate_instance(int length, TRAPS) { +objArrayOop RefArrayKlass::allocate_instance(int length, TRAPS) { check_array_allocation_length(length, arrayOopDesc::max_array_length(T_OBJECT), CHECK_NULL); size_t size = refArrayOopDesc::object_size(length); oop array = Universe::heap()->array_allocate( this, size, length, /* do_zero */ true, CHECK_NULL); - return oop_cast(array); + return oop_cast(array); } static void throw_array_null_pointer_store_exception(arrayOop src, arrayOop dst, TRAPS) { diff --git a/src/hotspot/share/oops/refArrayKlass.hpp b/src/hotspot/share/oops/refArrayKlass.hpp index ebc6e858503..5bac01c0f13 100644 --- a/src/hotspot/share/oops/refArrayKlass.hpp +++ b/src/hotspot/share/oops/refArrayKlass.hpp @@ -60,7 +60,7 @@ class RefArrayKlass : public ObjArrayKlass { int n, Klass* element_klass, ArrayProperties props, TRAPS); - refArrayOop allocate_instance(int length, TRAPS); + objArrayOop allocate_instance(int length, TRAPS) override final; // Copying void copy_array(arrayOop s, int src_pos, arrayOop d, int dst_pos, int length, TRAPS) override; diff --git a/src/hotspot/share/oops/symbol.hpp b/src/hotspot/share/oops/symbol.hpp index b5277d3cde8..2fc664dfab2 100644 --- a/src/hotspot/share/oops/symbol.hpp +++ b/src/hotspot/share/oops/symbol.hpp @@ -249,7 +249,7 @@ class Symbol : public MetaspaceObj { int index_of_at(int i, const char* substr, int substr_len) const; // Three-way compare for sorting; returns -1/0/1 if receiver is than arg - // note that the ordering is not alfabetical + // note that the ordering is not alphabetical inline int fast_compare(const Symbol* other) const; // Returns receiver converted to null-terminated UTF-8 string; string is diff --git a/src/hotspot/share/opto/bytecodeInfo.cpp b/src/hotspot/share/opto/bytecodeInfo.cpp index 4a2b5d93cd6..d1c7e24f608 100644 --- a/src/hotspot/share/opto/bytecodeInfo.cpp +++ b/src/hotspot/share/opto/bytecodeInfo.cpp @@ -89,7 +89,7 @@ static bool is_init_with_ea(ciMethod* callee_method, if (callee_method->is_object_constructor()) { return true; // constructor } - if ((caller_method->is_object_constructor() || caller_method->is_class_initializer()) && + if (caller_method->is_object_constructor() && caller_method != C->method() && caller_method->holder()->is_subclass_of(callee_method->holder())) { return true; // super constructor is called from inlined constructor diff --git a/src/hotspot/share/opto/callGenerator.cpp b/src/hotspot/share/opto/callGenerator.cpp index 6b4bbce3d87..8d9f8d8f32d 100644 --- a/src/hotspot/share/opto/callGenerator.cpp +++ b/src/hotspot/share/opto/callGenerator.cpp @@ -27,6 +27,7 @@ #include "ci/ciMemberName.hpp" #include "ci/ciMethodHandle.hpp" #include "ci/ciObjArray.hpp" +#include "ci/ciStreams.hpp" #include "classfile/javaClasses.hpp" #include "compiler/compileLog.hpp" #include "oops/accessDecorators.hpp" @@ -772,13 +773,19 @@ void CallGenerator::do_late_inline_helper() { Node* buffer_oop = nullptr; ciMethod* inline_method = inline_cg()->method(); ciType* return_type = inline_method->return_type(); - if (!call->tf()->returns_inline_type_as_fields() && - return_type->is_inlinetype() && return_type->as_inline_klass()->can_be_returned_as_fields()) { - assert(is_mh_late_inline(), "Unexpected return type"); + // Allocate a buffer for the inline type returned as fields because the caller expects an oop return. + // Moving this after the call would require distinct JVM states: a next-BCI state with the result for + // deoptimization at an allocation safepoint and an invoke-BCI state for exceptions like OOME. The + // pre-call state can safely execute the call if allocation deoptimizes. + bool needs_return_buffer = !call->tf()->returns_inline_type_as_fields() && + return_type->is_inlinetype() && + return_type->as_inline_klass()->can_be_returned_as_fields(); + // A non-null scalarized return would require a buffer. Since allocating that buffer could + // initialize the value class, speculate that the result is null and deoptimize otherwise. + bool assert_null_return = needs_return_buffer && !return_type->as_inline_klass()->is_initialized(); + assert(!needs_return_buffer || is_mh_late_inline(), "Unexpected return type"); - // Allocate a buffer for the inline type returned as fields because the caller expects an oop return. - // Do this before the method handle call in case the buffer allocation triggers deoptimization and - // we need to "re-execute" the call in the interpreter (to make sure the call is only executed once). + if (needs_return_buffer && !assert_null_return) { GraphKit arg_kit(jvms, &gvn); { PreserveReexecuteState preexecs(&arg_kit); @@ -840,6 +847,20 @@ void CallGenerator::do_late_inline_helper() { if (vt != nullptr) { if (call->tf()->returns_inline_type_as_fields()) { vt->replace_call_results(&kit, call, C); + } else if (assert_null_return && !vt->is_allocated(&kit.gvn())) { + // Deoptimize if the result is non-null. + // Put the trap at the next bytecode to avoid re-executing the method handle call. + ciBytecodeStream iter(kit.method()); + iter.force_bci(kit.bci()); + assert(Bytecodes::is_invoke(iter.cur_bc()), "unexpected bytecode: %s", Bytecodes::name(iter.cur_bc())); + int bci = kit.bci(); + kit.push(vt); + kit.set_bci(iter.next_bci()); + result = kit.null_assert(vt); + kit.set_bci(bci); + if (!kit.stopped()) { + result = kit.pop(); + } } else { // Result might still be allocated (for example, if it has been stored to a non-flat field) if (!vt->is_allocated(&kit.gvn())) { @@ -868,8 +889,8 @@ void CallGenerator::do_late_inline_helper() { oop->init_req(2, buffer_oop); mem->init_req(2, kit.merged_memory()); - // Update oop input to buffer - kit.gvn().hash_delete(vt); + // Use cloned InlineTypeNode to propagate oop from now on + vt = vt->clone_if_required(&kit.gvn(), kit.map()); vt->set_oop(kit.gvn(), kit.gvn().transform(oop)); vt->set_is_buffered(kit.gvn()); vt = kit.gvn().transform(vt)->as_InlineType(); @@ -899,6 +920,7 @@ void CallGenerator::do_late_inline_helper() { } } + C->set_do_cleanup(kit.stopped()); // path is dead; needs cleanup kit.replace_call(call, result, true, do_asserts); } } diff --git a/src/hotspot/share/opto/callnode.cpp b/src/hotspot/share/opto/callnode.cpp index 0bd9918083f..3916b20f59a 100644 --- a/src/hotspot/share/opto/callnode.cpp +++ b/src/hotspot/share/opto/callnode.cpp @@ -85,7 +85,7 @@ const RegMask &StartNode::in_RegMask(uint) const { //------------------------------match------------------------------------------ // Construct projections for incoming parameters, and their RegMask info -Node *StartNode::match(const ProjNode *proj, const Matcher *match, const RegMask* mask) { +Node* StartNode::match(const ProjNode* proj, const Matcher* match) { switch (proj->_con) { case TypeFunc::Control: case TypeFunc::I_O: @@ -782,15 +782,16 @@ void CallNode::calling_convention(BasicType* sig_bt, VMRegPair *parm_regs, uint //------------------------------match------------------------------------------ // Construct projections for control, I/O, memory-fields, ..., and // return result(s) along with their RegMask info -Node *CallNode::match(const ProjNode *proj, const Matcher *match, const RegMask* mask) { +Node* CallNode::match(const ProjNode* proj, const Matcher* match) { uint con = proj->_con; - const TypeTuple* range_cc = tf()->range_cc(); + const TypeTuple* range_cc = _tf->range_cc(); if (con >= TypeFunc::Parms) { - if (tf()->returns_inline_type_as_fields()) { + if (_tf->returns_inline_type_as_fields()) { // The call returns multiple values (inline type fields): we // create one projection per returned value. assert(con <= TypeFunc::Parms+1 || InlineTypeReturnedAsFields, "only for multi value return"); uint ideal_reg = range_cc->field_at(con)->ideal_reg(); + const RegMask* mask = match->return_values_mask(_tf); return new MachProjNode(this, con, mask[con-TypeFunc::Parms], ideal_reg); } else { if (con == TypeFunc::Parms) { @@ -1403,7 +1404,7 @@ bool CallStaticJavaNode::remove_unknown_flat_array_load(PhaseIterGVN* igvn, Node Node* CallStaticJavaNode::replace_is_substitutable(PhaseIterGVN* igvn) { Node* left = in(TypeFunc::Parms); Node* right = in(TypeFunc::Parms + 1); - if (!InlineTypeNode::can_emit_substitutability_check(left, right)) { + if (!InlineTypeNode::can_emit_substitutability_check(igvn, left, right)) { return nullptr; } @@ -1732,7 +1733,9 @@ Node *SafePointNode::Ideal(PhaseGVN *phase, bool can_reshape) { for (uint i = jvms()->debug_start(); i < jvms()->debug_end(); i++) { Node* n = in(i)->uncast(); if (n->is_InlineType()) { - n->as_InlineType()->make_scalar_in_safepoints(phase->is_IterGVN(), true, this); + if (!n->as_InlineType()->make_scalar_in_safepoints(phase->is_IterGVN(), true, this)) { + return nullptr; + } } } } @@ -2052,8 +2055,7 @@ AllocateNode::AllocateNode(Compile* C, const TypeFunc *atype, void AllocateNode::compute_MemBar_redundancy(ciMethod* initializer) { - assert(initializer != nullptr && - (initializer->is_object_constructor() || initializer->is_class_initializer()), + assert(initializer != nullptr && initializer->is_object_constructor(), "unexpected initializer method"); BCEscapeAnalyzer* analyzer = initializer->get_bcea(); if (analyzer == nullptr) { diff --git a/src/hotspot/share/opto/callnode.hpp b/src/hotspot/share/opto/callnode.hpp index e446420b3b7..465d1d429a2 100644 --- a/src/hotspot/share/opto/callnode.hpp +++ b/src/hotspot/share/opto/callnode.hpp @@ -76,7 +76,7 @@ public: virtual Node *Ideal(PhaseGVN *phase, bool can_reshape); virtual void calling_convention( BasicType* sig_bt, VMRegPair *parm_reg, uint length ) const; virtual const RegMask &in_RegMask(uint) const; - virtual Node *match(const ProjNode *proj, const Matcher *m, const RegMask* mask); + virtual Node* match(const ProjNode* proj, const Matcher* m); virtual uint ideal_reg() const { return 0; } #ifndef PRODUCT virtual void dump_spec(outputStream *st) const; @@ -799,7 +799,7 @@ public: virtual bool cmp(const Node &n) const; virtual uint size_of() const = 0; virtual void calling_convention(BasicType* sig_bt, VMRegPair* parm_regs, uint argcnt) const; - virtual Node* match(const ProjNode* proj, const Matcher* m, const RegMask* mask); + virtual Node* match(const ProjNode* proj, const Matcher* m); virtual uint ideal_reg() const { return NotAMachineReg; } // Are we guaranteed that this node is a safepoint? Not true for leaf calls and // for some macro nodes whose expansion does not have a safepoint on the fast path. diff --git a/src/hotspot/share/opto/cfgnode.cpp b/src/hotspot/share/opto/cfgnode.cpp index 46016bceaf8..5a7e3eff0b8 100644 --- a/src/hotspot/share/opto/cfgnode.cpp +++ b/src/hotspot/share/opto/cfgnode.cpp @@ -3115,6 +3115,7 @@ private: _clones.map(phi->_idx, vt); Node_List casts; for (uint i = 1; i < phi->req(); ++i) { + assert(casts.size() == 0, "must be cleared"); Node* n = phi->in(i); if (n == nullptr) { continue; @@ -3142,6 +3143,10 @@ private: n->as_InlineType()->set_oop(*_phase, _phase->transform(cast)); n = _phase->transform(n); if (n->is_top()) { + if (casts.size() > 0) { + // We could be skipping some unprocessed casts that are also dead. Clear the list for the next phi input. + casts.clear(); + } break; } } diff --git a/src/hotspot/share/opto/compile.cpp b/src/hotspot/share/opto/compile.cpp index c5c22c25bd4..72049ad027c 100644 --- a/src/hotspot/share/opto/compile.cpp +++ b/src/hotspot/share/opto/compile.cpp @@ -2130,7 +2130,10 @@ void Compile::process_inline_types(PhaseIterGVN &igvn, bool remove) { set_scalarize_in_safepoints(true); for (int i = _inline_type_nodes.length()-1; i >= 0; i--) { InlineTypeNode* vt = _inline_type_nodes.at(i)->as_InlineType(); - vt->make_scalar_in_safepoints(&igvn); + if (!vt->make_scalar_in_safepoints(&igvn)) { + record_failure("out of nodes during scalarization"); + return; + } igvn.record_for_igvn(vt); } if (remove) { @@ -3062,9 +3065,9 @@ void Compile::Optimize() { if (failing()) return; - if (AlwaysIncrementalInline || StressIncrementalInlining) { - inline_incrementally(igvn); - } + // inline_boxing_calls() may introduce new late inline candidates + // in stress modes or w/ some compile directives. + inline_incrementally(igvn); print_method(PHASE_INCREMENTAL_BOXING_INLINE, 2); @@ -3113,6 +3116,9 @@ void Compile::Optimize() { // Process inline type nodes now that all inlining is over process_inline_types(igvn); + if (failing()) { + return; + } adjust_flat_array_access_aliases(igvn); @@ -3299,6 +3305,9 @@ void Compile::Optimize() { // Process inline types before macro expansion. Otherwise, we will not be able to // remove unused allocations because it cannot match the expanded allocation. process_inline_types(igvn); + if (failing()) { + return; + } { TracePhase tp(_t_macroExpand); @@ -3332,6 +3341,9 @@ void Compile::Optimize() { // Process inline type nodes again and remove them. From here // on we don't need to keep track of field values anymore. process_inline_types(igvn, /* remove= */ true); + if (failing()) { + return; + } { TracePhase tp(_t_barrierExpand); @@ -4926,6 +4938,7 @@ bool Compile::final_graph_reshaping() { bool Compile::too_many_traps(ciMethod* method, int bci, Deoptimization::DeoptReason reason) { + assert(reason > Deoptimization::Reason_none && reason <= Deoptimization::Reason_LIMIT, "invalid reason"); ciMethodData* md = method->method_data(); if (md->is_empty()) { // Assume the trap has not occurred, or that it occurred only @@ -4951,6 +4964,7 @@ bool Compile::too_many_traps(ciMethod* method, // Less-accurate variant which does not require a method and bci. bool Compile::too_many_traps(Deoptimization::DeoptReason reason, ciMethodData* logmd) { + assert(reason > Deoptimization::Reason_none && reason <= Deoptimization::Reason_LIMIT, "invalid reason"); if (trap_count(reason) >= Deoptimization::per_method_trap_limit(reason)) { // Too many traps globally. // Note that we use cumulative trap_count, not just md->trap_count. @@ -4975,6 +4989,7 @@ bool Compile::too_many_traps(Deoptimization::DeoptReason reason, bool Compile::too_many_recompiles(ciMethod* method, int bci, Deoptimization::DeoptReason reason) { + assert(reason > Deoptimization::Reason_none && reason <= Deoptimization::Reason_LIMIT, "invalid reason"); ciMethodData* md = method->method_data(); if (md->is_empty()) { // Assume the trap has not occurred, or that it occurred only diff --git a/src/hotspot/share/opto/divnode.cpp b/src/hotspot/share/opto/divnode.cpp index d2026541abd..7b8a071d39a 100644 --- a/src/hotspot/share/opto/divnode.cpp +++ b/src/hotspot/share/opto/divnode.cpp @@ -1658,7 +1658,7 @@ DivModLNode* DivModLNode::make(Node* div_or_mod) { //------------------------------match------------------------------------------ // return result(s) along with their RegMask info -Node *DivModINode::match(const ProjNode *proj, const Matcher *match, const RegMask* mask) { +Node* DivModINode::match(const ProjNode* proj, const Matcher* match) { uint ideal_reg = proj->ideal_reg(); RegMask rm; if (proj->_con == first_proj_num) { @@ -1673,7 +1673,7 @@ Node *DivModINode::match(const ProjNode *proj, const Matcher *match, const RegMa //------------------------------match------------------------------------------ // return result(s) along with their RegMask info -Node *DivModLNode::match(const ProjNode *proj, const Matcher *match, const RegMask* mask) { +Node* DivModLNode::match(const ProjNode* proj, const Matcher* match) { uint ideal_reg = proj->ideal_reg(); RegMask rm; if (proj->_con == first_proj_num) { @@ -1711,7 +1711,7 @@ UDivModLNode* UDivModLNode::make(Node* div_or_mod) { //------------------------------match------------------------------------------ // return result(s) along with their RegMask info -Node* UDivModINode::match(const ProjNode* proj, const Matcher* match, const RegMask* mask) { +Node* UDivModINode::match(const ProjNode* proj, const Matcher* match) { uint ideal_reg = proj->ideal_reg(); RegMask rm; if (proj->_con == first_proj_num) { @@ -1726,7 +1726,7 @@ Node* UDivModINode::match(const ProjNode* proj, const Matcher* match, const RegM //------------------------------match------------------------------------------ // return result(s) along with their RegMask info -Node* UDivModLNode::match( const ProjNode* proj, const Matcher* match, const RegMask* mask) { +Node* UDivModLNode::match(const ProjNode* proj, const Matcher* match) { uint ideal_reg = proj->ideal_reg(); RegMask rm; if (proj->_con == first_proj_num) { diff --git a/src/hotspot/share/opto/divnode.hpp b/src/hotspot/share/opto/divnode.hpp index 129d04f19f1..1a68e156a0d 100644 --- a/src/hotspot/share/opto/divnode.hpp +++ b/src/hotspot/share/opto/divnode.hpp @@ -255,7 +255,7 @@ public: DivModINode(Node* ctrl, Node* dividend, Node* divisor) : DivModNode(ctrl, dividend, divisor) {} virtual int Opcode() const; virtual const Type *bottom_type() const { return TypeTuple::INT_PAIR; } - virtual Node *match(const ProjNode *proj, const Matcher *m, const RegMask* mask); + virtual Node* match(const ProjNode* proj, const Matcher* m); // Make a divmod and associated projections from a div or mod. static DivModINode* make(Node* div_or_mod); @@ -268,7 +268,7 @@ public: DivModLNode(Node* ctrl, Node* dividend, Node* divisor) : DivModNode(ctrl, dividend, divisor) {} virtual int Opcode() const; virtual const Type *bottom_type() const { return TypeTuple::LONG_PAIR; } - virtual Node *match(const ProjNode *proj, const Matcher *m, const RegMask* mask); + virtual Node *match(const ProjNode* proj, const Matcher* m); // Make a divmod and associated projections from a div or mod. static DivModLNode* make(Node* div_or_mod); @@ -282,7 +282,7 @@ public: UDivModINode(Node* ctrl, Node* dividend, Node* divisor) : DivModNode(ctrl, dividend, divisor) {} virtual int Opcode() const; virtual const Type *bottom_type() const { return TypeTuple::INT_PAIR; } - virtual Node* match(const ProjNode* proj, const Matcher* m, const RegMask* mask); + virtual Node* match(const ProjNode* proj, const Matcher* m); // Make a divmod and associated projections from a div or mod. static UDivModINode* make(Node* div_or_mod); @@ -295,7 +295,7 @@ public: UDivModLNode(Node* ctrl, Node* dividend, Node* divisor) : DivModNode(ctrl, dividend, divisor) {} virtual int Opcode() const; virtual const Type *bottom_type() const { return TypeTuple::LONG_PAIR; } - virtual Node* match(const ProjNode* proj, const Matcher* m, const RegMask* mask); + virtual Node* match(const ProjNode* proj, const Matcher* m); // Make a divmod and associated projections from a div or mod. static UDivModLNode* make(Node* div_or_mod); diff --git a/src/hotspot/share/opto/escape.cpp b/src/hotspot/share/opto/escape.cpp index 88bc85055f5..c2187cfd44b 100644 --- a/src/hotspot/share/opto/escape.cpp +++ b/src/hotspot/share/opto/escape.cpp @@ -1374,7 +1374,10 @@ bool ConnectionGraph::reduce_phi_on_safepoints_helper(Node* ophi, Node* cast, No const bool allow_oop = !merge_t->is_flat(); for (uint j = 0; j < value_worklist.size(); ++j) { InlineTypeNode* vt = value_worklist.at(j)->as_InlineType(); - vt->make_scalar_in_safepoints(_igvn, allow_oop); + if (!vt->make_scalar_in_safepoints(_igvn, allow_oop)) { + sfpt->restore_non_debug_edges(non_debug_edges_worklist); + return false; + } } } diff --git a/src/hotspot/share/opto/graphKit.hpp b/src/hotspot/share/opto/graphKit.hpp index d0de320ab84..bc4d6aeb371 100644 --- a/src/hotspot/share/opto/graphKit.hpp +++ b/src/hotspot/share/opto/graphKit.hpp @@ -772,7 +772,7 @@ class GraphKit : public Phase { } bool too_many_traps_or_recompiles(Deoptimization::DeoptReason reason) { - return C->too_many_traps_or_recompiles(method(), bci(), reason); + return C->too_many_traps_or_recompiles(method(), bci(), reason); } // Returns the object (if any) which was created the moment before. diff --git a/src/hotspot/share/opto/inlinetypenode.cpp b/src/hotspot/share/opto/inlinetypenode.cpp index 216ccd1361a..2f550fec89f 100644 --- a/src/hotspot/share/opto/inlinetypenode.cpp +++ b/src/hotspot/share/opto/inlinetypenode.cpp @@ -324,11 +324,11 @@ void InlineTypeNode::make_scalar_in_safepoint(PhaseIterGVN* igvn, Unique_Node_Li } } -void InlineTypeNode::make_scalar_in_safepoints(PhaseIterGVN* igvn, bool allow_oop) { - make_scalar_in_safepoints(igvn, allow_oop, nullptr); +bool InlineTypeNode::make_scalar_in_safepoints(PhaseIterGVN* igvn, bool allow_oop) { + return make_scalar_in_safepoints(igvn, allow_oop, nullptr); } -void InlineTypeNode::make_scalar_in_safepoints(PhaseIterGVN* igvn, bool allow_oop, SafePointNode* safepoint) { +bool InlineTypeNode::make_scalar_in_safepoints(PhaseIterGVN* igvn, bool allow_oop, SafePointNode* safepoint) { // If the inline type has a constant or loaded oop, use the oop instead of scalarization // in the safepoint to avoid keeping field loads live just for the debug info. Node* oop = get_oop(); @@ -378,8 +378,13 @@ void InlineTypeNode::make_scalar_in_safepoints(PhaseIterGVN* igvn, bool allow_oo safepoints.push(safepoint); } + // Scalarize the inline type in all safepoint uses but first check if we + // have enough nodes left to create a new SafePointScalarObjectNode per use. + Compile* C = igvn->C; + if ((C->live_nodes() + safepoints.size() + NodeLimitFudgeFactor) > C->max_node_limit()) { + return false; + } Unique_Node_List vt_worklist; - // Process all safepoint uses and scalarize inline type while (safepoints.size() > 0) { SafePointNode* sfpt = safepoints.pop()->as_SafePoint(); if (use_oop) { @@ -397,11 +402,14 @@ void InlineTypeNode::make_scalar_in_safepoints(PhaseIterGVN* igvn, bool allow_oo // Now scalarize non-flat fields for (uint i = 0; i < vt_worklist.size(); ++i) { InlineTypeNode* vt = vt_worklist.at(i)->isa_InlineType(); - vt->make_scalar_in_safepoints(igvn); + if (!vt->make_scalar_in_safepoints(igvn)) { + return false; + } } if (outcnt() == 0) { igvn->record_for_igvn(this); } + return true; } void InlineTypeNode::load(GraphKit* kit, Node* base, Node* ptr, bool immutable_memory, bool trust_null_free_oop, DecoratorSet decorators) { @@ -598,14 +606,32 @@ static bool check_cycle(ciInlineKlass* vk) { return false; } +// Check if 'lhs' and 'rhs' are the same oop, possibly wrapped in an InlineTypeNode. +static bool same_oop(PhaseGVN* phase, Node* lhs, Node* rhs) { + InlineTypeNode* lhs_inline = lhs->isa_InlineType(); + if (lhs_inline != nullptr && lhs_inline->is_allocated(phase)) { + lhs = lhs_inline->get_oop(); + } + InlineTypeNode* rhs_inline = rhs->isa_InlineType(); + if (rhs_inline != nullptr && rhs_inline->is_allocated(phase)) { + rhs = rhs_inline->get_oop(); + } + return lhs->eqv_uncast(rhs); +} + // Check if a substitutability check between 'lhs' and 'rhs' can be implemented in IR -bool InlineTypeNode::can_emit_substitutability_check(Node* lhs, Node* rhs) { +bool InlineTypeNode::can_emit_substitutability_check(PhaseGVN* phase, Node* lhs, Node* rhs) { + // We can't create new InlineTypeNodes after macro expansion + if (!phase->C->allow_macro_nodes()) { + return false; + } + if (!lhs->bottom_type()->isa_ptr() || (rhs != nullptr && !rhs->bottom_type()->isa_ptr())) { return false; } - if (rhs != nullptr && lhs->eqv_uncast(rhs)) { + if (rhs != nullptr && same_oop(phase, lhs, rhs)) { return true; } @@ -641,7 +667,7 @@ bool InlineTypeNode::can_emit_substitutability_check(Node* lhs, Node* rhs) { Node* lhs_fv = lhs_inline->field_value(i); Node* rhs_fv = rhs_inline != nullptr ? rhs_inline->field_value(i) : nullptr; - if (!can_emit_substitutability_check(lhs_fv, rhs_fv)) { + if (!can_emit_substitutability_check(phase, lhs_fv, rhs_fv)) { return false; } } @@ -696,7 +722,7 @@ static Node* emit_substitutability_check_pointer(GraphKit* kit, PhiNode* result, } Node* cmp = nullptr; - if (lhs->eqv_uncast(rhs)) { + if (same_oop(&gvn, lhs, rhs)) { cmp = kit->intcon(0); } else if (!lhs_type->is_ptr()->can_be_inline_type() || !rhs_type->is_ptr()->can_be_inline_type()) { // If one of the sides is not a value object, can only be substitutable if they are the same @@ -2287,10 +2313,12 @@ const Type* LoadFlatNode::Value(PhaseGVN* phase) const { } const Type* StoreFlatNode::Value(PhaseGVN* phase) const { + Node* val = in(TypeFunc::Parms + 2); if (phase->type(in(TypeFunc::Control)) == Type::TOP || phase->type(in(TypeFunc::Memory)) == Type::TOP || - phase->type(base()) == Type::TOP || phase->type(ptr()) == Type::TOP || phase->type(value()) == Type::TOP) { + phase->type(base()) == Type::TOP || phase->type(ptr()) == Type::TOP || phase->type(val) == Type::TOP) { return Type::TOP; } + assert(val->is_InlineType(), "must be InlineTypeNode: %s", val->Name()); return bottom_type(); } diff --git a/src/hotspot/share/opto/inlinetypenode.hpp b/src/hotspot/share/opto/inlinetypenode.hpp index af19c5a5535..3c1dea99ba6 100644 --- a/src/hotspot/share/opto/inlinetypenode.hpp +++ b/src/hotspot/share/opto/inlinetypenode.hpp @@ -122,9 +122,9 @@ public: uint field_index(int offset) const; // Replace InlineTypeNodes in debug info at safepoints with SafePointScalarObjectNodes - void make_scalar_in_safepoints(PhaseIterGVN* igvn, bool allow_oop = true); + [[nodiscard]] bool make_scalar_in_safepoints(PhaseIterGVN* igvn, bool allow_oop = true); // Variant that allows to limit to a single safepoint. If nullptr is given, all safepoint uses will be considered. - void make_scalar_in_safepoints(PhaseIterGVN* igvn, bool allow_oop, SafePointNode* safepoint); + [[nodiscard]] bool make_scalar_in_safepoints(PhaseIterGVN* igvn, bool allow_oop, SafePointNode* safepoint); // Store the inline type as a flat (headerless) representation void store_flat(GraphKit* kit, Node* base, Node* ptr, bool atomic, bool immutable_memory, bool null_free, DecoratorSet decorators); @@ -132,7 +132,7 @@ public: void store_flat_array(GraphKit* kit, Node* base, Node* idx); // Implementation of the substitutability check for acmp - static bool can_emit_substitutability_check(Node* lhs, Node* rhs); + static bool can_emit_substitutability_check(PhaseGVN* phase, Node* lhs, Node* rhs); static Node* emit_substitutability_check(GraphKit* kit, Node* lhs, Node* rhs); // Allocates the inline type (if not yet allocated) diff --git a/src/hotspot/share/opto/library_call.cpp b/src/hotspot/share/opto/library_call.cpp index ad0a720c211..217b613b58e 100644 --- a/src/hotspot/share/opto/library_call.cpp +++ b/src/hotspot/share/opto/library_call.cpp @@ -64,6 +64,7 @@ #include "prims/jvmtiExport.hpp" #include "prims/jvmtiThreadState.hpp" #include "prims/unsafe.hpp" +#include "runtime/arguments.hpp" #include "runtime/globals.hpp" #include "runtime/jniHandles.inline.hpp" #include "runtime/mountUnmountDisabler.hpp" @@ -2782,13 +2783,17 @@ bool LibraryCallKit::inline_unsafe_flat_access(bool is_store, AccessKind kind) { value = new_value; } - assert(value_type->inline_klass() == value_klass, "value is of type %s while valueType is %s", value_type->inline_klass()->name()->as_utf8(), value_klass->name()->as_utf8()); + assert(value_type == TypePtr::NULL_PTR || value_type->inline_klass() == value_klass, + "value is of type %s while value klass is %s", value_type->inline_klass()->name()->as_utf8(), value_klass->name()->as_utf8()); if (layout == LayoutKind::REFERENCE) { const TypePtr* ptr_type = (decorators & C2_MISMATCHED) != 0 ? TypeRawPtr::BOTTOM : _gvn.type(ptr)->is_ptr(); access_store_at(base, ptr, ptr_type, value, value_type, T_OBJECT, decorators); } else { bool atomic = LayoutKindHelper::is_atomic_flat(layout); bool null_free = !LayoutKindHelper::is_nullable_flat(layout); + if (null_free) { + null_check(value); + } value->as_InlineType()->store_flat(this, base, ptr, atomic, immutable_memory, null_free, decorators); } @@ -4686,6 +4691,7 @@ bool LibraryCallKit::inline_native_subtype_check() { // {P,P} & superc!=subc => false _prim_same_path, // {P,P} & superc==subc => true _prim_1_path, // {N,P} => false + _ref_same_path, // {N,N} & superk==subk => true _ref_subtype_path, // {N,N} & subtype check wins => true _both_ref_path, // {N,N} & subtype check loses => false PATH_LIMIT @@ -4733,6 +4739,16 @@ bool LibraryCallKit::inline_native_subtype_check() { // now we have two reference types, in klasses[0..1] Node* subk = klasses[1]; // the argument to isAssignableFrom Node* superk = klasses[0]; // the receiver + + // gen_subtype_check() refines exact array superklasses for comparison with + // (refined) klasses loaded from the header. Since both operands here are unrefined + // klasses, handle equality first. Unequal types then use the regular hierarchy check. + Node* cmp = _gvn.transform(new CmpPNode(subk, superk)); + Node* bol = _gvn.transform(new BoolNode(cmp, BoolTest::eq)); + IfNode* iff = create_and_xform_if(control(), bol, PROB_STATIC_FREQUENT, COUNT_UNKNOWN); + region->set_req(_ref_same_path, _gvn.transform(new IfTrueNode(iff))); + set_control(_gvn.transform(new IfFalseNode(iff))); + region->set_req(_both_ref_path, gen_subtype_check(subk, superk)); region->set_req(_ref_subtype_path, control()); } @@ -4757,6 +4773,7 @@ bool LibraryCallKit::inline_native_subtype_check() { // these are the only paths that produce 'true': phi->set_req(_prim_same_path, intcon(1)); + phi->set_req(_ref_same_path, intcon(1)); phi->set_req(_ref_subtype_path, intcon(1)); // pull together the cases: @@ -5227,11 +5244,13 @@ bool LibraryCallKit::inline_array_copyOf(bool is_copyOfRange) { // should be thrown generate_negative_guard(length, bailout, &length); - // Handle inline type arrays - // TODO 8251971 This is too strong - generate_fair_guard(flat_array_test(load_object_klass(original)), bailout); - generate_fair_guard(flat_array_test(refined_klass_node), bailout); - generate_fair_guard(null_free_array_test(original), bailout); + if (Arguments::is_valhalla_enabled()) { + // Handle inline type arrays + // TODO 8251971 This is too strong + generate_fair_guard(flat_array_test(load_object_klass(original)), bailout); + generate_fair_guard(flat_array_test(refined_klass_node), bailout); + generate_fair_guard(null_free_array_test(original), bailout); + } // Bail out if start is larger than the original length Node* orig_tail = _gvn.transform(new SubINode(orig_length, start)); @@ -6844,32 +6863,34 @@ bool LibraryCallKit::inline_arraycopy() { slow_region->add_req(not_subtype_ctrl); } - // TODO 8251971 Improve this. What about atomicity? Make sure this is always folded for type arrays. - // If destination is null-restricted, source must be null-restricted as well: src_null_restricted || !dst_null_restricted - Node* src_klass = load_object_klass(src); - Node* adr_prop_src = basic_plus_adr(top(), src_klass, in_bytes(ArrayKlass::properties_offset())); - Node* prop_src = _gvn.transform(LoadNode::make(_gvn, control(), immutable_memory(), adr_prop_src, - _gvn.type(adr_prop_src)->is_ptr(), TypeInt::INT, T_INT, - MemNode::unordered)); - Node* adr_prop_dest = basic_plus_adr(top(), refined_dest_klass, in_bytes(ArrayKlass::properties_offset())); - Node* prop_dest = _gvn.transform(LoadNode::make(_gvn, control(), immutable_memory(), adr_prop_dest, - _gvn.type(adr_prop_dest)->is_ptr(), TypeInt::INT, T_INT, - MemNode::unordered)); + if (Arguments::is_valhalla_enabled()) { + // TODO 8251971 Improve this. What about atomicity? Make sure this is always folded for type arrays. + // If destination is null-restricted, source must be null-restricted as well: src_null_restricted || !dst_null_restricted + Node* src_klass = load_object_klass(src); + Node* adr_prop_src = basic_plus_adr(top(), src_klass, in_bytes(ArrayKlass::properties_offset())); + Node* prop_src = _gvn.transform(LoadNode::make(_gvn, control(), immutable_memory(), adr_prop_src, + _gvn.type(adr_prop_src)->is_ptr(), TypeInt::INT, T_INT, + MemNode::unordered)); + Node* adr_prop_dest = basic_plus_adr(top(), refined_dest_klass, in_bytes(ArrayKlass::properties_offset())); + Node* prop_dest = _gvn.transform(LoadNode::make(_gvn, control(), immutable_memory(), adr_prop_dest, + _gvn.type(adr_prop_dest)->is_ptr(), TypeInt::INT, T_INT, + MemNode::unordered)); - const ArrayProperties props_null_restricted = ArrayProperties::Default().with_null_restricted(); - jint props_value = (jint)props_null_restricted.value(); + const ArrayProperties props_null_restricted = ArrayProperties::Default().with_null_restricted(); + jint props_value = (jint)props_null_restricted.value(); - prop_dest = _gvn.transform(new XorINode(prop_dest, intcon(props_value))); - prop_src = _gvn.transform(new OrINode(prop_dest, prop_src)); - prop_src = _gvn.transform(new AndINode(prop_src, intcon(props_value))); + prop_dest = _gvn.transform(new XorINode(prop_dest, intcon(props_value))); + prop_src = _gvn.transform(new OrINode(prop_dest, prop_src)); + prop_src = _gvn.transform(new AndINode(prop_src, intcon(props_value))); - Node* chk = _gvn.transform(new CmpINode(prop_src, intcon(props_value))); - Node* tst = _gvn.transform(new BoolNode(chk, BoolTest::ne)); - generate_fair_guard(tst, slow_region); + Node* chk = _gvn.transform(new CmpINode(prop_src, intcon(props_value))); + Node* tst = _gvn.transform(new BoolNode(chk, BoolTest::ne)); + generate_fair_guard(tst, slow_region); - // TODO 8251971 This is too strong - generate_fair_guard(flat_array_test(src), slow_region); - generate_fair_guard(flat_array_test(dest), slow_region); + // TODO 8251971 This is too strong + generate_fair_guard(flat_array_test(src), slow_region); + generate_fair_guard(flat_array_test(dest), slow_region); + } { PreserveJVMState pjvms(this); diff --git a/src/hotspot/share/opto/loopnode.hpp b/src/hotspot/share/opto/loopnode.hpp index e1f4300949a..dc4383b6917 100644 --- a/src/hotspot/share/opto/loopnode.hpp +++ b/src/hotspot/share/opto/loopnode.hpp @@ -84,10 +84,10 @@ protected: LoopNestInnerLoop = 1<<15, LoopNestLongOuterLoop = 1<<16, MultiversionFastLoop = 1<<17, - MultiversionSlowLoop = 2<<17, + MultiversionSlowLoop = 2<<17, // 1<<18 MultiversionDelayedSlowLoop = 3<<17, MultiversionFlagsMask = 3<<17, - FlatArrays = 1<<18}; + FlatArrays = 1<<19}; char _unswitch_count; enum { _unswitch_max=3 }; diff --git a/src/hotspot/share/opto/loopopts.cpp b/src/hotspot/share/opto/loopopts.cpp index 2077b232cef..e26fa68a870 100644 --- a/src/hotspot/share/opto/loopopts.cpp +++ b/src/hotspot/share/opto/loopopts.cpp @@ -1133,7 +1133,7 @@ void PhaseIdealLoop::move_flat_array_check_out_of_loop(Node* n) { return; } Node* mem = n->in(FlatArrayCheckNode::Memory); - Node* array = n->in(FlatArrayCheckNode::ArrayOrKlass)->uncast(); + Node* array = n->in(FlatArrayCheckNode::ArrayOrKlass); IdealLoopTree* check_loop = get_loop(get_ctrl(n)); IdealLoopTree* ary_loop = get_loop(get_ctrl(array)); diff --git a/src/hotspot/share/opto/macro.cpp b/src/hotspot/share/opto/macro.cpp index 443b3a3857e..fe0a513bc09 100644 --- a/src/hotspot/share/opto/macro.cpp +++ b/src/hotspot/share/opto/macro.cpp @@ -1316,7 +1316,10 @@ bool PhaseMacroExpand::scalar_replacement(AllocateNode* alloc, Unique_Node_List& bool allow_oop = (res_type != nullptr) && !res_type->is_flat(); for (uint i = 0; i < value_worklist.size(); ++i) { InlineTypeNode* vt = value_worklist.at(i)->as_InlineType(); - vt->make_scalar_in_safepoints(&_igvn, allow_oop); + if (!vt->make_scalar_in_safepoints(&_igvn, allow_oop)) { + C->record_failure("out of nodes during scalarization"); + return false; + } } return true; } @@ -3294,6 +3297,9 @@ void PhaseMacroExpand::eliminate_macro_nodes(bool eliminate_locks) { BarrierSet::barrier_set()->barrier_set_c2()->is_gc_barrier_node(n), "unknown node type in macro list"); } + if (C->failing()) { + return; + } assert(success == (C->macro_count() < old_macro_count), "elimination reduces macro count"); progress = progress || success; if (success) { diff --git a/src/hotspot/share/opto/macroArrayCopy.cpp b/src/hotspot/share/opto/macroArrayCopy.cpp index 0bfc1fc6569..678c083f808 100644 --- a/src/hotspot/share/opto/macroArrayCopy.cpp +++ b/src/hotspot/share/opto/macroArrayCopy.cpp @@ -33,6 +33,7 @@ #include "opto/macro.hpp" #include "opto/runtime.hpp" #include "opto/vectornode.hpp" +#include "runtime/arguments.hpp" #include "runtime/stubRoutines.hpp" #include "utilities/align.hpp" #include "utilities/powerOfTwo.hpp" @@ -1604,7 +1605,7 @@ void PhaseMacroExpand::expand_arraycopy_node(ArrayCopyNode *ac) { // TODO 8251971 This is too strong // We need to be careful here because 'adjust_for_flat_array' will adjust offsets/length etc. which then does not work anymore for the slow call to SharedRuntime::slow_arraycopy_C. assert(top_src->is_flat() == top_dest->is_flat(), "must have bailed out before"); - if (!flat_and_same_nullness) { + if (Arguments::is_valhalla_enabled() && !flat_and_same_nullness) { generate_flat_array_guard(&ctrl, src, merge_mem, slow_region); generate_flat_array_guard(&ctrl, dest, merge_mem, slow_region); generate_null_free_array_guard(&ctrl, dest, merge_mem, slow_region); diff --git a/src/hotspot/share/opto/matcher.cpp b/src/hotspot/share/opto/matcher.cpp index 6c44c0beadd..11d14cb090a 100644 --- a/src/hotspot/share/opto/matcher.cpp +++ b/src/hotspot/share/opto/matcher.cpp @@ -168,7 +168,7 @@ void Matcher::verify_new_nodes_only(Node* xroot) { // Array of RegMask, one per returned values (inline type instances can // be returned as multiple return values, one per field) -RegMask* Matcher::return_values_mask(const TypeFunc* tf) { +RegMask* Matcher::return_values_mask(const TypeFunc* tf) const { const TypeTuple* range = tf->range_cc(); uint cnt = range->cnt() - TypeFunc::Parms; if (cnt == 0) { @@ -1089,11 +1089,7 @@ Node *Matcher::xform( Node *n, int max_stack ) { } if (m == nullptr) { // Convert to machine-dependent projection - RegMask* mask = nullptr; - if (n->in(0)->is_Call() && n->in(0)->as_Call()->tf()->returns_inline_type_as_fields()) { - mask = return_values_mask(n->in(0)->as_Call()->tf()); - } - m = n->in(0)->as_Multi()->match(n->as_Proj(), this, mask); + m = n->in(0)->as_Multi()->match(n->as_Proj(), this); NOT_PRODUCT(record_new2old(m, n);) } if (m->in(0) != nullptr) // m might be top diff --git a/src/hotspot/share/opto/matcher.hpp b/src/hotspot/share/opto/matcher.hpp index 2308e3cdaaf..07b8d1c6cd9 100644 --- a/src/hotspot/share/opto/matcher.hpp +++ b/src/hotspot/share/opto/matcher.hpp @@ -264,7 +264,7 @@ public: // Helper for match OptoReg::Name warp_incoming_stk_arg( VMReg reg ); - RegMask* return_values_mask(const TypeFunc* tf); + RegMask* return_values_mask(const TypeFunc* tf) const; // Transform, then walk. Does implicit DCE while walking. // Name changed from "transform" to avoid it being virtual. diff --git a/src/hotspot/share/opto/memnode.cpp b/src/hotspot/share/opto/memnode.cpp index 0f86ba5fc1e..94dd9306771 100644 --- a/src/hotspot/share/opto/memnode.cpp +++ b/src/hotspot/share/opto/memnode.cpp @@ -2149,20 +2149,27 @@ AllocateNode* LoadNode::is_new_object_mark_load() const { return nullptr; } - -//------------------------------Ideal------------------------------------------ // If the load is from Field memory and the pointer is non-null, it might be possible to // zero out the control input. // If the offset is constant and the base is an object allocation, // try to hook me up to the exact initializing store. -Node *LoadNode::Ideal(PhaseGVN *phase, bool can_reshape) { - if (has_pinned_control_dependency()) { - return nullptr; - } - Node* p = MemNode::Ideal_common(phase, can_reshape); - if (p) return (p == NodeSentinel) ? nullptr : p; +Node* LoadNode::Ideal(PhaseGVN* phase, bool can_reshape) { + if (has_pinned_control_dependency()) { return nullptr; } + Node* p = Ideal_load_common(phase, can_reshape); + if (p == NodeSentinel) { return nullptr; } - Node* ctrl = in(MemNode::Control); + if (p == nullptr && !can_reshape) { + phase->record_for_igvn(this); + } + + return p; +} + +Node* LoadNode::Ideal_load_common(PhaseGVN* phase, bool can_reshape) { + Node* p = MemNode::Ideal_common(phase, can_reshape); + if (p != nullptr) { return p; } + + Node* ctrl = in(MemNode::Control); Node* address = in(MemNode::Address); bool addr_mark = ((phase->type(address)->isa_oopptr() || phase->type(address)->isa_narrowoop()) && @@ -2180,9 +2187,9 @@ Node *LoadNode::Ideal(PhaseGVN *phase, bool can_reshape) { } intptr_t ignore = 0; - Node* base = AddPNode::Ideal_base_and_offset(address, phase, ignore); - if (base != nullptr - && phase->C->get_alias_index(phase->type(address)->is_ptr()) != Compile::AliasIdxRaw) { + Node* base = AddPNode::Ideal_base_and_offset(address, phase, ignore); + if (base != nullptr && + phase->C->get_alias_index(phase->type(address)->is_ptr()) != Compile::AliasIdxRaw) { // Check for useless control edge in some common special cases if (in(MemNode::Control) != nullptr // TODO 8350865 Can we re-enable this? @@ -2197,26 +2204,26 @@ Node *LoadNode::Ideal(PhaseGVN *phase, bool can_reshape) { } Node* mem = in(MemNode::Memory); - const TypePtr *addr_t = phase->type(address)->isa_ptr(); + const TypePtr* addr_t = phase->type(address)->isa_ptr(); if (can_reshape && (addr_t != nullptr)) { // try to optimize our memory input Node* opt_mem = MemNode::optimize_memory_chain(mem, addr_t, this, phase); if (opt_mem != mem) { set_req_X(MemNode::Memory, opt_mem, phase); - if (phase->type( opt_mem ) == Type::TOP) return nullptr; + if (phase->type(opt_mem) == Type::TOP) { return NodeSentinel; } return this; } - const TypeOopPtr *t_oop = addr_t->isa_oopptr(); + const TypeOopPtr* t_oop = addr_t->isa_oopptr(); if ((t_oop != nullptr) && (t_oop->is_known_instance_field() || t_oop->is_ptr_to_boxed_value())) { - PhaseIterGVN *igvn = phase->is_IterGVN(); + PhaseIterGVN* igvn = phase->is_IterGVN(); assert(igvn != nullptr, "must be PhaseIterGVN when can_reshape is true"); if (igvn->_worklist.member(opt_mem)) { // Delay this transformation until memory Phi is processed. igvn->_worklist.push(this); - return nullptr; + return NodeSentinel; } // Split instance field load through Phi. Node* result = split_through_phi(phase); @@ -2234,7 +2241,7 @@ Node *LoadNode::Ideal(PhaseGVN *phase, bool can_reshape) { // barriers etc.) alone if (in(0) != nullptr && !adr_type()->isa_rawptr() && can_reshape) { for (DUIterator_Fast imax, i = mem->fast_outs(imax); i < imax; i++) { - Node *use = mem->fast_out(i); + Node* use = mem->fast_out(i); if (use != this && use->Opcode() == Opcode() && use->in(0) != nullptr && @@ -2287,10 +2294,6 @@ Node *LoadNode::Ideal(PhaseGVN *phase, bool can_reshape) { } } - if (!can_reshape) { - phase->record_for_igvn(this); - } - return nullptr; } @@ -2909,16 +2912,23 @@ Node* LoadKlassNode::Identity(PhaseGVN* phase) { Node* LoadNode::klass_identity_common(PhaseGVN* phase) { Node* x = LoadNode::Identity(phase); - if (x != this) return x; + if (x != this) { return x; } + Node* k = find_known_klass(phase); + return k == nullptr ? this : k; +} + +// Find an existing Klass node from a recognized allocation or +// class-mirror pattern. +Node* LoadNode::find_known_klass(PhaseGVN* phase) const { // Take apart the address into an oop and offset. - // Return 'this' if we cannot. - Node* adr = in(MemNode::Address); + // Return 'nullptr' if we cannot. + Node* adr = in(MemNode::Address); intptr_t offset = 0; - Node* base = AddPNode::Ideal_base_and_offset(adr, phase, offset); - if (base == nullptr) return this; + Node* base = AddPNode::Ideal_base_and_offset(adr, phase, offset); + if (base == nullptr) { return nullptr; } const TypeOopPtr* toop = phase->type(adr)->isa_oopptr(); - if (toop == nullptr) return this; + if (toop == nullptr) { return nullptr; } // Step over potential GC barrier for OopHandle resolve BarrierSetC2* bs = BarrierSet::barrier_set()->barrier_set_c2(); @@ -2973,7 +2983,7 @@ Node* LoadNode::klass_identity_common(PhaseGVN* phase) { } } - return this; + return nullptr; } LoadNode* LoadNode::clone_pinned() const { @@ -2998,7 +3008,32 @@ LoadNode* LoadNode::pin_node_under_control_impl() const { return nullptr; } -//------------------------------Value------------------------------------------ +Node* LoadNKlassNode::Ideal(PhaseGVN* phase, bool can_reshape) { + bool pinned = has_pinned_control_dependency(); + if (!pinned) { + Node* p = Ideal_load_common(phase, can_reshape); + if (p == NodeSentinel) { return nullptr; } + if (p != nullptr) { return p; } + } + + // To clean up reflective code, simplify k.java_mirror.as_klass to narrow k. + // Also feed through the klass in Allocate(...klass...)._klass. + Node* k = find_known_klass(phase); + if (k != nullptr) { + const Type* t = phase->type(k); + if (t != Type::TOP) { + assert(t->isa_klassptr(), "must be a klass pointer"); + return new EncodePKlassNode(k, t->make_narrowklass()); + } + } + + if (!pinned && !can_reshape) { + phase->record_for_igvn(this); + } + + return nullptr; +} + const Type* LoadNKlassNode::Value(PhaseGVN* phase) const { const Type *t = klass_value_common(phase); if (t == Type::TOP) @@ -3007,18 +3042,11 @@ const Type* LoadNKlassNode::Value(PhaseGVN* phase) const { return t->make_narrowklass(); } -//------------------------------Identity--------------------------------------- -// To clean up reflective code, simplify k.java_mirror.as_klass to narrow k. -// Also feed through the klass in Allocate(...klass...)._klass. Node* LoadNKlassNode::Identity(PhaseGVN* phase) { - Node *x = klass_identity_common(phase); - - const Type *t = phase->type( x ); - if( t == Type::TOP ) return x; - if( t->isa_narrowklass()) return x; - assert (!t->isa_narrowoop(), "no narrow oop here"); - - return phase->transform(new EncodePKlassNode(x, t->make_narrowklass())); + Node* x = klass_identity_common(phase); + const Type* t = phase->type(x); + if (t == Type::TOP || t->isa_narrowklass()) { return x; } + return this; } //------------------------------Value----------------------------------------- @@ -4815,7 +4843,7 @@ const Type* MemBarNode::Value(PhaseGVN* phase) const { //------------------------------match------------------------------------------ // Construct projections for memory. -Node *MemBarNode::match(const ProjNode *proj, const Matcher *m, const RegMask* mask) { +Node* MemBarNode::match(const ProjNode* proj, const Matcher* m) { switch (proj->_con) { case TypeFunc::Control: case TypeFunc::Memory: diff --git a/src/hotspot/share/opto/memnode.hpp b/src/hotspot/share/opto/memnode.hpp index ee60e7fefaa..126acf4d002 100644 --- a/src/hotspot/share/opto/memnode.hpp +++ b/src/hotspot/share/opto/memnode.hpp @@ -271,6 +271,7 @@ protected: virtual Node* find_previous_arraycopy(PhaseValues* phase, Node* ld_alloc, Node*& mem, bool can_see_stored_value) const; Node* can_see_stored_value_through_membars(Node* st, PhaseValues* phase) const; + Node* Ideal_load_common(PhaseGVN* phase, bool can_reshape); public: LoadNode(Node *c, Node *mem, Node *adr, const TypePtr* at, const Type *rt, MemOrd mo, ControlDependency control_dependency) @@ -322,6 +323,7 @@ public: // Common methods for LoadKlass and LoadNKlass nodes. const Type* klass_value_common(PhaseGVN* phase) const; Node* klass_identity_common(PhaseGVN* phase); + Node* find_known_klass(PhaseGVN* phase) const; virtual uint ideal_reg() const; virtual const Type *bottom_type() const; @@ -618,6 +620,7 @@ public: virtual const Type* Value(PhaseGVN* phase) const; virtual Node* Identity(PhaseGVN* phase); + virtual Node* Ideal(PhaseGVN* phase, bool can_reshape); }; //------------------------------StoreNode-------------------------------------- @@ -1151,13 +1154,16 @@ public: //------------------------------ClearArray------------------------------------- class ClearArrayNode: public Node { private: + // True if cnt is larger than InitArrayShortSize bool _is_large; - bool _word_copy_only; + // True if the fill value is a non-constant or non-zero 64-bit value. Such a + // value must be copied as a complete word and cannot use byte-wise zeroing. + bool _requires_word_fill; static Node* make_address(Node* dest, Node* offset, bool raw_base, PhaseGVN* phase); public: ClearArrayNode( Node *ctrl, Node *arymem, Node *word_cnt, Node *base, Node* val, bool is_large) : Node(ctrl, arymem, word_cnt, base, val), _is_large(is_large), - _word_copy_only(val->bottom_type()->isa_long() && (!val->bottom_type()->is_long()->is_con() || val->bottom_type()->is_long()->get_con() != 0)) { + _requires_word_fill(val->bottom_type()->isa_long() && (!val->bottom_type()->is_long()->is_con() || val->bottom_type()->is_long()->get_con() != 0)) { init_class_id(Class_ClearArray); } virtual int Opcode() const; @@ -1169,7 +1175,8 @@ public: virtual Node *Ideal(PhaseGVN *phase, bool can_reshape); virtual uint match_edge(uint idx) const; bool is_large() const { return _is_large; } - bool word_copy_only() const { return _word_copy_only; } + bool is_zero_fill() const { return !_requires_word_fill; } + bool requires_word_fill() const { return _requires_word_fill; } virtual uint size_of() const { return sizeof(ClearArrayNode); } virtual uint hash() const { return Node::hash() + _is_large; } virtual bool cmp(const Node& n) const { @@ -1251,7 +1258,7 @@ public: virtual Node *Ideal(PhaseGVN *phase, bool can_reshape); virtual uint match_edge(uint idx) const { return 0; } virtual const Type *bottom_type() const { return TypeTuple::MEMBAR; } - virtual Node *match(const ProjNode *proj, const Matcher *m, const RegMask* mask); + virtual Node* match(const ProjNode* proj, const Matcher* m); // Factory method. Builds a wide or narrow membar. // Optional 'precedent' becomes an extra edge if not null. static MemBarNode* make(Compile* C, int opcode, diff --git a/src/hotspot/share/opto/mulnode.cpp b/src/hotspot/share/opto/mulnode.cpp index d17dde24305..8dec62d73b3 100644 --- a/src/hotspot/share/opto/mulnode.cpp +++ b/src/hotspot/share/opto/mulnode.cpp @@ -626,7 +626,7 @@ UMulHiLoLNode* UMulHiLoLNode::make(Node* umul_hi) { return umul_hi_lo; } -Node* MulHiLoLNode::match(const ProjNode* proj, const Matcher* match, const RegMask*) { +Node* MulHiLoLNode::match(const ProjNode* proj, const Matcher* match) { uint ideal_reg = proj->ideal_reg(); RegMask rm; if (proj->_con == first_proj_num) { diff --git a/src/hotspot/share/opto/mulnode.hpp b/src/hotspot/share/opto/mulnode.hpp index 6faa7222342..f26137dfe49 100644 --- a/src/hotspot/share/opto/mulnode.hpp +++ b/src/hotspot/share/opto/mulnode.hpp @@ -217,7 +217,7 @@ public: virtual int Opcode() const; virtual const Type* bottom_type() const { return TypeTuple::LONG_PAIR; } - virtual Node* match(const ProjNode* proj, const Matcher* m, const RegMask* mask); + virtual Node* match(const ProjNode* proj, const Matcher* m); static MulHiLoLNode* make(Node* mul_hi); }; diff --git a/src/hotspot/share/opto/multnode.cpp b/src/hotspot/share/opto/multnode.cpp index fb6fc99f57b..7223a08e876 100644 --- a/src/hotspot/share/opto/multnode.cpp +++ b/src/hotspot/share/opto/multnode.cpp @@ -40,7 +40,7 @@ const RegMask &MultiNode::out_RegMask() const { return RegMask::EMPTY; } -Node *MultiNode::match(const ProjNode *proj, const Matcher *m, const RegMask* mask) { return proj->clone(); } +Node* MultiNode::match(const ProjNode* proj, const Matcher* m) { return proj->clone(); } //------------------------------proj_out--------------------------------------- // Get a named projection or null if not found diff --git a/src/hotspot/share/opto/multnode.hpp b/src/hotspot/share/opto/multnode.hpp index 5465222c939..6e07421a486 100644 --- a/src/hotspot/share/opto/multnode.hpp +++ b/src/hotspot/share/opto/multnode.hpp @@ -43,7 +43,7 @@ public: virtual bool is_CFG() const { return true; } virtual uint hash() const { return NO_HASH; } // CFG nodes do not hash virtual const RegMask &out_RegMask() const; - virtual Node *match(const ProjNode *proj, const Matcher *m, const RegMask* mask); + virtual Node* match(const ProjNode* proj, const Matcher* m); virtual uint ideal_reg() const { return NotAMachineReg; } ProjNode* proj_out(uint which_proj) const; // Get a named projection ProjNode* proj_out_or_null(uint which_proj) const; diff --git a/src/hotspot/share/opto/parse.hpp b/src/hotspot/share/opto/parse.hpp index 0a32bee9a07..2f0f8558d84 100644 --- a/src/hotspot/share/opto/parse.hpp +++ b/src/hotspot/share/opto/parse.hpp @@ -490,7 +490,7 @@ class Parse : public GraphKit { void do_one_bytecode(); // helper function to generate array store check - Node* array_store_check(Node*& adr, const Type*& elemtype); + Node* array_store_check(const Type*& elemtype); // Helper function to generate array load void array_load(BasicType etype); Node* load_from_unknown_flat_array(Node* array, Node* array_index, const TypeOopPtr* element_ptr); diff --git a/src/hotspot/share/opto/parse1.cpp b/src/hotspot/share/opto/parse1.cpp index e9305697604..2d1e0550fb6 100644 --- a/src/hotspot/share/opto/parse1.cpp +++ b/src/hotspot/share/opto/parse1.cpp @@ -1105,7 +1105,7 @@ void Parse::do_exits() { // such unusual early publications. But no barrier is needed on // exceptional returns, since they cannot publish normally. // - if ((method()->is_object_constructor() || method()->is_class_initializer()) && + if (method()->is_object_constructor() && (wrote_non_strict_final() || wrote_stable() || (AlwaysSafeConstructors && wrote_fields()) || (support_IRIW_for_not_multiple_copy_atomic_cpu && wrote_volatile()))) { diff --git a/src/hotspot/share/opto/parse2.cpp b/src/hotspot/share/opto/parse2.cpp index 612c32da7ae..397c6421c6d 100644 --- a/src/hotspot/share/opto/parse2.cpp +++ b/src/hotspot/share/opto/parse2.cpp @@ -204,7 +204,7 @@ void Parse::array_store(BasicType bt) { Node* stored_value_casted = nullptr; if (bt == T_OBJECT) { - stored_value_casted = array_store_check(adr, elemtype); + stored_value_casted = array_store_check(elemtype); if (stopped()) { return; } @@ -581,7 +581,7 @@ Node* Parse::speculate_non_flat_array(Node* const array, const TypeAryPtr* const !too_many_traps_or_recompiles(Deoptimization::Reason_speculate_class_check)) { flat_array = false; reason = Deoptimization::Reason_speculate_class_check; - } else if (UseArrayLoadStoreProfile && !too_many_traps_or_recompiles(reason)) { + } else if (UseArrayLoadStoreProfile && !too_many_traps_or_recompiles(Deoptimization::Reason_class_check)) { ciKlass* profiled_array_type = nullptr; ciKlass* profiled_element_type = nullptr; ProfilePtrKind element_ptr = ProfileMaybeNull; diff --git a/src/hotspot/share/opto/parseHelper.cpp b/src/hotspot/share/opto/parseHelper.cpp index 3b12d8fa3b7..b3638a822e7 100644 --- a/src/hotspot/share/opto/parseHelper.cpp +++ b/src/hotspot/share/opto/parseHelper.cpp @@ -140,10 +140,9 @@ void Parse::do_instanceof() { //------------------------------array_store_check------------------------------ // pull array from stack and check that the store is valid -Node* Parse::array_store_check(Node*& adr, const Type*& elemtype) { +Node* Parse::array_store_check(const Type*& elemtype) { // Shorthand access to array store elements without popping them. Node *obj = peek(0); - Node *idx = peek(1); Node *ary = peek(2); if (_gvn.type(obj) == TypePtr::NULL_PTR) { @@ -233,11 +232,7 @@ Node* Parse::array_store_check(Node*& adr, const Type*& elemtype) { Node* cast = _gvn.transform(new CheckCastPPNode(control(), ary, extak->as_exact_instance_type())); replace_in_map(ary, cast); ary = cast; - - // Recompute element type and address - const TypeAryPtr* arytype = _gvn.type(ary)->is_aryptr(); - elemtype = arytype->elem(); - adr = array_element_address(ary, idx, T_OBJECT, arytype->size(), control()); + elemtype = _gvn.type(ary)->is_aryptr()->elem(); CompileLog* log = C->log(); if (log != nullptr) { diff --git a/src/hotspot/share/opto/runtime.cpp b/src/hotspot/share/opto/runtime.cpp index 970e5e43f4d..b5905bdd99e 100644 --- a/src/hotspot/share/opto/runtime.cpp +++ b/src/hotspot/share/opto/runtime.cpp @@ -363,8 +363,8 @@ JRT_BLOCK_ENTRY(void, OptoRuntime::new_array_C(Klass* array_type, int len, oopDe result = oopFactory::new_typeArray(elem_type, len, THREAD); } else { Handle holder(current, array_type->klass_holder()); // keep the array klass alive - ObjArrayKlass* oak = ObjArrayKlass::cast(array_type); - result = oopFactory::new_objArray(oak->element_klass(), len, oak->properties(), THREAD); + result = ObjArrayKlass::cast(array_type)->allocate_instance(len, THREAD); + assert(HAS_PENDING_EXCEPTION || result->klass() == array_type, "array klass must be preserved"); if (!HAS_PENDING_EXCEPTION && array_type->is_null_free_array_klass() && !h_init_val.is_null()) { // Null-free arrays need to be initialized #ifdef ASSERT diff --git a/src/hotspot/share/prims/jvmtiExport.hpp b/src/hotspot/share/prims/jvmtiExport.hpp index 24992085473..858c8484090 100644 --- a/src/hotspot/share/prims/jvmtiExport.hpp +++ b/src/hotspot/share/prims/jvmtiExport.hpp @@ -322,7 +322,7 @@ class JvmtiExport : public AllStatic { static JvmtiThreadState* hide_single_stepping(JavaThread *thread) NOT_JVMTI_RETURN_(nullptr); // frame pop management - static bool has_frame_pop_for_top_frame(JavaThread *current); + static bool has_frame_pop_for_top_frame(JavaThread *current) NOT_JVMTI_RETURN_(false); // Methods that notify the debugger that something interesting has happened in the VM. static void post_early_vm_start () NOT_JVMTI_RETURN; diff --git a/src/hotspot/share/utilities/debug.cpp b/src/hotspot/share/utilities/debug.cpp index 23e8281f000..27479acd68b 100644 --- a/src/hotspot/share/utilities/debug.cpp +++ b/src/hotspot/share/utilities/debug.cpp @@ -240,10 +240,6 @@ void report_vm_out_of_memory(const char* file, int line, size_t size, VMError::report_and_die(Thread::current_or_null(), file, line, size, vm_err_type, detail_fmt, detail_args); va_end(detail_args); - - // The UseOSErrorReporting option in report_and_die() may allow a return - // to here. If so then we'll have to figure out how to handle it. - guarantee(false, "report_and_die() should not return here"); } void report_should_not_call(const char* file, int line) { diff --git a/src/java.base/share/classes/java/lang/Class.java b/src/java.base/share/classes/java/lang/Class.java index 87f783e627b..0a99133712e 100644 --- a/src/java.base/share/classes/java/lang/Class.java +++ b/src/java.base/share/classes/java/lang/Class.java @@ -3427,6 +3427,8 @@ public final class Class implements java.io.Serializable, * @since 1.4 */ public boolean desiredAssertionStatus() { + if (isPrimitive() || isArray()) return false; + ClassLoader loader = classLoader; // If the loader is null this is a system class, so ask the VM if (loader == null) diff --git a/src/java.base/share/classes/jdk/internal/jrtfs/ExplodedImage.java b/src/java.base/share/classes/jdk/internal/jrtfs/ExplodedImage.java index 88c3aba2f3c..db326023cb9 100644 --- a/src/java.base/share/classes/jdk/internal/jrtfs/ExplodedImage.java +++ b/src/java.base/share/classes/jdk/internal/jrtfs/ExplodedImage.java @@ -122,7 +122,7 @@ class ExplodedImage extends SystemImage { private PathNode(String name, PathNode link) { super(name, link.getFileAttributes()); this.file = null; - this.link = Objects.requireNonNull(link); + this.link = link; this.directories = null; this.childNames = null; } diff --git a/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileSystem.java b/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileSystem.java index 0a3782d2021..fe2f59c584f 100644 --- a/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileSystem.java +++ b/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileSystem.java @@ -78,7 +78,7 @@ class JrtFileSystem extends FileSystem { private final JrtFileSystemProvider provider; private final JrtPath rootPath = new JrtPath(this, "/"); private volatile boolean isOpen; - private volatile boolean isClosable; + private final boolean isClosable; private SystemImage image; /** diff --git a/src/java.base/share/classes/module-info.java b/src/java.base/share/classes/module-info.java index fc438320078..f84f27cb4a9 100644 --- a/src/java.base/share/classes/module-info.java +++ b/src/java.base/share/classes/module-info.java @@ -232,6 +232,7 @@ module java.base { exports jdk.internal.ref to java.desktop, java.net.http, + java.smartcardio, jdk.naming.dns; exports jdk.internal.reflect to java.logging, diff --git a/src/java.base/windows/classes/sun/nio/fs/WindowsConstants.java b/src/java.base/windows/classes/sun/nio/fs/WindowsConstants.java index 8e713464f19..d8056f6f2fb 100644 --- a/src/java.base/windows/classes/sun/nio/fs/WindowsConstants.java +++ b/src/java.base/windows/classes/sun/nio/fs/WindowsConstants.java @@ -96,6 +96,7 @@ class WindowsConstants { public static final int ERROR_NOT_SAME_DEVICE = 17; public static final int ERROR_NOT_READY = 21; public static final int ERROR_SHARING_VIOLATION = 32; + public static final int ERROR_NOT_SUPPORTED = 50; public static final int ERROR_NETWORK_ACCESS_DENIED = 65; public static final int ERROR_FILE_EXISTS = 80; public static final int ERROR_INVALID_PARAMETER = 87; diff --git a/src/java.base/windows/classes/sun/nio/fs/WindowsFileAttributes.java b/src/java.base/windows/classes/sun/nio/fs/WindowsFileAttributes.java index 76422c9ecc9..31ea68645ef 100644 --- a/src/java.base/windows/classes/sun/nio/fs/WindowsFileAttributes.java +++ b/src/java.base/windows/classes/sun/nio/fs/WindowsFileAttributes.java @@ -390,16 +390,22 @@ class WindowsFileAttributes if (supportsGetFileInformationByName()) { try (NativeBuffer buffer = NativeBuffers.getNativeBuffer(SIZEOF_STAT_BASIC_INFO)) { long addr = buffer.address(); - GetFileInformationByName(path.getPathForWin32Calls(), - FileStatBasicByNameInfo, addr, - SIZEOF_STAT_BASIC_INFO); + try { + GetFileInformationByName(path.getPathForWin32Calls(), + FileStatBasicByNameInfo, addr, + SIZEOF_STAT_BASIC_INFO); - // GetFileInformationByName() doesn't follow reparse points so if - // we discover that this is a reparse point and if we're being asked - // to follow links, then drop to the slow path. - int fileAttrs = unsafe.getInt(addr + OFFSETOF_STAT_BASIC_INFO_ATTRIBUTES); - if (!isReparsePoint(fileAttrs) || !followLinks) { - return fromStatBasicInfo(addr); + // GetFileInformationByName() doesn't follow reparse points so if + // we discover that this is a reparse point and if we're being asked + // to follow links, then drop to the slow path. + int fileAttrs = unsafe.getInt(addr + OFFSETOF_STAT_BASIC_INFO_ATTRIBUTES); + if (!isReparsePoint(fileAttrs) || !followLinks) { + return fromStatBasicInfo(addr); + } + } catch (WindowsException exc) { + if (exc.lastError() != ERROR_NOT_SUPPORTED) { + throw exc; + } } } } diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/BufferingSubscriber.java b/src/java.net.http/share/classes/jdk/internal/net/http/BufferingSubscriber.java index 273f23c1c1c..c52b96ac828 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/BufferingSubscriber.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/BufferingSubscriber.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -183,7 +183,7 @@ public class BufferingSubscriber implements TrustedSubscriber } private final SequentialScheduler pushDemandedScheduler = - new SequentialScheduler(new PushDemandedTask()); + SequentialScheduler.lockingScheduler(new PushDemandedTask()); void pushDemanded() { if (cancelled.get()) @@ -191,7 +191,7 @@ public class BufferingSubscriber implements TrustedSubscriber pushDemandedScheduler.runOrSchedule(); } - class PushDemandedTask extends SequentialScheduler.CompleteRestartableTask { + class PushDemandedTask implements Runnable { @Override public void run() { try { diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/HttpClientImpl.java b/src/java.net.http/share/classes/jdk/internal/net/http/HttpClientImpl.java index ff130e90358..aacfb27e99a 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/HttpClientImpl.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/HttpClientImpl.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 @@ -1228,7 +1228,6 @@ final class HttpClientImpl extends HttpClient implements Trackable { private final Selector selector; private volatile boolean closed; private final List registrations; - private final List deregistrations; private final Logger debug; private final Logger debugtimeout; private final HttpClientImpl owner; @@ -1242,7 +1241,6 @@ final class HttpClientImpl extends HttpClient implements Trackable { debugtimeout = ref.debugtimeout; pool = ref.connectionPool(); registrations = new ArrayList<>(); - deregistrations = new ArrayList<>(); selector = Selector.open(); } @@ -1328,9 +1326,7 @@ final class HttpClientImpl extends HttpClient implements Trackable { // OK - nothing to do... } toAbort.addAll(this.registrations); - toAbort.addAll(this.deregistrations); this.registrations.clear(); - this.deregistrations.clear(); } finally { lock.unlock(); } @@ -1402,10 +1398,6 @@ final class HttpClientImpl extends HttpClient implements Trackable { assert errorList.isEmpty(); assert readyList.isEmpty(); assert resetList.isEmpty(); - for (AsyncTriggerEvent event : deregistrations) { - event.handle(); - } - deregistrations.clear(); for (AsyncEvent event : registrations) { if (event instanceof AsyncTriggerEvent) { readyList.add(event); diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/HttpConnection.java b/src/java.net.http/share/classes/jdk/internal/net/http/HttpConnection.java index 0c1388cec8a..611c54a768b 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/HttpConnection.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/HttpConnection.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 @@ -52,7 +52,6 @@ import jdk.internal.net.http.common.Demand; import jdk.internal.net.http.common.FlowTube; import jdk.internal.net.http.common.Logger; import jdk.internal.net.http.common.SequentialScheduler; -import jdk.internal.net.http.common.SequentialScheduler.DeferredCompleter; import jdk.internal.net.http.common.Log; import jdk.internal.net.http.common.Utils; @@ -554,7 +553,7 @@ abstract class HttpConnection implements Closeable { volatile Flow.Subscriber> subscriber; volatile HttpWriteSubscription subscription; final SequentialScheduler writeScheduler = - new SequentialScheduler(this::flushTask); + SequentialScheduler.lockingScheduler(this::flushTask); @Override public void subscribe(Flow.Subscriber> subscriber) { synchronized (reading) { @@ -570,13 +569,9 @@ abstract class HttpConnection implements Closeable { signal(); } - void flushTask(DeferredCompleter completer) { - try { - HttpWriteSubscription sub = subscription; - if (sub != null) sub.flush(); - } finally { - completer.complete(); - } + void flushTask() { + HttpWriteSubscription sub = subscription; + if (sub != null) sub.flush(); } void signal() { diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/PullPublisher.java b/src/java.net.http/share/classes/jdk/internal/net/http/PullPublisher.java index d1019c05629..3b0bcc006c9 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/PullPublisher.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/PullPublisher.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -85,7 +85,7 @@ class PullPublisher implements Flow.Publisher { private volatile boolean completed; private volatile boolean cancelled; private volatile Throwable error; - final SequentialScheduler pullScheduler = new SequentialScheduler(new PullTask()); + final SequentialScheduler pullScheduler = SequentialScheduler.lockingScheduler(new PullTask()); private final Demand demand = new Demand(); Subscription(Flow.Subscriber subscriber, @@ -96,9 +96,9 @@ class PullPublisher implements Flow.Publisher { this.error = throwable; } - final class PullTask extends SequentialScheduler.CompleteRestartableTask { + final class PullTask implements Runnable { @Override - protected void run() { + public void run() { if (completed || cancelled) { return; } diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/SocketTube.java b/src/java.net.http/share/classes/jdk/internal/net/http/SocketTube.java index ef935b008d3..27b4ef37089 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/SocketTube.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/SocketTube.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,15 +29,12 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.util.List; import java.util.Objects; -import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.Flow; import java.util.concurrent.atomic.AtomicReference; import java.nio.channels.SelectableChannel; import java.nio.channels.SelectionKey; import java.nio.channels.SocketChannel; import java.util.ArrayList; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; import java.util.function.Consumer; import java.util.function.Supplier; import jdk.internal.net.http.common.BufferSupplier; @@ -46,8 +43,6 @@ import jdk.internal.net.http.common.FlowTube; import jdk.internal.net.http.common.Log; import jdk.internal.net.http.common.Logger; import jdk.internal.net.http.common.SequentialScheduler; -import jdk.internal.net.http.common.SequentialScheduler.DeferredCompleter; -import jdk.internal.net.http.common.SequentialScheduler.RestartableTask; import jdk.internal.net.http.common.Utils; /** @@ -160,34 +155,6 @@ final class SocketTube implements FlowTube { new IOException("connection closed locally", cause)); } - /** - * A restartable task used to process tasks in sequence. - */ - private static class SocketFlowTask implements RestartableTask { - final Runnable task; - private final Lock lock = new ReentrantLock(); - SocketFlowTask(Runnable task) { - this.task = task; - } - @Override - public final void run(DeferredCompleter taskCompleter) { - try { - // The logics of the sequential scheduler should ensure that - // the restartable task is running in only one thread at - // a given time: there should never be contention. - boolean locked = lock.tryLock(); - assert locked : "contention detected in SequentialScheduler"; - try { - task.run(); - } finally { - if (locked) lock.unlock(); - } - } finally { - taskCompleter.complete(); - } - } - } - // This is best effort - there's no guarantee that the printed set of values // is consistent. It should only be considered as weakly accurate - in // particular in what concerns the events states, especially when displaying @@ -682,7 +649,7 @@ final class SocketTube implements FlowTube { private final AsyncEvent subscribeEvent; InternalReadSubscription() { - readScheduler = new SequentialScheduler(new SocketFlowTask(this::read)); + readScheduler = SequentialScheduler.lockingScheduler(this::read); subscribeEvent = new AsyncTriggerEvent(this::signalError, this::handleSubscribeEvent); readEvent = new ReadEvent(channel, this); diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/common/SSLFlowDelegate.java b/src/java.net.http/share/classes/jdk/internal/net/http/common/SSLFlowDelegate.java index 0f97e191b37..2fa166028be 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/common/SSLFlowDelegate.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/common/SSLFlowDelegate.java @@ -244,17 +244,10 @@ public class SSLFlowDelegate { final ReentrantLock readBufferLock = new ReentrantLock(); final Logger debugr = Utils.getDebugLogger(this::dbgString, Utils.DEBUG); - private final class ReaderDownstreamPusher implements Runnable { - @Override - public void run() { - processData(); - } - } - Reader() { super(); scheduler = SequentialScheduler.lockingScheduler( - new ReaderDownstreamPusher()); + this::processData); this.readBuf = ByteBuffer.allocate(1024); readBuf.limit(0); // keep in read mode } @@ -588,14 +581,10 @@ public class SSLFlowDelegate { volatile boolean completing; boolean completed; // only accessed in processData - class WriterDownstreamPusher extends SequentialScheduler.CompleteRestartableTask { - @Override public void run() { processData(); } - } - Writer() { super(); writeList = Collections.synchronizedList(new LinkedList<>()); - scheduler = new SequentialScheduler(new WriterDownstreamPusher()); + scheduler = SequentialScheduler.lockingScheduler(this::processData); } @Override diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/common/SequentialScheduler.java b/src/java.net.http/share/classes/jdk/internal/net/http/common/SequentialScheduler.java index adc77f4d408..f5e896fc901 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/common/SequentialScheduler.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/common/SequentialScheduler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2023, 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 @@ -111,7 +111,7 @@ public final class SequentialScheduler { * later time, and maybe in different thread. This type exists for * readability purposes at use-sites only. */ - public abstract static class DeferredCompleter { + private abstract static class DeferredCompleter { /** Extensible from this (outer) class ONLY. */ private DeferredCompleter() { } @@ -124,7 +124,7 @@ public final class SequentialScheduler { * A restartable task. */ @FunctionalInterface - public interface RestartableTask { + private interface RestartableTask { /** * The body of the task. @@ -140,7 +140,7 @@ public final class SequentialScheduler { * A simple and self-contained task that completes once its {@code run} * method returns. */ - public abstract static class CompleteRestartableTask + private abstract static class CompleteRestartableTask implements RestartableTask { @Override @@ -161,7 +161,7 @@ public final class SequentialScheduler { * memory visibility between runs. Since the main loop can't run concurrently, * the lock shouldn't be contended and no deadlock should ever be possible. */ - public static final class LockingRestartableTask + private static final class LockingRestartableTask extends CompleteRestartableTask { private final Runnable mainLoop; @@ -208,7 +208,7 @@ public final class SequentialScheduler { } } - public SequentialScheduler(RestartableTask restartableTask) { + private SequentialScheduler(RestartableTask restartableTask) { this.restartableTask = requireNonNull(restartableTask); this.completer = new TryEndDeferredCompleter(); this.schedulableTask = new SchedulableTask(); diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/websocket/TransportImpl.java b/src/java.net.http/share/classes/jdk/internal/net/http/websocket/TransportImpl.java index c78555c8f6e..fa3529a0729 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/websocket/TransportImpl.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/websocket/TransportImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,7 +29,6 @@ import jdk.internal.net.http.common.Demand; import jdk.internal.net.http.common.Logger; import jdk.internal.net.http.common.MinimalFuture; import jdk.internal.net.http.common.SequentialScheduler; -import jdk.internal.net.http.common.SequentialScheduler.CompleteRestartableTask; import jdk.internal.net.http.common.Utils; import java.io.IOException; @@ -58,7 +57,7 @@ public class TransportImpl implements Transport { /* Used for correlating enters to and exists from a method */ private final AtomicLong counter = new AtomicLong(); - private final SequentialScheduler sendScheduler = new SequentialScheduler(new SendTask()); + private final SequentialScheduler sendScheduler = SequentialScheduler.lockingScheduler(new SendTask()); private final MessageQueue queue; private final MessageEncoder encoder = new MessageEncoder(); @@ -93,7 +92,7 @@ public class TransportImpl implements Transport { // To ensure the initial non-final `data` will be visible // (happens-before) when `readEvent.handle()` invokes `receiveScheduler` // the following assignment is done last: - receiveScheduler = new SequentialScheduler(new ReceiveTask()); + receiveScheduler = SequentialScheduler.lockingScheduler(new ReceiveTask()); } private ByteBuffer createWriteBuffer() { @@ -361,7 +360,7 @@ public class TransportImpl implements Transport { } @SuppressWarnings({"rawtypes"}) - private class SendTask extends CompleteRestartableTask { + private class SendTask implements Runnable { private final MessageQueue.QueueCallback encodingCallback = new MessageQueue.QueueCallback<>() { @@ -654,7 +653,7 @@ public class TransportImpl implements Transport { } } - private class ReceiveTask extends CompleteRestartableTask { + private class ReceiveTask implements Runnable { @Override public void run() { diff --git a/src/java.net.http/share/classes/jdk/internal/net/http/websocket/WebSocketImpl.java b/src/java.net.http/share/classes/jdk/internal/net/http/websocket/WebSocketImpl.java index aa9b027e200..98b6a47be58 100644 --- a/src/java.net.http/share/classes/jdk/internal/net/http/websocket/WebSocketImpl.java +++ b/src/java.net.http/share/classes/jdk/internal/net/http/websocket/WebSocketImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2018, 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 @@ -116,7 +116,7 @@ public final class WebSocketImpl implements WebSocket { private final AtomicBoolean pendingPingOrPong = new AtomicBoolean(); private final Transport transport; private final SequentialScheduler receiveScheduler - = new SequentialScheduler(new ReceiveTask()); + = SequentialScheduler.lockingScheduler(new ReceiveTask()); private final Demand demand = new Demand(); private final Executor clientExecutor; @@ -416,7 +416,7 @@ public final class WebSocketImpl implements WebSocket { * - after the state has been observed as CLOSE/ERROR, the scheduler * is stopped */ - private class ReceiveTask extends SequentialScheduler.CompleteRestartableTask { + private class ReceiveTask implements Runnable { // Transport only asked here and nowhere else because we must make sure // onOpen is invoked first and no messages become pending before onOpen diff --git a/src/java.smartcardio/share/classes/javax/smartcardio/package-info.java b/src/java.smartcardio/share/classes/javax/smartcardio/package-info.java index ef6489509df..9fa4bb18b66 100644 --- a/src/java.smartcardio/share/classes/javax/smartcardio/package-info.java +++ b/src/java.smartcardio/share/classes/javax/smartcardio/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2020, 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 @@ -81,12 +81,15 @@ * CardTerminal terminal = terminals.get(0); * // establish a connection with the card * Card card = terminal.connect("T=0"); - * System.out.println("card: " + card); - * CardChannel channel = card.getBasicChannel(); - * ResponseAPDU r = channel.transmit(new CommandAPDU(c1)); - * System.out.println("response: " + toString(r.getBytes())); - * // disconnect - * card.disconnect(false); + * try { + * System.out.println("card: " + card); + * CardChannel channel = card.getBasicChannel(); + * ResponseAPDU r = channel.transmit(new CommandAPDU(c1)); + * System.out.println("response: " + toString(r.getBytes())); + * } finally { + * // disconnect + * card.disconnect(false); + * } * * * @since 1.6 diff --git a/src/java.smartcardio/share/classes/sun/security/smartcardio/CardImpl.java b/src/java.smartcardio/share/classes/sun/security/smartcardio/CardImpl.java index f268f50d0ad..100ba76f7dd 100644 --- a/src/java.smartcardio/share/classes/sun/security/smartcardio/CardImpl.java +++ b/src/java.smartcardio/share/classes/sun/security/smartcardio/CardImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,6 +26,9 @@ package sun.security.smartcardio; import jdk.internal.util.OperatingSystem; +import jdk.internal.ref.CleanerFactory; +import java.lang.ref.Cleaner; +import java.lang.ref.Reference; import javax.smartcardio.*; import static sun.security.smartcardio.PCSC.*; @@ -43,9 +46,6 @@ final class CardImpl extends Card { // the terminal that created this card private final TerminalImpl terminal; - // the native SCARDHANDLE - final long cardId; - // atr of this card private final ATR atr; @@ -55,12 +55,45 @@ final class CardImpl extends Card { // the basic logical channel (channel 0) private final ChannelImpl basicChannel; - // state of this card connection - private volatile State state; - // thread holding exclusive access to the card, or null private volatile Thread exclusiveThread; + /* State and code for cleanup */ + static final class Context implements Runnable { + // the native SCARDHANDLE + final long cardId; + + // state of this card connection + private volatile State state; + + private Context(long cardId, State state) { + this.cardId = cardId; + this.state = state; + } + + /** + * The cleaning action calls SCardDisconnect if the card state is OK, + * otherwise it does nothing. + */ + public void run() { + if (state == State.OK) { + state = State.DISCONNECTED; + try { + SCardDisconnect(cardId, SCARD_LEAVE_CARD); + } catch (PCSCException e) { + // This will be swallowed if thrown when run by the Cleaner + // thread, and never thrown if called via Cleanable.clean() + // (only called if state != OK.) + throw new RuntimeException(e); + } + } + } + } + + final Context context; + private final Cleaner.Cleanable cleanable; + + CardImpl(TerminalImpl terminal, String protocol) throws PCSCException { this.terminal = terminal; int sharingMode = SCARD_SHARE_SHARED; @@ -83,36 +116,50 @@ final class CardImpl extends Card { } else { throw new IllegalArgumentException("Unsupported protocol " + protocol); } - cardId = SCardConnect(terminal.contextId, terminal.name, + long localCardId = SCardConnect(terminal.contextId, terminal.name, sharingMode, connectProtocol); + + this.context = new Context(localCardId, State.OK); + this.cleanable = CleanerFactory.cleaner().register(this, this.context); + byte[] status = new byte[2]; - byte[] atrBytes = SCardStatus(cardId, status); + byte[] atrBytes = SCardStatus(localCardId, status); atr = new ATR(atrBytes); this.protocol = status[1] & 0xff; basicChannel = new ChannelImpl(this, 0); - state = State.OK; } void checkState() { - State s = state; - if (s == State.DISCONNECTED) { - throw new IllegalStateException("Card has been disconnected"); - } else if (s == State.REMOVED) { - throw new IllegalStateException("Card has been removed"); + try { + State s = context.state; + if (s == State.DISCONNECTED) { + throw new IllegalStateException("Card has been disconnected"); + } else if (s == State.REMOVED) { + throw new IllegalStateException("Card has been removed"); + } + } finally { + Reference.reachabilityFence(this); } } boolean isValid() { - if (state != State.OK) { - return false; - } - // ping card via SCardStatus try { - SCardStatus(cardId, new byte[2]); - return true; - } catch (PCSCException e) { - state = State.REMOVED; - return false; + if (context.state != State.OK) { + return false; + } + // ping card via SCardStatus + try { + SCardStatus(context.cardId, new byte[2]); + return true; + } catch (PCSCException e) { + context.state = State.REMOVED; + // state has been set != OK. The cleaning action is now a noop. + // Deregister from Cleaner to reduce reference tracking. + cleanable.clean(); + return false; + } + } finally { + Reference.reachabilityFence(this); } } @@ -125,8 +172,15 @@ final class CardImpl extends Card { } void handleError(PCSCException e) { - if (e.code == SCARD_W_REMOVED_CARD) { - state = State.REMOVED; + try { + if (e.code == SCARD_W_REMOVED_CARD) { + context.state = State.REMOVED; + // state has been set != OK. The cleaning action is now a noop. + // Deregister from Cleaner to reduce reference tracking. + cleanable.clean(); + } + } finally { + Reference.reachabilityFence(this); } } @@ -164,21 +218,25 @@ final class CardImpl extends Card { private static byte[] commandOpenChannel = new byte[] {0, 0x70, 0, 0, 1}; public CardChannel openLogicalChannel() throws CardException { - checkSecurity("openLogicalChannel"); - checkState(); - checkExclusive(); try { - byte[] response = SCardTransmit - (cardId, protocol, commandOpenChannel, 0, commandOpenChannel.length); - if ((response.length != 3) || (getSW(response) != 0x9000)) { - throw new CardException - ("openLogicalChannel() failed, card response: " - + PCSC.toString(response)); + checkSecurity("openLogicalChannel"); + checkState(); + checkExclusive(); + try { + byte[] response = SCardTransmit + (context.cardId, protocol, commandOpenChannel, 0, commandOpenChannel.length); + if ((response.length != 3) || (getSW(response) != 0x9000)) { + throw new CardException + ("openLogicalChannel() failed, card response: " + + PCSC.toString(response)); + } + return new ChannelImpl(this, response[0]); + } catch (PCSCException e) { + handleError(e); + throw new CardException("openLogicalChannel() failed", e); } - return new ChannelImpl(this, response[0]); - } catch (PCSCException e) { - handleError(e); - throw new CardException("openLogicalChannel() failed", e); + } finally { + Reference.reachabilityFence(this); } } @@ -193,87 +251,98 @@ final class CardImpl extends Card { } public synchronized void beginExclusive() throws CardException { - checkSecurity("exclusive"); - checkState(); - if (exclusiveThread != null) { - throw new CardException - ("Exclusive access has already been assigned to Thread " - + exclusiveThread.getName()); - } try { - SCardBeginTransaction(cardId); - } catch (PCSCException e) { - handleError(e); - throw new CardException("beginExclusive() failed", e); + checkSecurity("exclusive"); + checkState(); + if (exclusiveThread != null) { + throw new CardException + ("Exclusive access has already been assigned to Thread " + + exclusiveThread.getName()); + } + try { + SCardBeginTransaction(context.cardId); + } catch (PCSCException e) { + handleError(e); + throw new CardException("beginExclusive() failed", e); + } + exclusiveThread = Thread.currentThread(); + } finally { + Reference.reachabilityFence(this); } - exclusiveThread = Thread.currentThread(); } public synchronized void endExclusive() throws CardException { - checkState(); - if (exclusiveThread != Thread.currentThread()) { - throw new IllegalStateException - ("Exclusive access not assigned to current Thread"); - } try { - SCardEndTransaction(cardId, SCARD_LEAVE_CARD); - } catch (PCSCException e) { - handleError(e); - throw new CardException("endExclusive() failed", e); + checkState(); + if (exclusiveThread != Thread.currentThread()) { + throw new IllegalStateException + ("Exclusive access not assigned to current Thread"); + } + try { + SCardEndTransaction(context.cardId, SCARD_LEAVE_CARD); + } catch (PCSCException e) { + handleError(e); + throw new CardException("endExclusive() failed", e); + } finally { + exclusiveThread = null; + } } finally { - exclusiveThread = null; + Reference.reachabilityFence(this); } } public byte[] transmitControlCommand(int controlCode, byte[] command) throws CardException { - checkSecurity("transmitControl"); - checkState(); - checkExclusive(); - if (command == null) { - throw new NullPointerException(); - } try { - byte[] r = SCardControl(cardId, controlCode, command); - return r; - } catch (PCSCException e) { - handleError(e); - throw new CardException("transmitControlCommand() failed", e); + checkSecurity("transmitControl"); + checkState(); + checkExclusive(); + if (command == null) { + throw new NullPointerException(); + } + try { + byte[] r = SCardControl(context.cardId, controlCode, command); + return r; + } catch (PCSCException e) { + handleError(e); + throw new CardException("transmitControlCommand() failed", e); + } + } finally { + Reference.reachabilityFence(this); } } public void disconnect(boolean reset) throws CardException { - if (reset) { - checkSecurity("reset"); - } - if (state != State.OK) { - return; - } - checkExclusive(); try { - SCardDisconnect(cardId, (reset ? SCARD_RESET_CARD : SCARD_LEAVE_CARD)); - } catch (PCSCException e) { - throw new CardException("disconnect() failed", e); + if (reset) { + checkSecurity("reset"); + } + if (context.state != State.OK) { + return; + } + checkExclusive(); + try { + SCardDisconnect(context.cardId, (reset ? SCARD_RESET_CARD : SCARD_LEAVE_CARD)); + } catch (PCSCException e) { + throw new CardException("disconnect() failed", e); + } finally { + context.state = State.DISCONNECTED; + exclusiveThread = null; + // state has been set != OK. The cleaning action is now a noop. + // Deregister from Cleaner to reduce reference tracking. + cleanable.clean(); + } } finally { - state = State.DISCONNECTED; - exclusiveThread = null; + Reference.reachabilityFence(this); } } public String toString() { - return "PC/SC card in " + terminal.name - + ", protocol " + getProtocol() + ", state " + state; - } - - @SuppressWarnings("removal") - protected void finalize() throws Throwable { try { - if (state == State.OK) { - state = State.DISCONNECTED; - SCardDisconnect(cardId, SCARD_LEAVE_CARD); - } + return "PC/SC card in " + terminal.name + + ", protocol " + getProtocol() + ", state " + context.state; } finally { - super.finalize(); + Reference.reachabilityFence(this); } } diff --git a/src/java.smartcardio/share/classes/sun/security/smartcardio/ChannelImpl.java b/src/java.smartcardio/share/classes/sun/security/smartcardio/ChannelImpl.java index 4d89c57e48c..a92ade5e184 100644 --- a/src/java.smartcardio/share/classes/sun/security/smartcardio/ChannelImpl.java +++ b/src/java.smartcardio/share/classes/sun/security/smartcardio/ChannelImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2021, 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 @@ -189,7 +189,7 @@ final class ChannelImpl extends CardChannel { " exceeded maximum " + RESPONSE_ITERATIONS); } byte[] response = SCardTransmit - (card.cardId, card.protocol, command, 0, n); + (card.context.cardId, card.protocol, command, 0, n); int rn = response.length; if (getresponse && (rn >= 2) && (n >= 1)) { // see ISO 7816/2005, 5.1.3 @@ -280,7 +280,7 @@ final class ChannelImpl extends CardChannel { byte[] com = new byte[] {0x00, 0x70, (byte)0x80, 0}; com[3] = (byte)getChannelNumber(); setChannel(com); - byte[] res = SCardTransmit(card.cardId, card.protocol, com, 0, com.length); + byte[] res = SCardTransmit(card.context.cardId, card.protocol, com, 0, com.length); if (isOK(res) == false) { throw new CardException("close() failed: " + PCSC.toString(res)); } diff --git a/src/jdk.crypto.cryptoki/unix/native/libj2pkcs11/p11_md.c b/src/jdk.crypto.cryptoki/unix/native/libj2pkcs11/p11_md.c index 62895895123..19f39188a18 100644 --- a/src/jdk.crypto.cryptoki/unix/native/libj2pkcs11/p11_md.c +++ b/src/jdk.crypto.cryptoki/unix/native/libj2pkcs11/p11_md.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. */ /* Copyright (c) 2002 Graz University of Technology. All rights reserved. @@ -79,10 +79,6 @@ JNIEXPORT jobject JNICALL Java_sun_security_pkcs11_wrapper_PKCS11_connect jstring jGetFunctionList) { void *hModule; - int i; - CK_ULONG ulCount = 0; - CK_C_GetInterfaceList C_GetInterfaceList = NULL; - CK_INTERFACE_PTR iList = NULL; CK_C_GetInterface C_GetInterface = NULL; CK_INTERFACE_PTR interface = NULL; CK_C_GetFunctionList C_GetFunctionList = NULL; @@ -124,27 +120,31 @@ JNIEXPORT jobject JNICALL Java_sun_security_pkcs11_wrapper_PKCS11_connect } #ifdef DEBUG - C_GetInterfaceList = (CK_C_GetInterfaceList) dlsym(hModule, - "C_GetInterfaceList"); + CK_C_GetInterfaceList C_GetInterfaceList = (CK_C_GetInterfaceList) dlsym(hModule, "C_GetInterfaceList"); if (C_GetInterfaceList != NULL) { + CK_ULONG ulCount = 0; TRACE0("Connect: Found C_GetInterfaceList func\n"); rv = (C_GetInterfaceList)(NULL, &ulCount); if (rv == CKR_OK) { TRACE1("Connect: interface list size %ld \n", ulCount); // retrieve available interfaces and report their info - iList = (CK_INTERFACE_PTR) - malloc(ulCount*sizeof(CK_INTERFACE)); - rv = C_GetInterfaceList(iList, &ulCount); - if (ckAssertReturnValueOK(env, rv) != CK_ASSERT_OK) { - TRACE0("Connect: error polling interface list\n"); - goto cleanup; - } - for (i=0; i < (int)ulCount; i++) { - TRACE4("Connect: name %s, version %d.%d, flags 0x%lX\n", - iList[i].pInterfaceName, - ((CK_VERSION *)iList[i].pFunctionList)->major, - ((CK_VERSION *)iList[i].pFunctionList)->minor, - iList[i].flags); + CK_INTERFACE_PTR iList = (CK_INTERFACE_PTR) malloc(ulCount*sizeof(CK_INTERFACE)); + if (iList == NULL) { + TRACE0("Connect: error allocating interface list\n"); + } else { + rv = C_GetInterfaceList(iList, &ulCount); + if (ckAssertReturnValueOK(env, rv) != CK_ASSERT_OK) { + TRACE0("Connect: error polling interface list\n"); + goto cleanup; + } + for (int i=0; i < (int)ulCount; i++) { + TRACE4("Connect: name %s, version %d.%d, flags 0x%lX\n", + iList[i].pInterfaceName, + ((CK_VERSION *)iList[i].pFunctionList)->major, + ((CK_VERSION *)iList[i].pFunctionList)->minor, + iList[i].flags); + } + free(iList); } } else { TRACE0("Connect: error polling interface list size\n"); diff --git a/test/docs/ProblemList.txt b/test/docs/ProblemList.txt index b08cb9519c9..6d496f62a21 100644 --- a/test/docs/ProblemList.txt +++ b/test/docs/ProblemList.txt @@ -56,4 +56,3 @@ ############################################################################# -# Value Objects failures start here: diff --git a/test/hotspot/jtreg/ProblemList-Xcomp.txt b/test/hotspot/jtreg/ProblemList-Xcomp.txt index 1262c1698bd..4b182d10501 100644 --- a/test/hotspot/jtreg/ProblemList-Xcomp.txt +++ b/test/hotspot/jtreg/ProblemList-Xcomp.txt @@ -44,6 +44,7 @@ vmTestbase/nsk/jvmti/scenarios/capability/CM03/cm03t001/TestDescription.java 829 vmTestbase/nsk/stress/thread/thread006.java 8321476 linux-all gc/arguments/TestNewSizeFlags.java 8299116 macosx-aarch64 +gc/g1/TestCodeCacheUnloadDuringConcurrentMark.java 8388169 generic-all ############################################################################# diff --git a/test/hotspot/jtreg/compiler/ciReplay/CiReplayBase.java b/test/hotspot/jtreg/compiler/ciReplay/CiReplayBase.java index 017242c6d9e..8fcdbca6ff3 100644 --- a/test/hotspot/jtreg/compiler/ciReplay/CiReplayBase.java +++ b/test/hotspot/jtreg/compiler/ciReplay/CiReplayBase.java @@ -58,9 +58,7 @@ public abstract class CiReplayBase { public static final String CLIENT_VM_OPTION = "-client"; public static final String SERVER_VM_OPTION = "-server"; public static final String TEST_CORE_FILE_NAME = "test_core"; - public static final String RUN_SHELL_NO_LIMIT = "ulimit -c unlimited && "; private static final String REPLAY_FILE_OPTION = "-XX:ReplayDataFile=" + REPLAY_FILE_NAME; - private static final String LOCATIONS_STRING = "location: "; private static final String HS_ERR_NAME = "hs_err_pid"; private static final String RUN_SHELL_ZERO_LIMIT = "ulimit -S -c 0 && "; private static final String VERSION_OPTION = "-version"; diff --git a/test/hotspot/jtreg/compiler/ciReplay/DumpReplayBase.java b/test/hotspot/jtreg/compiler/ciReplay/DumpReplayBase.java index 3597c0729a1..c737971e28e 100644 --- a/test/hotspot/jtreg/compiler/ciReplay/DumpReplayBase.java +++ b/test/hotspot/jtreg/compiler/ciReplay/DumpReplayBase.java @@ -40,7 +40,7 @@ import java.util.stream.Collectors; public abstract class DumpReplayBase extends CiReplayBase { - private static final String DUMP_REPLAY_PATTERN = "replay_pid"; + protected static final String DUMP_REPLAY_PATTERN = "replay_pid"; private List replayFiles; private String replayFileName; diff --git a/test/hotspot/jtreg/compiler/ciReplay/ReplayFile.java b/test/hotspot/jtreg/compiler/ciReplay/ReplayFile.java index decd3033096..ebb7e6e5b39 100644 --- a/test/hotspot/jtreg/compiler/ciReplay/ReplayFile.java +++ b/test/hotspot/jtreg/compiler/ciReplay/ReplayFile.java @@ -25,13 +25,15 @@ package compiler.ciReplay; import jdk.test.lib.Asserts; +import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.StandardOpenOption; -import java.util.ArrayList; -import java.util.List; +import java.util.*; +import java.util.function.BiConsumer; +import java.util.function.BiPredicate; public class ReplayFile { private final Path replayFilePath; @@ -78,4 +80,747 @@ public class ReplayFile { throw new Error("Failed to read/write replay data: " + ioe, ioe); } } + + static public class ParsedReplayFile { + sealed interface Command permits + VersionCommand, + JvmtiExportCommand, + InstanceKlassCommand, + CiInstanceKlassCommand, + StaticFieldCommand, + CiMethodDataCommand, + CiMethodCommand, + CompileCommand {} + + // version + public record VersionCommand(int version) implements Command {} + // JvmtiExport + public record JvmtiExportCommand(String field, int value) implements Command {} + // instanceKlass + // | @bci * ; + // | @cpi * ; + sealed interface InstanceKlassCommand extends Command permits InstanceKlassCommandName, InstanceKlassCommandBci, InstanceKlassCommandCpi {} + public record InstanceKlassCommandName(String name) implements InstanceKlassCommand {} + public record InstanceKlassCommandBci(String klass, String name, String signature, int bci, List location) implements InstanceKlassCommand {} + public record InstanceKlassCommandCpi(String klass, int cpi, List location) implements InstanceKlassCommand {} + // ciInstanceKlass tag* + public record CiInstanceKlassCommand(String name, boolean isLinked, boolean isInitialized, int length, List tag) implements Command {} + // staticfield [IBCSZJFD] + // | "[" [IBCSZJFD] + // | "ref" ("nullable" | "null-free") + // | "flat" ("nullable" | "null-free") ("atomic" | "non-atomic") + // | "Ljava/lang/String;" + // | ? + sealed interface StaticFieldCommand extends Command permits + StaticFieldCommandPrimitive, + StaticFieldCommandPrimitiveArray, + StaticFieldCommandRefArray, + StaticFieldCommandFlatArray, + StaticFieldCommandNullArray, + StaticFieldCommandString, + StaticFieldCommandInstance { + String klass(); + String fieldName(); + String signature(); + } + public record StaticFieldCommandPrimitive(String klass, String fieldName, String signature, String value) implements StaticFieldCommand {} + public record StaticFieldCommandPrimitiveArray(String klass, String fieldName, String signature, int length) implements StaticFieldCommand {} + public record StaticFieldCommandRefArray(String klass, String fieldName, String signature, int length, boolean nullFree, String actualKlass) implements StaticFieldCommand {} + public record StaticFieldCommandFlatArray(String klass, String fieldName, String signature, int length, boolean nullFree, boolean nonAtomic, String actualKlass) implements StaticFieldCommand {} + public record StaticFieldCommandNullArray(String klass, String fieldName, String signature) implements StaticFieldCommand {} + public record StaticFieldCommandString(String klass, String fieldName, String value) implements StaticFieldCommand { + public String signature() { return "Ljava/lang/String;"; } + } + public record StaticFieldCommandInstance(String klass, String fieldName, String signature, List actualKlassOrValues) implements StaticFieldCommand {} + // ciMethodData orig * data * oops ( ?)* methods ( )* + sealed interface CiMethodDataCommandOop permits CiMethodDataCommandOopInstance, CiMethodDataCommandOopArray {} + public record CiMethodDataCommandOopInstance(int offset, String klass) implements CiMethodDataCommandOop {} + public record CiMethodDataCommandOopArray(int offset, String klass, int arrayProperties) implements CiMethodDataCommandOop {} + public record CiMethodDataCommandMethod(int offset, String klass, String name, String signature) {} + public record CiMethodDataCommand(String klass, String name, String signature, int state, int invocationCounter, List orig, List data, List oops, List methods) implements Command {} + // ciMethod + public record CiMethodCommand(String klass, String name, String signature, int invocationCounter, int backedgeCounter, int interpreterInvocationCount, int interpreterThrowoutCount, int instructionsSize) implements Command {} + // compile inline ( )* + public record CompileCommandInline(int depth, int bci, boolean inlineLate, String klass, String name, String signature) {} + public record CompileCommand(String klass, String name, String signature, int entryBci, int compLevel, List inlines) implements Command {} + + ParsedReplayFile(List commands) { this.commands = commands; } + List commands; + + // Set by sanity checking + boolean checked = false; + // Set by indexing, only after sanity checking + public record StaticField(String klass, String name) {} + HashMap staticFieldCommands = null; + + static public ParsedReplayFile parse(File file) throws IOException { + return parse(Files.readAllLines(file.toPath())); + } + static public ParsedReplayFile parse(List lines) { + return new ParsedReplayFile(lines.stream().map(ParsedReplayFile::parseLine).filter(Objects::nonNull).toList()); + } + static Command parseLine(String line) { + List pieces = Arrays.stream(line.split(" ")).filter(piece -> !piece.isEmpty()).toList(); + int commentIdx = pieces.indexOf("#"); + if (commentIdx >= 0) { + pieces = pieces.subList(0, commentIdx); + } + if (pieces.isEmpty()) { + return null; + } + String command = pieces.getFirst(); + var linePieces = LinePieces.make(pieces, command); + var cmd = switch (command) { + case "version" -> parseVersion(linePieces); + case "JvmtiExport" -> parseJvmtiExport(linePieces); + case "instanceKlass" -> parseInstanceKlass(linePieces); + case "ciInstanceKlass" -> parseCiInstanceKlass(linePieces); + case "staticfield" -> parseStaticField(linePieces); + case "ciMethodData" -> parseCiMethodData(linePieces); + case "ciMethod" -> parseCiMethod(linePieces); + case "compile" -> parseCompile(linePieces); + default -> throw new RuntimeException("unknown command: " + command); + }; + linePieces.checkAtEnd(); + return cmd; + } + + static class LinePieces { + int pos = 0; + List pieces; + + private LinePieces(List pieces) { + this.pieces = List.copyOf(pieces); + } + + @Override + public String toString() { + var before = pieces.subList(0, pos); + var after = pieces.subList(pos, pieces.size()); + return before + ">>" + after; + } + + static public LinePieces make(List pieces, String commandName) { + var line = new LinePieces(pieces); + line.getKeywork(commandName); + return line; + } + + void checkBounds(int nb) { + if (pos < 0) + throw new IndexOutOfBoundsException("negative position: " + pos); + if (pos + nb - 1 >= pieces.size()) + throw new IndexOutOfBoundsException("size: " + pieces.size() + "; pos: " + pos + "; nb: " + nb); + } + + void getKeywork(String keyword) { + checkBounds(1); + String s = getString(); + if (!keyword.equals(s)) { + throw new RuntimeException("expected keyword: " + keyword + "; got: " + s); + } + } + + public String getString() { + checkBounds(1); + String s = pieces.get(pos); + pos++; + return s; + } + + public List getStrings(int n) { + checkBounds(n); + List sub = pieces.subList(pos, pos + n); + pos += n; + return sub; + } + + public List getLeftoverStrings() { + return getStrings(pieces.size() - pos); + } + + public int getInt() { + String s = getString(); + return Integer.parseInt(s); + } + + public List getInts(int n) { + List s = getStrings(n); + return s.stream().map(Integer::parseInt).toList(); + } + + public Optional getIntIfTwoIntsAvailable() { + if (pos + 1 >= pieces.size()) { + return Optional.empty(); + } + String s0 = pieces.get(pos); + String s1 = pieces.get(pos + 1); + try { + Integer.parseInt(s0); + Integer.parseInt(s1); + } catch (NumberFormatException _) { + return Optional.empty(); + } + return Optional.of(getInt()); + } + + public boolean getBool() { + int s = getInt(); + return switch (s) { + case 0 -> false; + case 1 -> true; + default -> throw new RuntimeException("unexpected bool: " + s); + }; + } + + public boolean getBoolKeyword(String falseKw, String trueKw) { + String s = getString(); + if (s.equals(falseKw)) return false; + if (s.equals(trueKw)) return true; + throw new RuntimeException("unexepcted boolean keyword; got " + s + "; expected " + falseKw + " (for false) or " + trueKw + " (for true)"); + } + + public boolean atEnd() { + return pos == pieces.size(); + } + + public void checkAtEnd() { + if (!atEnd()) { + throw new RuntimeException("not at end; size: " + pieces.size() + "; pos: " + pos + "; pieces: " + this); + } + } + } + + static VersionCommand parseVersion(LinePieces pieces) { + int version = pieces.getInt(); + return new VersionCommand(version); + } + + static JvmtiExportCommand parseJvmtiExport(LinePieces pieces) { + String field = pieces.getString(); + int value = pieces.getInt(); + return new JvmtiExportCommand(field, value); + } + + static InstanceKlassCommand parseInstanceKlass(LinePieces pieces) { + String name = pieces.getString(); + return switch (name) { + case "@bci" -> parseInstanceKlassBci(pieces); + case "@cpi" -> parseInstanceKlassCpi(pieces); + default -> new InstanceKlassCommandName(name); + }; + } + + static InstanceKlassCommandBci parseInstanceKlassBci(LinePieces pieces) { + String klass = pieces.getString(); + String name = pieces.getString(); + String signature = pieces.getString(); + int bci = pieces.getInt(); + List location = new ArrayList<>(); + var nextS = pieces.getString(); + while (!nextS.equals(";")) { + location.add(nextS); + nextS = pieces.getString(); + } + return new InstanceKlassCommandBci(klass, name, signature, bci, location); + } + + static InstanceKlassCommandCpi parseInstanceKlassCpi(LinePieces pieces) { + String klass = pieces.getString(); + int cpi = pieces.getInt(); + List location = pieces.getLeftoverStrings(); + return new InstanceKlassCommandCpi(klass, cpi, location); + } + + static CiInstanceKlassCommand parseCiInstanceKlass(LinePieces pieces) { + String name = pieces.getString(); + boolean isLinked = pieces.getBool(); + boolean isInitialized = pieces.getBool(); + int length = pieces.getInt(); + List tag = pieces.getInts(length - 1); + return new CiInstanceKlassCommand(name, isLinked, isInitialized, length, tag); + } + + static boolean isPrimitiveType(char c) { + return "IBCSZJFD".contains(String.valueOf(c)); + } + + static StaticFieldCommand parseStaticField(LinePieces pieces) { + String klass = pieces.getString(); + String fieldName = pieces.getString(); + String signature = pieces.getString(); + if (isPrimitiveType(signature.charAt(0))) { + String val = pieces.getString(); + return new StaticFieldCommandPrimitive(klass, fieldName, signature, val); + } + if (signature.charAt(0) == '[') { + if (isPrimitiveType(signature.charAt(1))) { + int length = pieces.getInt(); + return new StaticFieldCommandPrimitiveArray(klass, fieldName, signature, length); + } else { + int length = pieces.getInt(); + if (length == -1) { + return new StaticFieldCommandNullArray(klass, fieldName, signature); + } + boolean isFlat = pieces.getBoolKeyword("ref", "flat"); + boolean nullFree = pieces.getBoolKeyword("nullable", "null-free"); + if (isFlat) { + boolean nonAtomic = pieces.getBoolKeyword("atomic", "non-atomic"); + String actualKlass = pieces.getString(); + return new StaticFieldCommandFlatArray(klass, fieldName, signature, length, nullFree, nonAtomic, actualKlass); + } else { + String actualKlass = pieces.getString(); + return new StaticFieldCommandRefArray(klass, fieldName, signature, length, nullFree, actualKlass); + } + } + } + if (signature.equals("Ljava/lang/String;")) { + String value = pieces.getString(); + return new StaticFieldCommandString(klass, fieldName, value); + } + List actualKlassOrValues = pieces.getLeftoverStrings(); + return new StaticFieldCommandInstance(klass, fieldName, signature, actualKlassOrValues); + } + + // oops ( ?)* methods ( )* + static CiMethodDataCommand parseCiMethodData(LinePieces pieces) { + String klass = pieces.getString(); + String name = pieces.getString(); + String signature = pieces.getString(); + int state = pieces.getInt(); + int invocationCounter = pieces.getInt(); + + pieces.getKeywork("orig"); + int origLength = pieces.getInt(); + List orig = pieces.getInts(origLength); + + pieces.getKeywork("data"); + int datalength = pieces.getInt(); + List data = pieces.getStrings(datalength); + + pieces.getKeywork("oops"); + int oopsLength = pieces.getInt(); + List oops = new ArrayList<>(oopsLength); + + for (int i = 0; i < oopsLength; i++) { + int offset = pieces.getInt(); + String klass_ = pieces.getString(); + Optional properties = pieces.getIntIfTwoIntsAvailable(); + oops.add( + properties + .map(prop -> (CiMethodDataCommandOop)new CiMethodDataCommandOopArray(offset, klass_, prop)) + .orElse(new CiMethodDataCommandOopInstance(offset, klass_)) + ); + } + + pieces.getKeywork("methods"); + int methodsLength = pieces.getInt(); + List methods = new ArrayList<>(methodsLength); + + for (int i = 0; i < methodsLength; i++) { + int offset = pieces.getInt(); + String klass_ = pieces.getString(); + String name_ = pieces.getString(); + String signature_ = pieces.getString(); + methods.add(new CiMethodDataCommandMethod(offset, klass_, name_, signature_)); + } + + return new CiMethodDataCommand(klass, name, signature, state, invocationCounter, orig, data, oops, methods); + } + + static CiMethodCommand parseCiMethod(LinePieces pieces) { + String klass = pieces.getString(); + String name = pieces.getString(); + String signature = pieces.getString(); + int invocationCounter = pieces.getInt(); + int backedgeCounter = pieces.getInt(); + int interpreterInvocationCount = pieces.getInt(); + int interpreterThrowoutCount = pieces.getInt(); + int instructionsSize = pieces.getInt(); + return new CiMethodCommand(klass, name, signature, invocationCounter, backedgeCounter, interpreterInvocationCount, interpreterThrowoutCount, instructionsSize); + } + + static CompileCommand parseCompile(LinePieces pieces) { + String klass = pieces.getString(); + String name = pieces.getString(); + String signature = pieces.getString(); + int entryBci = pieces.getInt(); + int compLevel = pieces.getInt(); + pieces.getKeywork("inline"); + int count = pieces.getInt(); + + List inlines = new ArrayList<>(); + + for (int i = 0; i < count; i++) { + int depth = pieces.getInt(); + int bci = pieces.getInt(); + boolean inlineLate = pieces.getBool(); + String klass_ = pieces.getString(); + String name_ = pieces.getString(); + String signature_ = pieces.getString(); + inlines.add(new CompileCommandInline(depth, bci, inlineLate, klass_, name_, signature_)); + } + + return new CompileCommand(klass, name, signature, entryBci, compLevel, inlines); + } + + List checkSanity() { + record Method(String klass, String name, String signature) {} + record Field(String klass, String name) {} + List insanities = new ArrayList<>(); + + int seenVersionCommands = 0; + Set seenCiMethod = new HashSet<>(); + Set seenCiMethodData = new HashSet<>(); + Set seenCompile = new HashSet<>(); + Set seenKlasses = new HashSet<>(); + Map seenFields = new HashMap<>(); + for (Command c : commands) { + switch (c) { + case CiInstanceKlassCommand(String name, boolean isLinked, boolean isInitialized, int length, List tag) -> seenKlasses.add(name); + case StaticFieldCommand cmd -> { + String klass = cmd.klass(); + String fieldName = cmd.fieldName(); + if (!seenKlasses.contains(klass)) { + insanities.add("Static field command " + cmd + " seen before the corresponding ciInstanceKlass command."); + } + var field = new Field(klass, fieldName); + if (seenFields.containsKey(field)) { + insanities.add("Already seen the static field " + klass + "::" + fieldName + " with signature " + seenFields.get(field) + ". This time, it had signature " + cmd.signature() + "."); + } else { + seenFields.put(field, cmd.signature()); + } + } + case CompileCommand(String klass, String name, String signature, int entryBci, int compLevel, List inlines) -> { + var method = new Method(klass, name, signature); + seenCompile.add(method); + if (!seenCiMethod.contains(method)) { + insanities.add("Found \"compile\" command without a \"ciMethod\" command for the same method."); + } + if (!seenCiMethodData.contains(method)) { + insanities.add("Found \"compile\" command without a \"ciMethodData\" command for the same method."); + } + } + case CiMethodCommand(String klass, String name, String signature, int invocationCounter, int backedgeCounter, int interpreterInvocationCount, int interpreterThrowoutCount, int instructionsSize) -> + seenCiMethod.add(new Method(klass, name, signature)); + case CiMethodDataCommand(String klass, String name, String signature, int state, int invocationCounter, List orig, List data, List oops, List methods) -> + seenCiMethodData.add(new Method(klass, name, signature)); + case VersionCommand _ -> + seenVersionCommands++; + case InstanceKlassCommand _, + JvmtiExportCommand _ -> { + } + } + } + + if (seenCompile.isEmpty()) { + insanities.add("No \"compile\" command found."); + } + + if (seenVersionCommands == 0) { + insanities.add("No \"version\" command found."); + } else if (seenVersionCommands > 1) { + insanities.add("Found too many \"version\" commands: " + seenVersionCommands); + } + checked = true; + return insanities; + } + + // Use it only after checkSanity. + void index() { + Asserts.assertTrue(checked); + staticFieldCommands = new HashMap<>(); + + for (Command c : commands) { + switch (c) { + case StaticFieldCommand cmd -> { + String klass = cmd.klass(); + String fieldName = cmd.fieldName(); + staticFieldCommands.put(new StaticField(klass, fieldName), cmd); + } + case CiInstanceKlassCommand _, + CiMethodCommand _, + CiMethodDataCommand _, + CompileCommand _, + InstanceKlassCommand _, + JvmtiExportCommand _, + VersionCommand _ -> {} + } + + } + } + + static Optional getVersion(ParsedReplayFile parsed) { + return parsed.commands.stream().map(cmd -> switch (cmd) { case VersionCommand(int version) -> version; default -> null; }).filter(Objects::nonNull).findAny(); + } + static void compareVersion(ParsedReplayFile lhs, ParsedReplayFile rhs, List differences) { + Optional lhsVersion = getVersion(lhs); + Optional rhsVersion = getVersion(rhs); + + if (lhsVersion.isPresent() && rhsVersion.isPresent() && !lhsVersion.get().equals(rhsVersion.get())) { + differences.add("Versions mismatch: lhs=" + lhsVersion.get() + "; rhs=" + rhsVersion.get()); + } + } + + static HashSet extractSet(ParsedReplayFile parsed, BiConsumer, Command> f) { + return parsed.commands.stream().collect( + HashSet::new, + f, + HashSet::addAll + ); + } + static void diffSets(String name, HashSet lhs, HashSet rhs, List differences) { + lhs.forEach((v) -> { + if (!rhs.contains(v)) { + differences.add(name + " mismatch: element=" + v + " exists only in lhs"); + } + } + ); + rhs.forEach((v) -> { + if (!lhs.contains(v)) { + differences.add(name + " mismatch: element=" + v + " exists only in rhs"); + } + } + ); + } + static HashMap extractMap(ParsedReplayFile parsed, BiConsumer, Command> f) { + return parsed.commands.stream().collect( + HashMap::new, + f, + HashMap::putAll + ); + } + static void diffMaps(String name, HashMap lhs, HashMap rhs, BiPredicate eqValue, List differences) { + lhs.forEach((key, lValue) -> { + if (!rhs.containsKey(key)) { + differences.add(name + " mismatch: key=" + key + " exists only in lhs"); + } else { + U rValue = rhs.get(key); + if (!eqValue.test(lValue, rValue)) { + differences.add(name + " mismatch: for key=" + key + "; value in lhs=" + lValue + "; value in rhs=" + rValue); + } + } + } + ); + rhs.forEach((key, _) -> { + if (!lhs.containsKey(key)) { + differences.add(name + " mismatch: key=" + key + " exists only in rhs"); + } + } + ); + } + + static void compareJvmtiExport(ParsedReplayFile lhs, ParsedReplayFile rhs, List differences) { + BiConsumer, Command> folder = (acc, command) -> { + if (command instanceof JvmtiExportCommand(String field, int value)) { + acc.put(field, value); + } + }; + HashMap lhsJvmti = extractMap(lhs, folder); + HashMap rhsJvmti = extractMap(rhs, folder); + diffMaps("JvmtiExport", lhsJvmti, rhsJvmti, Integer::equals, differences); + } + + static void compareInstanceKlassNames(ParsedReplayFile lhs, ParsedReplayFile rhs, List differences) { + BiConsumer, Command> folder = (acc, command) -> { + if (command instanceof InstanceKlassCommandName(String name)) { + acc.add(name); + } + }; + HashSet lhsKlasses = extractSet(lhs, folder); + HashSet rhsKlasses = extractSet(rhs, folder); + diffSets("InstanceKlass", lhsKlasses, rhsKlasses, differences); + } + static void compareInstanceKlassCpi(ParsedReplayFile lhs, ParsedReplayFile rhs, List differences) { + record Key(String klass, int cpi) {} + BiConsumer>, Command> folder = (acc, command) -> { + if (command instanceof InstanceKlassCommandCpi(String klass, int cpi, List location)) { + acc.put(new Key(klass, cpi), location); + } + }; + var lhsKlasses = extractMap(lhs, folder); + var rhsKlasses = extractMap(rhs, folder); + diffMaps("InstanceKlass", lhsKlasses, rhsKlasses, List::equals, differences); + } + static void compareInstanceKlassBci(ParsedReplayFile lhs, ParsedReplayFile rhs, List differences) { + record Key(String klass, String name, String signature, int bci) {} + BiConsumer>, Command> folder = (acc, command) -> { + if (command instanceof InstanceKlassCommandBci(String klass, String name, String signature, int bci, List location)) { + acc.put(new Key(klass, name, signature, bci), location); + } + }; + var lhsKlasses = extractMap(lhs, folder); + var rhsKlasses = extractMap(rhs, folder); + diffMaps("InstanceKlass", lhsKlasses, rhsKlasses, List::equals, differences); + } + static void compareInstanceKlasses(ParsedReplayFile lhs, ParsedReplayFile rhs, List differences) { + compareInstanceKlassNames(lhs, rhs, differences); + compareInstanceKlassCpi(lhs, rhs, differences); + compareInstanceKlassBci(lhs, rhs, differences); + } + + static void compareCiInstanceKlasses(ParsedReplayFile lhs, ParsedReplayFile rhs, List differences) { + record Element(String name, boolean isLinked, boolean isInitialized, int length) {} + BiConsumer, Command> folder = (acc, command) -> { + if (command instanceof CiInstanceKlassCommand(String name, boolean isLinked, boolean isInitialized, int length, List _)) { + acc.add(new Element(name, isLinked, isInitialized, length)); + } + }; + var lhsCiLlasses = extractSet(lhs, folder); + var rhsCiLlasses = extractSet(rhs, folder); + diffSets("CiInstanceKlass", lhsCiLlasses, rhsCiLlasses, differences); + } + + static void compareStaticFieldCommandPrimitive(ParsedReplayFile lhs, ParsedReplayFile rhs, List differences) { + record Key(String klass, String fieldName, String signature) {} + BiConsumer, Command> folder = (acc, command) -> { + if (command instanceof StaticFieldCommandPrimitive(String klass, String fieldName, String signature, String value)) { + acc.put(new Key(klass, fieldName, signature), value); + } + }; + var lhsStaticFields = extractMap(lhs, folder); + var rhsStaticFields = extractMap(rhs, folder); + diffMaps("CiInstanceKlass", lhsStaticFields, rhsStaticFields, String::equals, differences); + } + static void compareStaticFieldCommandPrimitiveArray(ParsedReplayFile lhs, ParsedReplayFile rhs, List differences) { + record Key(String klass, String fieldName, String signature) {} + BiConsumer, Command> folder = (acc, command) -> { + if (command instanceof StaticFieldCommandPrimitiveArray(String klass, String fieldName, String signature, int length)) { + acc.put(new Key(klass, fieldName, signature), length); + } + }; + var lhsStaticFields = extractMap(lhs, folder); + var rhsStaticFields = extractMap(rhs, folder); + diffMaps("CiInstanceKlass", lhsStaticFields, rhsStaticFields, Integer::equals, differences); + } + static void compareStaticFieldCommandRefArray(ParsedReplayFile lhs, ParsedReplayFile rhs, List differences) { + record Key(String klass, String fieldName, String signature) {} + record Value(int length, boolean nullFree, String actualKlass) {} + BiConsumer, Command> folder = (acc, command) -> { + if (command instanceof StaticFieldCommandRefArray(String klass, String fieldName, String signature, int length, boolean nullFree, String actualKlass)) { + acc.put(new Key(klass, fieldName, signature), new Value(length, nullFree, actualKlass)); + } + }; + var lhsStaticFields = extractMap(lhs, folder); + var rhsStaticFields = extractMap(rhs, folder); + diffMaps("CiInstanceKlass", lhsStaticFields, rhsStaticFields, Value::equals, differences); + } + static void compareStaticFieldCommandFlatArray(ParsedReplayFile lhs, ParsedReplayFile rhs, List differences) { + record Key(String klass, String fieldName, String signature) {} + record Value(int length, boolean nullFree, boolean nonAtomic, String actualKlass) {} + BiConsumer, Command> folder = (acc, command) -> { + if (command instanceof StaticFieldCommandFlatArray(String klass, String fieldName, String signature, int length, boolean nullFree, boolean nonAtomic, String actualKlass)) { + acc.put(new Key(klass, fieldName, signature), new Value(length, nullFree, nonAtomic, actualKlass)); + } + }; + var lhsStaticFields = extractMap(lhs, folder); + var rhsStaticFields = extractMap(rhs, folder); + diffMaps("CiInstanceKlass", lhsStaticFields, rhsStaticFields, Value::equals, differences); + } + static void compareStaticFieldCommandNullArray(ParsedReplayFile lhs, ParsedReplayFile rhs, List differences) { + record Element(String klass, String fieldName, String signature) {} + BiConsumer, Command> folder = (acc, command) -> { + if (command instanceof StaticFieldCommandNullArray(String klass, String fieldName, String signature)) { + acc.add(new Element(klass, fieldName, signature)); + } + }; + var lhsStaticFields = extractSet(lhs, folder); + var rhsStaticFields = extractSet(rhs, folder); + diffSets("CiInstanceKlass", lhsStaticFields, rhsStaticFields, differences); + } + static void compareStaticFieldCommandString(ParsedReplayFile lhs, ParsedReplayFile rhs, List differences) { + record Key(String klass, String fieldName) {} + BiConsumer, Command> folder = (acc, command) -> { + if (command instanceof StaticFieldCommandString(String klass, String fieldName, String value)) { + acc.put(new Key(klass, fieldName), value); + } + }; + var lhsStaticFields = extractMap(lhs, folder); + var rhsStaticFields = extractMap(rhs, folder); + diffMaps("CiInstanceKlass", lhsStaticFields, rhsStaticFields, String::equals, differences); + } + static void compareStaticFieldCommandInstance(ParsedReplayFile lhs, ParsedReplayFile rhs, List differences) { + record Key(String klass, String fieldName, String signature) {} + BiConsumer>, Command> folder = (acc, command) -> { + if (command instanceof StaticFieldCommandInstance(String klass, String fieldName, String signature, List actualKlassOrValues)) { + acc.put(new Key(klass, fieldName, signature), actualKlassOrValues); + } + }; + var lhsStaticFields = extractMap(lhs, folder); + var rhsStaticFields = extractMap(rhs, folder); + diffMaps("CiInstanceKlass", lhsStaticFields, rhsStaticFields, List::equals, differences); + } + static void compareStaticFieldCommand(ParsedReplayFile lhs, ParsedReplayFile rhs, List differences) { + compareStaticFieldCommandPrimitive(lhs, rhs, differences); + compareStaticFieldCommandPrimitiveArray(lhs, rhs, differences); + compareStaticFieldCommandRefArray(lhs, rhs, differences); + compareStaticFieldCommandFlatArray(lhs, rhs, differences); + compareStaticFieldCommandNullArray(lhs, rhs, differences); + compareStaticFieldCommandString(lhs, rhs, differences); + compareStaticFieldCommandInstance(lhs, rhs, differences); + } + + static void compareCiMethodDataCommand(ParsedReplayFile lhs, ParsedReplayFile rhs, List differences) { + record Key(String klass, String name, String signature) {} + record Value(int state, int invocationCounter) {} + BiConsumer, Command> folder = (acc, command) -> { + if (command instanceof CiMethodDataCommand(String klass, String name, String signature, int state, int invocationCounter, List _, List _, List _, List _)) { + acc.put(new Key(klass, name, signature), new Value(state, invocationCounter)); + } + }; + var lhsStaticFields = extractMap(lhs, folder); + var rhsStaticFields = extractMap(rhs, folder); + diffMaps("CiInstanceKlass", lhsStaticFields, rhsStaticFields, Value::equals, differences); + } + + static void compareCiMethodCommand(ParsedReplayFile lhs, ParsedReplayFile rhs, List differences) { + record Key(String klass, String name, String signature) {} + record Value(int invocationCounter, int backedgeCounter, int interpreterInvocationCount, int interpreterThrowoutCount, int instructionsSize) {} + BiConsumer, Command> folder = (acc, command) -> { + if (command instanceof CiMethodCommand(String klass, String name, String signature, int invocationCounter, int backedgeCounter, int interpreterInvocationCount, int interpreterThrowoutCount, int instructionsSize)) { + acc.put(new Key(klass, name, signature), new Value(invocationCounter, backedgeCounter, interpreterInvocationCount, interpreterThrowoutCount, instructionsSize)); + } + }; + var lhsStaticFields = extractMap(lhs, folder); + var rhsStaticFields = extractMap(rhs, folder); + diffMaps("CiInstanceKlass", lhsStaticFields, rhsStaticFields, Value::equals, differences); + } + + static void compareCompileCommand(ParsedReplayFile lhs, ParsedReplayFile rhs, List differences) { + record Key(String klass, String name, String signature) {} + record Value(int entryBci, int compLevel, List inlines) {} + BiConsumer, Command> folder = (acc, command) -> { + if (command instanceof CompileCommand(String klass, String name, String signature, int entryBci, int compLevel, List inlines)) { + acc.put(new Key(klass, name, signature), new Value(entryBci, compLevel, inlines)); + } + }; + var lhsStaticFields = extractMap(lhs, folder); + var rhsStaticFields = extractMap(rhs, folder); + diffMaps("CiInstanceKlass", lhsStaticFields, rhsStaticFields, Value::equals, differences); + } + + static List findDifferences(ParsedReplayFile lhs, ParsedReplayFile rhs) { + List differences = new ArrayList<>(); + + compareVersion(lhs, rhs, differences); + compareJvmtiExport(lhs, rhs, differences); + compareInstanceKlasses(lhs, rhs, differences); + compareCiInstanceKlasses(lhs, rhs, differences); + compareStaticFieldCommand(lhs, rhs, differences); + compareCiMethodDataCommand(lhs, rhs, differences); + compareCiMethodCommand(lhs, rhs, differences); + compareCompileCommand(lhs, rhs, differences); + + return differences; + } + + Optional findStaticFieldCommand(String klass, String fieldName) { + Asserts.assertNotNull(staticFieldCommands); // Must be already indexed + var f = new StaticField(klass, fieldName); + if (!staticFieldCommands.containsKey(f)) { + return Optional.empty(); + } + return Optional.ofNullable(staticFieldCommands.get(f)); + } + } } diff --git a/test/hotspot/jtreg/compiler/ciReplay/TestDumpReplay.java b/test/hotspot/jtreg/compiler/ciReplay/TestDumpReplay.java index 30f8aff7d17..63fc84fb668 100644 --- a/test/hotspot/jtreg/compiler/ciReplay/TestDumpReplay.java +++ b/test/hotspot/jtreg/compiler/ciReplay/TestDumpReplay.java @@ -30,8 +30,8 @@ * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+IgnoreUnrecognizedVMOptions * -Xbootclasspath/a:. -XX:+WhiteBoxAPI * -Xbatch -XX:-TieredCompilation -XX:+AlwaysIncrementalInline - * -XX:CompileCommand=compileonly,compiler.ciReplay.TestDumpReplay::* - * compiler.ciReplay.TestDumpReplay + * -XX:CompileCommand=compileonly,${test.main.class}::* + * ${test.main.class} */ package compiler.ciReplay; diff --git a/test/hotspot/jtreg/compiler/ciReplay/TestDumpReplayCommandLine.java b/test/hotspot/jtreg/compiler/ciReplay/TestDumpReplayCommandLine.java index 04ad56f4d24..ca4d253d1b0 100644 --- a/test/hotspot/jtreg/compiler/ciReplay/TestDumpReplayCommandLine.java +++ b/test/hotspot/jtreg/compiler/ciReplay/TestDumpReplayCommandLine.java @@ -32,7 +32,7 @@ * @build jdk.test.whitebox.WhiteBox * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -XX:+TieredCompilation - * compiler.ciReplay.TestDumpReplayCommandLine + * ${test.main.class} */ package compiler.ciReplay; @@ -40,15 +40,7 @@ package compiler.ciReplay; import jdk.test.lib.Asserts; import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; import java.util.List; -import java.util.StringTokenizer; -import java.util.regex.Matcher; -import java.util.regex.Pattern; public class TestDumpReplayCommandLine extends DumpReplayBase { diff --git a/test/hotspot/jtreg/compiler/ciReplay/TestIncrementalInlining.java b/test/hotspot/jtreg/compiler/ciReplay/TestIncrementalInlining.java index 43daab7a9b0..c5a398d6dba 100644 --- a/test/hotspot/jtreg/compiler/ciReplay/TestIncrementalInlining.java +++ b/test/hotspot/jtreg/compiler/ciReplay/TestIncrementalInlining.java @@ -31,7 +31,7 @@ * @build jdk.test.whitebox.WhiteBox * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI - * compiler.ciReplay.TestIncrementalInlining + * ${test.main.class} */ package compiler.ciReplay; diff --git a/test/hotspot/jtreg/compiler/ciReplay/TestInlining.java b/test/hotspot/jtreg/compiler/ciReplay/TestInlining.java index 52a07abb5fd..9d1e888c028 100644 --- a/test/hotspot/jtreg/compiler/ciReplay/TestInlining.java +++ b/test/hotspot/jtreg/compiler/ciReplay/TestInlining.java @@ -31,7 +31,7 @@ * @build jdk.test.whitebox.WhiteBox * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI - * compiler.ciReplay.TestInlining + * ${test.main.class} */ package compiler.ciReplay; diff --git a/test/hotspot/jtreg/compiler/ciReplay/TestInliningProtectionDomain.java b/test/hotspot/jtreg/compiler/ciReplay/TestInliningProtectionDomain.java index 2687315f05b..824c80a0388 100644 --- a/test/hotspot/jtreg/compiler/ciReplay/TestInliningProtectionDomain.java +++ b/test/hotspot/jtreg/compiler/ciReplay/TestInliningProtectionDomain.java @@ -31,7 +31,7 @@ * @build jdk.test.whitebox.WhiteBox * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI - * compiler.ciReplay.TestInliningProtectionDomain + * ${test.main.class} */ package compiler.ciReplay; diff --git a/test/hotspot/jtreg/compiler/ciReplay/TestInvalidReplayFile.java b/test/hotspot/jtreg/compiler/ciReplay/TestInvalidReplayFile.java index 45a2e647c80..3ed9bd988be 100644 --- a/test/hotspot/jtreg/compiler/ciReplay/TestInvalidReplayFile.java +++ b/test/hotspot/jtreg/compiler/ciReplay/TestInvalidReplayFile.java @@ -29,9 +29,11 @@ * @requires vm.compMode != "Xint" * @modules java.base/jdk.internal.misc * java.management - * @run driver TestInvalidReplayFile + * @run driver ${test.main.class} */ +package compiler.ciReplay; + import jdk.test.lib.process.OutputAnalyzer; import jdk.test.lib.process.ProcessTools; diff --git a/test/hotspot/jtreg/compiler/ciReplay/TestLambdas.java b/test/hotspot/jtreg/compiler/ciReplay/TestLambdas.java index 33180a8c759..6d23a9404a8 100644 --- a/test/hotspot/jtreg/compiler/ciReplay/TestLambdas.java +++ b/test/hotspot/jtreg/compiler/ciReplay/TestLambdas.java @@ -31,7 +31,7 @@ * @build jdk.test.whitebox.WhiteBox * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI - * compiler.ciReplay.TestLambdas + * ${test.main.class} */ package compiler.ciReplay; diff --git a/test/hotspot/jtreg/compiler/ciReplay/TestNoClassFile.java b/test/hotspot/jtreg/compiler/ciReplay/TestNoClassFile.java index 0459cf0afd1..ad2901bd2e5 100644 --- a/test/hotspot/jtreg/compiler/ciReplay/TestNoClassFile.java +++ b/test/hotspot/jtreg/compiler/ciReplay/TestNoClassFile.java @@ -31,7 +31,7 @@ * @build jdk.test.whitebox.WhiteBox * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI - * compiler.ciReplay.TestNoClassFile + * ${test.main.class} */ package compiler.ciReplay; diff --git a/test/hotspot/jtreg/compiler/ciReplay/TestNullStaticField.java b/test/hotspot/jtreg/compiler/ciReplay/TestNullStaticField.java index 47a78ad5e44..cd179c14e69 100644 --- a/test/hotspot/jtreg/compiler/ciReplay/TestNullStaticField.java +++ b/test/hotspot/jtreg/compiler/ciReplay/TestNullStaticField.java @@ -31,7 +31,7 @@ * @build jdk.test.whitebox.WhiteBox * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI - * compiler.ciReplay.TestNullStaticField + * ${test.main.class} */ package compiler.ciReplay; diff --git a/test/hotspot/jtreg/compiler/ciReplay/TestReplayV4.java b/test/hotspot/jtreg/compiler/ciReplay/TestReplayV4.java new file mode 100644 index 00000000000..ef95056c91f --- /dev/null +++ b/test/hotspot/jtreg/compiler/ciReplay/TestReplayV4.java @@ -0,0 +1,238 @@ +/* + * 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 8375548 + * @enablePreview + * @library / /test/lib + * @summary Testing the additions and fixes of Replay file v4 + * @requires vm.flagless & vm.flightRecorder != true & vm.compMode != "Xint" & vm.compMode != "Xcomp" & + * vm.debug == true & vm.compiler2.enabled + * @modules java.base/jdk.internal.misc + * java.base/jdk.internal.value + * java.base/jdk.internal.vm.annotation + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -XX:+TieredCompilation + * ${test.main.class} + */ + +package compiler.ciReplay; + +import jdk.internal.value.ValueClass; +import jdk.internal.vm.annotation.NullRestricted; +import jdk.test.lib.Asserts; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.stream.Stream; +import compiler.ciReplay.ReplayFile.ParsedReplayFile; +import compiler.ciReplay.ReplayFile.ParsedReplayFile.*; + +public class TestReplayV4 extends DumpReplayBase { + private final String[] defaultReplayRunFlags; + + public static void main(String[] args) { + new TestReplayV4().runTest("-XX:CompileCommand=dontinline,*::*", + "--enable-preview", + "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED", + "--add-exports", "java.base/jdk.internal.vm.annotation=ALL-UNNAMED", + TIERED_DISABLED_VM_OPTION); + } + + private TestReplayV4() { + defaultReplayRunFlags = defaultReplayRunFlags(); + } + + + private String[] defaultReplayRunFlags() { + List vmFlags = new ArrayList<>(); + Collections.addAll(vmFlags, + "-XX:+ReplayIgnoreInitErrors", + "-XX:CompileCommand=dontinline,*::*", + "--enable-preview", + "--add-exports", "java.base/jdk.internal.value=ALL-UNNAMED", + "--add-exports", "java.base/jdk.internal.vm.annotation=ALL-UNNAMED", + TIERED_DISABLED_VM_OPTION + ); + return vmFlags.toArray(new String[0]); + } + + @Override + public void testAction() { + reDumpAndCompare(); + } + + private String makeMessageFromList(String header, List lines) { + var message = new StringBuilder(header); + message.append(":\n"); + for (String diff : lines) { + message.append(" - ").append(diff).append("\n"); + } + return message.toString(); + } + + private void reDumpAndCompare() { + ParsedReplayFile firstParsedReplay; + ParsedReplayFile secondParsedReplay; + try { + String[] reDumpingFlags = Arrays.copyOf(defaultReplayRunFlags, defaultReplayRunFlags.length + 2); + reDumpingFlags[defaultReplayRunFlags.length] = "-XX:CompileCommand=option," + "*::*" + ",bool,DumpReplay,true"; + reDumpingFlags[defaultReplayRunFlags.length+1] = "-XX:CompileCommand=PrintCompilation,*::*"; + Asserts.assertEQ(getReplayFiles().size(), 1); + File firstReplay = getReplayFiles().getFirst(); + positiveTest(reDumpingFlags); + List replayFilesPostRun; + try (Stream files = Files.list(Paths.get("."))) { + replayFilesPostRun = files.map(Path::toFile).filter(f -> f.getName().startsWith(DUMP_REPLAY_PATTERN)).toList(); + } + Asserts.assertEQ(replayFilesPostRun.size(), 2); + Asserts.assertTrue(replayFilesPostRun.contains(firstReplay)); + var secondReplayOpt = replayFilesPostRun.stream().filter(file -> !file.equals(firstReplay)).findAny(); + Asserts.assertTrue(secondReplayOpt.isPresent()); + var secondReplay = secondReplayOpt.get(); + System.out.println("Replay read by the second run: "+firstReplay+"; replay produced: "+secondReplay); + + firstParsedReplay = ParsedReplayFile.parse(firstReplay); + secondParsedReplay = ParsedReplayFile.parse(secondReplay); + } catch (Throwable t) { + System.out.println(t); + System.out.println(t.getMessage()); + throw new Error("Can't find replay: " + t, t); + } + + // First, let's make sure replay files are not crazy. + var firstInsanities = firstParsedReplay.checkSanity(); + Asserts.assertTrue(firstInsanities.isEmpty(), makeMessageFromList("Insane first replay file", firstInsanities)); + var secondInsanities = secondParsedReplay.checkSanity(); + Asserts.assertTrue(secondInsanities.isEmpty(), makeMessageFromList("Insane second replay file", secondInsanities)); + + // For lookup later. This is allowed only after sanity checking. + firstParsedReplay.index(); + secondParsedReplay.index(); + + // Now, we make sure they have equivalent enough content. + var differences = ParsedReplayFile.findDifferences(firstParsedReplay, secondParsedReplay); + Asserts.assertTrue(differences.isEmpty(), makeMessageFromList("Differences", differences)); + + // Finally, we check a few facts about the second replay file. + var oArrNullCmdOpt = secondParsedReplay.findStaticFieldCommand("compiler/ciReplay/TestReplayV4$Test", "oArrNull"); + Asserts.assertTrue(oArrNullCmdOpt.isPresent()); + var oArrNullCmd = oArrNullCmdOpt.get(); + Asserts.assertTrue(oArrNullCmd instanceof StaticFieldCommandNullArray); + + var oArrRefArrayCmdOpt = secondParsedReplay.findStaticFieldCommand("compiler/ciReplay/TestReplayV4$Test", "oArrRefArray"); + Asserts.assertTrue(oArrRefArrayCmdOpt.isPresent()); + var oArrRefArrayCmdUntyped = oArrRefArrayCmdOpt.get(); + Asserts.assertTrue(oArrRefArrayCmdUntyped instanceof StaticFieldCommandRefArray); + var oArrRefArrayCmd = (StaticFieldCommandRefArray)oArrRefArrayCmdUntyped; + Asserts.assertFalse(oArrRefArrayCmd.nullFree()); + Asserts.assertEquals(oArrRefArrayCmd.length(), 2); + + var oArrNullableAtomicArrayCmdOpt = secondParsedReplay.findStaticFieldCommand("compiler/ciReplay/TestReplayV4$Test", "oArrNullableAtomicArray"); + Asserts.assertTrue(oArrNullableAtomicArrayCmdOpt.isPresent()); + var oArrNullableAtomicArrayCmdUntyped = oArrNullableAtomicArrayCmdOpt.get(); + Asserts.assertTrue(oArrNullableAtomicArrayCmdUntyped instanceof StaticFieldCommandFlatArray); + var oArrNullableAtomicArrayCmd = (StaticFieldCommandFlatArray)oArrNullableAtomicArrayCmdUntyped; + Asserts.assertFalse(oArrNullableAtomicArrayCmd.nullFree()); + Asserts.assertFalse(oArrNullableAtomicArrayCmd.nonAtomic()); + Asserts.assertEquals(oArrNullableAtomicArrayCmd.length(), 2); + + var oArrNullRestrictedAtomicArrayCmdOpt = secondParsedReplay.findStaticFieldCommand("compiler/ciReplay/TestReplayV4$Test", "oArrNullRestrictedAtomicArray"); + Asserts.assertTrue(oArrNullRestrictedAtomicArrayCmdOpt.isPresent()); + var oArrNullRestrictedAtomicArrayCmdUntyped = oArrNullRestrictedAtomicArrayCmdOpt.get(); + Asserts.assertTrue(oArrNullRestrictedAtomicArrayCmdUntyped instanceof StaticFieldCommandFlatArray); + var oArrNullRestrictedAtomicArrayCmd = (StaticFieldCommandFlatArray)oArrNullRestrictedAtomicArrayCmdUntyped; + Asserts.assertTrue(oArrNullRestrictedAtomicArrayCmd.nullFree()); + Asserts.assertFalse(oArrNullRestrictedAtomicArrayCmd.nonAtomic()); + Asserts.assertEquals(oArrNullRestrictedAtomicArrayCmd.length(), 2); + } + + @Override + public String getTestClass() { + return Test.class.getName(); + } + + + private static class Test { + static final Base[] oArrDefault = new Base[2]; + static final Base[] oArrNullableAtomicArray = (Base[]) ValueClass.newNullableAtomicArray(Derived.class, 2); + static final Base[] oArrNullRestrictedAtomicArray = (Base[]) ValueClass.newNullRestrictedAtomicArray(Derived.class, 2, new Derived(1, 0)); + static final Base[] oArrNullRestrictedNonAtomicArray = (Base[]) ValueClass.newNullRestrictedNonAtomicArray(Derived.class, 2, new Derived(2, 0)); + static final Base[] oArrRefArray = (Base[]) ValueClass.newReferenceArray(Derived.class, 2); + static final Base[] oArrNull = null; + + static Base o1, o2, o3, o4; + static final Base a = new Derived(10, 15); + static final Base a_base_null = null; + static final Derived a_derived_null = null; + @NullRestricted + static final Base a_base_null_free = new Derived(10, 15); + @NullRestricted + static final Derived a_derived_null_free = new Derived(10, 15); + + public static void main(String[] args) { + oArrDefault[0] = new Derived(3, 5); + oArrNullableAtomicArray[0] = new Derived(4, 6); + oArrNullRestrictedAtomicArray[0] = new Derived(5, 7); + oArrNullRestrictedNonAtomicArray[0] = new Derived(6, 8); + oArrRefArray[0] = new Derived(7, 9); + for (int i = 0; i < 10000; i++) { + test(); + } + } + + static void test() { + o1 = oArrDefault[0]; + oArrDefault[1] = a; + o2 = oArrNullableAtomicArray[0]; + oArrNullableAtomicArray[1] = a; + o3 = oArrNullRestrictedAtomicArray[0]; + oArrNullRestrictedAtomicArray[1] = a; + o4 = oArrNullRestrictedNonAtomicArray[0]; + oArrNullRestrictedNonAtomicArray[1] = a; + } + + static abstract value class Base { + short x; + byte y; + public Base(int x, int y) { + this.x = (short)x; + this.y = (byte)y; + } + } + + static value class Derived extends Base { + public Derived(int x, int y) { + super(x, y); + } + } + } +} diff --git a/test/hotspot/jtreg/compiler/ciReplay/TestUnresolvedClasses.java b/test/hotspot/jtreg/compiler/ciReplay/TestUnresolvedClasses.java index 9f05969798b..3700c1e4035 100644 --- a/test/hotspot/jtreg/compiler/ciReplay/TestUnresolvedClasses.java +++ b/test/hotspot/jtreg/compiler/ciReplay/TestUnresolvedClasses.java @@ -31,7 +31,7 @@ * @build jdk.test.whitebox.WhiteBox * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI - * compiler.ciReplay.TestUnresolvedClasses + * ${test.main.class} */ package compiler.ciReplay; diff --git a/test/hotspot/jtreg/compiler/ciReplay/TestValueClassArrays.java b/test/hotspot/jtreg/compiler/ciReplay/TestValueClassArrays.java index 65ef1312d68..90d484065e5 100644 --- a/test/hotspot/jtreg/compiler/ciReplay/TestValueClassArrays.java +++ b/test/hotspot/jtreg/compiler/ciReplay/TestValueClassArrays.java @@ -35,7 +35,7 @@ * @build jdk.test.whitebox.WhiteBox * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox * @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -XX:+TieredCompilation - * compiler.ciReplay.TestValueClassArrays + * ${test.main.class} */ package compiler.ciReplay; diff --git a/test/hotspot/jtreg/compiler/inlining/TestSecondIncrementalInliningWithDelayInline.java b/test/hotspot/jtreg/compiler/inlining/TestSecondIncrementalInliningWithDelayInline.java new file mode 100644 index 00000000000..0defda60a54 --- /dev/null +++ b/test/hotspot/jtreg/compiler/inlining/TestSecondIncrementalInliningWithDelayInline.java @@ -0,0 +1,59 @@ +/* + * 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 8382605 + * @summary Verify that the second incremental inlining pass is performed + * when a call discovered during boxing late inlining is delayed + * by a delayinline directive. + * @library /test/lib / + * @run driver ${test.main.class} + */ + +package compiler.inlining; + +import compiler.lib.ir_framework.*; +import jdk.test.lib.Asserts; + +public class TestSecondIncrementalInliningWithDelayInline { + + public static void main(String[] args) { + TestFramework.runWithFlags("-XX:CompileCommand=delayinline,java.lang.Integer::"); + } + + @Test + @IR(failOn = { + IRNode.STATIC_CALL_OF_METHOD, + "java.lang.Integer::" + }, + phase = CompilePhase.BEFORE_MATCHING) + public static Integer test(int value) { + return Integer.valueOf(value); + } + + @Run(test = "test") + public static void run() { + Asserts.assertEQ(test(Integer.MIN_VALUE), Integer.MIN_VALUE); + } +} diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/README.md b/test/hotspot/jtreg/compiler/lib/ir_framework/README.md index 860e2d02969..5560869d80f 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/README.md +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/README.md @@ -168,9 +168,9 @@ testFramework The framework provides various stress and debug flags. They should mainly be used as JTreg VM and/or Javaoptions (apart from `VerifyIR`). The following (property) flags are supported: - `-DVerifyIR=false`: Explicitly disable IR verification. This is useful, for example, if some scenarios use VM flags that let `@IR` annotation rules fail and the user does not want to provide separate IR rules or add flag preconditions to the already existing IR rules. -- `-DTest=test1,test2`: Provide a list of `@Test` method names which should be executed. -- `-DExclude=test3`: Provide a list of `@Test` method names which should be excluded from execution. -- `-DScenarios=1,2`: Provide a list of scenario indexes to specify which scenarios should be executed. +- `-DTest=test1,test2`: Provide a list of `@Test` method names which should be executed. Case-insensitive, plural is allowed (`-DTests`, `-Dtest`, `Dtests` are all fine). +- `-DExclude=test3`: Provide a list of `@Test` method names which should be excluded from execution. Case-insensitive, plural is allowed. +- `-DScenario=1,2`: Provide a list of scenario indexes to specify which scenarios should be executed. Case-insensitive, plural is allowed. - `-DWarmup=200`: Provide a new default value of the number of warm-up iterations (framework default is 2000). This might have an influence on the resulting IR and could lead to matching failures (the user can also set a fixed default warm-up value in a test with `testFrameworkObject.setDefaultWarmup(200)`). - `-DReportStdout=true`: Print the standard output of the Test VM. - `-DVerbose=true`: Enable more fine-grained logging (slows the execution down). @@ -179,7 +179,7 @@ The framework provides various stress and debug flags. They should mainly be use - `-DPrintRuleMatchingTime=true`: Print the time of matching IR rules per method. Slows down the execution as the rules are warmed up before measurement. - `-DVerifyVM=true`: The framework runs the Test VM with additional verification flags (slows the execution down). - `-DExcludeRandom=true`: The framework randomly excludes some methods from compilation. IR verification is disabled completely with this flag. -- `-DFlipC1C2=true`: The framework compiles all `@Test` annotated method with C1 if a C2 compilation would have been applied and vice versa. IR verification is disabled completely with this flag. +- `-DFlipC1C2=true`: The framework compiles all `@Test` annotated methods with C1 if a C2 compilation would have been applied and vice versa. IR verification is disabled completely with this flag. - `-DShuffleTests=false`: Disables the random execution order of all tests (such a shuffling is always done by default). - `-DDumpReplay=true`: Add the `DumpReplay` directive to the Test VM. - `-DGCAfter=true`: Perform `System.gc()` after each test (slows the execution down). diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/Scenario.java b/test/hotspot/jtreg/compiler/lib/ir_framework/Scenario.java index 65f61173e2a..4971b87a236 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/Scenario.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/Scenario.java @@ -23,6 +23,8 @@ package compiler.lib.ir_framework; +import compiler.lib.ir_framework.shared.SystemProperty; +import compiler.lib.ir_framework.shared.SystemProperty.Mode; import compiler.lib.ir_framework.shared.TestRunException; import java.util.*; @@ -44,7 +46,7 @@ import java.util.stream.Collectors; */ public class Scenario { private static final String ADDITIONAL_SCENARIO_FLAGS_PROPERTY = System.getProperty("ScenarioFlags", ""); - private static final String SCENARIOS_PROPERTY = System.getProperty("Scenarios", ""); + private static final String SCENARIOS_PROPERTY = SystemProperty.getCaseInsensitive(Mode.CASE_INSENSITIVE_EMPTY_DEFAULT, "scenario", "scenarios"); private static final List ADDITIONAL_SCENARIO_FLAGS; private static final Set ENABLED_SCENARIOS; diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/TestFramework.java b/test/hotspot/jtreg/compiler/lib/ir_framework/TestFramework.java index 1ec39fbc300..2076b9cf088 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/TestFramework.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/TestFramework.java @@ -157,8 +157,8 @@ public class TestFramework { public static final boolean VERBOSE = Boolean.getBoolean("Verbose"); public static final boolean PRINT_RULE_MATCHING_TIME = Boolean.getBoolean("PrintRuleMatchingTime"); - public static final boolean TESTLIST = !System.getProperty("Test", "").isEmpty(); - public static final boolean EXCLUDELIST = !System.getProperty("Exclude", "").isEmpty(); + private static final boolean TEST_LIST_IS_EMPTY = SystemProperty.getTestList().isEmpty(); + private static final boolean EXCLUDE_LIST_IS_EMPTY = SystemProperty.getExcludeList().isEmpty();; private static final boolean REPORT_STDOUT = Boolean.getBoolean("ReportStdout"); // Only used for internal testing and should not be used for normal user testing. @@ -791,7 +791,7 @@ public class TestFramework { builder.append(System.lineSeparator()); } System.err.println(builder); - if (!VERBOSE && !REPORT_STDOUT && !TESTLIST && !EXCLUDELIST) { + if (!VERBOSE && !REPORT_STDOUT && TEST_LIST_IS_EMPTY && EXCLUDE_LIST_IS_EMPTY) { // Provide a hint to the user how to get additional output/debugging information. System.err.println(RERUN_HINT); } diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/shared/SystemProperty.java b/test/hotspot/jtreg/compiler/lib/ir_framework/shared/SystemProperty.java new file mode 100644 index 00000000000..03a8bc05d99 --- /dev/null +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/shared/SystemProperty.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package compiler.lib.ir_framework.shared; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.function.Function; + +public class SystemProperty { + public record Mode(boolean caseSensitive, String def) { + public static final Mode CASE_INSENSITIVE_EMPTY_DEFAULT = make().withCaseSensitive(false).withDefault(""); + + public static Mode make() { + return new Mode(false, null); + } + + public Mode withCaseSensitive(boolean c) { + return new Mode(c, this.def); + } + + public Mode withDefault(String def) { + return new Mode(this.caseSensitive, def); + } + } + + static public String getCaseInsensitive(Mode mode, String... keys) { + Function normalize = + mode.caseSensitive() + ? (x -> x) + : String::toLowerCase; + List normalizedKeys = Arrays.stream(keys).map(normalize).toList(); + for (Map.Entry e : System.getProperties().entrySet()) { + Object k = e.getKey(); + Object v = e.getValue(); + if (k instanceof String && v instanceof String) { + String ks = normalize.apply((String)k); + if (normalizedKeys.contains(ks)) { + return (String)v; + } + } + } + return mode.def(); + } + + static public String getTestList() { + return getCaseInsensitive(Mode.CASE_INSENSITIVE_EMPTY_DEFAULT, "test", "tests"); + } + + static public String getExcludeList() { + return getCaseInsensitive(Mode.CASE_INSENSITIVE_EMPTY_DEFAULT, "exclude", "excludes"); + } +} diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/test/TestVM.java b/test/hotspot/jtreg/compiler/lib/ir_framework/test/TestVM.java index e6156605423..07c1397749a 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/test/TestVM.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/test/TestVM.java @@ -95,8 +95,8 @@ public class TestVM { private static final boolean PRINT_TIMES = Boolean.getBoolean("PrintTimes") || VERBOSE; public static final boolean USE_COMPILER = WHITE_BOX.getBooleanVMFlag("UseCompiler"); static final boolean EXCLUDE_RANDOM = Boolean.getBoolean("ExcludeRandom"); - private static final String TESTLIST = System.getProperty("Test", ""); - private static final String EXCLUDELIST = System.getProperty("Exclude", ""); + private static final String TESTLIST = SystemProperty.getTestList(); + private static final String EXCLUDELIST = SystemProperty.getExcludeList(); private static final boolean DUMP_REPLAY = Boolean.getBoolean("DumpReplay"); private static final boolean GC_AFTER = Boolean.getBoolean("GCAfter"); private static final boolean SHUFFLE_TESTS = Boolean.parseBoolean(System.getProperty("ShuffleTests", "true")); diff --git a/test/hotspot/jtreg/compiler/rangechecks/TestLongRangeCheck.java b/test/hotspot/jtreg/compiler/rangechecks/TestLongRangeCheck.java index 6c0a2403c99..1c4fbb8cc9c 100644 --- a/test/hotspot/jtreg/compiler/rangechecks/TestLongRangeCheck.java +++ b/test/hotspot/jtreg/compiler/rangechecks/TestLongRangeCheck.java @@ -1,4 +1,5 @@ /* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2021, Red Hat, Inc. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -25,8 +26,9 @@ * @test * @bug 8259609 8276116 * @summary C2: optimize long range checks in long counted loops - * @requires vm.compiler2.enabled - * @requires vm.compMode != "Xcomp" + * @comment This test tests specific compile and deoptimization behaviors that are disrupted by -Xcomp or + -XX:+DeoptimizeALot. Let's exclude those. + * @requires vm.compiler2.enabled & vm.compMode != "Xcomp" & vm.opt.DeoptimizeALot != true * @library /test/lib / * @modules java.base/jdk.internal.util * @build jdk.test.whitebox.WhiteBox diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestAbstractDelayedAccess.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestAbstractDelayedAccess.java new file mode 100644 index 00000000000..389bc597043 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestAbstractDelayedAccess.java @@ -0,0 +1,71 @@ +/* + * 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.valhalla.inlinetypes; + +import jdk.internal.vm.annotation.NullRestricted; +import jdk.test.lib.Asserts; + +/* + * @test + * @bug 8389234 + * @summary Test C1 delayed access with a field declared in an abstract value class. + * @library /test/lib + * @enablePreview + * @modules java.base/jdk.internal.vm.annotation + * @run main ${test.main.class} + * @run main/othervm -Xbatch -XX:TieredStopAtLevel=1 + * ${test.main.class} + */ +public class TestAbstractDelayedAccess { + static abstract value class AbstractValue { + @NullRestricted + Integer i = 42; + } + + static value class ConcreteValue extends AbstractValue { } + + static class Holder { + @NullRestricted + ConcreteValue value; + + Holder() { + value = new ConcreteValue(); + super(); + } + } + + // Loading h.value starts a delayed flat field access. Resolving AbstractValue.i + // while it is pending must not cast the AbstractValue holder to ciInlineKlass. + static int test(Holder h) { + return h.value.i; + } + + public static void main(String[] args) { + Holder h = new Holder(); + for (int i = 0; i < 20_000; i++) { + Asserts.assertEQ(test(h), 42); + } + } +} + diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestDeadStoreFlat.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestDeadStoreFlat.java new file mode 100644 index 00000000000..c0189ee0267 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestDeadStoreFlat.java @@ -0,0 +1,59 @@ +/* + * 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 8389214 + * @summary Test that a StoreFlatNode with a TOP value input is properly handled by IGVN. + * @enablePreview + * @run main ${test.main.class} + * @run main/othervm -XX:+IgnoreUnrecognizedVMOptions -XX:-TieredCompilation -Xcomp + * -XX:+UnlockDiagnosticVMOptions -XX:+AlwaysIncrementalInline + * -XX:+StressIGVN -XX:StressSeed=331205763 + * -XX:CompileCommand=compileonly,${test.main.class}::test + * ${test.main.class} + */ + +package compiler.valhalla.inlinetypes; + +public class TestDeadStoreFlat { + static Integer box() { + throw new RuntimeException(); + } + + static Integer[] test() { + try { + Integer boxed = box(); + return new Integer[] { boxed }; + } catch (RuntimeException e) { + return null; + } + } + + public static void main(String[] args) { + // Make sure exception class loaded + RuntimeException tmp = new RuntimeException(); + test(); + } +} + diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestFlatArrayCheckHoisting.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestFlatArrayCheckHoisting.java new file mode 100644 index 00000000000..6c2c7de0041 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestFlatArrayCheckHoisting.java @@ -0,0 +1,88 @@ +/* + * 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 8389354 + * @summary Test that moving a flat array check does not create an anti-dependence cycle + * @enablePreview + * @run main ${test.main.class} + * @run main/othervm -Xbatch + * -XX:CompileCommand=compileonly,${test.main.class}::test + * -XX:+UnlockDiagnosticVMOptions -XX:-UseArrayLoadStoreProfile + * -XX:+StressGCM -XX:StressSeed=0 + * ${test.main.class} + */ + +package compiler.valhalla.inlinetypes; + +public class TestFlatArrayCheckHoisting { + // A and C arrays are flat, B is too large to flatten and I has identity + static value class A { final int x; A(int x) { this.x = x; } } + static value class B { final long x, y; B(long x, long y) { this.x = x; this.y = y; } } + static value class C { final int x; C(int x) { this.x = x; } } + static final class I { final int x; I(int x) { this.x = x; } } + + static long helper(Object v) { + if (v instanceof A x) return x.x; + if (v instanceof B x) return x.x * 3 + x.y; + if (v instanceof C x) return x.x * 3; + return ((I)v).x * 3; + } + + static long test(Object[] a, int kind) { + long sum = 0; + for (int i = 0; i < 150_000; i++) { + Object v = switch (kind) { + case 0 -> new A(i); + case 1 -> new B(i, i ^ 1); + case 2 -> new I(i); + default -> new C(i); + }; + int index = i & 1; + // The FlatArrayCheck for below access is incorrectly moved out of the loop + a[index] = v; + Object loaded = a[index]; + if (helper(loaded) != helper(v)) { + throw new AssertionError(); + } + if (kind == 3) { + C copy = new C(((C)v).x); + if (v != copy) { + throw new AssertionError(); + } + } + sum += helper(a[index]); + } + return sum; + } + + public static void main(String[] args) { + // Alternate non-flat and flat layouts until C2 uses a generic flat-array check + test(new I[2], 2); + test(new A[2], 0); + test(new B[2], 1); + test(new C[2], 3); + } +} + diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestFlatTwoOopField.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestFlatTwoOopField.java new file mode 100644 index 00000000000..cfaef1920ad --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestFlatTwoOopField.java @@ -0,0 +1,70 @@ +/* + * 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 8389139 + * @summary Test atomic flat field stores with two embedded oops + * @enablePreview + * @modules java.base/jdk.internal.vm.annotation + * @run main ${test.main.class} + * @run main/othervm -Xbatch -XX:TieredStopAtLevel=1 + * ${test.main.class} + */ + +package compiler.valhalla.inlinetypes; + +import jdk.internal.vm.annotation.NullRestricted; + +public class TestFlatTwoOopField { + static value class TwoOopValue { + Object x; + Object y; + + TwoOopValue(Object x, Object y) { + this.x = x; + this.y = y; + } + } + + @NullRestricted + TwoOopValue field; + + TestFlatTwoOopField() { + field = new TwoOopValue(null, null); + super(); + } + + static void test(TestFlatTwoOopField holder, TwoOopValue value) { + holder.field = value; + } + + public static void main(String[] args) { + TestFlatTwoOopField holder = new TestFlatTwoOopField(); + TwoOopValue value = new TwoOopValue(null, null); + for (int i = 0; i < 20_000; i++) { + test(holder, value); + } + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestIntrinsics.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestIntrinsics.java index 299230f0b98..0d91a856e04 100644 --- a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestIntrinsics.java +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestIntrinsics.java @@ -149,6 +149,7 @@ public class TestIntrinsics { public TestIntrinsics() { test24_vt = MyValue1.createWithFieldsInline(rI, rL); + test31_vt = MyValue1.createDefaultInline(); super(); } @@ -658,6 +659,7 @@ public class TestIntrinsics { Asserts.assertEQ(v.v1, res); } + @NullRestricted MyValue1 test31_vt; private static final long TEST31_VT_OFFSET; private static final boolean TEST31_VT_FLATTENED; @@ -837,6 +839,54 @@ public class TestIntrinsics { Asserts.assertEQ(vt, test31_vt); } + // Test put intrinsic with null + @Test + @IR(failOn = {CALL_UNSAFE}) + public void test39(MyValue1 val) { + if (TEST31_VT_FLATTENED) { + U.putFlatValue(this, TEST31_VT_OFFSET, TEST31_VT_LAYOUT, MyValue1.class, val); + } else { + U.putReference(this, TEST31_VT_OFFSET, null); + } + } + + @Run(test = "test39") + public void test39_verifier() { + test31_vt = MyValue1.createDefaultInline(); + try { + test39(null); + if (TEST31_VT_FLATTENED) { + throw new RuntimeException("No NullPointerException thrown"); + } + } catch (NullPointerException npe) { + // Expected + } + } + + // Same as test39 but with a constant null + @Test + @IR(failOn = {CALL_UNSAFE}) + public void test39Constant() { + if (TEST31_VT_FLATTENED) { + U.putFlatValue(this, TEST31_VT_OFFSET, TEST31_VT_LAYOUT, MyValue1.class, null); + } else { + U.putReference(this, TEST31_VT_OFFSET, null); + } + } + + @Run(test = "test39Constant") + public void test39Constant_verifier() { + test31_vt = MyValue1.createDefaultInline(); + try { + test39Constant(); + if (TEST31_VT_FLATTENED) { + throw new RuntimeException("No NullPointerException thrown"); + } + } catch (NullPointerException npe) { + // Expected + } + } + // Test value class array creation via reflection @Test public Object[] test40(Class componentType, int len) { @@ -1246,7 +1296,8 @@ public class TestIntrinsics { @ForceInline static SmallValue createWithFieldsInline(int x, long y) { - return new SmallValue((byte)x, (byte)y); + // Make sure it's different from the default value, some tests rely on this + return new SmallValue((byte)x, (byte) (y | 1)); } } diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestIsAssignableFrom.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestIsAssignableFrom.java new file mode 100644 index 00000000000..fd532da93ec --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestIsAssignableFrom.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8389341 + * @summary Test that Class.isAssignableFrom returns true for two identical arguments. + * @library /test/lib + * @run main ${test.main.class} + * @run main/othervm -Xcomp -XX:-TieredCompilation + * -XX:CompileCommand=compileonly,${test.main.class}::test* + * -XX:CompileCommand=delayinline,${test.main.class}::helperDelayed + * ${test.main.class} + */ + +package compiler.valhalla.inlinetypes; + +import jdk.test.lib.Asserts; + +public final class TestIsAssignableFrom { + static boolean test(Class c) { + return TestIsAssignableFrom[].class.isAssignableFrom(c); + } + + static Class helperDelayed() { + return TestIsAssignableFrom[].class; + } + + static boolean testLate(Class c) { + return helperDelayed().isAssignableFrom(c); + } + + public static void main(String[] args) { + Asserts.assertEQ(test(TestIsAssignableFrom[].class), true); + Asserts.assertEQ(testLate(TestIsAssignableFrom[].class), true); + } +} + diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestLWorld.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestLWorld.java index ef64dc12e78..3e332b5fec3 100644 --- a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestLWorld.java +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestLWorld.java @@ -3087,9 +3087,11 @@ public class TestLWorld { @IR(applyIf = {"UseArrayFlattening", "true"}, failOn = {STORE_UNKNOWN_INLINE, INLINE_ARRAY_NULL_GUARD}, counts = {COUNTED_LOOP, "= 2", LOAD_UNKNOWN_INLINE, "= 2"}, - // Match on CCP since we are removing one of the unswitched loop versions later due to being empty + // Match on CCP since we are removing one of the unswitched loop versions later due to being empty phase = {CompilePhase.CCP1}) public void test107(Object[] src1, Object[] src2) { + // Null check both arrays before the loop to allow both flat array checks to be hoisted. + src2 = Objects.requireNonNull(src2); for (int i = 0; i < src1.length; i++) { oFld1 = src1[i]; oFld2 = src2[i]; @@ -5511,4 +5513,16 @@ public class TestLWorld { public void runTestAcmp() { Asserts.assertFalse(testAcmp()); } + + @Test + @Arguments(values = Argument.BOOLEAN_TOGGLE_FIRST_FALSE) + static Object[] testRefinedArrayTypeFlow(boolean flag) { + Object[] oArr; + if (flag) { + oArr = new Object[1]; + } else { + oArr = new String[1]; + } + return oArr; + } } diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestLateInlineSharedInlineType.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestLateInlineSharedInlineType.java new file mode 100644 index 00000000000..c8846c913cf --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestLateInlineSharedInlineType.java @@ -0,0 +1,99 @@ +/* + * 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 8388544 + * @summary Test late inlining of a method handle that returns a shared value object. + * @requires vm.compiler2.enabled + * @enablePreview + * @run main/othervm -XX:+IgnoreUnrecognizedVMOptions -XX:+AlwaysIncrementalInline -Xbatch + * -XX:CompileCommand=dontinline,${test.main.class}::test* + * ${test.main.class} + */ + +package compiler.valhalla.inlinetypes; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; + +public value class TestLateInlineSharedInlineType { + static final MethodHandle MH1; + static final MethodHandle MH2; + static volatile Object vSink; + static Object sink1; + static Object sink2; + + Integer integer = 42; + + public Integer getInteger() { + return integer; + } + + static { + try { + MH1 = MethodHandles.lookup().findVirtual(TestLateInlineSharedInlineType.class, "getInteger", MethodType.methodType(Integer.class)); + MH2 = MH1.asType(MethodType.methodType(Object.class, TestLateInlineSharedInlineType.class)); + } catch (ReflectiveOperationException e) { + throw new ExceptionInInitializerError(e); + } + } + + // Below tests all trigger the same bug but with slightly different failure modes: + // Field 'integer' is represented by a (shared) InlineTypeNode and buffered on the + // first store to sink. Late MH inlining updates that node's oop input in-place to + // a newly created, later buffer. InlineTypeNode::Ideal() then removes the first + // buffer as redundant re-allocation and rewires an earlier use to the later buffer. + + // We assert with "Bad immediate dominator info." when walking the dominator chain + // from early use towards the non-dominating later definition. + void test1() throws Throwable { + vSink = integer; + vSink = (Integer)MH1.invokeExact(this); + } + + // We assert with "bad dominance" in loop verification because the definition of the + // second buffer does not dominate its use at the first vSink store. + void test2() throws Throwable { + vSink = integer; + vSink = MH2.invokeExact(this); + } + + // The invalid dependency remains hidden in a Phi and escapes normal PhaseCFG::verify(). + // The early sink1 store therefore reads the late buffer's register before it is + // defined, leading to an "object not in heap" assert in the GC. + void test3() throws Throwable { + sink1 = integer; + sink2 = MH2.invokeExact(this); + } + + public static void main(String[] args) throws Throwable { + for (int i = 0; i < 50_000; i++) { + TestLateInlineSharedInlineType test = new TestLateInlineSharedInlineType(); + test.test1(); + test.test2(); + test.test3(); + } + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestPushInlineTypeDownDeadBranch.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestPushInlineTypeDownDeadBranch.java new file mode 100644 index 00000000000..f9570e5995e --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestPushInlineTypeDownDeadBranch.java @@ -0,0 +1,73 @@ +/* + * 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 8389088 + * @summary Test that PushInlineTypeDown correctly handles dying branches in do_transform(). + * @library /test/lib + * @enablePreview + * @run main/othervm -XX:+IgnoreUnrecognizedVMOptions + * -Xcomp -XX:+DeoptimizeALot -XX:+AlwaysIncrementalInline -XX:CompileOnly=${test.main.class}::test + * ${test.main.class} + * @run main/othervm -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions + * -Xcomp -XX:+StressIGVN -XX:+AlwaysIncrementalInline -XX:CompileOnly=${test.main.class}::test + * ${test.main.class} + * @run main ${test.main.class} + */ + +package compiler.valhalla.inlinetypes; + +import jdk.test.lib.Asserts; + +public class TestPushInlineTypeDownDeadBranch { + public static void main(String[] args) { + for (int i = 0; i < 10000; i++) { + Asserts.assertEQ(test(), null); + } + } + + static V test() { + // null + // | + // v + // Before Incremental Inlining: CallStaticJava -> OpaqueParse -> CastPP -> CheckCastPP -> Phi -> InlineType -> Return + // After Incremental Inlining: InlineType(null) -> OpaqueParse -> CastPP -> CheckCastPP -> Phi -> InlineType -> Return + // During IGVN: InlineType(null) -> CastPP -> CheckCastPP -> Phi -> InlineType -> Return + // InlineType(null) -> CastPP -> CheckCastPP -> Phi -> InlineType -> Return + // InlineType(null) -> InlineType -> Return + // Before Patch: InlineType(TOP /* wrong! */) -> InlineType -> Return + // + // After Patch: InlineType(null) -> InlineType -> Return + Object obj = foo(null); + return (V)obj; + } + + static Object foo(Object obj) { + return (V)obj; + } + + static value class V { + int i = 34; + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestReferenceArrayClone.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestReferenceArrayClone.java new file mode 100644 index 00000000000..681eb5687b0 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestReferenceArrayClone.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @summary Verify that clone preserves the layout of reference arrays. + * @bug 8388256 + * @requires vm.compiler2.enabled + * @library /test/lib / + * @enablePreview + * @modules java.base/jdk.internal.value + * @run main ${test.main.class} + * @run main/othervm -Xcomp -XX:-TieredCompilation -XX:-UseTLAB + * -XX:CompileCommand=compileonly,${test.main.class}::testClone + * ${test.main.class} + */ + +package compiler.valhalla.inlinetypes; + +import jdk.internal.value.ValueClass; +import jdk.test.lib.Asserts; + +public class TestReferenceArrayClone { + static Integer[] testClone(Integer[] a) { + return a.clone(); + } + + public static void main(String[] args) { + Integer[] array = (Integer[])ValueClass.newReferenceArray(Integer.class, 1); + array[0] = 42; + array = testClone(array); + Asserts.assertEQ(array[0], 42, "unexpected element"); + Asserts.assertFalse(ValueClass.isFlatArray(array), "should not be flat"); + Asserts.assertFalse(ValueClass.isNullRestrictedArray(array), "should not be null-restricted"); + Asserts.assertTrue(ValueClass.isAtomicArray(array), "should be atomic"); + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestReturnBufferClassInitialization.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestReturnBufferClassInitialization.java new file mode 100644 index 00000000000..e276230a8c7 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestReturnBufferClassInitialization.java @@ -0,0 +1,104 @@ +/* + * 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 8389391 + * @key stress randomness + * @summary Return buffer allocation for late-inlined MH calls should not initialize the class. + * @requires vm.compiler2.enabled + * @library /test/lib + * @enablePreview + * @run main ${test.main.class} + * @run main/othervm -Xbatch -XX:-TieredCompilation -XX:+UnlockDiagnosticVMOptions + * -XX:+StressIncrementalInlining -XX:StressSeed=1 + * -XX:CompileCommand=exclude,${test.main.class}::target + * ${test.main.class} + */ + +package compiler.valhalla.inlinetypes; + +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; + +import jdk.test.lib.Asserts; + +public class TestReturnBufferClassInitialization { + static boolean initialized; + static boolean returnNonNull; + static int invocations; + + static value class UninitializedValue { + final int value; + + UninitializedValue(int value) { + this.value = value; + } + + static { + initialized = true; + } + } + + static UninitializedValue target() { + invocations++; + return returnNonNull ? new UninitializedValue(42) : null; + } + + static final MethodHandle HANDLE; + static { + try { + MethodType exact = MethodType.methodType(UninitializedValue.class); + MethodHandle target = MethodHandles.lookup().findStatic(TestReturnBufferClassInitialization.class, "target", exact); + HANDLE = target.asType(exact.changeReturnType(Object.class)); + } catch (ReflectiveOperationException e) { + throw new ExceptionInInitializerError(e); + } + } + + static Object test() throws Throwable { + // Late inlining 'target' through the MH requires buffering because the caller expects an oop return. + // C2 must not initialize UninitializedValue by allocating an unused buffer when the result is null. + return (Object) HANDLE.invokeExact(); + } + + public static void main(String[] args) throws Throwable { + Asserts.assertFalse(initialized, "Should not be initialized"); + Object result = null; + for (int i = 0; i < 10_000; i++) { + result = test(); + } + Asserts.assertNull(result, "Unexpected result"); + Asserts.assertFalse(initialized, "Should not be initialized"); + Asserts.assertEQ(invocations, 10_000, "Unexpected invocation count"); + + // Test that a non-null scalarized return deoptimizes at the next BCI without re-executing target + returnNonNull = true; + result = test(); + Asserts.assertEQ(invocations, 10_001, "Unexpected invocation count - target re-executed?"); + Asserts.assertEQ(result, new UninitializedValue(42), "Unexpected result"); + Asserts.assertTrue(initialized, "Should now be initialized"); + } +} + diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestSafepointScalarizationNodeLimit.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestSafepointScalarizationNodeLimit.java new file mode 100644 index 00000000000..f3eb37856d0 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestSafepointScalarizationNodeLimit.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @summary Test that scalarizing value objects in safepoint debug info respects the C2 node limit. + * @bug 8388361 + * @enablePreview + * @requires vm.compiler2.enabled + * @library /test/lib / + * @run main/othervm -Xcomp -XX:+IgnoreUnrecognizedVMOptions -XX:-TieredCompilation + * -XX:+DeoptimizeALot + * -XX:CompileCommand=compileonly,${test.main.class}::test* + * -XX:CompileCommand=dontinline,${test.main.class}::blackhole + * -XX:CompileCommand=inline,${test.main.class}::safepoints + * ${test.main.class} + */ + +package compiler.valhalla.inlinetypes; + +import jdk.test.lib.Asserts; + +public class TestSafepointScalarizationNodeLimit { + + // Not inlined + static void blackhole() { } + + // 30 safepoints + static void safepoints() { + blackhole(); blackhole(); blackhole(); blackhole(); blackhole(); + blackhole(); blackhole(); blackhole(); blackhole(); blackhole(); + blackhole(); blackhole(); blackhole(); blackhole(); blackhole(); + blackhole(); blackhole(); blackhole(); blackhole(); blackhole(); + blackhole(); blackhole(); blackhole(); blackhole(); blackhole(); + blackhole(); blackhole(); blackhole(); blackhole(); blackhole(); + } + + static int test1(int x) { + // DeoptimizeALot keeps all locals live. Each scalar value is therefore present + // in the debug info at every safepoint below. + Integer i0 = x, i1 = x + 1, i2 = x + 2, i3 = x + 3, i4 = x + 4, + i5 = x + 5, i6 = x + 6, i7 = x + 7, i8 = x + 8, i9 = x + 9, + i10 = x + 10, i11 = x + 11, i12 = x + 12, i13 = x + 13, i14 = x + 14, + i15 = x + 15, i16 = x + 16, i17 = x + 17, i18 = x + 18, i19 = x + 19, + i20 = x + 20, i21 = x + 21, i22 = x + 22, i23 = x + 23, i24 = x + 24, + i25 = x + 25, i26 = x + 26, i27 = x + 27, i28 = x + 28, i29 = x + 29, + i30 = x + 30, i31 = x + 31, i32 = x + 32, i33 = x + 33, i34 = x + 34, + i35 = x + 35, i36 = x + 36, i37 = x + 37, i38 = x + 38, i39 = x + 39, + i40 = x + 40, i41 = x + 41, i42 = x + 42, i43 = x + 43, i44 = x + 44, + i45 = x + 45, i46 = x + 46, i47 = x + 47, i48 = x + 48, i49 = x + 49, + i50 = x + 50, i51 = x + 51, i52 = x + 52, i53 = x + 53, i54 = x + 54, + i55 = x + 55, i56 = x + 56, i57 = x + 57, i58 = x + 58, i59 = x + 59, + i60 = x + 60, i61 = x + 61, i62 = x + 62, i63 = x + 63, i64 = x + 64, + i65 = x + 65, i66 = x + 66, i67 = x + 67, i68 = x + 68, i69 = x + 69, + i70 = x + 70, i71 = x + 71, i72 = x + 72, i73 = x + 73, i74 = x + 74, + i75 = x + 75, i76 = x + 76, i77 = x + 77, i78 = x + 78, i79 = x + 79, + i80 = x + 80, i81 = x + 81, i82 = x + 82, i83 = x + 83, i84 = x + 84, + i85 = x + 85, i86 = x + 86, i87 = x + 87, i88 = x + 88, i89 = x + 89, + i90 = x + 90, i91 = x + 91, i92 = x + 92, i93 = x + 93, i94 = x + 94, + i95 = x + 95, i96 = x + 96, i97 = x + 97, i98 = x + 98, i99 = x + 99; + + // 30 x 30 = 900 safepoints, each one with 100 live Integers + safepoints(); safepoints(); safepoints(); safepoints(); safepoints(); + safepoints(); safepoints(); safepoints(); safepoints(); safepoints(); + safepoints(); safepoints(); safepoints(); safepoints(); safepoints(); + safepoints(); safepoints(); safepoints(); safepoints(); safepoints(); + safepoints(); safepoints(); safepoints(); safepoints(); safepoints(); + safepoints(); safepoints(); safepoints(); safepoints(); safepoints(); + return i99; + } + + // Same as test2 but with fewer safepoints - triggered a different assert + static int test2(int x) { + // DeoptimizeALot keeps all locals live. Each scalar value is therefore present + // in the debug info at every safepoint below. + Integer i0 = x, i1 = x + 1, i2 = x + 2, i3 = x + 3, i4 = x + 4, + i5 = x + 5, i6 = x + 6, i7 = x + 7, i8 = x + 8, i9 = x + 9, + i10 = x + 10, i11 = x + 11, i12 = x + 12, i13 = x + 13, i14 = x + 14, + i15 = x + 15, i16 = x + 16, i17 = x + 17, i18 = x + 18, i19 = x + 19, + i20 = x + 20, i21 = x + 21, i22 = x + 22, i23 = x + 23, i24 = x + 24, + i25 = x + 25, i26 = x + 26, i27 = x + 27, i28 = x + 28, i29 = x + 29, + i30 = x + 30, i31 = x + 31, i32 = x + 32, i33 = x + 33, i34 = x + 34, + i35 = x + 35, i36 = x + 36, i37 = x + 37, i38 = x + 38, i39 = x + 39, + i40 = x + 40, i41 = x + 41, i42 = x + 42, i43 = x + 43, i44 = x + 44, + i45 = x + 45, i46 = x + 46, i47 = x + 47, i48 = x + 48, i49 = x + 49, + i50 = x + 50, i51 = x + 51, i52 = x + 52, i53 = x + 53, i54 = x + 54, + i55 = x + 55, i56 = x + 56, i57 = x + 57, i58 = x + 58, i59 = x + 59, + i60 = x + 60, i61 = x + 61, i62 = x + 62, i63 = x + 63, i64 = x + 64, + i65 = x + 65, i66 = x + 66, i67 = x + 67, i68 = x + 68, i69 = x + 69, + i70 = x + 70, i71 = x + 71, i72 = x + 72, i73 = x + 73, i74 = x + 74, + i75 = x + 75, i76 = x + 76, i77 = x + 77, i78 = x + 78, i79 = x + 79, + i80 = x + 80, i81 = x + 81, i82 = x + 82, i83 = x + 83, i84 = x + 84, + i85 = x + 85, i86 = x + 86, i87 = x + 87, i88 = x + 88, i89 = x + 89, + i90 = x + 90, i91 = x + 91, i92 = x + 92, i93 = x + 93, i94 = x + 94, + i95 = x + 95, i96 = x + 96, i97 = x + 97, i98 = x + 98, i99 = x + 99; + + // 20 x 30 = 600 safepoints, each one with 100 live Integers + safepoints(); safepoints(); safepoints(); safepoints(); safepoints(); + safepoints(); safepoints(); safepoints(); safepoints(); safepoints(); + safepoints(); safepoints(); safepoints(); safepoints(); safepoints(); + safepoints(); safepoints(); safepoints(); safepoints(); safepoints(); + return i99; + } + + public static void main(String[] args) { + Asserts.assertEquals(test1(42), 141); + Asserts.assertEquals(test2(42), 141); + } +} diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestSubstitutabilityExpansionAfterMacro.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestSubstitutabilityExpansionAfterMacro.java new file mode 100644 index 00000000000..462d69477e6 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestSubstitutabilityExpansionAfterMacro.java @@ -0,0 +1,89 @@ +/* + * 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 8388441 + * @summary Test acmp optimization when operands become known after macro expansion + * @library /test/lib + * @enablePreview + * @run main/othervm -XX:-TieredCompilation -Xbatch ${test.main.class} + */ + +import jdk.test.lib.Asserts; + +public class TestSubstitutabilityExpansionAfterMacro { + record Box(Object value) { } + value record MyValue(Object value) { } + + static boolean equals(Object a, Object b) { + return a == b; + } + + // Below tests trigger InlineTypeNode::can_emit_substitutability_check only + // after macro expansion. + + // EA leaves the right operand as Phi(InlineType, LoadN). + // The cast creates a buffered InlineTypeNode whose oop is a cast of that phi. + // After InlineTypeNode removal after macro expansion, both operands are equivalent. + static boolean test1(boolean b) { + Box box = b ? new Box(new MyValue(null)) : new Box(null); + if (box.value == null) { + box = new Box(new MyValue(null)); + } + return equals((MyValue) box.value, box.value); + } + + // InlineTypeNode removal changes Phi(InlineType(oop=null), exact Object) + // to Phi(null, exact Object) as the right operand. The phi then becomes an exact + // nullable Object and can_be_inline_type() changes to false after macro expansion. + static boolean test2(Object obj, boolean b) { + return equals(obj, b ? (Integer) null : new Object()); + } + + // Both operands are phis of an InlineTypeNode and its oop. After InlineTypeNode + // removal, both phis collapse to the same oop and the operands become equivalent + // after macro expansion. + static boolean test3(Object obj, boolean b) { + Object left = b ? (MyValue) obj : obj; + Object right = b ? obj : (MyValue) obj; + // Use local acmp for fresh profiling + return left == right; + } + + public static void main(String[] args) { + // Warmup and profile acmp with identity operands + for (int i = 0; i < 10_000; i++) { + equals(args, args); + } + Object obj = new Object(); + MyValue val = new MyValue(obj); + for (int i = 0; i < 50_000; i++) { + boolean b = (i & 1) == 0; + Asserts.assertEQ(test1(b), true); + Asserts.assertEQ(test2(b ? null : obj, b), b); + Asserts.assertEQ(test3(val, b), true); + } + } +} + diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestUninitializedFlatAccess.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestUninitializedFlatAccess.java new file mode 100644 index 00000000000..04aa43ed21f --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestUninitializedFlatAccess.java @@ -0,0 +1,104 @@ +/* + * 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.valhalla.inlinetypes; + +import jdk.test.lib.Asserts; + +/** + * @test + * @bug 8389089 + * @summary Accessing an uninitialized flat field or array should not initialize its value class. + * @library /test/lib + * @enablePreview + * @run main ${test.main.class} + * @run main/othervm -Xcomp -XX:TieredStopAtLevel=1 + * -XX:CompileCommand=compileonly,${test.main.class}::test* + * ${test.main.class} + */ + +public class TestUninitializedFlatAccess { + static int fieldClassInitCount; + static int arrayClassInitCount; + + static value class FieldValue { + final int value = 0; + + static { + fieldClassInitCount++; + } + } + + static value class ArrayValue { + final int value = 0; + + static { + arrayClassInitCount++; + } + } + + FieldValue value; + + Object testFieldLoad() { + return value; + } + + void testFieldStore(FieldValue value) { + this.value = value; + } + + static Object testArrayLoad() { + ArrayValue[] array = new ArrayValue[1]; + return array[0]; + } + + static ArrayValue[] testArrayStore(ArrayValue value) { + ArrayValue[] array = new ArrayValue[1]; + array[0] = value; + return array; + } + + public static void main(String[] args) { + TestUninitializedFlatAccess t = new TestUninitializedFlatAccess(); + ArrayValue[] tmp = new ArrayValue[0]; + Asserts.assertEQ(fieldClassInitCount, 0, "FieldValue should not be initialized"); + Asserts.assertEQ(arrayClassInitCount, 0, "ArrayValue should not be initialized"); + + Object fieldValue = t.testFieldLoad(); + Asserts.assertNull(fieldValue, "Unexpected field value"); + Asserts.assertEQ(fieldClassInitCount, 0, "FieldValue should not be initialized"); + + t.testFieldStore(null); + Asserts.assertNull(fieldValue, "Unexpected field value"); + Asserts.assertEQ(fieldClassInitCount, 0, "FieldValue should not be initialized"); + + Object arrayValue = testArrayLoad(); + Asserts.assertNull(arrayValue, "Unexpected array value"); + Asserts.assertEQ(arrayClassInitCount, 0, "ArrayValue should not be initialized"); + + ArrayValue[] array = testArrayStore(null); + Asserts.assertNull(array[0], "Unexpected array value"); + Asserts.assertEQ(arrayClassInitCount, 0, "ArrayValue should not be initialized"); + } +} + diff --git a/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestValueClassCodeCacheLeak.java b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestValueClassCodeCacheLeak.java new file mode 100644 index 00000000000..6286fbdc414 --- /dev/null +++ b/test/hotspot/jtreg/compiler/valhalla/inlinetypes/TestValueClassCodeCacheLeak.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8389450 + * @summary Test that loading and unloading value classes does not leak code cache memory. + * @requires vm.flagless & vm.opt.final.ClassUnloading + * @enablePreview + * @run main/othervm -XX:ReservedCodeCacheSize=32m ${test.main.class} + */ + +package compiler.valhalla.inlinetypes; + +import java.io.InputStream; + +public class TestValueClassCodeCacheLeak { + public static value class TemporaryValue { } + + static final class Loader extends ClassLoader { + Class define(byte[] bytes) { + return defineClass("compiler.valhalla.inlinetypes.TestValueClassCodeCacheLeak$TemporaryValue", bytes, 0, bytes.length); + } + } + + public static void main(String[] args) throws Exception { + InputStream in = TestValueClassCodeCacheLeak.class.getResourceAsStream("TestValueClassCodeCacheLeak$TemporaryValue.class"); + byte[] bytes = in.readAllBytes(); + + // Create a class loader, load value class and throw the class loader away + // to trigger class unloading. This should not leak (code cache) memory. + for (int i = 0; i < 5000; i++) { + new Loader().define(bytes); + } + } +} + diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotFlags/AOTFlags.java b/test/hotspot/jtreg/runtime/cds/appcds/aotFlags/AOTFlags.java index 56c31dcdbda..a62066efe91 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotFlags/AOTFlags.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotFlags/AOTFlags.java @@ -199,7 +199,7 @@ public class AOTFlags { "-XX:AOTConfiguration=" + aotConfigFile, "-Xlog:aot=debug", "-cp", appJar, helloClass); - out = CDSTestUtils.executeAndLog(pb, "ontstep-train"); + out = CDSTestUtils.executeAndLog(pb, "onestep-train"); out.shouldContain("Hello World"); out.shouldContain("AOTConfiguration recorded: " + aotConfigFile); out.shouldContain("AOTCache creation is complete: hello.aot"); @@ -211,7 +211,7 @@ public class AOTFlags { "-XX:AOTConfiguration=" + aotConfigFile, "-Xlog:aot=debug", "-cp", appJar, helloClass); - out = CDSTestUtils.executeAndLog(pb, "ontstep-train"); + out = CDSTestUtils.executeAndLog(pb, "onestep-train"); out.shouldContain("Hello World"); out.shouldContain("AOTConfiguration recorded: " + aotConfigFile); out.shouldContain("AOTCache creation is complete: hello.aot"); @@ -224,7 +224,7 @@ public class AOTFlags { "-XX:AOTConfiguration=" + aotConfigFile, "-Xlog:aot=debug", "-cp", appJar, helloClass); - out = CDSTestUtils.executeAndLog(pb, "ontstep-train"); + out = CDSTestUtils.executeAndLog(pb, "onestep-train"); out.shouldContain("Hello World"); out.shouldContain("AOTConfiguration recorded: " + aotConfigFile); out.shouldContain("AOTCache creation is complete: hello.aot"); @@ -235,7 +235,7 @@ public class AOTFlags { "-XX:AOTCacheOutput=" + aotCacheFile, "-Xlog:aot=debug", "-cp", appJar, helloClass); - out = CDSTestUtils.executeAndLog(pb, "ontstep-train"); + out = CDSTestUtils.executeAndLog(pb, "onestep-train"); out.shouldContain("Hello World"); out.shouldContain("Temporary AOTConfiguration recorded: " + aotCacheFile + ".config"); out.shouldContain("AOTCache creation is complete: hello.aot"); @@ -247,7 +247,7 @@ public class AOTFlags { "-XX:AOTCacheOutput=" + aotCacheFile, "-Xlog:aot=debug", "-cp", appJar, helloClass); - out = CDSTestUtils.executeAndLog(pb, "ontstep-train"); + out = CDSTestUtils.executeAndLog(pb, "onestep-train"); out.shouldContain("Hello World"); out.shouldContain("Temporary AOTConfiguration recorded: " + aotCacheFile + ".config"); out.shouldContain("AOTCache creation is complete: hello.aot"); @@ -259,7 +259,7 @@ public class AOTFlags { "-Dmy.prop=My string -Xshare:off here", // -Xshare:off should not be treated as a single VM opt for the child JVM "-Xlog:aot=debug", "-cp", appJar, helloClass); - out = CDSTestUtils.executeAndLog(pb, "ontstep-train"); + out = CDSTestUtils.executeAndLog(pb, "onestep-train"); out.shouldContain("Hello World"); out.shouldContain("AOTCache creation is complete: hello.aot"); out.shouldMatch("Picked up JAVA_TOOL_OPTIONS:.* -Dmy.prop=My' 'string' '-Xshare:off' 'here"); diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotFlags/AssemblySubProcessFailure.java b/test/hotspot/jtreg/runtime/cds/appcds/aotFlags/AssemblySubProcessFailure.java new file mode 100644 index 00000000000..a4ad79e4bfb --- /dev/null +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotFlags/AssemblySubProcessFailure.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +/* + * @test + * @summary Handling of assembly sub-process failure in onestep AOT training. + * @bug 8382879 + * @requires vm.cds + * @requires vm.flagless + * @library /test/lib /test/hotspot/jtreg/runtime/cds/appcds/test-classes + * @build Hello + * @run driver jdk.test.lib.helpers.ClassFileInstaller -jar hello.jar Hello + * @run driver AssemblySubProcessFailure + */ + +import java.io.File; +import jdk.test.lib.cds.CDSTestUtils; +import jdk.test.lib.helpers.ClassFileInstaller; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +public class AssemblySubProcessFailure { + static String appJar = ClassFileInstaller.getJarPath("hello.jar"); + static String aotConfigFile = "hello.aot.config"; + static String aotCacheFile = "hello.aot"; + static String helloClass = "Hello"; + + public static void main(String[] args) throws Exception { + // The main training run process should report the failure if the AOT assembly + // sub-process has failed. + // + // The easiest way to trigger a failure is to pass a bad VM option, only to the + // sub-process using JDK_AOT_VM_OPTIONS. + ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-XX:AOTMode=record", + "-XX:AOTCacheOutput=" + aotCacheFile, + "-cp", appJar, helloClass); + pb.environment().put("JDK_AOT_VM_OPTIONS", "-XX:+NoSuchOption"); + OutputAnalyzer out = CDSTestUtils.executeAndLog(pb, "onestep-train"); + + out.shouldContain("Hello World"); + out.shouldContain("Temporary AOTConfiguration recorded: " + aotConfigFile); + out.shouldContain("Child process failed; status = 1"); + out.shouldContain("Picked up JDK_AOT_VM_OPTIONS: -XX:+NoSuchOption"); + out.shouldContain("Unrecognized VM option 'NoSuchOption'"); + out.shouldContain("Error: Could not create the Java Virtual Machine."); + out.shouldNotContain("AOTCache creation is complete"); + out.shouldHaveExitValue(1); + + if (!(new File(aotConfigFile)).exists()) { + throw new RuntimeException("Should not delete temporary AOT config file when child process fails: " + aotConfigFile); + } + } +} diff --git a/test/hotspot/jtreg/runtime/cds/appcds/aotFlags/JDK_AOT_VM_OPTIONS.java b/test/hotspot/jtreg/runtime/cds/appcds/aotFlags/JDK_AOT_VM_OPTIONS.java index e7ac72b4d38..5c534b58ee5 100644 --- a/test/hotspot/jtreg/runtime/cds/appcds/aotFlags/JDK_AOT_VM_OPTIONS.java +++ b/test/hotspot/jtreg/runtime/cds/appcds/aotFlags/JDK_AOT_VM_OPTIONS.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -62,7 +62,7 @@ public class JDK_AOT_VM_OPTIONS { // The "-Xshare:off" below should be treated as part of a property value and not // a VM option by itself pb.environment().put("JDK_AOT_VM_OPTIONS", "-Dsome.option='foo -Xshare:off ' -Xmx512m -XX:-AOTClassLinking"); - out = CDSTestUtils.executeAndLog(pb, "ontstep-train"); + out = CDSTestUtils.executeAndLog(pb, "onestep-train"); out.shouldContain("Hello World"); out.shouldContain("AOTCache creation is complete: hello.aot"); out.shouldContain("Picked up JDK_AOT_VM_OPTIONS: -Dsome.option='foo -Xshare:off '"); diff --git a/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestDScenarios.java b/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestDScenarios.java index e5476afc1f6..d6d7cfd8aea 100644 --- a/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestDScenarios.java +++ b/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestDScenarios.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -38,8 +38,8 @@ import jdk.test.lib.process.ProcessTools; * @summary Test -DScenarios property flag. Run with othervm which should not be done when writing tests using the framework. * @library /test/lib / * @run main/othervm -DScenarios=1,5,10 ir_framework.tests.TestDScenarios test - * @run main/othervm -DScenarios=1,4 ir_framework.tests.TestDScenarios test - * @run main/othervm -DScenarios=3,4,9 ir_framework.tests.TestDScenarios test + * @run main/othervm -DScenario=1,4 ir_framework.tests.TestDScenarios test + * @run main/othervm -Dscenarios=3,4,9 ir_framework.tests.TestDScenarios test * @run driver ir_framework.tests.TestDScenarios test2 * @run driver ir_framework.tests.TestDScenarios */ diff --git a/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestDTestAndExclude.java b/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestDTestAndExclude.java index 22d52f692a3..04b3393b946 100644 --- a/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestDTestAndExclude.java +++ b/test/hotspot/jtreg/testlibrary_tests/ir_framework/tests/TestDTestAndExclude.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -95,11 +95,20 @@ public class TestDTestAndExclude { * Create a VM and simulate as if it was a driver VM spawned by JTreg that has -DTest/DExclude set as VM or Javaopts */ protected static void run(String dTest, String dExclude, String arg) throws Exception { - System.out.println("Run -DTest=" + dTest + " -DExclude=" + dExclude + " arg=" + arg); + // Let's randomize a bit which version of -Dtest and -Dexclude we use (caps and plural). + boolean plural = dTest.contains(","); + boolean capital = dTest.length() % 2 == 0; // Any criterion that is not constant or correlated with `plural` would do as well. + String dTestFlag = capital ? "-DTest" : "-Dtest"; + String dExcludeFlag = capital ? "-DExclude" : "-Dexclude"; + if (plural) { + dTestFlag = dTestFlag + "s"; + dExcludeFlag = dExcludeFlag + "s"; + } + System.out.println("Run " + dTestFlag + "=" + dTest + " " + dExcludeFlag + "=" + dExclude + " arg=" + arg); OutputAnalyzer oa; ProcessBuilder process = ProcessTools.createLimitedTestJavaProcessBuilder( "-Dtest.class.path=" + Utils.TEST_CLASS_PATH, "-Dtest.jdk=" + Utils.TEST_JDK, - "-Dtest.vm.opts=-DTest=" + dTest + " -DExclude=" + dExclude, + "-Dtest.vm.opts=" + dTestFlag + "=" + dTest + " " + dExcludeFlag + "=" + dExclude, "ir_framework.tests.TestDTestAndExclude", arg); oa = ProcessTools.executeProcess(process); oa.shouldHaveExitValue(0); diff --git a/test/jdk/TEST.groups b/test/jdk/TEST.groups index 70d74ed75d9..a4513d35de0 100644 --- a/test/jdk/TEST.groups +++ b/test/jdk/TEST.groups @@ -666,6 +666,7 @@ jdk_security_manual_no_input = \ com/sun/crypto/provider/Cipher/AEAD/GCMIncrementByte4.java \ com/sun/crypto/provider/Cipher/AEAD/GCMIncrementDirect4.java \ sun/security/smartcardio/TestChannel.java \ + sun/security/smartcardio/TestCleaner.java \ sun/security/smartcardio/TestConnect.java \ sun/security/smartcardio/TestConnectAgain.java \ sun/security/smartcardio/TestControl.java \ diff --git a/test/jdk/java/lang/Class/desiredAssertionStatus/Sanity.java b/test/jdk/java/lang/Class/desiredAssertionStatus/Sanity.java new file mode 100644 index 00000000000..0eaa6c464fd --- /dev/null +++ b/test/jdk/java/lang/Class/desiredAssertionStatus/Sanity.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* @test + * @bug 8389259 + * @summary Sanity check that Class.desiredAssertionStatus() works. + * @library /test/lib + * @run testng/othervm -esa -ea -Dsys.option=on -Duser.option=on Sanity + * @run testng/othervm -dsa -ea -Dsys.option=off -Duser.option=on Sanity + * @run testng/othervm -esa -da -Dsys.option=on -Duser.option=off Sanity + * @run testng/othervm -dsa -da -Dsys.option=off -Duser.option=off Sanity + */ + +import org.testng.annotations.Test; +import org.testng.Assert; + +public class Sanity { + + @Test + public void test() throws Exception { + boolean systemDefaultOn = System.getProperty("sys.option").equals("on"); + boolean userDefaultOn = System.getProperty("user.option").equals("on"); + + Assert.assertTrue(int.class.desiredAssertionStatus() == false); + Assert.assertTrue(Object.class.desiredAssertionStatus() == systemDefaultOn); + Assert.assertTrue(Object[].class.desiredAssertionStatus() == false); + Assert.assertTrue(Sanity.class.desiredAssertionStatus() == userDefaultOn); + Assert.assertTrue(Sanity[].class.desiredAssertionStatus() == false); + } +} diff --git a/test/jdk/java/net/InetAddress/policy.file b/test/jdk/java/net/InetAddress/policy.file deleted file mode 100644 index 449dba5334f..00000000000 --- a/test/jdk/java/net/InetAddress/policy.file +++ /dev/null @@ -1,10 +0,0 @@ -grant { - permission javax.security.auth.AuthPermission "modifyPrincipals"; - permission javax.security.auth.AuthPermission "doAsPrivileged"; - permission java.util.PropertyPermission "*", "read,write"; -}; - -grant Principal MyPrincipal "test" { - permission java.net.SocketPermission "${host.name}", "resolve"; -}; - diff --git a/test/jdk/java/net/SetFactoryPermission/policy.fail b/test/jdk/java/net/SetFactoryPermission/policy.fail deleted file mode 100644 index a4c6d11f44f..00000000000 --- a/test/jdk/java/net/SetFactoryPermission/policy.fail +++ /dev/null @@ -1,3 +0,0 @@ -grant { - -}; diff --git a/test/jdk/java/net/SetFactoryPermission/policy.success b/test/jdk/java/net/SetFactoryPermission/policy.success deleted file mode 100644 index d382afce449..00000000000 --- a/test/jdk/java/net/SetFactoryPermission/policy.success +++ /dev/null @@ -1,4 +0,0 @@ -grant { - permission java.lang.RuntimePermission "setFactory"; -}; - diff --git a/test/jdk/java/net/httpclient/http2/H2GoAwayPromptConnectionClose.java b/test/jdk/java/net/httpclient/http2/H2GoAwayPromptConnectionClose.java index b5e534afce9..83237bf76c0 100644 --- a/test/jdk/java/net/httpclient/http2/H2GoAwayPromptConnectionClose.java +++ b/test/jdk/java/net/httpclient/http2/H2GoAwayPromptConnectionClose.java @@ -74,7 +74,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals; * @build jdk.test.lib.net.SimpleSSLContext jdk.test.lib.RandomFactory jdk.test.lib.net.URIBuilder * @comment An arbitrary high value for idle connection timeout to prevent idle * connection management from closing the HTTP/2 connection - * @run junit/othervm -Djdk.httpclient.keepalive.timeout.h2=36000 ${test.main.class} + * @run junit/othervm -Djdk.httpclient.keepalive.timeout.h2=36000 + * -Djdk.internal.httpclient.debug=true + * ${test.main.class} */ class H2GoAwayPromptConnectionClose { diff --git a/test/jdk/java/net/httpclient/whitebox/java.net.http/jdk/internal/net/http/SSLEchoTubeTest.java b/test/jdk/java/net/httpclient/whitebox/java.net.http/jdk/internal/net/http/SSLEchoTubeTest.java index 4316a46a25c..3041992ac2a 100644 --- a/test/jdk/java/net/httpclient/whitebox/java.net.http/jdk/internal/net/http/SSLEchoTubeTest.java +++ b/test/jdk/java/net/httpclient/whitebox/java.net.http/jdk/internal/net/http/SSLEchoTubeTest.java @@ -265,7 +265,7 @@ public class SSLEchoTubeTest extends AbstractSSLTubeTest { private final Queue queue = new ConcurrentLinkedQueue<>(); private final int maxQueueSize; private final SequentialScheduler processingScheduler = - new SequentialScheduler(createProcessingTask()); + SequentialScheduler.lockingScheduler(createProcessingTask()); /* Writing into this tube */ private volatile long requested; @@ -360,11 +360,11 @@ public class SSLEchoTubeTest extends AbstractSSLTubeTest { } int transmitted = 0; - private SequentialScheduler.RestartableTask createProcessingTask() { - return new SequentialScheduler.CompleteRestartableTask() { + private Runnable createProcessingTask() { + return new Runnable() { @Override - protected void run() { + public void run() { try { while (!cancelled.get()) { Object item = queue.peek(); @@ -374,39 +374,36 @@ public class SSLEchoTubeTest extends AbstractSSLTubeTest { requestMore(); return; } - try { - System.out.printf("EchoTube processing item, requested=%s, demand=%s, transmitted=%s%n", - requested, demand.get(), transmitted); - if (item instanceof List) { - if (!demand.tryDecrement()) { - System.out.println("EchoTube no demand"); - return; - } - @SuppressWarnings("unchecked") - List bytes = (List) item; - Object removed = queue.remove(); - assert removed == item; - System.out.println("EchoTube processing " - + Utils.remaining(bytes)); - transmitted++; - subscriber.onNext(bytes); - requestMore(); - } else if (item instanceof Throwable) { - cancelled.set(true); - Object removed = queue.remove(); - assert removed == item; - System.out.println("EchoTube processing " + item); - subscriber.onError((Throwable) item); - } else if (item == EOF) { - cancelled.set(true); - Object removed = queue.remove(); - assert removed == item; - System.out.println("EchoTube processing EOF"); - subscriber.onComplete(); - } else { - throw new InternalError(String.valueOf(item)); + System.out.printf("EchoTube processing item, requested=%s, demand=%s, transmitted=%s%n", + requested, demand.get(), transmitted); + if (item instanceof List) { + if (!demand.tryDecrement()) { + System.out.println("EchoTube no demand"); + return; } - } finally { + @SuppressWarnings("unchecked") + List bytes = (List) item; + Object removed = queue.remove(); + assert removed == item; + System.out.println("EchoTube processing " + + Utils.remaining(bytes)); + transmitted++; + subscriber.onNext(bytes); + requestMore(); + } else if (item instanceof Throwable) { + cancelled.set(true); + Object removed = queue.remove(); + assert removed == item; + System.out.println("EchoTube processing " + item); + subscriber.onError((Throwable) item); + } else if (item == EOF) { + cancelled.set(true); + Object removed = queue.remove(); + assert removed == item; + System.out.println("EchoTube processing EOF"); + subscriber.onComplete(); + } else { + throw new InternalError(String.valueOf(item)); } } } catch(Throwable t) { diff --git a/test/jdk/sun/security/smartcardio/TestAll.java b/test/jdk/sun/security/smartcardio/TestAll.java index 72a0f85daaa..1911c58fbf0 100644 --- a/test/jdk/sun/security/smartcardio/TestAll.java +++ b/test/jdk/sun/security/smartcardio/TestAll.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2014, 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 @@ -33,6 +33,7 @@ public class TestAll { private static final Class[] CLASSES = { TestDefault.class, TestChannel.class, + TestCleaner.class, TestConnect.class, TestConnectAgain.class, TestControl.class, diff --git a/test/jdk/sun/security/smartcardio/TestCleaner.java b/test/jdk/sun/security/smartcardio/TestCleaner.java new file mode 100644 index 00000000000..dfcecdb90f8 --- /dev/null +++ b/test/jdk/sun/security/smartcardio/TestCleaner.java @@ -0,0 +1,107 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8380391 + * @summary basic test of CardImpl Cleaner + * @modules java.base/java.lang.ref:open + * @modules java.smartcardio/javax.smartcardio + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/manual/othervm + * -Xbootclasspath/a:. + * -XX:+UnlockDiagnosticVMOptions + * -XX:+WhiteBoxAPI + * TestCleaner + */ + +// This test requires special hardware; a card must be present + +import java.util.WeakHashMap; +import javax.smartcardio.Card; +import javax.smartcardio.CardTerminal; +import java.security.NoSuchAlgorithmException; + +import jdk.test.whitebox.WhiteBox; +import jtreg.SkippedException; + +/** + * Rudimentary test to confirm that the cleaning action does not prevent the + * CardImpl from becoming unreachable and being collected/cleaned. + */ +public class TestCleaner extends Utils { + static WhiteBox wb; + + public static void main(String[] args) throws Exception { + CardTerminal terminal = null; + try { + terminal = getTerminal(args); + } catch (NoSuchAlgorithmException e) { + if ("Error constructing TerminalFactory for PC/SC using SunPCSC".equals(e.getMessage())) { + // Cause is expected to be a PCSCException + if ("SCARD_E_NO_SERVICE".equals(e.getCause().getMessage())) { + throw new SkippedException("Skipping the test: " + + "Unable to construct TerminalFactory"); + } else { + throw e; + } + } + } + if (terminal == null) { + throw new SkippedException("Skipping the test: " + + "no card terminals available"); + } + + while (!terminal.isCardPresent()) { + System.out.println("*** Insert card!"); + Thread.sleep(1000); + } + + // Connect using any available protocol + Card card = terminal.connect("*"); + if (card == null) { + throw new SkippedException("Skipping the test: " + + "no card available"); + } + System.out.println("card is " + card); + + // Ensure card object can become unreachable + WeakHashMap whm = new WeakHashMap<>(); + whm.put(card, new Object()); + + System.out.println("Allow card object to be collected"); + card = null; + terminal = null; + + wb = WhiteBox.getWhiteBox(); + wb.fullGC(); + wb.waitForReferenceProcessing(); + + if (whm.size() > 0) { + throw new RuntimeException("*** TEST FAILED - Card could not be collected"); + } + System.out.println("Card object collected."); + } +}