diff --git a/.github/workflows/build-windows.yml b/.github/workflows/build-windows.yml index 3bb50a137ec8..49071de7771f 100644 --- a/.github/workflows/build-windows.yml +++ b/.github/workflows/build-windows.yml @@ -31,6 +31,9 @@ on: platform: required: true type: string + runs-on: + required: true + type: string extra-conf-options: required: false type: string @@ -67,7 +70,7 @@ env: jobs: build-windows: name: build - runs-on: windows-2025 + runs-on: ${{ inputs.runs-on }} defaults: run: shell: bash diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 20be196b128a..94f011ff76c2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -353,6 +353,7 @@ jobs: uses: ./.github/workflows/build-windows.yml with: platform: windows-x64 + runs-on: windows-2022 msvc-toolset-version: '14.44' msvc-toolset-architecture: 'x86.x64' configure-arguments: ${{ github.event.inputs.configure-arguments }} @@ -366,6 +367,7 @@ jobs: uses: ./.github/workflows/build-windows.yml with: platform: windows-aarch64 + runs-on: windows-2022 msvc-toolset-version: '14.44' msvc-toolset-architecture: 'arm64' make-target: 'hotspot' @@ -446,6 +448,6 @@ jobs: with: platform: windows-x64 bootjdk-platform: windows-x64 - runs-on: windows-2025 + runs-on: windows-2022 dry-run: ${{ needs.prepare.outputs.dry-run == 'true' }} debug-suffix: -debug diff --git a/make/RunTestsPrebuiltSpec.gmk b/make/RunTestsPrebuiltSpec.gmk index 5fe559eafadb..568f69da5a51 100644 --- a/make/RunTestsPrebuiltSpec.gmk +++ b/make/RunTestsPrebuiltSpec.gmk @@ -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 @@ -64,7 +64,7 @@ TEST_JOBS ?= 0 # Use hard-coded values for java flags (one size, fits all!) JAVA_FLAGS := -Duser.language=en -Duser.country=US JAVA_FLAGS_BIG := -Xms64M -Xmx2048M -JAVA_FLAGS_SMALL := -XX:+UseSerialGC -Xms32M -Xmx512M -XX:TieredStopAtLevel=1 +JAVA_FLAGS_SMALL := -Xms32M -Xmx512M -XX:TieredStopAtLevel=1 BUILDJDK_JAVA_FLAGS_SMALL := -Xms32M -Xmx512M -XX:TieredStopAtLevel=1 BUILD_JAVA_FLAGS := $(JAVA_FLAGS_BIG) diff --git a/make/autoconf/boot-jdk.m4 b/make/autoconf/boot-jdk.m4 index b3dbc2929191..4468a9acf279 100644 --- a/make/autoconf/boot-jdk.m4 +++ b/make/autoconf/boot-jdk.m4 @@ -1,5 +1,5 @@ # -# Copyright (c) 2011, 2025, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 2011, 2026, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -481,8 +481,6 @@ AC_DEFUN_ONCE([BOOTJDK_SETUP_BOOT_JDK_ARGUMENTS], AC_MSG_CHECKING([flags for boot jdk java command for small workloads]) - # Use serial gc for small short lived tools if possible - UTIL_ADD_JVM_ARG_IF_OK([-XX:+UseSerialGC],boot_jdk_jvmargs_small,[$JAVA]) UTIL_ADD_JVM_ARG_IF_OK([-Xms32M],boot_jdk_jvmargs_small,[$JAVA]) UTIL_ADD_JVM_ARG_IF_OK([-Xmx512M],boot_jdk_jvmargs_small,[$JAVA]) UTIL_ADD_JVM_ARG_IF_OK([-XX:TieredStopAtLevel=1],boot_jdk_jvmargs_small,[$JAVA]) @@ -492,8 +490,6 @@ AC_DEFUN_ONCE([BOOTJDK_SETUP_BOOT_JDK_ARGUMENTS], JAVA_FLAGS_SMALL=$boot_jdk_jvmargs_small AC_SUBST(JAVA_FLAGS_SMALL) - # Don't presuppose SerialGC is present in the buildjdk. Also, we cannot test - # the buildjdk, but on the other hand we know what it will support. BUILD_JAVA_FLAGS_SMALL="-Xms32M -Xmx512M -XX:TieredStopAtLevel=1" AC_SUBST(BUILD_JAVA_FLAGS_SMALL) diff --git a/make/data/cldr/common/dtd/ldml.dtd b/make/data/cldr/common/dtd/ldml.dtd index aebedd33a43c..b4247f2d9243 100644 --- a/make/data/cldr/common/dtd/ldml.dtd +++ b/make/data/cldr/common/dtd/ldml.dtd @@ -1,5 +1,5 @@ - + @@ -493,6 +493,16 @@ CLDR data files are interpreted according to the LDML specification (http://unic + + + + + + + + + + diff --git a/make/data/cldr/common/main/aa.xml b/make/data/cldr/common/main/aa.xml index 3ff6fb6dd066..791c30096585 100644 --- a/make/data/cldr/common/main/aa.xml +++ b/make/data/cldr/common/main/aa.xml @@ -1,6 +1,6 @@ - @@ -1027,6 +1027,7 @@ For terms of use, see http://www.unicode.org/copyright.html + diff --git a/make/data/cldr/common/supplemental/likelySubtags.xml b/make/data/cldr/common/supplemental/likelySubtags.xml index 76e215255fdc..a73b8a8c95bf 100644 --- a/make/data/cldr/common/supplemental/likelySubtags.xml +++ b/make/data/cldr/common/supplemental/likelySubtags.xml @@ -1,7 +1,7 @@ - + @@ -1343,7 +1343,7 @@ not be patched by hand, as any changes made in that fashion may be lost. - + diff --git a/make/data/cldr/common/supplemental/metaZones.xml b/make/data/cldr/common/supplemental/metaZones.xml index 710934fef81d..610921a8f6d2 100644 --- a/make/data/cldr/common/supplemental/metaZones.xml +++ b/make/data/cldr/common/supplemental/metaZones.xml @@ -735,7 +735,7 @@ For terms of use, see http://www.unicode.org/copyright.html - + diff --git a/make/data/cldr/common/supplemental/supplementalData.xml b/make/data/cldr/common/supplemental/supplementalData.xml index 25684d36c6e1..cbfe2c5e8751 100644 --- a/make/data/cldr/common/supplemental/supplementalData.xml +++ b/make/data/cldr/common/supplemental/supplementalData.xml @@ -1,7 +1,7 @@ @@ -57,7 +57,7 @@ For terms of use, see https://www.unicode.org/copyright.html - + @@ -3147,7 +3147,7 @@ XXX Code for transations where no currency is involved - + diff --git a/make/jdk/src/classes/build/tools/cldrconverter/CLDRConverter.java b/make/jdk/src/classes/build/tools/cldrconverter/CLDRConverter.java index 18ce0c334fb8..7b198f976793 100644 --- a/make/jdk/src/classes/build/tools/cldrconverter/CLDRConverter.java +++ b/make/jdk/src/classes/build/tools/cldrconverter/CLDRConverter.java @@ -815,6 +815,13 @@ private static Map extractZoneNames(Map map, Str data = map.get(TIMEZONE_ID_PREFIX + tzLink); } + String meta = handlerMetaZones.get(tzKey); + if (meta == null && tzLink != null) { + // Check for tzLink + meta = handlerMetaZones.get(tzLink); + } + String metaKey = meta != null ? METAZONE_ID_PREFIX + meta : null; + if (data instanceof String[] tznames) { // Hack for UTC. UTC is an alias to Etc/UTC in CLDR if (tzid.equals("Etc/UTC") && !map.containsKey(TIMEZONE_ID_PREFIX + "UTC")) { @@ -826,24 +833,14 @@ private static Map extractZoneNames(Map map, Str tznames = Arrays.copyOf(tznames, tznames.length); fillTZDBShortNames(tzKey, tznames); names.put(tzid, tznames); + if (meta != null && map.get(metaKey) instanceof String[] metaNames) { + recordMetazone(names, meta, tzKey, metaNames); + } } } else { - String meta = handlerMetaZones.get(tzKey); - if (meta == null && tzLink != null) { - // Check for tzLink - meta = handlerMetaZones.get(tzLink); - } if (meta != null) { - String metaKey = METAZONE_ID_PREFIX + meta; - data = map.get(metaKey); - if (data instanceof String[] tznames) { - if (isDefaultZone(meta, tzKey)) { - // Record the metazone names only from the default - // (001) zone, with short names filled from TZDB - tznames = Arrays.copyOf(tznames, tznames.length); - fillTZDBShortNames(tzKey, tznames); - names.put(metaKey, tznames); - } + if (map.get(metaKey) instanceof String[] metaNames) { + recordMetazone(names, meta, tzKey, metaNames); names.put(tzid, meta); if (tzLink != null && availableIds.contains(tzLink)) { names.put(tzLink, meta); @@ -1508,11 +1505,18 @@ private static void fillTZDBShortNames(String tzid, String[] names) { } } - private static boolean isDefaultZone(String meta, String tzid) { + private static void recordMetazone(Map names, String meta, String tzid, String[] tznames) { String zone001 = handlerMetaZones.zidMap().get(meta); var tzLink = getTZDBLink(tzid); - return canonicalTZMap.getOrDefault(tzid, tzid).equals(zone001) || - tzLink != null && canonicalTZMap.getOrDefault(tzLink, tzLink).equals(zone001); + + // Record the metazone names only from the default + // (001) zone, with short names filled from TZDB + if (canonicalTZMap.getOrDefault(tzid, tzid).equals(zone001) || + tzLink != null && canonicalTZMap.getOrDefault(tzLink, tzLink).equals(zone001)) { + tznames = Arrays.copyOf(tznames, tznames.length); + fillTZDBShortNames(tzid, tznames); + names.put(METAZONE_ID_PREFIX + meta, tznames); + } } private static String getTZDBLink(String tzid) { diff --git a/src/hotspot/cpu/aarch64/aarch64.ad b/src/hotspot/cpu/aarch64/aarch64.ad index 4fbbfc9d1dca..7487de2d5775 100644 --- a/src/hotspot/cpu/aarch64/aarch64.ad +++ b/src/hotspot/cpu/aarch64/aarch64.ad @@ -3389,12 +3389,13 @@ encode %{ assert(rtype == relocInfo::none || rtype == relocInfo::external_word_type, "unexpected reloc type"); // load fake address constants using a normal move if (! __ is_valid_AArch64_address(con) || - con < (address)(uintptr_t)os::vm_page_size()) { + con < (address)(uintptr_t)os::vm_page_size() || + rtype == relocInfo::none) { __ mov(dst_reg, con); } else { - // no reloc so just use adrp and add + // use shorter adrp/add sequence for external_word relocation uint64_t offset; - __ adrp(dst_reg, con, offset); + __ adrp(dst_reg, Address(con, rtype), offset); __ add(dst_reg, dst_reg, offset); } } diff --git a/src/hotspot/cpu/aarch64/gc/shenandoah/c1/shenandoahBarrierSetC1_aarch64.cpp b/src/hotspot/cpu/aarch64/gc/shenandoah/c1/shenandoahBarrierSetC1_aarch64.cpp deleted file mode 100644 index e31a58243b5a..000000000000 --- a/src/hotspot/cpu/aarch64/gc/shenandoah/c1/shenandoahBarrierSetC1_aarch64.cpp +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (c) 2018, 2021, Red Hat, Inc. All rights reserved. - * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - * - */ - -#include "c1/c1_LIRAssembler.hpp" -#include "c1/c1_MacroAssembler.hpp" -#include "compiler/compilerDefinitions.inline.hpp" -#include "gc/shared/gc_globals.hpp" -#include "gc/shenandoah/c1/shenandoahBarrierSetC1.hpp" -#include "gc/shenandoah/shenandoahBarrierSet.hpp" -#include "gc/shenandoah/shenandoahBarrierSetAssembler.hpp" - -#define __ masm->masm()-> - -void LIR_OpShenandoahCompareAndSwap::emit_code(LIR_Assembler* masm) { - Register addr = _addr->as_register_lo(); - Register newval = _new_value->as_register(); - Register cmpval = _cmp_value->as_register(); - Register tmp1 = _tmp1->as_register(); - Register tmp2 = _tmp2->as_register(); - Register result = result_opr()->as_register(); - - if (UseCompressedOops) { - __ encode_heap_oop(tmp1, cmpval); - cmpval = tmp1; - __ encode_heap_oop(tmp2, newval); - newval = tmp2; - } - - ShenandoahBarrierSet::assembler()->cmpxchg_oop(masm->masm(), addr, cmpval, newval, /*acquire*/ true, /*release*/ true, /*is_cae*/ false, result); - - // The membar here is necessary to prevent reordering between the - // release store in the CAS above and a subsequent volatile load. - // See also: LIR_Assembler::casw, LIR_Assembler::casl. - __ membar(__ AnyAny); -} - -#undef __ - -#ifdef ASSERT -#define __ gen->lir(__FILE__, __LINE__)-> -#else -#define __ gen->lir()-> -#endif - -LIR_Opr ShenandoahBarrierSetC1::atomic_cmpxchg_at_resolved(LIRAccess& access, LIRItem& cmp_value, LIRItem& new_value) { - BasicType bt = access.type(); - if (access.is_oop()) { - LIRGenerator *gen = access.gen(); - if (ShenandoahSATBBarrier) { - pre_barrier(gen, access.access_emit_info(), access.decorators(), access.resolved_addr(), - LIR_OprFact::illegalOpr /* pre_val */); - } - if (ShenandoahCASBarrier) { - cmp_value.load_item(); - new_value.load_item(); - - LIR_Opr t1 = gen->new_register(T_OBJECT); - LIR_Opr t2 = gen->new_register(T_OBJECT); - LIR_Opr addr = access.resolved_addr()->as_address_ptr()->base(); - LIR_Opr result = gen->new_register(T_INT); - - __ append(new LIR_OpShenandoahCompareAndSwap(addr, cmp_value.result(), new_value.result(), t1, t2, result)); - - if (ShenandoahCardBarrier) { - post_barrier(access, access.resolved_addr(), new_value.result()); - } - return result; - } - } - return BarrierSetC1::atomic_cmpxchg_at_resolved(access, cmp_value, new_value); -} - -LIR_Opr ShenandoahBarrierSetC1::atomic_xchg_at_resolved(LIRAccess& access, LIRItem& value) { - LIRGenerator* gen = access.gen(); - BasicType type = access.type(); - - LIR_Opr result = gen->new_register(type); - value.load_item(); - LIR_Opr value_opr = value.result(); - - assert(type == T_INT || is_reference_type(type) LP64_ONLY( || type == T_LONG ), "unexpected type"); - LIR_Opr tmp = gen->new_register(T_INT); - __ xchg(access.resolved_addr(), value_opr, result, tmp); - - if (access.is_oop()) { - result = load_reference_barrier(access.gen(), result, LIR_OprFact::addressConst(0), access.decorators()); - LIR_Opr tmp = gen->new_register(type); - __ move(result, tmp); - result = tmp; - if (ShenandoahSATBBarrier) { - pre_barrier(access.gen(), access.access_emit_info(), access.decorators(), LIR_OprFact::illegalOpr, - result /* pre_val */); - } - if (ShenandoahCardBarrier) { - post_barrier(access, access.resolved_addr(), result); - } - } - - return result; -} diff --git a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp index fdb016acf31a..8f5eb7027143 100644 --- a/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp +++ b/src/hotspot/cpu/aarch64/macroAssembler_aarch64.cpp @@ -6735,13 +6735,14 @@ void MacroAssembler::java_round_float(Register dst, FloatRegister src, // by the call to JavaThread::aarch64_get_thread_helper() or, indeed, // the call setup code. // -// On Linux, aarch64_get_thread_helper() clobbers only r0, r1, and flags. +// On Linux and Windows, aarch64_get_thread_helper() is implemented in +// assembly and clobbers only r0, r1, and flags. // On other systems, the helper is a usual C function. // void MacroAssembler::get_thread(Register dst) { RegSet saved_regs = - LINUX_ONLY(RegSet::range(r0, r1) + lr - dst) - NOT_LINUX (RegSet::range(r0, r17) + lr - dst); + BSD_ONLY(RegSet::range(r0, r17) + lr - dst) + NOT_BSD (RegSet::range(r0, r1) + lr - dst); protect_return_address(); push(saved_regs, sp); diff --git a/src/hotspot/cpu/arm/arm_32.ad b/src/hotspot/cpu/arm/arm_32.ad index 9438e8da8b59..2af7e253a1a8 100644 --- a/src/hotspot/cpu/arm/arm_32.ad +++ b/src/hotspot/cpu/arm/arm_32.ad @@ -1,5 +1,5 @@ // -// Copyright (c) 2008, 2025, Oracle and/or its affiliates. All rights reserved. +// Copyright (c) 2008, 2026, Oracle and/or its affiliates. All rights reserved. // DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. // // This code is free software; you can redistribute it and/or modify it @@ -501,7 +501,7 @@ operand immIRotn() %{ %} operand immPRot() %{ - predicate(n->get_ptr() == 0 || (AsmOperand::is_rotated_imm(n->get_ptr()) && ((ConPNode*)n)->type()->reloc() == relocInfo::none)); + predicate(n->get_ptr() == 0 || (AsmOperand::is_rotated_imm(n->get_ptr()) && ((ConPNode*)n)->type()->is_ptr()->reloc() == relocInfo::none)); match(ConP); diff --git a/src/hotspot/cpu/ppc/assembler_ppc.hpp b/src/hotspot/cpu/ppc/assembler_ppc.hpp index 378e01fc1ccd..f62c93e466cf 100644 --- a/src/hotspot/cpu/ppc/assembler_ppc.hpp +++ b/src/hotspot/cpu/ppc/assembler_ppc.hpp @@ -461,10 +461,6 @@ class Assembler : public AbstractAssembler { FRIN_OPCODE = (63u << OPCODE_SHIFT | 392u << 1), FRIP_OPCODE = (63u << OPCODE_SHIFT | 456u << 1), FRIM_OPCODE = (63u << OPCODE_SHIFT | 488u << 1), - // These are special Power6 opcodes, reused for "lfdepx" and "stfdepx" - // on Power7. Do not use. - // MFFGPR_OPCODE = (31u << OPCODE_SHIFT | 607u << 1), - // MFTGPR_OPCODE = (31u << OPCODE_SHIFT | 735u << 1), CMPB_OPCODE = (31u << OPCODE_SHIFT | 508 << 1), POPCNTB_OPCODE = (31u << OPCODE_SHIFT | 122 << 1), POPCNTW_OPCODE = (31u << OPCODE_SHIFT | 378 << 1), @@ -518,7 +514,6 @@ class Assembler : public AbstractAssembler { FSQRT_OPCODE = (63u << OPCODE_SHIFT | 22u << 1), // A-FORM FSQRTS_OPCODE = (59u << OPCODE_SHIFT | 22u << 1), // A-FORM - // Vector instruction support for >= Power6 // Vector Storage Access LVEBX_OPCODE = (31u << OPCODE_SHIFT | 7u << 1), LVEHX_OPCODE = (31u << OPCODE_SHIFT | 39u << 1), @@ -1236,7 +1231,7 @@ class Assembler : public AbstractAssembler { static int u( int x) { return opp_u_field(x, 19, 16); } static int ui( int x) { return opp_u_field(x, 31, 16); } - // Support vector instructions for >= Power6. + // Support vector instructions. static int vra( int x) { return opp_u_field(x, 15, 11); } static int vrb( int x) { return opp_u_field(x, 20, 16); } static int vrc( int x) { return opp_u_field(x, 25, 21); } @@ -2036,7 +2031,7 @@ class Assembler : public AbstractAssembler { inline void stqcx_( Register s, Register a, Register b); // Instructions for adjusting thread priority for simultaneous - // multithreading (SMT) on Power5. + // multithreading (SMT). private: inline void smt_prio_very_low(); inline void smt_prio_medium_high(); @@ -2204,7 +2199,7 @@ class Assembler : public AbstractAssembler { inline void fsqrt( FloatRegister d, FloatRegister b); inline void fsqrts(FloatRegister d, FloatRegister b); - // Vector instructions for >= Power6. + // Vector instructions. inline void lvebx( VectorRegister d, Register s1, Register s2); inline void lvehx( VectorRegister d, Register s1, Register s2); inline void lvewx( VectorRegister d, Register s1, Register s2); diff --git a/src/hotspot/cpu/ppc/assembler_ppc.inline.hpp b/src/hotspot/cpu/ppc/assembler_ppc.inline.hpp index d349bbc6f872..22b9e268dcdf 100644 --- a/src/hotspot/cpu/ppc/assembler_ppc.inline.hpp +++ b/src/hotspot/cpu/ppc/assembler_ppc.inline.hpp @@ -642,7 +642,6 @@ inline void Assembler::crorc( ConditionRegister crdst, Condition cdst, Condition crorc(dst_bit, src_bit, dst_bit); } -// Conditional move (>= Power7) inline void Assembler::isel(Register d, ConditionRegister cr, Condition cc, bool inv, Register a, Register b) { if (b == noreg) { b = d; // Can be omitted if old value should be kept in "else" case. @@ -689,7 +688,7 @@ inline void Assembler::elemental_membar(int e) { assert(0 < e && e < 16, "invali // Wait instructions for polling. inline void Assembler::wait() { emit_int32( WAIT_OPCODE); } -inline void Assembler::waitrsv() { emit_int32( WAIT_OPCODE | 1<<(31-10)); } // WC=0b01 >=Power7 +inline void Assembler::waitrsv() { emit_int32( WAIT_OPCODE | 1<<(31-10)); } // WC=0b01 // atomics // Use ra0mem to disallow R0 as base. @@ -709,19 +708,16 @@ inline void Assembler::stwcx_(Register s, Register a, Register b) inline void Assembler::stdcx_(Register s, Register a, Register b) { emit_int32( STDCX_OPCODE | rs(s) | ra0mem(a) | rb(b) | rc(1)); } inline void Assembler::stqcx_(Register s, Register a, Register b) { emit_int32( STQCX_OPCODE | rs(s) | ra0mem(a) | rb(b) | rc(1)); } -// Instructions for adjusting thread priority -// for simultaneous multithreading (SMT) on >= POWER5. +// Instructions for adjusting thread priority for simultaneous multithreading (SMT). inline void Assembler::smt_prio_very_low() { Assembler::or_unchecked(R31, R31, R31); } inline void Assembler::smt_prio_low() { Assembler::or_unchecked(R1, R1, R1); } inline void Assembler::smt_prio_medium_low() { Assembler::or_unchecked(R6, R6, R6); } inline void Assembler::smt_prio_medium() { Assembler::or_unchecked(R2, R2, R2); } inline void Assembler::smt_prio_medium_high() { Assembler::or_unchecked(R5, R5, R5); } -inline void Assembler::smt_prio_high() { Assembler::or_unchecked(R3, R3, R3); } -// >= Power7 +inline void Assembler::smt_prio_high() { Assembler::or_unchecked(R3, R3, R3); } // Restricted to supervisor state since Power9. inline void Assembler::smt_yield() { Assembler::or_unchecked(R27, R27, R27); } // never actually implemented -inline void Assembler::smt_mdoio() { Assembler::or_unchecked(R29, R29, R29); } // never actually implemetned +inline void Assembler::smt_mdoio() { Assembler::or_unchecked(R29, R29, R29); } // never actually implemented inline void Assembler::smt_mdoom() { Assembler::or_unchecked(R30, R30, R30); } // never actually implemented -// Power8 inline void Assembler::smt_miso() { Assembler::or_unchecked(R26, R26, R26); } // never actually implemented inline void Assembler::twi_0(Register a) { twi_unchecked(0, a, 0);} @@ -766,10 +762,6 @@ inline void Assembler::frin( FloatRegister d, FloatRegister b) { emit_int32( FRI inline void Assembler::frip( FloatRegister d, FloatRegister b) { emit_int32( FRIP_OPCODE | frt(d) | frb(b) | rc(0)); } inline void Assembler::frim( FloatRegister d, FloatRegister b) { emit_int32( FRIM_OPCODE | frt(d) | frb(b) | rc(0)); } -// These are special Power6 opcodes, reused for "lfdepx" and "stfdepx" -// on Power7. Do not use. -//inline void Assembler::mffgpr( FloatRegister d, Register b) { emit_int32( MFFGPR_OPCODE | frt(d) | rb(b) | rc(0)); } -//inline void Assembler::mftgpr( Register d, FloatRegister b) { emit_int32( MFTGPR_OPCODE | rt(d) | frb(b) | rc(0)); } // add cmpb and popcntb to detect ppc power version. inline void Assembler::cmpb( Register a, Register s, Register b) { emit_int32( CMPB_OPCODE | rta(a) | rs(s) | rb(b) | rc(0)); } inline void Assembler::popcntb(Register a, Register s) { emit_int32( POPCNTB_OPCODE | rta(a) | rs(s)); }; @@ -837,7 +829,7 @@ inline void Assembler::fcmpu( ConditionRegister crx, FloatRegister a, FloatRegis inline void Assembler::fsqrt( FloatRegister d, FloatRegister b) { emit_int32( FSQRT_OPCODE | frt(d) | frb(b) | rc(0)); } inline void Assembler::fsqrts(FloatRegister d, FloatRegister b) { emit_int32( FSQRTS_OPCODE | frt(d) | frb(b) | rc(0)); } -// Vector instructions for >= Power6. +// Vector instructions. inline void Assembler::lvebx( VectorRegister d, Register s1, Register s2) { emit_int32( LVEBX_OPCODE | vrt(d) | ra0mem(s1) | rb(s2)); } inline void Assembler::lvehx( VectorRegister d, Register s1, Register s2) { emit_int32( LVEHX_OPCODE | vrt(d) | ra0mem(s1) | rb(s2)); } inline void Assembler::lvewx( VectorRegister d, Register s1, Register s2) { emit_int32( LVEWX_OPCODE | vrt(d) | ra0mem(s1) | rb(s2)); } diff --git a/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp b/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp index 0b48653ae64c..777b41577bed 100644 --- a/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp +++ b/src/hotspot/cpu/ppc/c1_LIRAssembler_ppc.cpp @@ -210,7 +210,7 @@ int LIR_Assembler::emit_unwind_handler() { _masm->block_comment("Unwind handler"); int offset = code_offset(); - bool preserve_exception = method()->is_synchronized() || compilation()->env()->dtrace_method_probes(); + bool preserve_exception = method()->is_synchronized(); const Register Rexception = R3 /*LIRGenerator::exceptionOopOpr()*/, Rexception_save = R31; // Fetch the exception from TLS and clear out exception related thread state. @@ -232,10 +232,6 @@ int LIR_Assembler::emit_unwind_handler() { __ bind(*stub->continuation()); } - if (compilation()->env()->dtrace_method_probes()) { - Unimplemented(); - } - // Dispatch to the unwind logic. address unwind_stub = Runtime1::entry_for(StubId::c1_unwind_exception_id); //__ load_const_optimized(R0, unwind_stub); diff --git a/src/hotspot/cpu/ppc/c1_MacroAssembler_ppc.cpp b/src/hotspot/cpu/ppc/c1_MacroAssembler_ppc.cpp index 4d7af0e4a711..359c7cf22ad6 100644 --- a/src/hotspot/cpu/ppc/c1_MacroAssembler_ppc.cpp +++ b/src/hotspot/cpu/ppc/c1_MacroAssembler_ppc.cpp @@ -232,13 +232,6 @@ void C1_MacroAssembler::initialize_object( initialize_body(obj, t1, t2, con_size_in_bytes, hdr_size_in_bytes); } - if (CURRENT_ENV->dtrace_alloc_probes()) { - Unimplemented(); -// assert(obj == O0, "must be"); -// call(CAST_FROM_FN_PTR(address, Runtime1::entry_for(StubId::c1_dtrace_object_alloc_id)), -// relocInfo::runtime_call_type); - } - verify_oop(obj, FILE_AND_LINE); } @@ -308,13 +301,6 @@ void C1_MacroAssembler::allocate_array( initialize_body(base, index); } - if (CURRENT_ENV->dtrace_alloc_probes()) { - Unimplemented(); - //assert(obj == O0, "must be"); - //call(CAST_FROM_FN_PTR(address, Runtime1::entry_for(StubId::c1_dtrace_object_alloc_id)), - // relocInfo::runtime_call_type); - } - verify_oop(obj, FILE_AND_LINE); } diff --git a/src/hotspot/cpu/ppc/gc/shenandoah/c1/shenandoahBarrierSetC1_ppc.cpp b/src/hotspot/cpu/ppc/gc/shenandoah/c1/shenandoahBarrierSetC1_ppc.cpp deleted file mode 100644 index 5b24259103f5..000000000000 --- a/src/hotspot/cpu/ppc/gc/shenandoah/c1/shenandoahBarrierSetC1_ppc.cpp +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright (c) 2018, 2023, Red Hat, Inc. All rights reserved. - * Copyright (c) 2012, 2023 SAP SE. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - * - */ - -#include "asm/macroAssembler.inline.hpp" -#include "c1/c1_LIRAssembler.hpp" -#include "c1/c1_MacroAssembler.hpp" -#include "gc/shenandoah/c1/shenandoahBarrierSetC1.hpp" -#include "gc/shenandoah/shenandoahBarrierSet.hpp" -#include "gc/shenandoah/shenandoahBarrierSetAssembler.hpp" - -#define __ masm->masm()-> - -void LIR_OpShenandoahCompareAndSwap::emit_code(LIR_Assembler *masm) { - __ block_comment("LIR_OpShenandoahCompareAndSwap (shenandaohgc) {"); - - Register addr = _addr->as_register_lo(); - Register new_val = _new_value->as_register(); - Register cmp_val = _cmp_value->as_register(); - Register tmp1 = _tmp1->as_register(); - Register tmp2 = _tmp2->as_register(); - Register result = result_opr()->as_register(); - - if (UseCompressedOops) { - __ encode_heap_oop(cmp_val, cmp_val); - __ encode_heap_oop(new_val, new_val); - } - - // There might be a volatile load before this Unsafe CAS. - if (support_IRIW_for_not_multiple_copy_atomic_cpu) { - __ sync(); - } else { - __ lwsync(); - } - - ShenandoahBarrierSet::assembler()->cmpxchg_oop(masm->masm(), addr, cmp_val, new_val, tmp1, tmp2, - false, result); - - if (UseCompressedOops) { - __ decode_heap_oop(cmp_val); - __ decode_heap_oop(new_val); - } - - if (support_IRIW_for_not_multiple_copy_atomic_cpu) { - __ isync(); - } else { - __ sync(); - } - - __ block_comment("} LIR_OpShenandoahCompareAndSwap (shenandaohgc)"); -} - -#undef __ - -#ifdef ASSERT -#define __ gen->lir(__FILE__, __LINE__)-> -#else -#define __ gen->lir()-> -#endif - -LIR_Opr ShenandoahBarrierSetC1::atomic_cmpxchg_at_resolved(LIRAccess &access, LIRItem &cmp_value, LIRItem &new_value) { - BasicType bt = access.type(); - - if (access.is_oop()) { - LIRGenerator* gen = access.gen(); - - if (ShenandoahSATBBarrier) { - pre_barrier(gen, access.access_emit_info(), access.decorators(), access.resolved_addr(), - LIR_OprFact::illegalOpr); - } - - if (ShenandoahCASBarrier) { - cmp_value.load_item(); - new_value.load_item(); - - LIR_Opr t1 = gen->new_register(T_OBJECT); - LIR_Opr t2 = gen->new_register(T_OBJECT); - LIR_Opr addr = access.resolved_addr()->as_address_ptr()->base(); - LIR_Opr result = gen->new_register(T_INT); - - __ append(new LIR_OpShenandoahCompareAndSwap(addr, cmp_value.result(), new_value.result(), t1, t2, result)); - - if (ShenandoahCardBarrier) { - post_barrier(access, access.resolved_addr(), new_value.result()); - } - - return result; - } - } - - return BarrierSetC1::atomic_cmpxchg_at_resolved(access, cmp_value, new_value); -} - -LIR_Opr ShenandoahBarrierSetC1::atomic_xchg_at_resolved(LIRAccess &access, LIRItem &value) { - LIRGenerator* gen = access.gen(); - BasicType type = access.type(); - - LIR_Opr result = gen->new_register(type); - value.load_item(); - LIR_Opr value_opr = value.result(); - - assert(type == T_INT || is_reference_type(type) LP64_ONLY( || type == T_LONG ), "unexpected type"); - LIR_Opr tmp_xchg = gen->new_register(T_INT); - __ xchg(access.resolved_addr(), value_opr, result, tmp_xchg); - - if (access.is_oop()) { - result = load_reference_barrier_impl(access.gen(), result, LIR_OprFact::addressConst(0), - access.decorators()); - - LIR_Opr tmp_barrier = gen->new_register(type); - __ move(result, tmp_barrier); - result = tmp_barrier; - - if (ShenandoahSATBBarrier) { - pre_barrier(access.gen(), access.access_emit_info(), access.decorators(), LIR_OprFact::illegalOpr, result); - } - - if (ShenandoahCardBarrier) { - post_barrier(access, access.resolved_addr(), result); - } - } - - return result; -} diff --git a/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.cpp b/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.cpp index 43fd54eb78a4..82f3bb38012d 100644 --- a/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.cpp +++ b/src/hotspot/cpu/ppc/gc/shenandoah/shenandoahBarrierSetAssembler_ppc.cpp @@ -893,13 +893,11 @@ void ShenandoahBarrierSetAssembler::gen_load_reference_barrier_stub(LIR_Assemble Register tmp2 = stub->tmp2()->as_register(); assert_different_registers(addr, res, tmp1, tmp2); -#ifdef ASSERT - // Ensure that 'res' is 'R3_ARG1' and contains the same value as 'obj' to reduce the number of required - // copy instructions. assert(R3_RET == res, "res must be r3"); - __ cmpd(CR0, res, obj); - __ asm_assert_eq("result register must contain the reference stored in obj"); -#endif + + if (res != obj) { + __ mr(res, obj); + } DecoratorSet decorators = stub->decorators(); @@ -1034,7 +1032,7 @@ void ShenandoahBarrierSetAssembler::generate_c1_load_reference_barrier_runtime_s __ save_volatile_gprs(R1_SP, -nbytes_save, true, false); // Load arguments from stack. - // No load required, as assured by assertions in 'ShenandoahBarrierSetAssembler::gen_load_reference_barrier_stub'. + // No load required, as caller has already loaded obj into R3. Register R3_obj = R3_ARG1; Register R4_load_addr = R4_ARG2; __ ld(R4_load_addr, -8, R1_SP); diff --git a/src/hotspot/cpu/ppc/matcher_ppc.hpp b/src/hotspot/cpu/ppc/matcher_ppc.hpp index 88a189d670eb..a3ab382564c2 100644 --- a/src/hotspot/cpu/ppc/matcher_ppc.hpp +++ b/src/hotspot/cpu/ppc/matcher_ppc.hpp @@ -54,7 +54,7 @@ // PowerPC requires masked shift counts. static const bool need_masked_shift_count = true; - // Power6 requires postalloc expand (see block.cpp for description of postalloc expand). + // PPC64 requires postalloc expand (see block.cpp for description of postalloc expand). static const bool require_postalloc_expand = true; // No support for generic vector operands. @@ -157,7 +157,7 @@ // true means we have fast l2f conversion static constexpr bool convL2FSupported(void) { - // fcfids can do the conversion (>= Power7). + // fcfids can do the conversion. // fcfid + frsp showed rounding problem when result should be 0x3f800001. return true; } diff --git a/src/hotspot/cpu/ppc/templateTable_ppc_64.cpp b/src/hotspot/cpu/ppc/templateTable_ppc_64.cpp index 37f780535b4d..252425fb1045 100644 --- a/src/hotspot/cpu/ppc/templateTable_ppc_64.cpp +++ b/src/hotspot/cpu/ppc/templateTable_ppc_64.cpp @@ -3852,13 +3852,6 @@ void TemplateTable::_new() { __ store_klass(RallocatedObject, RinstanceKlass, Rscratch); } - // Check and trigger dtrace event. - if (DTraceAllocProbes) { - __ push(atos); - __ call_VM_leaf(CAST_FROM_FN_PTR(address, static_cast(SharedRuntime::dtrace_object_alloc))); - __ pop(atos); - } - __ b(Ldone); } diff --git a/src/hotspot/cpu/riscv/gc/shared/barrierSetAssembler_riscv.cpp b/src/hotspot/cpu/riscv/gc/shared/barrierSetAssembler_riscv.cpp index 5097d7ec58d2..fd78b429ee4f 100644 --- a/src/hotspot/cpu/riscv/gc/shared/barrierSetAssembler_riscv.cpp +++ b/src/hotspot/cpu/riscv/gc/shared/barrierSetAssembler_riscv.cpp @@ -228,7 +228,7 @@ void BarrierSetAssembler::nmethod_entry_barrier(MacroAssembler* masm, Label* slo BarrierSetNMethod* bs_nm = BarrierSet::barrier_set()->barrier_set_nmethod(); Assembler::IncompressibleScope scope(masm); // Fixed length: see entry_barrier_offset() - Label local_guard; + Label local_guard, skip_barrier; NMethodPatchingType patching_type = nmethod_patching_type(); if (slow_path == nullptr) { @@ -290,24 +290,26 @@ void BarrierSetAssembler::nmethod_entry_barrier(MacroAssembler* masm, Label* slo ShouldNotReachHere(); } + Label& barrier_target = slow_path == nullptr ? skip_barrier : *slow_path; if (slow_path == nullptr) { - Label skip_barrier; - __ beq(t0, t1, skip_barrier); + __ beq(t0, t1, barrier_target, true /* is_far */); + } else { + __ bne(t0, t1, barrier_target, true /* is_far */); + } + if (slow_path == nullptr) { __ rt_call(StubRoutines::method_entry_barrier()); - __ j(skip_barrier); __ bind(local_guard); MacroAssembler::assert_alignment(__ pc()); __ emit_int32(0); // nmethod guard value. Skipped over in common case. - __ bind(skip_barrier); } else { - __ beq(t0, t1, *continuation); - __ j(*slow_path); __ bind(*continuation); } + + __ bind(skip_barrier); } void BarrierSetAssembler::c2i_entry_barrier(MacroAssembler* masm) { diff --git a/src/hotspot/cpu/riscv/gc/shared/barrierSetNMethod_riscv.cpp b/src/hotspot/cpu/riscv/gc/shared/barrierSetNMethod_riscv.cpp index 5003b9584a31..9b318dbe5799 100644 --- a/src/hotspot/cpu/riscv/gc/shared/barrierSetNMethod_riscv.cpp +++ b/src/hotspot/cpu/riscv/gc/shared/barrierSetNMethod_riscv.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. * Copyright (c) 2020, 2022, Huawei Technologies Co., Ltd. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -41,17 +41,16 @@ static int slow_path_size(nmethod* nm) { // The slow path code is out of line with C2. - // Leave a jal to the stub in the fast path. - return nm->is_compiled_by_c2() ? 1 : 8; + return nm->is_compiled_by_c2() ? 0 : 4; } static int entry_barrier_offset(nmethod* nm) { BarrierSetAssembler* bs_asm = BarrierSet::barrier_set()->barrier_set_assembler(); switch (bs_asm->nmethod_patching_type()) { case NMethodPatchingType::stw_instruction_and_data_patch: - return -4 * (4 + slow_path_size(nm)); + return -4 * (5 + slow_path_size(nm)); case NMethodPatchingType::conc_instruction_and_data_patch: - return -4 * (15 + slow_path_size(nm)); + return -4 * ((UseZtso ? 14 : 16) + slow_path_size(nm)); } ShouldNotReachHere(); return 0; @@ -103,6 +102,10 @@ class NativeNMethodBarrier { } _guard_addr = reinterpret_cast(instruction_address() + local_guard_offset(nm)); } + + // Perform the checking as verification. + err_msg msg("%s", ""); + assert(check_barrier(msg), "%s", msg.buffer()); } int get_value() { @@ -128,10 +131,6 @@ class NativeNMethodBarrier { } bool check_barrier(err_msg& msg) const; - void verify() const { - err_msg msg("%s", ""); - assert(check_barrier(msg), "%s", msg.buffer()); - } }; // Store the instruction bitmask, bits and name for checking the barrier. @@ -142,8 +141,8 @@ struct CheckInsn { }; static const struct CheckInsn barrierInsn[] = { - { 0x00000fff, 0x00000297, "auipc t0, 0 "}, - { 0x000fffff, 0x0002e283, "lwu t0, guard_offset(t0) "}, + { 0x00000fff, 0x00000297, "auipc t0, 0 " }, + { 0x000fffff, 0x0002e283, "lwu t0, guard_offset(t0)" }, /* ...... */ /* ...... */ /* guard: */ @@ -155,10 +154,11 @@ static const struct CheckInsn barrierInsn[] = { // register numbers and immediate values in the encoding. bool NativeNMethodBarrier::check_barrier(err_msg& msg) const { address addr = instruction_address(); - for(unsigned int i = 0; i < sizeof(barrierInsn)/sizeof(struct CheckInsn); i++ ) { + for (unsigned int i = 0; i < sizeof(barrierInsn) / sizeof(struct CheckInsn); i++) { uint32_t inst = Assembler::ld_instr(addr); if ((inst & barrierInsn[i].mask) != barrierInsn[i].bits) { - msg.print("Addr: " INTPTR_FORMAT " Code: 0x%x not an %s instruction", p2i(addr), inst, barrierInsn[i].name); + msg.print("Nmethod entry barrier did not start with auipc & lwu as expected. " + "Addr: " INTPTR_FORMAT " Code: 0x%x not an %s instruction.", p2i(addr), inst, barrierInsn[i].name); return false; } addr += 4; diff --git a/src/hotspot/cpu/riscv/gc/shenandoah/c1/shenandoahBarrierSetC1_riscv.cpp b/src/hotspot/cpu/riscv/gc/shenandoah/c1/shenandoahBarrierSetC1_riscv.cpp deleted file mode 100644 index 11c4e5dc81b6..000000000000 --- a/src/hotspot/cpu/riscv/gc/shenandoah/c1/shenandoahBarrierSetC1_riscv.cpp +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (c) 2018, 2019, Red Hat, Inc. All rights reserved. - * Copyright (c) 2020, 2021, Huawei Technologies Co., Ltd. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - * - */ - -#include "c1/c1_LIRAssembler.hpp" -#include "c1/c1_MacroAssembler.hpp" -#include "gc/shared/gc_globals.hpp" -#include "gc/shenandoah/c1/shenandoahBarrierSetC1.hpp" -#include "gc/shenandoah/shenandoahBarrierSet.hpp" -#include "gc/shenandoah/shenandoahBarrierSetAssembler.hpp" - -#define __ masm->masm()-> - -void LIR_OpShenandoahCompareAndSwap::emit_code(LIR_Assembler* masm) { - Register addr = _addr->as_register_lo(); - Register newval = _new_value->as_register(); - Register cmpval = _cmp_value->as_register(); - Register tmp1 = _tmp1->as_register(); - Register tmp2 = _tmp2->as_register(); - Register result = result_opr()->as_register(); - - if (UseCompressedOops) { - __ encode_heap_oop(tmp1, cmpval); - cmpval = tmp1; - __ encode_heap_oop(tmp2, newval); - newval = tmp2; - } - - ShenandoahBarrierSet::assembler()->cmpxchg_oop(masm->masm(), addr, cmpval, newval, /* acquire */ Assembler::aq, - /* release */ Assembler::rl, /* is_cae */ false, result); -} - -#undef __ - -#ifdef ASSERT -#define __ gen->lir(__FILE__, __LINE__)-> -#else -#define __ gen->lir()-> -#endif - -LIR_Opr ShenandoahBarrierSetC1::atomic_cmpxchg_at_resolved(LIRAccess& access, LIRItem& cmp_value, LIRItem& new_value) { - BasicType bt = access.type(); - if (access.is_oop()) { - LIRGenerator *gen = access.gen(); - if (ShenandoahSATBBarrier) { - pre_barrier(gen, access.access_emit_info(), access.decorators(), access.resolved_addr(), - LIR_OprFact::illegalOpr /* pre_val */); - } - if (ShenandoahCASBarrier) { - cmp_value.load_item(); - new_value.load_item(); - - LIR_Opr tmp1 = gen->new_register(T_OBJECT); - LIR_Opr tmp2 = gen->new_register(T_OBJECT); - LIR_Opr addr = access.resolved_addr()->as_address_ptr()->base(); - LIR_Opr result = gen->new_register(T_INT); - - __ append(new LIR_OpShenandoahCompareAndSwap(addr, cmp_value.result(), new_value.result(), tmp1, tmp2, result)); - - if (ShenandoahCardBarrier) { - post_barrier(access, access.resolved_addr(), new_value.result()); - } - return result; - } - } - - return BarrierSetC1::atomic_cmpxchg_at_resolved(access, cmp_value, new_value); -} - -LIR_Opr ShenandoahBarrierSetC1::atomic_xchg_at_resolved(LIRAccess& access, LIRItem& value) { - LIRGenerator* gen = access.gen(); - BasicType type = access.type(); - - LIR_Opr result = gen->new_register(type); - value.load_item(); - LIR_Opr value_opr = value.result(); - - assert(type == T_INT || is_reference_type(type) LP64_ONLY( || type == T_LONG ), "unexpected type"); - LIR_Opr tmp = gen->new_register(T_INT); - __ xchg(access.resolved_addr(), value_opr, result, tmp); - - if (access.is_oop()) { - result = load_reference_barrier(access.gen(), result, LIR_OprFact::addressConst(0), access.decorators()); - LIR_Opr tmp_opr = gen->new_register(type); - __ move(result, tmp_opr); - result = tmp_opr; - if (ShenandoahSATBBarrier) { - pre_barrier(access.gen(), access.access_emit_info(), access.decorators(), LIR_OprFact::illegalOpr, - result /* pre_val */); - } - if (ShenandoahCardBarrier) { - post_barrier(access, access.resolved_addr(), result); - } - } - - return result; -} diff --git a/src/hotspot/cpu/s390/c1_LIRAssembler_s390.cpp b/src/hotspot/cpu/s390/c1_LIRAssembler_s390.cpp index e1d8d062c238..38698370faa2 100644 --- a/src/hotspot/cpu/s390/c1_LIRAssembler_s390.cpp +++ b/src/hotspot/cpu/s390/c1_LIRAssembler_s390.cpp @@ -2884,10 +2884,23 @@ void LIR_Assembler::on_spin_wait() { } void LIR_Assembler::leal(LIR_Opr addr_opr, LIR_Opr dest, LIR_PatchCode patch_code, CodeEmitInfo* info) { - assert(patch_code == lir_patch_none, "Patch code not supported"); + assert(addr_opr->is_address(), "must be an address"); + assert(dest->is_register(), "must be a register"); + LIR_Address* addr = addr_opr->as_address_ptr(); + Register reg = dest->as_pointer_register(); assert(addr->scale() == LIR_Address::times_1, "scaling unsupported"); - __ load_address(dest->as_pointer_register(), as_Address(addr)); + + if (addr->index()->is_illegal() && patch_code != lir_patch_none) { + PatchingStub* patch = new PatchingStub(_masm, PatchingStub::access_field_id); + + // TODO: Use load_const_32to64 here by extending NativeMovRegMem to support both instruction patterns. + __ load_const(Z_R0_scratch, (intptr_t)0); + __ z_agrk(reg, addr->base()->as_pointer_register(), Z_R0_scratch); + patching_epilog(patch, patch_code, addr->base()->as_register(), info); + } else { + __ load_address(reg, as_Address(addr)); + } } void LIR_Assembler::get_thread(LIR_Opr result_reg) { diff --git a/src/hotspot/cpu/s390/registerMap_s390.cpp b/src/hotspot/cpu/s390/registerMap_s390.cpp new file mode 100644 index 000000000000..85a49ff1d603 --- /dev/null +++ b/src/hotspot/cpu/s390/registerMap_s390.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2021, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2026 IBM Corp. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include "runtime/registerMap.hpp" + +address RegisterMap::pd_location(VMReg base_reg, int slot_idx) const { + if (base_reg->is_VectorRegister()) { + // Not all physical slots belonging to a VectorRegister have corresponding + // valid VMReg locations in the RegisterMap. + // (See RegisterSaver::save_live_registers.) + // However, the slots are always saved to the stack in a contiguous region + // of memory so we can calculate the address of the upper slots by + // offsetting from the base address. + assert(base_reg->is_concrete(), "must pass base reg"); + address base_location = location(base_reg, nullptr); + if (base_location != nullptr) { + intptr_t offset_in_bytes = slot_idx * VMRegImpl::stack_slot_size; + return base_location + offset_in_bytes; + } else { + return nullptr; + } + } else { + return location(base_reg->next(slot_idx), nullptr); + } +} diff --git a/src/hotspot/cpu/s390/registerMap_s390.hpp b/src/hotspot/cpu/s390/registerMap_s390.hpp index 827e3b44e046..9069fb1e31d3 100644 --- a/src/hotspot/cpu/s390/registerMap_s390.hpp +++ b/src/hotspot/cpu/s390/registerMap_s390.hpp @@ -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. * Copyright (c) 2016 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -36,9 +36,7 @@ // Since there is none, we just return null. address pd_location(VMReg reg) const {return nullptr;} - address pd_location(VMReg base_reg, int slot_idx) const { - return location(base_reg->next(slot_idx), nullptr); - } + address pd_location(VMReg base_reg, int slot_idx) const; // No PD state to clear or copy. void pd_clear() {} diff --git a/src/hotspot/cpu/s390/registerSaver_s390.hpp b/src/hotspot/cpu/s390/registerSaver_s390.hpp index a049f8b581b2..2d3c35250ba8 100644 --- a/src/hotspot/cpu/s390/registerSaver_s390.hpp +++ b/src/hotspot/cpu/s390/registerSaver_s390.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -58,7 +58,7 @@ class RegisterSaver { // During deoptimization only the result register need to be restored // all the other values have already been extracted. - static void restore_result_registers(MacroAssembler* masm); + static void restore_result_registers(MacroAssembler* masm, bool save_vectors); // Constants and data structures: diff --git a/src/hotspot/cpu/s390/sharedRuntime_s390.cpp b/src/hotspot/cpu/s390/sharedRuntime_s390.cpp index 00a830a80cd9..e5a27e66968b 100644 --- a/src/hotspot/cpu/s390/sharedRuntime_s390.cpp +++ b/src/hotspot/cpu/s390/sharedRuntime_s390.cpp @@ -402,9 +402,7 @@ OopMap* RegisterSaver::save_live_registers(MacroAssembler* masm, RegisterSet reg break; } - // Second set_callee_saved is really a waste but we'll keep things as they were for now map->set_callee_saved(VMRegImpl::stack2reg(offset >> 2), live_regs[i].vmreg); - map->set_callee_saved(VMRegImpl::stack2reg((offset + half_reg_size) >> 2), live_regs[i].vmreg->next()); } assert(first != noreg, "Should spill at least one int reg."); __ z_stmg(first, last, first_offset, Z_SP); @@ -416,12 +414,6 @@ OopMap* RegisterSaver::save_live_registers(MacroAssembler* masm, RegisterSet reg map->set_callee_saved(VMRegImpl::stack2reg(offset>>2), RegisterSaver_LiveVRegs[i].vmreg); - map->set_callee_saved(VMRegImpl::stack2reg((offset + half_reg_size ) >> 2), - RegisterSaver_LiveVRegs[i].vmreg->next()); - map->set_callee_saved(VMRegImpl::stack2reg((offset + (half_reg_size * 2)) >> 2), - RegisterSaver_LiveVRegs[i].vmreg->next(2)); - map->set_callee_saved(VMRegImpl::stack2reg((offset + (half_reg_size * 3)) >> 2), - RegisterSaver_LiveVRegs[i].vmreg->next(3)); } assert(offset == frame_size_in_bytes, "consistency check"); @@ -473,7 +465,6 @@ OopMap* RegisterSaver::generate_oop_map(MacroAssembler* masm, RegisterSet reg_se for (int i = 0; i < regstosave_num; i++) { if (live_regs[i].reg_type < RegisterSaver::excluded_reg) { map->set_callee_saved(VMRegImpl::stack2reg(offset>>2), live_regs[i].vmreg); - map->set_callee_saved(VMRegImpl::stack2reg((offset + half_reg_size)>>2), live_regs[i].vmreg->next()); } offset += reg_size; } @@ -580,10 +571,12 @@ void RegisterSaver::restore_live_registers(MacroAssembler* masm, RegisterSet reg // Pop the current frame and restore the registers that might be holding a result. -void RegisterSaver::restore_result_registers(MacroAssembler* masm) { +void RegisterSaver::restore_result_registers(MacroAssembler* masm, bool save_vectors) { const int regstosave_num = sizeof(RegisterSaver_LiveRegs) / sizeof(RegisterSaver::LiveRegType); - const int register_save_offset = live_reg_frame_size(all_registers) - live_reg_save_size(all_registers); + const int vecregstosave_num = save_vectors ? calculate_vregstosave_num() : 0; + const int vreg_save_size = vecregstosave_num * v_reg_size; + const int register_save_offset = live_reg_frame_size(all_registers, save_vectors) - (live_reg_save_size(all_registers) + vreg_save_size); // Restore all result registers (ints and floats). int offset = register_save_offset; @@ -609,7 +602,7 @@ void RegisterSaver::restore_result_registers(MacroAssembler* masm) { ShouldNotReachHere(); } } - assert(offset == live_reg_frame_size(all_registers), "consistency check"); + assert(offset == live_reg_frame_size(all_registers, save_vectors) - (save_vectors ? vreg_save_size : 0) , "consistency check"); } // --------------------------------------------------------------------------- @@ -2557,7 +2550,7 @@ void SharedRuntime::generate_deopt_blob() { // nmethod that was valid just before the nmethod was deoptimized. // save R14 into the deoptee frame. the `fetch_unroll_info' // procedure called below will read it from there. - map = RegisterSaver::save_live_registers(masm, RegisterSaver::all_registers); + map = RegisterSaver::save_live_registers(masm, RegisterSaver::all_registers, Z_R14, /* save_vectors= */ SuperwordUseVX); // note the entry point. __ load_const_optimized(exec_mode_reg, Deoptimization::Unpack_deopt); @@ -2573,7 +2566,7 @@ void SharedRuntime::generate_deopt_blob() { int reexecute_offset = __ offset() - start_off; // No need to update map as each call to save_live_registers will produce identical oopmap - (void) RegisterSaver::save_live_registers(masm, RegisterSaver::all_registers); + (void) RegisterSaver::save_live_registers(masm, RegisterSaver::all_registers, Z_R14, /* save_vectors= */ SuperwordUseVX); __ load_const_optimized(exec_mode_reg, Deoptimization::Unpack_reexecute); __ z_bru(exec_mode_initialized); @@ -2611,7 +2604,7 @@ void SharedRuntime::generate_deopt_blob() { __ z_lg(Z_R1_scratch, Address(Z_thread, JavaThread::exception_pc_offset())); // Save everything in sight. - (void) RegisterSaver::save_live_registers(masm, RegisterSaver::all_registers, Z_R1_scratch); + (void) RegisterSaver::save_live_registers(masm, RegisterSaver::all_registers, Z_R1_scratch, /* save_vectors= */ SuperwordUseVX); // Now it is safe to overwrite any register @@ -2661,7 +2654,7 @@ void SharedRuntime::generate_deopt_blob() { __ z_lgr(unroll_block_reg, Z_RET); // restore the return registers that have been saved // (among other registers) by save_live_registers(...). - RegisterSaver::restore_result_registers(masm); + RegisterSaver::restore_result_registers(masm, /* save_vectors= */ SuperwordUseVX); // reload the exec mode from the UnrollBlock (it might have changed) __ z_llgf(exec_mode_reg, Address(unroll_block_reg, Deoptimization::UnrollBlock::unpack_kind_offset())); @@ -2737,7 +2730,7 @@ void SharedRuntime::generate_deopt_blob() { // Make sure all code is generated masm->flush(); - _deopt_blob = DeoptimizationBlob::create(&buffer, oop_maps, 0, exception_offset, reexecute_offset, RegisterSaver::live_reg_frame_size(RegisterSaver::all_registers)/wordSize); + _deopt_blob = DeoptimizationBlob::create(&buffer, oop_maps, 0, exception_offset, reexecute_offset, RegisterSaver::live_reg_frame_size(RegisterSaver::all_registers, SuperwordUseVX)/wordSize); _deopt_blob->set_unpack_with_exception_in_tls_offset(exception_in_tls_offset); } diff --git a/src/hotspot/cpu/s390/vm_version_s390.cpp b/src/hotspot/cpu/s390/vm_version_s390.cpp index 7e9000991cae..c3f981f159a2 100644 --- a/src/hotspot/cpu/s390/vm_version_s390.cpp +++ b/src/hotspot/cpu/s390/vm_version_s390.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016, 2024 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -24,8 +24,9 @@ */ #include "asm/assembler.inline.hpp" -#include "compiler/disassembler.hpp" #include "code/compiledIC.hpp" +#include "compiler/compilerDefinitions.inline.hpp" +#include "compiler/disassembler.hpp" #include "jvm.h" #include "memory/resourceArea.hpp" #include "runtime/java.hpp" @@ -105,7 +106,7 @@ void VM_Version::initialize() { int model_ix = get_model_index(); if ( model_ix >= 7 ) { - if (FLAG_IS_DEFAULT(SuperwordUseVX)) { + if (FLAG_IS_DEFAULT(SuperwordUseVX) && CompilerConfig::is_c2_enabled()) { FLAG_SET_ERGO(SuperwordUseVX, true); } if (model_ix > 7 && FLAG_IS_DEFAULT(UseSFPV) && SuperwordUseVX) { diff --git a/src/hotspot/cpu/s390/vmreg_s390.hpp b/src/hotspot/cpu/s390/vmreg_s390.hpp index 517fb8e2130b..5fb5b7b40b10 100644 --- a/src/hotspot/cpu/s390/vmreg_s390.hpp +++ b/src/hotspot/cpu/s390/vmreg_s390.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2016, 2026, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016 SAP SE. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * @@ -59,7 +59,12 @@ inline VectorRegister as_VectorRegister() { inline bool is_concrete() { assert(is_reg(), "must be"); - return is_even(value()); + if (is_Register() || is_FloatRegister()) return is_even(value()); + if (is_VectorRegister()) { + int base = value() - ConcreteRegisterImpl::max_fpr; + return (base & 3) == 0; + } + return true; } #endif // CPU_S390_VMREG_S390_HPP diff --git a/src/hotspot/cpu/x86/assembler_x86.cpp b/src/hotspot/cpu/x86/assembler_x86.cpp index 0c8dd85b15d7..39e7f1734f11 100644 --- a/src/hotspot/cpu/x86/assembler_x86.cpp +++ b/src/hotspot/cpu/x86/assembler_x86.cpp @@ -15091,7 +15091,6 @@ void Assembler::cdqe() { } void Assembler::clflush(Address adr) { - assert(VM_Version::supports_clflush(), "should do"); prefix(adr, true /* is_map1 */); emit_int8((unsigned char)0xAE); emit_operand(rdi, adr, 0); diff --git a/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp b/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp index 3c4659934c60..69308bb2a7e8 100644 --- a/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp +++ b/src/hotspot/cpu/x86/c2_MacroAssembler_x86.cpp @@ -483,7 +483,7 @@ void C2_MacroAssembler::fast_unlock(Register obj, Register reg_rax, Register t, // Try to unlock. Transition lock bits 0b00 => 0b01 movptr(reg_rax, mark); - andptr(reg_rax, ~(int32_t)markWord::lock_mask); + andptr(reg_rax, ~(int32_t)markWord::lock_mask_in_place); orptr(mark, markWord::unlocked_value); lock(); cmpxchgptr(mark, Address(obj, oopDesc::mark_offset_in_bytes())); jcc(Assembler::notEqual, push_and_slow_path); diff --git a/src/hotspot/cpu/x86/gc/shenandoah/c1/shenandoahBarrierSetC1_x86.cpp b/src/hotspot/cpu/x86/gc/shenandoah/c1/shenandoahBarrierSetC1_x86.cpp deleted file mode 100644 index 66fb4cbb8c78..000000000000 --- a/src/hotspot/cpu/x86/gc/shenandoah/c1/shenandoahBarrierSetC1_x86.cpp +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright (c) 2018, 2021, Red Hat, Inc. All rights reserved. - * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - * - */ - -#include "c1/c1_LIRAssembler.hpp" -#include "c1/c1_MacroAssembler.hpp" -#include "gc/shared/gc_globals.hpp" -#include "gc/shenandoah/c1/shenandoahBarrierSetC1.hpp" -#include "gc/shenandoah/shenandoahBarrierSet.hpp" -#include "gc/shenandoah/shenandoahBarrierSetAssembler.hpp" - -#define __ masm->masm()-> - -void LIR_OpShenandoahCompareAndSwap::emit_code(LIR_Assembler* masm) { - Register addr = _addr->is_single_cpu() ? _addr->as_register() : _addr->as_register_lo(); - Register newval = _new_value->as_register(); - Register cmpval = _cmp_value->as_register(); - Register tmp1 = _tmp1->as_register(); - Register tmp2 = _tmp2->as_register(); - Register result = result_opr()->as_register(); - assert(cmpval == rax, "wrong register"); - assert(newval != noreg, "new val must be register"); - assert(cmpval != newval, "cmp and new values must be in different registers"); - assert(cmpval != addr, "cmp and addr must be in different registers"); - assert(newval != addr, "new value and addr must be in different registers"); - - if (UseCompressedOops) { - __ encode_heap_oop(cmpval); - __ mov(rscratch1, newval); - __ encode_heap_oop(rscratch1); - newval = rscratch1; - } - - ShenandoahBarrierSet::assembler()->cmpxchg_oop(masm->masm(), result, Address(addr, 0), cmpval, newval, false, tmp1, tmp2); -} - -#undef __ - -#ifdef ASSERT -#define __ gen->lir(__FILE__, __LINE__)-> -#else -#define __ gen->lir()-> -#endif - -LIR_Opr ShenandoahBarrierSetC1::atomic_cmpxchg_at_resolved(LIRAccess& access, LIRItem& cmp_value, LIRItem& new_value) { - - if (access.is_oop()) { - LIRGenerator* gen = access.gen(); - if (ShenandoahSATBBarrier) { - pre_barrier(gen, access.access_emit_info(), access.decorators(), access.resolved_addr(), - LIR_OprFact::illegalOpr /* pre_val */); - } - if (ShenandoahCASBarrier) { - cmp_value.load_item_force(FrameMap::rax_oop_opr); - new_value.load_item(); - - LIR_Opr t1 = gen->new_register(T_OBJECT); - LIR_Opr t2 = gen->new_register(T_OBJECT); - LIR_Opr addr = access.resolved_addr()->as_address_ptr()->base(); - LIR_Opr result = gen->new_register(T_INT); - - __ append(new LIR_OpShenandoahCompareAndSwap(addr, cmp_value.result(), new_value.result(), t1, t2, result)); - - if (ShenandoahCardBarrier) { - post_barrier(access, access.resolved_addr(), new_value.result()); - } - return result; - } - } - return BarrierSetC1::atomic_cmpxchg_at_resolved(access, cmp_value, new_value); -} - -LIR_Opr ShenandoahBarrierSetC1::atomic_xchg_at_resolved(LIRAccess& access, LIRItem& value) { - LIRGenerator* gen = access.gen(); - BasicType type = access.type(); - - LIR_Opr result = gen->new_register(type); - value.load_item(); - LIR_Opr value_opr = value.result(); - - // Because we want a 2-arg form of xchg and xadd - __ move(value_opr, result); - - assert(type == T_INT || is_reference_type(type) || type == T_LONG, "unexpected type"); - __ xchg(access.resolved_addr(), result, result, LIR_OprFact::illegalOpr); - - if (access.is_oop()) { - result = load_reference_barrier(access.gen(), result, LIR_OprFact::addressConst(0), access.decorators()); - LIR_Opr tmp = gen->new_register(type); - __ move(result, tmp); - result = tmp; - if (ShenandoahSATBBarrier) { - pre_barrier(access.gen(), access.access_emit_info(), access.decorators(), LIR_OprFact::illegalOpr, - result /* pre_val */); - } - if (ShenandoahCardBarrier) { - post_barrier(access, access.resolved_addr(), result); - } - } - - return result; -} diff --git a/src/hotspot/cpu/x86/globals_x86.hpp b/src/hotspot/cpu/x86/globals_x86.hpp index 6de467527906..c00cfba698fa 100644 --- a/src/hotspot/cpu/x86/globals_x86.hpp +++ b/src/hotspot/cpu/x86/globals_x86.hpp @@ -99,7 +99,7 @@ define_pd_global(intx, InitArrayShortSize, 8*BytesPerLong); \ product(int, UseSSE, 4, \ "Highest supported SSE instructions set on x86/x64") \ - range(0, 4) \ + range(2, 4) \ \ product(int, UseAVX, 3, \ "Highest supported AVX instructions set on x86/x64") \ diff --git a/src/hotspot/cpu/x86/macroAssembler_x86.cpp b/src/hotspot/cpu/x86/macroAssembler_x86.cpp index f64c4d3f086e..b250073be7cf 100644 --- a/src/hotspot/cpu/x86/macroAssembler_x86.cpp +++ b/src/hotspot/cpu/x86/macroAssembler_x86.cpp @@ -5355,12 +5355,10 @@ void MacroAssembler::print_CPU_state() { void MacroAssembler::restore_cpu_control_state_after_jni(Register rscratch) { // Either restore the MXCSR register after returning from the JNI Call // or verify that it wasn't changed (with -Xcheck:jni flag). - if (VM_Version::supports_sse()) { - if (RestoreMXCSROnJNICalls) { - ldmxcsr(ExternalAddress(StubRoutines::x86::addr_mxcsr_std()), rscratch); - } else if (CheckJNICalls) { - call(RuntimeAddress(StubRoutines::x86::verify_mxcsr_entry())); - } + if (RestoreMXCSROnJNICalls) { + ldmxcsr(ExternalAddress(StubRoutines::x86::addr_mxcsr_std()), rscratch); + } else if (CheckJNICalls) { + call(RuntimeAddress(StubRoutines::x86::verify_mxcsr_entry())); } // Clear upper bits of YMM registers to avoid SSE <-> AVX transition penalty. vzeroupper(); @@ -9811,7 +9809,6 @@ void MacroAssembler::convert_d2l(Register dst, XMMRegister src) { void MacroAssembler::cache_wb(Address line) { // 64 bit cpus always support clflush - assert(VM_Version::supports_clflush(), "clflush should be available"); bool optimized = VM_Version::supports_clflushopt(); bool no_evict = VM_Version::supports_clwb(); @@ -9833,7 +9830,6 @@ void MacroAssembler::cache_wb(Address line) void MacroAssembler::cache_wbsync(bool is_pre) { - assert(VM_Version::supports_clflush(), "clflush should be available"); bool optimized = VM_Version::supports_clflushopt(); bool no_evict = VM_Version::supports_clwb(); diff --git a/src/hotspot/cpu/x86/vm_version_x86.cpp b/src/hotspot/cpu/x86/vm_version_x86.cpp index 80d88f2ecb8f..46d0a41641e3 100644 --- a/src/hotspot/cpu/x86/vm_version_x86.cpp +++ b/src/hotspot/cpu/x86/vm_version_x86.cpp @@ -33,6 +33,7 @@ #include "memory/resourceArea.hpp" #include "memory/universe.hpp" #include "runtime/globals_extension.hpp" +#include "runtime/icache.hpp" #include "runtime/java.hpp" #include "runtime/os.inline.hpp" #include "runtime/stubCodeGenerator.hpp" @@ -80,20 +81,6 @@ static detect_virt_stub_t detect_virt_stub = nullptr; static clear_apx_test_state_t clear_apx_test_state_stub = nullptr; static getCPUIDBrandString_stub_t getCPUIDBrandString_stub = nullptr; -bool VM_Version::supports_clflush() { - // clflush should always be available on x86_64 - // if not we are in real trouble because we rely on it - // to flush the code cache. - // Unfortunately, Assembler::clflush is currently called as part - // of generation of the code cache flush routine. This happens - // under Universe::init before the processor features are set - // up. Assembler::flush calls this routine to check that clflush - // is allowed. So, we give the caller a free pass if Universe init - // is still in progress. - assert ((!Universe::is_fully_initialized() || _features.supports_feature(CPU_FLUSH)), "clflush should be available"); - return true; -} - #define CPUID_STANDARD_FN 0x0 #define CPUID_STANDARD_FN_1 0x1 #define CPUID_STANDARD_FN_4 0x4 @@ -511,7 +498,6 @@ class VM_Version_StubGenerator: public StubCodeGenerator { // and check upper YMM/ZMM bits after it. // int saved_useavx = UseAVX; - int saved_usesse = UseSSE; // If UseAVX is uninitialized or is set by the user to include EVEX if (use_evex) { @@ -542,7 +528,6 @@ class VM_Version_StubGenerator: public StubCodeGenerator { // EVEX setup: run in lowest evex mode VM_Version::set_evex_cpuFeatures(); // Enable temporary to pass asserts UseAVX = 3; - UseSSE = 2; #ifdef _WINDOWS // xmm5-xmm15 are not preserved by caller on windows // https://msdn.microsoft.com/en-us/library/9z1stfyw.aspx @@ -569,7 +554,6 @@ class VM_Version_StubGenerator: public StubCodeGenerator { // AVX setup VM_Version::set_avx_cpuFeatures(); // Enable temporary to pass asserts UseAVX = 1; - UseSSE = 2; #ifdef _WINDOWS __ subptr(rsp, 32); __ vmovdqu(Address(rsp, 0), xmm7); @@ -623,7 +607,6 @@ class VM_Version_StubGenerator: public StubCodeGenerator { // EVEX check: run in lowest evex mode VM_Version::set_evex_cpuFeatures(); // Enable temporary to pass asserts UseAVX = 3; - UseSSE = 2; __ lea(rsi, Address(rbp, in_bytes(VM_Version::zmm_save_offset()))); __ evmovdqul(Address(rsi, 0), xmm0, Assembler::AVX_512bit); __ evmovdqul(Address(rsi, 64), xmm7, Assembler::AVX_512bit); @@ -641,7 +624,6 @@ class VM_Version_StubGenerator: public StubCodeGenerator { generate_vzeroupper(wrapup); VM_Version::clean_cpuFeatures(); UseAVX = saved_useavx; - UseSSE = saved_usesse; __ jmp(wrapup); } @@ -649,7 +631,6 @@ class VM_Version_StubGenerator: public StubCodeGenerator { // AVX check VM_Version::set_avx_cpuFeatures(); // Enable temporary to pass asserts UseAVX = 1; - UseSSE = 2; __ lea(rsi, Address(rbp, in_bytes(VM_Version::ymm_save_offset()))); __ vmovdqu(Address(rsi, 0), xmm0); __ vmovdqu(Address(rsi, 32), xmm7); @@ -668,7 +649,6 @@ class VM_Version_StubGenerator: public StubCodeGenerator { generate_vzeroupper(wrapup); VM_Version::clean_cpuFeatures(); UseAVX = saved_useavx; - UseSSE = saved_usesse; __ bind(wrapup); __ popf(); @@ -905,25 +885,6 @@ void VM_Version::get_processor_features() { _supports_atomic_getset8 = true; _supports_atomic_getadd8 = true; - // OS should support SSE for x64 and hardware should support at least SSE2. - if (!VM_Version::supports_sse2()) { - vm_exit_during_initialization("Unknown x64 processor: SSE2 not supported"); - } - // in 64 bit the use of SSE2 is the minimum - if (UseSSE < 2) UseSSE = 2; - - // flush_icache_stub have to be generated first. - // That is why Icache line size is hard coded in ICache class, - // see icache_x86.hpp. It is also the reason why we can't use - // clflush instruction in 32-bit VM since it could be running - // on CPU which does not support it. - // - // The only thing we can do is to verify that flushed - // ICache::line_size has correct value. - guarantee(_cpuid_info.std_cpuid1_edx.bits.clflush != 0, "clflush is not supported"); - // clflush_size is size in quadwords (8 bytes). - guarantee(_cpuid_info.std_cpuid1_ebx.bits.clflush_size == 8, "such clflush size is not supported"); - // assigning this field effectively enables Unsafe.writebackMemory() // by initing UnsafeConstant.DATA_CACHE_LINE_FLUSH_SIZE to non-zero // that is only implemented on x86_64 and only if the OS plays ball @@ -952,12 +913,6 @@ void VM_Version::get_processor_features() { clear_feature(CPU_SSE4A); } - if (UseSSE < 2) - clear_feature(CPU_SSE2); - - if (UseSSE < 1) - clear_feature(CPU_SSE); - // ZX cpus specific settings if (is_zx() && FLAG_IS_DEFAULT(UseAVX)) { if (cpu_family() == 7) { @@ -972,21 +927,13 @@ void VM_Version::get_processor_features() { } // UseSSE is set to the smaller of what hardware supports and what - // the command line requires. I.e., you cannot set UseSSE to 2 on - // older Pentiums which do not support it. - int use_sse_limit = 0; - if (UseSSE > 0) { - if (UseSSE > 3 && supports_sse4_1()) { - use_sse_limit = 4; - } else if (UseSSE > 2 && supports_sse3()) { - use_sse_limit = 3; - } else if (UseSSE > 1 && supports_sse2()) { - use_sse_limit = 2; - } else if (UseSSE > 0 && supports_sse()) { - use_sse_limit = 1; - } else { - use_sse_limit = 0; - } + // the command line requires. i.e., you cannot set UseSSE to 4 on + // older systems which do not support it. + int use_sse_limit = 2; + if (UseSSE > 3 && supports_sse4_1()) { + use_sse_limit = 4; + } else if (UseSSE > 2 && supports_sse3()) { + use_sse_limit = 3; } if (FLAG_IS_DEFAULT(UseSSE)) { FLAG_SET_DEFAULT(UseSSE, use_sse_limit); @@ -1150,7 +1097,6 @@ void VM_Version::get_processor_features() { _has_intel_jcc_erratum = IntelJccErratumMitigation; } - assert(supports_clflush(), "Always present"); if (X86ICacheSync == -1) { // Auto-detect, choosing the best performant one that still flushes // the cache. We could switch to CPUID/SERIALIZE ("4"/"5") going forward. @@ -1535,7 +1481,7 @@ void VM_Version::get_processor_features() { } if (is_amd_family()) { // AMD cpus specific settings - if (supports_sse2() && FLAG_IS_DEFAULT(UseAddressNop)) { + if (FLAG_IS_DEFAULT(UseAddressNop)) { // Use it on new AMD cpus starting from Opteron. UseAddressNop = true; } @@ -1578,7 +1524,7 @@ void VM_Version::get_processor_features() { if (FLAG_IS_DEFAULT(AllocatePrefetchInstr)) { FLAG_SET_DEFAULT(AllocatePrefetchInstr, 3); } - if (supports_sse2() && FLAG_IS_DEFAULT(UseUnalignedLoadStores)) { + if (FLAG_IS_DEFAULT(UseUnalignedLoadStores)) { FLAG_SET_DEFAULT(UseUnalignedLoadStores, true); } } @@ -1594,7 +1540,7 @@ void VM_Version::get_processor_features() { if (cpu_family() >= 0x17) { // On family >=17h processors use XMM and UnalignedLoadStores // for Array Copy - if (supports_sse2() && FLAG_IS_DEFAULT(UseUnalignedLoadStores)) { + if (FLAG_IS_DEFAULT(UseUnalignedLoadStores)) { FLAG_SET_DEFAULT(UseUnalignedLoadStores, true); } #ifdef COMPILER2 @@ -1796,8 +1742,6 @@ void VM_Version::get_processor_features() { if (FLAG_IS_DEFAULT(AllocatePrefetchInstr)) { if (AllocatePrefetchInstr == 3 && !supports_3dnow_prefetch()) { FLAG_SET_DEFAULT(AllocatePrefetchInstr, 0); - } else if (!supports_sse() && supports_3dnow_prefetch()) { - FLAG_SET_DEFAULT(AllocatePrefetchInstr, 3); } } @@ -2889,29 +2833,23 @@ int64_t VM_Version::maximum_qualified_cpu_frequency(void) { VM_Version::VM_Features VM_Version::CpuidInfo::feature_flags() const { VM_Features vm_features; + + // check the features that must be present + guarantee(std_cpuid1_edx.bits.sse2 != 0, "sse2 is not supported"); + guarantee(_cpuid_info.std_cpuid1_edx.bits.clflush != 0, "clflush is not supported"); + // clflush_size is size in quadwords (8 bytes). + guarantee(_cpuid_info.std_cpuid1_ebx.bits.clflush_size == ICache::line_size/8, "clflush size is not supported"); + if (std_cpuid1_edx.bits.cmpxchg8 != 0) vm_features.set_feature(CPU_CX8); if (std_cpuid1_edx.bits.cmov != 0) vm_features.set_feature(CPU_CMOV); - if (std_cpuid1_edx.bits.clflush != 0) - vm_features.set_feature(CPU_FLUSH); - // clflush should always be available on x86_64 - // if not we are in real trouble because we rely on it - // to flush the code cache. - assert (vm_features.supports_feature(CPU_FLUSH), "clflush should be available"); if (std_cpuid1_edx.bits.fxsr != 0 || (is_amd_family() && ext_cpuid1_edx.bits.fxsr != 0)) vm_features.set_feature(CPU_FXSR); // HT flag is set for multi-core processors also. if (threads_per_core() > 1) vm_features.set_feature(CPU_HT); - if (std_cpuid1_edx.bits.mmx != 0 || (is_amd_family() && - ext_cpuid1_edx.bits.mmx != 0)) - vm_features.set_feature(CPU_MMX); - if (std_cpuid1_edx.bits.sse != 0) - vm_features.set_feature(CPU_SSE); - if (std_cpuid1_edx.bits.sse2 != 0) - vm_features.set_feature(CPU_SSE2); if (std_cpuid1_ecx.bits.sse3 != 0) vm_features.set_feature(CPU_SSE3); if (std_cpuid1_ecx.bits.ssse3 != 0) @@ -3243,17 +3181,9 @@ int VM_Version::allocate_prefetch_distance(bool use_watermark_prefetch) { // It will be used only when AllocatePrefetchStyle > 0 if (is_amd_family()) { // AMD | Hygon - if (supports_sse2()) { - return 256; // Opteron - } else { - return 128; // Athlon - } + return 256; // Opteron } else if (is_zx()) { - if (supports_sse2()) { - return 256; - } else { - return 128; - } + return 256; } else { // Intel if (supports_sse3() && is_intel_server_family()) { if (is_intel_modern_cpu()) { // Nehalem based cpus @@ -3262,14 +3192,10 @@ int VM_Version::allocate_prefetch_distance(bool use_watermark_prefetch) { return 384; } } - if (supports_sse2()) { - if (is_intel_server_family()) { - return 256; // Pentium M, Core, Core2 - } else { - return 512; // Pentium 4 - } + if (is_intel_server_family()) { + return 256; // Pentium M, Core, Core2 } else { - return 128; // Pentium 3 (and all other old CPUs) + return 512; // Pentium 4 } } } diff --git a/src/hotspot/cpu/x86/vm_version_x86.hpp b/src/hotspot/cpu/x86/vm_version_x86.hpp index fe6d424f50c2..a9bdeae41f1b 100644 --- a/src/hotspot/cpu/x86/vm_version_x86.hpp +++ b/src/hotspot/cpu/x86/vm_version_x86.hpp @@ -381,58 +381,43 @@ class VM_Version : public Abstract_VM_Version { decl(CMOV, cmov ) \ decl(FXSR, fxsr ) \ decl(HT, ht ) \ - \ - decl(MMX, mmx ) \ decl(3DNOW_PREFETCH, 3dnowpref ) /* Processor supports 3dnow prefetch and prefetchw instructions */ \ /* may not necessarily support other 3dnow instructions */ \ - decl(SSE, sse ) \ - decl(SSE2, sse2 ) \ - \ decl(SSE3, sse3 ) /* SSE3 comes from cpuid 1 (ECX) */ \ decl(SSSE3, ssse3 ) \ decl(SSE4A, sse4a ) \ decl(SSE4_1, sse4.1 ) \ - \ decl(SSE4_2, sse4.2 ) \ decl(POPCNT, popcnt ) \ decl(LZCNT, lzcnt ) \ decl(TSC, tsc ) \ - \ decl(TSCINV_BIT, tscinvbit ) \ decl(TSCINV, tscinv ) \ decl(AVX, avx ) \ decl(AVX2, avx2 ) \ - \ decl(AES, aes ) \ decl(ERMS, erms ) /* enhanced 'rep movsb/stosb' instructions */ \ decl(CLMUL, clmul ) /* carryless multiply for CRC */ \ decl(BMI1, bmi1 ) \ - \ decl(BMI2, bmi2 ) \ decl(RTM, rtm ) /* Restricted Transactional Memory instructions */ \ decl(ADX, adx ) \ decl(AVX512F, avx512f ) /* AVX 512bit foundation instructions */ \ - \ decl(AVX512DQ, avx512dq ) \ decl(AVX512PF, avx512pf ) \ decl(AVX512ER, avx512er ) \ decl(AVX512CD, avx512cd ) \ - \ decl(AVX512BW, avx512bw ) /* Byte and word vector instructions */ \ decl(AVX512VL, avx512vl ) /* EVEX instructions with smaller vector length */ \ decl(SHA, sha ) /* SHA instructions */ \ decl(FMA, fma ) /* FMA instructions */ \ - \ decl(VZEROUPPER, vzeroupper ) /* Vzeroupper instruction */ \ decl(AVX512_VPOPCNTDQ, avx512_vpopcntdq ) /* Vector popcount */ \ decl(AVX512_VPCLMULQDQ, avx512_vpclmulqdq ) /* Vector carryless multiplication */ \ decl(AVX512_VAES, avx512_vaes ) /* Vector AES instruction */ \ - \ decl(AVX512_VNNI, avx512_vnni ) /* Vector Neural Network Instructions */ \ - decl(FLUSH, clflush ) /* flush instruction */ \ decl(FLUSHOPT, clflushopt ) /* flusopth instruction */ \ decl(CLWB, clwb ) /* clwb instruction */ \ - \ decl(AVX512_VBMI2, avx512_vbmi2 ) /* VBMI2 shift left double instructions */ \ decl(AVX512_VBMI, avx512_vbmi ) /* Vector BMI instructions */ \ decl(HV, hv ) /* Hypervisor instructions */ \ @@ -790,16 +775,12 @@ class VM_Version : public Abstract_VM_Version { VM_Version::clear_cpu_features(); } static void set_avx_cpuFeatures() { - _features.set_feature(CPU_SSE); - _features.set_feature(CPU_SSE2); _features.set_feature(CPU_AVX); _features.set_feature(CPU_VZEROUPPER); } static void set_evex_cpuFeatures() { _features.set_feature(CPU_AVX10_1); _features.set_feature(CPU_AVX512F); - _features.set_feature(CPU_SSE); - _features.set_feature(CPU_SSE2); _features.set_feature(CPU_VZEROUPPER); } static void set_apx_cpuFeatures() { @@ -869,9 +850,6 @@ class VM_Version : public Abstract_VM_Version { static bool supports_cmov() { return _features.supports_feature(CPU_CMOV); } static bool supports_fxsr() { return _features.supports_feature(CPU_FXSR); } static bool supports_ht() { return _features.supports_feature(CPU_HT); } - static bool supports_mmx() { return _features.supports_feature(CPU_MMX); } - static bool supports_sse() { return _features.supports_feature(CPU_SSE); } - static bool supports_sse2() { return _features.supports_feature(CPU_SSE2); } static bool supports_sse3() { return _features.supports_feature(CPU_SSE3); } static bool supports_ssse3() { return _features.supports_feature(CPU_SSSE3); } static bool supports_sse4_1() { return _features.supports_feature(CPU_SSE4_1); } @@ -1010,10 +988,10 @@ class VM_Version : public Abstract_VM_Version { static int allocate_prefetch_distance(bool use_watermark_prefetch); - // SSE2 and later processors implement a 'pause' instruction - // that can be used for efficient implementation of - // the intrinsic for java.lang.Thread.onSpinWait() - static bool supports_on_spin_wait() { return supports_sse2(); } + // All currently supported processors support PAUSE instruction + // that can be used for efficient implementation of intrinsic for + // java.lang.Thread.onSpinWait(). + static bool supports_on_spin_wait() { return true; } // x86_64 supports fast class initialization checks static bool supports_fast_class_init_checks() { @@ -1046,7 +1024,6 @@ class VM_Version : public Abstract_VM_Version { // pending in-cache changes. // // 64 bit cpus always support clflush which writes back and evicts - // on 32 bit cpus support is recorded via a feature flag // // clflushopt is optional and acts like clflush except it does // not synchronize with other memory ops. it needs a preceding @@ -1057,8 +1034,6 @@ class VM_Version : public Abstract_VM_Version { // synchronize with other memory ops. so, it needs preceding // and trailing StoreStore fences. - static bool supports_clflush(); // Can't inline due to header file conflict - // Note: CPU_FLUSHOPT and CPU_CLWB bits should always be zero for 32-bit static bool supports_clflushopt() { return (_features.supports_feature(CPU_FLUSHOPT)); } static bool supports_clwb() { return (_features.supports_feature(CPU_CLWB)); } diff --git a/src/hotspot/cpu/x86/x86.ad b/src/hotspot/cpu/x86/x86.ad index f99d1ea9d48d..db87f81d6c41 100644 --- a/src/hotspot/cpu/x86/x86.ad +++ b/src/hotspot/cpu/x86/x86.ad @@ -4950,7 +4950,7 @@ operand immN0() %{ operand immP31() %{ - predicate(n->as_Type()->type()->reloc() == relocInfo::none + predicate(n->as_Type()->type()->is_ptr()->reloc() == relocInfo::none && (n->get_ptr() >> 31) == 0); match(ConP); @@ -16337,7 +16337,7 @@ instruct compP_rReg_mem(rFlagsRegU cr, rRegP op1, memory op2) // and raw pointers have no anti-dependencies. instruct compP_mem_rReg(rFlagsRegU cr, rRegP op1, memory op2) %{ - predicate(n->in(2)->in(2)->bottom_type()->reloc() == relocInfo::none && + predicate(n->in(2)->in(2)->bottom_type()->isa_rawptr() != nullptr && n->in(2)->as_Load()->barrier_data() == 0); match(Set cr (CmpP op1 (LoadP op2))); diff --git a/src/hotspot/os/bsd/os_bsd.cpp b/src/hotspot/os/bsd/os_bsd.cpp index b46cc6443938..fc5b9952f78a 100644 --- a/src/hotspot/os/bsd/os_bsd.cpp +++ b/src/hotspot/os/bsd/os_bsd.cpp @@ -1123,7 +1123,7 @@ bool os::dll_address_to_library_name(address addr, char* buf, // in case of error it checks if .dll/.so was built for the // same architecture as Hotspot is running on -void *os::Bsd::dlopen_helper(const char *filename, int mode, char *ebuf, int ebuflen) { +static void *dlopen_helper(const char *filename, char *ebuf, int ebuflen) { bool ieee_handling = IEEE_subnormal_handling_OK(); if (!ieee_handling) { Events::log_dll_message(nullptr, "IEEE subnormal handling check failed before loading %s", filename); @@ -1207,7 +1207,7 @@ void * os::dll_load(const char *filename, char *ebuf, int ebuflen) { log_info(os)("attempting shared library load of %s", filename); - return os::Bsd::dlopen_helper(filename, RTLD_LAZY, ebuf, ebuflen); + return dlopen_helper(filename, ebuf, ebuflen); } #else void * os::dll_load(const char *filename, char *ebuf, int ebuflen) { @@ -1218,7 +1218,7 @@ void * os::dll_load(const char *filename, char *ebuf, int ebuflen) { log_info(os)("attempting shared library load of %s", filename); void* result; - result = os::Bsd::dlopen_helper(filename, RTLD_LAZY, ebuf, ebuflen); + result = dlopen_helper(filename, ebuf, ebuflen); if (result != nullptr) { return result; } diff --git a/src/hotspot/os/bsd/os_bsd.hpp b/src/hotspot/os/bsd/os_bsd.hpp index e87a680b2d2f..91fcb090f503 100644 --- a/src/hotspot/os/bsd/os_bsd.hpp +++ b/src/hotspot/os/bsd/os_bsd.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -76,8 +76,6 @@ class os::Bsd { // Real-time clock functions static void clock_init(void); - static void *dlopen_helper(const char *path, int mode, char *ebuf, int ebuflen); - // Stack repair handling // none present @@ -105,6 +103,7 @@ class os::Bsd { static void set_numa_tonode_memory(numa_tonode_memory_func_t func) { _numa_tonode_memory = func; } static void set_numa_interleave_memory(numa_interleave_memory_func_t func) { _numa_interleave_memory = func; } static void set_numa_all_nodes(unsigned long* ptr) { _numa_all_nodes = ptr; } + public: static int sched_getcpu() { return _sched_getcpu != nullptr ? _sched_getcpu() : -1; } static int numa_node_to_cpus(int node, unsigned long *buffer, int bufferlen) { diff --git a/src/hotspot/os/windows/os_perf_windows.cpp b/src/hotspot/os/windows/os_perf_windows.cpp index 59ea83b91488..d083c72c2e03 100644 --- a/src/hotspot/os/windows/os_perf_windows.cpp +++ b/src/hotspot/os/windows/os_perf_windows.cpp @@ -779,6 +779,114 @@ static OSReturn allocate_pdh_constants() { return OS_OK; } +// Look up the PDH index by reading the English (locale 009) counter name +// registry. See KB Q287159: Using PDH APIs Correctly in a Localized Language +// for details. +static OSReturn lookup_perf_index_by_english_name(const char* english_name, + DWORD* result) { + ResourceMark rm; + + DWORD type = 0; + DWORD size = 0; + + // Determine the required buffer size + if (RegQueryValueEx(HKEY_PERFORMANCE_DATA, "Counter 009", + nullptr, &type, nullptr, &size) != ERROR_SUCCESS) { + return OS_ERR; + } + + // Since registry entries in `HKEY_PERFORMANCE_DATA` are generated on the fly, + // they could change between calls, so we can't rely just on the size returned + // by the first call. Instead, Microsoft's documentation suggests running + // these calls in a loop until the return code is no longer `ERROR_MORE_DATA`. + + char* buffer; + do { + if (size == 0) { + return OS_ERR; + } + + // When `RegQueryValueEx()` returns `ERROR_MORE_DATA`, the value in the + // callback argument is undefined, so we need to create a new variable whose + // address is passed as the callback size argument. + buffer = NEW_RESOURCE_ARRAY(char, size); + + DWORD cb_size = size; + LSTATUS status = RegQueryValueEx(HKEY_PERFORMANCE_DATA, "Counter 009", + nullptr, &type, (LPBYTE)buffer, + &cb_size); + if (status == ERROR_MORE_DATA) { + // We need to increase the buffer size. Since we don't know _how much_ to + // increase it by, we use an estimate (4096) for the increment. + DWORD increment = 4096; + if (size > MAXDWORD - increment) { + return OS_ERR; + } + size += increment; + } else if (status == ERROR_SUCCESS) { + break; + } else { + // If there was some other problem fetching this registry entry, tell the + // caller that we couldn't lookup the index. + return OS_ERR; + } + } while (true); + + if (type != REG_MULTI_SZ) { + return OS_ERR; + } + + // The buffer contains indices and names in the form (\0\0)*, so + // iterate character by character to parse the name and if it matches the + // English name, then we return the integer value of the index. + for (const char* p = buffer; *p != '\0'; ) { + const char* idx_str = p; + p += strlen(p) + 1; + if (*p == '\0') { + break; + } + + const char* name = p; + p += strlen(p) + 1; + if (strcmp(name, english_name) == 0) { + errno = 0; + char* end = nullptr; + unsigned long value = strtoul(idx_str, &end, 10); + if (errno == 0 && end != idx_str && value <= MAXDWORD) { + *result = (DWORD)value; + return OS_OK; + } + } + } + + return OS_ERR; +} + +// Return the counter index of the 'Processor Information' counter, if +// available, or else the 'Processor' counter. The former is aware of the +// possibility of multiple processor groups and thus provides a more accurate +// processor count whereas the latter serves as fallback. +static DWORD get_proc_counter() { + static DWORD pdh_idx = 0; + if (pdh_idx != 0) { + return pdh_idx; + } + + // Some APIs accept English counter names whereas others accept counter names + // in the specific user's locale. We determine the locale-specific name using + // the counter index, but to find the counter index, we use the English name + // of the counter and look for it in a specific registry key. + DWORD info_idx; + if (lookup_perf_index_by_english_name("Processor Information", + &info_idx) != OS_OK) { + info_idx = PDH_PROCESSOR_IDX; + } + + // Assign to the static variable so that the value persists across calls. + pdh_idx = info_idx; + return pdh_idx; +} + /* * Enuerate the Processor PDH object and returns a buffer containing the enumerated instances. * Caller needs ResourceMark; @@ -786,8 +894,11 @@ static OSReturn allocate_pdh_constants() { * @return buffer if successful, null on failure. */ static const char* enumerate_cpu_instances() { - char* processor; //'Processor' == PDH_PROCESSOR_IDX - if (lookup_name_by_index(PDH_PROCESSOR_IDX, &processor) != OS_OK) { + // The `PdhEnumObjectItems()` function accepts a localized name of the perf + // counter. To obtain the name that is specific to the user's locale, we + // perform a reverse lookup from counter index to counter name. + char* processor; + if (lookup_name_by_index(get_proc_counter(), &processor) != OS_OK) { return nullptr; } DWORD c_size = 0; @@ -821,13 +932,17 @@ static const char* enumerate_cpu_instances() { static int count_logical_cpus(const char* instances) { assert(instances != nullptr, "invariant"); - // count logical instances. - DWORD count; - char* tmp; - for (count = 0, tmp = const_cast(instances); *tmp != '\0'; tmp = &tmp[strlen(tmp) + 1], count++); - // PDH reports an instance for each logical processor plus an instance for the total (_Total) - assert(count == os::processor_count() + 1, "invalid enumeration!"); - return count - 1; + DWORD count = 0; + for (const char* tmp = instances; *tmp != '\0'; tmp += strlen(tmp) + 1) { + // In both the 'Processor' counter and the 'Processor Information' counter, + // the output contains totals for the processor group(s). We filter those + // out by looking for the `_Total` substring. + if (strstr(tmp, "_Total") == nullptr) { + count++; + } + } + assert(count >= 1, "invalid enumeration!"); + return count; } static int number_of_logical_cpus() { @@ -847,7 +962,16 @@ static double cpu_factor() { static double cpuFactor = .0; if (numCpus == 0) { numCpus = number_of_logical_cpus(); - assert(os::processor_count() <= (int)numCpus, "invariant"); + + // If we are using the legacy 'Processor' counter, which counts processors + // only in the first processor group, then `numCpus` can undercount, in + // which case, `numCpus` will be likely smaller than `os_processor_count`. + // However, when we use the 'Processor Information' counter, we expect both + // `numCpus` and `os::processorCount` to be identical. In both cases, we + // expect to see at least one CPU. + assert(numCpus >= 1 && numCpus <= (DWORD)os::processor_count(), + "unexpected cpu count"); + cpuFactor = numCpus * 100; } return cpuFactor; @@ -861,8 +985,8 @@ static void log_error_message_on_no_PDH_artifact(const char* counter_path) { static int initialize_cpu_query_counters(MultiCounterQueryP query, DWORD pdh_counter_idx) { assert(query != nullptr, "invariant"); assert(query->counters != nullptr, "invariant"); - char* processor; //'Processor' == PDH_PROCESSOR_IDX - if (lookup_name_by_index(PDH_PROCESSOR_IDX, &processor) != OS_OK) { + char* processor; + if (lookup_name_by_index(get_proc_counter(), &processor) != OS_OK) { return OS_ERR; } char* counter_name = nullptr; @@ -880,7 +1004,11 @@ static int initialize_cpu_query_counters(MultiCounterQueryP query, DWORD pdh_cou counter_len += OBJECT_WITH_INSTANCES_COUNTER_FMT_LEN; // "\\%s(%s)\\%s" const char* instances = enumerate_cpu_instances(); DWORD index = 0; - for (char* tmp = const_cast(instances); *tmp != '\0'; tmp = &tmp[strlen(tmp) + 1], index++) { + for (char* tmp = const_cast(instances); *tmp != '\0'; tmp = &tmp[strlen(tmp) + 1]) { + // Skip totals for each processor group. + if (strstr(tmp, ",_Total") != nullptr) { + continue; + } const size_t tmp_len = strlen(tmp); char* counter_path = NEW_RESOURCE_ARRAY(char, counter_len + tmp_len + 1); const size_t jio_snprintf_result = jio_snprintf(counter_path, @@ -896,6 +1024,7 @@ static int initialize_cpu_query_counters(MultiCounterQueryP query, DWORD pdh_cou // return OS_OK to have the system continue to run without the missing counter return OS_OK; } + index++; } // Query once to initialize the counters which require at least two samples // (like the % CPU usage) to calculate correctly. diff --git a/src/hotspot/os_cpu/windows_aarch64/javaThread_windows_aarch64.cpp b/src/hotspot/os_cpu/windows_aarch64/javaThread_windows_aarch64.cpp index 8f6f1ccd38ab..3f77a27f0519 100644 --- a/src/hotspot/os_cpu/windows_aarch64/javaThread_windows_aarch64.cpp +++ b/src/hotspot/os_cpu/windows_aarch64/javaThread_windows_aarch64.cpp @@ -26,6 +26,12 @@ #include "runtime/frame.inline.hpp" #include "runtime/javaThread.hpp" +// CRT-provided TLS slot for this module (jvm.dll), set by the OS loader. +extern "C" unsigned long _tls_index; + +// TLS offset read by the assembly code in `aarch64_get_thread_helper()`. +extern "C" ptrdiff_t _jvm_thr_current_tls_offset = JavaThread::get_thr_tls_offset(); + frame JavaThread::pd_last_frame() { assert(has_last_Java_frame(), "must have last_Java_sp() when suspended"); vmassert(_anchor.last_Java_pc() != nullptr, "not walkable"); @@ -87,3 +93,25 @@ bool JavaThread::pd_get_top_frame(frame* fr_addr, void* ucontext, bool isInJava) } void JavaThread::cache_global_variables() { } + +ptrdiff_t JavaThread::get_thr_tls_offset() { + char* tebPointer = (char*)NtCurrentTeb(); + + // 0x58 is the offset of ThreadLocalStoragePointer within the TEB. This is + // a stable Windows ABI constant but is not exposed in the SDK's minimal + // _TEB struct. + void** tls_array = *(void***)(tebPointer + 0x58); + char* curr_ptr = (char*)&Thread::_thr_current; + char* tls_block = (char*)tls_array[_tls_index]; + + // Compute the offset of Thread::_thr_current within this module's TLS + // block. Unlike ELF, which provides `tlsdesc` relocations that lets + // assembly code resolve TLS variables symbolically at link/load time, + // Windows PE/COFF has no equivalent mechanism for armasm64. So we compute + // the offset here in C++ (where the compiler knows how to access + // __declspec(thread) variables) and store it in a plain global that the + // assembly can load directly. In subsequent calls to + // `aarch64_get_thread_helper()`, the assembly will read the TEB to find the + // TLS block and then add this offset to find `Thread::_thr_current`. + return curr_ptr - tls_block; +} diff --git a/src/hotspot/os_cpu/windows_aarch64/javaThread_windows_aarch64.hpp b/src/hotspot/os_cpu/windows_aarch64/javaThread_windows_aarch64.hpp index 7d6ed16e629c..349846078146 100644 --- a/src/hotspot/os_cpu/windows_aarch64/javaThread_windows_aarch64.hpp +++ b/src/hotspot/os_cpu/windows_aarch64/javaThread_windows_aarch64.hpp @@ -46,8 +46,11 @@ bool pd_get_top_frame(frame* fr_addr, void* ucontext, bool isInJava); public: - static Thread *aarch64_get_thread_helper() { - return Thread::current(); - } + static Thread *aarch64_get_thread_helper(); + + // Compute the offset of `Thread::_thr_current` in the thread-local storage + // This offset is then used by the assembly code implementation of + // `aarch64_get_thread_helper()`. + static ptrdiff_t get_thr_tls_offset(); #endif // OS_CPU_WINDOWS_AARCH64_JAVATHREAD_WINDOWS_AARCH64_HPP diff --git a/src/hotspot/os_cpu/windows_aarch64/threadLS_windows_aarch64.S b/src/hotspot/os_cpu/windows_aarch64/threadLS_windows_aarch64.S new file mode 100644 index 000000000000..81749b9a3722 --- /dev/null +++ b/src/hotspot/os_cpu/windows_aarch64/threadLS_windows_aarch64.S @@ -0,0 +1,64 @@ +; +; Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. +; DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. +; +; This code is free software; you can redistribute it and/or modify it +; under the terms of the GNU General Public License version 2 only, as +; published by the Free Software Foundation. +; +; This code is distributed in the hope that it will be useful, but WITHOUT +; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +; FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License +; version 2 for more details (a copy is included in the LICENSE file that +; accompanied this code). +; +; You should have received a copy of the GNU General Public License version +; 2 along with this work; if not, write to the Free Software Foundation, +; Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. +; +; Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA +; or visit www.oracle.com if you need additional information or have any +; questions. +; + + ; JavaThread::aarch64_get_thread_helper() + ; + ; Optimized TLS access to `Thread::_thr_current` on Windows AArch64. + ; Returns the current thread pointer in x0, clobbers x1, while all other + ; registers are preserved. + + IMPORT _tls_index + IMPORT _jvm_thr_current_tls_offset + + AREA threadls_text, CODE, READONLY + ALIGN 4 + + ; MSVC-decorated name for: static Thread* JavaThread::aarch64_get_thread_helper() + EXPORT |?aarch64_get_thread_helper@JavaThread@@SAPEAVThread@@XZ| + +|?aarch64_get_thread_helper@JavaThread@@SAPEAVThread@@XZ| PROC + + ; x18 holds the TEB, 0x58 is a well-defined offset into the TEB on 64-bit + ; systems, so the following line loads the thread-local storage pointer + ; inside the TEB + ldr x1, [x18, #0x58] + + ; Load `_tls_index` and zero-extend it to 64 bits to occupy x0 + adrp x0, _tls_index + ldr w0, [x0, _tls_index] + + ; `x0` holds the index, `x1` holds the array base address (each entry is 64 + ; bits long), so in the following line, x1 = array_base[_tls_index] + ldr x1, [x1, x0, lsl #3] + + ; Load cached TLS offset of `Thread::_thr_current` + adrp x0, _jvm_thr_current_tls_offset + ldr x0, [x0, _jvm_thr_current_tls_offset] + + ; Load `Thread::_thr_current` value + ldr x0, [x1, x0] + + ret + + ENDP + END diff --git a/src/hotspot/share/adlc/formssel.cpp b/src/hotspot/share/adlc/formssel.cpp index 79779782c2a3..5802217c1c1c 100644 --- a/src/hotspot/share/adlc/formssel.cpp +++ b/src/hotspot/share/adlc/formssel.cpp @@ -453,6 +453,14 @@ Form::DataType InstructForm::is_ideal_store() const { return _matrule->is_ideal_store(); } +// Return 'true' if this instruction matches an ideal vector node +bool InstructForm::is_vector() const { + if( _matrule == nullptr ) return false; + + return _matrule->is_vector(); +} + + // Return the input register that must match the output register // If this is not required, return 0 uint InstructForm::two_address(FormDict &globals) { @@ -759,6 +767,51 @@ int InstructForm::memory_operand(FormDict &globals) const { return NO_MEMORY_OPERAND; } +// This instruction captures the machine-independent bottom_type +// Expected use is for pointer vs oop determination for LoadP +bool InstructForm::captures_bottom_type(FormDict &globals) const { + if (_matrule && _matrule->_rChild && + (!strcmp(_matrule->_rChild->_opType,"CastPP") || // new result type + !strcmp(_matrule->_rChild->_opType,"CastDD") || + !strcmp(_matrule->_rChild->_opType,"CastFF") || + !strcmp(_matrule->_rChild->_opType,"CastII") || + !strcmp(_matrule->_rChild->_opType,"CastLL") || + !strcmp(_matrule->_rChild->_opType,"CastVV") || + !strcmp(_matrule->_rChild->_opType,"CastX2P") || // new result type + !strcmp(_matrule->_rChild->_opType,"DecodeN") || + !strcmp(_matrule->_rChild->_opType,"EncodeP") || + !strcmp(_matrule->_rChild->_opType,"DecodeNKlass") || + !strcmp(_matrule->_rChild->_opType,"EncodePKlass") || + !strcmp(_matrule->_rChild->_opType,"LoadN") || + !strcmp(_matrule->_rChild->_opType,"LoadNKlass") || + !strcmp(_matrule->_rChild->_opType,"CreateEx") || // type of exception + !strcmp(_matrule->_rChild->_opType,"CheckCastPP") || + !strcmp(_matrule->_rChild->_opType,"GetAndSetP") || + !strcmp(_matrule->_rChild->_opType,"GetAndSetN") || + !strcmp(_matrule->_rChild->_opType,"RotateLeft") || + !strcmp(_matrule->_rChild->_opType,"RotateRight") || +#if INCLUDE_SHENANDOAHGC + !strcmp(_matrule->_rChild->_opType,"ShenandoahCompareAndExchangeP") || + !strcmp(_matrule->_rChild->_opType,"ShenandoahCompareAndExchangeN") || +#endif + !strcmp(_matrule->_rChild->_opType,"StrInflatedCopy") || + !strcmp(_matrule->_rChild->_opType,"VectorCmpMasked")|| + !strcmp(_matrule->_rChild->_opType,"VectorMaskGen")|| + !strcmp(_matrule->_rChild->_opType,"VerifyVectorAlignment")|| + !strcmp(_matrule->_rChild->_opType,"CompareAndExchangeP") || + !strcmp(_matrule->_rChild->_opType,"CompareAndExchangeN"))) return true; + else if ( is_ideal_load() == Form::idealP ) return true; + else if ( is_ideal_store() != Form::none ) return true; + + if (needs_base_oop_edge(globals)) return true; + + if (is_vector()) return true; + if (is_mach_constant()) return true; + + return false; +} + + // Access instr_cost attribute or return null. const char* InstructForm::cost() { for (Attribute* cur = _attribs; cur != nullptr; cur = (Attribute*)cur->_next) { @@ -1128,6 +1181,9 @@ const char *InstructForm::mach_base_class(FormDict &globals) const { } else if (is_mach_constant()) { return "MachConstantNode"; + } + else if (captures_bottom_type(globals)) { + return "MachTypeNode"; } else { return "MachNode"; } @@ -4281,6 +4337,58 @@ Form::DataType MatchRule::is_ideal_load() const { return ideal_load; } +bool MatchRule::is_vector() const { + static const char *vector_list[] = { + "AddVB","AddVS","AddVI","AddVL","AddVHF","AddVF","AddVD", + "SubVB","SubVS","SubVI","SubVL","SubVHF","SubVF","SubVD", + "MulVB","MulVS","MulVI","MulVL","MulVHF","MulVF","MulVD", + "DivVHF","DivVF","DivVD", + "AbsVB","AbsVS","AbsVI","AbsVL","AbsVF","AbsVD", + "NegVF","NegVD","NegVI","NegVL", + "SqrtVD","SqrtVF","SqrtVHF", + "AndV" ,"XorV" ,"OrV", + "MaxV", "MinV", "MinVHF", "MaxVHF", "UMinV", "UMaxV", + "CompressV", "ExpandV", "CompressM", "CompressBitsV", "ExpandBitsV", + "AddReductionVI", "AddReductionVL", + "AddReductionVHF", "AddReductionVF", "AddReductionVD", + "MulReductionVI", "MulReductionVL", + "MulReductionVHF", "MulReductionVF", "MulReductionVD", + "MaxReductionV", "MinReductionV", + "AndReductionV", "OrReductionV", "XorReductionV", + "MulAddVS2VI", "MacroLogicV", + "LShiftCntV","RShiftCntV", + "LShiftVB","LShiftVS","LShiftVI","LShiftVL", + "RShiftVB","RShiftVS","RShiftVI","RShiftVL", + "URShiftVB","URShiftVS","URShiftVI","URShiftVL", + "Replicate","ReverseV","ReverseBytesV", + "RoundDoubleModeV","RotateLeftV" , "RotateRightV", "LoadVector","StoreVector", + "LoadVectorGather", "StoreVectorScatter", "LoadVectorGatherMasked", "StoreVectorScatterMasked", + "SelectFromTwoVector", "VectorTest", "VectorLoadMask", "VectorStoreMask", "VectorBlend", "VectorInsert", + "VectorRearrange", "VectorLoadShuffle", "VectorLoadConst", + "VectorCastB2X", "VectorCastS2X", "VectorCastI2X", + "VectorCastL2X", "VectorCastF2X", "VectorCastD2X", "VectorCastF2HF", "VectorCastHF2F", + "VectorUCastB2X", "VectorUCastS2X", "VectorUCastI2X", + "VectorMaskWrapper","VectorMaskCmp","VectorReinterpret","LoadVectorMasked","StoreVectorMasked", + "FmaVD", "FmaVF", "FmaVHF", "PopCountVI", "PopCountVL", "PopulateIndex", "VectorLongToMask", + "CountLeadingZerosV", "CountTrailingZerosV", "SignumVF", "SignumVD", "SaturatingAddV", "SaturatingSubV", + // Next are vector mask ops. + "MaskAll", "AndVMask", "OrVMask", "XorVMask", "VectorMaskCast", + "RoundVF", "RoundVD", + // Next are not supported currently. + "PackB","PackS","PackI","PackL","PackF","PackD","Pack2L","Pack2D", + "ExtractB","ExtractUB","ExtractC","ExtractS","ExtractI","ExtractL","ExtractF","ExtractD" + }; + int cnt = sizeof(vector_list)/sizeof(char*); + if (_rChild) { + const char *opType = _rChild->_opType; + for (int i=0; iadd_req(inst%d->in(%d)); // unmatched ideal edge\n", inst_num, unmatched_edge); } - // Get bottom type from instruction whose result we are replacing - fprintf(fp, " root->_bottom_type = inst%d->bottom_type();\n", inst_num); + // If new instruction captures bottom type + if( root_form->captures_bottom_type(globals) ) { + // Get bottom type from instruction whose result we are replacing + fprintf(fp, " root->_bottom_type = inst%d->bottom_type();\n", inst_num); + } // Define result register and result operand fprintf(fp, " ra_->set_oop (root, ra_->is_oop(inst%d));\n", inst_num); fprintf(fp, " ra_->set_pair(root->_idx, ra_->get_reg_second(inst%d), ra_->get_reg_first(inst%d));\n", inst_num, inst_num); @@ -1584,8 +1587,11 @@ void ArchDesc::defineExpand(FILE *fp, InstructForm *node) { fprintf(fp, " ((MachIfNode*)n%d)->_fcnt = _fcnt;\n", cnt); } - // Fill in the bottom_type - fprintf(fp, " n%d->_bottom_type = bottom_type();\n", cnt); + // Fill in the bottom_type where requested + if (node->captures_bottom_type(_globalNames) && + new_inst->captures_bottom_type(_globalNames)) { + fprintf(fp, " ((MachTypeNode*)n%d)->_bottom_type = bottom_type();\n", cnt); + } const char *resultOper = new_inst->reduce_result(); fprintf(fp," n%d->set_opnd_array(0, state->MachOperGenerator(%s));\n", @@ -3959,15 +3965,13 @@ void ArchDesc::buildMachNode(FILE *fp_cpp, InstructForm *inst, const char *inden } } - // Fill in the bottom_type - if (inst->_matrule != nullptr && strcmp(inst->_matrule->_opType, "PrefetchAllocation") == 0) { - // Special case, with AllocatePrefetchStyle == 3, this should be Type::MEMORY, but the graph - // seems unsound, needs further investigation - fprintf(fp_cpp, "%s node->_bottom_type = Type::ABIO;\n", indent); - } else { - fprintf(fp_cpp, "%s node->_bottom_type = _leaf->bottom_type();\n", indent); + // Fill in the bottom_type where requested + if (inst->captures_bottom_type(_globalNames)) { + if (strncmp("MachCall", inst->mach_base_class(_globalNames), strlen("MachCall")) != 0 + && strncmp("MachIf", inst->mach_base_class(_globalNames), strlen("MachIf")) != 0) { + fprintf(fp_cpp, "%s node->_bottom_type = _leaf->bottom_type();\n", indent); + } } - if( inst->is_ideal_if() ) { fprintf(fp_cpp, "%s node->_prob = _leaf->as_If()->_prob;\n", indent); fprintf(fp_cpp, "%s node->_fcnt = _leaf->as_If()->_fcnt;\n", indent); @@ -4022,8 +4026,10 @@ void InstructForm::define_cisc_version(ArchDesc& AD, FILE* fp_cpp) { fprintf(fp_cpp, "MachNode *%sNode::cisc_version(int offset) {\n", this->_ident); // Create the MachNode object fprintf(fp_cpp, " %sNode *node = new %sNode();\n", name, name); - // Fill in the bottom_type - fprintf(fp_cpp, " node->_bottom_type = bottom_type();\n"); + // Fill in the bottom_type where requested + if ( this->captures_bottom_type(AD.globalNames()) ) { + fprintf(fp_cpp, " node->_bottom_type = bottom_type();\n"); + } uint cur_num_opnds = num_opnds(); if (cur_num_opnds > 1 && cur_num_opnds != num_unique_opnds()) { @@ -4069,9 +4075,10 @@ void InstructForm::define_short_branch_methods(ArchDesc& AD, FILE* fp_cpp) { fprintf(fp_cpp, " node->_prob = _prob;\n"); fprintf(fp_cpp, " node->_fcnt = _fcnt;\n"); } - - // Fill in the bottom_type - fprintf(fp_cpp, " node->_bottom_type = bottom_type();\n"); + // Fill in the bottom_type where requested + if ( this->captures_bottom_type(AD.globalNames()) ) { + fprintf(fp_cpp, " node->_bottom_type = bottom_type();\n"); + } fprintf(fp_cpp, "\n"); // Short branch version must use same node index for access diff --git a/src/hotspot/share/adlc/output_h.cpp b/src/hotspot/share/adlc/output_h.cpp index f7389b5a1b1f..6cb82f4df7f7 100644 --- a/src/hotspot/share/adlc/output_h.cpp +++ b/src/hotspot/share/adlc/output_h.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -1841,6 +1841,112 @@ void ArchDesc::declareClasses(FILE *fp) { fprintf(fp," virtual const Pipeline *pipeline() const;\n"); } + // Generate virtual function for MachNodeX::bottom_type when necessary + // + // Note on accuracy: Pointer-types of machine nodes need to be accurate, + // or else alias analysis on the matched graph may produce bad code. + // Moreover, the aliasing decisions made on machine-node graph must be + // no less accurate than those made on the ideal graph, or else the graph + // may fail to schedule. (Reason: Memory ops which are reordered in + // the ideal graph might look interdependent in the machine graph, + // thereby removing degrees of scheduling freedom that the optimizer + // assumed would be available.) + // + // %%% We should handle many of these cases with an explicit ADL clause: + // instruct foo() %{ ... bottom_type(TypeRawPtr::BOTTOM); ... %} + if( data_type != Form::none ) { + // A constant's bottom_type returns a Type containing its constant value + + // !!!!! + // Convert all ints, floats, ... to machine-independent TypeXs + // as is done for pointers + // + // Construct appropriate constant type containing the constant value. + fprintf(fp," virtual const class Type *bottom_type() const {\n"); + switch( data_type ) { + case Form::idealI: + fprintf(fp," return TypeInt::make(opnd_array(1)->constant());\n"); + break; + case Form::idealP: + case Form::idealN: + case Form::idealNKlass: + fprintf(fp," return opnd_array(1)->type();\n"); + break; + case Form::idealD: + fprintf(fp," return TypeD::make(opnd_array(1)->constantD());\n"); + break; + case Form::idealH: + fprintf(fp," return TypeH::make(opnd_array(1)->constantH());\n"); + break; + case Form::idealF: + fprintf(fp," return TypeF::make(opnd_array(1)->constantF());\n"); + break; + case Form::idealL: + fprintf(fp," return TypeLong::make(opnd_array(1)->constantL());\n"); + break; + default: + assert( false, "Unimplemented()" ); + break; + } + fprintf(fp," };\n"); + } +/* else if ( instr->_matrule && instr->_matrule->_rChild && + ( strcmp("ConvF2I",instr->_matrule->_rChild->_opType)==0 + || strcmp("ConvD2I",instr->_matrule->_rChild->_opType)==0 ) ) { + // !!!!! !!!!! + // Provide explicit bottom type for conversions to int + // On Intel the result operand is a stackSlot, untyped. + fprintf(fp," virtual const class Type *bottom_type() const {"); + fprintf(fp, " return TypeInt::INT;"); + fprintf(fp, " };\n"); + }*/ + else if( instr->is_ideal_copy() && + !strcmp(instr->_matrule->_lChild->_opType,"stackSlotP") ) { + // !!!!! + // Special hack for ideal Copy of pointer. Bottom type is oop or not depending on input. + fprintf(fp," const Type *bottom_type() const { return in(1)->bottom_type(); } // Copy?\n"); + } + else if( instr->is_ideal_loadPC() ) { + // LoadPCNode provides the return address of a call to native code. + // Define its bottom type to be TypeRawPtr::BOTTOM instead of TypePtr::BOTTOM + // since it is a pointer to an internal VM location and must have a zero offset. + // Allocation detects derived pointers, in part, by their non-zero offsets. + fprintf(fp," const Type *bottom_type() const { return TypeRawPtr::BOTTOM; } // LoadPC?\n"); + } + else if( instr->is_ideal_box() ) { + // BoxNode provides the address of a stack slot. + // Define its bottom type to be TypeRawPtr::BOTTOM instead of TypePtr::BOTTOM + // This prevents raise_above_anti_dependences from complaining. It will + // complain if it sees that the pointer base is TypePtr::BOTTOM since + // it doesn't understand what that might alias. + fprintf(fp," const Type *bottom_type() const { return TypeRawPtr::BOTTOM; } // Box?\n"); + } + else if (instr->_matrule && instr->_matrule->_rChild && + (!strcmp(instr->_matrule->_rChild->_opType,"CMoveP") || !strcmp(instr->_matrule->_rChild->_opType,"CMoveN")) ) { + int offset = 1; + // Special special hack to see if the Cmp? has been incorporated in the conditional move + MatchNode *rl = instr->_matrule->_rChild->_lChild; + if (rl && !strcmp(rl->_opType, "Binary") && rl->_rChild && strncmp(rl->_rChild->_opType, "Cmp", 3) == 0) { + offset = 2; + fprintf(fp," const Type *bottom_type() const { if (req() == 3) return in(2)->bottom_type();\n\tconst Type *t = in(oper_input_base()+%d)->bottom_type(); return (req() <= oper_input_base()+%d) ? t : t->meet(in(oper_input_base()+%d)->bottom_type()); } // %s\n", + offset, offset+1, offset+1, instr->_matrule->_rChild->_opType); + } else { + // Special hack for ideal CMove; ideal type depends on inputs + fprintf(fp," const Type *bottom_type() const { const Type *t = in(oper_input_base()+%d)->bottom_type(); return (req() <= oper_input_base()+%d) ? t : t->meet(in(oper_input_base()+%d)->bottom_type()); } // %s\n", + offset, offset+1, offset+1, instr->_matrule->_rChild->_opType); + } + } + else if (instr->is_tls_instruction()) { + // Special hack for tlsLoadP + fprintf(fp," const Type *bottom_type() const { return TypeRawPtr::BOTTOM; } // tlsLoadP\n"); + } + else if ( instr->is_ideal_if() ) { + fprintf(fp," const Type *bottom_type() const { return TypeTuple::IFBOTH; } // matched IfNode\n"); + } + else if ( instr->is_ideal_membar() ) { + fprintf(fp," const Type *bottom_type() const { return TypeTuple::MEMBAR; } // matched MemBar\n"); + } + // Check where 'ideal_type' must be customized /* if ( instr->_matrule && instr->_matrule->_rChild && diff --git a/src/hotspot/share/c1/c1_GraphBuilder.cpp b/src/hotspot/share/c1/c1_GraphBuilder.cpp index f910ecadc164..db55b8c5fa81 100644 --- a/src/hotspot/share/c1/c1_GraphBuilder.cpp +++ b/src/hotspot/share/c1/c1_GraphBuilder.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -3558,7 +3558,7 @@ const char* GraphBuilder::check_can_parse(ciMethod* callee) const { // negative filter: should callee NOT be inlined? returns null, ok to inline, or rejection msg const char* GraphBuilder::should_not_inline(ciMethod* callee) const { - if ( compilation()->directive()->should_not_inline(callee)) return "disallowed by CompileCommand"; + if ( compilation()->directive()->should_not_inline(callee, compilation()->env()->comp_level())) return "disallowed by CompileCommand"; if ( callee->dont_inline()) return "don't inline by annotation"; return nullptr; } diff --git a/src/hotspot/share/ci/ciEnv.hpp b/src/hotspot/share/ci/ciEnv.hpp index b384ff47a895..8167697e84be 100644 --- a/src/hotspot/share/ci/ciEnv.hpp +++ b/src/hotspot/share/ci/ciEnv.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -236,6 +236,9 @@ class ciEnv : StackObj { ciInstanceKlass* declared_holder = get_instance_klass_for_declared_method_holder(holder); return _factory->get_unloaded_method(declared_holder, name, signature, accessor); } + InstanceKlass::ClassState get_cached_init_state(uint id) { + return (InstanceKlass::ClassState)_factory->cached_init_state(id); + } // Get a ciKlass representing an unloaded klass. // Ensures uniqueness of the result. diff --git a/src/hotspot/share/ci/ciInstanceKlass.cpp b/src/hotspot/share/ci/ciInstanceKlass.cpp index 6243258acd9b..293063d0b680 100644 --- a/src/hotspot/share/ci/ciInstanceKlass.cpp +++ b/src/hotspot/share/ci/ciInstanceKlass.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -136,12 +136,14 @@ ciInstanceKlass::ciInstanceKlass(ciSymbol* name, // ------------------------------------------------------------------ -// ciInstanceKlass::compute_shared_is_initialized -void ciInstanceKlass::compute_shared_init_state() { - GUARDED_VM_ENTRY( - InstanceKlass* ik = get_instanceKlass(); - _init_state = ik->init_state(); - ) +InstanceKlass::ClassState ciInstanceKlass::compute_init_state() { + if (_is_shared && is_loaded()) { + // Return cached init state of shared klass + ciEnv* env = CURRENT_ENV; + assert(env->task() != nullptr, "only calls from compilation are expected here"); + return env->get_cached_init_state(ident()); + } + return _init_state; } // ------------------------------------------------------------------ @@ -319,11 +321,11 @@ void ciInstanceKlass::print_impl(outputStream* st) { bool_to_str(has_subklass()), layout_helper()); - _flags.print_klass_flags(); + _flags.print_klass_flags(st); if (_super) { st->print(" super="); - _super->print_name(); + _super->print_name_on(st); } if (_java_mirror) { st->print(" mirror=PRESENT"); diff --git a/src/hotspot/share/ci/ciInstanceKlass.hpp b/src/hotspot/share/ci/ciInstanceKlass.hpp index a84c63981c9b..221e6806b5ac 100644 --- a/src/hotspot/share/ci/ciInstanceKlass.hpp +++ b/src/hotspot/share/ci/ciInstanceKlass.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -106,43 +106,36 @@ class ciInstanceKlass : public ciKlass { bool is_shared() { return _is_shared; } - void compute_shared_init_state(); + InstanceKlass::ClassState compute_init_state(); bool compute_shared_has_subklass(); int compute_nonstatic_fields(); GrowableArray* compute_nonstatic_fields_impl(GrowableArray* super_fields); bool compute_has_trusted_loader(); - // Update the init_state for shared klasses - void update_if_shared(InstanceKlass::ClassState expected) { - if (_is_shared && _init_state != expected) { - if (is_loaded()) compute_shared_init_state(); - } - } - public: // Has this klass been initialized? bool is_initialized() { - update_if_shared(InstanceKlass::fully_initialized); - return _init_state == InstanceKlass::fully_initialized; + InstanceKlass::ClassState state = compute_init_state(); + return state == InstanceKlass::fully_initialized; } bool is_not_initialized() { - update_if_shared(InstanceKlass::fully_initialized); - return _init_state < InstanceKlass::being_initialized; + InstanceKlass::ClassState state = compute_init_state(); + return state < InstanceKlass::being_initialized; } // Is this klass being initialized? bool is_being_initialized() { - update_if_shared(InstanceKlass::being_initialized); - return _init_state == InstanceKlass::being_initialized; + InstanceKlass::ClassState state = compute_init_state(); + return state == InstanceKlass::being_initialized; } // Has this klass been linked? bool is_linked() { - update_if_shared(InstanceKlass::linked); - return _init_state >= InstanceKlass::linked; + InstanceKlass::ClassState state = compute_init_state(); + return state >= InstanceKlass::linked; } // Is this klass in error state? bool is_in_error_state() { - update_if_shared(InstanceKlass::initialization_error); - return _init_state == InstanceKlass::initialization_error; + InstanceKlass::ClassState state = compute_init_state(); + return state == InstanceKlass::initialization_error; } // General klass information. diff --git a/src/hotspot/share/ci/ciObjectFactory.cpp b/src/hotspot/share/ci/ciObjectFactory.cpp index 2af5d812922f..d3bef01f8525 100644 --- a/src/hotspot/share/ci/ciObjectFactory.cpp +++ b/src/hotspot/share/ci/ciObjectFactory.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -48,6 +48,7 @@ #include "gc/shared/collectedHeap.inline.hpp" #include "memory/allocation.inline.hpp" #include "memory/universe.hpp" +#include "oops/instanceKlass.hpp" #include "oops/oop.inline.hpp" #include "oops/trainingData.hpp" #include "runtime/handles.inline.hpp" @@ -83,6 +84,7 @@ ciObjectFactory::ciObjectFactory(Arena* arena, int expected_size) : _arena(arena), _ci_metadata(arena, expected_size, 0, nullptr), + _cached_init_state(arena, _shared_ident_limit, 0, (u1)0), _unloaded_methods(arena, 4, 0, nullptr), _unloaded_klasses(arena, 8, 0, nullptr), _unloaded_instances(arena, 4, 0, nullptr), @@ -97,6 +99,28 @@ ciObjectFactory::ciObjectFactory(Arena* arena, // If the shared ci objects exist append them to this factory's objects if (_shared_ci_metadata != nullptr) { _ci_metadata.appendAll(_shared_ci_metadata); + // ciInstanceKlass for well-known class is shared by all + // compiler threads and can be updated concurrently by + // other compiler threads during compilation. + // Make local copy of class state to avoid state change + // during compilation. + int len = _ci_metadata.length(); + for (int i = 0; i < len; i++) { + ciMetadata* obj = _ci_metadata.at(i); + if (obj->is_loaded() && obj->is_instance_klass()) { + ciInstanceKlass* cik = obj->as_instance_klass(); + precond(cik->is_shared()); + InstanceKlass::ClassState current_state = cik->_init_state; + InstanceKlass::ClassState state = InstanceKlass::fully_initialized; + if (current_state != state) { + GUARDED_VM_ENTRY( state = cik->get_instanceKlass()->init_state(); ) + // Update state of shared ciInstanceKlass + cik->_init_state = state; + } + // Cache state for current compilation + _cached_init_state.at_put_grow(cik->ident(), (u1)state, 0); + } + } } } diff --git a/src/hotspot/share/ci/ciObjectFactory.hpp b/src/hotspot/share/ci/ciObjectFactory.hpp index fd7ca6bb8013..c578aecb5647 100644 --- a/src/hotspot/share/ci/ciObjectFactory.hpp +++ b/src/hotspot/share/ci/ciObjectFactory.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -47,6 +47,8 @@ class ciObjectFactory : public ArenaObj { Arena* _arena; GrowableArray _ci_metadata; + // Local copy of shared ciInstanceKlass init state for current compilation + GrowableArray _cached_init_state; GrowableArray _unloaded_methods; GrowableArray _unloaded_klasses; GrowableArray _unloaded_instances; @@ -103,6 +105,11 @@ class ciObjectFactory : public ArenaObj { ciMetadata* cached_metadata(Metadata* key); ciSymbol* get_symbol(Symbol* key); + // Get cached init state of shared ciInstanceKlass + u1 cached_init_state(uint id) { + return _cached_init_state.at(id); + } + // Get the ciSymbol corresponding to one of the vmSymbols. static ciSymbol* vm_symbol_at(vmSymbolID index); diff --git a/src/hotspot/share/classfile/classLoader.cpp b/src/hotspot/share/classfile/classLoader.cpp index 6d25e460688d..bf00185ffa9a 100644 --- a/src/hotspot/share/classfile/classLoader.cpp +++ b/src/hotspot/share/classfile/classLoader.cpp @@ -96,9 +96,18 @@ static JImageClose_t JImageClose = nullptr; static JImageFindResource_t JImageFindResource = nullptr; static JImageGetResource_t JImageGetResource = nullptr; -// JimageFile pointer, or null if exploded JDK build. +// JImageFile pointer, or null if exploded JDK build. static JImageFile* JImage_file = nullptr; +// PreviewMode status to control preview behaviour. JImage_file is unusable +// for normal lookup until (Preview_mode != PREVIEW_MODE_UNINITIALIZED). +enum PreviewMode { + PREVIEW_MODE_UNINITIALIZED = 0, + PREVIEW_MODE_DEFAULT = 1, + PREVIEW_MODE_ENABLE_PREVIEW = 2 +}; +static PreviewMode Preview_mode = PREVIEW_MODE_UNINITIALIZED; + // Globals PerfCounter* ClassLoader::_perf_accumulated_time = nullptr; @@ -154,7 +163,7 @@ void ClassLoader::print_counters(outputStream *st) { GrowableArray* ClassLoader::_patch_mod_entries = nullptr; GrowableArray* ClassLoader::_exploded_entries = nullptr; -ClassPathEntry* ClassLoader::_jrt_entry = nullptr; +ClassPathImageEntry* ClassLoader::_jrt_entry = nullptr; ClassPathEntry* volatile ClassLoader::_first_append_entry_list = nullptr; ClassPathEntry* volatile ClassLoader::_last_append_entry = nullptr; @@ -171,15 +180,6 @@ static bool string_starts_with(const char* str, const char* str_to_find) { } #endif -static const char* get_jimage_version_string() { - static char version_string[10] = ""; - if (version_string[0] == '\0') { - jio_snprintf(version_string, sizeof(version_string), "%d.%d", - VM_Version::vm_major_version(), VM_Version::vm_minor_version()); - } - return (const char*)version_string; -} - bool ClassLoader::string_ends_with(const char* str, const char* str_to_find) { size_t str_len = strlen(str); size_t str_to_find_len = strlen(str_to_find); @@ -234,6 +234,69 @@ Symbol* ClassLoader::package_from_class_name(const Symbol* name, bool* bad_class return SymbolTable::new_symbol(name, pointer_delta_as_int(start, base), pointer_delta_as_int(end, base)); } +// -------------------------------- +// The following jimage_xxx static functions encapsulate all JImage_file and Preview_mode access. +// This is done to make it easy to reason about the JImage file state (exists vs initialized etc.). + +// Opens the named JImage file and sets the JImage file reference. +// Returns true if opening the JImage file was successful (see also jimage_is_open()). +static bool jimage_open(const char* modules_path) { + // Currently 'error' is not set to anything useful, so ignore it here. + jint error; + JImage_file = (*JImageOpen)(modules_path, &error); + if (Arguments::has_jimage() && JImage_file == nullptr) { + // The modules file exists but is unreadable or corrupt + vm_exit_during_initialization(err_msg("Unable to load %s", modules_path)); + } + return JImage_file != nullptr; +} + +// Closes and clears the JImage file reference (this will only be called during shutdown). +static void jimage_close() { + if (JImage_file != nullptr) { + (*JImageClose)(JImage_file); + JImage_file = nullptr; + } +} + +// Returns whether a JImage file was opened (but NOT whether it was initialized yet). +static bool jimage_is_open() { + return JImage_file != nullptr; +} + +// Returns the JImage file reference (which may or may not be initialized). +static JImageFile* jimage_non_null() { + assert(jimage_is_open(), "should have been opened by ClassLoader::lookup_vm_options " + "and remains open throughout normal JVM lifetime"); + return JImage_file; +} + +// Returns true if jimage_init() has been called. Once the JImage file is initialized, +// jimage_is_preview_enabled() can be called to correctly determine the access mode. +static bool jimage_is_initialized() { + return jimage_is_open() && Preview_mode != PREVIEW_MODE_UNINITIALIZED; +} + +// Returns the access mode for an initialized JImage file (reflects --enable-preview). +static bool is_preview_enabled() { + return Preview_mode == PREVIEW_MODE_ENABLE_PREVIEW; +} + +// Looks up the location of a named JImage resource. This "raw" lookup function allows +// the preview mode to be manually specified, so must not be accessible outside this +// class. ClassPathImageEntry manages all calls for resources after startup is complete. +static JImageLocationRef jimage_find_resource(const char* module_name, + const char* file_name, + bool is_preview, + jlong* size) { + return ((*JImageFindResource)(jimage_non_null(), + module_name, + file_name, + is_preview, + size)); +} +// -------------------------------- + // Given a fully qualified package name, find its defining package in the class loader's // package entry table. PackageEntry* ClassLoader::get_package_entry(Symbol* pkg_name, ClassLoaderData* loader_data) { @@ -372,28 +435,15 @@ ClassFileStream* ClassPathZipEntry::open_stream(JavaThread* current, const char* DEBUG_ONLY(ClassPathImageEntry* ClassPathImageEntry::_singleton = nullptr;) -JImageFile* ClassPathImageEntry::jimage() const { - return JImage_file; -} - -JImageFile* ClassPathImageEntry::jimage_non_null() const { - assert(ClassLoader::has_jrt_entry(), "must be"); - assert(jimage() != nullptr, "should have been opened by ClassLoader::lookup_vm_options " - "and remained throughout normal JVM lifetime"); - return jimage(); -} - void ClassPathImageEntry::close_jimage() { - if (jimage() != nullptr) { - (*JImageClose)(jimage()); - JImage_file = nullptr; - } + jimage_close(); } -ClassPathImageEntry::ClassPathImageEntry(JImageFile* jimage, const char* name) : +ClassPathImageEntry::ClassPathImageEntry(const char* name) : ClassPathEntry() { - guarantee(jimage != nullptr, "jimage file is null"); + guarantee(jimage_is_initialized(), "jimage is not initialized"); guarantee(name != nullptr, "jimage file name is null"); + assert(_singleton == nullptr, "VM supports only one jimage"); DEBUG_ONLY(_singleton = this); size_t len = strlen(name) + 1; @@ -412,6 +462,8 @@ ClassFileStream* ClassPathImageEntry::open_stream(JavaThread* current, const cha // 2. A package is in at most one module in the jimage file. // ClassFileStream* ClassPathImageEntry::open_stream_for_loader(JavaThread* current, const char* name, ClassLoaderData* loader_data) { + const bool is_preview = is_preview_enabled(); + jlong size; JImageLocationRef location = 0; @@ -420,7 +472,7 @@ ClassFileStream* ClassPathImageEntry::open_stream_for_loader(JavaThread* current if (pkg_name != nullptr) { if (!Universe::is_module_initialized()) { - location = (*JImageFindResource)(jimage_non_null(), JAVA_BASE_NAME, get_jimage_version_string(), name, &size); + location = jimage_find_resource(JAVA_BASE_NAME, name, is_preview, &size); } else { PackageEntry* package_entry = ClassLoader::get_package_entry(pkg_name, loader_data); if (package_entry != nullptr) { @@ -431,7 +483,7 @@ ClassFileStream* ClassPathImageEntry::open_stream_for_loader(JavaThread* current assert(module->is_named(), "Boot classLoader package is in unnamed module"); const char* module_name = module->name()->as_C_string(); if (module_name != nullptr) { - location = (*JImageFindResource)(jimage_non_null(), module_name, get_jimage_version_string(), name, &size); + location = jimage_find_resource(module_name, name, is_preview, &size); } } } @@ -444,7 +496,7 @@ ClassFileStream* ClassPathImageEntry::open_stream_for_loader(JavaThread* current char* data = NEW_RESOURCE_ARRAY(char, size); (*JImageGetResource)(jimage_non_null(), location, data, size); // Resource allocated - assert(this == (ClassPathImageEntry*)ClassLoader::get_jrt_entry(), "must be"); + assert(this == ClassLoader::get_jrt_entry(), "must be"); return new ClassFileStream((u1*)data, checked_cast(size), _name, @@ -454,16 +506,9 @@ ClassFileStream* ClassPathImageEntry::open_stream_for_loader(JavaThread* current return nullptr; } -JImageLocationRef ClassLoader::jimage_find_resource(JImageFile* jf, - const char* module_name, - const char* file_name, - jlong &size) { - return ((*JImageFindResource)(jf, module_name, get_jimage_version_string(), file_name, &size)); -} - bool ClassPathImageEntry::is_modules_image() const { assert(this == _singleton, "VM supports a single jimage"); - assert(this == (ClassPathImageEntry*)ClassLoader::get_jrt_entry(), "must be used for jrt entry"); + assert(this == ClassLoader::get_jrt_entry(), "must be used for jrt entry"); return true; } @@ -618,14 +663,15 @@ void ClassLoader::setup_bootstrap_search_path_impl(JavaThread* current, const ch struct stat st; if (os::stat(path, &st) == 0) { // Directory found - if (JImage_file != nullptr) { + if (jimage_is_open()) { assert(Arguments::has_jimage(), "sanity check"); const char* canonical_path = get_canonical_path(path, current); assert(canonical_path != nullptr, "canonical_path issue"); - _jrt_entry = new ClassPathImageEntry(JImage_file, canonical_path); + // Hand over lifecycle control of the JImage file to the _jrt_entry singleton + // (see ClassPathImageEntry::close_jimage). The image must be initialized by now. + _jrt_entry = new ClassPathImageEntry(canonical_path); assert(_jrt_entry != nullptr && _jrt_entry->is_modules_image(), "No java runtime image present"); - assert(_jrt_entry->jimage() != nullptr, "No java runtime image"); } // else it's an exploded build. } else { // If path does not exist, exit @@ -645,7 +691,7 @@ void ClassLoader::setup_bootstrap_search_path_impl(JavaThread* current, const ch static const char* get_exploded_module_path(const char* module_name, bool c_heap) { const char *home = Arguments::get_java_home(); const char file_sep = os::file_separator()[0]; - // 10 represents the length of "modules" + 2 file separators + \0 + // 10 represents the length of "modules" (7) + 2 file separators + \0 size_t len = strlen(home) + strlen(module_name) + 10; char *path = c_heap ? NEW_C_HEAP_ARRAY(char, len, mtModule) : NEW_RESOURCE_ARRAY(char, len); jio_snprintf(path, len, "%s%cmodules%c%s", home, file_sep, file_sep, module_name); @@ -1398,20 +1444,8 @@ void ClassLoader::initialize(TRAPS) { setup_bootstrap_search_path(THREAD); } -static char* lookup_vm_resource(JImageFile *jimage, const char *jimage_version, const char *path) { - jlong size; - JImageLocationRef location = (*JImageFindResource)(jimage, "java.base", jimage_version, path, &size); - if (location == 0) - return nullptr; - char *val = NEW_C_HEAP_ARRAY(char, size+1, mtClass); - (*JImageGetResource)(jimage, location, val, size); - val[size] = '\0'; - return val; -} - // Lookup VM options embedded in the modules jimage file char* ClassLoader::lookup_vm_options() { - jint error; char modules_path[JVM_MAXPATHLEN]; const char* fileSep = os::file_separator(); @@ -1419,32 +1453,41 @@ char* ClassLoader::lookup_vm_options() { load_jimage_library(); jio_snprintf(modules_path, JVM_MAXPATHLEN, "%s%slib%smodules", Arguments::get_java_home(), fileSep, fileSep); - JImage_file =(*JImageOpen)(modules_path, &error); - if (JImage_file == nullptr) { - if (Arguments::has_jimage()) { - // The modules file exists but is unreadable or corrupt - vm_exit_during_initialization(err_msg("Unable to load %s", modules_path)); + if (jimage_open(modules_path)) { + // Special case where we lookup the options string *before* set_preview_mode() is called. + // Since VM arguments have not been parsed, and the ClassPathImageEntry singleton + // has not been created yet, we access the JImage file directly in non-preview mode. + jlong size; + JImageLocationRef location = + jimage_find_resource(JAVA_BASE_NAME, "jdk/internal/vm/options", /* is_preview */ false, &size); + if (location != 0) { + char* options = NEW_C_HEAP_ARRAY(char, size+1, mtClass); + (*JImageGetResource)(jimage_non_null(), location, options, size); + options[size] = '\0'; + return options; } - return nullptr; } + return nullptr; +} - const char *jimage_version = get_jimage_version_string(); - char *options = lookup_vm_resource(JImage_file, jimage_version, "jdk/internal/vm/options"); - return options; +// Finishes initializing the JImageFile (if present) by setting the access mode. +void ClassLoader::set_preview_mode(bool enable_preview) { + assert(Preview_mode == PREVIEW_MODE_UNINITIALIZED, "set_preview_mode must not be called twice"); + Preview_mode = enable_preview ? PREVIEW_MODE_ENABLE_PREVIEW : PREVIEW_MODE_DEFAULT; } bool ClassLoader::is_module_observable(const char* module_name) { assert(JImageOpen != nullptr, "jimage library should have been opened"); - if (JImage_file == nullptr) { + if (!jimage_is_open()) { struct stat st; const char *path = get_exploded_module_path(module_name, true); bool res = os::stat(path, &st) == 0; FREE_C_HEAP_ARRAY(path); return res; } + // We don't expect preview mode (i.e. --enable-preview) to affect module visibility. jlong size; - const char *jimage_version = get_jimage_version_string(); - return (*JImageFindResource)(JImage_file, module_name, jimage_version, "module-info.class", &size) != 0; + return jimage_find_resource(module_name, "module-info.class", /* is_preview */ false, &size) != 0; } jlong ClassLoader::classloader_time_ms() { diff --git a/src/hotspot/share/classfile/classLoader.hpp b/src/hotspot/share/classfile/classLoader.hpp index a935d3027ac2..ff7e89996889 100644 --- a/src/hotspot/share/classfile/classLoader.hpp +++ b/src/hotspot/share/classfile/classLoader.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -99,7 +99,8 @@ class ClassPathZipEntry: public ClassPathEntry { }; -// For java image files +// A singleton path entry which takes ownership of the initialized JImageFile +// reference. Not used for exploded builds. class ClassPathImageEntry: public ClassPathEntry { private: const char* _name; @@ -107,11 +108,12 @@ class ClassPathImageEntry: public ClassPathEntry { public: bool is_modules_image() const; const char* name() const { return _name == nullptr ? "" : _name; } - JImageFile* jimage() const; - JImageFile* jimage_non_null() const; + // Called to close the JImage during os::abort (normally not called). void close_jimage(); - ClassPathImageEntry(JImageFile* jimage, const char* name); + // Takes effective ownership of the static JImageFile pointer. + ClassPathImageEntry(const char* name); virtual ~ClassPathImageEntry() { ShouldNotReachHere(); } + ClassFileStream* open_stream(JavaThread* current, const char* name); ClassFileStream* open_stream_for_loader(JavaThread* current, const char* name, ClassLoaderData* loader_data); }; @@ -201,10 +203,10 @@ class ClassLoader: AllStatic { static GrowableArray* _patch_mod_entries; // 2. the base piece - // Contains the ClassPathEntry of the modular java runtime image. + // Contains the ClassPathImageEntry of the modular java runtime image. // If no java runtime image is present, this indicates a // build with exploded modules is being used instead. - static ClassPathEntry* _jrt_entry; + static ClassPathImageEntry* _jrt_entry; static GrowableArray* _exploded_entries; enum { EXPLODED_ENTRY_SIZE = 80 }; // Initial number of exploded modules @@ -354,15 +356,20 @@ class ClassLoader: AllStatic { static void append_boot_classpath(ClassPathEntry* new_entry); #endif + // Retrieves additional VM options prior to flags processing. Options held + // in the JImage file are retrieved without fully initializing it. (this is + // the only JImage lookup which can succeed before init_jimage() is called). static char* lookup_vm_options(); + // Called once, after all flags are processed, to finish initializing the + // JImage file. Until this is called, jimage_find_resource(), and any other + // JImage resource lookups or access will fail. + static void set_preview_mode(bool enable_preview); + // Determines if the named module is present in the // modules jimage file or in the exploded modules directory. static bool is_module_observable(const char* module_name); - static JImageLocationRef jimage_find_resource(JImageFile* jf, const char* module_name, - const char* file_name, jlong &size); - static void trace_class_path(const char* msg, const char* name = nullptr); // VM monitoring and management support diff --git a/src/hotspot/share/code/codeCache.cpp b/src/hotspot/share/code/codeCache.cpp index 2aaa061dca3a..ffa88a88b296 100644 --- a/src/hotspot/share/code/codeCache.cpp +++ b/src/hotspot/share/code/codeCache.cpp @@ -65,6 +65,7 @@ #include "sanitizers/leak.hpp" #include "services/memoryService.hpp" #include "utilities/align.hpp" +#include "utilities/integerCast.hpp" #include "utilities/vmError.hpp" #include "utilities/xmlstream.hpp" #ifdef COMPILER1 @@ -228,9 +229,14 @@ void CodeCache::initialize_heaps() { assert(heap_available(CodeBlobType::MethodNonProfiled), "MethodNonProfiled heap is always available for segmented code heap"); - size_t compiler_buffer_size = 0; - COMPILER1_PRESENT(compiler_buffer_size += CompilationPolicy::c1_count() * Compiler::code_buffer_size()); - COMPILER2_PRESENT(compiler_buffer_size += CompilationPolicy::c2_count() * C2Compiler::initial_code_buffer_size()); + uint64_t compiler_buffer_size_uint64 = 0; + COMPILER1_PRESENT(compiler_buffer_size_uint64 += (uint64_t)CompilationPolicy::c1_count() * Compiler::code_buffer_size()); + COMPILER2_PRESENT(compiler_buffer_size_uint64 += (uint64_t)CompilationPolicy::c2_count() * C2Compiler::initial_code_buffer_size()); + if (compiler_buffer_size_uint64 > (uint64_t)CODE_CACHE_SIZE_LIMIT) { + err_msg msg("CICompilerCount is too large (%" PRIdPTR "): compiler buffer size exceeds the CodeCache size limit", CICompilerCount); + vm_exit_during_initialization(msg); + } + size_t compiler_buffer_size = integer_cast_permit_tautology(compiler_buffer_size_uint64); if (!non_nmethod.set) { non_nmethod.size += compiler_buffer_size; diff --git a/src/hotspot/share/compiler/compilationPolicy.cpp b/src/hotspot/share/compiler/compilationPolicy.cpp index 1cc44602186b..e69480560aeb 100644 --- a/src/hotspot/share/compiler/compilationPolicy.cpp +++ b/src/hotspot/share/compiler/compilationPolicy.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2010, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -814,23 +814,32 @@ CompileTask* CompilationPolicy::select_task(CompileQueue* compile_queue, JavaThr max_method = max_task->method(); } - methodHandle max_method_h(THREAD, max_method); + if (max_task != nullptr && max_method != nullptr) { + methodHandle max_method_h(THREAD, max_method); - if (max_task != nullptr && max_task->comp_level() == CompLevel_full_profile && TieredStopAtLevel > CompLevel_full_profile && - max_method != nullptr && is_method_profiled(max_method_h) && !Arguments::is_compiler_only()) { - max_task->set_comp_level(CompLevel_limited_profile); + if (max_task->comp_level() == CompLevel_full_profile && TieredStopAtLevel > CompLevel_full_profile && + is_method_profiled(max_method_h) && !Arguments::is_compiler_only()) { - if (CompileBroker::compilation_is_complete(max_method_h, max_task->osr_bci(), CompLevel_limited_profile)) { - if (PrintTieredEvents) { - print_event(REMOVE_FROM_QUEUE, max_method, max_method, max_task->osr_bci(), (CompLevel)max_task->comp_level()); - } - compile_queue->remove_and_mark_stale(max_task); - max_method->clear_queued_for_compilation(); - return nullptr; - } + CompilerDirectiveMatcher directive_matcher(max_method_h, CompLevel_limited_profile); + bool exclude_limited_profile = directive_matcher.directive_set()->ExcludeOption; - if (PrintTieredEvents) { - print_event(UPDATE_IN_QUEUE, max_method, max_method, max_task->osr_bci(), (CompLevel)max_task->comp_level()); + if (!exclude_limited_profile) { + max_task->set_comp_level(CompLevel_limited_profile); + max_task->transfer_directive(directive_matcher); + + if (CompileBroker::compilation_is_complete(max_method_h, max_task->osr_bci(), CompLevel_limited_profile)) { + if (PrintTieredEvents) { + print_event(REMOVE_FROM_QUEUE, max_method, max_method, max_task->osr_bci(), (CompLevel)max_task->comp_level()); + } + compile_queue->remove_and_mark_stale(max_task); + max_method->clear_queued_for_compilation(); + return nullptr; + } + + if (PrintTieredEvents) { + print_event(UPDATE_IN_QUEUE, max_method, max_method, max_task->osr_bci(), (CompLevel)max_task->comp_level()); + } + } } } return max_task; diff --git a/src/hotspot/share/compiler/compileBroker.cpp b/src/hotspot/share/compiler/compileBroker.cpp index c806d356d0cb..ddd3f8ae5f8c 100644 --- a/src/hotspot/share/compiler/compileBroker.cpp +++ b/src/hotspot/share/compiler/compileBroker.cpp @@ -1382,7 +1382,7 @@ nmethod* CompileBroker::compile_method(const methodHandle& method, int osr_bci, } #endif - CompilerDirectiveMatcher matcher(method, comp); + CompilerDirectiveMatcher matcher(method, comp_level); // CompileBroker::compile_method can trap and can have pending async exception. nmethod* nm = CompileBroker::compile_method(method, osr_bci, comp_level, hot_count, compile_reason, matcher.directive_set(), THREAD); return nm; diff --git a/src/hotspot/share/compiler/compileTask.cpp b/src/hotspot/share/compiler/compileTask.cpp index 193770b66a0e..b22aa4466a47 100644 --- a/src/hotspot/share/compiler/compileTask.cpp +++ b/src/hotspot/share/compiler/compileTask.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -57,7 +57,7 @@ CompileTask::CompileTask(int compile_id, _nm_insts_size(0), _comp_level(comp_level), _compiler(CompileBroker::compiler(comp_level)), - _comp_directive_matcher(method, _compiler), + _comp_directive_matcher(method, static_cast(comp_level)), JVMCI_ONLY(_has_waiter(_compiler->is_jvmci()) COMMA) JVMCI_ONLY(_blocking_jvmci_compile_state(nullptr) COMMA) _num_inlined_bytecodes(0), diff --git a/src/hotspot/share/compiler/compileTask.hpp b/src/hotspot/share/compiler/compileTask.hpp index 4b48ee63be50..b6174af72eb4 100644 --- a/src/hotspot/share/compiler/compileTask.hpp +++ b/src/hotspot/share/compiler/compileTask.hpp @@ -131,6 +131,7 @@ class CompileTask : public CHeapObj { bool is_blocking() const { return _is_blocking; } bool is_success() const { return _is_success; } DirectiveSet* directive() const { return _comp_directive_matcher.directive_set(); } + void transfer_directive(CompilerDirectiveMatcher& matcher) { _comp_directive_matcher.transfer_from(matcher); } CompileReason compile_reason() const { return _compile_reason; } CodeSection::csize_t nm_content_size() { return _nm_content_size; } void set_nm_content_size(CodeSection::csize_t size) { _nm_content_size = size; } diff --git a/src/hotspot/share/compiler/compilerDirectives.cpp b/src/hotspot/share/compiler/compilerDirectives.cpp index d0042d0e16cd..f61aa111e654 100644 --- a/src/hotspot/share/compiler/compilerDirectives.cpp +++ b/src/hotspot/share/compiler/compilerDirectives.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,6 +25,7 @@ #include "ci/ciMethod.hpp" #include "ci/ciUtilities.inline.hpp" #include "compiler/abstractCompiler.hpp" +#include "compiler/compileBroker.hpp" #include "compiler/compilerDefinitions.inline.hpp" #include "compiler/compilerDirectives.hpp" #include "compiler/compilerOracle.hpp" @@ -378,7 +379,7 @@ class DirectiveSetPtr { // - if some option is changed we need to copy directiveset since it no longer can be shared // - Need to free copy after use // - Requires a modified bit so we don't overwrite options that is set by directives -DirectiveSet* DirectiveSet::compilecommand_compatibility_init(const methodHandle& method) { +DirectiveSet* DirectiveSet::compilecommand_compatibility_init(const methodHandle& method, int comp_level) { // Early bail out - checking all options is expensive - we rely on them not being used // Only set a flag if it has not been modified and value changes. // Only copy set if a flag needs to be set @@ -397,7 +398,7 @@ DirectiveSet* DirectiveSet::compilecommand_compatibility_init(const methodHandle // All CompileCommands are not equal so this gets a bit verbose // When CompileCommands have been refactored less clutter will remain. - if (CompilerOracle::should_break_at(method)) { + if (CompilerOracle::should_break_at(method, static_cast(comp_level))) { // If the directives didn't have 'BreakAtCompile' or 'BreakAtExecute', // the sub-command 'Break' of the 'CompileCommand' would become effective. if (!_modified[BreakAtCompileIndex]) { @@ -414,13 +415,13 @@ DirectiveSet* DirectiveSet::compilecommand_compatibility_init(const methodHandle } } - if (CompilerOracle::should_print(method)) { + if (CompilerOracle::should_print(method, static_cast(comp_level))) { if (!_modified[PrintAssemblyIndex]) { set.cloned()->PrintAssemblyOption = true; } } // Exclude as in should not compile == Enabled - if (CompilerOracle::should_exclude(method)) { + if (CompilerOracle::should_exclude(method, static_cast(comp_level))) { if (!_modified[ExcludeIndex]) { set.cloned()->ExcludeOption = true; } @@ -547,7 +548,7 @@ bool DirectiveSet::should_inline(ciMethod* inlinee) { return false; } -bool DirectiveSet::should_not_inline(ciMethod* inlinee) { +bool DirectiveSet::should_not_inline(ciMethod* inlinee, int comp_level) { inlinee->check_is_loaded(); VM_ENTRY_MARK; methodHandle mh(THREAD, inlinee->get_Method()); @@ -556,7 +557,7 @@ bool DirectiveSet::should_not_inline(ciMethod* inlinee) { return matches_inline(mh, InlineMatcher::dont_inline); } if (!CompilerDirectivesIgnoreCompileCommandsOption) { - return CompilerOracle::should_not_inline(mh); + return CompilerOracle::should_not_inline(mh, static_cast(comp_level)); } return false; } @@ -755,7 +756,7 @@ void DirectivesStack::release(DirectiveSet* set) { assert(set != nullptr, "Never nullptr"); MutexLocker locker(DirectivesStack_lock, Mutex::_no_safepoint_check_flag); if (set->is_exclusive_copy()) { - // Old CompilecCmmands forced us to create an exclusive copy + // Old CompileCommands forced us to create an exclusive copy delete set; } else { assert(set->directive() != nullptr, "Never nullptr"); @@ -772,8 +773,9 @@ void DirectivesStack::release(CompilerDirectives* dir) { } } -DirectiveSet* DirectivesStack::getMatchingDirective(const methodHandle& method, AbstractCompiler *comp) { +DirectiveSet* DirectivesStack::getMatchingDirective(const methodHandle& method, int comp_level) { assert(_depth > 0, "Must never be empty"); + AbstractCompiler* comp = CompileBroker::compiler(comp_level); DirectiveSet* match = nullptr; { @@ -798,5 +800,5 @@ DirectiveSet* DirectivesStack::getMatchingDirective(const methodHandle& method, guarantee(match != nullptr, "There should always be a default directive that matches"); // Check for legacy compile commands update, without DirectivesStack_lock - return match->compilecommand_compatibility_init(method); + return match->compilecommand_compatibility_init(method, comp_level); } diff --git a/src/hotspot/share/compiler/compilerDirectives.hpp b/src/hotspot/share/compiler/compilerDirectives.hpp index 04873aab664b..ae814cdc491a 100644 --- a/src/hotspot/share/compiler/compilerDirectives.hpp +++ b/src/hotspot/share/compiler/compilerDirectives.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -115,7 +115,7 @@ class DirectivesStack : AllStatic { static int _depth; static void pop_inner(); // no lock version of pop - static DirectiveSet* getMatchingDirective(const methodHandle& mh, AbstractCompiler* comp); + static DirectiveSet* getMatchingDirective(const methodHandle& mh, int comp_level); static DirectiveSet* getDefaultDirective(AbstractCompiler* comp); static void release(DirectiveSet* set); static void release(CompilerDirectives* dir); @@ -145,10 +145,10 @@ class DirectiveSet : public CHeapObj { bool parse_and_add_inline(char* str, const char*& error_msg); void append_inline(InlineMatcher* m); bool should_inline(ciMethod* inlinee); - bool should_not_inline(ciMethod* inlinee); + bool should_not_inline(ciMethod* inlinee, int comp_level); bool should_delay_inline(ciMethod* inlinee); void print_inline(outputStream* st); - DirectiveSet* compilecommand_compatibility_init(const methodHandle& method); + DirectiveSet* compilecommand_compatibility_init(const methodHandle& method, int comp_level); bool is_exclusive_copy() { return _directive == nullptr; } bool matches_inline(const methodHandle& method, int inline_action); static DirectiveSet* clone(DirectiveSet const* src); @@ -335,21 +335,35 @@ class CompilerDirectives : public CHeapObj { class CompilerDirectiveMatcher { private: DirectiveSet* _match; + + void release_match() { + if (_match != nullptr) { + DirectivesStack::release(_match); + _match = nullptr; + } + } + public: // Use this constructor to get default directive CompilerDirectiveMatcher(AbstractCompiler* comp) { _match = DirectivesStack::getDefaultDirective(comp); } - CompilerDirectiveMatcher(const methodHandle& mh, AbstractCompiler* comp) { - _match = DirectivesStack::getMatchingDirective(mh, comp); + CompilerDirectiveMatcher(const methodHandle& mh, int comp_level) { + _match = DirectivesStack::getMatchingDirective(mh, comp_level); } ~CompilerDirectiveMatcher() { - DirectivesStack::release(_match); + release_match(); } DirectiveSet* directive_set() const { return _match; } + + void transfer_from(CompilerDirectiveMatcher& src) { + release_match(); + _match = src._match; + src._match = nullptr; + } }; #endif // SHARE_COMPILER_COMPILERDIRECTIVES_HPP diff --git a/src/hotspot/share/compiler/compilerOracle.cpp b/src/hotspot/share/compiler/compilerOracle.cpp index 5bcd01a4d09e..241d27eb6be8 100644 --- a/src/hotspot/share/compiler/compilerOracle.cpp +++ b/src/hotspot/share/compiler/compilerOracle.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -60,6 +60,37 @@ static const char* const default_compile_commands[] = { #endif nullptr }; +// CompLevel | -XX:CompileCommand bitmask +// ---------------------------------------------------- +// 0 (interpreter) | N/A +// 1 (C1) | 1 +// 2 (C1 + counters) | 10 +// 3 (C1 + counters + mdo) | 100 +// 4 (C2/JVMCI) | 1000 +// All C1 levels | 111 +// All levels | 1111 + +static const int comp_level_bitmask[CompLevel_count] = {0, 1, 10, 100, 1000}; +static const int comp_level_bitmask_all_levels = 1111; +static const intx default_comp_level_argument = comp_level_bitmask_all_levels; + +inline bool bitmask_applies_to_comp_level(int bitmask, int comp_level) { + assert(comp_level > CompLevel_none && comp_level < CompLevel_count, "CompLevel out of bounds"); + return (bitmask / comp_level_bitmask[comp_level]) % 10 == 1; +} + +static bool is_valid_comp_level_bitmask(intx bitmask) { + if (bitmask < 0 || bitmask > comp_level_bitmask_all_levels) { + return false; + } + for (; bitmask != 0; bitmask /= 10) { + if (bitmask % 10 > 1) { + return false; + } + } + return true; +} + static const char* optiontype_names[] = { #define enum_of_types(type, name) name, OPTION_TYPES(enum_of_types) @@ -456,36 +487,56 @@ template bool CompilerOracle::option_matches_type(CompileCommandEnum optio template bool CompilerOracle::option_matches_type(CompileCommandEnum option, ccstr& value); template bool CompilerOracle::option_matches_type(CompileCommandEnum option, double& value); +bool CompilerOracle::applies_to_comp_level(const methodHandle& method, CompileCommandEnum command, CompLevel current_level) { + if (current_level == CompLevel_none) { + return false; + } + + intx bitmask = 0; + if (!has_option_value(method, command, bitmask)) { + return false; + } + + // Since we don't have bitmask for interpreter level (0), but still need to call CompilerOracle::should_print() + // from collect_profiled_methods() in java.cpp, a special value of CompLevel_any produces a match with any bitmask, even 0 + return current_level == CompLevel_any + || bitmask_applies_to_comp_level(bitmask, current_level); +} + bool CompilerOracle::has_option(const methodHandle& method, CompileCommandEnum option) { bool value = false; has_option_value(method, option, value); return value; } -bool CompilerOracle::should_exclude(const methodHandle& method) { - if (check_predicate(CompileCommandEnum::Exclude, method)) { +bool CompilerOracle::should_exclude(const methodHandle& method, const CompLevel level) { + if (has_exclude(method, level)) { return true; } if (has_command(CompileCommandEnum::CompileOnly)) { - return !check_predicate(CompileCommandEnum::CompileOnly, method); + return !applies_to_comp_level(method, CompileCommandEnum::CompileOnly, level); } return false; } +bool CompilerOracle::has_exclude(const methodHandle& method, const CompLevel level) { + return applies_to_comp_level(method, CompileCommandEnum::Exclude, level); +} + bool CompilerOracle::should_inline(const methodHandle& method) { return (check_predicate(CompileCommandEnum::Inline, method)); } -bool CompilerOracle::should_not_inline(const methodHandle& method) { - return check_predicate(CompileCommandEnum::DontInline, method) || check_predicate(CompileCommandEnum::Exclude, method); +bool CompilerOracle::should_not_inline(const methodHandle& method, const CompLevel level) { + return check_predicate(CompileCommandEnum::DontInline, method) || has_exclude(method, level); } bool CompilerOracle::should_delay_inline(const methodHandle& method) { return (check_predicate(CompileCommandEnum::DelayInline, method)); } -bool CompilerOracle::should_print(const methodHandle& method) { - return check_predicate(CompileCommandEnum::Print, method); +bool CompilerOracle::should_print(const methodHandle& method, const CompLevel level) { + return applies_to_comp_level(method, CompileCommandEnum::Print, level); } bool CompilerOracle::should_print_methods() { @@ -505,8 +556,8 @@ bool CompilerOracle::should_log(const methodHandle& method) { return (check_predicate(CompileCommandEnum::Log, method)); } -bool CompilerOracle::should_break_at(const methodHandle& method) { - return check_predicate(CompileCommandEnum::Break, method); +bool CompilerOracle::should_break_at(const methodHandle& method, const CompLevel level) { + return applies_to_comp_level(method, CompileCommandEnum::Break, level); } void CompilerOracle::tag_blackhole_if_possible(const methodHandle& method) { @@ -678,6 +729,19 @@ static void usage() { tty->print_cr("from inlining, whereas the 'compileonly' command only excludes methods from"); tty->print_cr("top-level compilations (i.e. they can still be inlined into other compilation units)."); tty->cr(); + tty->print_cr("Compilation levels can be specified in the 'compileonly', 'exclude', 'print',"); + tty->print_cr("and 'break' commands using a binary bitmask as an optional value:"); + tty->print_cr(" -XX:CompileCommand=exclude,java/*.*,1011 -XX:CompileCommand=print,java/*.*,100"); + tty->cr(); + tty->print_cr("The bitmask is calculated by summing the desired compilation level values:"); + tty->print_cr(" C1 without profiling = 1"); + tty->print_cr(" C1 with limited profiling = 10"); + tty->print_cr(" C1 with full profiling = 100"); + tty->print_cr(" C2 = 1000"); + tty->cr(); + tty->print_cr("Note: Excluding specific compilation levels may disrupt normal state transitions"); + tty->print_cr("between the levels, as the VM will not automatically work around the excluded ones."); + tty->cr(); }; static int skip_whitespace(char* &line) { @@ -712,7 +776,7 @@ static bool parseMemLimit(const char* line, intx& value, int& bytes_read, char* size_t s = 0; char* end; if (!parse_integer(line, &end, &s)) { - jio_snprintf(errorbuf, buf_size, "MemLimit: invalid value"); + jio_snprintf(errorbuf, buf_size, ": invalid integer: '%.20s'", line); return false; } bytes_read = (int)(end - line); @@ -726,7 +790,7 @@ static bool parseMemLimit(const char* line, intx& value, int& bytes_read, char* // ok, this is the default bytes_read += 5; } else { - jio_snprintf(errorbuf, buf_size, "MemLimit: invalid option"); + jio_snprintf(errorbuf, buf_size, ": invalid suffix: '%.6s'", end); return false; } } @@ -751,7 +815,7 @@ static bool parseMemStat(const char* line, uintx& value, int& bytes_read, char* }); #undef IF_ENUM_STRING - jio_snprintf(errorbuf, buf_size, "MemStat: invalid option"); + jio_snprintf(errorbuf, buf_size, ": invalid option: '%.8s'", line); return false; } @@ -763,21 +827,42 @@ static bool scan_value(enum OptionType type, char* line, int& total_bytes_read, const char* type_str = optiontype2name(type); int skipped = skip_whitespace(line); total_bytes_read += skipped; + char parse_error_buf[80] = {}; + if (type == OptionType::Intx) { intx value; bool success = false; - if (option == CompileCommandEnum::MemLimit) { - // Special parsing for MemLimit - success = parseMemLimit(line, value, bytes_read, errorbuf, buf_size); - } else { - // Is it a raw number? - success = sscanf(line, "%zd%n", &value, &bytes_read) == 1; + switch (option) { + case CompileCommandEnum::MemLimit: + // Special parsing for MemLimit + success = parseMemLimit(line, value, bytes_read, parse_error_buf, sizeof(parse_error_buf)); + break; + case CompileCommandEnum::Break: + case CompileCommandEnum::CompileOnly: + case CompileCommandEnum::Exclude: + case CompileCommandEnum::Print: + // In the commands above the parameter used to be a boolean. Now it is an int (a compilation level mask). + // For compatibility with previous versions we keep it optional. If user did not specify the mask, assume default value + if (*line == '\0') { + value = default_comp_level_argument; + success = true; + } else { + success = sscanf(line, "%zd%n", &value, &bytes_read) == 1; + if (success && !is_valid_comp_level_bitmask(value)) { + jio_snprintf(parse_error_buf, sizeof(parse_error_buf), ": invalid compilation level bitmask '%.*s'", bytes_read, line); + success = false; + } + } + break; + default: + // Is it a raw number? + success = sscanf(line, "%zd%n", &value, &bytes_read) == 1; } if (success) { total_bytes_read += bytes_read; return register_command(matcher, option, errorbuf, buf_size, value); } else { - jio_snprintf(errorbuf, buf_size, "Value cannot be read for option '%s' of type '%s'", ccname, type_str); + jio_snprintf(errorbuf, buf_size, "Value cannot be read for option '%s' of type '%s'%s", ccname, type_str, parse_error_buf); return false; } } else if (type == OptionType::Uintx) { @@ -785,7 +870,7 @@ static bool scan_value(enum OptionType type, char* line, int& total_bytes_read, bool success = false; if (option == CompileCommandEnum::MemStat) { // Special parsing for MemStat - success = parseMemStat(line, value, bytes_read, errorbuf, buf_size); + success = parseMemStat(line, value, bytes_read, parse_error_buf, sizeof(parse_error_buf)); } else { // parse as raw number success = sscanf(line, "%zu%n", &value, &bytes_read) == 1; @@ -794,7 +879,7 @@ static bool scan_value(enum OptionType type, char* line, int& total_bytes_read, total_bytes_read += bytes_read; return register_command(matcher, option, errorbuf, buf_size, value); } else { - jio_snprintf(errorbuf, buf_size, "Value cannot be read for option '%s' of type '%s'", ccname, type_str); + jio_snprintf(errorbuf, buf_size, "Value cannot be read for option '%s' of type '%s'%s", ccname, type_str, parse_error_buf); return false; } } else if (type == OptionType::Ccstr) { @@ -1089,17 +1174,25 @@ bool CompilerOracle::parse_from_line(char* line) { return false; } return true; - } else if (option == CompileCommandEnum::MemStat) { - // MemStat default action is to collect data but to not print - if (!register_command(matcher, option, error_buf, sizeof(error_buf), (uintx)MemStatAction::collect)) { + } + + switch (option) { + case CompileCommandEnum::Break: + case CompileCommandEnum::CompileOnly: + case CompileCommandEnum::Exclude: + case CompileCommandEnum::Print: + break; + case CompileCommandEnum::MemStat: + // MemStat default action is to collect data but to not print + if (!register_command(matcher, option, error_buf, sizeof(error_buf), (uintx)MemStatAction::collect)) { + print_parse_error(error_buf, original.get()); + return false; + } + return true; + default: + jio_snprintf(error_buf, sizeof(error_buf), " Option '%s' is not followed by a value", option2name(option)); print_parse_error(error_buf, original.get()); return false; - } - return true; - } else { - jio_snprintf(error_buf, sizeof(error_buf), " Option '%s' is not followed by a value", option2name(option)); - print_parse_error(error_buf, original.get()); - return false; } } if (!scan_value(type, line, bytes_read, matcher, option, error_buf, sizeof(error_buf))) { @@ -1209,7 +1302,7 @@ bool CompilerOracle::parse_compile_only(char* line) { if (method_pattern != nullptr) { TypedMethodOptionMatcher* matcher = TypedMethodOptionMatcher::parse_method_pattern(method_pattern, error_buf, sizeof(error_buf)); if (matcher != nullptr) { - if (register_command(matcher, CompileCommandEnum::CompileOnly, error_buf, sizeof(error_buf), true)) { + if (register_command(matcher, CompileCommandEnum::CompileOnly, error_buf, sizeof(error_buf), default_comp_level_argument)) { continue; } } diff --git a/src/hotspot/share/compiler/compilerOracle.hpp b/src/hotspot/share/compiler/compilerOracle.hpp index 5615a2cf1fcd..bfed52f12e7b 100644 --- a/src/hotspot/share/compiler/compilerOracle.hpp +++ b/src/hotspot/share/compiler/compilerOracle.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,6 +25,7 @@ #ifndef SHARE_COMPILER_COMPILERORACLE_HPP #define SHARE_COMPILER_COMPILERORACLE_HPP +#include "compiler/compilerDirectives.hpp" #include "memory/allStatic.hpp" #include "oops/oopsHierarchy.hpp" #include "utilities/istream.hpp" @@ -49,14 +50,14 @@ class methodHandle; option(Help, "help", Unknown) \ option(Quiet, "quiet", Unknown) \ option(Log, "log", Bool) \ - option(Print, "print", Bool) \ + option(Print, "print", Intx) \ option(Inline, "inline", Bool) \ option(DelayInline, "delayinline", Bool) \ option(DontInline, "dontinline", Bool) \ option(Blackhole, "blackhole", Bool) \ - option(CompileOnly, "compileonly", Bool)\ - option(Exclude, "exclude", Bool) \ - option(Break, "break", Bool) \ + option(CompileOnly, "compileonly", Intx) \ + option(Exclude, "exclude", Intx) \ + option(Break, "break", Intx) \ option(BreakAtExecute, "BreakAtExecute", Bool) \ option(BreakAtCompile, "BreakAtCompile", Bool) \ option(MemLimit, "MemLimit", Intx) \ @@ -135,6 +136,9 @@ class CompilerOracle : AllStatic { static bool parse_from_input(inputStream::Input* input, parse_from_line_fn_t* parse_from_line); + static bool has_exclude(const methodHandle& method, CompLevel level); + static bool applies_to_comp_level(const methodHandle& method, CompileCommandEnum command, CompLevel current_level); + public: // True if the command file has been specified or is implicit static bool has_command_file(); @@ -143,14 +147,15 @@ class CompilerOracle : AllStatic { static bool parse_from_file(); // Tells whether we to exclude compilation of method - static bool should_exclude(const methodHandle& method); + static bool should_exclude(const methodHandle & method, CompLevel level); + static bool be_quiet() { return _quiet; } // Tells whether we want to inline this method static bool should_inline(const methodHandle& method); // Tells whether we want to disallow inlining of this method - static bool should_not_inline(const methodHandle& method); + static bool should_not_inline(const methodHandle& method, CompLevel level); // Tells whether we want to delay inlining of this method static bool should_delay_inline(const methodHandle& method); @@ -159,13 +164,14 @@ class CompilerOracle : AllStatic { static bool changes_current_thread(const methodHandle& method); // Tells whether we should print the assembly for this method - static bool should_print(const methodHandle& method); + // If level == CompLevel_none or CompLevel_any, returns true if there is a print command with any mask + static bool should_print(const methodHandle& method, CompLevel level); // Tells whether we should log the compilation data for this method static bool should_log(const methodHandle& method); // Tells whether to break when compiling method - static bool should_break_at(const methodHandle& method); + static bool should_break_at(const methodHandle& method, CompLevel level); // Tells whether there are any methods to print for print_method_statistics() static bool should_print_methods(); diff --git a/src/hotspot/share/gc/g1/g1ConcurrentMarkThread.inline.hpp b/src/hotspot/share/gc/g1/g1ConcurrentMarkThread.inline.hpp index 8cb7881e000a..64441ccac658 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentMarkThread.inline.hpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentMarkThread.inline.hpp @@ -32,7 +32,7 @@ // Total virtual time so far. inline double G1ConcurrentMarkThread::total_mark_cpu_time_s() { - return static_cast(os::thread_cpu_time(this)) + worker_threads_cpu_time_s(); + return static_cast(os::thread_cpu_time(this)) / NANOSECS_PER_SEC + worker_threads_cpu_time_s(); } // Marking virtual time so far diff --git a/src/hotspot/share/gc/g1/g1ConcurrentRefineStats.cpp b/src/hotspot/share/gc/g1/g1ConcurrentRefineStats.cpp index 7da0066e2f16..4fa32b388bd6 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentRefineStats.cpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentRefineStats.cpp @@ -25,18 +25,15 @@ #include "gc/g1/g1ConcurrentRefineStats.inline.hpp" #include "runtime/timer.hpp" -void G1ConcurrentRefineStats::add_atomic(G1ConcurrentRefineStats* other) { - _sweep_duration.add_then_fetch(other->_sweep_duration.load_relaxed(), memory_order_relaxed); - _yield_during_sweep_duration.add_then_fetch(other->yield_during_sweep_duration(), memory_order_relaxed); +void G1ConcurrentRefineStats::add_atomic(const G1LocalRefineStats* other) { + _cards_scanned.add_then_fetch(other->_cards_scanned, memory_order_relaxed); + _cards_clean.add_then_fetch(other->_cards_clean, memory_order_relaxed); + _cards_not_parsable.add_then_fetch(other->_cards_not_parsable, memory_order_relaxed); + _cards_already_refer_to_cset.add_then_fetch(other->_cards_already_refer_to_cset, memory_order_relaxed); + _cards_refer_to_cset.add_then_fetch(other->_cards_refer_to_cset, memory_order_relaxed); + _cards_no_cross_region.add_then_fetch(other->_cards_no_cross_region, memory_order_relaxed); - _cards_scanned.add_then_fetch(other->cards_scanned(), memory_order_relaxed); - _cards_clean.add_then_fetch(other->cards_clean(), memory_order_relaxed); - _cards_not_parsable.add_then_fetch(other->cards_not_parsable(), memory_order_relaxed); - _cards_already_refer_to_cset.add_then_fetch(other->cards_already_refer_to_cset(), memory_order_relaxed); - _cards_refer_to_cset.add_then_fetch(other->cards_refer_to_cset(), memory_order_relaxed); - _cards_no_cross_region.add_then_fetch(other->cards_no_cross_region(), memory_order_relaxed); - - _refine_duration.add_then_fetch(other->refine_duration(), memory_order_relaxed); + _refine_duration.add_then_fetch(other->_refine_duration, memory_order_relaxed); } void G1ConcurrentRefineStats::reset() { diff --git a/src/hotspot/share/gc/g1/g1ConcurrentRefineStats.hpp b/src/hotspot/share/gc/g1/g1ConcurrentRefineStats.hpp index a91ad0eb2e4f..6f4af71081b2 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentRefineStats.hpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentRefineStats.hpp @@ -29,9 +29,27 @@ #include "runtime/atomic.hpp" #include "utilities/globalDefinitions.hpp" -// Collection of statistics for concurrent refinement processing. -// Used for collecting per-thread statistics and for summaries over a -// collection of threads. +// Thread-local refinement statistics. +struct G1LocalRefineStats { + size_t _cards_scanned; + size_t _cards_clean; + size_t _cards_not_parsable; + size_t _cards_already_refer_to_cset; + size_t _cards_refer_to_cset; + size_t _cards_no_cross_region; + jlong _refine_duration; + + G1LocalRefineStats() : + _cards_scanned(0), + _cards_clean(0), + _cards_not_parsable(0), + _cards_already_refer_to_cset(0), + _cards_refer_to_cset(0), + _cards_no_cross_region(0), + _refine_duration(0) {} +}; + +// Global statistics for concurrent refinement processing. class G1ConcurrentRefineStats : public CHeapObj { Atomic _sweep_duration; // Time spent sweeping the table finding non-clean cards // and refining them. @@ -69,18 +87,10 @@ class G1ConcurrentRefineStats : public CHeapObj { inline size_t cards_to_cset() const; - inline void inc_sweep_time(jlong t); - inline void inc_yield_during_sweep_duration(jlong t); - inline void inc_refine_duration(jlong t); - - inline void inc_cards_scanned(size_t increment); - inline void inc_cards_clean(size_t increment); - inline void inc_cards_not_parsable(); - inline void inc_cards_already_refer_to_cset(); - inline void inc_cards_refer_to_cset(); - inline void inc_cards_no_cross_region(); + void add_atomic(const G1LocalRefineStats* other); - void add_atomic(G1ConcurrentRefineStats* other); + inline void inc_sweep_duration(jlong t); + inline void inc_yield_during_sweep_duration(jlong t); void reset(); }; diff --git a/src/hotspot/share/gc/g1/g1ConcurrentRefineStats.inline.hpp b/src/hotspot/share/gc/g1/g1ConcurrentRefineStats.inline.hpp index e1a296c64948..2ef35caab087 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentRefineStats.inline.hpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentRefineStats.inline.hpp @@ -79,40 +79,12 @@ inline size_t G1ConcurrentRefineStats::cards_to_cset() const { return cards_already_refer_to_cset() + cards_refer_to_cset(); } -inline void G1ConcurrentRefineStats::inc_sweep_time(jlong t) { - _sweep_duration.store_relaxed(_sweep_duration.load_relaxed() + t); +inline void G1ConcurrentRefineStats::inc_sweep_duration(jlong t) { + _sweep_duration.fetch_then_add(t, memory_order_relaxed); } inline void G1ConcurrentRefineStats::inc_yield_during_sweep_duration(jlong t) { - _yield_during_sweep_duration.store_relaxed(yield_during_sweep_duration() + t); -} - -inline void G1ConcurrentRefineStats::inc_refine_duration(jlong t) { - _refine_duration.store_relaxed(refine_duration() + t); -} - -inline void G1ConcurrentRefineStats::inc_cards_scanned(size_t increment) { - _cards_scanned.store_relaxed(cards_scanned() + increment); -} - -inline void G1ConcurrentRefineStats::inc_cards_clean(size_t increment) { - _cards_clean.store_relaxed(cards_clean() + increment); -} - -inline void G1ConcurrentRefineStats::inc_cards_not_parsable() { - _cards_not_parsable.store_relaxed(cards_not_parsable() + 1); -} - -inline void G1ConcurrentRefineStats::inc_cards_already_refer_to_cset() { - _cards_already_refer_to_cset.store_relaxed(cards_already_refer_to_cset() + 1); -} - -inline void G1ConcurrentRefineStats::inc_cards_refer_to_cset() { - _cards_refer_to_cset.store_relaxed(cards_refer_to_cset() + 1); -} - -inline void G1ConcurrentRefineStats::inc_cards_no_cross_region() { - _cards_no_cross_region.store_relaxed(cards_no_cross_region() + 1); + _yield_during_sweep_duration.fetch_then_add(t, memory_order_relaxed); } #endif // SHARE_GC_G1_G1CONCURRENTREFINESTATS_INLINE_HPP diff --git a/src/hotspot/share/gc/g1/g1ConcurrentRefineSweepTask.cpp b/src/hotspot/share/gc/g1/g1ConcurrentRefineSweepTask.cpp index e522163f9806..2f99611bb99d 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentRefineSweepTask.cpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentRefineSweepTask.cpp @@ -60,22 +60,22 @@ class G1RefineRegionClosure : public G1HeapRegionClosure { switch (res) { case G1RemSet::HasRefToCSet: { *dest_card = G1CardTable::g1_to_cset_card; - _refine_stats.inc_cards_refer_to_cset(); + _per_worker_refine_data._cards_refer_to_cset++; break; } case G1RemSet::AlreadyToCSet: { *dest_card = G1CardTable::g1_to_cset_card; - _refine_stats.inc_cards_already_refer_to_cset(); + _per_worker_refine_data._cards_already_refer_to_cset++; break; } case G1RemSet::NoCrossRegion: { - _refine_stats.inc_cards_no_cross_region(); + _per_worker_refine_data._cards_no_cross_region++; break; } case G1RemSet::CouldNotParse: { // Could not refine - redirty with the original value. *dest_card = *source_card; - _refine_stats.inc_cards_not_parsable(); + _per_worker_refine_data._cards_not_parsable++; break; } case G1RemSet::HasRefToOld : break; // Nothing special to do. @@ -92,7 +92,7 @@ class G1RefineRegionClosure : public G1HeapRegionClosure { public: bool _completed; - G1ConcurrentRefineStats _refine_stats; + G1LocalRefineStats _per_worker_refine_data; G1RefineRegionClosure(uint worker_id, G1CardTableClaimTable* scan_state) : G1HeapRegionClosure(), @@ -100,7 +100,7 @@ class G1RefineRegionClosure : public G1HeapRegionClosure { _scan_state(scan_state), _worker_id(worker_id), _completed(true), - _refine_stats() { } + _per_worker_refine_data() { } bool do_heap_region(G1HeapRegion* r) override { @@ -141,7 +141,7 @@ class G1RefineRegionClosure : public G1HeapRegionClosure { do_claimed_block(dirty_l, dirty_r, dest_card + pointer_delta(dirty_l, start_card, sizeof(CardValue))); num_dirty_cards += pointer_delta(dirty_r, dirty_l, sizeof(CardValue)); - _refine_stats.inc_refine_duration(os::elapsed_counter() - refine_start); + _per_worker_refine_data._refine_duration += os::elapsed_counter() - refine_start; }); if (VerifyDuringGC) { @@ -150,8 +150,8 @@ class G1RefineRegionClosure : public G1HeapRegionClosure { } } - _refine_stats.inc_cards_scanned(claim.size()); - _refine_stats.inc_cards_clean(claim.size() - num_dirty_cards); + _per_worker_refine_data._cards_scanned += claim.size(); + _per_worker_refine_data._cards_clean += claim.size() - num_dirty_cards; if (SuspendibleThreadSet::should_yield()) { _completed = false; @@ -183,8 +183,8 @@ void G1ConcurrentRefineSweepTask::work(uint worker_id) { _sweep_completed = false; } - sweep_cl._refine_stats.inc_sweep_time(os::elapsed_counter() - start); - _stats->add_atomic(&sweep_cl._refine_stats); + _stats->inc_sweep_duration(os::elapsed_counter() - start); + _stats->add_atomic(&sweep_cl._per_worker_refine_data); } bool G1ConcurrentRefineSweepTask::sweep_completed() const { return _sweep_completed; } diff --git a/src/hotspot/share/gc/g1/g1Policy.cpp b/src/hotspot/share/gc/g1/g1Policy.cpp index 769977c7dacf..01afb6a5c774 100644 --- a/src/hotspot/share/gc/g1/g1Policy.cpp +++ b/src/hotspot/share/gc/g1/g1Policy.cpp @@ -1006,7 +1006,7 @@ void G1Policy::record_young_collection_end(bool concurrent_operation_is_full_mar G1IHOPControl* G1Policy::create_ihop_control(const G1OldGenAllocationTracker* old_gen_alloc_tracker, const G1Predictions* predictor) { - return new G1IHOPControl(InitiatingHeapOccupancyPercent, + return new G1IHOPControl(G1IHOP, old_gen_alloc_tracker, G1UseAdaptiveIHOP, predictor, diff --git a/src/hotspot/share/gc/g1/g1_globals.hpp b/src/hotspot/share/gc/g1/g1_globals.hpp index baed70b7088b..14daac4800b1 100644 --- a/src/hotspot/share/gc/g1/g1_globals.hpp +++ b/src/hotspot/share/gc/g1/g1_globals.hpp @@ -100,9 +100,8 @@ \ product(bool, G1UseAdaptiveIHOP, true, \ "Adaptively adjust the initiating heap occupancy from the " \ - "initial value of InitiatingHeapOccupancyPercent. The policy " \ - "attempts to start marking in time based on application " \ - "behavior.") \ + "initial value of G1IHOP. The policy attempts to start marking " \ + "in time based on application behavior.") \ \ product(size_t, G1AdaptiveIHOPNumInitialSamples, 3, EXPERIMENTAL, \ "How many completed time periods from concurrent start to first " \ @@ -110,6 +109,19 @@ "of the optimal occupancy to start marking.") \ range(1, max_intx) \ \ + product(uint, G1IHOP, 45, \ + "The Initiating Heap Occupancy Percentage (IHOP) for the " \ + "concurrent cycle. G1IHOP sets the percentage of the current " \ + "Java heap capacity occupied by the old generation at which G1 " \ + "starts this process. If G1UseAdaptiveIHOP is enabled, this " \ + "value is used as the initial threshold and may be adjusted " \ + "ergonomically by G1. " \ + "A value of 0 will result in as frequent as possible concurrent " \ + "cycles. A value of 100 disables concurrent cycles. " \ + "Fragmentation waste in the old generation is not considered " \ + "free space in this calculation.") \ + range(0, 100) \ + \ product(uint, G1ConfidencePercent, 50, \ "Confidence level for MMU/pause predictions. A higher value " \ "means that G1 will use less safety margin for its predictions.") \ diff --git a/src/hotspot/share/gc/shared/c2/cardTableBarrierSetC2.cpp b/src/hotspot/share/gc/shared/c2/cardTableBarrierSetC2.cpp index f7445ff254ff..381a9f65295e 100644 --- a/src/hotspot/share/gc/shared/c2/cardTableBarrierSetC2.cpp +++ b/src/hotspot/share/gc/shared/c2/cardTableBarrierSetC2.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 @@ -126,7 +126,7 @@ Node* CardTableBarrierSetC2::byte_map_base_node(IdealKit* kit) const { #endif CardTable::CardValue* card_table_base = ci_card_table_address_const(); if (card_table_base != nullptr) { - return kit->makecon(TypeRawPtr::make((address)card_table_base)); + return kit->makecon(TypeRawPtr::make((address)card_table_base, relocInfo::none)); } else { return kit->makecon(Type::get_zero_type(T_ADDRESS)); } diff --git a/src/hotspot/share/gc/shared/referencePolicy.cpp b/src/hotspot/share/gc/shared/referencePolicy.cpp index d1867291479c..6c5f459ebb41 100644 --- a/src/hotspot/share/gc/shared/referencePolicy.cpp +++ b/src/hotspot/share/gc/shared/referencePolicy.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -28,21 +28,17 @@ #include "gc/shared/referencePolicy.hpp" #include "memory/universe.hpp" #include "runtime/globals.hpp" +#include "utilities/integerCast.hpp" -LRUCurrentHeapPolicy::LRUCurrentHeapPolicy() { - setup(); -} - -// Capture state (of-the-VM) information needed to evaluate the policy -void LRUCurrentHeapPolicy::setup() { - _max_interval = (Universe::heap()->free_at_last_gc() / M) * SoftRefLRUPolicyMSPerMB; - assert(_max_interval >= 0,"Sanity check"); +void AbstractLRUReferencePolicy::set_max_interval(jlong max_interval) { + assert(max_interval >= 0, "Sanity check"); + _max_interval = max_interval; } // The oop passed in is the SoftReference object, and not // the object the SoftReference points to. -bool LRUCurrentHeapPolicy::should_clear_reference(oop p, - jlong timestamp_clock) { +bool AbstractLRUReferencePolicy::should_clear_reference(oop p, jlong timestamp_clock) { + assert(_max_interval >= 0, "Forgot to call setup"); jlong interval = timestamp_clock - java_lang_ref_SoftReference::timestamp(p); assert(interval >= 0, "Sanity check"); @@ -54,10 +50,9 @@ bool LRUCurrentHeapPolicy::should_clear_reference(oop p, return true; } -/////////////////////// MaxHeap ////////////////////// - -LRUMaxHeapPolicy::LRUMaxHeapPolicy() { - setup(); +// Capture state (of-the-VM) information needed to evaluate the policy +void LRUCurrentHeapPolicy::setup() { + set_max_interval(integer_cast(Universe::heap()->free_at_last_gc() / M) * SoftRefLRUPolicyMSPerMB); } // Capture state (of-the-VM) information needed to evaluate the policy @@ -66,21 +61,5 @@ void LRUMaxHeapPolicy::setup() { max_heap -= Universe::heap()->used_at_last_gc(); max_heap /= M; - _max_interval = max_heap * SoftRefLRUPolicyMSPerMB; - assert(_max_interval >= 0,"Sanity check"); -} - -// The oop passed in is the SoftReference object, and not -// the object the SoftReference points to. -bool LRUMaxHeapPolicy::should_clear_reference(oop p, - jlong timestamp_clock) { - jlong interval = timestamp_clock - java_lang_ref_SoftReference::timestamp(p); - assert(interval >= 0, "Sanity check"); - - // The interval will be zero if the ref was accessed since the last scavenge/gc. - if(interval <= _max_interval) { - return false; - } - - return true; + set_max_interval(integer_cast(max_heap) * SoftRefLRUPolicyMSPerMB); } diff --git a/src/hotspot/share/gc/shared/referencePolicy.hpp b/src/hotspot/share/gc/shared/referencePolicy.hpp index cf0382b036b9..0fd918fa7235 100644 --- a/src/hotspot/share/gc/shared/referencePolicy.hpp +++ b/src/hotspot/share/gc/shared/referencePolicy.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2019, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -56,28 +56,28 @@ class AlwaysClearPolicy : public ReferencePolicy { } }; -class LRUCurrentHeapPolicy : public ReferencePolicy { +class AbstractLRUReferencePolicy : public ReferencePolicy { private: - jlong _max_interval; + jlong _max_interval = -1; + + protected: + void set_max_interval(jlong max_interval); public: - LRUCurrentHeapPolicy(); + bool should_clear_reference(oop p, jlong timestamp_clock) final; + void setup() override = 0; +}; +class LRUCurrentHeapPolicy : public AbstractLRUReferencePolicy { + public: // Capture state (of-the-VM) information needed to evaluate the policy - void setup(); - virtual bool should_clear_reference(oop p, jlong timestamp_clock); + void setup() final; }; -class LRUMaxHeapPolicy : public ReferencePolicy { - private: - jlong _max_interval; - +class LRUMaxHeapPolicy : public AbstractLRUReferencePolicy { public: - LRUMaxHeapPolicy(); - // Capture state (of-the-VM) information needed to evaluate the policy - void setup(); - virtual bool should_clear_reference(oop p, jlong timestamp_clock); + void setup() final; }; #endif // SHARE_GC_SHARED_REFERENCEPOLICY_HPP diff --git a/src/hotspot/share/gc/shenandoah/c1/shenandoahBarrierSetC1.cpp b/src/hotspot/share/gc/shenandoah/c1/shenandoahBarrierSetC1.cpp index ad1dca475036..637ed6e64074 100644 --- a/src/hotspot/share/gc/shenandoah/c1/shenandoahBarrierSetC1.cpp +++ b/src/hotspot/share/gc/shenandoah/c1/shenandoahBarrierSetC1.cpp @@ -133,7 +133,6 @@ LIR_Opr ShenandoahBarrierSetC1::load_reference_barrier_impl(LIRGenerator* gen, L addr = ensure_in_register(gen, addr, T_ADDRESS); assert(addr->is_register(), "must be a register at this point"); LIR_Opr result = gen->result_register_for(obj->value_type()); - __ move(obj, result); LIR_Opr tmp1 = gen->new_register(T_ADDRESS); LIR_Opr tmp2 = gen->new_register(T_ADDRESS); @@ -164,6 +163,11 @@ LIR_Opr ShenandoahBarrierSetC1::load_reference_barrier_impl(LIRGenerator* gen, L CodeStub* slow = new ShenandoahLoadReferenceBarrierStub(obj, addr, result, tmp1, tmp2, decorators); __ branch(lir_cond_notEqual, slow); + + // No barrier is needed, move obj to result now. + __ move(obj, result); + + // Slow-path re-enters here with result set. __ branch_destination(slow->continuation()); return result; @@ -199,7 +203,7 @@ void ShenandoahBarrierSetC1::store_at_resolved(LIRAccess& access, LIR_Opr value) bool precise = is_array || on_anonymous; LIR_Opr post_addr = precise ? access.resolved_addr() : access.base().opr(); - post_barrier(access, post_addr, value); + post_barrier(access, post_addr); } } @@ -314,7 +318,7 @@ bool ShenandoahBarrierSetC1::generate_c1_runtime_stubs(BufferBlob* buffer_blob) return true; } -void ShenandoahBarrierSetC1::post_barrier(LIRAccess& access, LIR_Opr addr, LIR_Opr new_val) { +void ShenandoahBarrierSetC1::post_barrier(LIRAccess& access, LIR_Opr addr) { assert(ShenandoahCardBarrier, "Should have been checked by caller"); DecoratorSet decorators = access.decorators(); @@ -368,3 +372,71 @@ void ShenandoahBarrierSetC1::post_barrier(LIRAccess& access, LIR_Opr addr, LIR_O __ move(dirty, card_addr); } } + +LIR_Opr ShenandoahBarrierSetC1::atomic_cmpxchg_at_resolved(LIRAccess& access, LIRItem& cmp_value, LIRItem& new_value) { + if (!access.is_oop()) { + return BarrierSetC1::atomic_cmpxchg_at_resolved(access, cmp_value, new_value); + } + + LIRGenerator* gen = access.gen(); + + LIR_Opr tmp = gen->new_register(T_OBJECT); + LIR_Opr addr = access.resolved_addr(); + + // Handle the previous value through SATB, as we are about to perform the store. + __ load(addr->as_address_ptr(), tmp); + if (ShenandoahSATBBarrier) { + pre_barrier(gen, access.access_emit_info(), access.decorators(), + /* addr_opr (unused) = */ LIR_OprFact::illegalOpr, + /* pre_val = */ tmp); + } + + // Perform LRB on location to fix it up for this and all following accesses. + // This guarantees there are no false negatives due to concurrent evacuation, + // and the value loaded later by CAS is sanitized by some LRB, or is null. + if (ShenandoahLoadRefBarrier) { + load_reference_barrier(gen, /* obj = */ tmp, /* addr = */ addr, access.decorators()); + } + + LIR_Opr result = BarrierSetC1::atomic_cmpxchg_at_resolved(access, cmp_value, new_value); + + if (ShenandoahCardBarrier) { + post_barrier(access, /* addr = */ addr); + } + + return result; +} + +LIR_Opr ShenandoahBarrierSetC1::atomic_xchg_at_resolved(LIRAccess& access, LIRItem& value) { + if (!access.is_oop()) { + return BarrierSetC1::atomic_xchg_at_resolved(access, value); + } + + LIRGenerator* gen = access.gen(); + + LIR_Opr tmp = gen->new_register(T_OBJECT); + LIR_Opr addr = access.resolved_addr(); + + // Handle the previous value through SATB, as we are about to perform the store. + __ load(addr->as_address_ptr(), tmp); + if (ShenandoahSATBBarrier) { + pre_barrier(gen, access.access_emit_info(), access.decorators(), + /* addr_opr (unused) = */ LIR_OprFact::illegalOpr, + /* pre_val = */ tmp); + } + + // Perform LRB on location to fix it up for this and all following accesses. + // This is purely opportunistic: we would not have any false negatives here. + // This guarantees the value loaded later by XCHG is sanitized by some LRB, or is null. + if (ShenandoahLoadRefBarrier) { + load_reference_barrier(gen, /* obj = */ tmp, /* addr = */ addr, access.decorators()); + } + + LIR_Opr result = BarrierSetC1::atomic_xchg_at_resolved(access, value); + + if (ShenandoahCardBarrier) { + post_barrier(access, /* addr = */ addr); + } + + return result; +} diff --git a/src/hotspot/share/gc/shenandoah/c1/shenandoahBarrierSetC1.hpp b/src/hotspot/share/gc/shenandoah/c1/shenandoahBarrierSetC1.hpp index 1b4f2c79bd25..413777a61ee6 100644 --- a/src/hotspot/share/gc/shenandoah/c1/shenandoahBarrierSetC1.hpp +++ b/src/hotspot/share/gc/shenandoah/c1/shenandoahBarrierSetC1.hpp @@ -127,6 +127,7 @@ class ShenandoahLoadReferenceBarrierStub: public CodeStub { visitor->do_input(_addr); visitor->do_temp(_addr); visitor->do_temp(_result); + visitor->do_output(_result); visitor->do_temp(_tmp1); visitor->do_temp(_tmp2); } @@ -135,61 +136,6 @@ class ShenandoahLoadReferenceBarrierStub: public CodeStub { #endif // PRODUCT }; -class LIR_OpShenandoahCompareAndSwap : public LIR_Op { - friend class LIR_OpVisitState; - -private: - LIR_Opr _addr; - LIR_Opr _cmp_value; - LIR_Opr _new_value; - LIR_Opr _tmp1; - LIR_Opr _tmp2; - -public: - LIR_OpShenandoahCompareAndSwap(LIR_Opr addr, LIR_Opr cmp_value, LIR_Opr new_value, - LIR_Opr t1, LIR_Opr t2, LIR_Opr result) - : LIR_Op(lir_none, result, nullptr) // no info - , _addr(addr) - , _cmp_value(cmp_value) - , _new_value(new_value) - , _tmp1(t1) - , _tmp2(t2) { } - - LIR_Opr addr() const { return _addr; } - LIR_Opr cmp_value() const { return _cmp_value; } - LIR_Opr new_value() const { return _new_value; } - LIR_Opr tmp1() const { return _tmp1; } - LIR_Opr tmp2() const { return _tmp2; } - - virtual void visit(LIR_OpVisitState* state) { - if (_info) state->do_info(_info); - assert(_addr->is_valid(), "used"); state->do_input(_addr); - state->do_temp(_addr); - assert(_cmp_value->is_valid(), "used"); state->do_input(_cmp_value); - state->do_temp(_cmp_value); - assert(_new_value->is_valid(), "used"); state->do_input(_new_value); - state->do_temp(_new_value); - if (_tmp1->is_valid()) state->do_temp(_tmp1); - if (_tmp2->is_valid()) state->do_temp(_tmp2); - if (_result->is_valid()) state->do_output(_result); - } - - virtual void emit_code(LIR_Assembler* masm); - - virtual void print_instr(outputStream* out) const { - addr()->print(out); out->print(" "); - cmp_value()->print(out); out->print(" "); - new_value()->print(out); out->print(" "); - tmp1()->print(out); out->print(" "); - tmp2()->print(out); out->print(" "); - } -#ifndef PRODUCT - virtual const char* name() const { - return "shenandoah_cas_obj"; - } -#endif // PRODUCT -}; - class ShenandoahBarrierSetC1 : public BarrierSetC1 { private: CodeBlob* _pre_barrier_c1_runtime_code_blob; @@ -244,7 +190,7 @@ class ShenandoahBarrierSetC1 : public BarrierSetC1 { virtual LIR_Opr atomic_xchg_at_resolved(LIRAccess& access, LIRItem& value); - void post_barrier(LIRAccess& access, LIR_Opr addr, LIR_Opr new_val); + void post_barrier(LIRAccess& access, LIR_Opr addr); public: diff --git a/src/hotspot/share/gc/shenandoah/c2/shenandoahBarrierSetC2.cpp b/src/hotspot/share/gc/shenandoah/c2/shenandoahBarrierSetC2.cpp index 4fcc90d7bde3..deab648a108e 100644 --- a/src/hotspot/share/gc/shenandoah/c2/shenandoahBarrierSetC2.cpp +++ b/src/hotspot/share/gc/shenandoah/c2/shenandoahBarrierSetC2.cpp @@ -990,19 +990,23 @@ void ShenandoahBarrierSetC2::eliminate_gc_barrier(PhaseMacroExpand* macro, Node* shenandoah_eliminate_wb_pre(node, ¯o->igvn()); } if (ShenandoahCardBarrier && node->Opcode() == Op_CastP2X) { - Node* shift = node->unique_out(); - Node* addp = shift->unique_out(); - for (DUIterator_Last jmin, j = addp->last_outs(jmin); j >= jmin; --j) { - Node* mem = addp->last_out(j); - if (UseCondCardMark && mem->is_Load()) { - assert(mem->Opcode() == Op_LoadB, "unexpected code shape"); - // The load is checking if the card has been written so - // replace it with zero to fold the test. - macro->replace_node(mem, macro->intcon(0)); - continue; + for (DUIterator_Last imin, i = node->last_outs(imin); i >= imin; --i) { + Node* shift = node->last_out(i); + for (DUIterator_Last kmin, k = shift->last_outs(kmin); k >= kmin; --k) { + Node* addp = shift->last_out(k); + for (DUIterator_Last jmin, j = addp->last_outs(jmin); j >= jmin; --j) { + Node* mem = addp->last_out(j); + if (UseCondCardMark && mem->is_Load()) { + assert(mem->Opcode() == Op_LoadB, "unexpected code shape"); + // The load is checking if the card has been written so + // replace it with zero to fold the test. + macro->replace_node(mem, macro->intcon(0)); + continue; + } + assert(mem->is_Store(), "store required"); + macro->replace_node(mem, mem->in(MemNode::Memory)); + } } - assert(mem->is_Store(), "store required"); - macro->replace_node(mem, mem->in(MemNode::Memory)); } } } diff --git a/src/hotspot/share/gc/shenandoah/shenandoahObjArrayAllocator.cpp b/src/hotspot/share/gc/shenandoah/shenandoahObjArrayAllocator.cpp index e2215ea58efe..dc9fef16cf69 100644 --- a/src/hotspot/share/gc/shenandoah/shenandoahObjArrayAllocator.cpp +++ b/src/hotspot/share/gc/shenandoah/shenandoahObjArrayAllocator.cpp @@ -71,16 +71,14 @@ oop ShenandoahObjArrayAllocator::initialize(HeapWord* mem) const { // Always initialize the mem with primitive array first so GC won't look into the elements in the array. // For obj array, the header will be corrected to object array after clearing the memory. Klass* filling_klass = _klass; - int filling_array_length = _length; - const bool is_ref_type = is_reference_type(element_type, true); + const bool is_ref_type = is_reference_type(element_type); if (is_ref_type) { - const bool is_narrow_oop = element_type == T_NARROWOOP; - size_t filling_element_byte_size = is_narrow_oop ? T_INT_aelem_bytes : T_LONG_aelem_bytes; - filling_klass = is_narrow_oop ? Universe::intArrayKlass() : Universe::longArrayKlass(); - filling_array_length = (int) ((process_size << LogBytesPerWord) / filling_element_byte_size); + filling_klass = LP64_ONLY(UseCompressedOops ? Universe::intArrayKlass() : Universe::longArrayKlass()) NOT_LP64(Universe::intArrayKlass()); + assert(type2aelembytes(ArrayKlass::cast(filling_klass)->element_type()) == type2aelembytes(element_type), "filling element size must match ref size"); } - ObjArrayAllocator filling_array_allocator(filling_klass, _word_size, filling_array_length , /* do_zero */ false); + // Use _length directly: it matches the ref count, and the filling element size equals the ref size. + ObjArrayAllocator filling_array_allocator(filling_klass, _word_size, _length, /* do_zero */ false); filling_array_allocator.initialize(mem); // Invisible roots will be scanned and marked at the end of marking. @@ -106,7 +104,6 @@ oop ShenandoahObjArrayAllocator::initialize(HeapWord* mem) const { // reference array, header need to be overridden to its own. if (is_ref_type) { - arrayOopDesc::set_length(mem, _length); finish(mem); // zap paddings after setting correct klass mem_zap_start_padding(mem); diff --git a/src/hotspot/share/gc/z/zIterator.hpp b/src/hotspot/share/gc/z/zIterator.hpp index e048002e52e9..0e9ef808dffa 100644 --- a/src/hotspot/share/gc/z/zIterator.hpp +++ b/src/hotspot/share/gc/z/zIterator.hpp @@ -31,11 +31,14 @@ class ZIterator : AllStatic { private: static bool is_invisible_object(oop obj); static bool is_invisible_object_array(oop obj); + static bool is_invisible_object_array(oop obj, Klass* klass); public: // This iterator skips invisible roots template static void oop_iterate_safe(oop obj, OopClosureT* cl); + template + static void oop_iterate_safe(oop obj, Klass* klass, OopClosureT* cl); template static void oop_iterate(oop obj, OopClosureT* cl); @@ -46,6 +49,8 @@ class ZIterator : AllStatic { // This function skips invisible roots template static void basic_oop_iterate_safe(oop obj, Function function); + template + static void basic_oop_iterate_safe(oop obj, Klass* klass, Function function); template static void basic_oop_iterate(oop obj, Function function); diff --git a/src/hotspot/share/gc/z/zIterator.inline.hpp b/src/hotspot/share/gc/z/zIterator.inline.hpp index cbfe1a79aafe..6e51929c7b40 100644 --- a/src/hotspot/share/gc/z/zIterator.inline.hpp +++ b/src/hotspot/share/gc/z/zIterator.inline.hpp @@ -45,17 +45,27 @@ inline bool ZIterator::is_invisible_object(oop obj) { } inline bool ZIterator::is_invisible_object_array(oop obj) { - return obj->klass()->is_objArray_klass() && is_invisible_object(obj); + return is_invisible_object_array(obj, obj->klass()); } -// This iterator skips invisible object arrays +inline bool ZIterator::is_invisible_object_array(oop obj, Klass* klass) { + return klass->is_objArray_klass() && is_invisible_object(obj); +} + +// These iterators skips invisible object arrays + template void ZIterator::oop_iterate_safe(oop obj, OopClosureT* cl) { + oop_iterate_safe(obj, obj->klass(), cl); +} + +template +void ZIterator::oop_iterate_safe(oop obj, Klass* klass, OopClosureT* cl) { // Skip invisible object arrays - we only filter out *object* arrays, // because that check is arguably faster than the is_invisible_object // check, and primitive arrays are cheap to call oop_iterate on. - if (!is_invisible_object_array(obj)) { - obj->oop_iterate(cl); + if (!is_invisible_object_array(obj, klass)) { + OopIteratorClosureDispatch::oop_oop_iterate(cl, obj, klass); } } @@ -89,11 +99,17 @@ class ZBasicOopIterateClosure : public BasicOopIterateClosure { } }; -// This function skips invisible roots +// These functions skip invisible roots + template void ZIterator::basic_oop_iterate_safe(oop obj, Function function) { + basic_oop_iterate_safe(obj, obj->klass(), function); +} + +template +void ZIterator::basic_oop_iterate_safe(oop obj, Klass* klass, Function function) { ZBasicOopIterateClosure cl(function); - oop_iterate_safe(obj, &cl); + oop_iterate_safe(obj, klass, &cl); } template diff --git a/src/hotspot/share/gc/z/zRelocate.cpp b/src/hotspot/share/gc/z/zRelocate.cpp index d51cf5abbaef..1c2a4078904a 100644 --- a/src/hotspot/share/gc/z/zRelocate.cpp +++ b/src/hotspot/share/gc/z/zRelocate.cpp @@ -1272,7 +1272,7 @@ class ZRelocateAddRemsetForFlipPromoted : public ZRestartableTask { for (ZPage* page; _iter.next(&page);) { page->object_iterate([&](oop obj) { // Remap oops and add remset if needed - ZIterator::basic_oop_iterate_safe(obj, remap_and_maybe_add_remset); + ZIterator::basic_oop_iterate_safe(obj, obj->klass(), remap_and_maybe_add_remset); // String dedup string_dedup_context.request(obj); diff --git a/src/hotspot/share/interpreter/interpreterRuntime.cpp b/src/hotspot/share/interpreter/interpreterRuntime.cpp index dd183f36ea28..375cb402892e 100644 --- a/src/hotspot/share/interpreter/interpreterRuntime.cpp +++ b/src/hotspot/share/interpreter/interpreterRuntime.cpp @@ -1487,23 +1487,32 @@ JRT_ENTRY(void, InterpreterRuntime::member_name_arg_or_null(JavaThread* current, Method* method, address bcp)) Bytecodes::Code code = Bytecodes::code_at(method, bcp); if (code != Bytecodes::_invokestatic) { + current->set_vm_result_oop(nullptr); return; } + ConstantPool* cpool = method->constants(); int cp_index = Bytes::get_native_u2(bcp + 1); Symbol* cname = cpool->klass_name_at(cpool->klass_ref_index_at(cp_index, code)); Symbol* mname = cpool->name_ref_at(cp_index, code); - if (MethodHandles::has_member_arg(cname, mname)) { - oop member_name_oop = cast_to_oop(member_name); - if (java_lang_invoke_DirectMethodHandle::is_instance(member_name_oop)) { - // FIXME: remove after j.l.i.InvokerBytecodeGenerator code shape is updated. - member_name_oop = java_lang_invoke_DirectMethodHandle::member(member_name_oop); - } - current->set_vm_result_oop(member_name_oop); - } else { + if (!MethodHandles::has_member_arg(cname, mname)) { current->set_vm_result_oop(nullptr); + return; + } + + oop member_name_oop = cast_to_oop(member_name); + + guarantee(member_name_oop != nullptr, "member_name_oop should not be nullptr"); + guarantee(oopDesc::is_oop(member_name_oop), "member_name_oop should be an oop"); + guarantee(java_lang_invoke_MemberName::is_instance(member_name_oop) || + java_lang_invoke_DirectMethodHandle::is_instance(member_name_oop), + "member_name_oop is not MemberName or DMH"); + + if (java_lang_invoke_DirectMethodHandle::is_instance(member_name_oop)) { + member_name_oop = java_lang_invoke_DirectMethodHandle::member(member_name_oop); } + current->set_vm_result_oop(member_name_oop); JRT_END #endif // INCLUDE_JVMTI diff --git a/src/hotspot/share/jfr/periodic/jfrPeriodic.cpp b/src/hotspot/share/jfr/periodic/jfrPeriodic.cpp index 969c9ca60c1d..92f406bd0950 100644 --- a/src/hotspot/share/jfr/periodic/jfrPeriodic.cpp +++ b/src/hotspot/share/jfr/periodic/jfrPeriodic.cpp @@ -256,10 +256,7 @@ TRACE_REQUEST_FUNC(SystemProcess) { // feature is implemented, write real event while (processes != nullptr) { SystemProcess* tmp = processes; - const char* info = processes->command_line(); - if (info == nullptr) { - info = processes->path(); - } + const char* info = processes->path(); if (info == nullptr) { info = processes->name(); } diff --git a/src/hotspot/share/jvmci/jvmciCodeInstaller.cpp b/src/hotspot/share/jvmci/jvmciCodeInstaller.cpp index ad787886d7f1..6043b400e3bc 100644 --- a/src/hotspot/share/jvmci/jvmciCodeInstaller.cpp +++ b/src/hotspot/share/jvmci/jvmciCodeInstaller.cpp @@ -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 @@ -818,7 +818,7 @@ JVMCI::CodeInstallResult CodeInstaller::install(JVMCICompiler* compiler, cb = nm; if (compile_state == nullptr) { // This compile didn't come through the CompileBroker so perform the printing here - CompilerDirectiveMatcher matcher(method, compiler); + CompilerDirectiveMatcher matcher(method, CompLevel_full_optimization); nm->maybe_print_nmethod(matcher.directive_set()); // Since this compilation didn't pass through the broker it wasn't logged yet. diff --git a/src/hotspot/share/jvmci/jvmciCompilerToVM.cpp b/src/hotspot/share/jvmci/jvmciCompilerToVM.cpp index a01f95d8cd5e..5d0d2aedc62b 100644 --- a/src/hotspot/share/jvmci/jvmciCompilerToVM.cpp +++ b/src/hotspot/share/jvmci/jvmciCompilerToVM.cpp @@ -585,7 +585,7 @@ C2V_END C2V_VMENTRY_0(jboolean, hasNeverInlineDirective,(JNIEnv* env, jobject, ARGUMENT_PAIR(method))) methodHandle method (THREAD, UNPACK_PAIR(Method, method)); - return !Inline || CompilerOracle::should_not_inline(method) || method->dont_inline(); + return !Inline || CompilerOracle::should_not_inline(method, CompLevel_full_optimization) || method->dont_inline(); C2V_END C2V_VMENTRY_0(jboolean, shouldInlineMethod,(JNIEnv* env, jobject, ARGUMENT_PAIR(method))) diff --git a/src/hotspot/share/memory/universe.cpp b/src/hotspot/share/memory/universe.cpp index aecaa1cac22d..f83f1b1786f7 100644 --- a/src/hotspot/share/memory/universe.cpp +++ b/src/hotspot/share/memory/universe.cpp @@ -1348,7 +1348,7 @@ static void log_cpu_time() { const double gc_string_dedup_cpu_time = (double) CPUTimeUsage::GC::stringdedup() / NANOSECS_PER_SEC; const double gc_cpu_time = (double) gc_threads_cpu_time + gc_vm_thread_cpu_time + gc_string_dedup_cpu_time; - const double elasped_time = os::elapsedTime(); + const double elapsed_time = os::elapsedTime(); const bool has_error = CPUTimeUsage::Error::has_error(); if (gc_cpu_time < process_cpu_time) { @@ -1359,13 +1359,13 @@ static void log_cpu_time() { cpuLog.print(" CPUs"); cpuLog.print(" s %% utilized"); cpuLog.print(" Process"); - cpuLog.print(" Total %30.4f %6.2f %8.1f", process_cpu_time, 100.0, process_cpu_time / elasped_time); - cpuLog.print(" Garbage Collection %30.4f %6.2f %8.1f", gc_cpu_time, percent_of(gc_cpu_time, process_cpu_time), gc_cpu_time / elasped_time); - cpuLog.print(" GC Threads %30.4f %6.2f %8.1f", gc_threads_cpu_time, percent_of(gc_threads_cpu_time, process_cpu_time), gc_threads_cpu_time / elasped_time); - cpuLog.print(" VM Thread %30.4f %6.2f %8.1f", gc_vm_thread_cpu_time, percent_of(gc_vm_thread_cpu_time, process_cpu_time), gc_vm_thread_cpu_time / elasped_time); + cpuLog.print(" Total %30.4f %6.2f %8.1f", process_cpu_time, 100.0, process_cpu_time / elapsed_time); + cpuLog.print(" Garbage Collection %30.4f %6.2f %8.1f", gc_cpu_time, percent_of(gc_cpu_time, process_cpu_time), gc_cpu_time / elapsed_time); + cpuLog.print(" GC Threads %30.4f %6.2f %8.1f", gc_threads_cpu_time, percent_of(gc_threads_cpu_time, process_cpu_time), gc_threads_cpu_time / elapsed_time); + cpuLog.print(" VM Thread %30.4f %6.2f %8.1f", gc_vm_thread_cpu_time, percent_of(gc_vm_thread_cpu_time, process_cpu_time), gc_vm_thread_cpu_time / elapsed_time); if (UseStringDeduplication) { - cpuLog.print(" String Deduplication %30.4f %6.2f %8.1f", gc_string_dedup_cpu_time, percent_of(gc_string_dedup_cpu_time, process_cpu_time), gc_string_dedup_cpu_time / elasped_time); + cpuLog.print(" String Deduplication %30.4f %6.2f %8.1f", gc_string_dedup_cpu_time, percent_of(gc_string_dedup_cpu_time, process_cpu_time), gc_string_dedup_cpu_time / elapsed_time); } cpuLog.print("====================================================================================="); } diff --git a/src/hotspot/share/oops/markWord.hpp b/src/hotspot/share/oops/markWord.hpp index 4583e6bd3a16..e67d632fdf65 100644 --- a/src/hotspot/share/oops/markWord.hpp +++ b/src/hotspot/share/oops/markWord.hpp @@ -30,6 +30,7 @@ #include "oops/compressedKlass.hpp" #include "oops/oopsHierarchy.hpp" #include "runtime/globals.hpp" +#include "utilities/powerOfTwo.hpp" // The markWord describes the header of an object. // @@ -37,32 +38,44 @@ // // 32 bits: // -------- -// hash:25 ------------>| age:4 self-fwd:1 lock:2 (normal object) +// hash:25 age:4 self-fwd:1 lock:2 // -// 64 bits: -// -------- -// unused:22 hash:31 -->| unused_gap:4 age:4 self-fwd:1 lock:2 (normal object) +// 64 bits (without compact headers): +// ---------------------------------- +// unused:22 hash:31 valhalla:4 age:4 self-fwd:1 lock:2 // // 64 bits (with compact headers): // ------------------------------- -// klass:22 hash:31 -->| unused_gap:4 age:4 self-fwd:1 lock:2 (normal object) -// -// - hash contains the identity hash value: largest value is -// 31 bits, see os::random(). Also, 64-bit vm's require -// a hash value no bigger than 32 bits because they will not -// properly generate a mask larger than that: see library_call.cpp +// klass:22 hash:31 valhalla:4 age:4 self-fwd:1 lock:2 // -// - the two lock bits are used to describe three states: locked/unlocked and monitor. +// - lock bits are used to describe lock states: locked/unlocked/monitor-locked +// and to indicate that an object has been GC marked / forwarded. // // [header | 00] locked locked regular object header (fast-locking in use) // [header | 01] unlocked regular object header -// [ptr | 10] monitor inflated lock (header is swapped out, UseObjectMonitorTable == false) // [header | 10] monitor inflated lock (UseObjectMonitorTable == true) -// [ptr | 11] marked used to mark an object +// [ptr | 10] monitor inflated lock (UseObjectMonitorTable == false, header is swapped out) +// [ptr | 11] marked used to mark an object (header is swapped out) +// +// - self-fwd - used by some GCs to indicate in-place forwarding. +// +// Note the position of 'self-fwd' is not by accident. When forwarding an +// object to a new heap position, HeapWord alignment guarantees the lower +// bits, including 'self-fwd' are 0. "is_self_forwarded()" will be correctly +// set to false. Otherwise encode_pointer_as_mark() may have 'self-fwd' set. +// +// - age - used by some GCs to track the age of objects. +// +// - valhalla - reserved for valhalla +// +// - hash - contains the identity hash value: largest value is 31 bits, see +// os::random(). Also, 64-bit VMs require a hash value no bigger than 32 +// bits because they will not properly generate a mask larger than that: +// see library_call.cpp +// +// - klass - klass identifier used when UseCompactObjectHeaders == true -class BasicLock; class ObjectMonitor; -class JavaThread; class outputStream; class markWord { @@ -97,33 +110,42 @@ class markWord { // Conversion uintptr_t value() const { return _value; } - // Constants - static const int age_bits = 4; + // Constants, in least significant bit order + + // Number of bits static const int lock_bits = 2; static const int self_fwd_bits = 1; - static const int max_hash_bits = BitsPerWord - age_bits - lock_bits - self_fwd_bits; + static const int age_bits = 4; + static const int valhalla_reserved_bits = LP64_ONLY(4) NOT_LP64(0); + static const int max_hash_bits = BitsPerWord - age_bits - lock_bits - self_fwd_bits - valhalla_reserved_bits; static const int hash_bits = max_hash_bits > 31 ? 31 : max_hash_bits; - static const int unused_gap_bits = LP64_ONLY(4) NOT_LP64(0); // Reserved for Valhalla. + // Shifts static const int lock_shift = 0; static const int self_fwd_shift = lock_shift + lock_bits; static const int age_shift = self_fwd_shift + self_fwd_bits; - static const int hash_shift = age_shift + age_bits + unused_gap_bits; + static const int valhalla_reserved_shift = age_shift + age_bits; + static const int hash_shift = valhalla_reserved_shift + valhalla_reserved_bits; + + // Masks (in-place) + static const uintptr_t lock_mask_in_place = right_n_bits(lock_bits) << lock_shift; + static const uintptr_t self_fwd_bit_in_place = right_n_bits(self_fwd_bits) << self_fwd_shift; + static const uintptr_t age_mask_in_place = right_n_bits(age_bits) << age_shift; + static const uintptr_t hash_mask_in_place = right_n_bits(hash_bits) << hash_shift; + + // Verify that _bit_in_place refers to constants with only one bit. + static_assert(is_power_of_2(self_fwd_bit_in_place)); - static const uintptr_t lock_mask = right_n_bits(lock_bits); - static const uintptr_t lock_mask_in_place = lock_mask << lock_shift; - static const uintptr_t self_fwd_mask = right_n_bits(self_fwd_bits); - static const uintptr_t self_fwd_mask_in_place = self_fwd_mask << self_fwd_shift; - static const uintptr_t age_mask = right_n_bits(age_bits); - static const uintptr_t age_mask_in_place = age_mask << age_shift; - static const uintptr_t hash_mask = right_n_bits(hash_bits); - static const uintptr_t hash_mask_in_place = hash_mask << hash_shift; + // Masks (unshifted) + static const uintptr_t lock_mask = lock_mask_in_place >> lock_shift; + static const uintptr_t age_mask = age_mask_in_place >> age_shift; + static const uintptr_t hash_mask = hash_mask_in_place >> hash_shift; #ifdef _LP64 // Used only with compact headers: // We store the (narrow) Klass* in the bits 43 to 64. - // These are for bit-precise extraction of the narrow Klass* from the 64-bit Markword + // These are for bit-precise extraction of the narrow Klass* from the 64-bit markWord static constexpr int klass_offset_in_bytes = 4; static constexpr int klass_shift = hash_shift + hash_bits; static constexpr int klass_shift_at_offset = klass_shift - klass_offset_in_bytes * BitsPerByte; @@ -132,7 +154,6 @@ class markWord { static constexpr uintptr_t klass_mask_in_place = klass_mask << klass_shift; #endif - static const uintptr_t locked_value = 0; static const uintptr_t unlocked_value = 1; static const uintptr_t monitor_value = 2; @@ -157,17 +178,19 @@ class markWord { bool is_marked() const { return (mask_bits(value(), lock_mask_in_place) == marked_value); } - bool is_forwarded() const { - // Returns true for normal forwarded (0b011) and self-forwarded (0b1xx). - return mask_bits(value(), lock_mask_in_place | self_fwd_mask_in_place) >= static_cast(marked_value); - } + bool is_neutral() const { // Not locked, or marked - a "clean" neutral state return (mask_bits(value(), lock_mask_in_place) == unlocked_value); } + bool is_forwarded() const { + // Returns true for normal forwarded (0b011) and self-forwarded (0b1xx). + return mask_bits(value(), lock_mask_in_place | self_fwd_bit_in_place) >= static_cast(marked_value); + } + // Should this header be preserved during GC? bool must_be_preserved() const { - return (!is_unlocked() || !has_no_hash()); + return !is_unlocked() || !has_no_hash(); } // WARNING: The following routines are used EXCLUSIVELY by @@ -204,10 +227,14 @@ class markWord { return markWord(tmp | monitor_value); } - bool has_displaced_mark_helper() const { + bool has_monitor_pointer() const { intptr_t lockbits = value() & lock_mask_in_place; return !UseObjectMonitorTable && lockbits == monitor_value; } + + bool has_displaced_mark_helper() const { + return has_monitor_pointer(); + } markWord displaced_mark_helper() const; void set_displaced_mark_helper(markWord m) const; @@ -248,7 +275,7 @@ class markWord { // Prototype mark for initialization static markWord prototype() { - return markWord( no_hash_in_place | no_lock_in_place ); + return markWord(unlocked_value); } // Debugging @@ -261,15 +288,15 @@ class markWord { inline void* decode_pointer() const { return (void*)clear_lock_bits().value(); } inline bool is_self_forwarded() const { - return mask_bits(value(), self_fwd_mask_in_place) != 0; + return mask_bits(value(), self_fwd_bit_in_place) != 0; } inline markWord set_self_forwarded() const { - return markWord(value() | self_fwd_mask_in_place); + return markWord(value() | self_fwd_bit_in_place); } inline markWord unset_self_forwarded() const { - return markWord(value() & ~self_fwd_mask_in_place); + return markWord(value() & ~self_fwd_bit_in_place); } inline oop forwardee() const { diff --git a/src/hotspot/share/oops/oop.hpp b/src/hotspot/share/oops/oop.hpp index d6cc71a60d8b..daa192d51245 100644 --- a/src/hotspot/share/oops/oop.hpp +++ b/src/hotspot/share/oops/oop.hpp @@ -90,6 +90,7 @@ class oopDesc { void set_narrow_klass(narrowKlass nk) NOT_CDS_JAVA_HEAP_RETURN; inline narrowKlass narrow_klass() const; + inline narrowKlass narrow_klass_acquire() const; inline void set_klass(Klass* k); static inline void release_set_klass(HeapWord* mem, Klass* k); diff --git a/src/hotspot/share/oops/oop.inline.hpp b/src/hotspot/share/oops/oop.inline.hpp index d5cb80e11220..066ba7a8e204 100644 --- a/src/hotspot/share/oops/oop.inline.hpp +++ b/src/hotspot/share/oops/oop.inline.hpp @@ -95,57 +95,38 @@ void oopDesc::init_mark() { } Klass* oopDesc::klass() const { - switch (ObjLayout::klass_mode()) { - case ObjLayout::Compact: - return mark().klass(); - case ObjLayout::Compressed: - return CompressedKlassPointers::decode_not_null(_compressed_klass); - default: - ShouldNotReachHere(); - } + return CompressedKlassPointers::decode_not_null(narrow_klass()); } Klass* oopDesc::klass_or_null() const { - switch (ObjLayout::klass_mode()) { - case ObjLayout::Compact: - return mark().klass_or_null(); - case ObjLayout::Compressed: - return CompressedKlassPointers::decode(_compressed_klass); - default: - ShouldNotReachHere(); - } + return CompressedKlassPointers::decode(narrow_klass()); } Klass* oopDesc::klass_or_null_acquire() const { - switch (ObjLayout::klass_mode()) { - case ObjLayout::Compact: - return mark_acquire().klass(); - case ObjLayout::Compressed: { - narrowKlass narrow_klass = AtomicAccess::load_acquire(&_compressed_klass); - return CompressedKlassPointers::decode(narrow_klass); - } - default: - ShouldNotReachHere(); - } + return CompressedKlassPointers::decode(narrow_klass_acquire()); } Klass* oopDesc::klass_without_asserts() const { + return CompressedKlassPointers::decode_without_asserts(narrow_klass()); +} + +narrowKlass oopDesc::narrow_klass() const { switch (ObjLayout::klass_mode()) { case ObjLayout::Compact: - return mark().klass_without_asserts(); + return mark().narrow_klass(); case ObjLayout::Compressed: - return CompressedKlassPointers::decode_without_asserts(_compressed_klass); + return _compressed_klass; default: ShouldNotReachHere(); } } -narrowKlass oopDesc::narrow_klass() const { +narrowKlass oopDesc::narrow_klass_acquire() const { switch (ObjLayout::klass_mode()) { case ObjLayout::Compact: - return mark().narrow_klass(); + return mark_acquire().narrow_klass(); case ObjLayout::Compressed: - return _compressed_klass; + return AtomicAccess::load_acquire(&_compressed_klass); default: ShouldNotReachHere(); } diff --git a/src/hotspot/share/opto/bytecodeInfo.cpp b/src/hotspot/share/opto/bytecodeInfo.cpp index 56c336dfc731..330a8688110b 100644 --- a/src/hotspot/share/opto/bytecodeInfo.cpp +++ b/src/hotspot/share/opto/bytecodeInfo.cpp @@ -234,7 +234,7 @@ bool InlineTree::should_not_inline(ciMethod* callee_method, ciMethod* caller_met return false; } - if (C->directive()->should_not_inline(callee_method)) { + if (C->directive()->should_not_inline(callee_method, CompLevel_full_optimization)) { set_msg("disallowed by CompileCommand"); return true; } diff --git a/src/hotspot/share/opto/compile.cpp b/src/hotspot/share/opto/compile.cpp index 788c4620b06c..25975334dcb6 100644 --- a/src/hotspot/share/opto/compile.cpp +++ b/src/hotspot/share/opto/compile.cpp @@ -3157,30 +3157,20 @@ void Compile::Code_Gen() { } //------------------------------Final_Reshape_Counts--------------------------- -// This class defines counters to help identify when a method -// may/must be executed using hardware with only 24-bit precision. +// This class defines counters and node lists collected during +// the final graph reshaping. struct Final_Reshape_Counts : public StackObj { - int _call_count; // count non-inlined 'common' calls - int _float_count; // count float ops requiring 24-bit precision - int _double_count; // count double ops requiring more precision int _java_call_count; // count non-inlined 'java' calls int _inner_loop_count; // count loops which need alignment VectorSet _visited; // Visitation flags Node_List _tests; // Set of IfNodes & PCTableNodes Final_Reshape_Counts() : - _call_count(0), _float_count(0), _double_count(0), _java_call_count(0), _inner_loop_count(0) { } - void inc_call_count () { _call_count ++; } - void inc_float_count () { _float_count ++; } - void inc_double_count() { _double_count++; } void inc_java_call_count() { _java_call_count++; } void inc_inner_loop_count() { _inner_loop_count++; } - int get_call_count () const { return _call_count ; } - int get_float_count () const { return _float_count ; } - int get_double_count() const { return _double_count; } int get_java_call_count() const { return _java_call_count; } int get_inner_loop_count() const { return _inner_loop_count; } }; @@ -3243,7 +3233,6 @@ void Compile::final_graph_reshaping_impl(Node *n, Final_Reshape_Counts& frc, Uni "unused CallLeafPureNode should have been removed before final graph reshaping"); } #endif - // Count FPU ops and common calls, implements item (3) bool gc_handled = BarrierSet::barrier_set()->barrier_set_c2()->final_graph_reshaping(this, n, nop, dead_nodes); if (!gc_handled) { final_graph_reshaping_main_switch(n, frc, nop, dead_nodes); @@ -3288,50 +3277,6 @@ void Compile::handle_div_mod_op(Node* n, BasicType bt, bool is_unsigned) { void Compile::final_graph_reshaping_main_switch(Node* n, Final_Reshape_Counts& frc, uint nop, Unique_Node_List& dead_nodes) { switch( nop ) { - // Count all float operations that may use FPU - case Op_AddHF: - case Op_MulHF: - case Op_AddF: - case Op_SubF: - case Op_MulF: - case Op_DivF: - case Op_NegF: - case Op_ModF: - case Op_ConvI2F: - case Op_ConF: - case Op_CmpF: - case Op_CmpF3: - case Op_StoreF: - case Op_LoadF: - // case Op_ConvL2F: // longs are split into 32-bit halves - frc.inc_float_count(); - break; - - case Op_ConvF2D: - case Op_ConvD2F: - frc.inc_float_count(); - frc.inc_double_count(); - break; - - // Count all double operations that may use FPU - case Op_AddD: - case Op_SubD: - case Op_MulD: - case Op_DivD: - case Op_NegD: - case Op_ModD: - case Op_ConvI2D: - case Op_ConvD2I: - // case Op_ConvL2D: // handled by leaf call - // case Op_ConvD2L: // handled by leaf call - case Op_ConD: - case Op_CmpD: - case Op_CmpD3: - case Op_StoreD: - case Op_LoadD: - case Op_LoadD_unaligned: - frc.inc_double_count(); - break; case Op_Opaque1: // Remove Opaque Nodes before matching n->subsume_by(n->in(1), this); break; @@ -3351,7 +3296,6 @@ void Compile::final_graph_reshaping_main_switch(Node* n, Final_Reshape_Counts& f } n->subsume_by(new_call, this); } - frc.inc_call_count(); break; } case Op_CallStaticJava: @@ -3364,13 +3308,8 @@ void Compile::final_graph_reshaping_main_switch(Node* n, Final_Reshape_Counts& f case Op_CallLeafNoFP: { assert (n->is_Call(), ""); CallNode *call = n->as_Call(); - // Count call sites where the FP mode bit would have to be flipped. - // Do not count uncommon runtime calls: - // uncommon_trap, _complete_monitor_locking, _complete_monitor_unlocking, - // _new_Java, _new_typeArray, _new_objArray, _rethrow_Java, ... - if (!call->is_CallStaticJava() || !call->as_CallStaticJava()->_name) { - frc.inc_call_count(); // Count the call site - } else { // See if uncommon argument is shared + // See if uncommon argument is shared + if (call->is_CallStaticJava() && call->as_CallStaticJava()->_name) { Node *n = call->in(TypeFunc::Parms); int nop = n->Opcode(); // Clone shared simple arguments to uncommon calls, item (1). @@ -3388,6 +3327,13 @@ void Compile::final_graph_reshaping_main_switch(Node* n, Final_Reshape_Counts& f } break; } + + // Mem nodes need explicit cases to satisfy assert(!n->is_Mem()) in default. + case Op_StoreF: + case Op_LoadF: + case Op_StoreD: + case Op_LoadD: + case Op_LoadD_unaligned: case Op_StoreB: case Op_StoreC: case Op_StoreI: @@ -3435,6 +3381,12 @@ void Compile::final_graph_reshaping_main_switch(Node* n, Final_Reshape_Counts& f case Op_LoadN: case Op_LoadRange: case Op_LoadS: + case Op_LoadVectorGather: + case Op_StoreVectorScatter: + case Op_LoadVectorGatherMasked: + case Op_StoreVectorScatterMasked: + case Op_LoadVectorMasked: + case Op_StoreVectorMasked: break; case Op_AddP: { // Assert sane base pointers @@ -3785,35 +3737,6 @@ void Compile::final_graph_reshaping_main_switch(Node* n, Final_Reshape_Counts& f #endif break; - case Op_LoadVectorGather: - case Op_StoreVectorScatter: - case Op_LoadVectorGatherMasked: - case Op_StoreVectorScatterMasked: - case Op_VectorCmpMasked: - case Op_VectorMaskGen: - case Op_LoadVectorMasked: - case Op_StoreVectorMasked: - break; - - case Op_AddReductionVI: - case Op_AddReductionVL: - case Op_AddReductionVHF: - case Op_AddReductionVF: - case Op_AddReductionVD: - case Op_MulReductionVI: - case Op_MulReductionVL: - case Op_MulReductionVHF: - case Op_MulReductionVF: - case Op_MulReductionVD: - case Op_MinReductionV: - case Op_MaxReductionV: - case Op_UMinReductionV: - case Op_UMaxReductionV: - case Op_AndReductionV: - case Op_OrReductionV: - case Op_XorReductionV: - break; - case Op_PackB: case Op_PackS: case Op_PackI: @@ -3889,8 +3812,6 @@ void Compile::final_graph_reshaping_main_switch(Node* n, Final_Reshape_Counts& f } break; } - case Op_Blackhole: - break; case Op_RangeCheck: { RangeCheckNode* rc = n->as_RangeCheck(); Node* iff = new IfNode(rc->in(0), rc->in(1), rc->_prob, rc->_fcnt); @@ -4059,20 +3980,13 @@ void Compile::final_graph_reshaping_walk(Node_Stack& nstack, Node* root, Final_R // Intel update-in-place two-address operations and better register usage // on RISCs. Must come after regular optimizations to avoid GVN Ideal // calls canonicalizing them back. -// (3) Count the number of double-precision FP ops, single-precision FP ops -// and call sites. On Intel, we can get correct rounding either by -// forcing singles to memory (requires extra stores and loads after each -// FP bytecode) or we can set a rounding mode bit (requires setting and -// clearing the mode bit around call sites). The mode bit is only used -// if the relative frequency of single FP ops to calls is low enough. -// This is a key transform for SPEC mpeg_audio. -// (4) Detect infinite loops; blobs of code reachable from above but not +// (3) Detect infinite loops; blobs of code reachable from above but not // below. Several of the Code_Gen algorithms fail on such code shapes, // so we simply bail out. Happens a lot in ZKM.jar, but also happens // from time to time in other codes (such as -Xcomp finalizer loops, etc). // Detection is by looking for IfNodes where only 1 projection is // reachable from below or CatchNodes missing some targets. -// (5) Assert for insane oop offsets in debug mode. +// (4) Assert for insane oop offsets in debug mode. bool Compile::final_graph_reshaping() { // an infinite loop may have been eliminated by the optimizer, diff --git a/src/hotspot/share/opto/machnode.cpp b/src/hotspot/share/opto/machnode.cpp index c35861df7350..ec861865ff59 100644 --- a/src/hotspot/share/opto/machnode.cpp +++ b/src/hotspot/share/opto/machnode.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -561,11 +561,7 @@ void MachNode::dump_spec(outputStream *st) const { if (barrier_data() != 0) { st->print(" barrier("); BarrierSet::barrier_set()->barrier_set_c2()->dump_barrier_data(this, st); - st->print(")"); - } - if (_bottom_type != nullptr) { - st->print(" "); - _bottom_type->dump_on(st); + st->print(") "); } } @@ -576,6 +572,19 @@ void MachNode::dump_format(PhaseRegAlloc *ra, outputStream *st) const { } #endif +//============================================================================= +#ifndef PRODUCT +void MachTypeNode::dump_spec(outputStream *st) const { + MachNode::dump_spec(st); + if (_bottom_type != nullptr) { + _bottom_type->dump_on(st); + } else { + st->print(" null"); + } +} +#endif + + //============================================================================= int MachConstantNode::constant_offset() { // Bind the offset lazily. diff --git a/src/hotspot/share/opto/machnode.hpp b/src/hotspot/share/opto/machnode.hpp index daa2aad960ad..b60313b7f758 100644 --- a/src/hotspot/share/opto/machnode.hpp +++ b/src/hotspot/share/opto/machnode.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -220,7 +220,7 @@ class MachOper : public ResourceObj { // ADLC inherit from this class. class MachNode : public Node { public: - MachNode() : Node((uint)0), _bottom_type(nullptr), _barrier(0), _num_opnds(0), _opnds(nullptr) { + MachNode() : Node((uint)0), _barrier(0), _num_opnds(0), _opnds(nullptr) { init_class_id(Class_Mach); } // Required boilerplate @@ -282,9 +282,6 @@ class MachNode : public Node { // output have choices - but they must use the same choice. virtual uint two_adr( ) const { return 0; } - // Capture the type of the matched ideal node - const Type* _bottom_type; - // The GC might require some barrier metadata for machine code emission. uint8_t _barrier; @@ -334,17 +331,7 @@ class MachNode : public Node { virtual MachNode *Expand( State *, Node_List &proj_list, Node* mem ) { return this; } // Bottom_type call; value comes from operand0 - virtual const Type* bottom_type() const { - if (_bottom_type != nullptr) { - return _bottom_type; - } - const Type* res = _opnds[0]->type(); - // The type system around pointers is complex, do not rely on operand type then - assert(res != nullptr, "must be not null"); - assert(is_MachTemp() || res->isa_ptr() == nullptr, "must not be a pointer"); - return res; - } - + virtual const class Type *bottom_type() const { return _opnds[0]->type(); } virtual uint ideal_reg() const { const Type *t = _opnds[0]->type(); if (t == TypeInt::CC) { @@ -431,6 +418,20 @@ class MachIdealNode : public MachNode { virtual const class Type *bottom_type() const { return _opnds == nullptr ? Type::CONTROL : MachNode::bottom_type(); } }; +//------------------------------MachTypeNode---------------------------- +// Machine Nodes that need to retain a known Type. +class MachTypeNode : public MachNode { + virtual uint size_of() const { return sizeof(*this); } // Size is bigger +public: + MachTypeNode( ) {} + const Type *_bottom_type; + + virtual const class Type *bottom_type() const { return _bottom_type; } +#ifndef PRODUCT + virtual void dump_spec(outputStream *st) const; +#endif +}; + //------------------------------MachBreakpointNode---------------------------- // Machine breakpoint or interrupt Node class MachBreakpointNode : public MachIdealNode { @@ -476,12 +477,12 @@ class MachConstantBaseNode : public MachIdealNode { //------------------------------MachConstantNode------------------------------- // Machine node that holds a constant which is stored in the constant table. -class MachConstantNode : public MachNode { +class MachConstantNode : public MachTypeNode { protected: ConstantTable::Constant _constant; // This node's constant. public: - MachConstantNode() : MachNode() { + MachConstantNode() : MachTypeNode() { init_class_id(Class_MachConstant); } diff --git a/src/hotspot/share/opto/matcher.cpp b/src/hotspot/share/opto/matcher.cpp index 69098befa388..d2a9250b3ee0 100644 --- a/src/hotspot/share/opto/matcher.cpp +++ b/src/hotspot/share/opto/matcher.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -2550,7 +2550,7 @@ bool Matcher::gen_narrow_oop_implicit_null_checks() { // Advice matcher to perform null checks on the narrow oop side. // Implicit checks are not possible on the uncompressed oop side anyway // (at least not for read accesses). - // Performs significantly better (especially on Power 6). + // Performs significantly better. if (!os::zero_page_read_protected()) { return true; } diff --git a/src/hotspot/share/opto/matcher.hpp b/src/hotspot/share/opto/matcher.hpp index 31f4a7822477..a3d0f8e7d6c6 100644 --- a/src/hotspot/share/opto/matcher.hpp +++ b/src/hotspot/share/opto/matcher.hpp @@ -38,6 +38,7 @@ class Compile; class Node; class MachNode; +class MachTypeNode; class MachOper; //---------------------------Matcher------------------------------------------- diff --git a/src/hotspot/share/opto/subnode.hpp b/src/hotspot/share/opto/subnode.hpp index 387c1c46ba9c..29ec25b41f82 100644 --- a/src/hotspot/share/opto/subnode.hpp +++ b/src/hotspot/share/opto/subnode.hpp @@ -520,7 +520,12 @@ class SqrtDNode : public Node { public: SqrtDNode(Compile* C, Node *c, Node *in1) : Node(c, in1) { init_flags(Flag_is_expensive); - C->add_expensive_node(this); + // Treat node only as expensive if a control input is set because it might + // be created from SqrtVDNode in VectorNode::push_through_replicate which + // does not have control input. + if (c != nullptr) { + C->add_expensive_node(this); + } } virtual int Opcode() const; const Type *bottom_type() const { return Type::DOUBLE; } diff --git a/src/hotspot/share/opto/type.cpp b/src/hotspot/share/opto/type.cpp index 6dd0cc183e12..4d1d05c596d2 100644 --- a/src/hotspot/share/opto/type.cpp +++ b/src/hotspot/share/opto/type.cpp @@ -62,66 +62,66 @@ Dict* Type::_shared_type_dict = nullptr; // Array which maps compiler types to Basic Types const Type::TypeInfo Type::_type_info[Type::lastype] = { - { Bad, T_ILLEGAL, "bad", false, Node::NotAMachineReg, relocInfo::none }, // Bad - { Control, T_ILLEGAL, "control", false, 0, relocInfo::none }, // Control - { Bottom, T_VOID, "top", false, 0, relocInfo::none }, // Top - { Bad, T_INT, "int:", false, Op_RegI, relocInfo::none }, // Int - { Bad, T_LONG, "long:", false, Op_RegL, relocInfo::none }, // Long - { Half, T_VOID, "half", false, 0, relocInfo::none }, // Half - { Bad, T_NARROWOOP, "narrowoop:", false, Op_RegN, relocInfo::none }, // NarrowOop - { Bad, T_NARROWKLASS,"narrowklass:", false, Op_RegN, relocInfo::none }, // NarrowKlass - { Bad, T_ILLEGAL, "tuple:", false, Node::NotAMachineReg, relocInfo::none }, // Tuple - { Bad, T_ARRAY, "array:", false, Node::NotAMachineReg, relocInfo::none }, // Array - { Bad, T_ARRAY, "interfaces:", false, Node::NotAMachineReg, relocInfo::none }, // Interfaces + { Bad, T_ILLEGAL, "bad", false, Node::NotAMachineReg}, // Bad + { Control, T_ILLEGAL, "control", false, 0 }, // Control + { Bottom, T_VOID, "top", false, 0 }, // Top + { Bad, T_INT, "int:", false, Op_RegI }, // Int + { Bad, T_LONG, "long:", false, Op_RegL }, // Long + { Half, T_VOID, "half", false, 0 }, // Half + { Bad, T_NARROWOOP, "narrowoop:", false, Op_RegN }, // NarrowOop + { Bad, T_NARROWKLASS,"narrowklass:", false, Op_RegN }, // NarrowKlass + { Bad, T_ILLEGAL, "tuple:", false, Node::NotAMachineReg}, // Tuple + { Bad, T_ARRAY, "array:", false, Node::NotAMachineReg}, // Array + { Bad, T_ARRAY, "interfaces:", false, Node::NotAMachineReg}, // Interfaces #if defined(PPC64) - { Bad, T_ILLEGAL, "vectormask:", false, Op_RegVectMask, relocInfo::none }, // VectorMask. - { Bad, T_ILLEGAL, "vectora:", false, Op_VecA, relocInfo::none }, // VectorA. - { Bad, T_ILLEGAL, "vectors:", false, 0, relocInfo::none }, // VectorS - { Bad, T_ILLEGAL, "vectord:", false, Op_RegL, relocInfo::none }, // VectorD - { Bad, T_ILLEGAL, "vectorx:", false, Op_VecX, relocInfo::none }, // VectorX - { Bad, T_ILLEGAL, "vectory:", false, 0, relocInfo::none }, // VectorY - { Bad, T_ILLEGAL, "vectorz:", false, 0, relocInfo::none }, // VectorZ + { Bad, T_ILLEGAL, "vectormask:", false, Op_RegVectMask }, // VectorMask. + { Bad, T_ILLEGAL, "vectora:", false, Op_VecA }, // VectorA. + { Bad, T_ILLEGAL, "vectors:", false, 0 }, // VectorS + { Bad, T_ILLEGAL, "vectord:", false, Op_RegL }, // VectorD + { Bad, T_ILLEGAL, "vectorx:", false, Op_VecX }, // VectorX + { Bad, T_ILLEGAL, "vectory:", false, 0 }, // VectorY + { Bad, T_ILLEGAL, "vectorz:", false, 0 }, // VectorZ #elif defined(S390) - { Bad, T_ILLEGAL, "vectormask:", false, Op_RegVectMask, relocInfo::none }, // VectorMask. - { Bad, T_ILLEGAL, "vectora:", false, Op_VecA, relocInfo::none }, // VectorA. - { Bad, T_ILLEGAL, "vectors:", false, 0, relocInfo::none }, // VectorS - { Bad, T_ILLEGAL, "vectord:", false, Op_RegL, relocInfo::none }, // VectorD - { Bad, T_ILLEGAL, "vectorx:", false, Op_VecX, relocInfo::none }, // VectorX - { Bad, T_ILLEGAL, "vectory:", false, 0, relocInfo::none }, // VectorY - { Bad, T_ILLEGAL, "vectorz:", false, 0, relocInfo::none }, // VectorZ + { Bad, T_ILLEGAL, "vectormask:", false, Op_RegVectMask }, // VectorMask. + { Bad, T_ILLEGAL, "vectora:", false, Op_VecA }, // VectorA. + { Bad, T_ILLEGAL, "vectors:", false, 0 }, // VectorS + { Bad, T_ILLEGAL, "vectord:", false, Op_RegL }, // VectorD + { Bad, T_ILLEGAL, "vectorx:", false, Op_VecX }, // VectorX + { Bad, T_ILLEGAL, "vectory:", false, 0 }, // VectorY + { Bad, T_ILLEGAL, "vectorz:", false, 0 }, // VectorZ #else // all other - { Bad, T_ILLEGAL, "vectormask:", false, Op_RegVectMask, relocInfo::none }, // VectorMask. - { Bad, T_ILLEGAL, "vectora:", false, Op_VecA, relocInfo::none }, // VectorA. - { Bad, T_ILLEGAL, "vectors:", false, Op_VecS, relocInfo::none }, // VectorS - { Bad, T_ILLEGAL, "vectord:", false, Op_VecD, relocInfo::none }, // VectorD - { Bad, T_ILLEGAL, "vectorx:", false, Op_VecX, relocInfo::none }, // VectorX - { Bad, T_ILLEGAL, "vectory:", false, Op_VecY, relocInfo::none }, // VectorY - { Bad, T_ILLEGAL, "vectorz:", false, Op_VecZ, relocInfo::none }, // VectorZ + { Bad, T_ILLEGAL, "vectormask:", false, Op_RegVectMask }, // VectorMask. + { Bad, T_ILLEGAL, "vectora:", false, Op_VecA }, // VectorA. + { Bad, T_ILLEGAL, "vectors:", false, Op_VecS }, // VectorS + { Bad, T_ILLEGAL, "vectord:", false, Op_VecD }, // VectorD + { Bad, T_ILLEGAL, "vectorx:", false, Op_VecX }, // VectorX + { Bad, T_ILLEGAL, "vectory:", false, Op_VecY }, // VectorY + { Bad, T_ILLEGAL, "vectorz:", false, Op_VecZ }, // VectorZ #endif - { Bad, T_ADDRESS, "anyptr:", false, Op_RegP, relocInfo::none }, // AnyPtr - { Bad, T_ADDRESS, "rawptr:", false, Op_RegP, relocInfo::external_word_type }, // RawPtr - { Bad, T_OBJECT, "oop:", true, Op_RegP, relocInfo::oop_type }, // OopPtr - { Bad, T_OBJECT, "inst:", true, Op_RegP, relocInfo::oop_type }, // InstPtr - { Bad, T_OBJECT, "ary:", true, Op_RegP, relocInfo::oop_type }, // AryPtr - { Bad, T_METADATA, "metadata:", false, Op_RegP, relocInfo::metadata_type }, // MetadataPtr - { Bad, T_METADATA, "klass:", false, Op_RegP, relocInfo::metadata_type }, // KlassPtr - { Bad, T_METADATA, "instklass:", false, Op_RegP, relocInfo::metadata_type }, // InstKlassPtr - { Bad, T_METADATA, "aryklass:", false, Op_RegP, relocInfo::metadata_type }, // AryKlassPtr - { Bad, T_OBJECT, "func", false, 0, relocInfo::none }, // Function - { Abio, T_ILLEGAL, "abIO", false, 0, relocInfo::none }, // Abio - { Return_Address, T_ADDRESS, "return_address",false, Op_RegP, relocInfo::none }, // Return_Address - { Memory, T_ILLEGAL, "memory", false, 0, relocInfo::none }, // Memory - { HalfFloatBot, T_SHORT, "halffloat_top", false, Op_RegF, relocInfo::none }, // HalfFloatTop - { HalfFloatCon, T_SHORT, "hfcon:", false, Op_RegF, relocInfo::none }, // HalfFloatCon - { HalfFloatTop, T_SHORT, "short", false, Op_RegF, relocInfo::none }, // HalfFloatBot - { FloatBot, T_FLOAT, "float_top", false, Op_RegF, relocInfo::none }, // FloatTop - { FloatCon, T_FLOAT, "ftcon:", false, Op_RegF, relocInfo::none }, // FloatCon - { FloatTop, T_FLOAT, "float", false, Op_RegF, relocInfo::none }, // FloatBot - { DoubleBot, T_DOUBLE, "double_top", false, Op_RegD, relocInfo::none }, // DoubleTop - { DoubleCon, T_DOUBLE, "dblcon:", false, Op_RegD, relocInfo::none }, // DoubleCon - { DoubleTop, T_DOUBLE, "double", false, Op_RegD, relocInfo::none }, // DoubleBot - { Top, T_ILLEGAL, "bottom", false, 0, relocInfo::none } // Bottom + { Bad, T_ADDRESS, "anyptr:", false, Op_RegP }, // AnyPtr + { Bad, T_ADDRESS, "rawptr:", false, Op_RegP }, // RawPtr + { Bad, T_OBJECT, "oop:", true, Op_RegP }, // OopPtr + { Bad, T_OBJECT, "inst:", true, Op_RegP }, // InstPtr + { Bad, T_OBJECT, "ary:", true, Op_RegP }, // AryPtr + { Bad, T_METADATA, "metadata:", false, Op_RegP }, // MetadataPtr + { Bad, T_METADATA, "klass:", false, Op_RegP }, // KlassPtr + { Bad, T_METADATA, "instklass:", false, Op_RegP }, // InstKlassPtr + { Bad, T_METADATA, "aryklass:", false, Op_RegP }, // AryKlassPtr + { Bad, T_OBJECT, "func", false, 0 }, // Function + { Abio, T_ILLEGAL, "abIO", false, 0 }, // Abio + { Return_Address, T_ADDRESS, "return_address",false, Op_RegP }, // Return_Address + { Memory, T_ILLEGAL, "memory", false, 0 }, // Memory + { HalfFloatBot, T_SHORT, "halffloat_top", false, Op_RegF }, // HalfFloatTop + { HalfFloatCon, T_SHORT, "hfcon:", false, Op_RegF }, // HalfFloatCon + { HalfFloatTop, T_SHORT, "short", false, Op_RegF }, // HalfFloatBot + { FloatBot, T_FLOAT, "float_top", false, Op_RegF }, // FloatTop + { FloatCon, T_FLOAT, "ftcon:", false, Op_RegF }, // FloatCon + { FloatTop, T_FLOAT, "float", false, Op_RegF }, // FloatBot + { DoubleBot, T_DOUBLE, "double_top", false, Op_RegD }, // DoubleTop + { DoubleCon, T_DOUBLE, "dblcon:", false, Op_RegD }, // DoubleCon + { DoubleTop, T_DOUBLE, "double", false, Op_RegD }, // DoubleBot + { Top, T_ILLEGAL, "bottom", false, 0 } // Bottom }; // Map ideal registers (machine types) to ideal types @@ -235,7 +235,7 @@ const Type* Type::get_typeflow_type(ciType* type) { case T_ADDRESS: assert(type->is_return_address(), ""); - return TypeRawPtr::make((address)(intptr_t)type->as_return_address()->bci()); + return TypeRawPtr::make((address)(intptr_t)type->as_return_address()->bci(), relocInfo::none); default: // make sure we did not mix up the cases: @@ -2369,7 +2369,7 @@ const TypePtr* TypePtr::with_inline_depth(int depth) const { if (!UseInlineDepthForSpeculativeTypes) { return this; } - return make(AnyPtr, _ptr, _offset, _speculative, depth); + return make(AnyPtr, _ptr, _offset, _speculative, depth, _reloc); } //------------------------------dump2------------------------------------------ @@ -2597,15 +2597,17 @@ const TypePtr::PTR TypePtr::ptr_meet[TypePtr::lastPTR][TypePtr::lastPTR] = { }; //------------------------------make------------------------------------------- -const TypePtr *TypePtr::make(TYPES t, enum PTR ptr, int offset, const TypePtr* speculative, int inline_depth) { - return (TypePtr*)(new TypePtr(t,ptr,offset, speculative, inline_depth))->hashcons(); +const TypePtr* TypePtr::make(TYPES t, enum PTR ptr, int offset, + const TypePtr* speculative, int inline_depth, + relocInfo::relocType reloc) { + return (TypePtr*)(new TypePtr(t, ptr, offset, reloc, speculative, inline_depth))->hashcons(); } //------------------------------cast_to_ptr_type------------------------------- const TypePtr* TypePtr::cast_to_ptr_type(PTR ptr) const { assert(_base == AnyPtr, "subclass must override cast_to_ptr_type"); if( ptr == _ptr ) return this; - return make(_base, ptr, _offset, _speculative, _inline_depth); + return make(_base, ptr, _offset, _speculative, _inline_depth, _reloc); } //------------------------------get_con---------------------------------------- @@ -2707,7 +2709,7 @@ const TypePtr::PTR TypePtr::ptr_dual[TypePtr::lastPTR] = { BotPTR, NotNull, Constant, Null, AnyNull, TopPTR }; const Type *TypePtr::xdual() const { - return new TypePtr(AnyPtr, dual_ptr(), dual_offset(), dual_speculative(), dual_inline_depth()); + return new TypePtr(AnyPtr, dual_ptr(), dual_offset(), relocInfo::none, dual_speculative(), dual_inline_depth()); } //------------------------------xadd_offset------------------------------------ @@ -2728,24 +2730,25 @@ int TypePtr::xadd_offset( intptr_t offset ) const { //------------------------------add_offset------------------------------------- const TypePtr *TypePtr::add_offset( intptr_t offset ) const { - return make(AnyPtr, _ptr, xadd_offset(offset), _speculative, _inline_depth); + return make(AnyPtr, _ptr, xadd_offset(offset), _speculative, _inline_depth, _reloc); } const TypePtr *TypePtr::with_offset(intptr_t offset) const { - return make(AnyPtr, _ptr, offset, _speculative, _inline_depth); + return make(AnyPtr, _ptr, offset, _speculative, _inline_depth, _reloc); } //------------------------------eq--------------------------------------------- // Structural equality check for Type representations bool TypePtr::eq( const Type *t ) const { const TypePtr *a = (const TypePtr*)t; - return _ptr == a->ptr() && _offset == a->offset() && eq_speculative(a) && _inline_depth == a->_inline_depth; + return _ptr == a->ptr() && _offset == a->offset() && _reloc == a->reloc() && + eq_speculative(a) && _inline_depth == a->_inline_depth; } //------------------------------hash------------------------------------------- // Type-specific hashing function. uint TypePtr::hash(void) const { - return (uint)_ptr + (uint)_offset + (uint)hash_speculative() + (uint)_inline_depth; + return (uint)_ptr + (uint)_offset + (uint)_reloc + (uint)hash_speculative() + (uint)_inline_depth; } /** @@ -2756,7 +2759,7 @@ const TypePtr* TypePtr::remove_speculative() const { return this; } assert(_inline_depth == InlineDepthTop || _inline_depth == InlineDepthBottom, "non speculative type shouldn't have inline depth"); - return make(AnyPtr, _ptr, _offset, nullptr, _inline_depth); + return make(AnyPtr, _ptr, _offset, nullptr, _inline_depth, _reloc); } /** @@ -3071,12 +3074,12 @@ const TypeRawPtr *TypeRawPtr::NOTNULL; const TypeRawPtr *TypeRawPtr::make( enum PTR ptr ) { assert( ptr != Constant, "what is the constant?" ); assert( ptr != Null, "Use TypePtr for null" ); - return (TypeRawPtr*)(new TypeRawPtr(ptr,nullptr))->hashcons(); + return (TypeRawPtr*)(new TypeRawPtr(ptr, nullptr, relocInfo::none))->hashcons(); } -const TypeRawPtr *TypeRawPtr::make(address bits) { +const TypeRawPtr* TypeRawPtr::make(address bits, relocInfo::relocType reloc) { assert(bits != nullptr, "Use TypePtr for null"); - return (TypeRawPtr*)(new TypeRawPtr(Constant,bits))->hashcons(); + return (TypeRawPtr*)(new TypeRawPtr(Constant, bits, reloc))->hashcons(); } //------------------------------cast_to_ptr_type------------------------------- @@ -3151,7 +3154,7 @@ const Type *TypeRawPtr::xmeet( const Type *t ) const { //------------------------------xdual------------------------------------------ // Dual: compute field-by-field dual const Type *TypeRawPtr::xdual() const { - return new TypeRawPtr( dual_ptr(), _bits ); + return new TypeRawPtr(dual_ptr(), _bits, _reloc); } //------------------------------add_offset------------------------------------- @@ -3174,7 +3177,7 @@ const TypePtr* TypeRawPtr::add_offset(intptr_t offset) const { } else if ( sum == 0 ) { return TypePtr::NULL_PTR; } else { - return make( (address)sum ); + return make((address)sum, _reloc); } } default: ShouldNotReachHere(); @@ -3469,7 +3472,7 @@ bool TypeInterfaces::has_non_array_interface() const { //------------------------------TypeOopPtr------------------------------------- TypeOopPtr::TypeOopPtr(TYPES t, PTR ptr, ciKlass* k, const TypeInterfaces* interfaces, bool xk, ciObject* o, int offset, int instance_id, const TypePtr* speculative, int inline_depth) - : TypePtr(t, ptr, offset, speculative, inline_depth), + : TypePtr(t, ptr, offset, relocInfo::oop_type, speculative, inline_depth), _const_oop(o), _klass(k), _interfaces(interfaces), _klass_is_exact(xk), @@ -5490,7 +5493,7 @@ void TypeMetadataPtr::dump2( Dict &d, uint depth, outputStream *st ) const { const TypeMetadataPtr *TypeMetadataPtr::BOTTOM; TypeMetadataPtr::TypeMetadataPtr(PTR ptr, ciMetadata* metadata, int offset): - TypePtr(MetadataPtr, ptr, offset), _metadata(metadata) { + TypePtr(MetadataPtr, ptr, offset, relocInfo::metadata_type), _metadata(metadata) { } const TypeMetadataPtr* TypeMetadataPtr::make(ciMethod* m) { @@ -5538,7 +5541,7 @@ const TypeKlassPtr* TypeKlassPtr::make(PTR ptr, ciKlass* klass, int offset, Inte //------------------------------TypeKlassPtr----------------------------------- TypeKlassPtr::TypeKlassPtr(TYPES t, PTR ptr, ciKlass* klass, const TypeInterfaces* interfaces, int offset) - : TypePtr(t, ptr, offset), _klass(klass), _interfaces(interfaces) { + : TypePtr(t, ptr, offset, relocInfo::metadata_type), _klass(klass), _interfaces(interfaces) { assert(klass == nullptr || !klass->is_loaded() || (klass->is_instance_klass() && !klass->is_interface()) || klass->is_type_array_klass() || !klass->as_obj_array_klass()->base_element_klass()->is_interface(), "no interface here"); } diff --git a/src/hotspot/share/opto/type.hpp b/src/hotspot/share/opto/type.hpp index 79777c82cc75..728b7af9c244 100644 --- a/src/hotspot/share/opto/type.hpp +++ b/src/hotspot/share/opto/type.hpp @@ -157,7 +157,6 @@ class Type { const char* msg; bool isa_oop; uint ideal_reg; - relocInfo::relocType reloc; } TypeInfo; // Dictionary of types shared among compilations. @@ -459,7 +458,6 @@ class Type { uint ideal_reg() const { return _type_info[_base].ideal_reg; } const char* msg() const { return _type_info[_base].msg; } bool isa_oop_ptr() const { return _type_info[_base].isa_oop; } - relocInfo::relocType reloc() const { return _type_info[_base].reloc; } // Mapping from CI type system to compiler type: static const Type* get_typeflow_type(ciType* type); @@ -1176,10 +1174,11 @@ class TypePtr : public Type { enum PTR { TopPTR, AnyNull, Constant, Null, NotNull, BotPTR, lastPTR }; protected: TypePtr(TYPES t, PTR ptr, int offset, + relocInfo::relocType reloc, const TypePtr* speculative = nullptr, int inline_depth = InlineDepthBottom) : Type(t), _speculative(speculative), _inline_depth(inline_depth), _offset(offset), - _ptr(ptr) {} + _ptr(ptr), _reloc(reloc) {} static const PTR ptr_meet[lastPTR][lastPTR]; static const PTR ptr_dual[lastPTR]; static const char * const ptr_msg[lastPTR]; @@ -1247,13 +1246,16 @@ class TypePtr : public Type { public: const int _offset; // Offset into oop, with TOP & BOT const PTR _ptr; // Pointer equivalence class + const relocInfo::relocType _reloc; int offset() const { return _offset; } PTR ptr() const { return _ptr; } + relocInfo::relocType reloc() const { return _reloc; } static const TypePtr *make(TYPES t, PTR ptr, int offset, const TypePtr* speculative = nullptr, - int inline_depth = InlineDepthBottom); + int inline_depth = InlineDepthBottom, + relocInfo::relocType reloc = relocInfo::none); // Return a 'ptr' version of this type virtual const TypePtr* cast_to_ptr_type(PTR ptr) const; @@ -1316,15 +1318,15 @@ class TypePtr : public Type { // include the stack pointer, top of heap, card-marking area, handles, etc. class TypeRawPtr : public TypePtr { protected: - TypeRawPtr( PTR ptr, address bits ) : TypePtr(RawPtr,ptr,0), _bits(bits){} + TypeRawPtr(PTR ptr, address bits, relocInfo::relocType reloc) : TypePtr(RawPtr, ptr, 0, reloc), _bits(bits){} public: virtual bool eq( const Type *t ) const; virtual uint hash() const; // Type specific hashing const address _bits; // Constant value, if applicable - static const TypeRawPtr *make( PTR ptr ); - static const TypeRawPtr *make( address bits ); + static const TypeRawPtr* make(PTR ptr); + static const TypeRawPtr* make(address bits, relocInfo::relocType reloc = relocInfo::external_word_type); // Return a 'ptr' version of this type virtual const TypeRawPtr* cast_to_ptr_type(PTR ptr) const; diff --git a/src/hotspot/share/opto/vectornode.cpp b/src/hotspot/share/opto/vectornode.cpp index 651a27af9c79..a54fe6e3a733 100644 --- a/src/hotspot/share/opto/vectornode.cpp +++ b/src/hotspot/share/opto/vectornode.cpp @@ -22,10 +22,12 @@ */ #include "memory/allocation.inline.hpp" +#include "opto/addnode.hpp" #include "opto/c2_globals.hpp" #include "opto/compile.hpp" #include "opto/connode.hpp" #include "opto/convertnode.hpp" +#include "opto/divnode.hpp" #include "opto/mulnode.hpp" #include "opto/subnode.hpp" #include "opto/vectornode.hpp" @@ -290,7 +292,146 @@ int VectorNode::opcode(int sopc, BasicType bt) { assert(!VectorNode::is_convert_opcode(sopc), "Convert node %s should be processed by VectorCastNode::opcode()", NodeClassNames[sopc]); - return 0; // Unimplemented + return 0; // not handled + } +} + +// Return the scalar opcode for the specified vector opcode and basic type. +// Returns 0 if not handled. +int VectorNode::scalar_opcode(int vopc, BasicType bt) { + switch (vopc) { + case Op_AddVB: + case Op_AddVS: + case Op_AddVI: + return Op_AddI; + case Op_AddVL: + return Op_AddL; + case Op_AddVF: + return Op_AddF; + case Op_AddVD: + return Op_AddD; + + case Op_SubVB: + case Op_SubVS: + case Op_SubVI: + return Op_SubI; + case Op_SubVL: + return Op_SubL; + case Op_SubVF: + return Op_SubF; + case Op_SubVD: + return Op_SubD; + + case Op_MulVB: + case Op_MulVS: + case Op_MulVI: + return Op_MulI; + case Op_MulVL: + return Op_MulL; + case Op_MulVF: + return Op_MulF; + case Op_MulVD: + return Op_MulD; + + case Op_DivVF: + return Op_DivF; + case Op_DivVD: + return Op_DivD; + + case Op_AndV: + switch (bt) { + case T_BOOLEAN: + case T_CHAR: + case T_BYTE: + case T_SHORT: + case T_INT: + return Op_AndI; + case T_LONG: + return Op_AndL; + default: + return 0; + } + + case Op_OrV: + switch (bt) { + case T_BOOLEAN: + case T_CHAR: + case T_BYTE: + case T_SHORT: + case T_INT: + return Op_OrI; + case T_LONG: + return Op_OrL; + default: + return 0; + } + + case Op_XorV: + switch (bt) { + case T_BOOLEAN: + case T_CHAR: + case T_BYTE: + case T_SHORT: + case T_INT: + return Op_XorI; + case T_LONG: + return Op_XorL; + default: + return 0; + } + + case Op_MinV: + switch (bt) { + case T_BOOLEAN: + case T_CHAR: + // unsigned, not supported for Min + return 0; + case T_BYTE: + case T_SHORT: + case T_INT: + return Op_MinI; + case T_LONG: + return Op_MinL; + case T_FLOAT: + return Op_MinF; + case T_DOUBLE: + return Op_MinD; + default: + return 0; + } + + case Op_MaxV: + switch (bt) { + case T_BOOLEAN: + case T_CHAR: + // unsigned, not supported for Max + return 0; + case T_BYTE: + case T_SHORT: + case T_INT: + return Op_MaxI; + case T_LONG: + return Op_MaxL; + case T_FLOAT: + return Op_MaxF; + case T_DOUBLE: + return Op_MaxD; + default: + return 0; + } + + case Op_SqrtVD: + return Op_SqrtD; + case Op_SqrtVF: + return Op_SqrtF; + + case Op_FmaVF: + return Op_FmaF; + case Op_FmaVD: + return Op_FmaD; + + default: + return 0; // not handled } } @@ -984,17 +1125,9 @@ static Node* ideal_partial_operations(PhaseGVN* phase, Node* node, const TypeVec } } -bool VectorNode::should_swap_inputs_to_help_global_value_numbering() { - // Predicated vector operations are sensitive to ordering of inputs. - // When the mask corresponding to a vector lane is false then - // the result of the operation is corresponding lane of its first operand. - // i.e. RES = VEC1.lanewise(OPER, VEC2, MASK) is semantically equivalent to - // RES = BLEND(VEC1, VEC1.lanewise(OPER, VEC2), MASK) - if (is_predicated_vector()) { - return false; - } - - switch(Opcode()) { +// Check if the vector operation is commutative (assuming that it is not predicated/masked). +static bool is_commutative_vector_operation(int opcode) { + switch(opcode) { case Op_AddVB: case Op_AddVS: case Op_AddVI: @@ -1022,18 +1155,228 @@ bool VectorNode::should_swap_inputs_to_help_global_value_numbering() { case Op_XorVMask: case Op_SaturatingAddV: - assert(req() == 3, "Must be a binary operation"); - // For non-predicated commutative operations, sort the inputs in - // increasing order of node indices. - if (in(1)->_idx > in(2)->_idx) { - return true; - } - // fallthrough + return true; default: return false; } } +bool VectorNode::should_swap_inputs_to_help_global_value_numbering() { + // Predicated vector operations are sensitive to ordering of inputs. + // When the mask corresponding to a vector lane is false then + // the result of the operation is corresponding lane of its first operand. + // i.e. RES = VEC1.lanewise(OPER, VEC2, MASK) is semantically equivalent to + // RES = BLEND(VEC1, VEC1.lanewise(OPER, VEC2), MASK) + if (is_predicated_vector()) { + return false; + } + + if (is_commutative_vector_operation(Opcode())) { + assert(req() == 3, "Must be a binary operation"); + // For non-predicated commutative operations, sort the inputs in + // increasing order of node indices. + if (in(1)->_idx > in(2)->_idx) { + return true; + } + } + + return false; +} + +// Check whether we can push this vector op through replicate (all inputs are Replicate). +bool VectorNode::can_push_through_replicate(BasicType bt) { + if (scalar_opcode(Opcode(), bt) == 0) { + return false; + } + + // Skip over predicated vector operations for now, for masked lanes we preserve + // destination/first source vector contents. + if (is_predicated_vector()) { + return false; + } + + for (uint i = 1; i < req(); i++) { + if (in(i)->Opcode() != Op_Replicate) { + return false; + } + } + return true; +} + +Node* VectorNode::make_scalar(Compile* c, int vopc, BasicType bt, Node* control, Node* in1, Node* in2, Node* in3) { + int sopc = scalar_opcode(vopc, bt); + assert(sopc != 0, "unhandled vector opcode %s", NodeClassNames[vopc]); + assert(opcode(sopc, bt) == vopc, "scalar_opcode and opcode must agree for %s", NodeClassNames[vopc]); + switch (sopc) { + case Op_AddI: + return new AddINode(in1, in2); + case Op_AddL: + return new AddLNode(in1, in2); + case Op_AddF: + return new AddFNode(in1, in2); + case Op_AddD: + return new AddDNode(in1, in2); + case Op_MulI: + return new MulINode(in1, in2); + case Op_MulL: + return new MulLNode(in1, in2); + case Op_MulF: + return new MulFNode(in1, in2); + case Op_MulD: + return new MulDNode(in1, in2); + case Op_AndI: + return new AndINode(in1, in2); + case Op_AndL: + return new AndLNode(in1, in2); + case Op_DivF: + return new DivFNode(control, in1, in2); + case Op_DivD: + return new DivDNode(control, in1, in2); + case Op_OrI: + return new OrINode(in1, in2); + case Op_OrL: + return new OrLNode(in1, in2); + case Op_XorI: + return new XorINode(in1, in2); + case Op_XorL: + return new XorLNode(in1, in2); + case Op_SubI: + return new SubINode(in1, in2); + case Op_SubL: + return new SubLNode(in1, in2); + case Op_SubF: + return new SubFNode(in1, in2); + case Op_SubD: + return new SubDNode(in1, in2); + case Op_MinI: + return new MinINode(in1, in2); + case Op_MinL: + return new MinLNode(c, in1, in2); + case Op_MinF: + return new MinFNode(in1, in2); + case Op_MinD: + return new MinDNode(in1, in2); + case Op_MaxI: + return new MaxINode(in1, in2); + case Op_MaxL: + return new MaxLNode(c, in1, in2); + case Op_MaxF: + return new MaxFNode(in1, in2); + case Op_MaxD: + return new MaxDNode(in1, in2); + case Op_SqrtF: + return new SqrtFNode(c, control, in1); + case Op_SqrtD: + return new SqrtDNode(c, control, in1); + case Op_FmaF: + return new FmaFNode(in1, in2, in3); + case Op_FmaD: + return new FmaDNode(in1, in2, in3); + default: + assert(false, "unexpected scalar opcode"); + return nullptr; + } +} + +// Re-wires and creates a new ideal graph pallet with following connectivity +// parent(child(cinput1, cinput2), pinput2) +Node* VectorNode::create_reassociated_node(Node* parent, Node* child, Node* cinput1, Node* cinput2, + Node* pinput2, PhaseGVN* phase) { + Node* cloned_child = child->clone(); + cloned_child->set_req(1, cinput1); + cloned_child->set_req(2, cinput2); + cloned_child = phase->transform(cloned_child); + Node* cloned_parent = parent->clone(); + cloned_parent->set_req(1, cloned_child); + cloned_parent->set_req(2, pinput2); + return cloned_parent; +} + +// Try to reassociate commutative vector operations using the following ideal transformation, +// this will facilitate strength reducing a vector operation with all replicated inputs to +// a scalar operation. +// +// VectorOp (Replicate INP1) (VectorOp (Replicate INP2) INP3) => +// VectorOp (VectorOp (Replicate INP1) (Replicate INP2)) INP3 +// +Node* VectorNode::reassociate_vector_operation(PhaseGVN* phase) { + // Enable re-association for integral vector operations. + if (!is_integral_type(vect_type()->element_basic_type())) { + return nullptr; + } + + // Enable re-association for commutative vector operations. + if (!is_commutative_vector_operation(Opcode())) { + return nullptr; + } + + Node* in1 = in(1); + Node* in2 = in(2); + if (in2->Opcode() == Op_Replicate && in1->Opcode() == Opcode()) { + swap(in1, in2); + } + + if (in1->Opcode() != Op_Replicate || in2->Opcode() != Opcode()) { + return nullptr; + } + + // Skip predicated vector operations, mask semantics prevent reassociation. + if (is_predicated_vector() || in2->as_Vector()->is_predicated_vector()) { + return nullptr; + } + + Node* in2_1 = in2->in(1); + Node* in2_2 = in2->in(2); + if (in2_1->Opcode() == Op_Replicate) { + return create_reassociated_node(this, in2, in1, in2_1, in2_2, phase); + } else if (in2_2->Opcode() == Op_Replicate) { + return create_reassociated_node(this, in2, in1, in2_2, in2_1, phase); + } + + return nullptr; +} + +// Convert vector operation with all Replicate inputs to scalar operation using following +// ideal transformation. +// +// VectorOp (Replicate INP1, Replicate INP2) => +// Replicate (ScalarOp INP1, INP2) +// +Node* VectorNode::push_through_replicate(PhaseGVN* phase) { + BasicType bt = vect_type()->element_basic_type(); + if (!can_push_through_replicate(bt)) { + return nullptr; + } + + assert(req() >= 2 && req() <= 4, "unexpected req() %u for %s", req(), NodeClassNames[Opcode()]); + + Node* sinp1 = nullptr; + Node* sinp2 = nullptr; + Node* sinp3 = nullptr; + + assert(in(1)->Opcode() == Op_Replicate, ""); + sinp1 = in(1)->in(1); + + if (req() > 2) { + assert(in(2)->Opcode() == Op_Replicate, ""); + sinp2 = in(2)->in(1); + } + + if (req() > 3) { + assert(in(3)->Opcode() == Op_Replicate, ""); + sinp3 = in(3)->in(1); + } + + Node* sop = make_scalar(phase->C, Opcode(), bt, in(0), sinp1, sinp2, sinp3); + if (sop == nullptr) { + return nullptr; + } + + sop = phase->transform(sop); + + return new ReplicateNode(sop, vect_type()); +} + Node* VectorNode::Ideal(PhaseGVN* phase, bool can_reshape) { Node* n = ideal_partial_operations(phase, this, vect_type()); if (n != nullptr) { @@ -1044,7 +1387,13 @@ Node* VectorNode::Ideal(PhaseGVN* phase, bool can_reshape) { if (should_swap_inputs_to_help_global_value_numbering()) { swap_edges(1, 2); } - return nullptr; + + n = push_through_replicate(phase); + if (n != nullptr) { + return n; + } + + return reassociate_vector_operation(phase); } // Traverses a chain of VectorMaskCast and returns the first non VectorMaskCast node. @@ -2094,7 +2443,7 @@ Node* FmaVNode::Ideal(PhaseGVN* phase, bool can_reshape) { swap_edges(1, 2); return this; } - return nullptr; + return VectorNode::Ideal(phase, can_reshape); } // Generate other vector nodes to implement the masked/non-masked vector negation. diff --git a/src/hotspot/share/opto/vectornode.hpp b/src/hotspot/share/opto/vectornode.hpp index 897cedd6a1bf..6bcb7702d13a 100644 --- a/src/hotspot/share/opto/vectornode.hpp +++ b/src/hotspot/share/opto/vectornode.hpp @@ -146,12 +146,20 @@ class VectorNode : public TypeNode { static bool is_minmax_opcode(int opc); bool should_swap_inputs_to_help_global_value_numbering(); + Node* reassociate_vector_operation(PhaseGVN* phase); + static Node* create_reassociated_node(Node* parent, Node* child, Node* cinput1, Node* cinput2, + Node* pinput2, PhaseGVN* phase); static bool is_vshift_cnt_opcode(int opc); static bool is_rotate_opcode(int opc); static int opcode(int sopc, BasicType bt); // scalar_opc -> vector_opc + static int scalar_opcode(int vopc, BasicType bt); // vector_opc -> scalar_opc, 0 if not handled + static Node* make_scalar(Compile* c, int vopc, BasicType bt, Node* control, Node* in1, Node* in2, Node* in3); + + bool can_push_through_replicate(BasicType bt); + Node* push_through_replicate(PhaseGVN* phase); static int shift_count_opcode(int opc); diff --git a/src/hotspot/share/prims/jni.cpp b/src/hotspot/share/prims/jni.cpp index d6a435d34d1a..318f40374a6f 100644 --- a/src/hotspot/share/prims/jni.cpp +++ b/src/hotspot/share/prims/jni.cpp @@ -2835,6 +2835,10 @@ JNI_ENTRY(void*, jni_GetPrimitiveArrayCritical(JNIEnv *env, jarray array, jboole Handle a(thread, JNIHandles::resolve_non_null(array)); assert(a->is_typeArray(), "just checking"); + // We must defer JVM TI suspension while we have access to a Java object + // as it could surprise the debugger if we mutate it concurrently whilst + // logically suspended. + thread->enter_jni_deferred_suspension(); // Pin object Universe::heap()->pin_object(thread, a()); @@ -2852,6 +2856,7 @@ JNI_ENTRY(void, jni_ReleasePrimitiveArrayCritical(JNIEnv *env, jarray array, voi HOTSPOT_JNI_RELEASEPRIMITIVEARRAYCRITICAL_ENTRY(env, array, carray, mode); // Unpin object Universe::heap()->unpin_object(thread, JNIHandles::resolve_non_null(array)); + thread->exit_jni_deferred_suspension(); HOTSPOT_JNI_RELEASEPRIMITIVEARRAYCRITICAL_RETURN(); JNI_END @@ -2859,6 +2864,13 @@ JNI_END JNI_ENTRY(const jchar*, jni_GetStringCritical(JNIEnv *env, jstring string, jboolean *isCopy)) HOTSPOT_JNI_GETSTRINGCRITICAL_ENTRY(env, string, (uintptr_t *) isCopy); oop s = JNIHandles::resolve_non_null(string); + + // We must defer JVM TI suspension while we have access to a Java object. + // Even if we are taking a private copy we must not be considered + // suspended as the debugger could be mutating the string we are about + // to copy. + thread->enter_jni_deferred_suspension(); + jchar* ret; if (!java_lang_String::is_latin1(s)) { typeArrayHandle s_value(thread, java_lang_String::value(s)); @@ -2879,6 +2891,10 @@ JNI_ENTRY(const jchar*, jni_GetStringCritical(JNIEnv *env, jstring string, jbool ret[i] = ((jchar) s_value->byte_at(i)) & 0xff; } ret[s_len] = 0; + } else { + // If we return null there should not be a paired release operation + // so we have to cancel suspension deferral here. + thread->exit_jni_deferred_suspension(); } if (isCopy != nullptr) *isCopy = JNI_TRUE; } @@ -2904,6 +2920,7 @@ JNI_ENTRY(void, jni_ReleaseStringCritical(JNIEnv *env, jstring str, const jchar // Unpin value array Universe::heap()->unpin_object(thread, value); } + thread->exit_jni_deferred_suspension(); HOTSPOT_JNI_RELEASESTRINGCRITICAL_RETURN(); JNI_END diff --git a/src/hotspot/share/prims/whitebox.cpp b/src/hotspot/share/prims/whitebox.cpp index 6d3449098148..4bbaa9eda3cc 100644 --- a/src/hotspot/share/prims/whitebox.cpp +++ b/src/hotspot/share/prims/whitebox.cpp @@ -872,11 +872,11 @@ WB_ENTRY(jboolean, WB_IsMethodCompiled(JNIEnv* env, jobject o, jobject method, j return !code->is_marked_for_deoptimization(); WB_END -static bool is_excluded_for_compiler(AbstractCompiler* comp, methodHandle& mh) { +static bool is_excluded_for_compiler(AbstractCompiler* comp, int comp_level, methodHandle& mh) { if (comp == nullptr) { return true; } - CompilerDirectiveMatcher matcher(mh, comp); + CompilerDirectiveMatcher matcher(mh, comp_level); return matcher.directive_set()->ExcludeOption; } @@ -902,8 +902,10 @@ WB_ENTRY(jboolean, WB_IsMethodCompilable(JNIEnv* env, jobject o, jobject method, // to exclude a compilation of 'method'. if (comp_level == CompLevel_any) { // Both compilers could have ExcludeOption set. Check all combinations. - bool excluded_c1 = is_excluded_for_compiler(CompileBroker::compiler1(), mh); - bool excluded_c2 = is_excluded_for_compiler(CompileBroker::compiler2(), mh); + bool excluded_c1 = is_excluded_for_compiler(CompileBroker::compiler1(), CompLevel_simple, mh) + && is_excluded_for_compiler(CompileBroker::compiler1(), CompLevel_limited_profile, mh) + && is_excluded_for_compiler(CompileBroker::compiler1(), CompLevel_full_profile, mh); + bool excluded_c2 = is_excluded_for_compiler(CompileBroker::compiler2(), CompLevel_full_optimization, mh); if (excluded_c1 && excluded_c2) { // Compilation of 'method' excluded by both compilers. return false; @@ -914,9 +916,11 @@ WB_ENTRY(jboolean, WB_IsMethodCompilable(JNIEnv* env, jobject o, jobject method, return can_be_compiled_at_level(mh, is_osr, CompLevel_full_optimization); } else if (excluded_c2) { // C2 only has ExcludeOption set: Check if compilable with C1. - return can_be_compiled_at_level(mh, is_osr, CompLevel_simple); + return can_be_compiled_at_level(mh, is_osr, CompLevel_simple) + || can_be_compiled_at_level(mh, is_osr, CompLevel_limited_profile) + || can_be_compiled_at_level(mh, is_osr, CompLevel_full_profile); } - } else if (comp_level > CompLevel_none && is_excluded_for_compiler(CompileBroker::compiler((int)comp_level), mh)) { + } else if (comp_level > CompLevel_none && is_excluded_for_compiler(CompileBroker::compiler((int)comp_level), comp_level, mh)) { // Compilation of 'method' excluded by compiler used for 'comp_level'. return false; } @@ -952,7 +956,7 @@ WB_ENTRY(jboolean, WB_IsIntrinsicAvailable(JNIEnv* env, jobject o, jobject metho compilation_context_id = reflected_method_to_jmid(thread, env, compilation_context); CHECK_JNI_EXCEPTION_(env, JNI_FALSE); methodHandle cch(THREAD, Method::checked_resolve_jmethod_id(compilation_context_id)); - CompilerDirectiveMatcher matcher(cch, comp); + CompilerDirectiveMatcher matcher(cch, compLevel); return comp->is_intrinsic_available(mh, matcher.directive_set()); } else { // Calling with null matches default directive @@ -1132,7 +1136,7 @@ bool WhiteBox::compile_method(Method* method, int comp_level, int bci, JavaThrea // Check if compilation is blocking methodHandle mh(THREAD, method); - CompilerDirectiveMatcher matcher(mh, comp); + CompilerDirectiveMatcher matcher(mh, comp_level); bool is_blocking = !matcher.directive_set()->BackgroundCompilationOption; // Compile method and check result @@ -1151,7 +1155,7 @@ bool WhiteBox::compile_method(Method* method, int comp_level, int bci, JavaThrea } else if (mh->lookup_osr_nmethod_for(bci, comp_level, false) != nullptr) { return true; } - tty->print("WB error: failed to %s compile at level %d method ", is_blocking ? "blocking" : "", comp_level); + tty->print("WB error: failed to%s compile at level %d method ", is_blocking ? " blocking" : "", comp_level); mh->print_short_name(tty); tty->cr(); if (is_blocking && is_queued) { @@ -1184,7 +1188,7 @@ WB_ENTRY(jboolean, WB_ShouldPrintAssembly(JNIEnv* env, jobject o, jobject method CHECK_JNI_EXCEPTION_(env, JNI_FALSE); methodHandle mh(THREAD, Method::checked_resolve_jmethod_id(jmid)); - CompilerDirectiveMatcher matcher(mh, CompileBroker::compiler(comp_level)); + CompilerDirectiveMatcher matcher(mh, comp_level); return matcher.directive_set()->PrintAssemblyOption; WB_END diff --git a/src/hotspot/share/runtime/arguments.cpp b/src/hotspot/share/runtime/arguments.cpp index fe5222df345d..341e0b80a5fa 100644 --- a/src/hotspot/share/runtime/arguments.cpp +++ b/src/hotspot/share/runtime/arguments.cpp @@ -534,6 +534,7 @@ static SpecialFlag const special_jvm_flags[] = { { "UseSharedSpaces", JDK_Version::jdk(18), JDK_Version::jdk(19), JDK_Version::undefined() }, // --- Deprecated alias flags (see also aliased_jvm_flags) - sorted by obsolete_in then expired_in: { "CreateMinidumpOnCrash", JDK_Version::jdk(9), JDK_Version::undefined(), JDK_Version::undefined() }, + { "InitiatingHeapOccupancyPercent", JDK_Version::jdk(27), JDK_Version::jdk(28), JDK_Version::jdk(29) }, // -------------- Obsolete Flags - sorted by expired_in -------------- @@ -582,6 +583,7 @@ typedef struct { static AliasedFlag const aliased_jvm_flags[] = { { "CreateMinidumpOnCrash", "CreateCoredumpOnCrash" }, + G1GC_ONLY({"InitiatingHeapOccupancyPercent" COMMA "G1IHOP" } COMMA) { nullptr, nullptr} }; @@ -2717,6 +2719,10 @@ jint Arguments::finalize_vm_init_args() { return JNI_ERR; } + // Called after ClassLoader::lookup_vm_options() but before class loading begins. + // TODO: Obtain and pass correct preview mode flag value here. + ClassLoader::set_preview_mode(false); + if (!check_vm_args_consistency()) { return JNI_ERR; } diff --git a/src/hotspot/share/runtime/flags/jvmFlagConstraintsCompiler.cpp b/src/hotspot/share/runtime/flags/jvmFlagConstraintsCompiler.cpp index 36eece6f013f..ebcf6b4e14e3 100644 --- a/src/hotspot/share/runtime/flags/jvmFlagConstraintsCompiler.cpp +++ b/src/hotspot/share/runtime/flags/jvmFlagConstraintsCompiler.cpp @@ -61,9 +61,21 @@ JVMFlag::Error CICompilerCountConstraintFunc(intx value, bool verbose) { "at least %d \n", value, min_number_of_compiler_threads); return JVMFlag::VIOLATES_CONSTRAINT; - } else { - return JVMFlag::SUCCESS; } + + // Limit CICompilerCount to a reasonable value in product builds. +#ifndef ASSERT + int active_processor_count = os::active_processor_count(); + // On a single-CPU machine we still can run C1 and C2 compiler threads, so allow up to 2x for tiered. + int reasonable_threads_num = CompilerConfig::is_tiered() ? active_processor_count * 2 : active_processor_count; + if (value > reasonable_threads_num) { + JVMFlag::printError(verbose, "CICompilerCount is too large (%" PRIdPTR ") for current active processor count %d \n", + CICompilerCount, active_processor_count); + return JVMFlag::VIOLATES_CONSTRAINT; + } +#endif + + return JVMFlag::SUCCESS; } JVMFlag::Error AllocatePrefetchStepSizeConstraintFunc(int value, bool verbose) { diff --git a/src/hotspot/share/runtime/handshake.cpp b/src/hotspot/share/runtime/handshake.cpp index b54068d65d6f..d61be7631cbb 100644 --- a/src/hotspot/share/runtime/handshake.cpp +++ b/src/hotspot/share/runtime/handshake.cpp @@ -83,8 +83,10 @@ class HandshakeOperation : public CHeapObj { int32_t pending_threads() { return AtomicAccess::load(&_pending_threads); } const char* name() { return _handshake_cl->name(); } bool is_async() { return _handshake_cl->is_async(); } - bool is_suspend() { return _handshake_cl->is_suspend(); } + bool is_self_suspend() { return _handshake_cl->is_self_suspend(); } + bool is_suspend_request() { return _handshake_cl->is_suspend_request(); } bool is_async_exception() { return _handshake_cl->is_async_exception(); } + bool is_enabled() { return _handshake_cl->is_enabled(_target); } }; class AsyncHandshakeOperation : public HandshakeOperation { @@ -472,18 +474,24 @@ void Handshake::execute(AsyncHandshakeClosure* hs_cl, JavaThread* target) { } // Filters + +// op is enabled and can be executed by the current thread rather than the target. static bool non_self_executable_filter(HandshakeOperation* op) { - return !op->is_async(); + return !op->is_async() && op->is_enabled(); } +// op is not an async-exception op static bool no_async_exception_filter(HandshakeOperation* op) { return !op->is_async_exception(); } +// op is an async-exception op static bool async_exception_filter(HandshakeOperation* op) { return op->is_async_exception(); } +// op is not any kind of suspend op, nor an async-exception op static bool no_suspend_no_async_exception_filter(HandshakeOperation* op) { - return !op->is_suspend() && !op->is_async_exception(); + return !op->is_self_suspend() && !op->is_suspend_request() && !op->is_async_exception(); } +// All ops static bool all_ops_filter(HandshakeOperation* op) { return true; } @@ -521,8 +529,12 @@ HandshakeOperation* HandshakeState::get_op_for_self(bool allow_suspend, bool che assert(_lock.owned_by_self(), "Lock must be held"); assert(allow_suspend || !check_async_exception, "invalid case"); #if INCLUDE_JVMTI - if (allow_suspend && (_handshakee->is_disable_suspend() || _handshakee->is_vthread_transition_disabler())) { - // filter out suspend operations while JavaThread can not be suspended + // Filter out suspend operations while JavaThread can not be suspended. + // Potentially this could be folded into the `is_enabled` state of the operation + // and filtered directly through _queue.peek, but the incoming `allow_suspend` + // complicates that so we just maintain the explicit checks for now. + if (allow_suspend && (_handshakee->is_disable_suspend() || _handshakee->is_vthread_transition_disabler() || + _handshakee->jni_deferred_suspension())) { allow_suspend = false; } #endif @@ -565,12 +577,16 @@ void HandshakeState::clean_async_exception_operation() { } } +// Returns true if there is an enabled op that the current thread can execute +// on behalf of the handshakee. bool HandshakeState::have_non_self_executable_operation() { assert(_handshakee != Thread::current(), "Must not be called by self"); assert(_lock.owned_by_self(), "Lock must be held"); return _queue.contains(non_self_executable_filter); } +// Returns an enabled op that the current thread can execute +// on behalf of the handshakee. HandshakeOperation* HandshakeState::get_op() { assert(_handshakee != Thread::current(), "Must not be called by self"); assert(_lock.owned_by_self(), "Lock must be held"); @@ -683,7 +699,7 @@ HandshakeState::ProcessResult HandshakeState::try_process(HandshakeOperation* ma return HandshakeState::_not_safe; } - // Claim the mutex if there still an operation to be executed. + // Claim the mutex if there still an enabled operation to be executed. if (!claim_handshake()) { return HandshakeState::_claim_failed; } @@ -699,8 +715,14 @@ HandshakeState::ProcessResult HandshakeState::try_process(HandshakeOperation* ma Thread* current_thread = Thread::current(); HandshakeOperation* op = get_op(); + // It is possible that since we claimed the handshake the op has + // transitioned to a disabled state and so won't be returned by get_op. + if (op == nullptr) { + _lock.unlock(); + return HandshakeState::_no_operation; + } - assert(op != nullptr, "Must have an op"); + assert(op->is_enabled(), "Should not reach here with a disabled op"); assert(SafepointMechanism::local_poll_armed(_handshakee), "Must be"); assert(op->_target == nullptr || _handshakee == op->_target, "Wrong thread"); diff --git a/src/hotspot/share/runtime/handshake.hpp b/src/hotspot/share/runtime/handshake.hpp index c764bbcfcd23..cfdaf2e82df1 100644 --- a/src/hotspot/share/runtime/handshake.hpp +++ b/src/hotspot/share/runtime/handshake.hpp @@ -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 @@ -50,8 +50,10 @@ class HandshakeClosure : public ThreadClosure, public CHeapObj { virtual ~HandshakeClosure() {} const char* name() const { return _name; } virtual bool is_async() { return false; } - virtual bool is_suspend() { return false; } + virtual bool is_self_suspend() { return false; } + virtual bool is_suspend_request() { return false; } virtual bool is_async_exception() { return false; } + virtual bool is_enabled(Thread* target) { return true; } virtual void do_thread(Thread* thread) = 0; }; @@ -99,7 +101,6 @@ class HandshakeState { Monitor _lock; // Set to the thread executing the handshake operation. Thread* volatile _active_handshaker; - bool claim_handshake(); bool possibly_can_process_handshake(); bool can_process_handshake(); diff --git a/src/hotspot/share/runtime/java.cpp b/src/hotspot/share/runtime/java.cpp index 758051a73515..e501930b3a04 100644 --- a/src/hotspot/share/runtime/java.cpp +++ b/src/hotspot/share/runtime/java.cpp @@ -29,6 +29,7 @@ #include "cds/dynamicArchive.hpp" #include "classfile/classLoader.hpp" #include "classfile/classLoaderDataGraph.hpp" +#include "classfile/classPrinter.hpp" #include "classfile/javaClasses.hpp" #include "classfile/stringTable.hpp" #include "classfile/symbolTable.hpp" @@ -114,11 +115,16 @@ static int compare_methods(Method** a, Method** b) { return (diff < 0) ? -1 : (diff > 0) ? 1 : 0; } +inline CompLevel method_code_comp_level(const Method* m) { + const nmethod* code = m->code(); + return code != nullptr ? static_cast(code->comp_level()) : CompLevel_any; +} + static void collect_profiled_methods(Method* m) { Thread* thread = Thread::current(); methodHandle mh(thread, m); if ((m->method_data() != nullptr) && - (PrintMethodData || CompilerOracle::should_print(mh))) { + (PrintMethodData || CompilerOracle::should_print(mh, method_code_comp_level(m)))) { collected_profiled_methods->push(m); } } @@ -152,7 +158,7 @@ static void print_method_profiling_data() { m->method_data()->parameters_type_data()->print_data_on(&ss); } // Buffering to a stringStream, disable internal buffering so it's not done twice. - m->print_codes_on(&ss, 0, false); + m->print_codes_on(&ss, ClassPrinter::PRINT_METHOD_DATA, false); tty->print("%s", ss.as_string()); // print all at once total_size += m->method_data()->size_in_bytes(); } diff --git a/src/hotspot/share/runtime/javaThread.cpp b/src/hotspot/share/runtime/javaThread.cpp index 5a29a668a549..ec1c8f64619f 100644 --- a/src/hotspot/share/runtime/javaThread.cpp +++ b/src/hotspot/share/runtime/javaThread.cpp @@ -469,6 +469,7 @@ JavaThread::JavaThread(MemTag mem_tag) : _exception_handler_pc(nullptr), _jni_active_critical(0), + _jni_deferred_suspension_count(0), _pending_jni_exception_check_fn(nullptr), _depth_first_number(0), diff --git a/src/hotspot/share/runtime/javaThread.hpp b/src/hotspot/share/runtime/javaThread.hpp index bc6c0a3a4fda..5dd84fcc01d9 100644 --- a/src/hotspot/share/runtime/javaThread.hpp +++ b/src/hotspot/share/runtime/javaThread.hpp @@ -458,9 +458,12 @@ class JavaThread: public Thread { volatile address _exception_handler_pc; // PC for handler of exception private: - // support for JNI critical regions + // support for JNI critical regions interaction with GC jint _jni_active_critical; // count of entries into JNI critical region + // support for JNI critical regions deferral of JVM TI suspension + jint _jni_deferred_suspension_count; + // Checked JNI: function name requires exception check char* _pending_jni_exception_check_fn; @@ -980,10 +983,19 @@ class JavaThread: public Thread { _jni_active_critical--; assert(_jni_active_critical >= 0, "JNI critical nesting problem?"); } - // Atomic version; invoked by a thread other than the owning thread. bool in_critical_atomic() { return AtomicAccess::load(&_jni_active_critical) > 0; } + bool jni_deferred_suspension() { return AtomicAccess::load(&_jni_deferred_suspension_count); } + inline void enter_jni_deferred_suspension(); + void exit_jni_deferred_suspension() { + precond(Thread::current() == this); + int sc = AtomicAccess::load(&_jni_deferred_suspension_count); + AtomicAccess::store(&_jni_deferred_suspension_count, sc - 1); + assert(_jni_deferred_suspension_count >= 0, + "JNI deferred suspension nesting problem?"); + } + // Checked JNI: is the programmer required to check for exceptions, if so specify // which function name. Returning to a Java frame should implicitly clear the // pending check, this is done for Native->Java transitions (i.e. user JNI code). diff --git a/src/hotspot/share/runtime/javaThread.inline.hpp b/src/hotspot/share/runtime/javaThread.inline.hpp index 2c2ed2da2fae..5b4556b56668 100644 --- a/src/hotspot/share/runtime/javaThread.inline.hpp +++ b/src/hotspot/share/runtime/javaThread.inline.hpp @@ -196,6 +196,14 @@ void JavaThread::enter_critical() { _jni_active_critical++; } +void JavaThread::enter_jni_deferred_suspension() { + precond(JavaThread::current() == this); + assert(_thread_state != _thread_in_native && _thread_state != _thread_blocked, + "Must not defer suspension when handshake-safe"); + int sc = AtomicAccess::load(&_jni_deferred_suspension_count); + AtomicAccess::store(&_jni_deferred_suspension_count, sc + 1); +} + inline void JavaThread::set_done_attaching_via_jni() { _jni_attach_state = _attached_via_jni; OrderAccess::fence(); diff --git a/src/hotspot/share/runtime/sharedRuntime.cpp b/src/hotspot/share/runtime/sharedRuntime.cpp index e39ecb7c6c38..3d0ed5d2a23e 100644 --- a/src/hotspot/share/runtime/sharedRuntime.cpp +++ b/src/hotspot/share/runtime/sharedRuntime.cpp @@ -3194,7 +3194,7 @@ void AdapterHandlerLibrary::create_native_wrapper(const methodHandle& method) { } } - CompilerDirectiveMatcher matcher(method, CompileBroker::compiler(CompLevel_simple)); + CompilerDirectiveMatcher matcher(method, CompLevel_simple); if (matcher.directive_set()->PrintAssemblyOption) { nm->print_code(); } diff --git a/src/hotspot/share/runtime/stubCodeGenerator.cpp b/src/hotspot/share/runtime/stubCodeGenerator.cpp index 94825ba2d0bd..252f90e1bde3 100644 --- a/src/hotspot/share/runtime/stubCodeGenerator.cpp +++ b/src/hotspot/share/runtime/stubCodeGenerator.cpp @@ -255,8 +255,19 @@ void StubCodeGenerator::print_statistics_on(outputStream* st) { } #ifdef ASSERT -void StubCodeGenerator::verify_stub(StubId stub_id) { - assert(StubRoutines::stub_to_blob(stub_id) == blob_id(), "wrong blob %s for generation of stub %s", StubRoutines::get_blob_name(blob_id()), StubRoutines::get_stub_name(stub_id)); +void StubCodeGenerator::verify_stub(const char* name, StubId stub_id) { + if (_stub_data != nullptr) { + // we are collecting stub data for the current blob so the stub + // code should have been marked using a stub id + assert(stub_id != StubId::NO_STUBID, "StubCodeMark for stub %s must be declared with stub id as argument", name); + } + if (stub_id != StubId::NO_STUBID) { + // we may not always be collecting stub data but any stub id that + // is provided needs to belong to the current blob id and its name + // ought to have been retrieved via a call to StubInfo::name + assert(StubRoutines::stub_to_blob(stub_id) == blob_id(), "wrong blob %s for generation of stub %s", StubRoutines::get_blob_name(blob_id()), StubRoutines::get_stub_name(stub_id)); + assert(name == StubInfo::name(stub_id), "name %s does not match declared stub name %s", name, StubInfo::name(stub_id)); + } } #endif @@ -268,15 +279,15 @@ StubCodeMark::StubCodeMark(StubCodeGenerator* cgen, const char* group, const cha _cgen->stub_prolog(_cdesc); // define the stub's beginning (= entry point) to be after the prolog: _cdesc->set_begin(_cgen->assembler()->pc()); +#ifdef ASSERT + cgen->verify_stub(name, stub_id); +#endif } StubCodeMark::StubCodeMark(StubCodeGenerator* cgen, const char* group, const char* name) : StubCodeMark(cgen, group, name, StubId::NO_STUBID) { } StubCodeMark::StubCodeMark(StubCodeGenerator* cgen, StubId stub_id) : StubCodeMark(cgen, "StubRoutines", StubRoutines::get_stub_name(stub_id), stub_id) { -#ifdef ASSERT - cgen->verify_stub(stub_id); -#endif } StubCodeMark::~StubCodeMark() { diff --git a/src/hotspot/share/runtime/stubCodeGenerator.hpp b/src/hotspot/share/runtime/stubCodeGenerator.hpp index 9b94e24fd9b1..9f796b43fbae 100644 --- a/src/hotspot/share/runtime/stubCodeGenerator.hpp +++ b/src/hotspot/share/runtime/stubCodeGenerator.hpp @@ -188,7 +188,7 @@ class StubCodeGenerator: public StackObj { address load_archive_data(StubId stub_id, GrowableArray
*entries = nullptr, GrowableArray
* extras = nullptr); void store_archive_data(StubId stub_id, address start, address end, GrowableArray
*entries = nullptr, GrowableArray
* extras = nullptr); #ifdef ASSERT - void verify_stub(StubId stub_id); + void verify_stub(const char* name, StubId stub_id); #endif }; diff --git a/src/hotspot/share/runtime/suspendResumeManager.cpp b/src/hotspot/share/runtime/suspendResumeManager.cpp index 3408d763e573..3181c0f78aac 100644 --- a/src/hotspot/share/runtime/suspendResumeManager.cpp +++ b/src/hotspot/share/runtime/suspendResumeManager.cpp @@ -48,7 +48,7 @@ class ThreadSelfSuspensionHandshakeClosure : public AsyncHandshakeClosure { current->set_thread_state(jts); current->suspend_resume_manager()->set_async_suspend_handshake(false); } - virtual bool is_suspend() { return true; } + virtual bool is_self_suspend() { return true; } }; // This is the closure that synchronously honors the suspend request. @@ -64,6 +64,16 @@ class SuspendThreadHandshakeClosure : public HandshakeClosure { _did_suspend = target->suspend_resume_manager()->suspend_with_handshake(_register_vthread_SR); } bool did_suspend() { return _did_suspend; } + virtual bool is_suspend_request() { return true; } + + // If the target is in a JNI deferred suspension region, then we cannot + // process this operation. This must be checked with the HandshakeState lock + // held, which together with the fact the target only enters a deferred + // region from a handshake-unsafe state, means we cannot race with the + // target entering that region. + virtual bool is_enabled(Thread* target) { + return !JavaThread::cast(target)->jni_deferred_suspension(); + } }; void SuspendResumeManager::set_suspended(bool is_suspend, bool register_vthread_SR) { diff --git a/src/hotspot/share/utilities/unsigned5.hpp b/src/hotspot/share/utilities/unsigned5.hpp index 8e3724a0012a..1fc6aeaffb60 100644 --- a/src/hotspot/share/utilities/unsigned5.hpp +++ b/src/hotspot/share/utilities/unsigned5.hpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -280,6 +280,7 @@ class UNSIGNED5 : AllStatic { int len = next_length(); // 0 or length in [1..5] if (len == 0) break; _position += len; + ++actual; } return actual; } diff --git a/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM.java b/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM.java index 56a119893a79..6564f40545a3 100644 --- a/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM.java +++ b/src/java.base/share/classes/com/sun/crypto/provider/ML_KEM.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2024, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2024, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -527,6 +527,8 @@ protected ML_KEM_KeyPair generateKemKeyPair(byte[] kem_d_z) { } catch (DigestException e) { // This should never happen. throw new RuntimeException(e); + } finally { + mlKemH.reset(); } // The 2nd 32-byte `z` is copied into decapsKey System.arraycopy(kem_d_z, 32, decapsKey, @@ -562,7 +564,6 @@ protected ML_KEM_EncapsulateResult encapsulate( var randomCoins = Arrays.copyOfRange(kHatAndRandomCoins, 32, 64); var cipherText = kPkeEncrypt(new K_PKE_EncryptionKey(encapsulationKey.keyBytes), randomMessage, randomCoins); - Arrays.fill(randomCoins, (byte) 0); byte[] sharedSecret = Arrays.copyOfRange(kHatAndRandomCoins, 0, 32); Arrays.fill(kHatAndRandomCoins, (byte) 0); @@ -613,6 +614,7 @@ protected byte[] decapsulate(ML_KEM_DecapsulationKey decapsulationKey, var fakeResult = mlKemJ.digest(); var computedCipherText = kPkeEncrypt( new K_PKE_EncryptionKey(encapsKeyBytes), mCandidate, coins); + Arrays.fill(mCandidate, (byte)0); // The rest of this method implements the following in constant time // @@ -648,9 +650,11 @@ private K_PKE_KeyPair generateK_PkeKeyPair(byte[] seed) { MessageDigest mlKemG; SHAKE256 mlKemJ; + int cbdInputLen = 64 * mlKem_eta1; + byte[] cbdInput = new byte[cbdInputLen]; try { mlKemG = MessageDigest.getInstance(HASH_G_NAME); - mlKemJ = new SHAKE256(64 * mlKem_eta1); + mlKemJ = new SHAKE256(cbdInputLen); } catch (NoSuchAlgorithmException e) { // This should never happen. throw new RuntimeException(e); @@ -671,22 +675,26 @@ private K_PKE_KeyPair generateK_PkeKeyPair(byte[] seed) { int keyGenN = 0; byte[] prfSeed = new byte[sigma.length + 1]; System.arraycopy(sigma, 0, prfSeed, 0, sigma.length); - byte[] cbdInput; short[][] keyGenS = new short[mlKem_k][]; short[][] keyGenE = new short[mlKem_k][]; - for (int i = 0; i < mlKem_k; i++) { - prfSeed[sigma.length] = (byte) (keyGenN++); - mlKemJ.update(prfSeed); - cbdInput = mlKemJ.digest(); - keyGenS[i] = centeredBinomialDistribution(mlKem_eta1, cbdInput); - } - for (int i = 0; i < mlKem_k; i++) { - prfSeed[sigma.length] = (byte) (keyGenN++); - mlKemJ.update(prfSeed); - cbdInput = mlKemJ.digest(); - keyGenE[i] = centeredBinomialDistribution(mlKem_eta1, cbdInput); + try { + for (int i = 0; i < mlKem_k; i++) { + prfSeed[sigma.length] = (byte) (keyGenN++); + mlKemJ.update(prfSeed); + mlKemJ.digest(cbdInput, 0, cbdInputLen); + keyGenS[i] = centeredBinomialDistribution(mlKem_eta1, cbdInput); + } + for (int i = 0; i < mlKem_k; i++) { + prfSeed[sigma.length] = (byte) (keyGenN++); + mlKemJ.update(prfSeed); + mlKemJ.digest(cbdInput, 0, cbdInputLen); + keyGenE[i] = centeredBinomialDistribution(mlKem_eta1, cbdInput); + } + } catch (DigestException e) { + throw new ProviderException("Internal error", e); } Arrays.fill(sigma, (byte)0); + Arrays.fill(cbdInput, (byte)0); short[][] keyGenSHat = mlKemVectorNTT(keyGenS); mlKemVectorReduce(keyGenSHat); @@ -700,7 +708,6 @@ private K_PKE_KeyPair generateK_PkeKeyPair(byte[] seed) { for (int i = 0; i < mlKem_k; i++) { encodePoly12(keyGenTHat[i], pkEncoded, i * ((ML_KEM_N * 12) / 8)); encodePoly12(keyGenSHat[i], skEncoded, i * ((ML_KEM_N * 12) / 8)); - Arrays.fill(keyGenEHat[i], (short) 0); Arrays.fill(keyGenSHat[i], (short) 0); } System.arraycopy(rho, 0, @@ -723,39 +730,61 @@ private K_PKE_CipherText kPkeEncrypt( var encryptA = generateA(rho, true); short[][] encryptR = new short[mlKem_k][]; short[][] encryptE1 = new short[mlKem_k][]; + short[] encryptE2; int encryptN = 0; byte[] prfSeed = new byte[sigma.length + 1]; System.arraycopy(sigma, 0, prfSeed, 0, sigma.length); + Arrays.fill(sigma, (byte)0); - var kPkePRFeta1 = new SHAKE256(64 * mlKem_eta1); - var kPkePRFeta2 = new SHAKE256(64 * mlKem_eta2); - for (int i = 0; i < mlKem_k; i++) { - prfSeed[sigma.length] = (byte) (encryptN++); - kPkePRFeta1.update(prfSeed); - byte[] cbdInput = kPkePRFeta1.digest(); - encryptR[i] = centeredBinomialDistribution(mlKem_eta1, cbdInput); - } - for (int i = 0; i < mlKem_k; i++) { - prfSeed[sigma.length] = (byte) (encryptN++); + int cbdInput1Len = 64 * mlKem_eta1; + var kPkePRFeta1 = new SHAKE256(cbdInput1Len); + byte[] cbdInput1 = new byte[cbdInput1Len]; + int cbdInput2Len = 64 * mlKem_eta2; + var kPkePRFeta2 = new SHAKE256(cbdInput2Len); + byte[] cbdInput2 = new byte[cbdInput2Len]; + try { + for (int i = 0; i < mlKem_k; i++) { + prfSeed[sigma.length] = (byte) (encryptN++); + kPkePRFeta1.update(prfSeed); + kPkePRFeta1.digest(cbdInput1, 0, cbdInput1Len); + encryptR[i] = centeredBinomialDistribution(mlKem_eta1, cbdInput1); + } + for (int i = 0; i < mlKem_k; i++) { + prfSeed[sigma.length] = (byte) (encryptN++); + kPkePRFeta2.update(prfSeed); + kPkePRFeta2.digest(cbdInput2, 0, cbdInput2Len); + encryptE1[i] = centeredBinomialDistribution(mlKem_eta2, cbdInput2); + } + prfSeed[sigma.length] = (byte) encryptN; kPkePRFeta2.update(prfSeed); - byte[] cbdInput = kPkePRFeta2.digest(); - encryptE1[i] = centeredBinomialDistribution(mlKem_eta2, cbdInput); + kPkePRFeta2.digest(cbdInput2, 0, cbdInput2Len); + encryptE2 = centeredBinomialDistribution(mlKem_eta2, cbdInput2); + } catch (DigestException e) { + throw new ProviderException("Internal error", e); + } finally { + kPkePRFeta1.reset(); + kPkePRFeta2.reset(); + Arrays.fill(prfSeed, (byte)0); + Arrays.fill(cbdInput1, (byte)0); + Arrays.fill(cbdInput2, (byte)0); } - prfSeed[sigma.length] = (byte) encryptN; - kPkePRFeta2.reset(); - kPkePRFeta2.update(prfSeed); - byte[] cbdInput = kPkePRFeta2.digest(); - var encryptE2 = centeredBinomialDistribution(mlKem_eta2, cbdInput); var encryptRHat = mlKemVectorNTT(encryptR); var encryptUHat = mlKemMatrixVectorMuladd(encryptA, encryptRHat, zeroes); var encryptU = mlKemVectorInverseNTT(encryptUHat); encryptU = mlKemAddVec(encryptU, encryptE1); + + for (int i = 0; i < mlKem_k; i++) { + Arrays.fill(encryptE1[i], (short)0); + } + var encryptVHat = mlKemVectorScalarMult(encryptTHat, encryptRHat); var encryptV = mlKemInverseNTT(encryptVHat); encryptV = mlKemAddPoly(encryptV, encryptE2, decompressDecode(message)); var encryptC1 = encodeVector(mlKem_du, compressVector10_11(encryptU, mlKem_du)); var encryptC2 = encodePoly(mlKem_dv, compressPoly4_5(encryptV, mlKem_dv)); + Arrays.fill(encryptE2, (short)0); + Arrays.fill(encryptV, (short)0); byte[] result = new byte[encryptC1.length + encryptC2.length]; System.arraycopy(encryptC1, 0, @@ -783,9 +812,11 @@ private byte[] kPkeDecrypt(K_PKE_DecryptionKey privateKey, Arrays.fill(decryptSHat[i], (short) 0); } decryptV = mlKemSubtractPoly(decryptV, decryptSU); + var result = encodeCompress(decryptV); Arrays.fill(decryptSU, (short) 0); + Arrays.fill(decryptV, (short) 0); - return encodeCompress(decryptV); + return result; } /* diff --git a/src/java.base/share/classes/java/lang/LazyConstant.java b/src/java.base/share/classes/java/lang/LazyConstant.java index 85f9d0e82fd8..77d36bb2f52e 100644 --- a/src/java.base/share/classes/java/lang/LazyConstant.java +++ b/src/java.base/share/classes/java/lang/LazyConstant.java @@ -29,28 +29,35 @@ import jdk.internal.lang.LazyConstantImpl; import java.io.Serializable; -import java.util.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; + import java.util.function.Function; import java.util.function.IntFunction; import java.util.function.Supplier; /** - * A lazy constant is a holder of contents that can be set at most once. + * A lazy constant is a holder of content that can be initialized at most once. *

* A lazy constant is created using the factory method * {@linkplain LazyConstant#of(Supplier) LazyConstant.of({@code })}. *

- * When created, the lazy constant is not initialized, meaning it has no contents. + * When created, the lazy constant is not initialized, meaning it has no content. *

* The lazy constant (of type {@code T}) can then be initialized - * (and its contents retrieved) by calling {@linkplain #get() get()}. The first time + * (and its content retrieved) by calling {@linkplain #get() get()}. The first time * {@linkplain #get() get()} is called, the underlying computing function * (provided at construction) will be invoked and the result will be used to initialize * the constant. *

- * Once a lazy constant is initialized, its contents can never change - * and will be retrieved over and over again upon subsequent {@linkplain #get() get()} - * invocations. + * Once a lazy constant is initialized, its content can never change + * and will always be returned by subsequent {@linkplain #get() get()} invocations. *

* Consider the following example where a lazy constant field "{@code logger}" holds * an object of type {@code Logger}: @@ -83,12 +90,25 @@ * may result in storage resources being prepared. * *

Exception handling

- * If the computing function returns {@code null}, a {@linkplain NullPointerException} - * is thrown. Hence, a lazy constant can never hold a {@code null} value. Clients who - * want to use a nullable constant can wrap the value into an {@linkplain Optional} holder. + * If evaluation of the computing function throws an unchecked exception (i.e., a runtime + * exception or an error), the lazy constant is not initialized but instead transitions to + * an error state whereafter a {@linkplain NoSuchElementException} is thrown with the + * unchecked exception as a cause. Subsequent {@linkplain #get() get()} calls throw + * {@linkplain NoSuchElementException} (without ever invoking the computing function + * again) with no cause and with a message that includes the name of the original + * unchecked exception's class. + *

+ * All failures are handled in this way. There are two special cases that cause unchecked + * exceptions to be thrown: *

- * If the computing function recursively invokes itself via the lazy constant, an - * {@linkplain IllegalStateException} is thrown, and the lazy constant is not initialized. + * If the computing function returns {@code null}, a {@linkplain NoSuchElementException} + * (with a {@linkplain NullPointerException} as a cause) will be thrown. Hence, a + * lazy constant can never hold a {@code null} value. Clients who want to use a nullable + * constant can wrap the value into an {@linkplain Optional} holder. + *

+ * If the computing function recursively invokes itself via the lazy constant, a + * {@linkplain NoSuchElementException} (with an {@linkplain IllegalStateException} as a + * cause) will be thrown. * *

Composing lazy constants

* A lazy constant can depend on other lazy constants, forming a dependency graph @@ -98,31 +118,25 @@ * which are held by lazy constants: * * {@snippet lang = java: - * public final class DependencyUtil { - * - * private DependencyUtil() {} + * public static class Foo { + * // ... + * } * - * public static class Foo { + * public static class Bar { + * public Bar(Foo foo) { * // ... - * } - * - * public static class Bar { - * public Bar(Foo foo) { - * // ... - * } * } + * } * - * private static final LazyConstant FOO = LazyConstant.of( Foo::new ); - * private static final LazyConstant BAR = LazyConstant.of( () -> new Bar(FOO.get()) ); - * - * public static Foo foo() { - * return FOO.get(); - * } + * static final LazyConstant FOO = LazyConstant.of( Foo::new ); + * static final LazyConstant BAR = LazyConstant.of( () -> new Bar(FOO.get()) ); * - * public static Bar bar() { - * return BAR.get(); - * } + * public static Foo foo() { + * return FOO.get(); + * } * + * public static Bar bar() { + * return BAR.get(); * } * } * Calling {@code BAR.get()} will create the {@code Bar} singleton if it is not already @@ -134,13 +148,17 @@ * competing threads are racing to initialize a lazy constant, only one updating thread * runs the computing function (which runs on the caller's thread and is hereafter denoted * the computing thread), while the other threads are blocked until the constant - * is initialized, after which the other threads observe the lazy constant is initialized - * and leave the constant unchanged and will never invoke any computation. + * is initialized (or computation fails), after which the other threads observe the lazy + * constant is initialized (or has transisioned to an error state) and leave the constant + * unchanged and will never invoke any computation. *

* The invocation of the computing function and the resulting initialization of * the constant {@linkplain java.util.concurrent##MemoryVisibility happens-before} * the initialized constant's content is read. Hence, the initialized constant's content, * including any {@code final} fields of any newly created objects, is safely published. + * As subsequent retrieval of the content might be elided, there are no other memory + * ordering or visibility guarantees provided as a consequence of calling + * {@linkplain #get()} again. *

* Thread interruption does not cancel the initialization of a lazy constant. In other * words, if the computing thread is interrupted, {@code LazyConstant::get} doesn't clear @@ -150,9 +168,9 @@ * lazy constant may block indefinitely; no timeouts or cancellations are provided. * *

Performance

- * The contents of a lazy constant can never change after the lazy constant has been + * The content of a lazy constant can never change after the lazy constant has been * initialized. Therefore, a JVM implementation may, for an initialized lazy constant, - * elide all future reads of that lazy constant's contents and instead use the contents + * elide all future reads of that lazy constant's content and instead use the content * that has been previously observed. We call this optimization constant folding. * This is only possible if there is a direct reference from a {@code static final} field * to a lazy constant or if there is a chain from a {@code static final} field -- via one @@ -160,15 +178,10 @@ * {@linkplain Record record} fields, or final instance fields in hidden classes) -- * to a lazy constant. * - *

Miscellaneous

- * Except for {@linkplain Object#equals(Object) equals(obj)} and - * {@linkplain #orElse(Object) orElse(other)} parameters, all method parameters - * must be non-null, or a {@link NullPointerException} will be thrown. - * - * @apiNote Once a lazy constant is initialized, its contents cannot ever be removed. + * @apiNote Once a lazy constant is initialized, its content can't be removed. * This can be a source of an unintended memory leak. More specifically, * a lazy constant {@linkplain java.lang.ref##reachability strongly references} - * it contents. Hence, the contents of a lazy constant will be reachable as long + * its content. Hence, the content of a lazy constant will be reachable as long * as the lazy constant itself is reachable. *

* While it's possible to store an array inside a lazy constant, doing so will @@ -185,7 +198,7 @@ * @implNote * A lazy constant is free to synchronize on itself. Hence, care must be * taken when directly or indirectly synchronizing on a lazy constant. - * A lazy constant is unmodifiable but its contents may or may not be + * A lazy constant is unmodifiable but its content may or may not be * immutable (e.g., it may hold an {@linkplain ArrayList}). * * @param type of the constant @@ -205,39 +218,24 @@ public sealed interface LazyConstant permits LazyConstantImpl { /** - * {@return the contents of this lazy constant if initialized, otherwise, - * returns {@code other}} + * {@return the initialized content of this constant, computing it if necessary} *

- * This method never triggers initialization of this lazy constant and will observe - * initialization by other threads atomically (i.e., it returns the contents - * if and only if the initialization has already completed). - * - * @param other value to return if the content is not initialized - * (can be {@code null}) - */ - T orElse(T other); - - /** - * {@return the contents of this initialized constant. If not initialized, first - * computes and initializes this constant using the computing function} + * If this constant is not initialized, first computes and initializes it + * using the computing function. *

* After this method returns successfully, the constant is guaranteed to be * initialized. *

- * If the computing function throws, the throwable is relayed to the caller and - * the lazy constant remains uninitialized; a subsequent call to get() may then - * attempt the computation again. + * If an unchecked exception is thrown when evaluating the computing function or if + * the computing function returns {@code null}, this lazy constant is not initialized + * but transitions to an error state whereafter a {@linkplain NoSuchElementException} + * is thrown as described in the {@linkplain ##exception-handling Exception handling} + * section. + * + * @throws NoSuchElementException if this lazy constant is in an error state */ T get(); - /** - * {@return {@code true} if the constant is initialized, {@code false} otherwise} - *

- * This method never triggers initialization of this lazy constant and will observe - * changes in the initialization state made by other threads atomically. - */ - boolean isInitialized(); - // Object methods /** @@ -245,7 +243,7 @@ public sealed interface LazyConstant * the provided {@code obj}, otherwise {@code false}} *

* In other words, equals compares the identity of this lazy constant and {@code obj} - * to determine equality. Hence, two distinct lazy constants with the same contents are + * to determine equality. Hence, two distinct lazy constants with the same content are * not equal. *

* This method never triggers initialization of this lazy constant. @@ -267,11 +265,11 @@ public sealed interface LazyConstant *

* This method never triggers initialization of this lazy constant and will observe * initialization by other threads atomically (i.e., it observes the - * contents if and only if the initialization has already completed). + * content if and only if the initialization has already completed). *

* If this lazy constant is initialized, an implementation-dependent string * containing the {@linkplain Object#toString()} of the - * contents will be returned; otherwise, an implementation-dependent string is + * content will be returned; otherwise, an implementation-dependent string is * returned that indicates this lazy constant is not yet initialized. */ @Override @@ -280,31 +278,41 @@ public sealed interface LazyConstant // Factory /** - * {@return a lazy constant whose contents is to be computed later via the provided - * {@code computingFunction}} + * {@return a new lazy constant whose content is to be computed later via the + * provided {@code computingFunction}} *

* The returned lazy constant strongly references the provided - * {@code computingFunction} at least until initialization completes successfully. + * {@code computingFunction} until computation completes (successfully or with + * failure). *

- * If the provided computing function is already an instance of - * {@code LazyConstant}, the method is free to return the provided computing function - * directly. + * By design, the method always returns a new lazy constant even if the provided + * computing function is already an instance of {@code LazyConstant}. Clients that + * want to elide creation under this condition can write a utility method similar + * to the one in the snippet below and create lazy constants via this method rather + * than calling the built-in factory {@linkplain #of(Supplier)} directly: + * + * {@snippet lang = java: + * static LazyConstant ofFlattened(Supplier computingFunction) { + * return (computingFunction instanceof LazyConstant lc) + * ? (LazyConstant) lc // unchecked cast is safe under normal generic usage + * : LazyConstant.of(computingFunction); + * } + * } * - * @implNote after initialization completes successfully, the computing function is - * no longer strongly referenced and becomes eligible for - * garbage collection. + * @implNote after the computing function completes (regardless of whether it + * succeeds or throws an unchecked exception), the computing function is no + * longer strongly referenced and becomes eligible for garbage collection. * * @param computingFunction in the form of a {@linkplain Supplier} to be used * to initialize the constant * @param type of the constant + * @throws NullPointerException if the provided {@code computingFunction} is + * {@code null} * */ @SuppressWarnings("unchecked") static LazyConstant of(Supplier computingFunction) { Objects.requireNonNull(computingFunction); - if (computingFunction instanceof LazyConstant lc) { - return (LazyConstant) lc; - } return LazyConstantImpl.ofLazy(computingFunction); } diff --git a/src/java.base/share/classes/java/math/BigDecimal.java b/src/java.base/share/classes/java/math/BigDecimal.java index 6e651b4fde28..3cfe18e84c30 100644 --- a/src/java.base/share/classes/java/math/BigDecimal.java +++ b/src/java.base/share/classes/java/math/BigDecimal.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -155,6 +155,7 @@ * Multiplymultiplier.scale() + multiplicand.scale() * Dividedividend.scale() - divisor.scale() * Square rootceil(radicand.scale()/2.0) + * nth rootceil((double) radicand.scale()/n) * * * @@ -2142,149 +2143,224 @@ public BigDecimal[] divideAndRemainder(BigDecimal divisor, MathContext mc) { * @since 9 */ public BigDecimal sqrt(MathContext mc) { + return rootn(2, mc); + } + + /** + * Returns an approximation to the {@code n}th root of {@code this} + * with rounding according to the context settings. + * + *

The preferred scale of the returned result is equal to + * {@code Math.ceilDiv(this.scale(), n)}. The value of the returned result is + * always within one ulp of the exact decimal value for the + * precision in question. If the rounding mode is {@link + * RoundingMode#HALF_UP HALF_UP}, {@link RoundingMode#HALF_DOWN + * HALF_DOWN}, or {@link RoundingMode#HALF_EVEN HALF_EVEN}, the + * result is within one half an ulp of the exact decimal value. + * + *

Special case: + *

    + *
  • The {@code n}th root of a number numerically equal to {@code + * ZERO} is numerically equal to {@code ZERO} with a preferred + * scale according to the general rule above. In particular, for + * {@code ZERO}, {@code ZERO.rootn(n, mc).equals(ZERO)} is true with + * any {@code MathContext} as an argument. + *
+ * + * @param n the root degree + * @param mc the context to use. + * @return the {@code n}th root of {@code this}. + * @throws ArithmeticException if {@code n == 0 || n == Integer.MIN_VALUE}. + * @throws ArithmeticException if {@code n} is even and {@code this} is negative. + * @throws ArithmeticException if {@code n} is negative and {@code this} is zero. + * @throws ArithmeticException if an exact result is requested + * ({@code mc.getPrecision() == 0}) and there is no finite decimal + * expansion of the exact result + * @throws ArithmeticException if + * {@code (mc.getRoundingMode() == RoundingMode.UNNECESSARY}) and + * the exact result cannot fit in {@code mc.getPrecision()} digits. + * @see #sqrt(MathContext) + * @see BigInteger#rootn(int) + * @since 27 + * @apiNote Note that calling {@code rootn(2, mc)} is equivalent to calling {@code sqrt(mc)}. + */ + public BigDecimal rootn(int n, MathContext mc) { + // Special cases + if (n == 0) + throw new ArithmeticException("Zero root degree"); + final int signum = signum(); - if (signum != 1) { - switch (signum) { - case -1 -> throw new ArithmeticException("Attempted square root of negative BigDecimal"); - case 0 -> { - BigDecimal result = valueOf(0L, scale/2); - assert squareRootResultAssertions(result, mc); - return result; - } - default -> throw new AssertionError("Bad value from signum"); - } + if (signum < 0 && (n & 1) == 0) + throw new ArithmeticException("Negative radicand with even root degree"); + + final int preferredScale = saturateLong(Math.ceilDiv((long) this.scale, n)); + if (signum == 0) { + if (n < 0) + throw new ArithmeticException("Zero radicand with negative root degree"); + + return zeroValueOf(preferredScale); } + /* * The main steps of the algorithm below are as follows, * first argument reduce the value to an integer - * using the following relations: + * using the following relations, assuming n > 0: * * x = y * 10 ^ exp - * sqrt(x) = sqrt(y) * 10^(exp / 2) if exp is even - * sqrt(x) = sqrt(y*10) * 10^((exp-1)/2) is exp is odd + * rootn(x, n) = rootn(y, n) * 10^(exp / n) if exp mod n == 0 + * rootn(x, n) = rootn(y*10^(exp mod n), n) * 10^((exp - (exp mod n))/n) otherwise * - * Then use BigInteger.sqrt() on the reduced value to compute + * where exp mod n == Math.floorMod(exp, n). + * + * Then use BigInteger.rootn() on the reduced value to compute * the numerical digits of the desired result. * * Finally, scale back to the desired exponent range and * perform any adjustment to get the preferred scale in the * representation. */ - - // The code below favors relative simplicity over checking - // for special cases that could run faster. - final int preferredScale = Math.ceilDiv(this.scale, 2); - + final int nAbs = Math.absExact(n); BigDecimal result; if (mc.roundingMode == RoundingMode.UNNECESSARY || mc.precision == 0) { // Exact result requested // To avoid trailing zeros in the result, strip trailing zeros. final BigDecimal stripped = this.stripTrailingZeros(); - final int strippedScale = stripped.scale; - - if ((strippedScale & 1) != 0) // 10*stripped.unscaledValue() can't be an exact square - throw new ArithmeticException("Computed square root not exact."); - // Check for even powers of 10. Numerically sqrt(10^2N) = 10^N - if (stripped.isPowerOfTen()) { - result = valueOf(1L, strippedScale >> 1); - // Adjust to requested precision and preferred - // scale as appropriate. - return result.adjustToPreferredScale(preferredScale, mc.precision); - } + // if stripped.scale is not a multiple of n, + // 10^((-stripped.scale) mod n)*stripped.unscaledValue() can't be an exact power + if (stripped.scale % n != 0) + throw new ArithmeticException("Computed root not exact."); // After stripTrailingZeros, the representation is normalized as // // unscaledValue * 10^(-scale) // // where unscaledValue is an integer with the minimum - // precision for the cohort of the numerical value and the scale is even. - BigInteger[] sqrtRem = stripped.unscaledValue().sqrtAndRemainder(); - result = new BigDecimal(sqrtRem[0], strippedScale >> 1); - - // If result*result != this numerically or requires too high precision, - // the square root isn't exact - if (sqrtRem[1].signum != 0 || mc.precision != 0 && result.precision() > mc.precision) - throw new ArithmeticException("Computed square root not exact."); - - // Test numerical properties at full precision before any - // scale adjustments. - assert squareRootResultAssertions(result, mc); - // Adjust to requested precision and preferred - // scale as appropriate. + // precision for the cohort of the numerical value and the scale is a multiple of n. + BigInteger[] rootRem = stripped.unscaledValue().rootnAndRemainder(nAbs); + result = new BigDecimal(rootRem[0], stripped.scale / nAbs); + // If result^nAbs != this numerically, the root isn't exact + if (rootRem[1].signum != 0) + throw new ArithmeticException("Computed root not exact."); + + if (n > 0) { + // If result requires too high precision, the root isn't exact + if (mc.precision != 0 && result.precision() > mc.precision) + throw new ArithmeticException("Computed root not exact."); + } else { + try { + result = ONE.divide(result, mc); + } catch (ArithmeticException e) { + // The exact result requires too high precision, + // including non-terminating decimal expansions + throw new ArithmeticException("Computed root not exact."); + } + } + // Test numerical properties at full precision before any scale adjustments. + assert rootnResultAssertions(result, mc, n); + // Adjust to requested precision and preferred scale as appropriate. return result.adjustToPreferredScale(preferredScale, mc.precision); } - // To allow BigInteger.sqrt() to be used to get the square - // root, it is necessary to normalize the input so that - // its integer part is sufficient to get the square root + + // Handle negative radicands + BigDecimal x = this; + if (signum < 0) { + x = x.negate(); + if (mc.roundingMode == RoundingMode.FLOOR) { + mc = new MathContext(mc.precision, RoundingMode.UP); + } else if (mc.roundingMode == RoundingMode.CEILING) { + mc = new MathContext(mc.precision, RoundingMode.DOWN); + } + } + + // To allow BigInteger.rootn() to be used to get the root, + // it is necessary to normalize the input so that + // its integer part is sufficient to get the root // with the desired precision. final boolean halfWay = isHalfWay(mc.roundingMode); - // To obtain a square root with N digits, - // the radicand must have at least 2*(N-1)+1 == 2*N-1 digits. - final long minWorkingPrec = ((mc.precision + (halfWay ? 1L : 0L)) << 1) - 1L; - // normScale is the number of digits to take from the fraction of the input - long normScale = minWorkingPrec - this.precision() + this.scale; - normScale += normScale & 1L; // the scale for normalizing must be even - - final long workingScale = this.scale - normScale; - if (workingScale != (int) workingScale) - throw new ArithmeticException("Overflow"); - - BigDecimal working = new BigDecimal(this.intVal, this.intCompact, (int) workingScale, this.precision); - BigInteger workingInt = working.toBigInteger(); - - BigInteger sqrt; - long resultScale = normScale >> 1; - // Round sqrt with the specified settings + final long rootDigits = mc.precision + (halfWay ? 1L : 0L); + // To obtain an n-th root with k digits, + // the radicand must have at least n*(k-1)+1 digits. + final long minWorkingPrec = nAbs * (rootDigits - 1L) + 1L; + + long normScale; // the number of digits to take from the fraction of the input + BigDecimal working = null, xInv = null; + BigInteger workingInt; + if (n > 0) { + normScale = minWorkingPrec - x.precision() + x.scale; + int mod = Math.floorMod(normScale, n); + if (mod != 0) // the scale for normalizing must be a multiple of n + normScale += n - mod; + + working = new BigDecimal(x.intVal, x.intCompact, checkScaleNonZero(x.scale - normScale), x.precision); + workingInt = working.toBigInteger(); + } else { // Handle negative degrees + /* Computing the n-th root of x is equivalent + * to computing the (-n)-th root of 1/x. + */ + // Compute the scale for xInv, in order to ensure + // that xInv's precision is at least minWorkingPrec + final int fracZeros = x.precision() - 1 - (x.isPowerOfTen() ? 1 : 0); + normScale = minWorkingPrec + fracZeros - x.scale; + int mod = Math.floorMod(normScale, nAbs); + if (mod != 0) + normScale += nAbs - mod; + + xInv = ONE.divide(x, checkScaleNonZero(normScale), RoundingMode.DOWN); + workingInt = xInv.unscaledValue(); + } + + // Compute and round the root with the specified settings + BigInteger root; + long resultScale = normScale / nAbs; + boolean increment = false; if (halfWay) { // half-way rounding - BigInteger workingSqrt = workingInt.sqrt(); + BigInteger[] rootRem = workingInt.rootnAndRemainder(nAbs); // remove the one-tenth digit - BigInteger[] quotRem10 = workingSqrt.divideAndRemainder(BigInteger.TEN); - sqrt = quotRem10[0]; + BigInteger[] quotRem10 = rootRem[0].divideAndRemainder(BigInteger.TEN); + root = quotRem10[0]; resultScale--; - boolean increment = false; int digit = quotRem10[1].intValue(); if (digit > 5) { increment = true; } else if (digit == 5) { if (mc.roundingMode == RoundingMode.HALF_UP - || mc.roundingMode == RoundingMode.HALF_EVEN && sqrt.testBit(0) + || mc.roundingMode == RoundingMode.HALF_EVEN && root.testBit(0) // Check if remainder is non-zero - || !workingInt.equals(workingSqrt.multiply(workingSqrt)) - || !working.isInteger()) { + || rootRem[1].signum != 0 + || (n > 0 ? !working.isInteger() : xInv.multiply(x).compareMagnitude(ONE) != 0)) { increment = true; } } - - if (increment) - sqrt = sqrt.add(1L); } else { switch (mc.roundingMode) { - case DOWN, FLOOR -> sqrt = workingInt.sqrt(); // No need to round + case DOWN, FLOOR -> root = workingInt.rootn(nAbs); // No need to round case UP, CEILING -> { - BigInteger[] sqrtRem = workingInt.sqrtAndRemainder(); - sqrt = sqrtRem[0]; + BigInteger[] rootRem = workingInt.rootnAndRemainder(nAbs); + root = rootRem[0]; // Check if remainder is non-zero - if (sqrtRem[1].signum != 0 || !working.isInteger()) - sqrt = sqrt.add(1L); + if (rootRem[1].signum != 0 + || (n > 0 ? !working.isInteger() : xInv.multiply(x).compareMagnitude(ONE) != 0)) + increment = true; } default -> throw new AssertionError("Unexpected value for RoundingMode: " + mc.roundingMode); } } + if (increment) { + root = root.add(1L); + } - result = new BigDecimal(sqrt, checkScale(sqrt, resultScale), mc); // mc ensures no increase of precision - // Test numerical properties at full precision before any - // scale adjustments. - assert squareRootResultAssertions(result, mc); - // Adjust to requested precision and preferred - // scale as appropriate. - if (result.scale > preferredScale) // else can't increase the result's precision to fit the preferred scale + result = new BigDecimal(root, checkScale(root, resultScale), mc); // mc ensures no increase of precision + // Test numerical properties at full precision before any scale adjustments. + assert rootnResultAssertions(result, mc, n); + // Adjust to requested precision and preferred scale as appropriate. + if (result.scale > preferredScale) // else can't increase result's precision to fit the preferred scale result = stripZerosToMatchScale(result.intVal, result.intCompact, result.scale, preferredScale); - return result; + return signum > 0 ? result : result.negate(); } /** @@ -2315,12 +2391,8 @@ private static boolean isHalfWay(RoundingMode m) { }; } - private BigDecimal square() { - return this.multiply(this); - } - private boolean isPowerOfTen() { - return BigInteger.ONE.equals(this.unscaledValue()); + return this.stripTrailingZeros().unscaledValue().equals(BigInteger.ONE); } /** @@ -2331,92 +2403,102 @@ private boolean isPowerOfTen() { * *
    * - *
  • For DOWN and FLOOR, result^2 must be {@code <=} the input - * and (result+ulp)^2 must be {@code >} the input. + *
  • For DOWN and FLOOR if input > 0 and CEIL if input < 0, + * |result|^n must be {@code <=} |input| + * and (|result|+ulp)^n must be {@code >} |input|. * - *
  • Conversely, for UP and CEIL, result^2 must be {@code >=} - * the input and (result-ulp)^2 must be {@code <} the input. + *
  • Conversely, for UP and FLOOR if input < 0 and CEIL if input > 0, + * |result|^n must be {@code >=} |input| + * and (|result|-ulp)^n must be {@code <} |input|. *
*/ - private boolean squareRootResultAssertions(BigDecimal result, MathContext mc) { - if (result.signum() == 0) { - return squareRootZeroResultAssertions(result, mc); - } else { - RoundingMode rm = mc.getRoundingMode(); - BigDecimal ulp = result.ulp(); - BigDecimal neighborUp = result.add(ulp); - // Make neighbor down accurate even for powers of ten - if (result.isPowerOfTen()) { - ulp = ulp.divide(TEN); + private boolean rootnResultAssertions(BigDecimal result, MathContext mc, int n) { + BigDecimal rad = this.abs(), resAbs = result.abs(); + RoundingMode rm = mc.roundingMode; + if (this.signum() < 0) { + if (rm == RoundingMode.FLOOR) { + rm = RoundingMode.UP; + } else if (rm == RoundingMode.CEILING) { + rm = RoundingMode.DOWN; } - BigDecimal neighborDown = result.subtract(ulp); - - // Both the starting value and result should be nonzero and positive. - assert (result.signum() == 1 && - this.signum() == 1) : - "Bad signum of this and/or its sqrt."; - - switch (rm) { - case DOWN: - case FLOOR: - assert - result.square().compareTo(this) <= 0 && - neighborUp.square().compareTo(this) > 0: - "Square of result out for bounds rounding " + rm; - return true; + } - case UP: - case CEILING: - assert - result.square().compareTo(this) >= 0 && - neighborDown.square().compareTo(this) < 0: - "Square of result out for bounds rounding " + rm; - return true; + int nAbs = Math.abs(n); + BigDecimal ulp = resAbs.ulp(); + BigDecimal neighborUp = resAbs.add(ulp); + // Make neighbor down accurate even for powers of ten + if (resAbs.isPowerOfTen()) { + ulp = ulp.scaleByPowerOfTen(-1); + } + BigDecimal neighborDown = resAbs.subtract(ulp); + + switch (rm) { + case DOWN: + case FLOOR: + assert + (n > 0 ? resAbs.pow(nAbs).compareTo(rad) <= 0 && + neighborUp.pow(nAbs).compareTo(rad) > 0 + : resAbs.pow(nAbs).multiply(rad).compareTo(ONE) <= 0 && + neighborUp.pow(nAbs).multiply(rad).compareTo(ONE) > 0) + : "Power of result out for bounds rounding " + rm; + return true; + case UP: + case CEILING: + assert + (n > 0 ? resAbs.pow(nAbs).compareTo(rad) >= 0 && + neighborDown.pow(nAbs).compareTo(rad) < 0 + : resAbs.pow(nAbs).multiply(rad).compareTo(ONE) >= 0 && + neighborDown.pow(nAbs).multiply(rad).compareTo(ONE) < 0) + : "Power of result out for bounds rounding " + rm; + return true; - case HALF_DOWN: - case HALF_EVEN: - case HALF_UP: - BigDecimal err = result.square().subtract(this).abs(); - BigDecimal errUp = neighborUp.square().subtract(this); - BigDecimal errDown = this.subtract(neighborDown.square()); - // All error values should be positive so don't need to - // compare absolute values. - - int err_comp_errUp = err.compareTo(errUp); - int err_comp_errDown = err.compareTo(errDown); - - assert - errUp.signum() == 1 && - errDown.signum() == 1 : - "Errors of neighbors squared don't have correct signs"; - - // For breaking a half-way tie, the return value may - // have a larger error than one of the neighbors. For - // example, the square root of 2.25 to a precision of - // 1 digit is either 1 or 2 depending on how the exact - // value of 1.5 is rounded. If 2 is returned, it will - // have a larger rounding error than its neighbor 1. - assert - err_comp_errUp <= 0 || - err_comp_errDown <= 0 : - "Computed square root has larger error than neighbors for " + rm; - - assert - ((err_comp_errUp == 0 ) ? err_comp_errDown < 0 : true) && - ((err_comp_errDown == 0 ) ? err_comp_errUp < 0 : true) : - "Incorrect error relationships"; - // && could check for digit conditions for ties too - return true; - default: // Definition of UNNECESSARY already verified. - return true; + case HALF_DOWN: + case HALF_EVEN: + case HALF_UP: + BigDecimal err, errUp, errDown; + if (n > 0) { + err = resAbs.pow(nAbs).subtract(rad).abs(); + errUp = neighborUp.pow(nAbs).subtract(rad); + errDown = rad.subtract(neighborDown.pow(nAbs)); + } else { + err = resAbs.pow(nAbs).multiply(rad).subtract(ONE).abs(); + errUp = neighborUp.pow(nAbs).multiply(rad).subtract(ONE); + errDown = ONE.subtract(neighborDown.pow(nAbs).multiply(rad)); } - } - } - private boolean squareRootZeroResultAssertions(BigDecimal result, MathContext mc) { - return this.compareTo(ZERO) == 0; + // All error values should be positive + // so don't need to compare absolute values. + int err_comp_errUp = err.compareTo(errUp); + int err_comp_errDown = err.compareTo(errDown); + + assert + errUp.signum() == 1 && + errDown.signum() == 1 + : "Errors of neighbors powered don't have correct signs"; + + // For breaking a half-way tie, the return value may + // have a larger error than one of the neighbors. For + // example, the square root of 2.25 to a precision of + // 1 digit is either 1 or 2 depending on how the exact + // value of 1.5 is rounded. If 2 is returned, it will + // have a larger rounding error than its neighbor 1. + assert + err_comp_errUp <= 0 || + err_comp_errDown <= 0 : + "Computed root has larger error than neighbors for " + rm; + + assert + ((err_comp_errUp == 0 ) ? err_comp_errDown < 0 : true) && + ((err_comp_errDown == 0 ) ? err_comp_errUp < 0 : true) : + "Incorrect error relationships"; + // && could check for digit conditions for ties too + return true; + + default: // Definition of UNNECESSARY already verified. + return true; + } } /** diff --git a/src/java.base/share/classes/java/net/Socket.java b/src/java.base/share/classes/java/net/Socket.java index 42ca5314b78e..4c91a6ffce61 100644 --- a/src/java.base/share/classes/java/net/Socket.java +++ b/src/java.base/share/classes/java/net/Socket.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1995, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1995, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -1632,16 +1632,15 @@ public void close() throws IOException { } /** - * Places the input stream for this socket at "end of stream". - * Any data sent to the input stream side of the socket is acknowledged - * and then silently discarded. + * Shutdown the connection for reading without closing the socket. *

- * If you read from a socket input stream after invoking this method on the - * socket, the stream's {@code available} method will return 0, and its - * {@code read} methods will return {@code -1} (end of stream). + * If you read from a {@linkplain Socket#getInputStream() socket input stream} + * after invoking this method, the stream's {@code available} method will + * return {@code 0}, and its {@code read} methods will return {@code -1} (end of stream). * * @throws IOException if an I/O error occurs when shutting down this socket, the - * socket is not connected or the socket is closed. + * socket is not connected, the socket is already shutdown for reading, + * or the socket is closed. * * @since 1.3 * @see java.net.Socket#shutdownOutput() @@ -1662,16 +1661,14 @@ public void shutdownInput() throws IOException { } /** - * Disables the output stream for this socket. - * For a TCP socket, any previously written data will be sent - * followed by TCP's normal connection termination sequence. - * - * If you write to a socket output stream after invoking - * shutdownOutput() on the socket, the stream will throw - * an IOException. + * Shutdown the connection for writing without closing the socket. + *

+ * If you write to a {@linkplain Socket#getOutputStream() socket output stream} + * after invoking this method, the stream will throw an {@code IOException}. * - * @throws IOException if an I/O error occurs when shutting down this socket, the socket - * is not connected or the socket is closed. + * @throws IOException if an I/O error occurs when shutting down this socket, the + * socket is not connected, the socket is already shutdown for writing, + * or the socket is closed. * * @since 1.3 * @see java.net.Socket#shutdownInput() @@ -1710,10 +1707,9 @@ public String toString() { /** * Returns the connection state of the socket. *

- * Note: Closing a socket doesn't clear its connection state, which means + * {@linkplain #close() Closing} a socket doesn't clear its connection state, which means * this method will return {@code true} for a closed socket - * (see {@link #isClosed()}) if it was successfully connected prior - * to being closed. + * if it was successfully connected prior to being closed. * * @return true if the socket was successfully connected to a server * @since 1.4 @@ -1725,10 +1721,9 @@ public boolean isConnected() { /** * Returns the binding state of the socket. *

- * Note: Closing a socket doesn't clear its binding state, which means + * {@linkplain #close() Closing} a socket doesn't clear its binding state, which means * this method will return {@code true} for a closed socket - * (see {@link #isClosed()}) if it was successfully bound prior - * to being closed. + * if it was successfully bound prior to being closed. * * @return true if the socket was successfully bound to an address * @since 1.4 @@ -1750,22 +1745,22 @@ public boolean isClosed() { } /** - * Returns whether the read-half of the socket connection is closed. + * Returns {@code true} if the socket was shutdown for reading. * - * @return true if the input of the socket has been shutdown + * @return true only if a prior call to {@link #shutdownInput()} completed successfully, + * false otherwise * @since 1.4 - * @see #shutdownInput */ public boolean isInputShutdown() { return isInputShutdown(state); } /** - * Returns whether the write-half of the socket connection is closed. + * Returns {@code true} if the socket was shutdown for writing. * - * @return true if the output of the socket has been shutdown + * @return true only if a prior call to {@link #shutdownOutput()} completed successfully, + * false otherwise * @since 1.4 - * @see #shutdownOutput */ public boolean isOutputShutdown() { return isOutputShutdown(state); diff --git a/src/java.base/share/classes/java/text/AttributedString.java b/src/java.base/share/classes/java/text/AttributedString.java index 0333efde81b5..52e5b5dba3c9 100644 --- a/src/java.base/share/classes/java/text/AttributedString.java +++ b/src/java.base/share/classes/java/text/AttributedString.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -46,7 +46,6 @@ * @see Annotation * @since 1.2 */ - public class AttributedString { // field holding the text String text; @@ -54,12 +53,11 @@ public class AttributedString { // Fields holding run attribute information. // Run attributes are organized by run. // Arrays are always of equal lengths (the current capacity). - // Since there are no vectors of int, we have to use arrays. + // Since there are not yet Lists of unboxed int, we use arrays. private static final int INITIAL_CAPACITY = 10; - int runCount; // actual number of runs, <= current capacity - int[] runStarts; // start index for each run - Vector[] runAttributes; // vector of attribute keys for each run - Vector[] runAttributeValues; // parallel vector of attribute values for each run + int runCount; // actual number of runs, <= current capacity + int[] runStarts; // start index for each run + Map[] runAttributes; // attributes for each run /** * Constructs an AttributedString instance with the given @@ -152,16 +150,8 @@ public AttributedString(String text, int attributeCount = attributes.size(); if (attributeCount > 0) { - createRunAttributeDataVectors(); - Vector newRunAttributes = new Vector<>(attributeCount); - Vector newRunAttributeValues = new Vector<>(attributeCount); - runAttributes[0] = newRunAttributes; - runAttributeValues[0] = newRunAttributeValues; - - for (Map.Entry entry : attributes.entrySet()) { - newRunAttributes.addElement(entry.getKey()); - newRunAttributeValues.addElement(entry.getValue()); - } + createRunAttributeDataArrays(); + runAttributes[0] = new HashMap<>(attributes); } } @@ -250,12 +240,13 @@ public AttributedString(AttributedCharacterIterator text, // Select attribute keys to be taken care of HashSet keys = new HashSet<>(); + Set textKeys = text.getAllAttributeKeys(); if (attributes == null) { - keys.addAll(text.getAllAttributeKeys()); + keys.addAll(textKeys); } else { for (int i = 0; i < attributes.length; i++) keys.add(attributes[i]); - keys.retainAll(text.getAllAttributeKeys()); + keys.retainAll(textKeys); } if (keys.isEmpty()) return; @@ -376,9 +367,9 @@ public void addAttributes(Map attributes, throw new IllegalArgumentException("Can't add attribute to 0-length text"); } - // make sure we have run attribute data vectors + // make sure we have run attribute data arrays if (runCount == 0) { - createRunAttributeDataVectors(); + createRunAttributeDataArrays(); } // break up runs if necessary @@ -393,9 +384,9 @@ public void addAttributes(Map attributes, private synchronized void addAttributeImpl(Attribute attribute, Object value, int beginIndex, int endIndex) { - // make sure we have run attribute data vectors + // make sure we have run attribute data arrays if (runCount == 0) { - createRunAttributeDataVectors(); + createRunAttributeDataArrays(); } // break up runs if necessary @@ -405,19 +396,14 @@ private synchronized void addAttributeImpl(Attribute attribute, Object value, addAttributeRunData(attribute, value, beginRunIndex, endRunIndex); } - private final void createRunAttributeDataVectors() { + private final void createRunAttributeDataArrays() { // use temporary variables so things remain consistent in case of an exception int[] newRunStarts = new int[INITIAL_CAPACITY]; - @SuppressWarnings("unchecked") - Vector[] newRunAttributes = (Vector[]) new Vector[INITIAL_CAPACITY]; - - @SuppressWarnings("unchecked") - Vector[] newRunAttributeValues = (Vector[]) new Vector[INITIAL_CAPACITY]; + Map[] newRunAttributes = (Map[]) new Map[INITIAL_CAPACITY]; runStarts = newRunStarts; runAttributes = newRunAttributes; - runAttributeValues = newRunAttributeValues; runCount = 1; // assume initial run starting at index 0 } @@ -463,29 +449,21 @@ private final int ensureRunBreak(int offset, boolean copyAttrs) { // use temporary variables so things remain consistent in case of an exception int[] newRunStarts = Arrays.copyOf(runStarts, newCapacity); - Vector[] newRunAttributes = + Map[] newRunAttributes = Arrays.copyOf(runAttributes, newCapacity); - Vector[] newRunAttributeValues = - Arrays.copyOf(runAttributeValues, newCapacity); runStarts = newRunStarts; runAttributes = newRunAttributes; - runAttributeValues = newRunAttributeValues; } // make copies of the attribute information of the old run that the new one used to be part of // use temporary variables so things remain consistent in case of an exception - Vector newRunAttributes = null; - Vector newRunAttributeValues = null; + Map newRunAttributes = null; if (copyAttrs) { - Vector oldRunAttributes = runAttributes[runIndex - 1]; - Vector oldRunAttributeValues = runAttributeValues[runIndex - 1]; + Map oldRunAttributes = runAttributes[runIndex - 1]; if (oldRunAttributes != null) { - newRunAttributes = new Vector<>(oldRunAttributes); - } - if (oldRunAttributeValues != null) { - newRunAttributeValues = new Vector<>(oldRunAttributeValues); + newRunAttributes = new HashMap<>(oldRunAttributes); } } @@ -494,11 +472,9 @@ private final int ensureRunBreak(int offset, boolean copyAttrs) { for (int i = runCount - 1; i > runIndex; i--) { runStarts[i] = runStarts[i - 1]; runAttributes[i] = runAttributes[i - 1]; - runAttributeValues[i] = runAttributeValues[i - 1]; } runStarts[runIndex] = offset; runAttributes[runIndex] = newRunAttributes; - runAttributeValues[runIndex] = newRunAttributeValues; return runIndex; } @@ -508,32 +484,13 @@ private void addAttributeRunData(Attribute attribute, Object value, int beginRunIndex, int endRunIndex) { for (int i = beginRunIndex; i < endRunIndex; i++) { - int keyValueIndex = -1; // index of key and value in our vectors; assume we don't have an entry yet - if (runAttributes[i] == null) { - Vector newRunAttributes = new Vector<>(); - Vector newRunAttributeValues = new Vector<>(); + Map attributes = runAttributes[i]; + if (attributes == null) { + Map newRunAttributes = new HashMap<>(); runAttributes[i] = newRunAttributes; - runAttributeValues[i] = newRunAttributeValues; - } else { - // check whether we have an entry already - keyValueIndex = runAttributes[i].indexOf(attribute); - } - - if (keyValueIndex == -1) { - // create new entry - int oldSize = runAttributes[i].size(); - runAttributes[i].addElement(attribute); - try { - runAttributeValues[i].addElement(value); - } - catch (Exception e) { - runAttributes[i].setSize(oldSize); - runAttributeValues[i].setSize(oldSize); - } - } else { - // update existing entry - runAttributeValues[i].set(keyValueIndex, value); + attributes = newRunAttributes; } + attributes.put(attribute, value); } } @@ -596,18 +553,11 @@ private char charAt(int index) { } private synchronized Object getAttribute(Attribute attribute, int runIndex) { - Vector currentRunAttributes = runAttributes[runIndex]; - Vector currentRunAttributeValues = runAttributeValues[runIndex]; + Map currentRunAttributes = runAttributes[runIndex]; if (currentRunAttributes == null) { return null; } - int attributeIndex = currentRunAttributes.indexOf(attribute); - if (attributeIndex != -1) { - return currentRunAttributeValues.elementAt(attributeIndex); - } - else { - return null; - } + return currentRunAttributes.get(attribute); } // gets an attribute value, but returns an annotation only if it's range does not extend outside the range beginIndex..endIndex @@ -680,22 +630,11 @@ private final void appendContents(StringBuilder buf, */ private void setAttributes(Map attrs, int offset) { if (runCount == 0) { - createRunAttributeDataVectors(); + createRunAttributeDataArrays(); } - int index = ensureRunBreak(offset, false); - int size; - - if (attrs != null && (size = attrs.size()) > 0) { - Vector runAttrs = new Vector<>(size); - Vector runValues = new Vector<>(size); - - for (Map.Entry entry : attrs.entrySet()) { - runAttrs.add(entry.getKey()); - runValues.add(entry.getValue()); - } - runAttributes[index] = runAttrs; - runAttributeValues[index] = runValues; + if (attrs != null && !attrs.isEmpty()) { + runAttributes[index] = new HashMap<>(attrs); } } @@ -945,18 +884,13 @@ public Set getAllAttributeKeys() { // ??? should try to create this only once, then update if necessary, // and give callers read-only view Set keys = new HashSet<>(); - int i = 0; - while (i < runCount) { + for (int i = 0; i < runCount; i++) { if (runStarts[i] < endIndex && (i == runCount - 1 || runStarts[i + 1] > beginIndex)) { - Vector currentRunAttributes = runAttributes[i]; + Map currentRunAttributes = runAttributes[i]; if (currentRunAttributes != null) { - int j = currentRunAttributes.size(); - while (j-- > 0) { - keys.add(currentRunAttributes.get(j)); - } + keys.addAll(currentRunAttributes.keySet()); } } - i++; } return keys; } @@ -1040,10 +974,10 @@ private final class AttributeMap extends AbstractMap { public Set> entrySet() { HashSet> set = new HashSet<>(); synchronized (AttributedString.this) { - int size = runAttributes[runIndex].size(); - for (int i = 0; i < size; i++) { - Attribute key = runAttributes[runIndex].get(i); - Object value = runAttributeValues[runIndex].get(i); + Map attributes = runAttributes[runIndex]; + for (Map.Entry entry : attributes.entrySet()) { + Attribute key = entry.getKey(); + Object value = entry.getValue(); if (value instanceof Annotation) { value = AttributedString.this.getAttributeCheckRange(key, runIndex, beginIndex, endIndex); @@ -1051,9 +985,7 @@ public Set> entrySet() { continue; } } - - Map.Entry entry = new AttributeEntry(key, value); - set.add(entry); + set.add(new AttributeEntry(key, value)); } } return set; diff --git a/src/java.base/share/classes/java/util/LazyCollections.java b/src/java.base/share/classes/java/util/LazyCollections.java index 0bbdad87ac45..e15721db2ce2 100644 --- a/src/java.base/share/classes/java/util/LazyCollections.java +++ b/src/java.base/share/classes/java/util/LazyCollections.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,20 +25,21 @@ package java.util; +import jdk.internal.lang.LazyConstantImpl; import jdk.internal.misc.Unsafe; import jdk.internal.util.ImmutableBitSetPredicate; import jdk.internal.vm.annotation.AOTSafeClassInitializer; import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.Stable; -import java.lang.LazyConstant; import java.lang.reflect.Array; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.IntFunction; import java.util.function.IntPredicate; -import java.util.function.Supplier; +import java.util.function.Predicate; /** * Container class for lazy collections implementations. Not part of the public API. @@ -54,6 +55,7 @@ private LazyCollections() { } // Unsafe allows LazyCollection classes to be used early in the boot sequence private static final Unsafe UNSAFE = Unsafe.getUnsafe(); + @jdk.internal.vm.annotation.TrustFinalFields @jdk.internal.ValueBased static final class LazyList extends ImmutableCollections.AbstractImmutableList { @@ -62,18 +64,17 @@ static final class LazyList private final E[] elements; // Keeping track of `size` separately reduces bytecode size compared to // using `elements.length`. - @Stable private final int size; - @Stable - final FunctionHolder> functionHolder; - @Stable + private final FunctionHolder> functionHolder; private final Mutexes mutexes; + private final Throwables throwables; private LazyList(int size, IntFunction computingFunction) { this.elements = newGenericArray(size); this.size = size; this.functionHolder = new FunctionHolder<>(computingFunction, size); this.mutexes = new Mutexes(size); + this.throwables = new Throwables(size); super(); } @@ -89,7 +90,7 @@ public E get(int i) { } private E getSlowPath(int i) { - return orElseComputeSlowPath(elements, i, mutexes, i, functionHolder); + return orElseComputeSlowPath(elements, i, mutexes, throwables, i, functionHolder); } @Override @@ -109,6 +110,7 @@ public T[] toArray(T[] a) { @Override public int indexOf(Object o) { + Objects.requireNonNull(o); for (int i = 0; i < size; i++) { if (Objects.equals(o, get(i))) { return i; @@ -119,6 +121,7 @@ public int indexOf(Object o) { @Override public int lastIndexOf(Object o) { + Objects.requireNonNull(o); for (int i = size - 1; i >= 0; i--) { if (Objects.equals(o, get(i))) { return i; @@ -143,16 +146,14 @@ private E contentsAcquire(long offset) { } - static final class LazyEnumMap, V> + @jdk.internal.vm.annotation.TrustFinalFields + private static final class LazyEnumMap, V> extends AbstractLazyMap { - @Stable private final Class enumType; - @Stable // We are using a wrapper class here to be able to use a min value of zero that // is also stable. private final Integer min; - @Stable private final IntPredicate member; public LazyEnumMap(Set set, @@ -182,29 +183,26 @@ public V getOrDefault(Object key, V defaultValue) { if (member.test(ordinal)) { @SuppressWarnings("unchecked") final K k = (K) key; - return orElseCompute(k, indexForAsInt(k)); + return orElseCompute(k, indexFor(k)); } } return defaultValue; } + @ForceInline @Override - Integer indexFor(K key) { - return indexForAsInt(key); - } - - private int indexForAsInt(K key) { + int indexFor(K key) { return key.ordinal() - min; } } - static final class LazyMap + @jdk.internal.vm.annotation.TrustFinalFields + private static final class LazyMap extends AbstractLazyMap { // Use an unmodifiable map with known entries that are @Stable. Lookups through this map can be folded because - // it is created using Map.ofEntrie. This allows us to avoid creating a separate hashing function. - @Stable + // it is created using Map.ofEntries. This allows us to avoid creating a separate hashing function. private final Map indexMapper; public LazyMap(Set keys, Function computingFunction) { @@ -232,29 +230,34 @@ public V getOrDefault(Object key, V defaultValue) { @Override public boolean containsKey(Object o) { return indexMapper.containsKey(o); } + @ForceInline @Override - Integer indexFor(K key) { + // This method will throw an NPE if the key does not exist. So, callers need to + // make sure the key exist before invoking this method. + int indexFor(K key) { return indexMapper.get(key); } } - static sealed abstract class AbstractLazyMap + @jdk.internal.vm.annotation.TrustFinalFields + private static abstract sealed class AbstractLazyMap extends ImmutableCollections.AbstractImmutableMap { - // This field shadows AbstractMap.keySet which is not @Stable. - @Stable - Set keySet; - // This field shadows AbstractMap.values which is of another type - @Stable - final V[] values; - @Stable - Mutexes mutexes; - @Stable + private final Mutexes mutexes; + private final Throwables throwables; private final int size; + private final FunctionHolder> functionHolder; + private final Set> entrySet; + // This field shadows AbstractMap.values which is of another type @Stable - final FunctionHolder> functionHolder; + private final V[] values; + // This field shadows AbstractMap.keySet which is not trusted + private final Set keySet; + + // We are using a `long` here to get stable access even in the case + // that the 32-bit hash code is zero. @Stable - private final Set> entrySet; + private long hash; private AbstractLazyMap(Set keySet, int size, @@ -264,14 +267,15 @@ private AbstractLazyMap(Set keySet, this.functionHolder = new FunctionHolder<>(computingFunction, size); this.values = newGenericArray(backingSize); this.mutexes = new Mutexes(backingSize); - super(); + this.throwables = new Throwables(backingSize); this.keySet = keySet; + super(); this.entrySet = LazyMapEntrySet.of(this); } // Abstract methods @Override public abstract boolean containsKey(Object o); - abstract Integer indexFor(K key); + abstract int indexFor(K key); // Public methods @Override public final int size() { return size; } @@ -279,6 +283,45 @@ private AbstractLazyMap(Set keySet, @Override public final Set> entrySet() { return entrySet; } @Override public Set keySet() { return keySet; } + @Override + public final boolean containsValue(Object value) { + Objects.requireNonNull(value); + for (K key : keySet) { + if (value.equals(orElseCompute(key, indexFor(key)))) { + return true; + } + } + return false; + } + + @Override + public final int hashCode() { + // Racy computation + long h = hash; + if (h == 0) { + // Set a bit in the upper 32-bit region of the `long` to + // cater for the case the lower 32-bit hash is zero. + hash = h = expandToLong(hashCode0()); + } + return reduceToInt(h); + } + + private int hashCode0() { + int hash = 0; + for (K key : keySet) { + hash += key.hashCode() ^ orElseCompute(key, indexFor(key)).hashCode(); + } + return hash; + } + + @Override + public final void forEach(BiConsumer action) { + Objects.requireNonNull(action); + for (K key : keySet) { + action.accept(key, orElseCompute(key, indexFor(key))); + } + } + @ForceInline @Override public final V get(Object key) { @@ -293,15 +336,15 @@ final V orElseCompute(K key, int index) { if (v != null) { return v; } - return orElseComputeSlowPath(values, index, mutexes, key, functionHolder); + return orElseComputeSlowPath(values, index, mutexes, throwables, key, functionHolder); } + @jdk.internal.vm.annotation.TrustFinalFields @jdk.internal.ValueBased - static final class LazyMapEntrySet extends ImmutableCollections.AbstractImmutableSet> { + private static final class LazyMapEntrySet extends ImmutableCollections.AbstractImmutableSet> { // Use a separate field for the outer class in order to facilitate - // a @Stable annotation. - @Stable + // a trusted field. private final AbstractLazyMap map; private LazyMapEntrySet(AbstractLazyMap map) { @@ -318,14 +361,13 @@ private static LazyMapEntrySet of(AbstractLazyMap outer) { return new LazyMapEntrySet<>(outer); } + @jdk.internal.vm.annotation.TrustFinalFields @jdk.internal.ValueBased static final class LazyMapIterator implements Iterator> { // Use a separate field for the outer class in order to facilitate - // a @Stable annotation. - @Stable + // a trusted field. private final AbstractLazyMap map; - @Stable private final Iterator keyIterator; private LazyMapIterator(AbstractLazyMap map) { @@ -334,21 +376,22 @@ private LazyMapIterator(AbstractLazyMap map) { super(); } - @Override public boolean hasNext() { return keyIterator.hasNext(); } + @Override public boolean hasNext() { return keyIterator.hasNext(); } @Override public Entry next() { final K k = keyIterator.next(); - return new LazyEntry<>(k, map, map.functionHolder); + return new LazyEntry<>(k, map); } @Override public void forEachRemaining(Consumer> action) { + Objects.requireNonNull(action); final Consumer innerAction = new Consumer<>() { @Override public void accept(K key) { - action.accept(new LazyEntry<>(key, map, map.functionHolder)); + action.accept(new LazyEntry<>(key, map)); } }; keyIterator.forEachRemaining(innerAction); @@ -362,13 +405,12 @@ private static LazyMapIterator of(AbstractLazyMap map) { } } - private record LazyEntry(K getKey, // trick - AbstractLazyMap map, - FunctionHolder> functionHolder) implements Entry { + private record LazyEntry(@Override K getKey, // trick + AbstractLazyMap map) implements Entry { @Override public V setValue(V value) { throw ImmutableCollections.uoe(); } @Override public V getValue() { return map.orElseCompute(getKey, map.indexFor(getKey)); } - @Override public int hashCode() { return hash(getKey()) ^ hash(getValue()); } + @Override public int hashCode() { return Objects.hashCode(getKey()) ^ Objects.hashCode(getValue()); } @Override public String toString() { return getKey() + "=" + getValue(); } @Override @@ -379,9 +421,6 @@ public boolean equals(Object o) { && Objects.equals(getValue(), e.getValue()); } - private int hash(Object obj) { - return (obj == null) ? 0 : obj.hashCode(); - } } @Override @@ -389,12 +428,12 @@ public Collection values() { return LazyMapValues.of(this); } + @jdk.internal.vm.annotation.TrustFinalFields @jdk.internal.ValueBased static final class LazyMapValues extends ImmutableCollections.AbstractImmutableCollection { // Use a separate field for the outer class in order to facilitate - // a @Stable annotation. - @Stable + // a trusted field. private final AbstractLazyMap map; private LazyMapValues(AbstractLazyMap map) { @@ -416,7 +455,133 @@ private static LazyMapValues of(AbstractLazyMap outer) { } - static final class Mutexes { + @jdk.internal.vm.annotation.TrustFinalFields + private static final class LazySet + extends ImmutableCollections.AbstractImmutableSet + implements Set { + + private final Map map; + + // -1 is used as a sentinel value for zero so we can get + // stable access for all `size` values. `size` is always non-negative. + @Stable + private int size; + // We are using a `long` here to get stable access even in the case + // that the 32-bit hash code is zero. + @Stable + private long hash; + + public LazySet(Set elementCandidates, + Predicate computingFunction) { + this.map = Map.ofLazy(elementCandidates, computingFunction::test); + super(); + } + + @Override + public boolean contains(Object o) { + return map.getOrDefault(o, Boolean.FALSE).booleanValue(); + } + + @Override + public int hashCode() { + // Racy computation + long h = hash; + if (h == 0) { + // Set a bit in the upper 32-bit region of the `long` to + // cater for the case the lower 32-bit hash is zero. + hash = h = expandToLong(hashCode0()); + } + return reduceToInt(h); + } + + private int hashCode0() { + int hash = 0; + for (var e: map.entrySet()) { + if (e.getValue()) { + hash += e.getKey().hashCode(); + } + } + return hash; + } + + @Override + public Iterator iterator() { + return new LazySetIterator<>(map.entrySet().iterator()); + } + + @jdk.internal.vm.annotation.TrustFinalFields + static final class LazySetIterator implements Iterator { + + private final Iterator> iterator; + + E current; + + public LazySetIterator(Iterator> iterator) { + this.iterator = iterator; + super(); + } + + @Override + public boolean hasNext() { + if (current != null) { + return true; + } + while (iterator.hasNext()) { + Map.Entry e = iterator.next(); + if (e.getValue()) { + current = e.getKey(); + return true; + } + } + return false; + } + + @Override + public E next() { + E e = current; + if (e != null) { + return consumeCurrent(e); + } + if (!hasNext()) { + throw new NoSuchElementException(); + } + return consumeCurrent(current); + } + + private E consumeCurrent(E e) { + current = null; + return e; + } + + } + + @Override + public int size() { + // Racy computation + int s = size; + if (s == 0) { + s = size0(); + if (s == 0) { + s = -1; + } + size = s; + } + return s == -1 ? 0 : s; + } + + private int size0() { + int size = 0; + for (var e: map.entrySet()) { + if (e.getValue()) { + size++; + } + } + return size; + } + + } + + private static final class Mutexes { private static final Object TOMB_STONE = new Object(); @@ -431,9 +596,15 @@ private Mutexes(int length) { this.counter = new AtomicInteger(length); } - @ForceInline private Object acquireMutex(long offset) { - assert mutexes != null; + // Snapshot + var mutexes = this.mutexes; + if (mutexes == null) { + // We have already computed all the elements and if we end up here + // there was at least one unchecked exception thrown by the + // computing function. + return null; + } // Check if there already is a mutex (Object or TOMB_STONE) final Object mutex = UNSAFE.getReferenceVolatile(mutexes, offset); if (mutex != null) { @@ -447,7 +618,7 @@ private Object acquireMutex(long offset) { private void releaseMutex(long offset) { // Replace the old mutex with a tomb stone since now the old mutex can be collected. - UNSAFE.putReference(mutexes, offset, TOMB_STONE); + UNSAFE.putReferenceVolatile(mutexes, offset, TOMB_STONE); if (counter != null && counter.decrementAndGet() == 0) { mutexes = null; counter = null; @@ -456,6 +627,33 @@ private void releaseMutex(long offset) { } + /** Holds the throwable class names produced by the computing function. + *

+ * Class names are used instead of Class objects to avoid pinning class loaders after + * a failed computation. + *

+ * This class is not thread safe across indices. However, it will always be accessed + * under the same monitor for a given index. + */ + private static final class Throwables { + + @Stable + final String[] throwables; + + Throwables(int size) { + this.throwables = new String[size]; + super(); + } + + Optional get(int index) { + return Optional.ofNullable(throwables[index]); + } + + void set(int index, Throwable throwable) { + throwables[index] = throwable.getClass().getName().intern(); + } + } + @ForceInline private static long offsetFor(long index) { return Unsafe.ARRAY_OBJECT_BASE_OFFSET + Unsafe.ARRAY_OBJECT_INDEX_SCALE * index; @@ -466,75 +664,81 @@ private static E[] newGenericArray(int length) { return (E[]) new Object[length]; } - public static List ofLazyList(int size, - IntFunction computingFunction) { - return new LazyList<>(size, computingFunction); - } - - public static Map ofLazyMap(Set keys, - Function computingFunction) { - return new LazyMap<>(keys, computingFunction); - } - - @SuppressWarnings("unchecked") - public static , V> - Map ofLazyMapWithEnumKeys(Set keys, - Function computingFunction) { - // The input set is not empty - final Class enumType = ((E) keys.iterator().next()).getDeclaringClass(); - final BitSet bitSet = new BitSet(enumType.getEnumConstants().length); - int min = Integer.MAX_VALUE; - int max = Integer.MIN_VALUE; - for (K t : keys) { - final int ordinal = ((E) t).ordinal(); - min = Math.min(min, ordinal); - max = Math.max(max, ordinal); - bitSet.set(ordinal); - } - final int backingSize = max - min + 1; - final IntPredicate member = ImmutableBitSetPredicate.of(bitSet); - return (Map) new LazyEnumMap<>((Set) keys, enumType, min, backingSize, member, (Function) computingFunction); - } @SuppressWarnings("unchecked") - static T orElseComputeSlowPath(final T[] array, + private static T orElseComputeSlowPath(final T[] array, final int index, final Mutexes mutexes, + final Throwables throwables, final Object input, final FunctionHolder functionHolder) { final long offset = offsetFor(index); final Object mutex = mutexes.acquireMutex(offset); - preventReentry(mutex); + if (mutex == null) { + throwIfPreviousException(index, throwables, input); + // There must be an exception + throw cannotReachHere(functionHolder, input); + } + preventReentry(mutex, input); synchronized (mutex) { final T t = array[index]; // Plain semantics suffice here if (t == null) { - final T newValue = switch (functionHolder.function()) { - case IntFunction iFun -> (T) iFun.apply((int) input); - case Function fun -> ((Function) fun).apply(input); - default -> throw new InternalError("cannot reach here"); - }; - Objects.requireNonNull(newValue); - // Reduce the counter and if it reaches zero, clear the reference - // to the underlying holder. - functionHolder.countDown(); - - // The mutex is not reentrant so we know newValue should be returned - set(array, index, mutex, newValue); - // We do not need the mutex anymore - mutexes.releaseMutex(offset); - return newValue; + throwIfPreviousException(index, throwables, input); + try { + final T newValue = switch (functionHolder.function()) { + case IntFunction iFun -> (T) iFun.apply((int) input); + case Function fun -> ((Function) fun).apply(input); + default -> throw cannotReachHere(functionHolder, input); + }; + Objects.requireNonNull(newValue); + + // The mutex is not reentrant so we know newValue should be returned + set(array, index, mutex, newValue); + return newValue; + } catch (Throwable x) { + throwables.set(index, x); + // Wrap the initial throwable without pinning its class loader. + throw noSuchElementException(x.getClass().getName(), input, x); + } finally { + // Reduce the counter and if it reaches zero, clear the reference + // to the underlying holder. + functionHolder.countDown(); + + // We do not need the mutex anymore + mutexes.releaseMutex(offset); + } } return t; } } - static void preventReentry(Object mutex) { + private static void throwIfPreviousException(int index, Throwables throwables, Object input) { + final var throwable = throwables.get(index); + if (throwable.isPresent()) { + throw noSuchElementException(throwable.get(), input, null); + } + } + + private static NoSuchElementException noSuchElementException(String throwableName, + Object input, + Throwable cause) { + final String isolatedToString = LazyConstantImpl.isolateToString(input); + var message = "Unable to access the lazy collection because " + throwableName + + " was thrown at initial computation for input '" + isolatedToString + "'"; + return new NoSuchElementException(message, cause); + } + + private static InternalError cannotReachHere(FunctionHolder functionHolder, Object input) { + return new InternalError("cannot reach here: " + functionHolder.function() + " for " + LazyConstantImpl.isolateToString(input)); + } + + private static void preventReentry(Object mutex, Object input) { if (Thread.holdsLock(mutex)) { - throw new IllegalStateException("Recursive initialization of a lazy collection is illegal"); + throw new IllegalStateException("Recursive initialization of a lazy collection is illegal: " + LazyConstantImpl.isolateToString(input)); } } - static void set(T[] array, int index, Object mutex, T newValue) { + private static void set(T[] array, int index, Object mutex, T newValue) { assert Thread.holdsLock(mutex) : index + "didn't hold " + mutex; // We know we hold the monitor here so plain semantic is enough // This is an extra safety net to emulate a CAS op. @@ -551,7 +755,7 @@ static void set(T[] array, int index, Object mutex, T newValue) { * @param the underlying function type */ @AOTSafeClassInitializer - static final class FunctionHolder { + private static final class FunctionHolder { private static final long COUNTER_OFFSET = UNSAFE.objectFieldOffset(FunctionHolder.class, "counter"); @@ -581,4 +785,50 @@ public void countDown() { } } + // Methods for supporting stable `int` values using a `long` field. + + private static long expandToLong(int value) { + return (value + (1L << 33)); + } + + private static int reduceToInt(long value) { + return (int) value; + } + + // Factories + + static List ofLazyList(int size, + IntFunction computingFunction) { + return new LazyList<>(size, computingFunction); + } + + static Map ofLazyMap(Set keys, + Function computingFunction) { + return new LazyMap<>(keys, computingFunction); + } + + static Set ofLazySet(Set elementCandidates, + Predicate computingFunction) { + return new LazySet<>(elementCandidates, computingFunction); + } + + @SuppressWarnings("unchecked") + static , V> Map ofLazyMapWithEnumKeys(Set keys, + Function computingFunction) { + // The input set is not empty + final Class enumType = ((E) keys.iterator().next()).getDeclaringClass(); + final BitSet bitSet = new BitSet(enumType.getEnumConstants().length); + int min = Integer.MAX_VALUE; + int max = Integer.MIN_VALUE; + for (K t : keys) { + final int ordinal = ((E) t).ordinal(); + min = Math.min(min, ordinal); + max = Math.max(max, ordinal); + bitSet.set(ordinal); + } + final int backingSize = max - min + 1; + final IntPredicate member = ImmutableBitSetPredicate.of(bitSet); + return (Map) new LazyEnumMap<>((Set) keys, enumType, min, backingSize, member, (Function) computingFunction); + } + } diff --git a/src/java.base/share/classes/java/util/List.java b/src/java.base/share/classes/java/util/List.java index 5f9a90e1748e..a906215f16b5 100644 --- a/src/java.base/share/classes/java/util/List.java +++ b/src/java.base/share/classes/java/util/List.java @@ -1200,23 +1200,36 @@ static List copyOf(Collection coll) { * {@return a new lazily computed list of the provided {@code size}} *

* The returned list is an {@linkplain Collection##unmodifiable unmodifiable} list - * with the provided {@code size}. The list's elements are lazily computed via the - * provided {@code computingFunction} when they are first accessed + * for which the elements are lazily computed via the provided + * {@code computingFunction} when they are first accessed * (e.g., via {@linkplain List#get(int) List::get}). *

- * The provided computing function is guaranteed to be successfully + * The provided computing function is guaranteed to be * invoked at most once per list index, even in a multi-threaded environment. * Competing threads accessing an element already under computation will block until * an element is computed or the computing function completes abnormally. *

- * If invoking the provided computing function throws an exception, it is rethrown - * to the initial caller and no value for the element is recorded. + * If evaluation of the provided computing function throws an unchecked exception (for + * an index), the lazy element is not initialized but instead transitions to an error + * state whereafter a {@linkplain NoSuchElementException} is thrown with the unchecked + * exception as a cause. Subsequent {@linkplain List#get(int) List::get} calls for the + * same index throw {@linkplain NoSuchElementException} (without ever invoking the + * computing function again) with no cause and with a message that includes the name + * of the original unchecked exception's class. *

- * If the provided computing function returns {@code null}, - * a {@linkplain NullPointerException} will be thrown. Hence, just like other - * unmodifiable lists created via the {@code List::of} factories, a lazy list - * cannot contain {@code null} elements. Clients that want to use nullable elements - * can wrap elements into an {@linkplain Optional} holder. + * All failures are handled in this way. There are two special cases that cause + * unchecked exceptions to be thrown: + *

+ * If the computing function returns {@code null}, + * a {@linkplain NoSuchElementException} (with a {@linkplain NullPointerException} as + * a cause) will be thrown. Hence, just like other unmodifiable lists created via the + * {@code List::of} factories, a lazy list can never contain {@code null} + * elements. Clients that want to use nullable elements can wrap elements into an + * {@linkplain Optional} holder. + *

+ * If the computing function recursively invokes itself (for the same index) via the + * returned lazy list, a {@linkplain NoSuchElementException} + * (with an {@linkplain IllegalStateException} as a cause) will be thrown. *

* The elements of any {@link List#subList(int, int) subList()} or * {@link List#reversed()} views of the returned list are also lazily computed. @@ -1224,31 +1237,78 @@ static List copyOf(Collection coll) { * The returned list and its {@link List#subList(int, int) subList()} or * {@link List#reversed()} views implement the {@link RandomAccess} interface. *

- * If the provided computing function recursively calls itself via the returned - * lazy list for the same index, an {@linkplain IllegalStateException} - * will be thrown. - *

* The returned list's {@linkplain Object Object methods}; * {@linkplain Object#equals(Object) equals()}, * {@linkplain Object#hashCode() hashCode()}, and * {@linkplain Object#toString() toString()} methods may trigger initialization of - * one or more lazy elements. + * one or more lazy elements. If initialization fails for at least one element, + * the {@linkplain Object Object methods} may throw {@linkplain NoSuchElementException}. *

* The returned lazy list strongly references its computing * function used to compute elements at least as long as there are uninitialized * elements. *

* The returned List is not {@linkplain Serializable}. + *

+ * Here is an example involving an application that maintains three separate + * {@code OrderController} components. Depending on a thread's id, one of the + * three {@code OrderController} components will be selected. By using a lazy list, + * we ensure that at most three {@code OrderController} instances are created. Once + * created, the component retrieval is eligible for constant folding by the JVM: + * {@snippet lang = java: + * class Application { + * + * private static final int POOL_SIZE = 3; + * + * static final List ORDERS + * = List.ofLazy(POOL_SIZE, _ -> new OrderController()); + * + * public static OrderController orders() { + * long index = Thread.currentThread().threadId() % POOL_SIZE; + * return ORDERS.get((int)index); + * } + * + * // Eligible for constant folding + * OrderController orders = orders(); + * } + * } + *

+ * The returned {@code List} can be thought of as a list backed by a + * {@code List>} field and where the {@linkplain List#get(int)} + * operation is equivalent to: + * {@snippet lang = java: + * class LazyList extends AbstractList { + * + * private final List> backingList; + * + * public LazyList(int size, IntFunction computingFunction) { + * this.backingList = IntStream.range(0, size) + * .mapToObj(i -> LazyConstant.of(() -> computingFunction.apply(i))) + * .toList(); + * } + * + * @Override + * public E get(int index) { + * return backingList.get(index).get(); + * } + * } + *} + * Except, performance and storage efficiency might be better. + *

+ * Elements in the returned list are eligible for certain performance optimizations + * such as constant folding as described in + * {@linkplain LazyConstant##performance LazyConstant}. * - * @implNote after all elements have been initialized successfully, the computing - * function is no longer strongly referenced and becomes eligible for - * garbage collection. + * @implNote after all elements have been initialized successfully or transitioned to + * an error state, the computing function is no longer strongly referenced + * and becomes eligible for garbage collection. * * @param size the size of the returned lazy list * @param computingFunction to invoke whenever an element is first accessed * (may not return {@code null}) * @param the type of elements in the returned list * @throws IllegalArgumentException if the provided {@code size} is negative. + * @throws NullPointerException if the provided {@code computingFunction} is {@code null} * * @see LazyConstant * @since 26 diff --git a/src/java.base/share/classes/java/util/Locale.java b/src/java.base/share/classes/java/util/Locale.java index 6b071cd15b25..2bab271a4890 100644 --- a/src/java.base/share/classes/java/util/Locale.java +++ b/src/java.base/share/classes/java/util/Locale.java @@ -3493,7 +3493,8 @@ public String toString() { * sorted in descending order based on priority or weight, or an empty * list if nothing matches. The list is modifiable. * @throws NullPointerException if {@code priorityList} or {@code locales} - * is {@code null} + * are {@code null}. {@code NullPointerException} may be thrown if any + * elements within either {@code Collection} are {@code null}. * @throws IllegalArgumentException if one or more extended language ranges * are included in the given list when * {@link FilteringMode#REJECT_EXTENDED_RANGES} is specified @@ -3503,6 +3504,8 @@ public String toString() { public static List filter(List priorityList, Collection locales, FilteringMode mode) { + Objects.requireNonNull(priorityList); + Objects.requireNonNull(locales); return LocaleMatcher.filter(priorityList, locales, mode); } @@ -3522,12 +3525,15 @@ public static List filter(List priorityList, * sorted in descending order based on priority or weight, or an empty * list if nothing matches. The list is modifiable. * @throws NullPointerException if {@code priorityList} or {@code locales} - * is {@code null} + * are {@code null}. {@code NullPointerException} may be thrown if any + * elements within either {@code Collection} are {@code null}. * * @since 1.8 */ public static List filter(List priorityList, Collection locales) { + Objects.requireNonNull(priorityList); + Objects.requireNonNull(locales); return filter(priorityList, locales, FilteringMode.AUTOSELECT_FILTERING); } @@ -3553,8 +3559,9 @@ public static List filter(List priorityList, * @return a list of matching language tags sorted in descending order * based on priority or weight, or an empty list if nothing matches. * The list is modifiable. - * @throws NullPointerException if {@code priorityList} or {@code tags} is - * {@code null} + * @throws NullPointerException if {@code priorityList} or {@code tags} + * are {@code null}. {@code NullPointerException} may be thrown if any + * elements within either {@code Collection} are {@code null}. * @throws IllegalArgumentException if one or more extended language ranges * are included in the given list when * {@link FilteringMode#REJECT_EXTENDED_RANGES} is specified @@ -3564,6 +3571,8 @@ public static List filter(List priorityList, public static List filterTags(List priorityList, Collection tags, FilteringMode mode) { + Objects.requireNonNull(priorityList); + Objects.requireNonNull(tags); return LocaleMatcher.filterTags(priorityList, tags, mode); } @@ -3590,13 +3599,15 @@ public static List filterTags(List priorityList, * @return a list of matching language tags sorted in descending order * based on priority or weight, or an empty list if nothing matches. * The list is modifiable. - * @throws NullPointerException if {@code priorityList} or {@code tags} is - * {@code null} - * + * @throws NullPointerException if {@code priorityList} or {@code tags} + * are {@code null}. {@code NullPointerException} may be thrown if any + * elements within either {@code Collection} are {@code null}. * @since 1.8 */ public static List filterTags(List priorityList, Collection tags) { + Objects.requireNonNull(priorityList); + Objects.requireNonNull(tags); return filterTags(priorityList, tags, FilteringMode.AUTOSELECT_FILTERING); } @@ -3609,13 +3620,15 @@ public static List filterTags(List priorityList, * @param locales {@code Locale} instances used for matching * @return the best matching {@code Locale} instance chosen based on * priority or weight, or {@code null} if nothing matches. - * @throws NullPointerException if {@code priorityList} or {@code locales} is - * {@code null} - * + * @throws NullPointerException if {@code priorityList} or {@code locales} + * are {@code null}. {@code NullPointerException} may be thrown if any + * elements within either {@code Collection} are {@code null}. * @since 1.8 */ public static Locale lookup(List priorityList, Collection locales) { + Objects.requireNonNull(priorityList); + Objects.requireNonNull(locales); return LocaleMatcher.lookup(priorityList, locales); } @@ -3631,13 +3644,15 @@ public static Locale lookup(List priorityList, * @param tags language tags used for matching * @return the best matching language tag chosen based on priority or * weight, or {@code null} if nothing matches. - * @throws NullPointerException if {@code priorityList} or {@code tags} is - * {@code null} - * + * @throws NullPointerException if {@code priorityList} or {@code tags} + * are {@code null}. {@code NullPointerException} may be thrown if any + * elements within either {@code Collection} are {@code null}. * @since 1.8 */ public static String lookupTag(List priorityList, Collection tags) { + Objects.requireNonNull(priorityList); + Objects.requireNonNull(tags); return LocaleMatcher.lookupTag(priorityList, tags); } diff --git a/src/java.base/share/classes/java/util/Map.java b/src/java.base/share/classes/java/util/Map.java index fa16fb89050a..f5fb190f7d88 100644 --- a/src/java.base/share/classes/java/util/Map.java +++ b/src/java.base/share/classes/java/util/Map.java @@ -1759,50 +1759,122 @@ static Map copyOf(Map map) { * provided {@code computingFunction} when they are first accessed * (e.g., via {@linkplain Map#get(Object) Map::get}). *

- * The provided computing function is guaranteed to be successfully invoked + * The provided computing function is guaranteed to be invoked * at most once per key, even in a multi-threaded environment. Competing - * threads accessing a value already under computation will block until an element + * threads accessing a value already under computation will block until a value * is computed or the computing function completes abnormally. *

- * If invoking the provided computing function throws an exception, it - * is rethrown to the initial caller and no value associated with the provided key - * is recorded. + * If evaluation of the provided computing function throws an unchecked exception (for + * a key), the lazy value is not initialized but instead transitions to an error + * state whereafter a {@linkplain NoSuchElementException} is thrown with the unchecked + * exception as a cause. Subsequent {@linkplain Map#get(Object) Map::get} calls for + * the same key throw {@linkplain NoSuchElementException} (without ever invoking the + * computing function again) with no cause and with a message that includes the name + * of the original unchecked exception's class. *

- * If the provided computing function returns {@code null}, - * a {@linkplain NullPointerException} will be thrown. Hence, just like other - * unmodifiable maps created via the {@code Map::of} factories, a lazy map - * cannot contain {@code null} values. Clients that want to use nullable values can - * wrap values into an {@linkplain Optional} holder. + * All failures are handled in this way. There are two special cases that cause + * unchecked exceptions to be thrown: + *

+ * If the computing function returns {@code null}, + * a {@linkplain NoSuchElementException} (with a {@linkplain NullPointerException} as + * a cause) will be thrown. Hence, just like other unmodifiable maps created via the + * {@code Map::of} factories, a lazy map can never contain {@code null} values. + * Clients that want to use nullable values can wrap elements into an + * {@linkplain Optional} holder. + *

+ * If the computing function recursively invokes itself (for the same key) via the + * returned lazy map, a {@linkplain NoSuchElementException} + * (with an {@linkplain IllegalStateException} as a cause) will be thrown. *

* The values of any {@link Map#values()} or {@link Map#entrySet()} views of * the returned map are also lazily computed. *

- * If the provided computing function recursively calls itself via - * the returned lazy map for the same key, an {@linkplain IllegalStateException} - * will be thrown. - *

* The returned map's {@linkplain Object Object methods}; * {@linkplain Object#equals(Object) equals()}, * {@linkplain Object#hashCode() hashCode()}, and * {@linkplain Object#toString() toString()} methods may trigger initialization of - * one or more lazy elements. + * one or more lazy values. If initialization fails for at least one value, + * the {@linkplain Object Object methods} may throw {@linkplain NoSuchElementException}. *

* The returned lazy map strongly references its underlying * computing function used to compute values at least as long as there are * uncomputed values. *

* The returned Map is not {@linkplain Serializable}. + *

+ * If the provided {@code Set} of {@code keys} is subsequently modified, the returned + * {@code Map} will not reflect such modifications. + *

+ * The {@code Set} of {@code keys} must use {@linkplain Set#equals(Object) equals()} + * as its equivalence relation, or its comparison method must be consistent with + * equals, otherwise the behavior is unspecified. + *

+ * Here is an example involving an application that caches the values returned by some + * {@code expensiveOperation(int param)} for a given set of input parameters. By + * using a lazy map, we ensure that the {@code expensiveOperation(int param)} is + * called at most once per distinct input parameter. Once created, the retrieval of + * values is eligible for constant folding by the JVM: + * {@snippet lang = java: + * class Application { + * + * private static final Map CACHE + * = Map.ofLazy(Set.of(0, 1, 3, 42, 97), param -> expensiveOperation(param)); + * + * public static Optional cachedExpensiveOperation(int param) { + * return Optional.ofNullable(CACHE.get(param)); + * } + * + * private static double expensiveOperation(int param) { + * // Calculate the value ... + * } + * + * // Eligible for constant folding + * double val = cachedExpensiveOperation(42).orElseThrow(); + * + * } + * } + *

+ * The returned {@code Map} can be thought of as a map backed by a + * {@code Map>} field and where the {@linkplain Map#get(Object)} + * operation is equivalent to: + * {@snippet lang = java: + * class LazyMap extends AbstractMap { + * + * private final Map> backingMap; + * + * public LazyMap(Set keys, Function computingFunction) { + * this.backingMap = keys.stream() + * .collect(Collectors.toUnmodifiableMap( + * Function.identity(), + * k -> LazyConstant.of(() -> computingFunction.apply(k)))); + * } + * + * @Override + * public V get(Object key) { + * var lazyConstant = backingMap.get(key); + * return lazyConstant == null + * ? null + * : lazyConstant.get(); + * } + * } + *} + * Except, performance and storage efficiency might be better. + *

+ * Values in the returned map are eligible for certain performance optimizations + * such as constant folding as described in + * {@linkplain LazyConstant##performance LazyConstant}. * - * @implNote after all values have been initialized successfully, the computing - * function is no longer strongly referenced and becomes eligible for - * garbage collection. + * @implNote after all values have been initialized successfully or transitioned to + * an error state, the computing function is no longer strongly referenced + * and becomes eligible for garbage collection. * * @param keys the (non-null) keys in the returned computed map * @param computingFunction to invoke whenever an associated value is first accessed * @param the type of keys maintained by the returned map * @param the type of mapped values in the returned map - * @throws NullPointerException if the provided set of {@code keys} is {@code null} - * or if the set of {@code keys} contains a {@code null} element. + * @throws NullPointerException if the provided set of {@code keys} is {@code null}, + * if the set of {@code keys} contains a {@code null} element, or + * if the provided {@code computingFunction} is {@code null} * * @see LazyConstant * @since 26 diff --git a/src/java.base/share/classes/java/util/Set.java b/src/java.base/share/classes/java/util/Set.java index 5ce3bf04c7c4..0c66d4ef53c1 100644 --- a/src/java.base/share/classes/java/util/Set.java +++ b/src/java.base/share/classes/java/util/Set.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,6 +25,11 @@ package java.util; +import jdk.internal.javac.PreviewFeature; + +import java.io.Serializable; +import java.util.function.Predicate; + /** * A collection that contains no duplicate elements. More formally, sets * contain no pair of elements {@code e1} and {@code e2} such that @@ -77,7 +82,7 @@ * Set to behave inconsistently or its contents to appear to change. *

  • They disallow {@code null} elements. Attempts to create them with * {@code null} elements result in {@code NullPointerException}. - *
  • They are serializable if all elements are serializable. + *
  • Unless otherwise specified, they are serializable if all elements are serializable. *
  • They reject duplicate elements at creation time. Duplicate elements * passed to a static factory method result in {@code IllegalArgumentException}. *
  • The iteration order of set elements is unspecified and is subject to change. @@ -734,4 +739,158 @@ static Set copyOf(Collection coll) { return (Set)Set.of(new HashSet<>(coll).toArray()); } } + + /** + * {@return a new lazily computed set whose logical membership for each distinct + * element candidate in the set of {@code elementCandidates} is + * computed via the provided {@code computingFunction} on demand} + *

    + * In the following, the term membership status is used to indicate whether + * an element belongs to the returned set or not. That is, if the membership status + * for element {@code E} is {@code true}, then {@code E} is a member of the + * returned set. Conversely, if the membership status for element {@code E} is + * {@code false}, then {@code E} is not a member of the returned set. + *

    + * The returned set is an {@linkplain Collection##unmodifiable unmodifiable} set. The + * elements in the returned set are derived from the element candidates given at + * construction in combination with evaluating each element's membership status. The + * set's element membership statuses are lazily computed via the provided + * {@code computingFunction} when first accessed (e.g., via + * {@linkplain Set#contains(Object) Set::contains}). Once the membership status has + * been successfully computed for an element candidate, the associated membership + * status is initialized (i.e., either as a logical member or as + * a logical non-member). + *

    + * The provided computing function is guaranteed to be invoked at most once per + * element candidate, even in a multi-threaded environment. Competing threads + * accessing an element candidate already under membership status computation will + * block until the membership status of the element candidate is computed or the + * computing function completes abnormally. + *

    + * If evaluation of the provided computing function throws an unchecked exception (for + * an element candidate), the lazy membership status is not initialized but instead + * transitions to an error state whereafter a {@linkplain NoSuchElementException} is + * thrown with the unchecked exception as a cause. Subsequent + * {@linkplain Set#contains(Object) Set::contains} calls for the same membership + * candidate throw {@linkplain NoSuchElementException} (without ever invoking the + * computing function again) with no cause and with a message that includes the name + * of the original unchecked exception's class. + *

    + * All failures are handled in this way. There is a special case that causes + * unchecked exceptions to be thrown: + *

    + * If the computing function recursively invokes itself (for the same membership + * candidate) via the returned lazy set, a {@linkplain NoSuchElementException} + * (with an {@linkplain IllegalStateException} as a cause) will be thrown. + *

    + * The returned set's {@linkplain Object Object methods}; + * {@linkplain Object#equals(Object) equals()}, + * {@linkplain Object#hashCode() hashCode()}, and + * {@linkplain Object#toString() toString()} methods may trigger initialization of + * one or more lazy elements. If initialization fails for at least one element, + * the {@linkplain Object Object methods} may throw {@linkplain NoSuchElementException}. + *

    + * The returned lazy set strongly references its underlying + * computing function used to compute membership status at least as long as there are + * uncomputed element candidates. + *

    + * The returned Set is not {@linkplain Serializable}. + *

    + * If the provided {@code Set} of {@code elementCandidates} is subsequently modified, + * the returned {@code Set} will not reflect such modifications. + *

    + * The {@code Set} of {@code elementCandidates} must use + * {@linkplain Set#equals(Object) equals()} as its equivalence relation, or its + * comparison method must be consistent with {@code equals()}, otherwise the behavior + * is unspecified. + *

    + * Here is an example involving an application that manages various configurable + * options -- commonly referred to as "switches" -- that control its behavior. The + * state of these switches can be determined through the command line, a configuration + * file, or even a database connection. By using a lazy set, we ensure that the states + * of these switches are evaluated only once. Once computed, the results are eligible + * for constant folding by the JVM: + * {@snippet lang = java: + * class Application { + * + * enum Option {VERBOSE, DRY_RUN, STRICT} + * + * // Lazily initialized Set of Options + * static final Set

    + * The returned {@code Set} can be thought of as a set backed by a + * {@code Map>} field and where the {@linkplain Set#contains(Object)} + * operation is equivalent to: + * {@snippet lang = java: + * class LazySet extends AbstractCollection implements Set { + * + * private final Map> backingMap; + * + * public LazySet(Set elementCandidates, Predicate computingFunction) { + * this.backingMap = elementCandidates.stream() + * .collect(Collectors.toUnmodifiableMap( + * Function.identity(), + * k -> LazyConstant.of(() -> computingFunction.test(k)))); + * } + * + * @Override + * public boolean contains(Object o) { + * var lazyConstant = backingMap.get(o); + * return lazyConstant == null + * ? false + * : lazyConstant.get(); + * } + * } + *} + * Except, performance and storage efficiency might be better. + *

    + * Elements in the returned set are eligible for certain performance optimizations + * such as constant folding as described in + * {@linkplain LazyConstant##performance LazyConstant}. + * + * @implNote after all element membership statuses have been initialized + * successfully or transitioned to an error state, the computing function + * is no longer strongly referenced and becomes eligible for garbage + * collection. + * + * @param elementCandidates the (non-null) element candidates to be evaluated + * @param computingFunction to invoke whenever the membership status of an element + * candidate is first computed + * @param the type of elements maintained by the returned set + * @throws NullPointerException if the provided set of {@code elementCandidates} is + * {@code null}, if the set of {@code elementCandidates} + * contains a {@code null} element, or if the provided + * {@code computingFunction} is {@code null} + * + * @see LazyConstant + * @since 27 + */ + @PreviewFeature(feature = PreviewFeature.Feature.LAZY_CONSTANTS) + static Set ofLazy(Set elementCandidates, + Predicate computingFunction) { + Objects.requireNonNull(elementCandidates); + Objects.requireNonNull(computingFunction); + return LazyCollections.ofLazySet(elementCandidates, computingFunction); + } + } diff --git a/src/java.base/share/classes/java/util/spi/LocaleServiceProvider.java b/src/java.base/share/classes/java/util/spi/LocaleServiceProvider.java index abb9f3aca38b..e357665ed3cc 100644 --- a/src/java.base/share/classes/java/util/spi/LocaleServiceProvider.java +++ b/src/java.base/share/classes/java/util/spi/LocaleServiceProvider.java @@ -184,8 +184,8 @@ * CLDR version * * - * JDK 26 - * CLDR 48 + * JDK 27 + * CLDR 48.2 * JDK 25 * CLDR 47 * JDK 21 @@ -208,6 +208,8 @@ * CLDR version * * + * JDK 26 + * CLDR 48 * JDK 24 * CLDR 46 * JDK 23 diff --git a/src/java.base/share/classes/javax/crypto/spec/DESKeySpec.java b/src/java.base/share/classes/javax/crypto/spec/DESKeySpec.java index 078cb9bbefd8..a2afd88c3ba4 100644 --- a/src/java.base/share/classes/javax/crypto/spec/DESKeySpec.java +++ b/src/java.base/share/classes/javax/crypto/spec/DESKeySpec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -123,7 +123,7 @@ public class DESKeySpec implements java.security.spec.KeySpec { * of the buffer are copied to protect against subsequent modification. * * @exception NullPointerException if the given key material is - * null + * null. * @exception InvalidKeyException if the given key material is shorter * than 8 bytes. */ @@ -146,14 +146,22 @@ public DESKeySpec(byte[] key) throws InvalidKeyException { * material starts. * * @exception NullPointerException if the given key material is - * null + * null. * @exception InvalidKeyException if the given key material, starting at * offset inclusive, is shorter than 8 bytes. + * @exception ArrayIndexOutOfBoundsException if offset is + * negative. */ public DESKeySpec(byte[] key, int offset) throws InvalidKeyException { + if (key == null) { + throw new NullPointerException("null key"); + } if (key.length - offset < DES_KEY_LEN) { throw new InvalidKeyException("Wrong key size"); } + if (offset < 0) { + throw new ArrayIndexOutOfBoundsException("offset is negative"); + } this.key = new byte[DES_KEY_LEN]; System.arraycopy(key, offset, this.key, 0, DES_KEY_LEN); } @@ -182,6 +190,8 @@ public byte[] getKey() { * @exception InvalidKeyException if the given key material is * null, or starting at offset inclusive, is * shorter than 8 bytes. + * @exception ArrayIndexOutOfBoundsException if offset is + * negative. */ public static boolean isParityAdjusted(byte[] key, int offset) throws InvalidKeyException { @@ -191,7 +201,9 @@ public static boolean isParityAdjusted(byte[] key, int offset) if (key.length - offset < DES_KEY_LEN) { throw new InvalidKeyException("Wrong key size"); } - + if (offset < 0) { + throw new ArrayIndexOutOfBoundsException("offset is negative"); + } for (int i = 0; i < DES_KEY_LEN; i++) { int k = Integer.bitCount(key[offset++] & 0xff); if ((k & 1) == 0) { @@ -215,6 +227,8 @@ public static boolean isParityAdjusted(byte[] key, int offset) * @exception InvalidKeyException if the given key material is * null, or starting at offset inclusive, is * shorter than 8 bytes. + * @exception ArrayIndexOutOfBoundsException if offset is + * negative. */ public static boolean isWeak(byte[] key, int offset) throws InvalidKeyException { @@ -224,6 +238,9 @@ public static boolean isWeak(byte[] key, int offset) if (key.length - offset < DES_KEY_LEN) { throw new InvalidKeyException("Wrong key size"); } + if (offset < 0) { + throw new ArrayIndexOutOfBoundsException("offset is negative"); + } for (int i = 0; i < WEAK_KEYS.length; i++) { boolean found = true; for (int j = 0; j < DES_KEY_LEN; j++) { diff --git a/src/java.base/share/classes/javax/crypto/spec/DESedeKeySpec.java b/src/java.base/share/classes/javax/crypto/spec/DESedeKeySpec.java index 7fd5576c4870..fb5a19b4a9b5 100644 --- a/src/java.base/share/classes/javax/crypto/spec/DESedeKeySpec.java +++ b/src/java.base/share/classes/javax/crypto/spec/DESedeKeySpec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -78,14 +78,22 @@ public DESedeKeySpec(byte[] key) throws InvalidKeyException { * * @exception NullPointerException if key is null. * @exception InvalidKeyException if the given key material, starting at - * offset inclusive, is shorter than 24 bytes + * offset inclusive, is shorter than 24 bytes. + * @exception ArrayIndexOutOfBoundsException if offset is + * negative. */ public DESedeKeySpec(byte[] key, int offset) throws InvalidKeyException { - if (key.length - offset < 24) { + if (key == null) { + throw new NullPointerException("null key"); + } + if (key.length - offset < DES_EDE_KEY_LEN) { throw new InvalidKeyException("Wrong key size"); } + if (offset < 0) { + throw new ArrayIndexOutOfBoundsException("offset is negative"); + } this.key = new byte[24]; - System.arraycopy(key, offset, this.key, 0, 24); + System.arraycopy(key, offset, this.key, 0, DES_EDE_KEY_LEN); } /** @@ -107,15 +115,23 @@ public byte[] getKey() { * @return true if the given DES-EDE key is parity-adjusted, false * otherwise * - * @exception NullPointerException if key is null. - * @exception InvalidKeyException if the given key material, starting at - * offset inclusive, is shorter than 24 bytes + * @exception InvalidKeyException if the given key material is + * null, or starting at offset inclusive, is + * shorter than 8 bytes. + * @exception ArrayIndexOutOfBoundsException if offset is + * negative. */ public static boolean isParityAdjusted(byte[] key, int offset) throws InvalidKeyException { - if (key.length - offset < 24) { - throw new InvalidKeyException("Wrong key size"); - } + if (key == null) { + throw new InvalidKeyException("null key"); + } + if (key.length - offset < DES_EDE_KEY_LEN) { + throw new InvalidKeyException("Wrong key size"); + } + if (offset < 0) { + throw new ArrayIndexOutOfBoundsException("offset is negative"); + } return DESKeySpec.isParityAdjusted(key, offset) && DESKeySpec.isParityAdjusted(key, offset + 8) && DESKeySpec.isParityAdjusted(key, offset + 16); diff --git a/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java b/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java index 2c8ab1f86c07..82c55d6f017f 100644 --- a/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java +++ b/src/java.base/share/classes/jdk/internal/javac/PreviewFeature.java @@ -66,7 +66,7 @@ public enum Feature { @JEP(number=533, title="Structured Concurrency", status="Seventh Preview") STRUCTURED_CONCURRENCY, - @JEP(number = 526, title = "Lazy Constants", status = "Second Preview") + @JEP(number = 531, title = "Lazy Constants", status = "Third Preview") LAZY_CONSTANTS, @JEP(number=524, title="PEM Encodings of Cryptographic Objects", status="Second Preview") diff --git a/src/java.base/share/classes/jdk/internal/jimage/BasicImageReader.java b/src/java.base/share/classes/jdk/internal/jimage/BasicImageReader.java index 1c565a52c0f3..20d3b8837fe7 100644 --- a/src/java.base/share/classes/jdk/internal/jimage/BasicImageReader.java +++ b/src/java.base/share/classes/jdk/internal/jimage/BasicImageReader.java @@ -37,8 +37,11 @@ import java.nio.file.StandardOpenOption; import java.security.AccessController; import java.security.PrivilegedAction; +import java.util.NoSuchElementException; import java.util.Objects; import java.util.stream.IntStream; +import java.util.stream.Stream; + import jdk.internal.jimage.decompressor.Decompressor; /** @@ -316,6 +319,56 @@ public String[] getEntryNames() { .toArray(String[]::new); } + /** + * Returns the "raw" API for accessing underlying jimage resource entries. + * + *

    This is only meaningful for use by code dealing directly with jimage + * files, and cannot be used to reliably lookup resources used at runtime. + * + *

    The returned {@code ResourceEntries} remains valid until the image + * reader from which it was obtained is closed. + */ + // Package visible for use by ImageReader. + ResourceEntries getResourceEntries() { + return new ResourceEntries() { + @Override + public Stream getEntryNames(String module) { + if (module.isEmpty() || module.equals("modules") || module.equals("packages")) { + throw new IllegalArgumentException("Invalid module name: " + module); + } + return IntStream.range(0, offsets.capacity()) + .map(offsets::get) + .filter(offset -> offset != 0) + // Reusing a location instance or getting the module + // offset directly would save a lot of allocations here. + .mapToObj(offset -> ImageLocation.readFrom(BasicImageReader.this, offset)) + // Reverse lookup of module offset would be faster here. + .filter(loc -> module.equals(loc.getModule())) + .map(ImageLocation::getFullName); + } + + private ImageLocation getResourceLocation(String name) { + if (!name.startsWith("/modules/") && !name.startsWith("/packages/")) { + ImageLocation location = BasicImageReader.this.findLocation(name); + if (location != null) { + return location; + } + } + throw new NoSuchElementException("No such resource entry: " + name); + } + + @Override + public long getSize(String name) { + return getResourceLocation(name).getUncompressedSize(); + } + + @Override + public byte[] getBytes(String name) { + return BasicImageReader.this.getResource(getResourceLocation(name)); + } + }; + } + ImageLocation getLocation(int offset) { return ImageLocation.readFrom(this, offset); } diff --git a/src/java.base/share/classes/jdk/internal/jimage/ImageHeader.java b/src/java.base/share/classes/jdk/internal/jimage/ImageHeader.java index f63665119e2d..c128aa9736a7 100644 --- a/src/java.base/share/classes/jdk/internal/jimage/ImageHeader.java +++ b/src/java.base/share/classes/jdk/internal/jimage/ImageHeader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2016, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -30,6 +30,23 @@ import java.util.Objects; /** + * Defines the header and version information for jimage files. + * + *

    Version number changes must be synced in a single change across all code + * which reads/writes jimage files, and code which tries to open a jimage file + * with an unexpected version should fail. + * + *

    Known jimage file code which needs updating on version change: + *

      + *
    • src/java.base/share/native/libjimage/imageFile.hpp + *
    + * + *

    Version history: + *

      + *
    • {@code 1.0}: Original version. + *
    • {@code 1.1}: Support preview mode with new flags. + *
    + * * @implNote This class needs to maintain JDK 8 source compatibility. * * It is used internally in the JDK to implement jimage/jrtfs access, @@ -39,7 +56,7 @@ public final class ImageHeader { public static final int MAGIC = 0xCAFEDADA; public static final int MAJOR_VERSION = 1; - public static final int MINOR_VERSION = 0; + public static final int MINOR_VERSION = 1; private static final int HEADER_SLOTS = 7; private final int magic; diff --git a/src/java.base/share/classes/jdk/internal/jimage/ImageLocation.java b/src/java.base/share/classes/jdk/internal/jimage/ImageLocation.java index f31c7291927e..0822b17f0bbd 100644 --- a/src/java.base/share/classes/jdk/internal/jimage/ImageLocation.java +++ b/src/java.base/share/classes/jdk/internal/jimage/ImageLocation.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,7 +26,9 @@ package jdk.internal.jimage; import java.nio.ByteBuffer; +import java.util.List; import java.util.Objects; +import java.util.function.Predicate; /** * @implNote This class needs to maintain JDK 8 source compatibility. @@ -36,15 +38,172 @@ * to the jimage file provided by the shipped JDK by tools running on JDK 8. */ public class ImageLocation { + // Also defined in src/java.base/share/native/libjimage/imageFile.hpp + + /** End of attribute stream marker. */ public static final int ATTRIBUTE_END = 0; + /** String table offset of module name. */ public static final int ATTRIBUTE_MODULE = 1; + /** String table offset of resource path parent. */ public static final int ATTRIBUTE_PARENT = 2; + /** String table offset of resource path base. */ public static final int ATTRIBUTE_BASE = 3; + /** String table offset of resource path extension. */ public static final int ATTRIBUTE_EXTENSION = 4; + /** Container byte offset of resource. */ public static final int ATTRIBUTE_OFFSET = 5; + /** In-image byte size of the compressed resource. */ public static final int ATTRIBUTE_COMPRESSED = 6; + /** In-memory byte size of the uncompressed resource. */ public static final int ATTRIBUTE_UNCOMPRESSED = 7; - public static final int ATTRIBUTE_COUNT = 8; + /** Flags relating to preview mode resources. */ + public static final int ATTRIBUTE_PREVIEW_FLAGS = 8; + /** Number of attribute kinds. */ + public static final int ATTRIBUTE_COUNT = 9; + + // Flag masks for the ATTRIBUTE_PREVIEW_FLAGS attribute. Defined so + // that zero is the overwhelmingly common case for normal resources. + + /** + * Indicates that a non-preview location is associated with preview + * resources. + * + *

    This can apply to both resources and directories in the + * {@code /modules/xxx/...} namespace, as well as {@code /packages/xxx} + * directories. + * + *

    For {@code /packages/xxx} directories, it indicates that the package + * has preview resources in one of the modules in which it exists. + */ + private static final int FLAGS_HAS_PREVIEW_VERSION = 0x1; + + /** + * Set on all locations in the {@code /modules/xxx/META-INF/preview/...} + * namespace. + * + *

    This flag is mutually exclusive with {@link #FLAGS_HAS_PREVIEW_VERSION}. + */ + private static final int FLAGS_IS_PREVIEW_VERSION = 0x2; + + /** + * Indicates that a location only exists due to preview resources. + * + *

    This can apply to both resources and directories in the + * {@code /modules/xxx/...} namespace, as well as {@code /packages/xxx} + * directories. + * + *

    For {@code /packages/xxx} directories it indicates that, for every + * module in which the package exists, it is preview only. + * + *

    This flag is mutually exclusive with {@link #FLAGS_HAS_PREVIEW_VERSION} + * and need not imply that {@link #FLAGS_IS_PREVIEW_VERSION} is set (i.e. + * for {@code /packages/xxx} directories). + */ + private static final int FLAGS_IS_PREVIEW_ONLY = 0x4; + + // Also used in ImageReader. + static final String MODULES_PREFIX = "/modules"; + static final String PACKAGES_PREFIX = "/packages"; + static final String PREVIEW_INFIX = "/META-INF/preview"; + + /** + * Helper function to calculate preview flags (ATTRIBUTE_PREVIEW_FLAGS). + * + *

    Since preview flags are calculated separately for resource nodes and + * directory nodes (in two quite different places) it's useful to have a + * common helper. + * + *

    Based on the entry name, the flags are: + *

      + *
    • {@code "[/modules]//"} normal resource or directory:
      + * Zero, or {@code FLAGS_HAS_PREVIEW_VERSION} if a preview entry exists. + *
    • {@code "[/modules]//META-INF/preview/"} preview + * resource or directory:
      + * {@code FLAGS_IS_PREVIEW_VERSION}, and additionally {@code + * FLAGS_IS_PREVIEW_ONLY} if no normal version of the resource or + * directory exists. + *
    • In all other cases, returned flags are zero (note that {@code + * "/packages/xxx"} entries may have flags, but these are calculated + * elsewhere). + *
    + * + * @param name the jimage name of the resource or directory. + * @param hasEntry a predicate for jimage names returning whether an entry + * is present. + * @return flags for the ATTRIBUTE_PREVIEW_FLAGS attribute. + */ + public static int getPreviewFlags(String name, Predicate hasEntry) { + if (name.startsWith(PACKAGES_PREFIX + "/")) { + throw new IllegalArgumentException( + "Package sub-directory flags handled separately: " + name); + } + // Find suffix for either '/modules/xxx/suffix' or '/xxx/suffix' paths. + int idx = name.startsWith(MODULES_PREFIX + "/") ? MODULES_PREFIX.length() + 1 : 1; + int suffixStart = name.indexOf('/', idx); + if (suffixStart == -1) { + // No flags for '[/modules]/xxx' paths (esp. '/modules', '/packages'). + // '/packages/xxx' entries have flags, but not calculated here. + return 0; + } + // Prefix is either '/modules/xxx' or '/xxx', and suffix starts with '/'. + String prefix = name.substring(0, suffixStart); + String suffix = name.substring(suffixStart); + if (suffix.startsWith(PREVIEW_INFIX + "/")) { + // Preview resources/directories. + String nonPreviewName = prefix + suffix.substring(PREVIEW_INFIX.length()); + return FLAGS_IS_PREVIEW_VERSION + | (hasEntry.test(nonPreviewName) ? 0 : FLAGS_IS_PREVIEW_ONLY); + } else if (!suffix.startsWith("/META-INF/")) { + // Non-preview resources/directories. + String previewName = prefix + PREVIEW_INFIX + suffix; + return hasEntry.test(previewName) ? FLAGS_HAS_PREVIEW_VERSION : 0; + } else { + // Suffix is '/META-INF/xxx' and no preview version is even possible. + return 0; + } + } + + /** + * Helper function to calculate package flags for {@code "/packages/xxx"} + * directory entries. + * + *

    Based on the module links, the flags are: + *

      + *
    • {@code FLAGS_HAS_PREVIEW_VERSION} if any referenced + * package has a preview version. + *
    • {@code FLAGS_IS_PREVIEW_ONLY} if all referenced packages + * are preview only. + *
    + * + * @return package flags for {@code "/packages/xxx"} directory entries. + */ + public static int getPackageFlags(List moduleLinks) { + boolean hasPreviewVersion = + moduleLinks.stream().anyMatch(ModuleLink::hasPreviewVersion); + boolean isPreviewOnly = + moduleLinks.stream().allMatch(ModuleLink::isPreviewOnly); + return (hasPreviewVersion ? ImageLocation.FLAGS_HAS_PREVIEW_VERSION : 0) + | (isPreviewOnly ? ImageLocation.FLAGS_IS_PREVIEW_ONLY : 0); + } + + /** + * Tests a non-preview image location's flags to see if it has preview + * content associated with it. + */ + public static boolean hasPreviewVersion(int flags) { + return (flags & FLAGS_HAS_PREVIEW_VERSION) != 0; + } + + /** + * Tests an image location's flags to see if it only exists in preview mode. + */ + public static boolean isPreviewOnly(int flags) { + return (flags & FLAGS_IS_PREVIEW_ONLY) != 0; + } + + public enum LocationType { + RESOURCE, MODULES_ROOT, MODULES_DIR, PACKAGES_ROOT, PACKAGES_DIR; + } protected final long[] attributes; @@ -285,6 +444,10 @@ public int getExtensionOffset() { return (int)getAttribute(ATTRIBUTE_EXTENSION); } + public int getFlags() { + return (int) getAttribute(ATTRIBUTE_PREVIEW_FLAGS); + } + public String getFullName() { return getFullName(false); } @@ -294,7 +457,7 @@ public String getFullName(boolean modulesPrefix) { if (getModuleOffset() != 0) { if (modulesPrefix) { - builder.append("/modules"); + builder.append(MODULES_PREFIX); } builder.append('/'); @@ -317,36 +480,6 @@ public String getFullName(boolean modulesPrefix) { return builder.toString(); } - String buildName(boolean includeModule, boolean includeParent, - boolean includeName) { - StringBuilder builder = new StringBuilder(); - - if (includeModule && getModuleOffset() != 0) { - builder.append("/modules/"); - builder.append(getModule()); - } - - if (includeParent && getParentOffset() != 0) { - builder.append('/'); - builder.append(getParent()); - } - - if (includeName) { - if (includeModule || includeParent) { - builder.append('/'); - } - - builder.append(getBase()); - - if (getExtensionOffset() != 0) { - builder.append('.'); - builder.append(getExtension()); - } - } - - return builder.toString(); - } - public long getContentOffset() { return getAttribute(ATTRIBUTE_OFFSET); } @@ -359,6 +492,42 @@ public long getUncompressedSize() { return getAttribute(ATTRIBUTE_UNCOMPRESSED); } + // Fast (zero allocation) type determination for locations. + public LocationType getType() { + switch (getModuleOffset()) { + case ImageStrings.MODULES_STRING_OFFSET: + // Locations in /modules/... namespace are directory entries. + return LocationType.MODULES_DIR; + case ImageStrings.PACKAGES_STRING_OFFSET: + // Locations in /packages/... namespace are always 2-level + // "/packages/xxx" directories. + return LocationType.PACKAGES_DIR; + case ImageStrings.EMPTY_STRING_OFFSET: + // Only 2 choices, either the "/modules" or "/packages" root. + assert isRootDir() : "Invalid root directory: " + getFullName(); + return getBase().charAt(1) == 'p' + ? LocationType.PACKAGES_ROOT + : LocationType.MODULES_ROOT; + default: + // Anything else is // and references a resource. + return LocationType.RESOURCE; + } + } + + private boolean isRootDir() { + if (getModuleOffset() == 0 && getParentOffset() == 0) { + String name = getFullName(); + return name.equals(MODULES_PREFIX) || name.equals(PACKAGES_PREFIX); + } + return false; + } + + @Override + public String toString() { + // Cannot use String.format() (too early in startup for locale code). + return "ImageLocation[name='" + getFullName() + "', type=" + getType() + ", flags=" + getFlags() + "]"; + } + static ImageLocation readFrom(BasicImageReader reader, int offset) { Objects.requireNonNull(reader); long[] attributes = reader.getAttributes(offset); diff --git a/src/java.base/share/classes/jdk/internal/jimage/ImageReader.java b/src/java.base/share/classes/jdk/internal/jimage/ImageReader.java index 4c3588201662..2cf28b835ceb 100644 --- a/src/java.base/share/classes/jdk/internal/jimage/ImageReader.java +++ b/src/java.base/share/classes/jdk/internal/jimage/ImageReader.java @@ -24,6 +24,8 @@ */ package jdk.internal.jimage; +import jdk.internal.jimage.ImageLocation.LocationType; + import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -34,16 +36,26 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.TreeMap; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Stream; +import static jdk.internal.jimage.ImageLocation.LocationType.MODULES_DIR; +import static jdk.internal.jimage.ImageLocation.LocationType.MODULES_ROOT; +import static jdk.internal.jimage.ImageLocation.LocationType.PACKAGES_DIR; +import static jdk.internal.jimage.ImageLocation.LocationType.RESOURCE; +import static jdk.internal.jimage.ImageLocation.MODULES_PREFIX; +import static jdk.internal.jimage.ImageLocation.PACKAGES_PREFIX; +import static jdk.internal.jimage.ImageLocation.PREVIEW_INFIX; + /** * A view over the entries of a jimage file with a unified namespace suitable * for file system use. The jimage entries (resources, module and package @@ -77,6 +89,10 @@ * to the jimage file provided by the shipped JDK by tools running on JDK 8. */ public final class ImageReader implements AutoCloseable { + + // For resource paths, there's no leading '/'. + private static final String PREVIEW_RESOURCE_PREFIX = PREVIEW_INFIX.substring(1); + private final SharedImageReader reader; private volatile boolean closed; @@ -86,22 +102,27 @@ private ImageReader(SharedImageReader reader) { } /** - * Opens an image reader for a jimage file at the specified path, using the - * given byte order. + * Opens an image reader for a jimage file at the specified path. + * + * @param imagePath file system path of the jimage file. + * @param mode whether to return preview resources. */ - public static ImageReader open(Path imagePath, ByteOrder byteOrder) throws IOException { - Objects.requireNonNull(imagePath); - Objects.requireNonNull(byteOrder); - - return SharedImageReader.open(imagePath, byteOrder); + public static ImageReader open(Path imagePath, PreviewMode mode) throws IOException { + return open(imagePath, ByteOrder.nativeOrder(), mode); } /** - * Opens an image reader for a jimage file at the specified path, using the - * platform native byte order. + * Opens an image reader for a jimage file at the specified path. + * + * @param imagePath file system path of the jimage file. + * @param byteOrder the byte-order to be used when reading the jimage file. + * @param mode controls whether preview resources are visible. */ - public static ImageReader open(Path imagePath) throws IOException { - return open(imagePath, ByteOrder.nativeOrder()); + public static ImageReader open(Path imagePath, ByteOrder byteOrder, PreviewMode mode) + throws IOException { + Objects.requireNonNull(imagePath); + Objects.requireNonNull(byteOrder); + return SharedImageReader.open(imagePath, byteOrder, mode.isPreviewModeEnabled()); } @Override @@ -200,15 +221,45 @@ public ByteBuffer getResourceBuffer(Node node) { return reader.getResourceBuffer(node.getLocation()); } + // Package protected for use only by SystemImageReader. + ResourceEntries getResourceEntries() { + return reader.getResourceEntries(); + } + private static final class SharedImageReader extends BasicImageReader { - private static final Map OPEN_FILES = new HashMap<>(); - private static final String MODULES_ROOT = "/modules"; - private static final String PACKAGES_ROOT = "/packages"; // There are >30,000 nodes in a complete jimage tree, and even relatively // common tasks (e.g. starting up javac) load somewhere in the region of // 1000 classes. Thus, an initial capacity of 2000 is a reasonable guess. private static final int INITIAL_NODE_CACHE_CAPACITY = 2000; + static final class ReaderKey { + private final Path imagePath; + private final boolean isPreviewEnabled; + + public ReaderKey(Path imagePath, boolean isPreviewEnabled) { + this.imagePath = imagePath; + this.isPreviewEnabled = isPreviewEnabled; + } + + @Override + public boolean equals(Object obj) { + // No pattern variables here (Java 8 compatible source). + if (obj instanceof ReaderKey) { + ReaderKey other = (ReaderKey) obj; + return this.imagePath.equals(other.imagePath) + && this.isPreviewEnabled == other.isPreviewEnabled; + } + return false; + } + + @Override + public int hashCode() { + return imagePath.hashCode() ^ Boolean.hashCode(isPreviewEnabled); + } + } + + private static final Map OPEN_FILES = new HashMap<>(); + // List of openers for this shared image. private final Set openers = new HashSet<>(); @@ -219,55 +270,140 @@ private static final class SharedImageReader extends BasicImageReader { // Cache of all user visible nodes, guarded by synchronizing 'this' instance. private final Map nodes; - // Used to classify ImageLocation instances without string comparison. - private final int modulesStringOffset; - private final int packagesStringOffset; - private SharedImageReader(Path imagePath, ByteOrder byteOrder) throws IOException { + // Preview mode support. + private final boolean isPreviewEnabled; + // A relativized mapping from non-preview name to directories containing + // preview-only nodes. This is used to merge preview-only content into + // directories as they are completed. + // E.g. "/modules/xxx/foo/bar" -> Directory("/modules/xxx/META-INF/preview/foo/bar") + private final Map previewDirectoriesToMerge; + + private SharedImageReader(Path imagePath, ByteOrder byteOrder, boolean isPreviewEnabled) throws IOException { super(imagePath, byteOrder); this.imageFileAttributes = Files.readAttributes(imagePath, BasicFileAttributes.class); this.nodes = new HashMap<>(INITIAL_NODE_CACHE_CAPACITY); - // Pick stable jimage names from which to extract string offsets (we cannot - // use "/modules" or "/packages", since those have a module offset of zero). - this.modulesStringOffset = getModuleOffset("/modules/java.base"); - this.packagesStringOffset = getModuleOffset("/packages/java.lang"); + this.isPreviewEnabled = isPreviewEnabled; // Node creation is very lazy, so we can just make the top-level directories // now without the risk of triggering the building of lots of other nodes. - Directory packages = newDirectory(PACKAGES_ROOT); - nodes.put(packages.getName(), packages); - Directory modules = newDirectory(MODULES_ROOT); - nodes.put(modules.getName(), modules); + Directory packages = ensureCached(newDirectory(PACKAGES_PREFIX)); + Directory modules = ensureCached(newDirectory(MODULES_PREFIX)); Directory root = newDirectory("/"); root.setChildren(Arrays.asList(packages, modules)); - nodes.put(root.getName(), root); + ensureCached(root); + + // By scanning the /packages directory information early we can determine + // which module/package pairs have preview resources, and build the (small) + // set of preview nodes early. This also ensures that preview-only entries + // in the /packages directory are not present in non-preview mode. + this.previewDirectoriesToMerge = isPreviewEnabled ? new HashMap<>() : null; + packages.setChildren(processPackagesDirectory(isPreviewEnabled)); } /** - * Returns the offset of the string denoting the leading "module" segment in - * the given path (e.g. {@code /}). We can't just pass in the - * {@code /} string here because that has a module offset of zero. + * Process {@code "/packages/xxx"} entries to build the child nodes for the + * root {@code "/packages"} node. Preview-only entries will be skipped if + * {@code previewMode == false}. + * + *

    If {@code previewMode == true}, this method also populates the {@link + * #previewDirectoriesToMerge} map with any preview-only nodes, to be merged + * into directories as they are completed. It also caches preview resources + * and preview-only directories for direct lookup. */ - private int getModuleOffset(String path) { - ImageLocation location = findLocation(path); - assert location != null : "Cannot find expected jimage location: " + path; - int offset = location.getModuleOffset(); - assert offset != 0 : "Invalid module offset for jimage location: " + path; - return offset; + private ArrayList processPackagesDirectory(boolean previewMode) { + ImageLocation pkgRoot = findLocation(PACKAGES_PREFIX); + assert pkgRoot != null : "Invalid jimage file"; + IntBuffer offsets = getOffsetBuffer(pkgRoot); + ArrayList pkgDirs = new ArrayList<>(offsets.capacity()); + // Package path to module map, sorted in reverse order so that + // longer child paths get processed first. + Map> previewPackagesToModules = + new TreeMap<>(Comparator.reverseOrder()); + for (int i = 0; i < offsets.capacity(); i++) { + ImageLocation pkgDir = getLocation(offsets.get(i)); + int flags = pkgDir.getFlags(); + // A package subdirectory is "preview only" if all the modules + // it references have that package marked as preview only. + // Skipping these entries avoids empty package subdirectories. + if (previewMode || !ImageLocation.isPreviewOnly(flags)) { + pkgDirs.add(ensureCached(newDirectory(pkgDir.getFullName()))); + } + if (previewMode && ImageLocation.hasPreviewVersion(flags)) { + // Only do this in preview mode for the small set of packages with + // preview versions (the number of preview entries should be small). + List moduleNames = new ArrayList<>(); + ModuleLink.readNameOffsets(getOffsetBuffer(pkgDir), /*normal*/ false, /*preview*/ true) + .forEachRemaining(n -> moduleNames.add(getString(n))); + previewPackagesToModules.put(pkgDir.getBase().replace('.', '/'), moduleNames); + } + } + // Reverse sorted map means child directories are processed first. + previewPackagesToModules.forEach((pkgPath, modules) -> + modules.forEach(modName -> processPreviewDir(MODULES_PREFIX + "/" + modName, pkgPath))); + // We might have skipped some preview-only package entries. + pkgDirs.trimToSize(); + return pkgDirs; } - private static ImageReader open(Path imagePath, ByteOrder byteOrder) throws IOException { + void processPreviewDir(String namePrefix, String pkgPath) { + String previewDirName = namePrefix + PREVIEW_INFIX + "/" + pkgPath; + ImageLocation previewLoc = findLocation(previewDirName); + assert previewLoc != null : "Missing preview directory location: " + previewDirName; + String nonPreviewDirName = namePrefix + "/" + pkgPath; + List previewOnlyChildren = createChildNodes(previewLoc, 0, childLoc -> { + String baseName = getBaseName(childLoc); + String nonPreviewChildName = nonPreviewDirName + "/" + baseName; + boolean isPreviewOnly = ImageLocation.isPreviewOnly(childLoc.getFlags()); + LocationType type = childLoc.getType(); + if (type == RESOURCE) { + // Preview resources are cached to override non-preview versions. + Node childNode = ensureCached(newResource(nonPreviewChildName, childLoc)); + return isPreviewOnly ? childNode : null; + } else { + // Child directories are not cached here (they are either cached + // already or have been added to previewDirectoriesToMerge). + assert type == MODULES_DIR : "Invalid location type: " + childLoc; + Node childNode = nodes.get(nonPreviewChildName); + assert isPreviewOnly == (childNode != null) : + "Inconsistent child node: " + nonPreviewChildName; + return childNode; + } + }); + Directory previewDir = newDirectory(nonPreviewDirName); + previewDir.setChildren(previewOnlyChildren); + if (ImageLocation.isPreviewOnly(previewLoc.getFlags())) { + // If we are preview-only, our children are also preview-only, so + // this directory is a complete hierarchy and should be cached. + assert !previewOnlyChildren.isEmpty() : "Invalid empty preview-only directory: " + nonPreviewDirName; + ensureCached(previewDir); + } else if (!previewOnlyChildren.isEmpty()) { + // A partial directory containing extra preview-only nodes + // to be merged when the non-preview directory is completed. + previewDirectoriesToMerge.put(nonPreviewDirName, previewDir); + } + } + + // Adds a node to the cache, ensuring that no matching entry already existed. + private T ensureCached(T node) { + Node existingNode = nodes.put(node.getName(), node); + assert existingNode == null : "Unexpected node already cached for: " + node; + return node; + } + + private static ImageReader open(Path imagePath, ByteOrder byteOrder, boolean previewMode) throws IOException { Objects.requireNonNull(imagePath); Objects.requireNonNull(byteOrder); synchronized (OPEN_FILES) { - SharedImageReader reader = OPEN_FILES.get(imagePath); + ReaderKey key = new ReaderKey(imagePath, previewMode); + SharedImageReader reader = OPEN_FILES.get(key); if (reader == null) { // Will fail with an IOException if wrong byteOrder. - reader = new SharedImageReader(imagePath, byteOrder); - OPEN_FILES.put(imagePath, reader); + reader = new SharedImageReader(imagePath, byteOrder, previewMode); + OPEN_FILES.put(key, reader); } else if (reader.getByteOrder() != byteOrder) { throw new IOException("\"" + reader.getName() + "\" is not an image file"); } @@ -291,7 +427,7 @@ public void close(ImageReader image) throws IOException { close(); nodes.clear(); - if (!OPEN_FILES.remove(this.getImagePath(), this)) { + if (!OPEN_FILES.remove(new ReaderKey(getImagePath(), isPreviewEnabled), this)) { throw new IOException("image file not found in open list"); } } @@ -309,20 +445,14 @@ public void close(ImageReader image) throws IOException { * "/modules" or "/packages". */ synchronized Node findNode(String name) { + // Root directories "/", "/modules" and "/packages", as well + // as all "/packages/xxx" subdirectories are already cached. Node node = nodes.get(name); if (node == null) { - // We cannot get the root paths ("/modules" or "/packages") here - // because those nodes are already in the nodes cache. - if (name.startsWith(MODULES_ROOT + "/")) { - // This may perform two lookups, one for a directory (in - // "/modules/...") and one for a non-prefixed resource - // (with "/modules" removed). - node = buildModulesNode(name); - } else if (name.startsWith(PACKAGES_ROOT + "/")) { - node = buildPackagesNode(name); - } - if (node != null) { - nodes.put(node.getName(), node); + if (name.startsWith(MODULES_PREFIX + "/")) { + node = buildAndCacheModulesNode(name); + } else if (name.startsWith(PACKAGES_PREFIX + "/")) { + node = buildAndCacheLinkNode(name); } } else if (!node.isCompleted()) { // Only directories can be incomplete. @@ -341,18 +471,27 @@ synchronized Node findNode(String name) { * the node handling code. */ Node findResourceNode(String moduleName, String resourcePath) { - // Unlike findNode(), this method makes only one lookup in the - // underlying jimage, but can only reliably return resource nodes. + // Unlike findNode(), this method can only reliably return resource nodes. if (moduleName.indexOf('/') >= 0) { throw new IllegalArgumentException("invalid module name: " + moduleName); } - String nodeName = MODULES_ROOT + "/" + moduleName + "/" + resourcePath; + if (resourcePath.startsWith(PREVIEW_RESOURCE_PREFIX)) { + return null; + } + String nodeName = MODULES_PREFIX + "/" + moduleName + "/" + resourcePath; // Synchronize as tightly as possible to reduce locking contention. synchronized (this) { Node node = nodes.get(nodeName); if (node == null) { - ImageLocation loc = findLocation(moduleName, resourcePath); - if (loc != null && isResource(loc)) { + ImageLocation loc = null; + if (isPreviewEnabled) { + // We must test preview location first (if in preview mode). + loc = findLocation(moduleName, PREVIEW_RESOURCE_PREFIX + resourcePath); + } + if (loc == null) { + loc = findLocation(moduleName, resourcePath); + } + if (loc != null && loc.getType() == RESOURCE) { node = newResource(nodeName, loc); nodes.put(node.getName(), node); } @@ -368,18 +507,36 @@ Node findResourceNode(String moduleName, String resourcePath) { * *

    This method is expected to be called frequently for resources * which do not exist in the given module (e.g. as part of classpath - * search). As such, it skips checking the nodes cache and only checks - * for an entry in the jimage file, as this is faster if the resource - * is not present. This also means it doesn't need synchronization. + * search). As such, it skips checking the nodes cache if possible, and + * only checks for an entry in the jimage file, as this is faster if the + * resource is not present. This also means it doesn't need + * synchronization most of the time. */ boolean containsResource(String moduleName, String resourcePath) { if (moduleName.indexOf('/') >= 0) { throw new IllegalArgumentException("invalid module name: " + moduleName); } - // If the given module name is 'modules', then 'isResource()' - // returns false to prevent false positives. - ImageLocation loc = findLocation(moduleName, resourcePath); - return loc != null && isResource(loc); + if (resourcePath.startsWith(PREVIEW_RESOURCE_PREFIX)) { + return false; + } + // In preview mode, preview-only resources are eagerly added to the + // cache, so we must check that first. + ImageLocation loc = null; + if (isPreviewEnabled) { + String nodeName = MODULES_PREFIX + "/" + moduleName + "/" + resourcePath; + // Synchronize as tightly as possible to reduce locking contention. + synchronized (this) { + Node node = nodes.get(nodeName); + if (node != null) { + return node.isResource(); + } + } + loc = findLocation(moduleName, PREVIEW_RESOURCE_PREFIX + resourcePath); + } + if (loc == null) { + loc = findLocation(moduleName, resourcePath); + } + return loc != null && loc.getType() == RESOURCE; } /** @@ -388,55 +545,82 @@ boolean containsResource(String moduleName, String resourcePath) { *

    Called by {@link #findNode(String)} if a {@code /modules/...} node * is not present in the cache. */ - private Node buildModulesNode(String name) { - assert name.startsWith(MODULES_ROOT + "/") : "Invalid module node name: " + name; + private Node buildAndCacheModulesNode(String name) { + assert name.startsWith(MODULES_PREFIX + "/") : "Invalid module node name: " + name; + if (isPreviewName(name)) { + return null; + } // Returns null for non-directory resources, since the jimage name does not // start with "/modules" (e.g. "/java.base/java/lang/Object.class"). ImageLocation loc = findLocation(name); if (loc != null) { assert name.equals(loc.getFullName()) : "Mismatched location for directory: " + name; - assert isModulesSubdirectory(loc) : "Invalid modules directory: " + name; - return completeModuleDirectory(newDirectory(name), loc); + assert loc.getType() == MODULES_DIR : "Invalid modules directory: " + name; + return ensureCached(completeModuleDirectory(newDirectory(name), loc)); } // Now try the non-prefixed resource name, but be careful to avoid false // positives for names like "/modules/modules/xxx" which could return a // location of a directory entry. - loc = findLocation(name.substring(MODULES_ROOT.length())); - return loc != null && isResource(loc) ? newResource(name, loc) : null; + loc = findLocation(name.substring(MODULES_PREFIX.length())); + return loc != null && loc.getType() == RESOURCE + ? ensureCached(newResource(name, loc)) + : null; + } + + /** + * Returns whether a directory name in the "/modules/" directory could be referencing + * the "META-INF" directory". + */ + private boolean isMetaInf(Directory dir) { + String name = dir.getName(); + int pathStart = name.indexOf('/', MODULES_PREFIX.length() + 1); + return name.length() == pathStart + "/META-INF".length() + && name.endsWith("/META-INF"); + } + + /** + * Returns whether a node name in the "/modules/" directory could be referencing + * a preview resource or directory under "META-INF/preview". + */ + private boolean isPreviewName(String name) { + int pathStart = name.indexOf('/', MODULES_PREFIX.length() + 1); + int previewEnd = pathStart + PREVIEW_INFIX.length(); + return pathStart > 0 + && name.regionMatches(pathStart, PREVIEW_INFIX, 0, PREVIEW_INFIX.length()) + && (name.length() == previewEnd || name.charAt(previewEnd) == '/'); + } + + private String getBaseName(ImageLocation loc) { + // Matches logic in ImageLocation#getFullName() regarding extensions. + String trailingParts = loc.getBase() + + ((loc.getExtensionOffset() != 0) ? "." + loc.getExtension() : ""); + return trailingParts.substring(trailingParts.lastIndexOf('/') + 1); } /** - * Builds a node in the "/packages/..." namespace. + * Builds a link node of the form "/packages/xxx/yyy". * - *

    Called by {@link #findNode(String)} if a {@code /packages/...} node - * is not present in the cache. + *

    Called by {@link #findNode(String)} if a {@code /packages/...} + * node is not present in the cache (the name is not trusted). */ - private Node buildPackagesNode(String name) { - // There are only locations for the root "/packages" or "/packages/xxx" - // directories, but not the symbolic links below them (the links can be - // entirely derived from the name information in the parent directory). - // However, unlike resources this means that we do not have a constant - // time lookup for link nodes when creating them. - int packageStart = PACKAGES_ROOT.length() + 1; + private Node buildAndCacheLinkNode(String name) { + // There are only locations for "/packages" or "/packages/xxx" + // directories, but not the symbolic links below them (links are + // derived from the name information in the parent directory). + int packageStart = PACKAGES_PREFIX.length() + 1; int packageEnd = name.indexOf('/', packageStart); - if (packageEnd == -1) { - ImageLocation loc = findLocation(name); - return loc != null ? completePackageDirectory(newDirectory(name), loc) : null; - } else { - // We cannot assume that the parent directory exists for a link node, since - // the given name is untrusted and could reference a non-existent link. - // However, if the parent directory is present, we can conclude that the - // given name was not a valid link (or else it would already be cached). + // We already built the 2-level "/packages/xxx" directories, + // so if this is a 2-level name, it cannot reference a node. + if (packageEnd >= 0) { String dirName = name.substring(0, packageEnd); - if (!nodes.containsKey(dirName)) { - ImageLocation loc = findLocation(dirName); - // If the parent location doesn't exist, the link node cannot exist. - if (loc != null) { - nodes.put(dirName, completePackageDirectory(newDirectory(dirName), loc)); - // When the parent is created its child nodes are created and cached, - // but this can still return null if given name wasn't a valid link. - return nodes.get(name); + // If no parent exists here, the name cannot be valid. + Directory parent = (Directory) nodes.get(dirName); + if (parent != null) { + if (!parent.isCompleted()) { + // This caches all child links of the parent directory. + completePackageSubdirectory(parent, findLocation(dirName)); } + return nodes.get(name); } } return null; @@ -448,127 +632,125 @@ private void completeDirectory(Directory dir) { // Since the node exists, we can assert that its name starts with // either "/modules" or "/packages", making differentiation easy. // It also means that the name is valid, so it must yield a location. - assert name.startsWith(MODULES_ROOT) || name.startsWith(PACKAGES_ROOT); + assert name.startsWith(MODULES_PREFIX) || name.startsWith(PACKAGES_PREFIX); ImageLocation loc = findLocation(name); assert loc != null && name.equals(loc.getFullName()) : "Invalid location for name: " + name; - // We cannot use 'isXxxSubdirectory()' methods here since we could - // be given a top-level directory (for which that test doesn't work). - // The string MUST start "/modules" or "/packages" here. - if (name.charAt(1) == 'm') { + LocationType type = loc.getType(); + if (type == MODULES_DIR || type == MODULES_ROOT) { completeModuleDirectory(dir, loc); } else { - completePackageDirectory(dir, loc); + assert type == PACKAGES_DIR : "Invalid location type: " + loc; + completePackageSubdirectory(dir, loc); } assert dir.isCompleted() : "Directory must be complete by now: " + dir; } - /** - * Completes a modules directory by setting the list of child nodes. - * - *

    The given directory can be the top level {@code /modules} directory, - * so it is NOT safe to use {@code isModulesSubdirectory(loc)} here. - */ + /** Completes a modules directory by setting the list of child nodes. */ private Directory completeModuleDirectory(Directory dir, ImageLocation loc) { assert dir.getName().equals(loc.getFullName()) : "Mismatched location for directory: " + dir; - List children = createChildNodes(loc, childLoc -> { - if (isModulesSubdirectory(childLoc)) { - return nodes.computeIfAbsent(childLoc.getFullName(), this::newDirectory); + List previewOnlyNodes = getPreviewNodesToMerge(dir); + // We hide preview names from direct lookup, but must also prevent + // the preview directory from appearing in any META-INF directories. + boolean parentIsMetaInfDir = isMetaInf(dir); + List children = createChildNodes(loc, previewOnlyNodes.size(), childLoc -> { + LocationType type = childLoc.getType(); + if (type == MODULES_DIR) { + String name = childLoc.getFullName(); + return parentIsMetaInfDir && name.endsWith("/preview") + ? null + : nodes.computeIfAbsent(name, this::newDirectory); } else { + assert type == RESOURCE : "Invalid location type: " + loc; // Add "/modules" prefix to image location paths to get node names. String resourceName = childLoc.getFullName(true); return nodes.computeIfAbsent(resourceName, n -> newResource(n, childLoc)); } }); + children.addAll(previewOnlyNodes); dir.setChildren(children); return dir; } + /** Completes a package directory by setting the list of child nodes. */ + private void completePackageSubdirectory(Directory dir, ImageLocation loc) { + assert dir.getName().equals(loc.getFullName()) : "Mismatched location for directory: " + dir; + assert !dir.isCompleted() : "Directory already completed: " + dir; + assert loc.getType() == PACKAGES_DIR : "Invalid location type: " + loc.getType(); + + // In non-preview mode we might skip a very small number of preview-only + // entries, but it's not worth "right-sizing" the array for that. + IntBuffer offsets = getOffsetBuffer(loc); + List children = new ArrayList<>(offsets.capacity() / 2); + ModuleLink.readNameOffsets(offsets, /*normal*/ true, isPreviewEnabled) + .forEachRemaining(n -> { + String modName = getString(n); + Node link = newLinkNode(dir.getName() + "/" + modName, MODULES_PREFIX + "/" + modName); + children.add(ensureCached(link)); + }); + // If the parent directory exists, there must be at least one child node. + assert !children.isEmpty() : "Invalid empty package directory: " + dir; + dir.setChildren(children); + } + /** - * Completes a package directory by setting the list of child nodes. + * Returns the list of child preview nodes to be merged into the given directory. * - *

    The given directory can be the top level {@code /packages} directory, - * so it is NOT safe to use {@code isPackagesSubdirectory(loc)} here. + *

    Because this is only called once per-directory (since the result is cached + * indefinitely) we can remove any entries we find from the cache. If ever the + * node cache allowed entries to expire, this would have to be changed so that + * directories could be completed more than once. */ - private Directory completePackageDirectory(Directory dir, ImageLocation loc) { - assert dir.getName().equals(loc.getFullName()) : "Mismatched location for directory: " + dir; - // The only directories in the "/packages" namespace are "/packages" or - // "/packages/". However, unlike "/modules" directories, the - // location offsets mean different things. - List children; - if (dir.getName().equals(PACKAGES_ROOT)) { - // Top-level directory just contains a list of subdirectories. - children = createChildNodes(loc, c -> nodes.computeIfAbsent(c.getFullName(), this::newDirectory)); - } else { - // A package directory's content is array of offset PAIRS in the - // Strings table, but we only need the 2nd value of each pair. - IntBuffer intBuffer = getOffsetBuffer(loc); - int offsetCount = intBuffer.capacity(); - assert (offsetCount & 0x1) == 0 : "Offset count must be even: " + offsetCount; - children = new ArrayList<>(offsetCount / 2); - // Iterate the 2nd offset in each pair (odd indices). - for (int i = 1; i < offsetCount; i += 2) { - String moduleName = getString(intBuffer.get(i)); - children.add(nodes.computeIfAbsent( - dir.getName() + "/" + moduleName, - n -> newLinkNode(n, MODULES_ROOT + "/" + moduleName))); + List getPreviewNodesToMerge(Directory dir) { + if (previewDirectoriesToMerge != null) { + Directory mergeDir = previewDirectoriesToMerge.remove(dir.getName()); + if (mergeDir != null) { + return mergeDir.children; } } - // This only happens once and "completes" the directory. - dir.setChildren(children); - return dir; + return Collections.emptyList(); } /** - * Creates the list of child nodes for a {@code Directory} based on a given + * Creates the list of child nodes for a modules {@code Directory} from + * its parent location. * - *

    Note: This cannot be used for package subdirectories as they have - * child offsets stored differently to other directories. + *

    The {@code getChildFn} may return existing cached nodes rather + * than creating them, and if newly created nodes are to be cached, + * it is the job of {@code getChildFn}, or the caller of this method, + * to do that. + * + * @param loc a location relating to a "/modules" directory. + * @param extraNodesCount a known number of preview-only child nodes + * which will be merged onto the end of the returned list later. + * @param getChildFn a function to return a node for each child location + * (or null to skip putting anything in the list). + * @return the list of the non-null child nodes, returned by + * {@code getChildFn}, in the order of the locations entries. */ - private List createChildNodes(ImageLocation loc, Function newChildFn) { + private List createChildNodes(ImageLocation loc, int extraNodesCount, Function getChildFn) { + LocationType type = loc.getType(); + assert type == MODULES_DIR || type == MODULES_ROOT : "Invalid location type: " + loc; IntBuffer offsets = getOffsetBuffer(loc); int childCount = offsets.capacity(); - List children = new ArrayList<>(childCount); + List children = new ArrayList<>(childCount + extraNodesCount); for (int i = 0; i < childCount; i++) { - children.add(newChildFn.apply(getLocation(offsets.get(i)))); + Node childNode = getChildFn.apply(getLocation(offsets.get(i))); + if (childNode != null) { + children.add(childNode); + } } return children; } /** Helper to extract the integer offset buffer from a directory location. */ private IntBuffer getOffsetBuffer(ImageLocation dir) { - assert !isResource(dir) : "Not a directory: " + dir.getFullName(); + assert dir.getType() != RESOURCE : "Not a directory: " + dir.getFullName(); byte[] offsets = getResource(dir); ByteBuffer buffer = ByteBuffer.wrap(offsets); buffer.order(getByteOrder()); return buffer.asIntBuffer(); } - /** - * Efficiently determines if an image location is a resource. - * - *

    A resource must have a valid module associated with it, so its - * module offset must be non-zero, and not equal to the offsets for - * "/modules/..." or "/packages/..." entries. - */ - private boolean isResource(ImageLocation loc) { - int moduleOffset = loc.getModuleOffset(); - return moduleOffset != 0 - && moduleOffset != modulesStringOffset - && moduleOffset != packagesStringOffset; - } - - /** - * Determines if an image location is a directory in the {@code /modules} - * namespace (if so, the location name is the node name). - * - *

    In jimage, every {@code ImageLocation} under {@code /modules/} is a - * directory and has the same value for {@code getModule()}, and {@code - * getModuleOffset()}. - */ - private boolean isModulesSubdirectory(ImageLocation loc) { - return loc.getModuleOffset() == modulesStringOffset; - } - /** * Creates an "incomplete" directory node with no child nodes set. * Directories need to be "completed" before they are returned by @@ -584,7 +766,6 @@ private Directory newDirectory(String name) { * In image files, resource locations are NOT prefixed by {@code /modules}. */ private Resource newResource(String name, ImageLocation loc) { - assert name.equals(loc.getFullName(true)) : "Mismatched location for resource: " + name; return new Resource(name, loc, imageFileAttributes); } @@ -816,7 +997,7 @@ public Stream getChildNames() { throw new IllegalStateException("Cannot get child nodes of an incomplete directory: " + getName()); } - private void setChildren(List children) { + private void setChildren(List children) { assert this.children == null : this + ": Cannot set child nodes twice!"; this.children = Collections.unmodifiableList(children); } diff --git a/src/java.base/share/classes/jdk/internal/jimage/ImageStrings.java b/src/java.base/share/classes/jdk/internal/jimage/ImageStrings.java index eea62e444de3..9cd1e662d644 100644 --- a/src/java.base/share/classes/jdk/internal/jimage/ImageStrings.java +++ b/src/java.base/share/classes/jdk/internal/jimage/ImageStrings.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -33,6 +33,23 @@ * to the jimage file provided by the shipped JDK by tools running on JDK 8. */ public interface ImageStrings { + // String offset constants are useful for efficient classification + // of location entries without string comparison. + // They are validated during initialization of ImageStringsWriter. + // + // Adding new strings (with larger offsets) is possible without changing + // the jimage version number, but any change to existing strings must be + // accompanied by a jimage version number change. + + /** Fixed offset for the empty string in the strings table. */ + int EMPTY_STRING_OFFSET = 0; + /** Fixed offset for the string "class" in the strings table. */ + int CLASS_STRING_OFFSET = 1; + /** Fixed offset for the string "modules" in the strings table. */ + int MODULES_STRING_OFFSET = 7; + /** Fixed offset for the string "packages" in the strings table. */ + int PACKAGES_STRING_OFFSET = 15; + String get(int offset); int add(final String string); diff --git a/src/java.base/share/classes/jdk/internal/jimage/ModuleLink.java b/src/java.base/share/classes/jdk/internal/jimage/ModuleLink.java new file mode 100644 index 000000000000..57224906179f --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/jimage/ModuleLink.java @@ -0,0 +1,290 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.jimage; + +import java.nio.IntBuffer; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.function.Function; + +/** + * Represents links to modules stored in the buffer of {@code "/packages/xxx"} + * image locations (package subdirectories). + * + *

    Package subdirectories store their data differently to all other jimage + * entries. Instead of storing a sequence of offsets to their child entries, + * they store a flattened representation of the child's data in an interleaved + * buffer. These entries also use flags which are similar to, but distinct from, + * the {@link ImageLocation} flags. + * + *

    This class encapsulates that complexity to help avoid confusion. + * + * @implNote This class needs to maintain JDK 8 source compatibility. + * + * It is used internally in the JDK to implement jimage/jrtfs access, + * but also compiled and delivered as part of the jrtfs.jar to support access + * to the jimage file provided by the shipped JDK by tools running on JDK 8. + */ +public final class ModuleLink implements Comparable { + // These flags are additive (hence "has-content" rather than "is-empty"). + + /** If set, this package exists in preview mode. */ + private static final int FLAGS_PKG_HAS_PREVIEW_VERSION = 0x1; + /** If set, this package exists in non-preview mode. */ + private static final int FLAGS_PKG_HAS_NORMAL_VERSION = 0x2; + /** If set, the associated module has resources (in normal or preview mode). */ + private static final int FLAGS_PKG_HAS_RESOURCES = 0x4; + + /** + * Links are ordered with preview versions first, which permits early + * exit when processing preview entries (it's reversed because the default + * order for a boolean is {@code false < true}). + */ + private static final Comparator PREVIEW_FIRST = + Comparator.comparing(ModuleLink::hasPreviewVersion).reversed() + .thenComparing(ModuleLink::name); + + /** + * Returns a link for non-empty packages (those with resources) in a + * given module. + * + *

    The same link can be used for multiple packages in the same module. + * + * @param moduleName the name of the module in which this package exits. + * @param isPreview whether the associated package is defined for preview mode. + */ + public static ModuleLink forPackage(String moduleName, boolean isPreview) { + return new ModuleLink(moduleName, FLAGS_PKG_HAS_RESOURCES | previewFlag(isPreview)); + } + + /** + * Returns a link for empty packages in a given module. + * + *

    The same link can be used for multiple packages in the same module. + * + * @param moduleName the name of the module in which this package exits. + * @param isPreview whether the associated package is defined for preview mode. + */ + public static ModuleLink forEmptyPackage(String moduleName, boolean isPreview) { + return new ModuleLink(moduleName, previewFlag(isPreview)); + } + + /** + * Returns the appropriate FLAGS_PKG_HAS_XXX_VERSION constant according to + * whether the associated package is defined for preview mode. + */ + private static int previewFlag(boolean isPreview) { + return isPreview ? FLAGS_PKG_HAS_PREVIEW_VERSION : FLAGS_PKG_HAS_NORMAL_VERSION; + } + + /** Merges two links for the same module (combining their flags). */ + public ModuleLink merge(ModuleLink other) { + if (!name.equals(other.name)) { + throw new IllegalArgumentException("Cannot merge " + other + " with " + this); + } + // Because flags are additive, we can just OR them here. + return new ModuleLink(name, flags | other.flags); + } + + private final String name; + private final int flags; + + private ModuleLink(String moduleName, int flags) { + this.name = Objects.requireNonNull(moduleName); + this.flags = flags; + } + + /** Returns the module name of this link. */ + public String name() { + return name; + } + + /** + * Returns whether the package associated with this link contains resources + * in its module. + * + *

    An invariant of the module system is that while a package may exist + * under many modules, it only has resources in one. + */ + public boolean hasResources() { + return (flags & FLAGS_PKG_HAS_RESOURCES) != 0; + } + + /** + * Returns whether the package associated with this module link has a + * preview version (empty or otherwise) in this link's module. + */ + public boolean hasPreviewVersion() { + return (flags & FLAGS_PKG_HAS_PREVIEW_VERSION) != 0; + } + + /** Returns whether this module link exists only in preview mode. */ + public boolean isPreviewOnly() { + return (flags & FLAGS_PKG_HAS_NORMAL_VERSION) == 0; + } + + @Override + public int compareTo(ModuleLink rhs) { + return PREVIEW_FIRST.compare(this, rhs); + } + + @Override + public String toString() { + return "ModuleLink{ module=" + name + ", flags=" + flags + " }"; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof ModuleLink)) { + return false; + } + ModuleLink other = (ModuleLink) obj; + return name.equals(other.name) && flags == other.flags; + } + + @Override + public int hashCode() { + return Objects.hash(name, flags); + } + + /** + * Reads the content buffer of a package subdirectory to return a sequence + * of module name offsets in the jimage. + * + *

    Package subdirectories store their entries using pairs of integers in + * an interleaved buffer: + *

    +     *     ...
    +     *     [ entry-N flags ]
    +     *     [ entry-N name offset ]
    +     *     [ entry-(N+1) flags ]
    +     *     [ entry-(N+1) name offset ]
    +     *     ...
    +     * 
    + * + *

    Entry flags control whether an entry name should be included by the + * returned iterator, depending on the given include-flags. + * + * @param buffer the content buffer of an {@link ImageLocation} with type + * {@link ImageLocation.LocationType#PACKAGES_DIR PACKAGES_DIR}. + * @param includeNormal whether to include name offsets for modules present + * in normal (non-preview) mode. + * @param includePreview whether to include name offsets for modules present + * in preview mode. + * @return an iterator of module name offsets. + */ + public static Iterator readNameOffsets( + IntBuffer buffer, boolean includeNormal, boolean includePreview) { + int bufferSize = buffer.capacity(); + if (bufferSize == 0 || (bufferSize & 0x1) != 0) { + throw new IllegalArgumentException("Invalid buffer size"); + } + int includeMask = (includeNormal ? FLAGS_PKG_HAS_NORMAL_VERSION : 0) + + (includePreview ? FLAGS_PKG_HAS_PREVIEW_VERSION : 0); + if (includeMask == 0) { + throw new IllegalArgumentException("Invalid flags"); + } + + return new Iterator() { + private int idx = nextIdx(0); + + int nextIdx(int idx) { + for (; idx < bufferSize; idx += 2) { + // If any of the test flags are set, include this entry. + if ((buffer.get(idx) & includeMask) != 0) { + return idx; + } else if (!includeNormal) { + // Preview entries are first in the offset buffer, so we + // can exit early (by returning the end index) if we are + // only iterating preview entries, and have run out. + break; + } + } + return bufferSize; + } + + @Override + public boolean hasNext() { + return idx < bufferSize; + } + + @Override + public Integer next() { + if (idx < bufferSize) { + int nameOffset = buffer.get(idx + 1); + idx = nextIdx(idx + 2); + return nameOffset; + } + throw new NoSuchElementException(); + } + }; + } + + /** + * Writes a list of module links to a given buffer. The given entry list is + * checked carefully to ensure the written buffer will be valid. + * + *

    Entries are written in order, taking two integer slots per entry as + * {@code [, ]}. + * + * @param links the module links to write, correctly ordered. + * @param buffer destination buffer. + * @param nameEncoder encoder for module names. + * @throws IllegalArgumentException in the link entries are invalid in any way. + */ + public static void write( + List links, IntBuffer buffer, Function nameEncoder) { + if (links.isEmpty()) { + throw new IllegalArgumentException("References list must be non-empty"); + } + int expectedCapacity = 2 * links.size(); + if (buffer.capacity() != expectedCapacity) { + throw new IllegalArgumentException( + "Invalid buffer capacity: expected " + expectedCapacity + ", got " + buffer.capacity()); + } + // This catches exact duplicates in the list. + links.stream().reduce((lhs, rhs) -> { + if (lhs.compareTo(rhs) >= 0) { + throw new IllegalArgumentException("References must be strictly ordered: " + links); + } + return rhs; + }); + // Distinct references can have the same name (but we don't allow this). + if (links.stream().map(ModuleLink::name).distinct().count() != links.size()) { + throw new IllegalArgumentException("Module links names must be unique: " + links); + } + if (links.stream().filter(ModuleLink::hasResources).count() > 1) { + throw new IllegalArgumentException("At most one module link can have resources: " + links); + } + for (ModuleLink link : links) { + buffer.put(link.flags); + buffer.put(nameEncoder.apply(link.name)); + } + } +} diff --git a/src/java.base/share/classes/jdk/internal/jimage/PreviewMode.java b/src/java.base/share/classes/jdk/internal/jimage/PreviewMode.java new file mode 100644 index 000000000000..dd273494b153 --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/jimage/PreviewMode.java @@ -0,0 +1,86 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.jimage; + +import java.lang.reflect.InvocationTargetException; + +/** + * Specifies the preview mode used to open a jimage file via {@link ImageReader}. + * + * @implNote This class needs to maintain JDK 8 source compatibility. + * + * It is used internally in the JDK to implement jimage/jrtfs access, + * but also compiled and delivered as part of the jrtfs.jar to support access + * to the jimage file provided by the shipped JDK by tools running on JDK 8. + * */ +public enum PreviewMode { + /** + * Preview mode is disabled. No preview classes or resources will be available + * in this mode. + */ + DISABLED, + /** + * Preview mode is enabled. If preview classes or resources exist in the jimage file, + * they will be made available. + */ + ENABLED, + /** + * The preview mode of the current run-time, typically determined by the + * {@code --enable-preview} flag. + */ + FOR_RUNTIME; + + /** + * Resolves whether preview mode should be enabled for an {@link ImageReader}. + */ + public boolean isPreviewModeEnabled() { + // A switch, instead of an abstract method, saves 3 subclasses. + switch (this) { + case DISABLED: + return false; + case ENABLED: + return true; + case FOR_RUNTIME: + // We want to call jdk.internal.misc.PreviewFeatures.isEnabled(), but + // is not available in older JREs, so we must look to it reflectively. + Class clazz; + try { + clazz = Class.forName("jdk.internal.misc.PreviewFeatures"); + } catch (ClassNotFoundException e) { + // It is valid and expected that the class might not exist (JDK-8). + return false; + } + try { + return (Boolean) clazz.getDeclaredMethod("isEnabled").invoke(null); + } catch (NoSuchMethodException | IllegalAccessException | + InvocationTargetException e) { + // But if the class exists, the method must exist and be callable. + throw new InternalError(e); + } + default: + throw new IllegalStateException("Invalid mode: " + this); + } + } +} diff --git a/src/java.base/share/classes/jdk/internal/jimage/ResourceEntries.java b/src/java.base/share/classes/jdk/internal/jimage/ResourceEntries.java new file mode 100644 index 000000000000..18b3675d399a --- /dev/null +++ b/src/java.base/share/classes/jdk/internal/jimage/ResourceEntries.java @@ -0,0 +1,73 @@ +/* + * 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.internal.jimage; + +import java.util.stream.Stream; + +/** + * Accesses the underlying resource entries in a jimage file. + * + *

    This is a special-case API designed only for use by the jlink tool, + * which read the raw jimage files. It is not the correct API for accessing + * jimage resources at runtime. + * + *

    This API ignores the {@code previewMode} of the {@link ImageReader} from + * which it is obtained, and returns an unmapped view of entries (e.g. allowing + * for direct access of resources in the {@code META-INF/preview/...} namespace). + * + *

    It disallows access to resource directories (i.e. {@code "/modules/..."}) + * or packages entries (i.e. {@code "/packages/..."}). + * + *

    Use the {@link ImageReader} API to correctly account for preview mode at + * runtime. + * + * @implNote This class needs to maintain JDK 8 source compatibility. + * + * It is used internally in the JDK to implement jimage/jrtfs access, + * but also compiled and delivered as part of the jrtfs.jar to support access + * to the jimage file provided by the shipped JDK by tools running on JDK 8. + */ +public interface ResourceEntries { + /** + * Returns the jimage names for all resources in the given module, in + * random order. Entry names will always be prefixed by the given module + * name (e.g. {@code "//..."}). + */ + Stream getEntryNames(String module); + + /** + * Returns the (uncompressed) size of a resource given its jimage name. + * + * @throws java.util.NoSuchElementException if the resource does not exist. + */ + long getSize(String name); + + /** + * Returns a copy of a resource's content given its jimage name. + * + * @throws java.util.NoSuchElementException if the resource does not exist. + */ + byte[] getBytes(String name); +} diff --git a/src/java.base/share/classes/jdk/internal/jimage/ImageReaderFactory.java b/src/java.base/share/classes/jdk/internal/jimage/SystemImageReader.java similarity index 57% rename from src/java.base/share/classes/jdk/internal/jimage/ImageReaderFactory.java rename to src/java.base/share/classes/jdk/internal/jimage/SystemImageReader.java index 2ecec20d6f9b..87f301c8ba89 100644 --- a/src/java.base/share/classes/jdk/internal/jimage/ImageReaderFactory.java +++ b/src/java.base/share/classes/jdk/internal/jimage/SystemImageReader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -29,14 +29,9 @@ import java.io.UncheckedIOException; import java.nio.file.FileSystem; import java.nio.file.FileSystems; -import java.nio.file.Path; -import java.util.concurrent.ConcurrentHashMap; -import java.util.Map; -import java.util.Objects; -import java.util.function.Function; /** - * Factory to get ImageReader + * Static holder class for singleton {@link ImageReader} instance. * * @implNote This class needs to maintain JDK 8 source compatibility. * @@ -44,15 +39,13 @@ * but also compiled and delivered as part of the jrtfs.jar to support access * to the jimage file provided by the shipped JDK by tools running on JDK 8. */ -public class ImageReaderFactory { - private ImageReaderFactory() {} - - private static final String JAVA_HOME = System.getProperty("java.home"); - private static final Path BOOT_MODULES_JIMAGE; +public class SystemImageReader { + private static final ImageReader SYSTEM_IMAGE_READER; static { + String javaHome = System.getProperty("java.home"); FileSystem fs; - if (ImageReaderFactory.class.getClassLoader() == null) { + if (SystemImageReader.class.getClassLoader() == null) { try { fs = (FileSystem) Class.forName("sun.nio.fs.DefaultFileSystemProvider") .getMethod("theFileSystem") @@ -63,44 +56,32 @@ private ImageReaderFactory() {} } else { fs = FileSystems.getDefault(); } - BOOT_MODULES_JIMAGE = fs.getPath(JAVA_HOME, "lib", "modules"); + try { + SYSTEM_IMAGE_READER = ImageReader.open(fs.getPath(javaHome, "lib", "modules"), PreviewMode.FOR_RUNTIME); + } catch (IOException e) { + throw new ExceptionInInitializerError(e); + } } - private static final Map readers = new ConcurrentHashMap<>(); - /** - * Returns an {@code ImageReader} to read from the given image file + * Returns the singleton {@code ImageReader} to read the image file in this + * run-time image. The returned instance must not be closed. + * + * @throws UncheckedIOException if an I/O error occurs */ - public static ImageReader get(Path jimage) throws IOException { - Objects.requireNonNull(jimage); - try { - return readers.computeIfAbsent(jimage, OPENER); - } catch (UncheckedIOException io) { - throw io.getCause(); - } + public static ImageReader get() { + return SYSTEM_IMAGE_READER; } - private static Function OPENER = new Function() { - public ImageReader apply(Path path) { - try { - return ImageReader.open(path); - } catch (IOException io) { - throw new UncheckedIOException(io); - } - } - }; - /** - * Returns the {@code ImageReader} to read the image file in this - * run-time image. + * Returns the "raw" API for accessing underlying jimage resource entries. * - * @throws UncheckedIOException if an I/O error occurs + *

    This is only meaningful for use by code dealing directly with jimage + * files, and cannot be used to reliably lookup resources used at runtime. */ - public static ImageReader getImageReader() { - try { - return get(BOOT_MODULES_JIMAGE); - } catch (IOException ioe) { - throw new UncheckedIOException(ioe); - } + public static ResourceEntries getResourceEntries() { + return get().getResourceEntries(); } + + private SystemImageReader() {} } 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 c405801506fd..4cd47413bd0d 100644 --- a/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileSystem.java +++ b/src/java.base/share/classes/jdk/internal/jrtfs/JrtFileSystem.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -63,6 +63,7 @@ import java.util.Set; import java.util.regex.Pattern; import jdk.internal.jimage.ImageReader.Node; +import jdk.internal.jimage.PreviewMode; /** * jrt file system implementation built on System jimage files. @@ -85,7 +86,8 @@ class JrtFileSystem extends FileSystem { throws IOException { this.provider = provider; - this.image = SystemImage.open(); // open image file + // TODO: Obtain and pass correct preview mode flag value here. + this.image = SystemImage.open(PreviewMode.DISABLED); // open image file this.isOpen = true; this.isClosable = env != null; } diff --git a/src/java.base/share/classes/jdk/internal/jrtfs/SystemImage.java b/src/java.base/share/classes/jdk/internal/jrtfs/SystemImage.java index b38e953a5f95..560e4942a17d 100644 --- a/src/java.base/share/classes/jdk/internal/jrtfs/SystemImage.java +++ b/src/java.base/share/classes/jdk/internal/jrtfs/SystemImage.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -39,6 +39,7 @@ import jdk.internal.jimage.ImageReader; import jdk.internal.jimage.ImageReader.Node; +import jdk.internal.jimage.PreviewMode; /** * @implNote This class needs to maintain JDK 8 source compatibility. @@ -47,35 +48,49 @@ * but also compiled and delivered as part of the jrtfs.jar to support access * to the jimage file provided by the shipped JDK by tools running on JDK 8. */ -@SuppressWarnings({ "removal", "suppression"} ) -abstract class SystemImage { +@SuppressWarnings({"removal", "suppression"}) +public abstract class SystemImage implements AutoCloseable { - abstract Node findNode(String path) throws IOException; - abstract byte[] getResource(Node node) throws IOException; - abstract void close() throws IOException; + public abstract Node findNode(String path) throws IOException; + public abstract byte[] getResource(Node node) throws IOException; + public abstract void close() throws IOException; - static SystemImage open() throws IOException { - if (modulesImageExists) { - // open a .jimage and build directory structure - final ImageReader image = ImageReader.open(moduleImageFile); - return new SystemImage() { - @Override - Node findNode(String path) throws IOException { - return image.findNode(path); - } - @Override - byte[] getResource(Node node) throws IOException { - return image.getResource(node); - } - @Override - void close() throws IOException { - image.close(); - } - }; + /** + * Opens the system image for the current runtime. + * + * @param mode determines whether preview mode should be enabled. + * @return a new system image based on either the jimage file or an "exploded" + * modules directory, according to the build state. + */ + public static SystemImage open(PreviewMode mode) throws IOException { + return modulesImageExists ? fromJimage(moduleImageFile, mode) : fromDirectory(explodedModulesDir, mode); + } + + /** Internal factory method for testing only, use {@link SystemImage#open(PreviewMode)}. */ + public static SystemImage fromJimage(Path path, PreviewMode mode) throws IOException { + final ImageReader image = ImageReader.open(path, mode); + return new SystemImage() { + @Override + public Node findNode(String path) throws IOException { + return image.findNode(path); + } + @Override + public byte[] getResource(Node node) throws IOException { + return image.getResource(node); + } + @Override + public void close() throws IOException { + image.close(); + } + }; + } + + /** Internal factory method for testing only, use {@link SystemImage#open(PreviewMode)}. */ + public static SystemImage fromDirectory(Path modulesDir, PreviewMode mode) throws IOException { + if (!Files.isDirectory(modulesDir)) { + throw new FileSystemNotFoundException(modulesDir.toString()); } - if (Files.notExists(explodedModulesDir)) - throw new FileSystemNotFoundException(explodedModulesDir.toString()); - return new ExplodedImage(explodedModulesDir); + return new ExplodedImage(modulesDir); } private static final String RUNTIME_HOME; diff --git a/src/java.base/share/classes/jdk/internal/lang/LazyConstantImpl.java b/src/java.base/share/classes/jdk/internal/lang/LazyConstantImpl.java index 59d0174c4c95..e5a1dd62c117 100644 --- a/src/java.base/share/classes/jdk/internal/lang/LazyConstantImpl.java +++ b/src/java.base/share/classes/jdk/internal/lang/LazyConstantImpl.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 @@ -30,6 +30,7 @@ import jdk.internal.vm.annotation.ForceInline; import jdk.internal.vm.annotation.Stable; +import java.util.NoSuchElementException; import java.util.Objects; import java.util.function.Supplier; @@ -67,12 +68,13 @@ public final class LazyConstantImpl implements LazyConstant { // The field needs to be `volatile` as a lazy constant can be // created by one thread and computed by another thread. // After the function is successfully invoked, the field is set to - // `null` to allow the function to be collected. - @Stable - private volatile Supplier computingFunction; + // `null` to allow the function to be collected. If the function fails, the field is + // set to the fully qualified name of the exception class. We are not storing the + // exception class as that would have pinned the class loader of the exception. + private volatile Object computingFunctionOrExceptionType; private LazyConstantImpl(Supplier computingFunction) { - this.computingFunction = computingFunction; + this.computingFunctionOrExceptionType = computingFunction; } @ForceInline @@ -82,34 +84,53 @@ public T get() { return (t != null) ? t : getSlowPath(); } + @SuppressWarnings("unchecked") private T getSlowPath() { preventReentry(); synchronized (this) { T t = getAcquire(); if (t == null) { - t = computingFunction.get(); - Objects.requireNonNull(t); - setRelease(t); - // Allow the underlying supplier to be collected after successful use - computingFunction = null; + switch (computingFunctionOrExceptionType) { + case Supplier computingFunction -> { + try { + @SuppressWarnings("unchecked") + final T newT = (T) computingFunction.get(); + t = newT; + Objects.requireNonNull(t); + setRelease(t); + // Allow the underlying supplier to be collected after + // a successful initialization + computingFunctionOrExceptionType = null; + } catch (Throwable ex) { + // Release the original computing function and replace it with + // an exception marker + final String exceptionType = ex.getClass().getName().intern(); + computingFunctionOrExceptionType = exceptionType; + throw unableToAccessConstant(exceptionType, ex); + } + } + case String exceptionType -> + throw unableToAccessConstant(exceptionType, null); + default -> + throw new InternalError("Cannot reach here"); + } } return t; } } + static NoSuchElementException unableToAccessConstant(String exceptionType, Throwable cause) { + return new NoSuchElementException("Unable to access the constant because " + + exceptionType + " was thrown at initial computation", cause); + } + + // For testing only @ForceInline - @Override public T orElse(T other) { final T t = getAcquire(); return (t == null) ? other : t; } - @ForceInline - @Override - public boolean isInitialized() { - return getAcquire() != null; - } - @Override public String toString() { return super.toString() + "[" + toStringSuffix() + "]"; @@ -121,16 +142,19 @@ private String toStringSuffix() { return "(this LazyConstant)"; } else if (t != null) { return t.toString(); + } else { + // Volatile read + final Object cf = computingFunctionOrExceptionType; + // There could be a race here + if (cf != null) { + return (cf instanceof Supplier supplier) + ? "computing function=" + isolateToString(supplier) + : "failed with=" + cf; + } + // As we know `computingFunction` is `null` or via a volatile read, we + // can now be sure that this lazy constant is initialized + return getAcquire().toString(); } - // Volatile read - final Supplier cf = computingFunction; - // There could be a race here - if (cf != null) { - return "computing function=" + computingFunction.toString(); - } - // As we know `computingFunction` is `null` via a volatile read, we - // can now be sure that this lazy constant is initialized - return getAcquire().toString(); } @@ -160,7 +184,16 @@ private void setRelease(T newValue) { private void preventReentry() { if (Thread.holdsLock(this)) { - throw new IllegalStateException("Recursive invocation of a LazyConstant's computing function: " + computingFunction); + throw new IllegalStateException("Recursive invocation of a LazyConstant's computing function: " + isolateToString(computingFunctionOrExceptionType)); + } + } + + public static String isolateToString(Object input) { + // Protect against user-controlled `input.toString` methods that might throw or recurse. + try { + return input.toString(); + } catch (Throwable t) { + return Objects.toIdentityString(input); } } diff --git a/src/java.base/share/classes/jdk/internal/module/SystemModuleFinders.java b/src/java.base/share/classes/jdk/internal/module/SystemModuleFinders.java index afebb89168cd..565da646e287 100644 --- a/src/java.base/share/classes/jdk/internal/module/SystemModuleFinders.java +++ b/src/java.base/share/classes/jdk/internal/module/SystemModuleFinders.java @@ -54,7 +54,7 @@ import java.util.stream.StreamSupport; import jdk.internal.jimage.ImageReader; -import jdk.internal.jimage.ImageReaderFactory; +import jdk.internal.jimage.SystemImageReader; import jdk.internal.access.JavaNetUriAccess; import jdk.internal.access.SharedSecrets; import jdk.internal.util.StaticProperty; @@ -392,7 +392,7 @@ public byte[] generate(String algorithm) { * Holder class for the ImageReader. */ private static class SystemImage { - static final ImageReader READER = ImageReaderFactory.getImageReader(); + static final ImageReader READER = SystemImageReader.get(); static ImageReader reader() { return READER; } diff --git a/src/java.base/share/classes/jdk/internal/vm/ThreadDumper.java b/src/java.base/share/classes/jdk/internal/vm/ThreadDumper.java index fa7d4bab0766..63d2d300421f 100644 --- a/src/java.base/share/classes/jdk/internal/vm/ThreadDumper.java +++ b/src/java.base/share/classes/jdk/internal/vm/ThreadDumper.java @@ -603,7 +603,7 @@ private static class BoundedByteArrayOutputStream extends ByteArrayOutputStream } @Override public void write(int b) { - if (max < count) { + if (count < max) { super.write(b); } } diff --git a/src/java.base/share/classes/sun/launcher/LauncherHelper.java b/src/java.base/share/classes/sun/launcher/LauncherHelper.java index 9badf2beaebd..04adf0549b41 100644 --- a/src/java.base/share/classes/sun/launcher/LauncherHelper.java +++ b/src/java.base/share/classes/sun/launcher/LauncherHelper.java @@ -98,6 +98,8 @@ private LauncherHelper() {} "javafx.application.Application"; private static final String JAVAFX_FXHELPER_CLASS_NAME_SUFFIX = "sun.launcher.LauncherHelper$FXHelper"; + private static final String JAVAFX_GRAPHICS_MODULE_NAME = + "javafx.graphics"; private static final String LAUNCHER_AGENT_CLASS = "Launcher-Agent-Class"; private static final String MAIN_CLASS = "Main-Class"; private static final String ADD_EXPORTS = "Add-Exports"; @@ -768,8 +770,9 @@ public static Class checkAndLoadMain(boolean printToStderr, * the main class may or may not have a main method, so do this before * validating the main class. */ - if (JAVAFX_FXHELPER_CLASS_NAME_SUFFIX.equals(mainClass.getName()) || - doesExtendFXApplication(mainClass)) { + if ((JAVAFX_FXHELPER_CLASS_NAME_SUFFIX.equals(mainClass.getName()) || + doesExtendFXApplication(mainClass)) && + ModuleLayer.boot().findModule(JAVAFX_GRAPHICS_MODULE_NAME).isPresent()) { // Will abort() if there are problems with FX runtime FXHelper.setFXLaunchParameters(what, mode); mainClass = FXHelper.class; @@ -1082,9 +1085,6 @@ public String toString() { static final class FXHelper { - private static final String JAVAFX_GRAPHICS_MODULE_NAME = - "javafx.graphics"; - private static final String JAVAFX_LAUNCHER_CLASS_NAME = "com.sun.javafx.application.LauncherImpl"; diff --git a/src/java.base/share/classes/sun/net/www/protocol/jrt/JavaRuntimeURLConnection.java b/src/java.base/share/classes/sun/net/www/protocol/jrt/JavaRuntimeURLConnection.java index 71080950b80d..d994f2d38421 100644 --- a/src/java.base/share/classes/sun/net/www/protocol/jrt/JavaRuntimeURLConnection.java +++ b/src/java.base/share/classes/sun/net/www/protocol/jrt/JavaRuntimeURLConnection.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -34,7 +34,7 @@ import jdk.internal.jimage.ImageReader; import jdk.internal.jimage.ImageReader.Node; -import jdk.internal.jimage.ImageReaderFactory; +import jdk.internal.jimage.SystemImageReader; import sun.net.www.ParseUtil; import sun.net.www.URLConnection; @@ -48,7 +48,7 @@ public class JavaRuntimeURLConnection extends URLConnection { // ImageReader to access resources in jimage. - private static final ImageReader READER = ImageReaderFactory.getImageReader(); + private static final ImageReader READER = SystemImageReader.get(); // The module and resource name in the URL (i.e. "jrt:/[$MODULE[/$PATH]]"). // @@ -109,9 +109,10 @@ public InputStream getInputStream() throws IOException { @Override public long getContentLengthLong() { + // connectResourceNode() may throw UncheckedIOException. try { return connectResourceNode().size(); - } catch (IOException ioe) { + } catch (IOException | UncheckedIOException ioe) { return -1L; } } @@ -124,6 +125,10 @@ public int getContentLength() { // Perform percent decoding of the resource name/path from the URL. private static String percentDecode(String path) throws MalformedURLException { + if (path.indexOf('%') == -1) { + // Nothing to decode (common case). + return path; + } // Any additional special case decoding logic should go here. try { return ParseUtil.decode(path); diff --git a/src/java.base/share/classes/sun/security/pkcs12/PKCS12KeyStore.java b/src/java.base/share/classes/sun/security/pkcs12/PKCS12KeyStore.java index 33e8406a7f63..c7240b54ebdc 100644 --- a/src/java.base/share/classes/sun/security/pkcs12/PKCS12KeyStore.java +++ b/src/java.base/share/classes/sun/security/pkcs12/PKCS12KeyStore.java @@ -2503,6 +2503,7 @@ private String getUnfriendlyName() { * 30 80 02 01 03 30 80 06 09 2A 86 48 86 F7 0D 01 07 01 A0 80 24 80 04 -- * 30 82 -- -- 02 01 03 30 82 -- -- 06 09 2A 86 48 86 F7 0D 01 07 01 A0 8- * 30 -- 02 01 03 30 -- 06 09 2A 86 48 86 F7 0D 01 07 01 A0 -- 04 -- -- -- + * 30 81 -- 02 01 03 30 -- 06 09 2A 86 48 86 F7 0D 01 07 01 A0 -- 04 -- -- * 30 81 -- 02 01 03 30 81 -- 06 09 2A 86 48 86 F7 0D 01 07 01 A0 81 -- 04 * 30 82 -- -- 02 01 03 30 81 -- 06 09 2A 86 48 86 F7 0D 01 07 01 A0 81 -- * 30 83 -- -- -- 02 01 03 30 82 -- -- 06 09 2A 86 48 86 F7 0D 01 07 01 A0 @@ -2515,6 +2516,7 @@ private String getUnfriendlyName() { { 0x3080020103308006L, 0x092A864886F70D01L, 0x0701A08024800400L }, { 0x3082000002010330L, 0x82000006092A8648L, 0x86F70D010701A080L }, { 0x3000020103300006L, 0x092A864886F70D01L, 0x0701A00004000000L }, + { 0x3081000201033000L, 0x06092A864886F70DL, 0x010701A000040000L }, { 0x3081000201033081L, 0x0006092A864886F7L, 0x0D010701A0810004L }, { 0x3082000002010330L, 0x810006092A864886L, 0xF70D010701A08100L }, { 0x3083000000020103L, 0x3082000006092A86L, 0x4886F70D010701A0L }, @@ -2527,6 +2529,7 @@ private String getUnfriendlyName() { { 0xFFFFFFFFFFFFFFFFL, 0xFFFFFFFFFFFFFFFFL, 0xFFFFFFFFFFFFFF00L }, { 0xFFFF0000FFFFFFFFL, 0xFF0000FFFFFFFFFFL, 0xFFFFFFFFFFFFFFF0L }, { 0xFF00FFFFFFFF00FFL, 0xFFFFFFFFFFFFFFFFL, 0xFFFFFF00FF000000L }, + { 0xFFFF00FFFFFFFF00L, 0xFFFFFFFFFFFFFFFFL, 0xFFFFFFFF00FF0000L }, { 0xFFFF00FFFFFFFFFFL, 0x00FFFFFFFFFFFFFFL, 0xFFFFFFFFFFFF00FFL }, { 0xFFFF0000FFFFFFFFL, 0xFF00FFFFFFFFFFFFL, 0xFFFFFFFFFFFFFF00L }, { 0xFFFF000000FFFFFFL, 0xFFFF0000FFFFFFFFL, 0xFFFFFFFFFFFFFFFFL }, diff --git a/src/java.base/share/classes/sun/security/provider/SHA3.java b/src/java.base/share/classes/sun/security/provider/SHA3.java index 0578645c1cd5..5eafface0d3e 100644 --- a/src/java.base/share/classes/sun/security/provider/SHA3.java +++ b/src/java.base/share/classes/sun/security/provider/SHA3.java @@ -28,6 +28,7 @@ import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; import java.nio.ByteOrder; +import java.security.DigestException; import java.security.ProviderException; import java.util.Arrays; import java.util.Objects; @@ -481,6 +482,11 @@ public byte[] digest() { return engineDigest(); } + public int digest(byte[] out, int offs, int len) + throws DigestException { + return engineDigest(out, offs, len); + } + public void squeeze(byte[] output, int offset, int numBytes) { implSqueeze(output, offset, numBytes); } diff --git a/src/java.base/share/classes/sun/util/locale/LocaleMatcher.java b/src/java.base/share/classes/sun/util/locale/LocaleMatcher.java index 72844b966d03..bc5115e1ff17 100644 --- a/src/java.base/share/classes/sun/util/locale/LocaleMatcher.java +++ b/src/java.base/share/classes/sun/util/locale/LocaleMatcher.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -30,12 +30,17 @@ import java.util.HashMap; import java.util.List; import java.util.Locale; -import java.util.Locale.*; -import static java.util.Locale.FilteringMode.*; -import static java.util.Locale.LanguageRange.*; +import java.util.Locale.FilteringMode; +import java.util.Locale.LanguageRange; import java.util.Map; -import java.util.Set; -import java.util.TreeSet; +import java.util.Objects; + +import static java.util.Locale.FilteringMode.AUTOSELECT_FILTERING; +import static java.util.Locale.FilteringMode.EXTENDED_FILTERING; +import static java.util.Locale.FilteringMode.MAP_EXTENDED_RANGES; +import static java.util.Locale.FilteringMode.REJECT_EXTENDED_RANGES; +import static java.util.Locale.LanguageRange.MAX_WEIGHT; +import static java.util.Locale.LanguageRange.MIN_WEIGHT; /** * Implementation for BCP47 Locale matching diff --git a/src/java.base/share/data/cacerts/wisekeyglobalrootgbca b/src/java.base/share/data/cacerts/wisekeyglobalrootgbca new file mode 100644 index 000000000000..5c2c35d04c1d --- /dev/null +++ b/src/java.base/share/data/cacerts/wisekeyglobalrootgbca @@ -0,0 +1,29 @@ +Owner: CN=OISTE WISeKey Global Root GB CA, OU=OISTE Foundation Endorsed, O=WISeKey, C=CH +Issuer: CN=OISTE WISeKey Global Root GB CA, OU=OISTE Foundation Endorsed, O=WISeKey, C=CH +Serial number: 76b1205274f0858746b3f8231af6c2c0 +Valid from: Mon Dec 01 15:00:32 GMT 2014 until: Thu Dec 01 15:10:31 GMT 2039 +Signature algorithm name: SHA256withRSA +Subject Public Key Algorithm: 2048-bit RSA key +Version: 3 +-----BEGIN CERTIFICATE----- +MIIDtTCCAp2gAwIBAgIQdrEgUnTwhYdGs/gjGvbCwDANBgkqhkiG9w0BAQsFADBt +MQswCQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUg +Rm91bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9i +YWwgUm9vdCBHQiBDQTAeFw0xNDEyMDExNTAwMzJaFw0zOTEyMDExNTEwMzFaMG0x +CzAJBgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBG +b3VuZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2Jh +bCBSb290IEdCIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2Be3 +HEokKtaXscriHvt9OO+Y9bI5mE4nuBFde9IllIiCFSZqGzG7qFshISvYD06fWvGx +WuR51jIjK+FTzJlFXHtPrby/h0oLS5daqPZI7H17Dc0hBt+eFf1Biki3IPShehtX +1F1Q/7pn2COZH8g/497/b1t3sWtuuMlk9+HKQUYOKXHQuSP8yYFfTvdv37+ErXNk +u7dCjmn21HYdfp2nuFeKUWdy19SouJVUQHMD9ur06/4oQnc/nSMbsrY9gBQHTC5P +99UKFg29ZkM3fiNDecNAhvVMKdqOmq0NpQSHiB6F4+lT1ZvIiwNjeOvgGUpuuy9r +M2RYk61pv48b74JIxwIDAQABo1EwTzALBgNVHQ8EBAMCAYYwDwYDVR0TAQH/BAUw +AwEB/zAdBgNVHQ4EFgQUNQ/INmNe4qPs+TtmFc5RUuORmj0wEAYJKwYBBAGCNxUB +BAMCAQAwDQYJKoZIhvcNAQELBQADggEBAEBM+4eymYGQfp3FsLAmzYh7KzKNbrgh +cViXfa43FK8+5/ea4n32cZiZBKpDdHij40lhPnOMTZTg+XHEthYOU3gf1qKHLwI5 +gSk8rxWYITD+KJAAjNHhy/peyP34EEY7onhCkRd0VQreUGdNZtGn//3ZwLWoo4rO +ZvUPQ82nK1d7Y0Zqqi5S2PTt4W2tKZB4SLrhI6qjiey1q5bAtEuiHZeeevJuQHHf +aPFlTc58Bd9TZaml8LGXBHAVRgOY1NK/VLSgWH1Sb9pWJmLU2NuJMW8c8CLC02Ic +Nc1MaRVUGpCY3useX8p3x8uOPUNpnJpY0CQ73xtAln41rYHHTnG6iBM= +-----END CERTIFICATE----- diff --git a/src/java.base/share/data/cacerts/wisekeyglobalrootgcca b/src/java.base/share/data/cacerts/wisekeyglobalrootgcca new file mode 100644 index 000000000000..e42432b95a64 --- /dev/null +++ b/src/java.base/share/data/cacerts/wisekeyglobalrootgcca @@ -0,0 +1,22 @@ +Owner: CN=OISTE WISeKey Global Root GC CA, OU=OISTE Foundation Endorsed, O=WISeKey, C=CH +Issuer: CN=OISTE WISeKey Global Root GC CA, OU=OISTE Foundation Endorsed, O=WISeKey, C=CH +Serial number: 212a560caeda0cab4045bf2ba22d3aea +Valid from: Tue May 09 09:48:34 GMT 2017 until: Fri May 09 09:58:33 GMT 2042 +Signature algorithm name: SHA384withECDSA +Subject Public Key Algorithm: 384-bit EC (secp384r1) key +Version: 3 +-----BEGIN CERTIFICATE----- +MIICaTCCAe+gAwIBAgIQISpWDK7aDKtARb8roi066jAKBggqhkjOPQQDAzBtMQsw +CQYDVQQGEwJDSDEQMA4GA1UEChMHV0lTZUtleTEiMCAGA1UECxMZT0lTVEUgRm91 +bmRhdGlvbiBFbmRvcnNlZDEoMCYGA1UEAxMfT0lTVEUgV0lTZUtleSBHbG9iYWwg +Um9vdCBHQyBDQTAeFw0xNzA1MDkwOTQ4MzRaFw00MjA1MDkwOTU4MzNaMG0xCzAJ +BgNVBAYTAkNIMRAwDgYDVQQKEwdXSVNlS2V5MSIwIAYDVQQLExlPSVNURSBGb3Vu +ZGF0aW9uIEVuZG9yc2VkMSgwJgYDVQQDEx9PSVNURSBXSVNlS2V5IEdsb2JhbCBS +b290IEdDIENBMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAETOlQwMYPchi82PG6s4ni +eUqjFqdrVCTbUf/q9Akkwwsin8tqJ4KBDdLArzHkdIJuyiXZjHWd8dvQmqJLIX4W +p2OQ0jnUsYd4XxiWD1AbNTcPasbc2RNNpI6QN+a9WzGRo1QwUjAOBgNVHQ8BAf8E +BAMCAQYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUSIcUrOPDnpBgOtfKie7T +rYy0UGYwEAYJKwYBBAGCNxUBBAMCAQAwCgYIKoZIzj0EAwMDaAAwZQIwJsdpW9zV +57LnyAyMjMPdeYwbY9XJUpROTYJKcx6ygISpJcBMWm1JKWB4E+J+SOtkAjEA2zQg +Mgj/mkkCtojeFK9dbJlxjRo/i9fgojaGHAeCOnZT/cKi7e97sIBPWA9LUzm9 +-----END CERTIFICATE----- diff --git a/src/java.base/share/legal/cldr.md b/src/java.base/share/legal/cldr.md index 6e609f353023..7423d3492f5f 100644 --- a/src/java.base/share/legal/cldr.md +++ b/src/java.base/share/legal/cldr.md @@ -1,4 +1,4 @@ -## Unicode Common Local Data Repository (CLDR) v48 +## Unicode Common Local Data Repository (CLDR) v48.2 ### CLDR License diff --git a/src/java.base/share/man/java.md b/src/java.base/share/man/java.md index 6c079c268b16..54f03218c3b6 100644 --- a/src/java.base/share/man/java.md +++ b/src/java.base/share/man/java.md @@ -1800,6 +1800,35 @@ performed by the Java HotSpot VM. You can suppress this by specifying the `-XX:CompileCommand=quiet` option before other `-XX:CompileCommand` options. + Compilation levels can be specified in the `compileonly`, `exclude`, `print`, + and `break` commands using a bitmask as an optional value: + + ``` + -XX:CompileCommand=exclude,java/lang/String.indexOf,1011 + -XX:CompileCommand=compileonly,java/lang/String.indexOf,100 + -XX:CompileCommand=print,java/lang/String.indexOf,100 + -XX:CompileCommand=break,java/lang/StringBuffer.append,1000 + ``` + + The bitmask is calculated by summing the desired compilation level values: + + `1` + : C1 JIT compiler without profiling. + + `10` + : C1 JIT compiler with limited profiling. + + `100` + : C1 JIT compiler with full profiling. + + `1000` + : C2 JIT compiler: no profiling, full optimization. + + If the bitmask is not specified, all levels are assumed. + + Note: Excluding specific compilation levels may disrupt normal state transitions + between the levels, as the VM will not automatically work around the excluded ones. + [`-XX:CompileCommandFile=`]{#-XX_CompileCommandFile}*filename* : Sets the file from which JIT compiler commands are read. By default, the `.hotspot_compiler` file is used to store commands performed by the JIT @@ -2350,11 +2379,11 @@ Java HotSpot VM. option is disabled by default and can be enabled only with the `-XX:+UseG1GC` option. [`-XX:G1AdaptiveIHOPNumInitialSamples=`]{#-XX_G1AdaptiveIHOPNumInitialSamples}*number* -: When `-XX:UseAdaptiveIHOP` is enabled, this option sets the number of - completed marking cycles used to gather samples until G1 adaptively - determines the optimum value of `-XX:InitiatingHeapOccupancyPercent`. Before, - G1 uses the value of `-XX:InitiatingHeapOccupancyPercent` directly for - this purpose. The default value is 3. +: When `-XX:UseAdaptiveIHOP` is enabled, this option sets the number + of completed concurrent cycles used to gather samples until G1 + adaptively determines the optimum value of `-XX:G1IHOP`. Before, + G1 uses the value of `-XX:G1IHOP` directly for this purpose. The + default value is 3. [`-XX:G1HeapRegionSize=`]{#-XX_G1HeapRegionSize}*size* : Sets the size of the regions into which the Java heap is subdivided when @@ -2416,15 +2445,14 @@ Java HotSpot VM. > `-XX:G1ReservePercent=20` [`-XX:+G1UseAdaptiveIHOP`]{#-XX__G1UseAdaptiveIHOP} -: Controls adaptive calculation of the old generation occupancy to start - background work preparing for an old generation collection. If enabled, - G1 uses `-XX:InitiatingHeapOccupancyPercent` for the first few times as - specified by the value of `-XX:G1AdaptiveIHOPNumInitialSamples`, and after - that adaptively calculates a new optimum value for the initiating - occupancy automatically. - Otherwise, the old generation collection process always starts at the - old generation occupancy determined by - `-XX:InitiatingHeapOccupancyPercent`. +: Controls adaptive calculation of the old generation occupancy to + start background work preparing for an old generation collection. + If enabled, G1 uses `-XX:G1IHOP` for the first few times as + specified by the value of `-XX:G1AdaptiveIHOPNumInitialSamples`, + and after that adaptively calculates a new optimum value for the + initiating occupancy automatically. Otherwise, the old generation + collection process always starts at the old generation occupancy + determined by `-XX:G1IHOP`. The default is enabled. @@ -2491,7 +2519,7 @@ Java HotSpot VM. > `-XX:InitialSurvivorRatio=4` -[`-XX:InitiatingHeapOccupancyPercent=`]{#-XX_InitiatingHeapOccupancyPercent}*percent* +[`-XX:G1IHOP=`]{#-XX_G1IHOP}*percent* : Sets the percentage of the old generation occupancy (0 to 100) at which to start the first few concurrent marking cycles for the G1 garbage collector. @@ -2504,7 +2532,7 @@ Java HotSpot VM. The following example shows how to set the initiating heap occupancy to 75%: - > `-XX:InitiatingHeapOccupancyPercent=75` + > `-XX:G1IHOP=75` [`-XX:MaxGCPauseMillis=`]{#-XX_MaxGCPauseMillis}*time* : Sets a target for the maximum GC pause time (in milliseconds). This is a diff --git a/src/java.base/share/native/libjimage/imageFile.cpp b/src/java.base/share/native/libjimage/imageFile.cpp index e2479ba2c9e1..b5e65da59bcf 100644 --- a/src/java.base/share/native/libjimage/imageFile.cpp +++ b/src/java.base/share/native/libjimage/imageFile.cpp @@ -357,8 +357,8 @@ u4 ImageFileReader::find_location_index(const char* path, u8 *size) const { ImageLocation location(data); // Make sure result is not a false positive. if (verify_location(location, path)) { - *size = (jlong)location.get_attribute(ImageLocation::ATTRIBUTE_UNCOMPRESSED); - return offset; + *size = (jlong)location.get_attribute(ImageLocation::ATTRIBUTE_UNCOMPRESSED); + return offset; } } return 0; // not found @@ -389,7 +389,7 @@ bool ImageFileReader::verify_location(ImageLocation& location, const char* path) } // Get base name string. const char* base = location.get_attribute(ImageLocation::ATTRIBUTE_BASE, strings); - // Compare with basne name. + // Compare with base name. if (!(next = ImageStrings::starts_with(next, base))) return false; // Get extension string. const char* extension = location.get_attribute(ImageLocation::ATTRIBUTE_EXTENSION, strings); diff --git a/src/java.base/share/native/libjimage/imageFile.hpp b/src/java.base/share/native/libjimage/imageFile.hpp index a4c8d159efa4..ccc0e0b1ad11 100644 --- a/src/java.base/share/native/libjimage/imageFile.hpp +++ b/src/java.base/share/native/libjimage/imageFile.hpp @@ -232,18 +232,34 @@ class ImageStrings { // class ImageLocation { public: + // See also src/java.base/share/classes/jdk/internal/jimage/ImageLocation.java enum { ATTRIBUTE_END, // End of attribute stream marker ATTRIBUTE_MODULE, // String table offset of module name ATTRIBUTE_PARENT, // String table offset of resource path parent ATTRIBUTE_BASE, // String table offset of resource path base - ATTRIBUTE_EXTENSION, // String table offset of resource path extension + ATTRIBUTE_EXTENSION, // String table offset of resource path extension ATTRIBUTE_OFFSET, // Container byte offset of resource - ATTRIBUTE_COMPRESSED, // In image byte size of the compressed resource - ATTRIBUTE_UNCOMPRESSED, // In memory byte size of the uncompressed resource + ATTRIBUTE_COMPRESSED, // In-image byte size of the compressed resource + ATTRIBUTE_UNCOMPRESSED, // In-memory byte size of the uncompressed resource + ATTRIBUTE_PREVIEW_FLAGS, // Flags relating to preview mode resources. ATTRIBUTE_COUNT // Number of attribute kinds }; + // Flag masks for the ATTRIBUTE_PREVIEW_FLAGS attribute. Defined so + // that zero is the overwhelmingly common case for normal resources. + // See also src/java.base/share/classes/jdk/internal/jimage/ImageLocation.java + enum { + // Set on a "normal" (non-preview) location if a preview version of + // it exists in the same module. + FLAGS_HAS_PREVIEW_VERSION = 0x1, + // Set on all preview locations in "/modules/xxx/META-INF/preview/..." + FLAGS_IS_PREVIEW_VERSION = 0x2, + // Set on a preview location if no normal (non-preview) version of + // it exists in the same module. + FLAGS_IS_PREVIEW_ONLY = 0x4 + }; + private: // Values of inflated attributes. u8 _attributes[ATTRIBUTE_COUNT]; @@ -300,6 +316,11 @@ class ImageLocation { inline const char* get_attribute(u4 kind, const ImageStrings& strings) const { return strings.get((u4)get_attribute(kind)); } + + // Retrieve flags from the ATTRIBUTE_PREVIEW_FLAGS attribute. + inline u4 get_preview_flags() const { + return (u4) get_attribute(ATTRIBUTE_PREVIEW_FLAGS); + } }; // Image file header, starting at offset 0. @@ -391,6 +412,7 @@ class ImageFileReaderTable { // leads the ImageFileReader to be actually closed and discarded. class ImageFileReader { friend class ImageFileReaderTable; +friend class PackageFlags; private: // Manage a number of image files such that an image can be shared across // multiple uses (ex. loader.) @@ -430,7 +452,7 @@ friend class ImageFileReaderTable; // Image file major version number. MAJOR_VERSION = 1, // Image file minor version number. - MINOR_VERSION = 0 + MINOR_VERSION = 1 }; // Locate an image if file already open. diff --git a/src/java.base/share/native/libjimage/jimage.cpp b/src/java.base/share/native/libjimage/jimage.cpp index 91a86f992e6b..d2830bd3f5c4 100644 --- a/src/java.base/share/native/libjimage/jimage.cpp +++ b/src/java.base/share/native/libjimage/jimage.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2015, 2026, Oracle and/or its affiliates. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -91,45 +91,106 @@ JIMAGE_Close(JImageFile* image) { * name, a version string and the name of a class/resource, return location * information describing the resource and its size. If no resource is found, the * function returns JIMAGE_NOT_FOUND and the value of size is undefined. - * The version number should be "9.0" and is not used in locating the resource. * The resulting location does/should not have to be released. * All strings are utf-8, zero byte terminated. * * Ex. * jlong size; * JImageLocationRef location = (*JImageFindResource)(image, - * "java.base", "9.0", "java/lang/String.class", &size); + * "java.base", "java/lang/String.class", is_preview_mode, &size); */ extern "C" JNIEXPORT JImageLocationRef JIMAGE_FindResource(JImageFile* image, - const char* module_name, const char* version, const char* name, + const char* module_name, const char* name, bool is_preview_mode, jlong* size) { + static const char str_modules[] = "modules"; + static const char str_packages[] = "packages"; + static const char preview_infix[] = "/META-INF/preview"; + + size_t module_name_len = strlen(module_name); + size_t name_len = strlen(name); + size_t preview_infix_len = strlen(preview_infix); + assert(module_name_len > 0 && "module name must be non-empty"); + assert(name_len > 0 && "name must non-empty"); + + // Do not attempt to lookup anything of the form /modules/... or /packages/... + if (strncmp(module_name, str_modules, sizeof(str_modules)) == 0 + || strncmp(module_name, str_packages, sizeof(str_packages)) == 0) { + return 0L; + } + // If the preview mode version of the path string is too long for the buffer, + // return not found (even when not in preview mode). + if (1 + module_name_len + preview_infix_len + 1 + name_len + 1 > IMAGE_MAX_PATH) { + return 0L; + } + // Concatenate to get full path - char fullpath[IMAGE_MAX_PATH]; - size_t moduleNameLen = strlen(module_name); - size_t nameLen = strlen(name); - size_t index; + char name_buffer[IMAGE_MAX_PATH]; + char* path; + { // Write the buffer with room to prepend the preview mode infix + // at the start (saves copying the trailing name part twice). + size_t index = preview_infix_len; + name_buffer[index++] = '/'; + memcpy(&name_buffer[index], module_name, module_name_len); + index += module_name_len; + name_buffer[index++] = '/'; + memcpy(&name_buffer[index], name, name_len); + index += name_len; + name_buffer[index++] = '\0'; + // Path begins at the leading '/' (not the start of the buffer). + path = &name_buffer[preview_infix_len]; + } - assert(moduleNameLen > 0 && "module name must be non-empty"); - assert(nameLen > 0 && "name must non-empty"); + // find_location_index() returns the data "offset", not an index. + const ImageFileReader* image_file = (ImageFileReader*) image; + u4 locOffset = image_file->find_location_index(path, (u8*) size); + if (locOffset != 0) { + ImageLocation loc; + loc.set_data(image_file->get_location_offset_data(locOffset)); - // If the concatenated string is too long for the buffer, return not found - if (1 + moduleNameLen + 1 + nameLen + 1 > IMAGE_MAX_PATH) { + u4 flags = loc.get_preview_flags(); + // No preview flags means "a normal resource, without a preview version". + // This is the overwhelmingly common case, with or without preview mode. + if (flags == 0) { + return locOffset; + } + // Regardless of preview mode, don't return resources requested directly + // via their preview path. + if ((flags & ImageLocation::FLAGS_IS_PREVIEW_VERSION) != 0) { + return 0L; + } + // Even if there is a preview version, we might not want to return it. + if (!is_preview_mode || (flags & ImageLocation::FLAGS_HAS_PREVIEW_VERSION) == 0) { + return locOffset; + } + } else if (!is_preview_mode) { + // No normal resource found, and not in preview mode. return 0L; } - index = 0; - fullpath[index++] = '/'; - memcpy(&fullpath[index], module_name, moduleNameLen); - index += moduleNameLen; - fullpath[index++] = '/'; - memcpy(&fullpath[index], name, nameLen); - index += nameLen; - fullpath[index++] = '\0'; - - JImageLocationRef loc = - (JImageLocationRef) ((ImageFileReader*) image)->find_location_index(fullpath, (u8*) size); - return loc; + // We are in preview mode, and the preview version of the resource is needed. + // This is either because: + // 1. The normal resource was flagged as having a preview version (rare) + // 2. This is a preview-only resource (there was no normal resource, very rare) + // 3. The requested resource doesn't exist (this should typically not happen) + // + // Since we only expect requests for resources which exist in jimage files, we + // expect this 2nd lookup to succeed (this is contrary to the expectations for + // the JRT file system, where non-existent resource lookups are common). + + { // Rewrite the front of the name buffer to make it a preview path. + size_t index = 0; + name_buffer[index++] = '/'; + memcpy(&name_buffer[index], module_name, module_name_len); + index += module_name_len; + memcpy(&name_buffer[index], preview_infix, preview_infix_len); + index += preview_infix_len; + // Check we copied up to the expected '/' separator. + assert(name_buffer[index] == '/' && "bad string concatenation"); + // The preview path now begins at the start of the buffer. + path = &name_buffer[0]; + } + return image_file->find_location_index(path, (u8*) size); } /* diff --git a/src/java.base/share/native/libjimage/jimage.hpp b/src/java.base/share/native/libjimage/jimage.hpp index a514e737b493..c340b8b7c182 100644 --- a/src/java.base/share/native/libjimage/jimage.hpp +++ b/src/java.base/share/native/libjimage/jimage.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. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -98,21 +98,20 @@ typedef void (*JImageClose_t)(JImageFile* jimage); * name, a version string and the name of a class/resource, return location * information describing the resource and its size. If no resource is found, the * function returns JIMAGE_NOT_FOUND and the value of size is undefined. - * The version number should be "9.0" and is not used in locating the resource. * The resulting location does/should not have to be released. * All strings are utf-8, zero byte terminated. * * Ex. * jlong size; * JImageLocationRef location = (*JImageFindResource)(image, - * "java.base", "9.0", "java/lang/String.class", &size); + * "java.base", "java/lang/String.class", is_preview_mode, &size); */ extern "C" JNIEXPORT JImageLocationRef JIMAGE_FindResource(JImageFile* jimage, - const char* module_name, const char* version, const char* name, + const char* module_name, const char* name, bool is_preview_mode, jlong* size); typedef JImageLocationRef(*JImageFindResource_t)(JImageFile* jimage, - const char* module_name, const char* version, const char* name, + const char* module_name, const char* name, bool is_preview_mode, jlong* size); diff --git a/src/java.base/unix/native/libnio/ch/Net.c b/src/java.base/unix/native/libnio/ch/Net.c index 28c1814f4228..2b281f9a2048 100644 --- a/src/java.base/unix/native/libnio/ch/Net.c +++ b/src/java.base/unix/native/libnio/ch/Net.c @@ -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 @@ -929,13 +929,13 @@ Java_sun_nio_ch_Net_pollConnect(JNIEnv *env, jobject this, jobject fdo, jlong ti errno = 0; result = getsockopt(fd, SOL_SOCKET, SO_ERROR, &error, &n); if (result < 0) { - handleSocketError(env, errno); + handleSocketErrorWithMessage(env, errno, "getsockopt failed"); return JNI_FALSE; } else if (error) { - handleSocketError(env, error); + handleSocketErrorWithMessage(env, error, "connect failed"); return JNI_FALSE; } else if ((poller.revents & POLLHUP) != 0) { - handleSocketError(env, ENOTCONN); + handleSocketErrorWithMessage(env, ENOTCONN, "peer closed connection after accepting"); return JNI_FALSE; } // connected diff --git a/src/java.desktop/share/classes/java/awt/Container.java b/src/java.desktop/share/classes/java/awt/Container.java index 683c24f9d138..0ba87eac12e9 100644 --- a/src/java.desktop/share/classes/java/awt/Container.java +++ b/src/java.desktop/share/classes/java/awt/Container.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1995, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1995, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -61,7 +61,6 @@ import sun.awt.AWTAccessor; import sun.awt.AWTAccessor.MouseEventAccessor; -import sun.awt.AppContext; import sun.awt.PeerEvent; import sun.awt.SunToolkit; import sun.awt.dnd.SunDropTargetEvent; @@ -2875,13 +2874,8 @@ public boolean isAncestorOf(Component c) { */ transient Component modalComp; - transient AppContext modalAppContext; private void startLWModal() { - // Store the app context on which this component is being shown. - // Event dispatch thread of this app context will be sleeping until - // we wake it by any event from hideAndDisposeHandler(). - modalAppContext = AppContext.getAppContext(); // keep the KeyEvents from being dispatched // until the focus has been transferred @@ -2946,25 +2940,22 @@ private void startLWModal() { private void stopLWModal() { synchronized (getTreeLock()) { - if (modalAppContext != null) { - Container nativeContainer = getHeavyweightContainer(); - if(nativeContainer != null) { - if (this.modalComp != null) { - nativeContainer.modalComp = this.modalComp; - this.modalComp = null; - return; - } - else { - nativeContainer.modalComp = null; - } + Container nativeContainer = getHeavyweightContainer(); + if (nativeContainer != null) { + if (this.modalComp != null) { + nativeContainer.modalComp = this.modalComp; + this.modalComp = null; + return; + } + else { + nativeContainer.modalComp = null; } - // Wake up event dispatch thread on which the dialog was - // initially shown - SunToolkit.postEvent(modalAppContext, - new PeerEvent(this, - new WakingRunnable(), - PeerEvent.PRIORITY_EVENT)); } + // Wake up event dispatch thread on which the dialog was + // initially shown + SunToolkit.postEvent(new PeerEvent(this, + new WakingRunnable(), + PeerEvent.PRIORITY_EVENT)); EventQueue.invokeLater(new WakingRunnable()); getTreeLock().notifyAll(); } @@ -4797,34 +4788,12 @@ public void eventDispatched(AWTEvent e) { // translate coordinates to this native container final Point ptSrcOrigin = srcComponent.getLocationOnScreen(); - if (AppContext.getAppContext() != nativeContainer.appContext) { - final MouseEvent mouseEvent = me; - Runnable r = new Runnable() { - public void run() { - if (!nativeContainer.isShowing() ) { - return; - } - - Point ptDstOrigin = nativeContainer.getLocationOnScreen(); - mouseEvent.translatePoint(ptSrcOrigin.x - ptDstOrigin.x, - ptSrcOrigin.y - ptDstOrigin.y ); - Component targetOver = - nativeContainer.getMouseEventTarget(mouseEvent.getX(), - mouseEvent.getY(), - Container.INCLUDE_SELF); - trackMouseEnterExit(targetOver, mouseEvent); - } - }; - SunToolkit.executeOnEventHandlerThread(nativeContainer, r); + if (!nativeContainer.isShowing()) { return; - } else { - if (!nativeContainer.isShowing() ) { - return; - } - - Point ptDstOrigin = nativeContainer.getLocationOnScreen(); - me.translatePoint( ptSrcOrigin.x - ptDstOrigin.x, ptSrcOrigin.y - ptDstOrigin.y ); } + + Point ptDstOrigin = nativeContainer.getLocationOnScreen(); + me.translatePoint( ptSrcOrigin.x - ptDstOrigin.x, ptSrcOrigin.y - ptDstOrigin.y ); } //System.out.println("Track event: " + me); // feed the 'dragged-over' event directly to the enter/exit @@ -4905,8 +4874,6 @@ void retargetMouseEvent(Component target, int id, MouseEvent e) { // avoid recursively calling LightweightDispatcher... ((Container)target).dispatchEventToSelf(retargeted); } else { - assert AppContext.getAppContext() == target.appContext; - if (nativeContainer.modalComp != null) { if (((Container)nativeContainer.modalComp).isAncestorOf(target)) { target.dispatchEvent(retargeted); diff --git a/src/java.desktop/share/classes/java/awt/EventQueue.java b/src/java.desktop/share/classes/java/awt/EventQueue.java index 571ec9f7e883..5a933a6cdf44 100644 --- a/src/java.desktop/share/classes/java/awt/EventQueue.java +++ b/src/java.desktop/share/classes/java/awt/EventQueue.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -40,6 +40,7 @@ import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.atomic.AtomicInteger; /** @@ -112,11 +113,11 @@ public class EventQueue { /* * A single lock to synchronize the push()/pop() and related operations with - * all the EventQueues from the AppContext. Synchronization on any particular + * all the EventQueues. Synchronization on any particular * event queue(s) is not enough: we should lock the whole stack. */ - private final Lock pushPopLock; - private final Condition pushPopCond; + private static final Lock pushPopLock = new ReentrantLock(); + private static final Condition pushPopCond = pushPopLock.newCondition(); /* * Dummy runnable to wake up EDT from getNextEvent() after @@ -156,11 +157,6 @@ public void run() { */ private volatile int waitForID; - /* - * AppContext corresponding to the queue. - */ - private final AppContext appContext; - private final String name = "AWT-EventQueue-" + threadInitNumber.getAndIncrement(); private FwDispatcher fwDispatcher; @@ -222,18 +218,6 @@ public EventQueue() { for (int i = 0; i < NUM_PRIORITIES; i++) { queues[i] = new Queue(); } - /* - * NOTE: if you ever have to start the associated event dispatch - * thread at this point, be aware of the following problem: - * If this EventQueue instance is created in - * SunToolkit.createNewAppContext() the started dispatch thread - * may call AppContext.getAppContext() before createNewAppContext() - * completes thus causing mess in thread group to appcontext mapping. - */ - - appContext = AppContext.getAppContext(); - pushPopLock = (Lock)appContext.get(AppContext.EVENT_QUEUE_LOCK_KEY); - pushPopCond = (Condition)appContext.get(AppContext.EVENT_QUEUE_COND_KEY); } /** @@ -247,7 +231,7 @@ public EventQueue() { * @throws NullPointerException if {@code theEvent} is {@code null} */ public void postEvent(AWTEvent theEvent) { - SunToolkit.flushPendingEvents(appContext); + SunToolkit.flushPendingEvents(); postEventPrivate(theEvent); } @@ -532,7 +516,7 @@ public AWTEvent getNextEvent() throws InterruptedException { * of the synchronized block to avoid deadlock when * event queues are nested with push()/pop(). */ - SunToolkit.flushPendingEvents(appContext); + SunToolkit.flushPendingEvents(); pushPopLock.lock(); try { AWTEvent event = getNextEventPrivate(); @@ -572,7 +556,7 @@ AWTEvent getNextEvent(int id) throws InterruptedException { * of the synchronized block to avoid deadlock when * event queues are nested with push()/pop(). */ - SunToolkit.flushPendingEvents(appContext); + SunToolkit.flushPendingEvents(); pushPopLock.lock(); try { for (int i = 0; i < NUM_PRIORITIES; i++) { @@ -869,8 +853,8 @@ public void push(EventQueue newEventQueue) { newEventQueue.previousQueue = topQueue; topQueue.nextQueue = newEventQueue; - if (appContext.get(AppContext.EVENT_QUEUE_KEY) == topQueue) { - appContext.put(AppContext.EVENT_QUEUE_KEY, newEventQueue); + if (SunToolkit.currentEventQueue == topQueue) { + SunToolkit.currentEventQueue = newEventQueue; } pushPopCond.signalAll(); @@ -929,8 +913,8 @@ protected void pop() throws EmptyStackException { topQueue.dispatchThread.setEventQueue(prevQueue); } - if (appContext.get(AppContext.EVENT_QUEUE_KEY) == this) { - appContext.put(AppContext.EVENT_QUEUE_KEY, prevQueue); + if (SunToolkit.currentEventQueue == this) { + SunToolkit.currentEventQueue = prevQueue; } // Wake up EDT waiting in getNextEvent(), so it can @@ -1053,7 +1037,7 @@ final boolean isDispatchThreadImpl() { final void initDispatchThread() { pushPopLock.lock(); try { - if (dispatchThread == null && !threadGroup.isDestroyed() && !appContext.isDisposed()) { + if (dispatchThread == null && !threadGroup.isDestroyed()) { EventDispatchThread t = new EventDispatchThread(threadGroup, name, EventQueue.this); t.setContextClassLoader(classLoader); t.setPriority(Thread.NORM_PRIORITY + 1); @@ -1071,7 +1055,7 @@ final void detachDispatchThread(EventDispatchThread edt) { /* * Minimize discard possibility for non-posted events */ - SunToolkit.flushPendingEvents(appContext); + SunToolkit.flushPendingEvents(); /* * This synchronized block is to secure that the event dispatch * thread won't die in the middle of posting a new event to the @@ -1129,7 +1113,7 @@ final EventDispatchThread getDispatchThread() { * {@code removeNotify} method. */ final void removeSourceEvents(Object source, boolean removeAllEvents) { - SunToolkit.flushPendingEvents(appContext); + SunToolkit.flushPendingEvents(); pushPopLock.lock(); try { for (int i = 0; i < NUM_PRIORITIES; i++) { diff --git a/src/java.desktop/share/classes/java/awt/FileDialog.java b/src/java.desktop/share/classes/java/awt/FileDialog.java index fa695730fb6d..55778e5954ab 100644 --- a/src/java.desktop/share/classes/java/awt/FileDialog.java +++ b/src/java.desktop/share/classes/java/awt/FileDialog.java @@ -43,6 +43,7 @@ * its {@code show} method to display the dialog, * it blocks the rest of the application until the user has * chosen a file. + * This means that {@link Dialog#setModalityType(ModalityType)} may be ignored. * * @see Window#show * @@ -450,6 +451,14 @@ public void setDirectory(String dir) { /** * Gets the selected file of this file dialog. If the user * selected {@code CANCEL}, the returned file is {@code null}. + *

    + * It is platform-specific as to what happens for {@code LOAD} mode of a + * non-existent file. For example, it may be possible for the user to + * "accept" the result of a {@code setFile} value or to edit in the name + * of some other file to load/open even if it is not present. + * Applications should therefore verify the file represented by the + * returned {@code String} exists. + * The value is usually the basename, not a full path name. * * @return the currently selected file of this file dialog window, * or {@code null} if none is selected @@ -504,13 +513,26 @@ private void setFiles(File[] files) { * specified file. This file becomes the default file if it is set * before the file dialog window is first shown. *

    - * When the dialog is shown, the specified file is selected. The kind of - * selection depends on the file existence, the dialog type, and the native - * platform. E.g., the file could be highlighted in the file list, or a + * When the dialog is shown, the specified file may be pre-selected. + * The visible manifestation of this selection, if any, depends on factors + * such as the file existence, the dialog mode, and the native platform. + * For example, the file could be highlighted in the file list, or a * file name editbox could be populated with the file name. *

    * This method accepts either a full file path, or a file name with an * extension if used together with the {@code setDirectory} method. + * It is platform-specific how a full file path interacts with {@code setDirectory}. + * It may be that the directory is always used instead of the file path, or + * the file path overrides the directory, or even that the full file path + * is interpreted as a base file name. Therefore, it is strongly recommended to + * use {@code setDirectory} to set the folder and {@code setFile} to + * set a base file name. + *

    + * Appearance and behaviours may also differ between {@code LOAD} and {@code SAVE} modes. + *

    + * It is also platform-specific as to what happens for {@code LOAD} mode of a non-existent file. + * For example, it may be possible for the user to "accept" the {@code setFile} value or + * to edit in the name of some other file to load/open even if it is not present. *

    * Specifying "" as the file is exactly equivalent to specifying * {@code null} as the file. @@ -529,6 +551,9 @@ public void setFile(String file) { /** * Enables or disables multiple file selection for the file dialog. + *

    + * Multiple mode may be ignored in some cases, for example, commonly + * {@code SAVE} mode dialogs do not support it. * * @param enable if {@code true}, multiple file selection is enabled; * {@code false} - disabled. diff --git a/src/java.desktop/share/classes/java/awt/SentEvent.java b/src/java.desktop/share/classes/java/awt/SentEvent.java index eb85fa1453dc..419f137ba2b7 100644 --- a/src/java.desktop/share/classes/java/awt/SentEvent.java +++ b/src/java.desktop/share/classes/java/awt/SentEvent.java @@ -30,8 +30,7 @@ import sun.awt.SunToolkit; /** - * A wrapping tag for a nested AWTEvent which indicates that the event was - * sent from another AppContext. The destination AppContext should handle the + * A wrapping tag for a nested AWTEvent. The EventQueue should handle the * event even if it is currently blocked waiting for a SequencedEvent or * another SentEvent to be handled. * diff --git a/src/java.desktop/share/classes/java/awt/Taskbar.java b/src/java.desktop/share/classes/java/awt/Taskbar.java index 2a46aca71165..3c81adf5a847 100644 --- a/src/java.desktop/share/classes/java/awt/Taskbar.java +++ b/src/java.desktop/share/classes/java/awt/Taskbar.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016, 2024, 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 @@ -184,6 +184,8 @@ private Taskbar() { } } + private static volatile Taskbar taskbar; + /** * Returns the {@code Taskbar} instance of the current * taskbar context. On some platforms the Taskbar API may not be @@ -205,12 +207,8 @@ public static synchronized Taskbar getTaskbar(){ "supported on the current platform"); } - sun.awt.AppContext context = sun.awt.AppContext.getAppContext(); - Taskbar taskbar = (Taskbar)context.get(Taskbar.class); - if (taskbar == null) { taskbar = new Taskbar(); - context.put(Taskbar.class, taskbar); } return taskbar; diff --git a/src/java.desktop/share/classes/java/beans/ThreadGroupContext.java b/src/java.desktop/share/classes/java/beans/ThreadGroupContext.java index dc42799c9f73..fa7118bbc097 100644 --- a/src/java.desktop/share/classes/java/beans/ThreadGroupContext.java +++ b/src/java.desktop/share/classes/java/beans/ThreadGroupContext.java @@ -35,7 +35,6 @@ /** * The {@code ThreadGroupContext} is an application-dependent * context referenced by the specific {@link ThreadGroup}. - * This is a replacement for the {@link sun.awt.AppContext}. * * @author Sergey Malenkov */ diff --git a/src/java.desktop/share/classes/sun/awt/AWTAutoShutdown.java b/src/java.desktop/share/classes/sun/awt/AWTAutoShutdown.java index d60211ae318a..d3f7b32ae8ce 100644 --- a/src/java.desktop/share/classes/sun/awt/AWTAutoShutdown.java +++ b/src/java.desktop/share/classes/sun/awt/AWTAutoShutdown.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2000, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,6 +26,7 @@ package sun.awt; import java.awt.AWTEvent; +import java.awt.Toolkit; import java.util.HashSet; import java.util.IdentityHashMap; import java.util.Map; @@ -317,7 +318,7 @@ public void run() { } } if (!interrupted) { - AppContext.stopEventDispatchThreads(); + Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(getShutdownEvent()); } } diff --git a/src/java.desktop/share/classes/sun/awt/AppContext.java b/src/java.desktop/share/classes/sun/awt/AppContext.java index 01b1dd098872..b81869040b4b 100644 --- a/src/java.desktop/share/classes/sun/awt/AppContext.java +++ b/src/java.desktop/share/classes/sun/awt/AppContext.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -43,9 +43,6 @@ import java.lang.ref.SoftReference; import sun.util.logging.PlatformLogger; -import java.util.concurrent.locks.Condition; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.atomic.AtomicInteger; /** @@ -121,17 +118,6 @@ public final class AppContext { /* Since the contents of an AppContext are unique to each Java * session, this class should never be serialized. */ - /* - * The key to put()/get() the Java EventQueue into/from the AppContext. - */ - public static final Object EVENT_QUEUE_KEY = new StringBuffer("EventQueue"); - - /* - * The keys to store EventQueue push/pop lock and condition. - */ - public static final Object EVENT_QUEUE_LOCK_KEY = new StringBuilder("EventQueue.Lock"); - public static final Object EVENT_QUEUE_COND_KEY = new StringBuilder("EventQueue.Condition"); - /* A map of AppContexts, referenced by ThreadGroup. */ private static final Map threadGroup2appContext = @@ -225,12 +211,6 @@ public boolean isDisposed() { threadGroup2appContext.put(threadGroup, this); this.contextClassLoader = Thread.currentThread().getContextClassLoader(); - // Initialize push/pop lock and its condition to be used by all the - // EventQueues within this AppContext - Lock eventQueuePushPopLock = new ReentrantLock(); - put(EVENT_QUEUE_LOCK_KEY, eventQueuePushPopLock); - Condition eventQueuePushPopCond = eventQueuePushPopLock.newCondition(); - put(EVENT_QUEUE_COND_KEY, eventQueuePushPopCond); } private static final ThreadLocal threadAppContext = @@ -480,44 +460,6 @@ public void run() { mostRecentKeyValue = null; } - static final class PostShutdownEventRunnable implements Runnable { - private final AppContext appContext; - - PostShutdownEventRunnable(AppContext ac) { - appContext = ac; - } - - public void run() { - final EventQueue eq = (EventQueue)appContext.get(EVENT_QUEUE_KEY); - if (eq != null) { - eq.postEvent(AWTAutoShutdown.getShutdownEvent()); - } - } - } - - static void stopEventDispatchThreads() { - for (AppContext appContext: getAppContexts()) { - if (appContext.isDisposed()) { - continue; - } - Runnable r = new PostShutdownEventRunnable(appContext); - // For security reasons EventQueue.postEvent should only be called - // on a thread that belongs to the corresponding thread group. - if (appContext != AppContext.getAppContext()) { - // Create a thread that belongs to the thread group associated - // with the AppContext and invokes EventQueue.postEvent. - Thread thread = new Thread(appContext.getThreadGroup(), - r, "AppContext Disposer", 0, false); - thread.setContextClassLoader(appContext.getContextClassLoader()); - thread.setPriority(Thread.NORM_PRIORITY + 1); - thread.setDaemon(true); - thread.start(); - } else { - r.run(); - } - } - } - private MostRecentKeyValue mostRecentKeyValue = null; private MostRecentKeyValue shadowMostRecentKeyValue = null; diff --git a/src/java.desktop/share/classes/sun/awt/SunToolkit.java b/src/java.desktop/share/classes/sun/awt/SunToolkit.java index e97b654486fd..06f3132df929 100644 --- a/src/java.desktop/share/classes/sun/awt/SunToolkit.java +++ b/src/java.desktop/share/classes/sun/awt/SunToolkit.java @@ -133,10 +133,6 @@ private static void initStatic() { */ public static final int GRAB_EVENT_MASK = 0x80000000; - /* The key to put()/get() the PostEventQueue into/from the AppContext. - */ - private static final String POST_EVENT_QUEUE_KEY = "PostEventQueue"; - /** * Number of buttons. * By default it's taken from the system. If system value does not @@ -156,20 +152,17 @@ private static void initStatic() { */ public static final int MAX_BUTTONS_SUPPORTED = 20; + public static volatile EventQueue currentEventQueue; + private static volatile PostEventQueue postEventQueue; + /** - * Creates and initializes EventQueue instance for the specified - * AppContext. - * Note that event queue must be created from createNewAppContext() - * only in order to ensure that EventQueue constructor obtains - * the correct AppContext. - * @param appContext AppContext to associate with the event queue + * Creates and initializes EventQueue instance. */ - private static void initEQ(AppContext appContext) { - EventQueue eventQueue = new EventQueue(); - appContext.put(AppContext.EVENT_QUEUE_KEY, eventQueue); - - PostEventQueue postEventQueue = new PostEventQueue(eventQueue); - appContext.put(POST_EVENT_QUEUE_KEY, postEventQueue); + private static synchronized void initEQ() { + if (currentEventQueue == null) { + currentEventQueue = new EventQueue(); + postEventQueue = new PostEventQueue(currentEventQueue); + } } public SunToolkit() { @@ -279,8 +272,7 @@ static final AppContext createNewAppContext(ThreadGroup threadGroup) { // the calls to AppContext.getAppContext() from EventQueue ctor // return correct values AppContext appContext = new AppContext(threadGroup); - initEQ(appContext); - + initEQ(); return appContext; } @@ -468,12 +460,6 @@ public static void postEvent(AppContext appContext, AWTEvent event) { // otherwise have to be modified to precisely identify // system-generated events. setSystemGenerated(event); - AppContext eventContext = targetToAppContext(event.getSource()); - if (eventContext != null && !eventContext.equals(appContext)) { - throw new RuntimeException("Event posted on wrong app context : " + event); - } - PostEventQueue postEventQueue = - (PostEventQueue)appContext.get(POST_EVENT_QUEUE_KEY); if (postEventQueue != null) { postEventQueue.postEvent(event); } @@ -498,18 +484,6 @@ public void run() { * EventQueue yet. */ public static void flushPendingEvents() { - AppContext appContext = AppContext.getAppContext(); - flushPendingEvents(appContext); - } - - /* - * Flush the PostEventQueue for the right AppContext. - * The default flushPendingEvents only flushes the thread-local context, - * which is not always correct, c.f. 3746956 - */ - public static void flushPendingEvents(AppContext appContext) { - PostEventQueue postEventQueue = - (PostEventQueue)appContext.get(POST_EVENT_QUEUE_KEY); if (postEventQueue != null) { postEventQueue.flush(); } @@ -600,20 +574,6 @@ class AWTInvocationLock {} } } - /* - * Returns true if the calling thread is the event dispatch thread - * contained within AppContext which associated with the given target. - * Use this call to ensure that a given task is being executed - * (or not being) on the event dispatch thread for the given target. - */ - public static boolean isDispatchThreadForAppContext(Object target) { - AppContext appContext = targetToAppContext(target); - EventQueue eq = (EventQueue)appContext.get(AppContext.EVENT_QUEUE_KEY); - - AWTAccessor.EventQueueAccessor accessor = AWTAccessor.getEventQueueAccessor(); - return accessor.isDispatchThreadImpl(eq); - } - @Override public Dimension getScreenSize() { return GraphicsEnvironment.getLocalGraphicsEnvironment() @@ -1035,13 +995,8 @@ protected EventQueue getSystemEventQueueImpl() { } public static EventQueue getSystemEventQueueImplPP() { - return getSystemEventQueueImplPP(AppContext.getAppContext()); - } - - public static EventQueue getSystemEventQueueImplPP(AppContext appContext) { - EventQueue theEventQueue = - (EventQueue)appContext.get(AppContext.EVENT_QUEUE_KEY); - return theEventQueue; + initEQ(); + return currentEventQueue; } /** @@ -2021,7 +1976,7 @@ public static boolean isSystemGenerated(AWTEvent e) { /* - * PostEventQueue is a Thread that runs in the same AppContext as the + * PostEventQueue is a Thread tied to the * Java EventQueue. It is a queue of AWTEvents to be posted to the * Java EventQueue. The toolkit Thread (AWT-Windows/AWT-Motif) posts * events to this queue, which then calls EventQueue.postEvent(). diff --git a/src/java.desktop/share/native/common/awt/medialib/mlib_ImageCreate.c b/src/java.desktop/share/native/common/awt/medialib/mlib_ImageCreate.c index 6bb9b36e494b..830e86a2a516 100644 --- a/src/java.desktop/share/native/common/awt/medialib/mlib_ImageCreate.c +++ b/src/java.desktop/share/native/common/awt/medialib/mlib_ImageCreate.c @@ -116,6 +116,7 @@ * mlib_ImageSetFormat() sets new value for the image format */ +#include #include #include "mlib_image.h" #include "mlib_ImageRowTable.h" @@ -313,10 +314,12 @@ mlib_image* mlib_ImageCreate(mlib_type type, return NULL; } + data = mlib_malloc(wb * height); if (data == NULL) { return NULL; } + memset(data, 0, wb * height); image = (mlib_image *)mlib_malloc(sizeof(mlib_image)); if (image == NULL) { diff --git a/src/java.desktop/share/native/libawt/awt/medialib/awt_ImagingLib.c b/src/java.desktop/share/native/libawt/awt/medialib/awt_ImagingLib.c index ae86b309465a..bb93108f111f 100644 --- a/src/java.desktop/share/native/libawt/awt/medialib/awt_ImagingLib.c +++ b/src/java.desktop/share/native/libawt/awt/medialib/awt_ImagingLib.c @@ -1133,12 +1133,14 @@ fprintf(stderr,"Flags : %d\n",dst->flags); { unsigned char *cP = (unsigned char *)mlib_ImageGetData(dst); - memset(cP, 0, mlib_ImageGetWidth(dst)*mlib_ImageGetHeight(dst)); + if (ddata == NULL) { // zero medialib allocated memory + memset(cP, 0, mlib_ImageGetWidth(dst) * mlib_ImageGetHeight(dst) * mlib_ImageGetChannels(dst)); + } } /* Perform the transformation */ - if ((status = (*sMlibFns[MLIB_AFFINE].fptr)(dst, src, mtx, filter, - MLIB_EDGE_SRC_EXTEND) != MLIB_SUCCESS)) + status = (*sMlibFns[MLIB_AFFINE].fptr)(dst, src, mtx, filter, MLIB_EDGE_SRC_EXTEND); + if (status != MLIB_SUCCESS) { printMedialibError(status); /* REMIND: Free the regions */ diff --git a/src/java.desktop/unix/classes/sun/awt/X11/XListPeer.java b/src/java.desktop/unix/classes/sun/awt/X11/XListPeer.java index 813483272948..fbf5d27543eb 100644 --- a/src/java.desktop/unix/classes/sun/awt/X11/XListPeer.java +++ b/src/java.desktop/unix/classes/sun/awt/X11/XListPeer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2003, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -1233,10 +1233,12 @@ public void delItems(int s, int e) { */ @Override public void select(int index) { - // Programmatic select() should also set the focus index - setFocusIndex(index); - repaint(PAINT_FOCUS); - selectItem(index); + if (index >= 0 && index < items.size()) { + // Programmatic select() should also set the focus index + setFocusIndex(index); + repaint(PAINT_FOCUS); + selectItem(index); + } } /** diff --git a/src/java.desktop/unix/classes/sun/awt/X11/XToolkit.java b/src/java.desktop/unix/classes/sun/awt/X11/XToolkit.java index 1ec0039febdb..0f6aa73f204e 100644 --- a/src/java.desktop/unix/classes/sun/awt/X11/XToolkit.java +++ b/src/java.desktop/unix/classes/sun/awt/X11/XToolkit.java @@ -128,7 +128,6 @@ import javax.swing.UIDefaults; import sun.awt.AWTAccessor; -import sun.awt.AppContext; import sun.awt.DisplayChangedListener; import sun.awt.LightweightFrame; import sun.awt.SunToolkit; diff --git a/src/java.desktop/unix/classes/sun/awt/X11/XWindow.java b/src/java.desktop/unix/classes/sun/awt/X11/XWindow.java index b79f2f9033f1..a1010543572e 100644 --- a/src/java.desktop/unix/classes/sun/awt/X11/XWindow.java +++ b/src/java.desktop/unix/classes/sun/awt/X11/XWindow.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2002, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -31,6 +31,7 @@ import java.awt.Component; import java.awt.Container; import java.awt.Cursor; +import java.awt.EventQueue; import java.awt.Font; import java.awt.FontMetrics; import java.awt.Graphics; @@ -425,7 +426,7 @@ public void run() { if (focusLog.isLoggable(PlatformLogger.Level.FINER) && (e instanceof FocusEvent)) { focusLog.finer("Sending " + e); } - XToolkit.postEvent(XToolkit.targetToAppContext(e.getSource()), pe); + XToolkit.postEvent(pe); } @@ -435,11 +436,11 @@ public void run() { // NOTE: This method may be called by privileged threads. // DO NOT INVOKE CLIENT CODE ON THIS THREAD! void postEvent(AWTEvent event) { - XToolkit.postEvent(XToolkit.targetToAppContext(event.getSource()), event); + XToolkit.postEvent(event); } static void postEventStatic(AWTEvent event) { - XToolkit.postEvent(XToolkit.targetToAppContext(event.getSource()), event); + XToolkit.postEvent(event); } public void postEventToEventQueue(final AWTEvent event) { @@ -514,7 +515,7 @@ public final void repaint(int x, int y, int width, int height) { if (g != null) { try { g.setClip(x, y, width, height); - if (SunToolkit.isDispatchThreadForAppContext(getTarget())) { + if (EventQueue.isDispatchThread()) { paint(g); // The native and target will be painted in place. } else { paintPeer(g); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Lint.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Lint.java index 776fd544bfb5..e546d019f5f8 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Lint.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/Lint.java @@ -25,14 +25,14 @@ package com.sun.tools.javac.code; -import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.EnumSet; import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; import java.util.Optional; +import java.util.SequencedMap; +import java.util.SequencedSet; import java.util.Set; import java.util.stream.Stream; @@ -112,8 +112,6 @@ public static boolean isDeprecatedDeclaration(Symbol sym) { private EnumSet suppressedValues; private boolean withinDeprecated; - private static final Map map = new LinkedHashMap<>(40); - @SuppressWarnings("this-escape") protected Lint(Context context) { this.context = context; @@ -196,7 +194,7 @@ public enum LintCategory { *

    * This category is not supported by {@code @SuppressWarnings}. */ - CLASSFILE("classfile", false, false), + CLASSFILE("classfile", Property.NO_ANNOTATION_SUPPRESSION), /** * Warn about "dangling" documentation comments, @@ -213,7 +211,7 @@ public enum LintCategory { * Warn about items which are documented with an {@code @deprecated} JavaDoc * comment, but which do not have {@code @Deprecated} annotation. */ - DEP_ANN("dep-ann", true, true), + DEP_ANN("dep-ann", Property.ENABLED_BY_DEFAULT), /** * Warn about division by constant integer 0. @@ -243,7 +241,7 @@ public enum LintCategory { /** * Warn about uses of @ValueBased classes where an identity class is expected. */ - IDENTITY("identity", true, true, "synchronization"), + IDENTITY(List.of("identity", "synchronization"), Property.ENABLED_BY_DEFAULT), /** * Warn about use of incubating modules. @@ -251,7 +249,7 @@ public enum LintCategory { *

    * This category is not supported by {@code @SuppressWarnings}. */ - INCUBATING("incubating", false, true), + INCUBATING("incubating", Property.NO_ANNOTATION_SUPPRESSION, Property.ENABLED_BY_DEFAULT), /** * Warn about compiler possible lossy conversions. @@ -266,12 +264,12 @@ public enum LintCategory { /** * Warn about module system related issues. */ - MODULE("module", true, true), + MODULE("module", Property.ENABLED_BY_DEFAULT), /** * Warn about issues regarding module opens. */ - OPENS("opens", true, true), + OPENS("opens", Property.ENABLED_BY_DEFAULT), /** * Warn about issues relating to use of command line options. @@ -279,7 +277,7 @@ public enum LintCategory { *

    * This category is not supported by {@code @SuppressWarnings}. */ - OPTIONS("options", false, false), + OPTIONS("options", Property.NO_ANNOTATION_SUPPRESSION), /** * Warn when any output file is written to more than once. @@ -287,7 +285,7 @@ public enum LintCategory { *

    * This category is not supported by {@code @SuppressWarnings}. */ - OUTPUT_FILE_CLASH("output-file-clash", false, false), + OUTPUT_FILE_CLASH("output-file-clash", Property.NO_ANNOTATION_SUPPRESSION), /** * Warn about issues regarding method overloads. @@ -305,7 +303,7 @@ public enum LintCategory { *

    * This category is not supported by {@code @SuppressWarnings}. */ - PATH("path", false, false), + PATH("path", Property.NO_ANNOTATION_SUPPRESSION), /** * Warn about issues regarding annotation processing. @@ -313,7 +311,7 @@ public enum LintCategory { *

    * This category is not supported by {@code @SuppressWarnings}. */ - PROCESSING("processing", false, false), + PROCESSING("processing", Property.NO_ANNOTATION_SUPPRESSION), /** * Warn about unchecked operations on raw types. @@ -323,7 +321,7 @@ public enum LintCategory { /** * Warn about use of deprecated-for-removal items. */ - REMOVAL("removal", true, true), + REMOVAL("removal", Property.ENABLED_BY_DEFAULT), /** * Warn about use of automatic modules in the requires clauses. @@ -333,7 +331,7 @@ public enum LintCategory { /** * Warn about automatic modules in requires transitive. */ - REQUIRES_TRANSITIVE_AUTOMATIC("requires-transitive-automatic", true, true), + REQUIRES_TRANSITIVE_AUTOMATIC("requires-transitive-automatic", Property.ENABLED_BY_DEFAULT), /** * Warn about Serializable classes that do not provide a serial version ID. @@ -348,7 +346,7 @@ public enum LintCategory { /** * Warn about unnecessary uses of the strictfp modifier */ - STRICTFP("strictfp", true, true), + STRICTFP("strictfp", Property.ENABLED_BY_DEFAULT), /** * Warn about issues relating to use of text blocks @@ -378,28 +376,38 @@ public enum LintCategory { /** * Warn about use of preview features. */ - PREVIEW("preview", true, true), + PREVIEW("preview", Property.ENABLED_BY_DEFAULT), /** * Warn about use of restricted methods. */ RESTRICTED("restricted"); - LintCategory(String option) { - this(option, true, false); + enum Property { NO_ANNOTATION_SUPPRESSION, ENABLED_BY_DEFAULT } + + LintCategory(String option, Property... properties) { + this(List.of(option), properties); } - LintCategory(String option, boolean annotationSuppression, boolean enabledByDefault, String... aliases) { - this.option = option; - this.annotationSuppression = annotationSuppression; - this.enabledByDefault = enabledByDefault; - ArrayList optionList = new ArrayList<>(1 + aliases.length); - optionList.add(option); - Collections.addAll(optionList, aliases); - this.optionList = Collections.unmodifiableList(optionList); - this.optionList.forEach(ident -> map.put(ident, this)); + LintCategory(List options, Property... properties) { + this.option = options.getFirst(); + this.optionList = options; + Set propertySet = properties.length == 0 ? Set.of() : EnumSet.copyOf(Arrays.asList(properties)); + this.annotationSuppression = !propertySet.contains(Property.NO_ANNOTATION_SUPPRESSION); + this.enabledByDefault = propertySet.contains(Property.ENABLED_BY_DEFAULT); } + private static final SequencedMap lookup = createLookup(); + + private static SequencedMap createLookup() { + var map = new LinkedHashMap(); + for (var category : values()) { + category.optionList.forEach(id -> map.put(id, category)); + } + return Collections.unmodifiableSequencedMap(map); + } + + /** * Get the {@link LintCategory} having the given command line option. * @@ -407,14 +415,14 @@ public enum LintCategory { * @return corresponding {@link LintCategory}, or empty if none exists */ public static Optional get(String option) { - return Optional.ofNullable(map.get(option)); + return Optional.ofNullable(lookup.get(option)); } /** * Get all lint category option strings and aliases. */ - public static Set options() { - return Collections.unmodifiableSet(map.keySet()); + public static SequencedSet options() { + return lookup.sequencedKeySet(); } public static EnumSet newEmptySet() { diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/TypeAnnotations.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/TypeAnnotations.java index 452d15ed2190..caa081505b65 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/code/TypeAnnotations.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/code/TypeAnnotations.java @@ -705,7 +705,7 @@ public Type visitErrorType(ErrorType t, List s) { @Override public Type visitType(Type t, List s) { - return t.annotatedType(s); + return t.hasTag(TypeTag.VOID) ? t : t.annotatedType(s); } }; diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java index 7b589b9fa0b1..a38ca7c01a10 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Attr.java @@ -5273,7 +5273,7 @@ public void visitModifiers(JCModifiers tree) { public void visitAnnotatedType(JCAnnotatedType tree) { attribAnnotationTypes(tree.annotations, env); Type underlyingType = attribTree(tree.underlyingType, env, resultInfo); - if (underlyingType.getTag() == PACKAGE) { + if (underlyingType.getTag() == PACKAGE || underlyingType.getTag() == VOID) { result = tree.type = underlyingType; } else { Type annotatedType = underlyingType.preannotatedType(); diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java index 0af30be46712..84920aaf556f 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/comp/Check.java @@ -898,7 +898,7 @@ void checkVarargsMethodDecl(Env env, JCMethodDecl tree) { } else if (!hasTrustMeAnno && varargElemType != null && !types.isReifiable(varargElemType)) { - warnUnchecked(tree.params.head.pos(), LintWarnings.UncheckedVarargsNonReifiableType(varargElemType)); + warnUnchecked(tree.params.last().pos(), LintWarnings.UncheckedVarargsNonReifiableType(varargElemType)); } } //where @@ -3692,10 +3692,11 @@ void checkDeprecatedAnnotation(DiagnosticPosition pos, Symbol s) { log.warning(pos, LintWarnings.MissingDeprecatedAnnotation); } // Note: @Deprecated has no effect on local variables, parameters and package decls. - if (lint.isEnabled(LintCategory.DEPRECATION) && !s.isDeprecatableViaAnnotation()) { - if (!syms.deprecatedType.isErroneous() && s.attribute(syms.deprecatedType.tsym) != null) { - log.warning(pos, LintWarnings.DeprecatedAnnotationHasNoEffect(Kinds.kindName(s))); - } + if (lint.isEnabled(LintCategory.DEPRECATION) && !s.isDeprecatableViaAnnotation() && + (s.flags() & RECORD) == 0 && + !syms.deprecatedType.isErroneous() && + s.attribute(syms.deprecatedType.tsym) != null) { + log.warning(pos, LintWarnings.DeprecatedAnnotationHasNoEffect(Kinds.kindName(s))); } } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/Pretty.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/Pretty.java index c22dfd616379..f48bcaa8b7d1 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/Pretty.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/tree/Pretty.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1999, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1999, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -26,7 +26,7 @@ package com.sun.tools.javac.tree; import java.io.*; -import java.util.stream.Collectors; +import java.util.Optional; import com.sun.source.tree.MemberReferenceTree.ReferenceMode; import com.sun.source.tree.ModuleTree.ModuleKind; @@ -75,6 +75,10 @@ public Pretty(Writer out, boolean sourceOutput) { */ Name enclClassName; + /** The enclosing class flags. + */ + long enclClassFlags; + /** A table mapping trees to their documentation comments * (can be null) */ @@ -314,6 +318,22 @@ public void printTypeParameters(List trees) throws IOException } } + /** Print record components. + */ + public void printRecordComponents(List stats) throws IOException { + print('('); + boolean first = true; + for (List l = stats; l.nonEmpty(); l = l.tail) { + if (isRecordComponent(l.head)) { + if (!first) { + print(", "); + } + printStat(l.head); + first = false; + } + } + print(')'); + } /** Print a block. */ public void printBlock(List stats) throws IOException { @@ -347,7 +367,25 @@ public void printEnumBody(List stats) throws IOException { print(';'); println(); for (List l = stats; l.nonEmpty(); l = l.tail) { - if (!isEnumerator(l.head)) { + if (!isEnumerator(l.head) && (!sourceOutput || !isGeneratedDefaultConstructor(l.head))) { + align(); + printStat(l.head); + println(); + } + } + undent(); + align(); + print('}'); + } + + /** Print a block. + */ + public void printRecordBody(List stats) throws IOException { + print('{'); + println(); + indent(); + for (List l = stats; l.nonEmpty(); l = l.tail) { + if (!isRecordComponent(l.head) && (!sourceOutput || !isGeneratedDefaultConstructor(l.head))) { align(); printStat(l.head); println(); @@ -363,6 +401,21 @@ boolean isEnumerator(JCTree t) { return t.hasTag(VARDEF) && (((JCVariableDecl) t).mods.flags & ENUM) != 0; } + /** Is the given tree a record component? */ + boolean isRecordComponent(JCTree t) { + return t.hasTag(VARDEF) && (((JCVariableDecl) t).mods.flags & RECORD) != 0; + } + + /** Is the given tree a compact record constructor? */ + boolean isCompactRecordConstructor(JCTree t) { + return t.hasTag(METHODDEF) && (((JCMethodDecl) t).mods.flags & COMPACT_RECORD_CONSTRUCTOR) != 0; + } + + /** Is the given tree a generated default constructor? */ + boolean isGeneratedDefaultConstructor(JCTree t) { + return t.hasTag(METHODDEF) && (((JCMethodDecl) t).mods.flags & GENERATEDCONSTR) != 0; + } + /** Print unit consisting of package clause and import statements in toplevel, * followed by class definition. if class definition == null, * print all definitions in toplevel. @@ -566,6 +619,8 @@ public void visitClassDef(JCClassDecl tree) { printFlags(tree.mods.flags & ~INTERFACE); Name enclClassNamePrev = enclClassName; enclClassName = tree.name; + long enclClassFlagsPrev = enclClassFlags; + enclClassFlags = tree.mods.flags; if ((tree.mods.flags & INTERFACE) != 0) { print("interface "); print(tree.name); @@ -581,10 +636,15 @@ public void visitClassDef(JCClassDecl tree) { } else { if ((tree.mods.flags & ENUM) != 0) print("enum "); + else if ((tree.mods.flags & RECORD) != 0) + print("record "); else print("class "); print(tree.name); printTypeParameters(tree.typarams); + if ((tree.mods.flags & RECORD) != 0) { + printRecordComponents(tree.defs); + } if (tree.extending != null) { print(" extends "); printExpr(tree.extending); @@ -601,10 +661,13 @@ public void visitClassDef(JCClassDecl tree) { print(' '); if ((tree.mods.flags & ENUM) != 0) { printEnumBody(tree.defs); + } else if ((tree.mods.flags & RECORD) != 0) { + printRecordBody(tree.defs); } else { printBlock(tree.defs); } enclClassName = enclClassNamePrev; + enclClassFlags = enclClassFlagsPrev; } catch (IOException e) { throw new UncheckedIOException(e); } @@ -627,26 +690,41 @@ public void visitMethodDef(JCMethodDecl tree) { print(' '); print(tree.name); } - print('('); - if (tree.recvparam!=null) { - printExpr(tree.recvparam); - if (tree.params.size() > 0) { - print(", "); + if (!isCompactRecordConstructor(tree)) { + print('('); + if (tree.recvparam!=null) { + printExpr(tree.recvparam); + if (tree.params.size() > 0) { + print(", "); + } + } + printExprs(tree.params); + print(')'); + if (tree.thrown.nonEmpty()) { + print(" throws "); + printExprs(tree.thrown); + } + if (tree.defaultValue != null) { + print(" default "); + printExpr(tree.defaultValue); } - } - printExprs(tree.params); - print(')'); - if (tree.thrown.nonEmpty()) { - print(" throws "); - printExprs(tree.thrown); - } - if (tree.defaultValue != null) { - print(" default "); - printExpr(tree.defaultValue); } if (tree.body != null) { print(' '); - printStat(tree.body); + if (tree.name == tree.name.table.names.init && + ((enclClassFlags & ENUM) != 0 || + (enclClassFlags & RECORD) != 0)) { + ListBuffer buf = new ListBuffer<>(); + for (List l = tree.body.stats; l.nonEmpty(); l = l.tail) { + // Filter out super constructor calls + if (!TreeInfo.isSuperCall(l.head)) { + buf.append(l.head); + } + } + printBlock(buf.toList()); + } else { + printStat(tree.body); + } } else { print(';'); } @@ -662,6 +740,7 @@ public void visitVarDef(JCVariableDecl tree) { } printDocComment(tree); if ((tree.mods.flags & ENUM) != 0) { + printAnnotations(tree.mods.annotations); print("/*public static final*/ "); print(tree.name); if (tree.init != null) { @@ -676,7 +755,13 @@ public void visitVarDef(JCVariableDecl tree) { } if (init.def != null && init.def.defs != null) { print(' '); - printBlock(init.def.defs); + ListBuffer buf = new ListBuffer<>(); + for (List l = init.def.defs; l.nonEmpty(); l = l.tail) { + if (!isGeneratedDefaultConstructor(l.head)) { + buf.append(l.head); + } + } + printBlock(buf.toList()); } return; }else { @@ -707,6 +792,11 @@ public void visitVarDef(JCVariableDecl tree) { printExpr(tree.init); print(" */"); } + } else if ((tree.mods.flags & RECORD) != 0) { + printTypeAnnotations(tree.mods.annotations); + printExpr(tree.vartype); + print(' '); + print(tree.name); } else { printExpr(tree.mods); if ((tree.mods.flags & VARARGS) != 0) { diff --git a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/amd64/AMD64HotSpotJVMCIBackendFactory.java b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/amd64/AMD64HotSpotJVMCIBackendFactory.java index cae6d18e71ec..7c46c239092b 100644 --- a/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/amd64/AMD64HotSpotJVMCIBackendFactory.java +++ b/src/jdk.internal.vm.ci/share/classes/jdk/vm/ci/hotspot/amd64/AMD64HotSpotJVMCIBackendFactory.java @@ -61,8 +61,10 @@ private static EnumSet computeFeatures(AMD64HotSpotVMConfig config) long featureIndex = idx >>> featuresElementShiftCount; return Unsafe.getUnsafe().getLong(featuresBitMapAddress + featureIndex * Long.BYTES); }, renaming); - assert features.contains(AMD64.CPUFeature.SSE) : "minimum config for x64"; - assert features.contains(AMD64.CPUFeature.SSE2) : "minimum config for x64"; + // SSE and SSE2 are no longer reported as of JDK-8383881, but JVMCI compiler may + // still model instructions using these feature flags, so add them explicitly here. + features.add(AMD64.CPUFeature.SSE); + features.add(AMD64.CPUFeature.SSE2); return features; } diff --git a/src/jdk.jlink/share/classes/jdk/tools/jimage/JImageTask.java b/src/jdk.jlink/share/classes/jdk/tools/jimage/JImageTask.java index 3f324ba13648..402f60475537 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jimage/JImageTask.java +++ b/src/jdk.jlink/share/classes/jdk/tools/jimage/JImageTask.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -298,7 +298,7 @@ private void extract(BasicImageReader reader, String name, parent.getAbsolutePath()); } - if (!ImageResourcesTree.isTreeInfoResource(name)) { + if (location.getType() == ImageLocation.LocationType.RESOURCE) { Files.write(resource.toPath(), bytes); } } @@ -415,21 +415,18 @@ private void iterate(JImageAction jimageAction, continue; } - if (!ImageResourcesTree.isTreeInfoResource(name)) { + ImageLocation location = reader.findLocation(name); + if (location.getType() == ImageLocation.LocationType.RESOURCE) { if (moduleAction != null) { - int offset = name.indexOf('/', 1); - - String newModule = offset != -1 ? - name.substring(1, offset) : - ""; - + String newModule = location.getModule(); + if (newModule.isEmpty()) { + newModule = ""; + } if (!oldModule.equals(newModule)) { moduleAction.apply(reader, oldModule, newModule); oldModule = newModule; } } - - ImageLocation location = reader.findLocation(name); resourceAction.apply(reader, name, location); } } diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/Archive.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/Archive.java index c56346b6994f..1cd8f5993d72 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/Archive.java +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/Archive.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,6 +24,7 @@ */ package jdk.tools.jlink.internal; +import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.nio.file.Path; @@ -34,7 +35,7 @@ * An Archive of all content, classes, resources, configuration files, and * other, for a module. */ -public interface Archive { +public interface Archive extends Closeable { /** * Entry is contained in an Archive @@ -59,11 +60,12 @@ public static enum EntryType { private final String path; /** - * Constructs an entry of the given archive - * @param archive archive - * @param path - * @param name an entry name that does not contain the module name - * @param type + * Constructs an entry of the given archive. + * + * @param archive the archive in which this entry exists. + * @param path the complete path of the entry, including the module. + * @param name an entry name relative to its containing module. + * @param type the entry type. */ public Entry(Archive archive, String path, String name, EntryType type) { this.archive = Objects.requireNonNull(archive); @@ -72,10 +74,6 @@ public Entry(Archive archive, String path, String name, EntryType type) { this.type = Objects.requireNonNull(type); } - public final Archive archive() { - return archive; - } - public final EntryType type() { return type; } @@ -134,5 +132,6 @@ public String toString() { /* * Close the archive */ + @Override void close() throws IOException; } diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/BasicImageWriter.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/BasicImageWriter.java index ca5762276928..67895dd14509 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/BasicImageWriter.java +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/BasicImageWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -36,21 +36,17 @@ public final class BasicImageWriter { public static final String MODULES_IMAGE_NAME = "modules"; - private ByteOrder byteOrder; - private ImageStringsWriter strings; + private final ByteOrder byteOrder; + private final ImageStringsWriter strings; private int length; private int[] redirect; private ImageLocationWriter[] locations; - private List input; - private ImageStream headerStream; - private ImageStream redirectStream; - private ImageStream locationOffsetStream; - private ImageStream locationStream; - private ImageStream allIndexStream; - - public BasicImageWriter() { - this(ByteOrder.nativeOrder()); - } + private final List input; + private final ImageStream headerStream; + private final ImageStream redirectStream; + private final ImageStream locationOffsetStream; + private final ImageStream locationStream; + private final ImageStream allIndexStream; public BasicImageWriter(ByteOrder byteOrder) { this.byteOrder = Objects.requireNonNull(byteOrder); @@ -75,11 +71,15 @@ public String getString(int offset) { return strings.get(offset); } - public void addLocation(String fullname, long contentOffset, - long compressedSize, long uncompressedSize) { + public void addLocation( + String fullname, + long contentOffset, + long compressedSize, + long uncompressedSize, + int previewFlags) { ImageLocationWriter location = ImageLocationWriter.newLocation(fullname, strings, - contentOffset, compressedSize, uncompressedSize); + contentOffset, compressedSize, uncompressedSize, previewFlags); input.add(location); length++; } @@ -88,10 +88,6 @@ ImageLocationWriter[] getLocations() { return locations; } - int getLocationsCount() { - return input.size(); - } - private void generatePerfectHash() { PerfectHashBuilder builder = new PerfectHashBuilder<>( @@ -174,16 +170,4 @@ public byte[] getBytes() { return allIndexStream.toArray(); } - - ImageLocationWriter find(String key) { - int index = redirect[ImageStringsReader.hashCode(key) % length]; - - if (index < 0) { - index = -index - 1; - } else { - index = ImageStringsReader.hashCode(key, index) % length; - } - - return locations[index]; - } } diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImageFileCreator.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImageFileCreator.java index 9e05fe31aa9a..b29a9de52129 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImageFileCreator.java +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImageFileCreator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -49,6 +49,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import jdk.internal.jimage.ImageLocation; import jdk.tools.jlink.internal.Archive.Entry; import jdk.tools.jlink.internal.Archive.Entry.EntryType; import jdk.tools.jlink.internal.JRTArchive.ResourceFileEntry; @@ -227,31 +228,8 @@ private static ResourcePool generateJImage(ResourcePoolManager allContent, DataOutputStream out, boolean generateRuntimeImage ) throws IOException { - ResourcePool resultResources; - try { - resultResources = pluginSupport.visitResources(allContent); - if (generateRuntimeImage) { - // Keep track of non-modules resources for linking from a run-time image - resultResources = addNonClassResourcesTrackFiles(resultResources, - writer); - // Generate the diff between the input resources from packaged - // modules in 'allContent' to the plugin- or otherwise - // generated-content in 'resultResources' - resultResources = addResourceDiffFiles(allContent.resourcePool(), - resultResources, - writer); - } - } catch (PluginException pe) { - if (JlinkTask.DEBUG) { - pe.printStackTrace(); - } - throw pe; - } catch (Exception ex) { - if (JlinkTask.DEBUG) { - ex.printStackTrace(); - } - throw new IOException(ex); - } + ResourcePool resultResources = + getResourcePool(allContent, writer, pluginSupport, generateRuntimeImage); Set duplicates = new HashSet<>(); long[] offset = new long[1]; @@ -282,8 +260,10 @@ private static ResourcePool generateJImage(ResourcePoolManager allContent, offset[0] += onFileSize; return; } + int locFlags = ImageLocation.getPreviewFlags( + res.path(), p -> resultResources.findEntry(p).isPresent()); duplicates.add(path); - writer.addLocation(path, offset[0], compressedSize, uncompressedSize); + writer.addLocation(path, offset[0], compressedSize, uncompressedSize, locFlags); paths.add(path); offset[0] += onFileSize; } @@ -307,6 +287,40 @@ private static ResourcePool generateJImage(ResourcePoolManager allContent, return resultResources; } + private static ResourcePool getResourcePool( + ResourcePoolManager allContent, + BasicImageWriter writer, + ImagePluginStack pluginSupport, + boolean generateRuntimeImage) + throws IOException { + ResourcePool resultResources; + try { + resultResources = pluginSupport.visitResources(allContent); + if (generateRuntimeImage) { + // Keep track of non-modules resources for linking from a run-time image + resultResources = addNonClassResourcesTrackFiles(resultResources, + writer); + // Generate the diff between the input resources from packaged + // modules in 'allContent' to the plugin- or otherwise + // generated-content in 'resultResources' + resultResources = addResourceDiffFiles(allContent.resourcePool(), + resultResources, + writer); + } + } catch (PluginException pe) { + if (JlinkTask.DEBUG) { + pe.printStackTrace(); + } + throw pe; + } catch (Exception ex) { + if (JlinkTask.DEBUG) { + ex.printStackTrace(); + } + throw new IOException(ex); + } + return resultResources; + } + /** * Support for creating a runtime suitable for linking from the run-time * image. @@ -558,62 +572,4 @@ private static ResourcePoolManager createPoolManager(ResourcePool resultResource resultResources.entries().forEach(resources::add); return resources; } - - /** - * Helper method that splits a Resource path onto 3 items: module, parent - * and resource name. - * - * @param path - * @return An array containing module, parent and name. - */ - public static String[] splitPath(String path) { - Objects.requireNonNull(path); - String noRoot = path.substring(1); - int pkgStart = noRoot.indexOf("/"); - String module = noRoot.substring(0, pkgStart); - List result = new ArrayList<>(); - result.add(module); - String pkg = noRoot.substring(pkgStart + 1); - String resName; - int pkgEnd = pkg.lastIndexOf("/"); - if (pkgEnd == -1) { // No package. - resName = pkg; - } else { - resName = pkg.substring(pkgEnd + 1); - } - - pkg = toPackage(pkg, false); - result.add(pkg); - result.add(resName); - - String[] array = new String[result.size()]; - return result.toArray(array); - } - - /** - * Returns the path of the resource. - */ - public static String resourceName(String path) { - Objects.requireNonNull(path); - String s = path.substring(1); - int index = s.indexOf("/"); - return s.substring(index + 1); - } - - public static String toPackage(String name) { - return toPackage(name, false); - } - - private static String toPackage(String name, boolean log) { - int index = name.lastIndexOf('/'); - if (index > 0) { - return name.substring(0, index).replace('/', '.'); - } else { - // ## unnamed package - if (log) { - System.err.format("Warning: %s in unnamed package%n", name); - } - return ""; - } - } } diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImageLocationWriter.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImageLocationWriter.java index f2c7f102027c..18ee9c881036 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImageLocationWriter.java +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImageLocationWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -53,9 +53,13 @@ private ImageLocationWriter addAttribute(int kind, String value) { return addAttribute(kind, strings.add(value)); } - static ImageLocationWriter newLocation(String fullName, + static ImageLocationWriter newLocation( + String fullName, ImageStringsWriter strings, - long contentOffset, long compressedSize, long uncompressedSize) { + long contentOffset, + long compressedSize, + long uncompressedSize, + int previewFlags) { String moduleName = ""; String parentName = ""; String baseName; @@ -90,13 +94,14 @@ static ImageLocationWriter newLocation(String fullName, } return new ImageLocationWriter(strings) - .addAttribute(ATTRIBUTE_MODULE, moduleName) - .addAttribute(ATTRIBUTE_PARENT, parentName) - .addAttribute(ATTRIBUTE_BASE, baseName) - .addAttribute(ATTRIBUTE_EXTENSION, extensionName) - .addAttribute(ATTRIBUTE_OFFSET, contentOffset) - .addAttribute(ATTRIBUTE_COMPRESSED, compressedSize) - .addAttribute(ATTRIBUTE_UNCOMPRESSED, uncompressedSize); + .addAttribute(ATTRIBUTE_MODULE, moduleName) + .addAttribute(ATTRIBUTE_PARENT, parentName) + .addAttribute(ATTRIBUTE_BASE, baseName) + .addAttribute(ATTRIBUTE_EXTENSION, extensionName) + .addAttribute(ATTRIBUTE_OFFSET, contentOffset) + .addAttribute(ATTRIBUTE_COMPRESSED, compressedSize) + .addAttribute(ATTRIBUTE_UNCOMPRESSED, uncompressedSize) + .addAttribute(ATTRIBUTE_PREVIEW_FLAGS, previewFlags); } @Override diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImageResourcesTree.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImageResourcesTree.java index 8a4288f0cfd3..2f8143820ca7 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImageResourcesTree.java +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImageResourcesTree.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,33 +24,34 @@ */ package jdk.tools.jlink.internal; +import jdk.internal.jimage.ImageLocation; +import jdk.internal.jimage.ModuleLink; + import java.io.DataOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collections; +import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.TreeMap; -import java.util.TreeSet; +import java.util.stream.Collectors; /** * A class to build a sorted tree of Resource paths as a tree of ImageLocation. - * */ // XXX Public only due to the JImageTask / JImageTask code duplication public final class ImageResourcesTree { - public static boolean isTreeInfoResource(String path) { - return path.startsWith("/packages") || path.startsWith("/modules"); - } - /** * Path item tree node. */ - private static class Node { + // Visible for testing only. + static class Node { private final String name; private final Map children = new TreeMap<>(); @@ -66,6 +67,14 @@ private Node(String name, Node parent) { } } + private void setLocation(ImageLocationWriter loc) { + // This *can* be called more than once, but only with the same instance. + if (this.loc != null && loc != this.loc) { + throw new IllegalStateException("Cannot add different locations: " + name); + } + this.loc = Objects.requireNonNull(loc); + } + public String getPath() { if (parent == null) { return "/"; @@ -95,215 +104,206 @@ private static String buildPath(Node item) { } } - private static final class ResourceNode extends Node { + // Visible for testing only. + static final class ResourceNode extends Node { public ResourceNode(String name, Node parent) { super(name, parent); } } - private static class PackageNode extends Node { - /** - * A reference to a package. Empty packages can be located inside one or - * more modules. A package with classes exist in only one module. - */ - static final class PackageReference { - - private final String name; - private final boolean isEmpty; + /** + * A 2nd level package directory, {@code "/packages/"}. + * + *

    While package paths can exist within many modules, for each package + * there is at most one module in which that package has resources. + * + *

    For example, the package path {@code java/util} exists in both the + * {@code java.base} and {@code java.logging} modules. This means both + * {@code "/packages/java.util/java.base"} and + * {@code "/packages/java.util/java.logging"} will exist, but only + * {@code "java.base"} entry will be marked as having content. + * + *

    When processing module links in non-preview mode, entries marked + * as {@link ModuleLink#isPreviewOnly() preview-only} must be ignored. + * + *

    If all links in a package are preview-only, then the entire package is + * marked as preview-only, and must be ignored. + */ + // Visible for testing only. + static final class PackageNode extends Node { + private final List moduleLinks; - PackageReference(String name, boolean isEmpty) { - this.name = Objects.requireNonNull(name); - this.isEmpty = isEmpty; + PackageNode(String name, List moduleLinks, Node parent) { + super(name, parent); + if (moduleLinks.isEmpty()) { + throw new IllegalStateException("Package must be associated with modules: " + name); } - - @Override - public String toString() { - return name + "[empty:" + isEmpty + "]"; + if (moduleLinks.stream().filter(ModuleLink::hasResources).count() > 1) { + throw new IllegalStateException("Multiple modules contain non-empty package: " + name); } + this.moduleLinks = Collections.unmodifiableList(moduleLinks); } - private final Map references = new TreeMap<>(); - - PackageNode(String name, Node parent) { - super(name, parent); - } - - private void addReference(String name, boolean isEmpty) { - PackageReference ref = references.get(name); - if (ref == null || ref.isEmpty) { - references.put(name, new PackageReference(name, isEmpty)); - } + List getModuleLinks() { + return moduleLinks; } + } - private void validate() { - boolean exists = false; - for (PackageReference ref : references.values()) { - if (!ref.isEmpty) { - if (exists) { - throw new RuntimeException("Multiple modules to contain package " - + getName()); - } else { - exists = true; - } - } - } + // Not serialized, and never stored in any field of any class that is. + @SuppressWarnings("serial") + private static final class InvalidTreeException extends Exception { + public InvalidTreeException(Node badNode) { + super("Resources tree, invalid data structure, skipping: " + badNode.getPath()); } + // Exception only used for program flow, not debugging. + @Override + public Throwable fillInStackTrace() {return this;} } /** * Tree of nodes. */ - private static final class Tree { + // Visible for testing only. + static final class Tree { + private static final String PREVIEW_PREFIX = "META-INF/preview/"; private final Map directAccess = new HashMap<>(); private final List paths; private final Node root; - private Node modules; - private Node packages; + private Node packagesRoot; - private Tree(List paths) { - this.paths = paths; + // Visible for testing only. + Tree(List paths) { + this.paths = paths.stream().sorted(Comparator.reverseOrder()).toList(); + // Root node is not added to the directAccess map. root = new Node("", null); buildTree(); } private void buildTree() { - modules = new Node("modules", root); - directAccess.put(modules.getPath(), modules); - - Map> moduleToPackage = new TreeMap<>(); - Map> packageToModule = new TreeMap<>(); - - for (String p : paths) { - if (!p.startsWith("/")) { - continue; - } - String[] split = p.split("/"); - // minimum length is 3 items: // - if (split.length < 3) { - System.err.println("Resources tree, invalid data structure, " - + "skipping " + p); - continue; - } - Node current = modules; - String module = null; - for (int i = 0; i < split.length; i++) { - // When a non terminal node is marked as being a resource, something is wrong. + Node modulesRoot = new Node("modules", root); + directAccess.put(modulesRoot.getPath(), modulesRoot); + packagesRoot = new Node("packages", root); + directAccess.put(packagesRoot.getPath(), packagesRoot); + + // Map of dot-separated package names to module links (those in + // which the package appear). Links are merged after to ensure each + // module name appears only once, but temporarily a module may have + // several link entries per package (e.g. with-content, + // without-content, normal, preview-only etc..). + Map> packageToModules = new TreeMap<>(); + for (String fullPath : paths) { + try { + processPath(fullPath, modulesRoot, packageToModules); + } catch (InvalidTreeException ex) { // It has been observed some badly created jar file to contain - // invalid directory entry marled as not directory (see 8131762) - if (current instanceof ResourceNode) { - System.err.println("Resources tree, invalid data structure, " - + "skipping " + p); - continue; - } - String s = split[i]; - if (!s.isEmpty()) { - // First item, this is the module, simply add a new node to the - // tree. - if (module == null) { - module = s; - } - Node n = current.children.get(s); - if (n == null) { - if (i == split.length - 1) { // Leaf - n = new ResourceNode(s, current); - String pkg = toPackageName(n.parent); - //System.err.println("Adding a resource node. pkg " + pkg + ", name " + s); - if (pkg != null && !pkg.startsWith("META-INF")) { - Set pkgs = moduleToPackage.get(module); - if (pkgs == null) { - pkgs = new TreeSet<>(); - moduleToPackage.put(module, pkgs); - } - pkgs.add(pkg); - } - } else { // put only sub trees, no leaf - n = new Node(s, current); - directAccess.put(n.getPath(), n); - String pkg = toPackageName(n); - if (pkg != null && !pkg.startsWith("META-INF")) { - Set mods = packageToModule.get(pkg); - if (mods == null) { - mods = new TreeSet<>(); - packageToModule.put(pkg, mods); - } - mods.add(module); - } - } - } - current = n; - } - } - } - packages = new Node("packages", root); - directAccess.put(packages.getPath(), packages); - // The subset of package nodes that have some content. - // These packages exist only in a single module. - for (Map.Entry> entry : moduleToPackage.entrySet()) { - for (String pkg : entry.getValue()) { - PackageNode pkgNode = new PackageNode(pkg, packages); - pkgNode.addReference(entry.getKey(), false); - directAccess.put(pkgNode.getPath(), pkgNode); + // invalid directory entry marked as not directory (see 8131762). + System.err.println(ex.getMessage()); } } - // All packages - for (Map.Entry> entry : packageToModule.entrySet()) { - // Do we already have a package node? - PackageNode pkgNode = (PackageNode) packages.getChildren(entry.getKey()); - if (pkgNode == null) { - pkgNode = new PackageNode(entry.getKey(), packages); - } - for (String module : entry.getValue()) { - pkgNode.addReference(module, true); - } + // We've collected information for all "packages", including the root + // (empty) package and anything under "META-INF". However, these should + // not have entries in the "/packages" directory. + packageToModules.keySet().removeIf(p -> p.isEmpty() || p.equals("META-INF") || p.startsWith("META-INF.")); + packageToModules.forEach((pkgName, modLinks) -> { + // Merge multiple links for the same module. + List pkgModules = modLinks.stream() + .collect(Collectors.groupingBy(ModuleLink::name)) + .values().stream() + .map(links -> links.stream().reduce(ModuleLink::merge).orElseThrow()) + .sorted() + .toList(); + PackageNode pkgNode = new PackageNode(pkgName, pkgModules, packagesRoot); directAccess.put(pkgNode.getPath(), pkgNode); + }); + } + + private void processPath( + String fullPath, + Node modulesRoot, + Map> packageToModules) + throws InvalidTreeException { + // Paths are untrusted, so be careful about checking expected format. + if (!fullPath.startsWith("/") || fullPath.endsWith("/") || fullPath.contains("//")) { + return; + } + int modEnd = fullPath.indexOf('/', 1); + // Ensure non-empty module name with non-empty suffix. + if (modEnd <= 1) { + return; } - // Validate that the packages are well formed. - for (Node n : packages.children.values()) { - ((PackageNode)n).validate(); + String modName = fullPath.substring(1, modEnd); + String pkgPath = fullPath.substring(modEnd + 1); + + Node parentNode = getDirectoryNode(modName, modulesRoot); + boolean isPreviewPath = false; + if (pkgPath.startsWith(PREVIEW_PREFIX)) { + // For preview paths, process nodes relative to the preview directory. + pkgPath = pkgPath.substring(PREVIEW_PREFIX.length()); + Node metaInf = getDirectoryNode("META-INF", parentNode); + parentNode = getDirectoryNode("preview", metaInf); + isPreviewPath = true; } + int pathEnd = pkgPath.lastIndexOf('/'); + // From invariants tested above, this must now be well-formed. + String fullPkgName = (pathEnd == -1) ? "" : pkgPath.substring(0, pathEnd).replace('/', '.'); + String resourceName = pkgPath.substring(pathEnd + 1); + // Intermediate packages are marked "empty" (no resources). This might + // later be merged with a non-empty link for the same package. + ModuleLink emptyLink = ModuleLink.forEmptyPackage(modName, isPreviewPath); + + // Work down through empty packages to final resource. + for (int i = pkgEndIndex(fullPkgName, 0); i != -1; i = pkgEndIndex(fullPkgName, i)) { + // Due to invariants already checked, pkgName is non-empty. + String pkgName = fullPkgName.substring(0, i); + packageToModules.computeIfAbsent(pkgName, p -> new HashSet<>()).add(emptyLink); + String childNodeName = pkgName.substring(pkgName.lastIndexOf('.') + 1); + parentNode = getDirectoryNode(childNodeName, parentNode); + } + // Reached non-empty (leaf) package (could still be a duplicate). + Node resourceNode = parentNode.getChildren(resourceName); + if (resourceNode == null) { + ModuleLink resourceLink = ModuleLink.forPackage(modName, isPreviewPath); + packageToModules.computeIfAbsent(fullPkgName, p -> new HashSet<>()).add(resourceLink); + // Init adds new node to parent (don't add resources to directAccess). + new ResourceNode(resourceName, parentNode); + } else if (!(resourceNode instanceof ResourceNode)) { + throw new InvalidTreeException(resourceNode); + } } - public String toResourceName(Node node) { - if (!node.children.isEmpty()) { - throw new RuntimeException("Node is not a resource"); + private Node getDirectoryNode(String name, Node parent) throws InvalidTreeException { + Node child = parent.getChildren(name); + if (child == null) { + // Adds child to parent during init. + child = new Node(name, parent); + directAccess.put(child.getPath(), child); + } else if (child instanceof ResourceNode) { + throw new InvalidTreeException(child); } - return removeRadical(node); + return child; } - public String getModule(Node node) { - if (node.parent == null || node.getName().equals("modules") - || node.getName().startsWith("packages")) { - return null; - } - String path = removeRadical(node); - // "/xxx/..."; - path = path.substring(1); - int i = path.indexOf("/"); - if (i == -1) { - return path; - } else { - return path.substring(0, i); + // Helper to iterate package names up to, and including, the complete name. + private int pkgEndIndex(String s, int i) { + if (i >= 0 && i < s.length()) { + i = s.indexOf('.', i + 1); + return i != -1 ? i : s.length(); } + return -1; } - public String toPackageName(Node node) { - if (node.parent == null) { - return null; - } - String path = removeRadical(node.getPath(), "/modules/"); - String module = getModule(node); - if (path.equals(module)) { - return null; + private String toResourceName(Node node) { + if (!node.children.isEmpty()) { + throw new RuntimeException("Node is not a resource"); } - String pkg = removeRadical(path, module + "/"); - return pkg.replace('/', '.'); + return removeRadical(node); } - public String removeRadical(Node node) { + private String removeRadical(Node node) { return removeRadical(node.getPath(), "/modules"); } @@ -339,9 +339,10 @@ private static final class LocationsAdder { private int addLocations(Node current) { if (current instanceof PackageNode) { - PackageNode pkgNode = (PackageNode) current; - int size = pkgNode.references.size() * 8; - writer.addLocation(current.getPath(), offset, 0, size); + List links = ((PackageNode) current).getModuleLinks(); + // "/packages/" entries have 8-byte entries (flags+offset). + int size = links.size() * 8; + writer.addLocation(current.getPath(), offset, 0, size, ImageLocation.getPackageFlags(links)); offset += size; } else { int[] ret = new int[current.children.size()]; @@ -351,8 +352,10 @@ private int addLocations(Node current) { i += 1; } if (current != tree.getRoot() && !(current instanceof ResourceNode)) { + int locFlags = ImageLocation.getPreviewFlags(current.getPath(), tree.directAccess::containsKey); + // Normal directory entries have 4-byte entries (offset only). int size = ret.length * 4; - writer.addLocation(current.getPath(), offset, 0, size); + writer.addLocation(current.getPath(), offset, 0, size, locFlags); offset += size; } } @@ -369,7 +372,7 @@ private List computeContent() { for (Map.Entry entry : outLocations.entrySet()) { Node item = tree.getMap().get(entry.getKey()); if (item != null) { - item.loc = entry.getValue(); + item.setLocation(entry.getValue()); } } computeContent(tree.getRoot(), outLocations); @@ -378,18 +381,13 @@ private List computeContent() { private int computeContent(Node current, Map outLocations) { if (current instanceof PackageNode) { - // /packages/ - PackageNode pkgNode = (PackageNode) current; - int size = pkgNode.references.size() * 8; - ByteBuffer buff = ByteBuffer.allocate(size); - buff.order(writer.getByteOrder()); - for (PackageNode.PackageReference mod : pkgNode.references.values()) { - buff.putInt(mod.isEmpty ? 1 : 0); - buff.putInt(writer.addString(mod.name)); - } - byte[] arr = buff.array(); - content.add(arr); - current.loc = outLocations.get(current.getPath()); + // "/packages/" entries have 8-byte entries (flags+offset). + List links = ((PackageNode) current).getModuleLinks(); + ByteBuffer byteBuffer = ByteBuffer.allocate(8 * links.size()); + byteBuffer.order(writer.getByteOrder()); + ModuleLink.write(links, byteBuffer.asIntBuffer(), writer::addString); + content.add(byteBuffer.array()); + current.setLocation(outLocations.get(current.getPath())); } else { int[] ret = new int[current.children.size()]; int i = 0; @@ -410,10 +408,10 @@ private int computeContent(Node current, Map outLoc if (current instanceof ResourceNode) { // A resource location, remove "/modules" String s = tree.toResourceName(current); - current.loc = outLocations.get(s); + current.setLocation(outLocations.get(s)); } else { // empty "/packages" or empty "/modules" paths - current.loc = outLocations.get(current.getPath()); + current.setLocation(outLocations.get(current.getPath())); } } if (current.loc == null && current != tree.getRoot()) { diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImageStringsWriter.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImageStringsWriter.java index 7ba9b7db72e6..18794d46b948 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImageStringsWriter.java +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ImageStringsWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2014, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -33,7 +33,6 @@ class ImageStringsWriter implements ImageStrings { private static final int NOT_FOUND = -1; - static final int EMPTY_OFFSET = 0; private final HashMap stringToOffsetMap; private final ImageStream stream; @@ -42,16 +41,20 @@ class ImageStringsWriter implements ImageStrings { this.stringToOffsetMap = new HashMap<>(); this.stream = new ImageStream(); - // Reserve 0 offset for empty string. - int offset = addString(""); - if (offset != 0) { - throw new InternalError("Empty string not offset zero"); - } + // Frequently used/special strings for which the offset is useful. + // New strings can be reserved after existing strings without having to + // change the jimage file version, but any change to existing entries + // requires the jimage file version to be increased at the same time. + reserveString("", ImageStrings.EMPTY_STRING_OFFSET); + reserveString("class", ImageStrings.CLASS_STRING_OFFSET); + reserveString("modules", ImageStrings.MODULES_STRING_OFFSET); + reserveString("packages", ImageStrings.PACKAGES_STRING_OFFSET); + } - // Reserve 1 offset for frequently used ".class". - offset = addString("class"); - if (offset != 1) { - throw new InternalError("'class' string not offset one"); + private void reserveString(String value, int expectedOffset) { + int offset = addString(value); + if (offset != expectedOffset) { + throw new InternalError("Reserved string \"" + value + "\" not at expected offset " + expectedOffset + "[was " + offset + "]"); } } diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JRTArchive.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JRTArchive.java index aac220e5b943..284f1cf6c77a 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JRTArchive.java +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JRTArchive.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2024, Red Hat, Inc. + * 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 @@ -32,7 +33,6 @@ import java.io.InputStream; import java.io.UncheckedIOException; import java.lang.module.ModuleFinder; -import java.lang.module.ModuleReference; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -51,6 +51,8 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import jdk.internal.jimage.ResourceEntries; +import jdk.internal.jimage.SystemImageReader; import jdk.internal.util.OperatingSystem; import jdk.tools.jlink.internal.Archive.Entry.EntryType; import jdk.tools.jlink.internal.runtimelink.ResourceDiff; @@ -63,10 +65,9 @@ * associated files from the filesystem of the JDK installation. */ public class JRTArchive implements Archive { - private final String module; private final Path path; - private final ModuleReference ref; + private final ResourceEntries imageResources; // The collection of files of this module private final List files = new ArrayList<>(); // Files not part of the lib/modules image of the JDK install. @@ -99,12 +100,11 @@ public class JRTArchive implements Archive { Set upgradeableFiles) { this.module = module; this.path = path; - this.ref = ModuleFinder.ofSystem() - .find(module) - .orElseThrow(() -> - new IllegalArgumentException( - "Module " + module + - " not part of the JDK install")); + ModuleFinder.ofSystem() + .find(module) + .orElseThrow(() -> new IllegalArgumentException( + "Module " + module + " not part of the JDK install")); + this.imageResources = SystemImageReader.getResourceEntries(); this.errorOnModifiedFile = errorOnModifiedFile; this.otherRes = readModuleResourceFile(module); this.resDiff = Objects.requireNonNull(perModDiff).stream() @@ -159,52 +159,35 @@ public boolean equals(Object obj) { Objects.equals(path, other.path)); } + private boolean isNormalOrModifiedDiff(String name) { + ResourceDiff rd = resDiff.get(name); + // Filter all resources with a resource diff of kind MODIFIED. + // Note that REMOVED won't happen since in that case the module listing + // won't have the resource anyway. + // Note as well that filter removes files of kind ADDED. Those files are + // not in the packaged modules, so ought not to get returned from the + // pipeline. + return (rd == null || rd.getKind() == ResourceDiff.Kind.MODIFIED); + } + private void collectFiles() throws IOException { if (files.isEmpty()) { addNonClassResources(); + // Add classes/resources from the run-time image, // patched with the run-time image diff - files.addAll(ref.open().list() - .filter(i -> { - String lookupKey = String.format("/%s/%s", module, i); - ResourceDiff rd = resDiff.get(lookupKey); - // Filter all resources with a resource diff - // that are of kind MODIFIED. - // Note that REMOVED won't happen since in - // that case the module listing won't have - // the resource anyway. - // Note as well that filter removes files - // of kind ADDED. Those files are not in - // the packaged modules, so ought not to - // get returned from the pipeline. - return (rd == null || - rd.getKind() == ResourceDiff.Kind.MODIFIED); - }) - .map(s -> { - String lookupKey = String.format("/%s/%s", module, s); - return new JRTArchiveFile(JRTArchive.this, s, - EntryType.CLASS_OR_RESOURCE, - null /* hashOrTarget */, - false /* symlink */, - resDiff.get(lookupKey)); - }) - .toList()); + imageResources.getEntryNames(module) + .filter(this::isNormalOrModifiedDiff) + .sorted() + .map(name -> new JrtClassOrResource(this, name, resDiff.get(name))) + .forEach(files::add); + // Finally add all files only present in the resource diff // That is, removed items in the run-time image. files.addAll(resDiff.values().stream() - .filter(rd -> rd.getKind() == ResourceDiff.Kind.REMOVED) - .map(s -> { - int secondSlash = s.getName().indexOf("/", 1); - assert secondSlash != -1; - String pathWithoutModule = s.getName().substring(secondSlash + 1); - return new JRTArchiveFile(JRTArchive.this, - pathWithoutModule, - EntryType.CLASS_OR_RESOURCE, - null /* hashOrTarget */, - false /* symlink */, - s); - }) - .toList()); + .filter(rd -> rd.getKind() == ResourceDiff.Kind.REMOVED) + .map(rd -> new JrtClassOrResource(this, rd.getName(), rd)) + .toList()); } } @@ -234,15 +217,10 @@ private void addNonClassResources() { } } - return new JRTArchiveFile(JRTArchive.this, - m.resPath, - toEntryType(m.resType), - m.hashOrTarget, - m.symlink, - /* diff only for resources */ - null); - }) - .toList()); + return new JrtOtherFile( + this, m.resPath, toEntryType(m.resType), m.hashOrTarget, m.symlink); + }) + .toList()); } } @@ -323,11 +301,11 @@ public String encodeToString() { resPath); } - /** + /* * line: ||| * - * Take the integer before '|' convert it to a Type. The second - * token is an integer representing symlinks (or not). The third token is + * Take the integer before '|' convert it to a Type. The second token + * is an integer representing symlinks (or not). The third token is * a hash sum (sha512) of the file denoted by the fourth token (path). */ static ResourceFileEntry decodeFromString(String line) { @@ -437,47 +415,75 @@ interface JRTFile { Entry toEntry(); } - record JRTArchiveFile(Archive archive, - String resPath, - EntryType resType, - String sha, - boolean symlink, - ResourceDiff diff) implements JRTFile { + record JrtClassOrResource( + JRTArchive archive, + String resPath, + ResourceDiff diff) implements JRTFile { + @Override + public Entry toEntry() { + assert resPath.startsWith("/" + archive.moduleName() + "/"); + String resName = resPath.substring(archive.moduleName().length() + 2); + + // If the resource has a diff to the packaged modules, use the diff. + // Diffs of kind ADDED have been filtered out in collectFiles(); + if (diff != null) { + assert diff.getKind() != ResourceDiff.Kind.ADDED; + assert diff.getName().equals(resPath); + + return new Entry(archive, resPath, resName, EntryType.CLASS_OR_RESOURCE) { + @Override + public long size() { + return diff.getResourceBytes().length; + } + @Override + public InputStream stream() { + return new ByteArrayInputStream(diff.getResourceBytes()); + } + }; + } else { + return new Entry(archive, resPath, resName, EntryType.CLASS_OR_RESOURCE) { + @Override + public long size() { + return archive.imageResources.getSize(resPath); + } + + @Override + public InputStream stream() { + // Byte content could be cached in the entry if needed. + return new ByteArrayInputStream(archive.imageResources.getBytes(resPath)); + } + }; + } + } + } + + record JrtOtherFile( + JRTArchive archive, + String resPath, + EntryType resType, + String sha, + boolean symlink) implements JRTFile { + + // Read from the base JDK image, special casing + // symlinks, which have the link target in the + // hashOrTarget field. + Path targetPath() { + return BASE.resolve(symlink ? sha : resPath); + } + public Entry toEntry() { - return new Entry(archive, - String.format("/%s/%s", - archive.moduleName(), - resPath), - resPath, - resType) { + assert resType != EntryType.CLASS_OR_RESOURCE; + + return new Entry( + archive, + String.format("/%s/%s", archive.moduleName(), resPath), + resPath, + resType) { + @Override public long size() { try { - if (resType != EntryType.CLASS_OR_RESOURCE) { - // Read from the base JDK image, special casing - // symlinks, which have the link target in the - // hashOrTarget field - if (symlink) { - return Files.size(BASE.resolve(sha)); - } - return Files.size(BASE.resolve(resPath)); - } else { - if (diff != null) { - // If the resource has a diff to the - // packaged modules, use the diff. Diffs of kind - // ADDED have been filtered out in collectFiles(); - assert diff.getKind() != ResourceDiff.Kind.ADDED; - assert diff.getName().equals(String.format("/%s/%s", - archive.moduleName(), - resPath)); - return diff.getResourceBytes().length; - } - // Read from the module image. This works, because - // the underlying base path is a JrtPath with the - // JrtFileSystem underneath which is able to handle - // this size query. - return Files.size(archive.getPath().resolve(resPath)); - } + return Files.size(targetPath()); } catch (IOException e) { throw new UncheckedIOException(e); } @@ -485,28 +491,8 @@ public long size() { @Override public InputStream stream() throws IOException { - if (resType != EntryType.CLASS_OR_RESOURCE) { - // Read from the base JDK image. - Path path = symlink ? BASE.resolve(sha) : BASE.resolve(resPath); - return Files.newInputStream(path); - } else { - // Read from the module image. Use the diff to the - // packaged modules if we have one. Diffs of kind - // ADDED have been filtered out in collectFiles(); - if (diff != null) { - assert diff.getKind() != ResourceDiff.Kind.ADDED; - assert diff.getName().equals(String.format("/%s/%s", - archive.moduleName(), - resPath)); - return new ByteArrayInputStream(diff.getResourceBytes()); - } - String module = archive.moduleName(); - ModuleReference mRef = ModuleFinder.ofSystem() - .find(module).orElseThrow(); - return mRef.open().open(resPath).orElseThrow(); - } + return Files.newInputStream(targetPath()); } - }; } } diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java index 3baae08eed63..45800be52725 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/JlinkTask.java @@ -373,21 +373,22 @@ public static void createImage(JlinkConfiguration config, plugins = plugins == null ? new PluginsConfiguration() : plugins; // First create the image provider - ImageProvider imageProvider = - createImageProvider(config, - null, - IGNORE_SIGNING_DEFAULT, - false, - null, - false, - new OptionsValues(), - null); - - // Then create the Plugin Stack - ImagePluginStack stack = ImagePluginConfiguration.parseConfiguration(plugins); - - //Ask the stack to proceed; - stack.operate(imageProvider); + try (ImageHelper imageProvider = + createImageProvider(config, + null, + IGNORE_SIGNING_DEFAULT, + false, + null, + false, + new OptionsValues(), + null)) { + + // Then create the Plugin Stack + ImagePluginStack stack = ImagePluginConfiguration.parseConfiguration(plugins); + + // Ask the stack to proceed; + stack.operate(imageProvider); + } } // the token for "all modules on the module path" @@ -511,22 +512,24 @@ private void createImage(JlinkConfiguration config) throws Exception { } // First create the image provider - ImageHelper imageProvider = createImageProvider(config, - options.packagedModulesPath, - options.ignoreSigning, - options.bindServices, - options.endian, - options.verbose, - options, - log); - - // Then create the Plugin Stack - ImagePluginStack stack = ImagePluginConfiguration.parseConfiguration( - taskHelper.getPluginsConfig(options.output, options.launchers, - imageProvider.targetPlatform)); - - //Ask the stack to proceed - stack.operate(imageProvider); + try (ImageHelper imageProvider = createImageProvider(config, + options.packagedModulesPath, + options.ignoreSigning, + options.bindServices, + options.endian, + options.verbose, + options, + log)) { + // Then create the Plugin Stack + ImagePluginStack stack = ImagePluginConfiguration.parseConfiguration( + taskHelper.getPluginsConfig( + options.output, + options.launchers, + imageProvider.targetPlatform)); + + //Ask the stack to proceed + stack.operate(imageProvider); + } } /** @@ -1054,10 +1057,11 @@ private String getSaveOpts() { return sb.toString(); } - private static record ImageHelper(Set archives, - Platform targetPlatform, - Path packagedModulesPath, - boolean generateRuntimeImage) implements ImageProvider { + private record ImageHelper(Set archives, + Platform targetPlatform, + Path packagedModulesPath, + boolean generateRuntimeImage) + implements ImageProvider, AutoCloseable { @Override public ExecutableImage retrieve(ImagePluginStack stack) throws IOException { ExecutableImage image = ImageFileCreator.create(archives, @@ -1073,5 +1077,25 @@ public ExecutableImage retrieve(ImagePluginStack stack) throws IOException { } return image; } + + @Override + public void close() throws IOException { + List thrown = null; + for (Archive archive : archives) { + try { + archive.close(); + } catch (IOException ex) { + if (thrown == null) { + thrown = new ArrayList<>(); + } + thrown.add(ex); + } + } + if (thrown != null) { + IOException ex = new IOException("Archives could not be closed", thrown.getFirst()); + thrown.subList(1, thrown.size()).forEach(ex::addSuppressed); + throw ex; + } + } } } diff --git a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ResourcePoolManager.java b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ResourcePoolManager.java index ba04b9db014e..c49b8971e060 100644 --- a/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ResourcePoolManager.java +++ b/src/jdk.jlink/share/classes/jdk/tools/jlink/internal/ResourcePoolManager.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 @@ -66,20 +66,8 @@ static Attributes readModuleAttributes(ResourcePoolModule mod) { } } - /** - * Returns true if a resource is located in a named package. - */ - public static boolean isNamedPackageResource(String name) { - int index = name.lastIndexOf("/"); - if (index == -1) { - return false; - } else { - String pn = name.substring(0, index).replace('/', '.'); - return Checks.isPackageName(pn); - } - } - static class ResourcePoolModuleImpl implements ResourcePoolModule { + private static final String PREVIEW_PREFIX = "META-INF/preview/"; final Map moduleContent = new LinkedHashMap<>(); // lazily initialized @@ -132,16 +120,8 @@ private void initModuleAttributes() { public Set packages() { Set pkgs = new HashSet<>(); moduleContent.values().stream() - .filter(m -> m.type() == ResourcePoolEntry.Type.CLASS_OR_RESOURCE) - .forEach(res -> { - String name = ImageFileCreator.resourceName(res.path()); - if (isNamedPackageResource(name)) { - String pkg = ImageFileCreator.toPackage(name); - if (!pkg.isEmpty()) { - pkgs.add(pkg); - } - } - }); + .filter(m -> m.type() == ResourcePoolEntry.Type.CLASS_OR_RESOURCE) + .forEach(res -> inferPackageName(res).ifPresent(pkgs::add)); return pkgs; } @@ -159,6 +139,39 @@ public Stream entries() { public int entryCount() { return moduleContent.values().size(); } + + /** + * Returns a valid non-empty package name, inferred from a resource pool + * entry's path. + * + *

    If the resource pool entry is for a preview resource (i.e. with + * path {@code "/mod-name/META-INF/preview/pkg-path/resource-name"}) + * the package name is the non-preview name based on {@code "pkg-path"}. + * + * @return the inferred package name, or {@link Optional#empty() empty} + * if no name could be inferred. + */ + private static Optional inferPackageName(ResourcePoolEntry res) { + // Expect entry paths to be "/mod-name/pkg-path/resource-name", but + // may also get "/mod-name/META-INF/preview/pkg-path/resource-name" + String name = res.path(); + if (name.charAt(0) == '/') { + int pkgStart = name.indexOf('/', 1) + 1; + int pkgEnd = name.lastIndexOf('/'); + if (pkgStart > 0 && pkgEnd > pkgStart) { + String pkgPath = name.substring(pkgStart, pkgEnd); + // Handle preview paths by removing the prefix. + if (pkgPath.startsWith(PREVIEW_PREFIX)) { + pkgPath = pkgPath.substring(PREVIEW_PREFIX.length()); + } + String pkgName = pkgPath.replace('/', '.'); + if (Checks.isPackageName(pkgName)) { + return Optional.of(pkgName); + } + } + } + return Optional.empty(); + } } public class ResourcePoolImpl implements ResourcePool { diff --git a/src/jdk.localedata/share/legal/cldr.md b/src/jdk.localedata/share/legal/cldr.md index 6e609f353023..7423d3492f5f 100644 --- a/src/jdk.localedata/share/legal/cldr.md +++ b/src/jdk.localedata/share/legal/cldr.md @@ -1,4 +1,4 @@ -## Unicode Common Local Data Repository (CLDR) v48 +## Unicode Common Local Data Repository (CLDR) v48.2 ### CLDR License diff --git a/test/hotspot/gtest/aarch64/test_threadLS.cpp b/test/hotspot/gtest/aarch64/test_threadLS.cpp new file mode 100644 index 000000000000..1a4a152ea1d9 --- /dev/null +++ b/test/hotspot/gtest/aarch64/test_threadLS.cpp @@ -0,0 +1,35 @@ +/* + * 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. + */ + +#if defined(AARCH64) && !defined(ZERO) + +#include "runtime/javaThread.hpp" +#include "unittest.hpp" + +TEST_VM(ThreadLS, get_thread_helper) { + Thread* expected = Thread::current(); + Thread* actual = JavaThread::aarch64_get_thread_helper(); + ASSERT_EQ(actual, expected); +} + +#endif // AARCH64 && !ZERO diff --git a/test/hotspot/gtest/gc/shared/test_collectorPolicy.cpp b/test/hotspot/gtest/gc/serial/test_collectorPolicy.cpp similarity index 100% rename from test/hotspot/gtest/gc/shared/test_collectorPolicy.cpp rename to test/hotspot/gtest/gc/serial/test_collectorPolicy.cpp diff --git a/test/hotspot/gtest/utilities/test_unsigned5.cpp b/test/hotspot/gtest/utilities/test_unsigned5.cpp index f3859d0a750a..d3288e1f8c7a 100644 --- a/test/hotspot/gtest/utilities/test_unsigned5.cpp +++ b/test/hotspot/gtest/utilities/test_unsigned5.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2022, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -221,6 +221,46 @@ TEST_VM(unsigned5, reader) { ASSERT_EQ(x, y) << i; } ASSERT_TRUE(i < LEN); + + { // Begin try_skip() test. + i = 0; + int skipped = 0; + r1.set_position(0); + // Skip from beginning to ith position. + for (; i < LEN; i++) { + skipped = r1.try_skip(i); + ASSERT_EQ(i, skipped); + const int x = r1.next_uint(); + const int y = ints[i]; + ASSERT_EQ(x, y) << i; + r1.set_position(0); + } + // Perform incremental skips. + int skipped_total = 0; + while (true) { + const int skip = skipped_total % 9; + skipped = r1.try_skip(skip); + skipped_total += skipped; + if (skipped_total > LEN - 1) { + r1.set_position(0); + break; + } + ASSERT_EQ(skip, skipped); + const int x = r1.next_uint(); + const int y = ints[skipped_total]; + ASSERT_EQ(x, y) << skipped_total; + // Update skipped_total because reader + // moved one position when reading x. + ++skipped_total; + } + // Underflow. + skipped = r1.try_skip(-1); + ASSERT_EQ(0, skipped) << skipped; + // Overflow. + skipped = r1.try_skip(LEN + 1); + ASSERT_EQ(LEN, skipped) << skipped; + } // End try_skip() test. + // copy from reader to writer UNSIGNED5::Reader r3(buf); int array_limit = 1; diff --git a/test/hotspot/jtreg/ProblemList.txt b/test/hotspot/jtreg/ProblemList.txt index 14c7017052f0..8c86d6aaddec 100644 --- a/test/hotspot/jtreg/ProblemList.txt +++ b/test/hotspot/jtreg/ProblemList.txt @@ -47,8 +47,8 @@ compiler/compilercontrol/jcmd/ClearDirectivesFileStackTest.java 8225370 generic- compiler/cpuflags/TestAESIntrinsicsOnSupportedConfig.java 8190680 generic-all -compiler/runtime/Test8168712.java#with-dtrace 8211769,8211771 generic-ppc64,generic-ppc64le,linux-s390x -compiler/runtime/Test8168712.java#without-dtrace 8211769,8211771 generic-ppc64,generic-ppc64le,linux-s390x +compiler/runtime/Test8168712.java#with-dtrace 8211771 linux-s390x +compiler/runtime/Test8168712.java#without-dtrace 8211771 linux-s390x compiler/runtime/Test7196199.java 8365196 windows-x64 compiler/c2/Test8004741.java 8235801 generic-all @@ -62,6 +62,8 @@ compiler/vectorization/TestVectorAlgorithms.java#noSuperWord 8376803 aix-ppc64,l compiler/vectorization/TestVectorAlgorithms.java#vanilla 8376803 aix-ppc64,linux-s390x compiler/vectorization/TestVectorAlgorithms.java#noOptimizeFill 8376803 aix-ppc64,linux-s390x +compiler/vectorapi/TestVectorLongToMaskNodeIdealization.java 8384428 generic-x64 + compiler/jvmci/TestUncaughtErrorInCompileMethod.java 8309073 generic-all compiler/jvmci/jdk.vm.ci.code.test/src/jdk/vm/ci/code/test/DataPatchTest.java 8331704 linux-riscv64 compiler/jvmci/jdk.vm.ci.code.test/src/jdk/vm/ci/code/test/MaxOopMapStackOffsetTest.java 8331704 linux-riscv64 @@ -115,6 +117,7 @@ runtime/os/TestTracePageSizes.java#Serial 8267460 linux-aarch64 runtime/StackGuardPages/TestStackGuardPagesNative.java 8303612 linux-all runtime/ErrorHandling/MachCodeFramesInErrorFile.java 8313315 linux-ppc64le runtime/NMT/VirtualAllocCommitMerge.java 8309698 linux-s390x +runtime/Thread/TestAlwaysPreTouchStacks.java 8383372 macosx-aarch64 applications/ctw/modules/jdk_jfr.java 8286300 linux-s390x applications/jcstress/copy.java 8229852 linux-all diff --git a/test/hotspot/jtreg/TEST.groups b/test/hotspot/jtreg/TEST.groups index 6623676d2ba3..a1aff1654458 100644 --- a/test/hotspot/jtreg/TEST.groups +++ b/test/hotspot/jtreg/TEST.groups @@ -73,7 +73,8 @@ hotspot_misc = \ -:hotspot_gc \ -:hotspot_runtime \ -:hotspot_serviceability \ - -:hotspot_containers + -:hotspot_containers \ + -:hotspot_resourcehogs hotspot_native_sanity = \ native_sanity diff --git a/test/hotspot/jtreg/compiler/arguments/CheckCICompilerCount.java b/test/hotspot/jtreg/compiler/arguments/CheckCICompilerCount.java index 6f137e20261c..ef9389542c40 100644 --- a/test/hotspot/jtreg/compiler/arguments/CheckCICompilerCount.java +++ b/test/hotspot/jtreg/compiler/arguments/CheckCICompilerCount.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2023, 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 @@ -23,7 +23,7 @@ /* * @test CheckCheckCICompilerCount - * @bug 8130858 8132525 8162881 + * @bug 8130858 8132525 8162881 8379396 * @summary Check that correct range of values for CICompilerCount are allowed depending on whether tiered is enabled or not * @library /test/lib / * @requires vm.flagless @@ -186,7 +186,25 @@ public class CheckCICompilerCount { 0 }; - private static void verifyValidOption(String[] arguments, String expected_output, int exit, boolean tiered) throws Exception { + private static final String[][] INVALID_ARGUMENTS = { + { + "-XX:CICompilerCount=100M", + "-version" + }, + }; + + private static final String[] INVALID_EXPECTED_OUTPUTS = { + // "CICompilerCount is too large" is a common prefix for two different messages: + // - product build: flag constraint fires early: "CICompilerCount is too large for current active processor count N" + // - debug build: CodeCache overflow guard fires: "CICompilerCount is too large: compiler buffer size exceeds the CodeCache size limit" + "CICompilerCount is too large" + }; + + private static final int[] INVALID_EXIT = { + 1, + }; + + private static void verifyOptionBehavior(String[] arguments, String expected_output, int exit, boolean tiered) throws Exception { ProcessBuilder pb; OutputAnalyzer out; @@ -215,11 +233,15 @@ public static void main(String[] args) throws Exception { } for (int i = 0; i < NON_TIERED_ARGUMENTS.length; i++) { - verifyValidOption(NON_TIERED_ARGUMENTS[i], NON_TIERED_EXPECTED_OUTPUTS[i], NON_TIERED_EXIT[i], false); + verifyOptionBehavior(NON_TIERED_ARGUMENTS[i], NON_TIERED_EXPECTED_OUTPUTS[i], NON_TIERED_EXIT[i], false); } for (int i = 0; i < TIERED_ARGUMENTS.length; i++) { - verifyValidOption(TIERED_ARGUMENTS[i], TIERED_EXPECTED_OUTPUTS[i], TIERED_EXIT[i], true); + verifyOptionBehavior(TIERED_ARGUMENTS[i], TIERED_EXPECTED_OUTPUTS[i], TIERED_EXIT[i], true); + } + + for (int i = 0; i < INVALID_ARGUMENTS.length; i++) { + verifyOptionBehavior(INVALID_ARGUMENTS[i], INVALID_EXPECTED_OUTPUTS[i], INVALID_EXIT[i], true); } } } diff --git a/test/hotspot/jtreg/compiler/arguments/TestUseSSE42IntrinsicsWithLowLevelSSE.java b/test/hotspot/jtreg/compiler/arguments/TestUseSSE42IntrinsicsWithLowLevelSSE.java index 27886fb76947..3f4fffe387aa 100644 --- a/test/hotspot/jtreg/compiler/arguments/TestUseSSE42IntrinsicsWithLowLevelSSE.java +++ b/test/hotspot/jtreg/compiler/arguments/TestUseSSE42IntrinsicsWithLowLevelSSE.java @@ -24,10 +24,10 @@ /** * @test * @bug 8358592 - * @summary Regression test for -XX:+UseSSE42Intrinsics -XX:UseSSE=1 crash + * @summary Regression test for -XX:+UseSSE42Intrinsics -XX:UseSSE=2 crash * @requires os.arch=="amd64" | os.arch=="x86_64" * @requires vm.debug - * @run main/othervm -XX:+UseSSE42Intrinsics -XX:UseSSE=1 compiler.arguments.TestUseSSE42IntrinsicsWithLowLevelSSE + * @run main/othervm -XX:+UseSSE42Intrinsics -XX:UseSSE=2 compiler.arguments.TestUseSSE42IntrinsicsWithLowLevelSSE */ package compiler.arguments; @@ -36,4 +36,4 @@ public class TestUseSSE42IntrinsicsWithLowLevelSSE { public static void main(String[] args) { System.out.println("passed"); } -} \ No newline at end of file +} diff --git a/test/hotspot/jtreg/compiler/arraycopy/stress/TestStressArrayCopy.java b/test/hotspot/jtreg/compiler/arraycopy/stress/TestStressArrayCopy.java index 8c410c77e5fc..07ad4113d173 100644 --- a/test/hotspot/jtreg/compiler/arraycopy/stress/TestStressArrayCopy.java +++ b/test/hotspot/jtreg/compiler/arraycopy/stress/TestStressArrayCopy.java @@ -126,16 +126,6 @@ public static void main(String... args) throws Exception { configs.add(List.of("-XX:UseAVX=0", "-XX:UseSSE=2")); } - // x86_64 always has UseSSE >= 2. These lower configurations only - // make sense for x86_32. - if (Platform.isX86()) { - if (containsFuzzy(cpuFeatures, "sse")) { - configs.add(List.of("-XX:UseAVX=0", "-XX:UseSSE=1")); - } - - configs.add(List.of("-XX:UseAVX=0", "-XX:UseSSE=0")); - } - // Alternate configs with other flags if (Platform.isX64()) { configs = alternate(configs, "UseCompressedOops"); diff --git a/test/hotspot/jtreg/compiler/c1/Test6579789.java b/test/hotspot/jtreg/compiler/c1/Test6579789.java index 58edf3a96107..fc2390ea6694 100644 --- a/test/hotspot/jtreg/compiler/c1/Test6579789.java +++ b/test/hotspot/jtreg/compiler/c1/Test6579789.java @@ -26,7 +26,7 @@ * @bug 6579789 * @summary Internal error "c1_LinearScan.cpp:1429 Error: assert(false,"")" in debuggee with fastdebug VM * - * @run main/othervm -Xcomp -XX:+IgnoreUnrecognizedVMOptions -XX:UseSSE=0 + * @run main/othervm -Xcomp -XX:+IgnoreUnrecognizedVMOptions -XX:UseSSE=2 * -XX:CompileCommand=compileonly,compiler.c1.Test6579789::bug * compiler.c1.Test6579789 */ diff --git a/test/hotspot/jtreg/compiler/c1/Test6855215.java b/test/hotspot/jtreg/compiler/c1/Test6855215.java index 6f97cda3be9b..c934df820bae 100644 --- a/test/hotspot/jtreg/compiler/c1/Test6855215.java +++ b/test/hotspot/jtreg/compiler/c1/Test6855215.java @@ -26,7 +26,7 @@ * @bug 6855215 * @summary Calculation error (NaN) after about 1500 calculations * - * @run main/othervm -Xbatch -XX:+IgnoreUnrecognizedVMOptions -XX:UseSSE=0 compiler.c1.Test6855215 + * @run main/othervm -Xbatch -XX:+IgnoreUnrecognizedVMOptions -XX:UseSSE=2 compiler.c1.Test6855215 */ package compiler.c1; diff --git a/test/hotspot/jtreg/compiler/c2/TestDependsOnTestSqrtHFAssertion.java b/test/hotspot/jtreg/compiler/c2/TestDependsOnTestSqrtHFAssertion.java index 5ad9a9270971..f5f6107ff0c7 100644 --- a/test/hotspot/jtreg/compiler/c2/TestDependsOnTestSqrtHFAssertion.java +++ b/test/hotspot/jtreg/compiler/c2/TestDependsOnTestSqrtHFAssertion.java @@ -28,7 +28,7 @@ * @library /test/lib / * @modules jdk.incubator.vector * @requires vm.debug & vm.compiler2.enabled - * @run main/othervm --add-modules=jdk.incubator.vector compiler.c2.TestDependsOnTestSqrtHFAssertion + * @run main/othervm --add-modules=jdk.incubator.vector -Xbatch compiler.c2.TestDependsOnTestSqrtHFAssertion */ package compiler.c2; @@ -54,15 +54,15 @@ public static int micro(int x1, int x2, int y, int ctr) { } } } - } + } } return res; } public static void main(String [] args) { int res = 0; - for (int i = 0 ; i < 100000; i++) { - res += micro(x1, x2, y, i); + for (int i = 0 ; i < 10000; i++) { + res += micro(x1, x2, y, i % 100); } IO.println("PASS" + res); } diff --git a/test/hotspot/jtreg/compiler/c2/irTests/TestAutoVectorization2DArray.java b/test/hotspot/jtreg/compiler/c2/irTests/TestAutoVectorization2DArray.java index 5b1d6f51bb35..4520f45a47a7 100644 --- a/test/hotspot/jtreg/compiler/c2/irTests/TestAutoVectorization2DArray.java +++ b/test/hotspot/jtreg/compiler/c2/irTests/TestAutoVectorization2DArray.java @@ -29,8 +29,7 @@ * @test * @bug 8279258 * @summary Auto-vectorization enhancement for two-dimensional array operations - * @requires (os.arch != "x86" & os.arch != "i386" & os.arch != "ppc64" & os.arch != "ppc64le" & os.arch != "riscv64") - * | ((os.arch == "x86" | os.arch == "i386") & (vm.opt.UseSSE == "null" | vm.opt.UseSSE >= 2)) + * @requires (os.arch != "ppc64" & os.arch != "ppc64le" & os.arch != "riscv64") * | ((os.arch == "ppc64" | os.arch == "ppc64le") & vm.cpu.features ~= ".*darn.*") * | (os.arch == "riscv64" & vm.cpu.features ~= ".*rvv.*") * @library /test/lib / diff --git a/test/hotspot/jtreg/compiler/compilercontrol/commands/CompileLevelParseTest.java b/test/hotspot/jtreg/compiler/compilercontrol/commands/CompileLevelParseTest.java new file mode 100644 index 000000000000..abb54ba10745 --- /dev/null +++ b/test/hotspot/jtreg/compiler/compilercontrol/commands/CompileLevelParseTest.java @@ -0,0 +1,69 @@ +/* + * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +/* + * @test + * @bug 8313713 + * @summary Test if the following CompileCommand options support compilation + * level bitmask argument: break, compileonly, exclude, print + * @library /test/lib + * @run main ${test.main.class} + */ + +package compiler.compilercontrol.commands; + +import jdk.test.lib.process.ProcessTools; + +import java.util.List; + +public class CompileLevelParseTest { + private static final List commandsWithCompileLevel = List.of("break", "compileonly", "exclude", "print"); + private static final List compLevels = List.of("0", "1", "11", "111", "10", "100", "101", "1000", "1111"); + private static final List invalidCompLevels = List.of("-9223372036854775808", "-1", "-1111", "10000", "2", "20000", "01012", + "91", "9", "c1", "true", "false"); + private static final String DEFAULT_COMP_LEVEL = "1111"; + private static final String METHOD_EXP = "java/lang/Object.toString"; + + public static void main(String[] args) throws Exception { + for (String cmd : commandsWithCompileLevel) { + ProcessTools.executeTestJava("-XX:CompileCommand=" + cmd + "," + METHOD_EXP, "-version") + .shouldHaveExitValue(0) + .shouldNotContain("CompileCommand: An error occurred during parsing") + .shouldContain("CompileCommand: " + cmd + " " + METHOD_EXP + " intx " + cmd + " = " + DEFAULT_COMP_LEVEL); // should be registered + for (String level : compLevels) { + ProcessTools.executeTestJava("-XX:CompileCommand=" + cmd + "," + METHOD_EXP + "," + level, "-version") + .shouldHaveExitValue(0) + .shouldNotContain("CompileCommand: An error occurred during parsing") + .shouldContain("CompileCommand: " + cmd + " " + METHOD_EXP + " intx " + cmd + " = " + level); // should be registered + } + // Note that values like "1suffix" are still accepted + for (String incorrectLevel : invalidCompLevels) { + ProcessTools.executeTestJava("-XX:CompileCommand=" + cmd + "," + METHOD_EXP + "," +incorrectLevel, "-version") + .shouldHaveExitValue(1) + .shouldContain("CompileCommand: An error occurred during parsing") + .shouldNotContain("CompileCommand: " + cmd + " " + METHOD_EXP + " intx " + cmd + " = " + incorrectLevel); + } + } + } +} diff --git a/test/hotspot/jtreg/compiler/compilercontrol/commands/CompileLevelPrintTest.java b/test/hotspot/jtreg/compiler/compilercontrol/commands/CompileLevelPrintTest.java new file mode 100644 index 000000000000..dcf625c807b3 --- /dev/null +++ b/test/hotspot/jtreg/compiler/compilercontrol/commands/CompileLevelPrintTest.java @@ -0,0 +1,514 @@ +/* + * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +/* + * @test + * @bug 8313713 + * @summary Test -XX:CompileCommand=exclude and compileonly with different compilation levels, + * monitoring compilation events in VM -XX:+PrintCompilation and -XX:+PrintTieredEvents output + * @requires vm.compMode != "Xint" & vm.flavor == "server" + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @run main ${test.main.class} runner + */ + +package compiler.compilercontrol.commands; + +import jdk.test.lib.Asserts; +import jdk.test.lib.management.InputArguments; +import jdk.test.lib.process.ProcessTools; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.io.Writer; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.BooleanSupplier; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class CompileLevelPrintTest { + static final Method TEST_METHOD; + + static { + try { + TEST_METHOD = Testee.class.getDeclaredMethod("compiledMethod", new Class[] {int.class}); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + static final String TEST_METHOD_NAME_DOT = TEST_METHOD.getDeclaringClass().getName().replace('.', '/') + + "." + TEST_METHOD.getName(); + static final String TEST_METHOD_NAME_DBL_COLON = TEST_METHOD.getDeclaringClass().getName() + + "::" + TEST_METHOD.getName(); + static final String TEST_METHOD_SIGNATURE = TEST_METHOD_NAME_DBL_COLON + "("; + + static final String TESTEE_WAITING_FOR_START_CMD = "==> waiting for start command"; + + static final String START_CMD = "start"; + static final String STOP_CMD = "stop"; + + static final boolean DEBUG_OUTPUT = false; + + static int TIMEOUT_SEC = 30; + + static class TesteeState { + final CountDownLatch waitingForStartTest = new CountDownLatch(1); + final AtomicInteger compiler1QueueSize = new AtomicInteger(); + final AtomicInteger compiler2QueueSize = new AtomicInteger(0); + final Set compileCommandsReported = Collections.synchronizedSet(new HashSet<>()); + volatile Set testMethodCompiledAtLevel = Collections.synchronizedSet(new HashSet<>()); + final Set testMethodExcludedAtLevel = Collections.synchronizedSet(new HashSet<>()); + volatile Set testMethodPrintedAtLevel = Collections.synchronizedSet(new HashSet<>()); + volatile boolean testMethodMDOPrinted = false; + + @Override + public String toString() { + return "TesteeState{" + + "\n compileCommandsReported=" + compileCommandsReported + + "\n testMethodCompiledAtLevel=" + testMethodCompiledAtLevel + + "\n testMethodExcludedAtLevel=" + testMethodExcludedAtLevel + + "\n testMethodPrintedAtLevel=" + testMethodPrintedAtLevel + + "\n testMethodMDOPrinted=" + testMethodMDOPrinted + + "\n}"; + } + } + + public static void main(String[] args) throws IOException, InterruptedException, ExecutionException { + // Use the same launcher to avoid double launch cost compared to multiple jtreg @test annotations + if (args.length > 0 && "runner".equals(args[0])) { + if (Arrays.asList(InputArguments.getVmInputArgs()).contains("-Xcomp")) { + TIMEOUT_SEC *= 3; + } + + if (Arrays.asList(InputArguments.getVmInputArgs()).contains("-XX:-TieredCompilation")) { + // If we have -XX:-TieredCompilation, we check only for C2 compilation + // A space is printed instead of compile level + Runner.run("compileonly", 1, 1, 1, Set.of(), Set.of("4"), true, false); + Runner.run("compileonly", 10, 1, 1, Set.of(), Set.of("4"), true, false); + Runner.run("compileonly", 100, 1, 1, Set.of(), Set.of("4"), true, false); + Runner.run("compileonly", 1000, 1000, 4, Set.of(" "), Set.of(), true, false); + Runner.run("compileonly", 1100, 1100, 4, Set.of(" "), Set.of(), true, false); + + Runner.run("exclude", 1110, 1, 1, Set.of(), Set.of("4"), true, false); + Runner.run("exclude", 1101, 10, 2, Set.of(), Set.of("4"), true, false); + Runner.run("exclude", 1011, 100, 3, Set.of(), Set.of("4"), true, false); + Runner.run("exclude", 111, 1000, 4, Set.of(" "), Set.of(), true, false); + } else { + // -XX:+TieredCompilation + Runner.run("compileonly", 1, 1, 1, Set.of("1"), Set.of(), false, true); + Runner.run("compileonly", 10, 10, 2, Set.of("2"), Set.of(), true, true); + Runner.run("compileonly", 100, 100, 3, Set.of("3"), Set.of(), true, true); + Runner.run("compileonly", 1000, 1000, 4, Set.of("4"), Set.of("3"), true, true); + Runner.run("compileonly", 1100, 1100, 4, Set.of("3", "4"), Set.of(), true, true); + + Runner.run("exclude", 1110, 1, 1, Set.of("1"), Set.of(), false, true); + Runner.run("exclude", 1101, 10, 2, Set.of("2"), Set.of(), true, true); + Runner.run("exclude", 1011, 100, 3, Set.of("3"), Set.of(), true, true); + Runner.run("exclude", 111, 1000, 4, Set.of("4"), Set.of("3"), true, true); + } + } else if (args.length > 1 && "parse-logs".equals(args[0])) { + // For test troubleshooting: if test has failed due to regexp matching, + // try parsing testee-.out and hotspot_pid.log and adjust the patterns + TesteeState testeeState = new TesteeState(); + for (int i = 1; i < args.length; i++) { + Runner.matchMessagesInHotspotLog(args[i], testeeState); + } + IO.println(testeeState.toString()); + } else { + Testee.run(); + } + } + + static class Runner { + private static final int LAST_N_LINES_COUNT = 5; + + private static final Pattern reCompileCommand = Pattern.compile( + "CompileCommand: (.*)"); + private static final Pattern reTieredEvent = Pattern.compile( + "[0-9.]+: \\[(call|loop|compile|force-compile|remove-from-queue|update-in-queue|reprofile|make-not-entrant) " + + "level=\\d \\[([^]]+)] @-?\\d+ queues=(\\d+),(\\d+).*]"); + private static final Pattern reCompilation = Pattern.compile( + "(\\d+) (C1|C2|no compiler): *(\\d+) ([ %][ s][ !][ b][ n]) ([-0-4 ]) +([^ ]+).*"); + private static final Pattern reExcludeCompile = Pattern.compile( + ".*made not compilable on level (\\d) +([^ ]+) .* excluded by CompileCommand"); + private static final Pattern reCompiledMethod = Pattern.compile( + ".*-{35} Assembly -{35}\\n(?:\\[[0-9.]+s]\\[warning]\\[os] Loading hsdis library failed\\n)?\\nCompiled method \\((?:c1|c2)\\) (\\d+) ([Cc][12]): *" + + "(\\d+) ([ %][ s][ !][ b][ n]) ([-0-4 ]) +([^ ]+) +(@ -?[0-9]+ +)?\\(\\d+ bytes\\)", Pattern.DOTALL); + private static final Pattern reMethodData = Pattern.compile( + ".*-{72}\\nstatic ([^\\n]+)\\n *interpreter_invocation_count: *\\d+\\n *invocation_counter: *\\d+", Pattern.DOTALL); + private static final Pattern reEndOfLog = Pattern.compile(""); + + public static void run(String compileCmd, + int cmdCompLevel, + int printCmdCompLevel, + int tieredStopAtLevel, + Set expectedCompLevel, + Set expectExcludedAtLevels, + boolean expectMDOPrinted, + boolean tieredCompilation) + throws IOException, InterruptedException { + + IO.println("\n########> Testing " + compileCmd + " " + cmdCompLevel); + + ProcessBuilder pb = ProcessTools.createTestJavaProcessBuilder( + "-XX:+UnlockDiagnosticVMOptions", + "-XX:+PrintCompilation", + "-XX:+CIPrintCompilerName", + "-XX:+PrintTieredEvents", + "-XX:+LogVMOutput", + "-XX:+LogCompilation", + "-XX:" + (tieredCompilation ? "+" : "-") + "TieredCompilation", + "-XX:TieredStopAtLevel=" + tieredStopAtLevel, + "-XX:CompileCommand=" + compileCmd + "," + TEST_METHOD_NAME_DBL_COLON + "," + cmdCompLevel, + "-XX:CompileCommand=print," + TEST_METHOD_NAME_DBL_COLON + "," + printCmdCompLevel, + CompileLevelPrintTest.class.getName()); + + try (Process process = pb.start(); + BufferedWriter processInput = process.outputWriter(); + BufferedReader processOutput = process.inputReader(); + BufferedReader processErrOut = process.errorReader()) { + long startNanos = System.nanoTime(); + try { + IO.println("##> Testee PID: " + process.pid()); + TesteeState testeeState = new TesteeState(); + + Thread stdoutParser = startDaemonThread(() -> + matchVmMessages(processOutput, testeeState, "", "testee-" + process.pid() + ".out")); + Thread stderrParser = startDaemonThread(() -> + matchTesteeMessages(processErrOut, testeeState, "testee-" + process.pid() + ".err")); + + IO.println("##> Waiting for testee to get ready for the start command"); + if (!testeeState.waitingForStartTest.await(TIMEOUT_SEC, TimeUnit.SECONDS)) { + throw new RuntimeException("No start signal from testee"); + } + + Asserts.assertTrue(waitUntil(() -> !process.isAlive() + || (testeeState.compiler1QueueSize.get() < 5 + && testeeState.compiler2QueueSize.get() < 5)), + "Compiler queue is still not empty"); + Asserts.assertTrue(testeeState.compileCommandsReported.contains( + compileCmd + " " + TEST_METHOD_NAME_DOT + " intx " + compileCmd + " = " + cmdCompLevel), + "'CompileCommand: " + compileCmd + "...' was not printed"); + Asserts.assertTrue(testeeState.compileCommandsReported.contains( + "print " + TEST_METHOD_NAME_DOT + " intx print = " + printCmdCompLevel), + "'CompileCommand: print ...' was not printed"); + + IO.println("##> Order testee to start"); + processInput.write(START_CMD); processInput.newLine(); processInput.flush(); + + waitUntil(() -> !process.isAlive() + || (!expectedCompLevel.isEmpty() && !expectExcludedAtLevels.isEmpty() + && expectedCompLevel.equals(testeeState.testMethodCompiledAtLevel) + && expectedCompLevel.equals(testeeState.testMethodPrintedAtLevel) + && expectExcludedAtLevels.equals(testeeState.testMethodExcludedAtLevel))); + + if (process.isAlive()) { + IO.println("##> Required messages have been found in testee output, now stop it"); + processInput.write(STOP_CMD + "\n"); + processInput.flush(); + processInput.close(); + } + + Asserts.assertEquals(0, process.waitFor()); + stdoutParser.join(); + stderrParser.join(); + + // Process stdout can be garbled: pieces of different messages can be intertwined and regexps may + // intermittently fail to match the messages. + // Now parse Hotspot log file to re-match them. Duplicates are OK. + matchMessagesInHotspotLog("hotspot_pid" + process.pid() + ".log", testeeState); + + Asserts.assertEquals(expectedCompLevel, testeeState.testMethodCompiledAtLevel, + "Test method was not compiled at required level (" + expectedCompLevel + ")"); + Asserts.assertEquals(expectedCompLevel, testeeState.testMethodPrintedAtLevel, + "Test method assembly was not printed at required level (" + expectedCompLevel + ")"); + Asserts.assertEquals(expectExcludedAtLevels, testeeState.testMethodExcludedAtLevel, + "Test method compilation was not excluded at required levels (" + expectExcludedAtLevels + ")"); + Asserts.assertEquals(expectMDOPrinted, testeeState.testMethodMDOPrinted, + "Test method MDO was" + (expectMDOPrinted ? " NOT" : "") + " printed"); + + IO.println("########> Test passed"); + } catch (Exception ex) { + IO.println("########> Test failed"); + ex.printStackTrace(); + process.destroyForcibly(); + throw ex; + } finally { + IO.println("########> Elapsed " + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNanos) + " ms"); + } + } + } + + static void matchMessagesInHotspotLog(String logFileName, TesteeState testeeState) throws IOException { + IO.println("##> Parsing " + logFileName + " to match possibly missed messages"); + try (BufferedReader reader = new BufferedReader(new FileReader(logFileName))) { + matchVmMessages(reader, testeeState, "Log: ", null); + } + } + + private static void matchVmMessages(BufferedReader testeeOutput, TesteeState testeeState, String logPrefix, String fileName) { + try (Writer outWriter = fileName != null ? new BufferedWriter(new FileWriter(fileName)) : null) { + String line; + LinkedList lastNLines = new LinkedList<>(); + boolean endOfLog = false; + + while (!endOfLog && (line = testeeOutput.readLine()) != null) { + if (outWriter != null) { + outWriter.write(line); + outWriter.write('\n'); + } + + line = line.trim(); + + lastNLines.addLast(line); + while (lastNLines.size() > LAST_N_LINES_COUNT) { + lastNLines.removeFirst(); + } + String lastNLinesStr = String.join("\n", lastNLines); + + Matcher matcher; + String msg = ""; + + if ((matcher = reCompileCommand.matcher(line)).matches()) { + testeeState.compileCommandsReported.add(matcher.group(1)); + + msg = "Compile command reported: " + matcher.group(1); + + } else if ((matcher = reTieredEvent.matcher(line)).matches()) { + testeeState.compiler1QueueSize.set(Integer.parseInt(matcher.group(3))); + testeeState.compiler2QueueSize.set(Integer.parseInt(matcher.group(4))); + + } else if ((matcher = reCompilation.matcher(line)).matches()) { + if ("C1".equalsIgnoreCase(matcher.group(2))) { + testeeState.compiler1QueueSize.decrementAndGet(); + } else { + testeeState.compiler2QueueSize.decrementAndGet(); + } + + if (matcher.group(6).contains(TEST_METHOD_NAME_DBL_COLON)) { + testeeState.testMethodCompiledAtLevel.add(matcher.group(5)); + + msg = "Test method compiled:" + + " compiler=" + matcher.group(2) + + " level=" + matcher.group(5) + + " compilation#=" + matcher.group(3) + + " flags=" + matcher.group(4).trim() + + " name=" + matcher.group(6); + } + } else if ((matcher = reCompiledMethod.matcher(lastNLinesStr)).matches()) { + if (matcher.group(6).contains(TEST_METHOD_NAME_DBL_COLON)) { + testeeState.testMethodPrintedAtLevel.add(matcher.group(5)); + + msg = "Test method assembly printed:" + + " compiler=" + matcher.group(2) + + " level=" + matcher.group(5) + + " compilation#=" + matcher.group(3) + + " flags=" + matcher.group(4).trim() + + " name=" + matcher.group(6) + + " bci=" + (matcher.group(7) != null ? matcher.group(7).trim() : ""); + } + } else if ((matcher = reExcludeCompile.matcher(line)).matches()) { + if (matcher.group(2).contains(TEST_METHOD_NAME_DBL_COLON)) { + testeeState.testMethodExcludedAtLevel.add(matcher.group(1)); + + msg = "Test method not compilable:" + + " level=" + matcher.group(1) + + " name=" + matcher.group(2); + } + } else if ((matcher = reMethodData.matcher(lastNLinesStr)).matches()) { + if (matcher.group(1).contains(TEST_METHOD_SIGNATURE)) { + testeeState.testMethodMDOPrinted = true; + + msg = "Test method data:" + + " name=" + matcher.group(1); + } + } else if (reEndOfLog.matcher(line).matches()) { + endOfLog = true; + + msg = "End of log"; + } + + if (!msg.isEmpty()) { + msg = "##> " + logPrefix + msg; + IO.println(msg); + if (outWriter != null) { + outWriter.write(msg); + outWriter.write('\n'); + } + } + } + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + private static void matchTesteeMessages(BufferedReader testeeErrorOutput, TesteeState testeeState, String fileName) { + try (BufferedWriter outWriter = new BufferedWriter(new FileWriter(fileName))) { + String line; + while ((line = testeeErrorOutput.readLine()) != null) { + outWriter.write(line); + outWriter.newLine(); + + line = line.trim(); + + if (TESTEE_WAITING_FOR_START_CMD.equals(line)) { + IO.println("##> Testee is waiting for start command"); + testeeState.waitingForStartTest.countDown(); + } else if (line.startsWith("==>")) { + IO.println(line); + } else if (line.startsWith("Exception in thread ") || line.startsWith("at ")) { + IO.println("==>" + line); + } else if (DEBUG_OUTPUT) { + IO.println("Did not parse stderr: " + line); + } + } + } catch (Exception ex) { + ex.printStackTrace(); + } + } + } + + static class Testee { + private static final CountDownLatch startCmd = new CountDownLatch(1); + private static final CountDownLatch stopCmd = new CountDownLatch(1); + + static void run() throws IOException, InterruptedException { + System.err.println("==> entering testee()"); + + try { + startDaemonThread(Testee::inputMonitor); + + if (stopCmd.getCount() == 0) { + return; + } + + // Print 3 times, since the output can be intermixed with + System.err.println(TESTEE_WAITING_FOR_START_CMD); + if (!startCmd.await(TIMEOUT_SEC + 1, TimeUnit.SECONDS)) { + System.err.println("==> 'start' command was not given in stdin"); + return; + } + + if (stopCmd.getCount() == 0) { + return; + } + + System.err.println("==> starting test"); + runTestCode(); + } finally { + System.err.println("==> exiting testee()"); + } + } + + private static void inputMonitor() { + try (BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in))) { + String line; + while ((line = stdin.readLine()) != null) { + line = line.trim(); + System.err.println("==> STDIN: " + line); + + switch (line) { + case START_CMD: + startCmd.countDown(); + break; + + case STOP_CMD: + stopCmd.countDown(); + break; + + default: + System.err.println("==> ERROR: unknown command"); + } + } + } catch (Exception ex) { + ex.printStackTrace(); + } + } + + // For Tier4 600 invocation of this method with avg 25 loops for each should be enough + // to trigger Tier4CompilationThreshold=15000 + private static void compiledMethod(final int a) { + int r = 0; + for (int i = 0; i < a % 50; i++) { + r ^= i; + } + if (r == 42) { + System.err.println("MAGIC!"); + } + } + + private static boolean longLoop() { + // To trigger compilation, 100-200 should be enough for C1 and 600-700 for C2 + for (int i = 0; i < 10000; i++) { + compiledMethod(i); + if ((i & 0xf) == 0 && stopCmd.getCount() == 0) { + System.err.println("==> Bailing out of compiledMethod() at iteration " + i); + return true; + } + } + return false; + } + + private static void runTestCode() { + for (int i = 0; i < 100; i++) { + if (longLoop()) { + return; + } + } + } + } + + static Thread startDaemonThread(Runnable code) { + Thread t = new Thread(code); + t.setDaemon(true); + t.start(); + return t; + } + + static boolean waitUntil(BooleanSupplier condition) throws InterruptedException { + for (int maxWait = TIMEOUT_SEC * 5; maxWait > 0; --maxWait) { + if (condition.getAsBoolean()) { + return true; + } + Thread.sleep(200); + } + return false; + } +} diff --git a/test/hotspot/jtreg/compiler/compilercontrol/commands/CompileLevelWBTest.java b/test/hotspot/jtreg/compiler/compilercontrol/commands/CompileLevelWBTest.java new file mode 100644 index 000000000000..81c72990107c --- /dev/null +++ b/test/hotspot/jtreg/compiler/compilercontrol/commands/CompileLevelWBTest.java @@ -0,0 +1,1088 @@ +/* + * Copyright Amazon.com Inc. or its affiliates. All Rights Reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + * + */ + +/* + * @test id=exclude-all-levels + * @bug 8313713 + * @summary Test -XX:CompileCommand=exclude with different compilation levels + * @requires vm.compMode != "Xint" & vm.flavor == "server" + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=exclude,*.CompileLevelWBTest::compiledMethod + * ${test.main.class} + */ + +/* + * @test id=exclude-mask-1-mixed + * @bug 8313713 + * @summary Test -XX:CompileCommand=exclude with different compilation levels + * @requires vm.compMode == "Xmixed" & vm.flavor == "server" & vm.opt.TieredCompilation != false + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=exclude,*.CompileLevelWBTest::compiledMethod,1 + * ${test.main.class} + * 2 3 4 + */ + +/* + * @test id=exclude-mask-1-comp + * @bug 8313713 + * @summary Test -XX:CompileCommand=exclude with different compilation levels + * @requires vm.compMode == "Xcomp" & vm.flavor == "server" & vm.opt.TieredCompilation != false + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=exclude,*.CompileLevelWBTest::compiledMethod,1 + * ${test.main.class} + * 2 3/4 4 + */ + +/* + * @test id=exclude-mask-1-no-tiered + * @bug 8313713 + * @summary Test -XX:CompileCommand=exclude with different compilation levels + * @requires vm.compMode == "Xmixed" & vm.flavor == "server" & vm.opt.TieredCompilation == false + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=exclude,*.CompileLevelWBTest::compiledMethod,1 + * ${test.main.class} + * 4 + */ + +/* + * @test id=exclude-mask-2-mixed + * @bug 8313713 + * @summary Test -XX:CompileCommand=exclude with different compilation levels + * @requires vm.compMode == "Xmixed" & vm.flavor == "server" & vm.opt.TieredCompilation != false + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=exclude,*.CompileLevelWBTest::compiledMethod,10 + * ${test.main.class} + * 1 3 4 + */ + +/* + * @test id=exclude-mask-2-comp + * @bug 8313713 + * @summary Test -XX:CompileCommand=exclude with different compilation levels + * @requires vm.compMode == "Xcomp" & vm.flavor == "server" & vm.opt.TieredCompilation != false + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=exclude,*.CompileLevelWBTest::compiledMethod,10 + * ${test.main.class} + * 1 3/4 4 + */ + +/* + * @test id=exclude-mask-2-no-tiered + * @bug 8313713 + * @summary Test -XX:CompileCommand=exclude with different compilation levels + * @requires vm.compMode == "Xcomp" & vm.flavor == "server" & vm.opt.TieredCompilation == false + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=exclude,*.CompileLevelWBTest::compiledMethod,10 + * ${test.main.class} + * 4 + */ + +/* + * @test id=exclude-mask-3-mixed + * @bug 8313713 + * @summary Test -XX:CompileCommand=exclude with different compilation levels + * @requires vm.compMode == "Xmixed" & vm.flavor == "server" & vm.opt.TieredCompilation != false + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=exclude,*.CompileLevelWBTest::compiledMethod,11 + * ${test.main.class} + * 3 4 + */ + +/* + * @test id=exclude-mask-3-comp + * @bug 8313713 + * @summary Test -XX:CompileCommand=exclude with different compilation levels + * @requires vm.compMode == "Xcomp" & vm.flavor == "server" & vm.opt.TieredCompilation != false + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=exclude,*.CompileLevelWBTest::compiledMethod,11 + * ${test.main.class} + * 3/4 4 + */ + +/* + * @test id=exclude-mask-3-no-tiered + * @bug 8313713 + * @summary Test -XX:CompileCommand=exclude with different compilation levels + * @requires vm.compMode == "Xcomp" & vm.flavor == "server" & vm.opt.TieredCompilation == false + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=exclude,*.CompileLevelWBTest::compiledMethod,11 + * ${test.main.class} + * 4 + */ + +/* + * @test id=exclude-mask-4-tiered + * @bug 8313713 + * @summary Test -XX:CompileCommand=exclude with different compilation levels + * @requires vm.compMode != "Xint" & vm.flavor == "server" & vm.opt.TieredCompilation != false + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=exclude,*.CompileLevelWBTest::compiledMethod,100 + * ${test.main.class} + * 1 2 4 + */ + +/* + * @test id=exclude-mask-4-no-tiered + * @bug 8313713 + * @summary Test -XX:CompileCommand=exclude with different compilation levels + * @requires vm.compMode != "Xint" & vm.flavor == "server" & vm.opt.TieredCompilation == false + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=exclude,*.CompileLevelWBTest::compiledMethod,100 + * ${test.main.class} + * 4 + */ + +/* + * @test id=exclude-mask-5-tiered + * @bug 8313713 + * @summary Test -XX:CompileCommand=exclude with different compilation levels + * @requires vm.compMode != "Xint" & vm.flavor == "server" & vm.opt.TieredCompilation != false + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=exclude,*.CompileLevelWBTest::compiledMethod,101 + * ${test.main.class} + * 2 4 + */ + +/* + * @test id=exclude-mask-5-no-tiered + * @bug 8313713 + * @summary Test -XX:CompileCommand=exclude with different compilation levels + * @requires vm.compMode != "Xint" & vm.flavor == "server" & vm.opt.TieredCompilation == false + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=exclude,*.CompileLevelWBTest::compiledMethod,101 + * ${test.main.class} + * 4 + */ + +/* + * @test id=exclude-mask-6-tiered + * @bug 8313713 + * @summary Test -XX:CompileCommand=exclude with different compilation levels + * @requires vm.compMode != "Xint" & vm.flavor == "server" & vm.opt.TieredCompilation != false + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=exclude,*.CompileLevelWBTest::compiledMethod,110 + * ${test.main.class} + * 1 4 + */ + +/* + * @test id=exclude-mask-6-no-tiered + * @bug 8313713 + * @summary Test -XX:CompileCommand=exclude with different compilation levels + * @requires vm.compMode != "Xint" & vm.flavor == "server" & vm.opt.TieredCompilation == false + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=exclude,*.CompileLevelWBTest::compiledMethod,110 + * ${test.main.class} + * 4 + */ + +/* + * @test id=exclude-mask-7 + * @bug 8313713 + * @summary Test -XX:CompileCommand=exclude with different compilation levels + * @requires vm.compMode != "Xint" & vm.flavor == "server" + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=exclude,*.CompileLevelWBTest::compiledMethod,111 + * ${test.main.class} + * 4 + */ + +/* + * @test id=exclude-mask-8 + * @bug 8313713 + * @summary Test -XX:CompileCommand=exclude with different compilation levels + * @requires vm.compMode != "Xint" & vm.flavor == "server" & vm.opt.TieredCompilation != false + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=exclude,*.CompileLevelWBTest::compiledMethod,1000 + * ${test.main.class} + * 1 2 3 + */ + +/* + * @test id=exclude-mask-9 + * @bug 8313713 + * @summary Test -XX:CompileCommand=exclude with different compilation levels + * @requires vm.compMode != "Xint" & vm.flavor == "server" & vm.opt.TieredCompilation != false + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=exclude,*.CompileLevelWBTest::compiledMethod,1001 + * ${test.main.class} + * 2 3 + */ + +/* + * @test id=exclude-mask-10 + * @bug 8313713 + * @summary Test -XX:CompileCommand=exclude with different compilation levels + * @requires vm.compMode != "Xint" & vm.flavor == "server" & vm.opt.TieredCompilation != false + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=exclude,*.CompileLevelWBTest::compiledMethod,1010 + * ${test.main.class} + * 1 3 + */ + +/* + * @test id=exclude-mask-11 + * @bug 8313713 + * @summary Test -XX:CompileCommand=exclude with different compilation levels + * @requires vm.compMode != "Xint" & vm.flavor == "server" & vm.opt.TieredCompilation != false + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=exclude,*.CompileLevelWBTest::compiledMethod,1011 + * ${test.main.class} + * 3 + */ + +/* + * @test id=exclude-mask-12 + * @bug 8313713 + * @summary Test -XX:CompileCommand=exclude with different compilation levels + * @requires vm.compMode != "Xint" & vm.flavor == "server" & vm.opt.TieredCompilation != false + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=exclude,*.CompileLevelWBTest::compiledMethod,1100 + * ${test.main.class} + * 1 2 + */ + +/* + * @test id=exclude-mask-13 + * @bug 8313713 + * @summary Test -XX:CompileCommand=exclude with different compilation levels + * @requires vm.compMode != "Xint" & vm.flavor == "server" & vm.opt.TieredCompilation != false + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=exclude,*.CompileLevelWBTest::compiledMethod,1101 + * ${test.main.class} + * 2 + */ + +/* + * @test id=exclude-mask-14 + * @bug 8313713 + * @summary Test -XX:CompileCommand=exclude with different compilation levels + * @requires vm.compMode != "Xint" & vm.flavor == "server" & vm.opt.TieredCompilation != false + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=exclude,*.CompileLevelWBTest::compiledMethod,1110 + * ${test.main.class} + * 1 + */ + +/* + * @test id=exclude-mask-15 + * @bug 8313713 + * @summary Test -XX:CompileCommand=exclude with different compilation levels + * @requires vm.compMode != "Xint" & vm.flavor == "server" + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=exclude,*.CompileLevelWBTest::compiledMethod,1111 + * ${test.main.class} + */ + +/* + * @test id=compileonly-all-levels-mixed + * @bug 8313713 + * @summary Test -XX:CompileCommand=compileonly with different compilation levels + * @requires vm.compMode == "Xmixed" & vm.flavor == "server" & vm.opt.TieredCompilation != false + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=compileonly,*.CompileLevelWBTest::compiledMethod + * ${test.main.class} + * 1 2 3 4 + */ + +/* + * @test id=compileonly-all-levels-comp + * @bug 8313713 + * @summary Test -XX:CompileCommand=compileonly with different compilation levels + * @requires vm.compMode == "Xcomp" & vm.flavor == "server" & vm.opt.TieredCompilation != false + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=compileonly,*.CompileLevelWBTest::compiledMethod + * ${test.main.class} + * 1 2 3/4 4 + */ + +/* + * @test id=compileonly-all-levels-no-tiered + * @bug 8313713 + * @summary Test -XX:CompileCommand=compileonly with different compilation levels + * @requires vm.compMode != "Xint" & vm.flavor == "server" & vm.opt.TieredCompilation == false + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=compileonly,*.CompileLevelWBTest::compiledMethod + * ${test.main.class} + * 4 + */ + +/* + * @test id=compileonly-mask-1 + * @bug 8313713 + * @summary Test -XX:CompileCommand=compileonly with different compilation levels + * @requires vm.compMode != "Xint" & vm.flavor == "server" & vm.opt.TieredCompilation != false + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=compileonly,*.CompileLevelWBTest::compiledMethod,1 + * ${test.main.class} + * 1 + */ + +/* + * @test id=compileonly-mask-2 + * @bug 8313713 + * @summary Test -XX:CompileCommand=compileonly with different compilation levels + * @requires vm.compMode != "Xint" & vm.flavor == "server" & vm.opt.TieredCompilation != false + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=compileonly,*.CompileLevelWBTest::compiledMethod,10 + * ${test.main.class} + * 2 + */ + +/* + * @test id=compileonly-mask-3 + * @bug 8313713 + * @summary Test -XX:CompileCommand=compileonly with different compilation levels + * @requires vm.compMode != "Xint" & vm.flavor == "server" & vm.opt.TieredCompilation != false + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=compileonly,*.CompileLevelWBTest::compiledMethod,11 + * ${test.main.class} + * 1 2 + */ + +/* + * @test id=compileonly-mask-4 + * @bug 8313713 + * @summary Test -XX:CompileCommand=compileonly with different compilation levels + * @requires vm.compMode != "Xint" & vm.flavor == "server" & vm.opt.TieredCompilation != false + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=compileonly,*.CompileLevelWBTest::compiledMethod,100 + * ${test.main.class} + * 3 + */ + +/* + * @test id=compileonly-mask-5 + * @bug 8313713 + * @summary Test -XX:CompileCommand=compileonly with different compilation levels + * @requires vm.compMode != "Xint" & vm.flavor == "server" & vm.opt.TieredCompilation != false + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=compileonly,*.CompileLevelWBTest::compiledMethod,101 + * ${test.main.class} + * 1 3 + */ + +/* + * @test id=compileonly-mask-6 + * @bug 8313713 + * @summary Test -XX:CompileCommand=compileonly with different compilation levels + * @requires vm.compMode != "Xint" & vm.flavor == "server" & vm.opt.TieredCompilation != false + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=compileonly,*.CompileLevelWBTest::compiledMethod,110 + * ${test.main.class} + * 2 3 + */ + +/* + * @test id=compileonly-mask-7 + * @bug 8313713 + * @summary Test -XX:CompileCommand=compileonly with different compilation levels + * @requires vm.compMode != "Xint" & vm.flavor == "server" & vm.opt.TieredCompilation != false + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=compileonly,*.CompileLevelWBTest::compiledMethod,111 + * ${test.main.class} + * 1 2 3 + */ + +/* + * @test id=compileonly-mask-8 + * @bug 8313713 + * @summary Test -XX:CompileCommand=compileonly with different compilation levels + * @requires vm.compMode != "Xint" & vm.flavor == "server" + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=compileonly,*.CompileLevelWBTest::compiledMethod,1000 + * ${test.main.class} + * 4 + */ + +/* + * @test id=compileonly-mask-9-tiered + * @bug 8313713 + * @summary Test -XX:CompileCommand=compileonly with different compilation levels + * @requires vm.compMode != "Xint" & vm.flavor == "server" & vm.opt.TieredCompilation != false + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=compileonly,*.CompileLevelWBTest::compiledMethod,1001 + * ${test.main.class} + * 1 4 + */ + +/* + * @test id=compileonly-mask-9-no-tiered + * @bug 8313713 + * @summary Test -XX:CompileCommand=compileonly with different compilation levels + * @requires vm.compMode != "Xint" & vm.flavor == "server" & vm.opt.TieredCompilation == false + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=compileonly,*.CompileLevelWBTest::compiledMethod,1001 + * ${test.main.class} + * 4 + */ + +/* + * @test id=compileonly-mask-10-tiered + * @bug 8313713 + * @summary Test -XX:CompileCommand=compileonly with different compilation levels + * @requires vm.compMode != "Xint" & vm.flavor == "server" & vm.opt.TieredCompilation != false + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=compileonly,*.CompileLevelWBTest::compiledMethod,1010 + * ${test.main.class} + * 2 4 + */ + +/* + * @test id=compileonly-mask-10-no-tiered + * @bug 8313713 + * @summary Test -XX:CompileCommand=compileonly with different compilation levels + * @requires vm.compMode != "Xint" & vm.flavor == "server" & vm.opt.TieredCompilation == false + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=compileonly,*.CompileLevelWBTest::compiledMethod,1010 + * ${test.main.class} + * 4 + */ + +/* + * @test id=compileonly-mask-11-tiered + * @bug 8313713 + * @summary Test -XX:CompileCommand=compileonly with different compilation levels + * @requires vm.compMode != "Xint" & vm.flavor == "server" & vm.opt.TieredCompilation != false + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=compileonly,*.CompileLevelWBTest::compiledMethod,1011 + * ${test.main.class} + * 1 2 4 + */ + +/* + * @test id=compileonly-mask-11-no-tiered + * @bug 8313713 + * @summary Test -XX:CompileCommand=compileonly with different compilation levels + * @requires vm.compMode != "Xint" & vm.flavor == "server" & vm.opt.TieredCompilation == false + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=compileonly,*.CompileLevelWBTest::compiledMethod,1011 + * ${test.main.class} + * 4 + */ + +/* + * @test id=compileonly-mask-12-mixed + * @bug 8313713 + * @summary Test -XX:CompileCommand=compileonly with different compilation levels + * @requires vm.compMode == "Xmixed" & vm.flavor == "server" & vm.opt.TieredCompilation != false + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=compileonly,*.CompileLevelWBTest::compiledMethod,1100 + * ${test.main.class} + * 3 4 + */ + +/* + * @test id=compileonly-mask-12-comp + * @bug 8313713 + * @summary Test -XX:CompileCommand=compileonly with different compilation levels + * @requires vm.compMode == "Xcomp" & vm.flavor == "server" & vm.opt.TieredCompilation != false + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=compileonly,*.CompileLevelWBTest::compiledMethod,1100 + * ${test.main.class} + * 3/4 4 + */ + +/* + * @test id=compileonly-mask-12-no-tiered + * @bug 8313713 + * @summary Test -XX:CompileCommand=compileonly with different compilation levels + * @requires vm.compMode != "Xint" & vm.flavor == "server" & vm.opt.TieredCompilation == false + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=compileonly,*.CompileLevelWBTest::compiledMethod,1100 + * ${test.main.class} + * 4 + */ + +/* + * @test id=compileonly-mask-13-mixed + * @bug 8313713 + * @summary Test -XX:CompileCommand=compileonly with different compilation levels + * @requires vm.compMode == "Xmixed" & vm.flavor == "server" & vm.opt.TieredCompilation != false + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=compileonly,*.CompileLevelWBTest::compiledMethod,1101 + * ${test.main.class} + * 1 3 4 + */ + +/* + * @test id=compileonly-mask-13-comp + * @bug 8313713 + * @summary Test -XX:CompileCommand=compileonly with different compilation levels + * @requires vm.compMode == "Xcomp" & vm.flavor == "server" & vm.opt.TieredCompilation != false + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=compileonly,*.CompileLevelWBTest::compiledMethod,1101 + * ${test.main.class} + * 1 3/4 4 + */ + +/* + * @test id=compileonly-mask-13-no-tiered + * @bug 8313713 + * @summary Test -XX:CompileCommand=compileonly with different compilation levels + * @requires vm.compMode != "Xint" & vm.flavor == "server" & vm.opt.TieredCompilation == false + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=compileonly,*.CompileLevelWBTest::compiledMethod,1101 + * ${test.main.class} + * 4 + */ + +/* + * @test id=compileonly-mask-14-mixed + * @bug 8313713 + * @summary Test -XX:CompileCommand=compileonly with different compilation levels + * @requires vm.compMode == "Xmixed" & vm.flavor == "server" & vm.opt.TieredCompilation != false + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=compileonly,*.CompileLevelWBTest::compiledMethod,1110 + * ${test.main.class} + * 2 3 4 + */ + +/* + * @test id=compileonly-mask-14-comp + * @bug 8313713 + * @summary Test -XX:CompileCommand=compileonly with different compilation levels + * @requires vm.compMode == "Xcomp" & vm.flavor == "server" & vm.opt.TieredCompilation != false + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=compileonly,*.CompileLevelWBTest::compiledMethod,1110 + * ${test.main.class} + * 2 3/4 4 + */ + +/* + * @test id=compileonly-mask-14-no-tiered + * @bug 8313713 + * @summary Test -XX:CompileCommand=compileonly with different compilation levels + * @requires vm.compMode != "Xint" & vm.flavor == "server" & vm.opt.TieredCompilation == false + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=compileonly,*.CompileLevelWBTest::compiledMethod,1110 + * ${test.main.class} + * 4 + */ + +/* + * @test id=compileonly-mask-15-mixed + * @bug 8313713 + * @summary Test -XX:CompileCommand=compileonly with different compilation levels + * @requires vm.compMode == "Xmixed" & vm.flavor == "server" & vm.opt.TieredCompilation != false + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=compileonly,*.CompileLevelWBTest::compiledMethod,1111 + * ${test.main.class} + * 1 2 3 4 + */ + +/* + * @test id=compileonly-mask-15-comp + * @bug 8313713 + * @summary Test -XX:CompileCommand=compileonly with different compilation levels + * @requires vm.compMode == "Xcomp" & vm.flavor == "server" & vm.opt.TieredCompilation != false + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=compileonly,*.CompileLevelWBTest::compiledMethod,1111 + * ${test.main.class} + * 1 2 3/4 4 + */ + +/* + * @test id=compileonly-mask-15-no-tiered + * @bug 8313713 + * @summary Test -XX:CompileCommand=compileonly with different compilation levels + * @requires vm.compMode != "Xint" & vm.flavor == "server" & vm.opt.TieredCompilation == false + * & (vm.opt.TieredStopAtLevel == 4 | vm.opt.TieredStopAtLevel == null) + * & (vm.opt.CompilationMode == "normal" | vm.opt.CompilationMode == null) + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm/timeout=30 -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. -XX:+PrintCompilation + * -XX:CompileCommand=BackgroundCompilation,*.CompileLevelWBTest::compiledMethod,false + * -XX:CompileCommand=compileonly,*.CompileLevelWBTest::compiledMethod,1111 + * ${test.main.class} + * 4 + */ + +package compiler.compilercontrol.commands; + +import jdk.test.lib.Asserts; +import jdk.test.whitebox.WhiteBox; + +import java.lang.reflect.Method; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.LockSupport; +import java.util.function.BooleanSupplier; + +public class CompileLevelWBTest { + private static final WhiteBox WB = WhiteBox.getWhiteBox(); + private static final Method testMethod; + private static boolean[] allowedToCompileAtLevel; + private static int[] expectedLevelAfterWBCompileRequestAtLevel; + + static { + try { + testMethod = CompileLevelWBTest.class.getDeclaredMethod("compiledMethod", new Class[] {int.class, boolean.class}); + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + + // For Tier4 600 invocation of this method with avg 25 loops for each should be enough + // to trigger Tier4CompilationThreshold=15000 + private static void compiledMethod(final int a, boolean uncommonTrap) { + int r = 0; + if (uncommonTrap) { + IO.println("==> compiledMethod(): uncommon trap! <=="); + } + for (int i = 0; i < a % 50; i++) { + r ^= i; + } + if (r == 42) { + IO.println("xopowo!"); + } + } + + private static boolean longLoop(int expectedLevel) { + // To trigger compilation, 100-200 should be enough for C1 and 600-700 for C2 + for (int i = 0; i < 10000; i++) { + compiledMethod(i, false); + if ((i & 0xf) == 0) { + if (isTestMethodCompiledAtLevel(expectedLevel)) { + IO.println("==> longLoop(): compiledMethod() has been compiled at iteration " + i + " <=="); + compiledMethod(i + 1, expectedLevel == 4); + return true; + } + } + } + return false; + } + + private static boolean runTestCode(int expectedLevel) { + for (int i = 0; i < 10; i++) { + if (longLoop(expectedLevel)) { + return true; + } + } + IO.println("==> runTestCode(): " + testMethod.getName() + " has not been compiled <=="); + return false; + } + + private static boolean isTestMethodCompiledAtLevel(int expectedLevel) { + int curLevel = WB.getMethodCompilationLevel(testMethod); + if (curLevel == 0) { + // we're in interpreter. keep going. + return false; + } + verifyCompileLevel(curLevel); + Asserts.assertTrue(allowedToCompileAtLevel[curLevel], "The test method should not be compiled at excluded level=" + curLevel); + return curLevel == expectedLevel; + } + + public static void main(String[] args) { + IO.println("==> entering main() <=="); + try { + parseExpectedLevels(args); + // Wait until compilers are free, so thresholds are not altered + waitUntilCompilerQueuesIsAlmostEmpty(); + + IO.println("==> starting test <=="); + for (int level = 1; level <= 4; level++) { + WB.deoptimizeMethod(testMethod); + WB.clearMethodState(testMethod); + + waitUntil(() -> { + int curLevel = WB.getMethodCompilationLevel(testMethod); + IO.println("==> waiting for deoptimization, current level: " + curLevel + " <=="); + return curLevel == 0; + }); + + boolean shouldBeCompilable = allowedToCompileAtLevel[level]; + String expectedResult = shouldBeCompilable ? "" : " NOT"; + IO.println("==> checking compilation at level " + level + " (should" + expectedResult + " be compiled) <=="); + Asserts.assertEquals(shouldBeCompilable, WB.isMethodCompilable(testMethod, level), + "WB.isMethodCompilable() returns wrong answer for level " + level); + Asserts.assertEquals(shouldBeCompilable, WB.enqueueMethodForCompilation(testMethod, level), + "Error enqueuing method for compilation at level " + level + " via WhiteBox"); + int expectedLevel = expectedLevelAfterWBCompileRequestAtLevel[level]; + Asserts.assertEquals(shouldBeCompilable, runTestCode(expectedLevel), + testMethod.getName() + " was" + expectedResult + " compiled at level " + expectedLevel); + } + } finally { + IO.println("==> exiting main() <=="); + } + } + + private static void parseExpectedLevels(String[] args) { + allowedToCompileAtLevel = new boolean[] { false, false, false, false, false }; + expectedLevelAfterWBCompileRequestAtLevel = new int[] { 0, 1, 2, 3, 4, 5 }; + + for (String arg : args) { + try { + String[] parts = arg.split("/"); + int level = Integer.parseInt(parts[0]); + int goesToLevel = parts.length > 1 ? Integer.parseInt(parts[1]) : level; + verifyCompileLevel(level); + allowedToCompileAtLevel[level] = true; + expectedLevelAfterWBCompileRequestAtLevel[level] = goesToLevel; + } catch (NumberFormatException ex) { + ex.printStackTrace(); + } + } + } + + private static void waitUntilCompilerQueuesIsAlmostEmpty() { + waitUntil(() -> { + int cqSize = WB.getCompileQueuesSize(); + IO.println("==> Compiler queues size: " + cqSize); + return cqSize < 5; + }); + } + + private static void waitUntil(BooleanSupplier condition) { + for (int maxWait = 30; maxWait > 0; --maxWait) { + if (condition.getAsBoolean()) { + return; + } + LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1)); + } + } + + private static void verifyCompileLevel(int curLevel) { + Asserts.assertTrue(curLevel > 0 && curLevel < 5, "Invalid compile level"); + } +} diff --git a/test/hotspot/jtreg/compiler/compilercontrol/jcmd/ClearDirectivesFileStackTest.java b/test/hotspot/jtreg/compiler/compilercontrol/jcmd/ClearDirectivesFileStackTest.java index 018d55be01b3..01675a7801fd 100644 --- a/test/hotspot/jtreg/compiler/compilercontrol/jcmd/ClearDirectivesFileStackTest.java +++ b/test/hotspot/jtreg/compiler/compilercontrol/jcmd/ClearDirectivesFileStackTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015, 2022, 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 @@ -73,16 +73,16 @@ public void test() { // skip invalid command command = Command.COMPILEONLY; } - CompileCommand compileCommand = new CompileCommand(command, + CompileCommand compileCommand = new CompileCommand(command, true, methodDescriptor, cmdGen.generateCompiler(), Scenario.Type.DIRECTIVE); builder.add(compileCommand); } // clear the stack - builder.add(new JcmdCommand(Command.NONEXISTENT, null, null, + builder.add(new JcmdCommand(Command.NONEXISTENT, true, null, null, Scenario.Type.JCMD, Scenario.JcmdType.CLEAR)); // print all directives after the clear - builder.add(new JcmdCommand(Command.NONEXISTENT, null, null, + builder.add(new JcmdCommand(Command.NONEXISTENT, true, null, null, Scenario.Type.JCMD, Scenario.JcmdType.PRINT)); Scenario scenario = builder.build(); scenario.execute(); diff --git a/test/hotspot/jtreg/compiler/cpuflags/CPUFeaturesClearTest.java b/test/hotspot/jtreg/compiler/cpuflags/CPUFeaturesClearTest.java index 9b243ad6b39e..ae16fd3c094d 100644 --- a/test/hotspot/jtreg/compiler/cpuflags/CPUFeaturesClearTest.java +++ b/test/hotspot/jtreg/compiler/cpuflags/CPUFeaturesClearTest.java @@ -90,14 +90,6 @@ public void testX86Flags() throws Throwable { outputAnalyzer.shouldNotMatch("[os,cpu] CPU: .* sse3.*"); outputAnalyzer.shouldNotMatch("[os,cpu] CPU: .* ssse3.*"); } - if (isCpuFeatureSupported("sse2")) { - outputAnalyzer = ProcessTools.executeTestJava(generateArgs(prepareNumericFlag("UseSSE", 1))); - outputAnalyzer.shouldNotMatch("[os,cpu] CPU: .* sse2.*"); - } - if (isCpuFeatureSupported("sse")) { - outputAnalyzer = ProcessTools.executeTestJava(generateArgs(prepareNumericFlag("UseSSE", 0))); - outputAnalyzer.shouldNotMatch("[os,cpu] CPU: .* sse.*"); - } if (isCpuFeatureSupported("avx512f")) { outputAnalyzer = ProcessTools.executeTestJava(generateArgs(prepareNumericFlag("UseAVX", 2))); outputAnalyzer.shouldNotMatch("[os,cpu] CPU: .* avx512.*"); diff --git a/test/hotspot/jtreg/compiler/floatingpoint/NaNTest.java b/test/hotspot/jtreg/compiler/floatingpoint/NaNTest.java index 959647014ca9..cef183afebf0 100644 --- a/test/hotspot/jtreg/compiler/floatingpoint/NaNTest.java +++ b/test/hotspot/jtreg/compiler/floatingpoint/NaNTest.java @@ -73,35 +73,7 @@ static void testDouble() { } public static void main(String args[]) { - // Some platforms are known to strip signaling NaNs. - // The block below can be used to except them. - boolean expectStableFloats = true; - boolean expectStableDoubles = true; - - // On x86_32 without relevant SSE-enabled stubs, we are entering - // native methods that use FPU instructions, and those strip the - // signaling NaNs. - if (Platform.isX86()) { - int sse = WHITE_BOX.getIntVMFlag("UseSSE").intValue(); - boolean stubsPresent = WHITE_BOX.getBooleanVMFlag("InlineIntrinsics"); - expectStableFloats = (sse >= 1) && stubsPresent; - expectStableDoubles = (sse >= 2) && stubsPresent; - } - - if (expectStableFloats) { - testFloat(); - } else { - System.out.println("Stable floats cannot be expected, skipping"); - } - - if (expectStableDoubles) { - testDouble(); - } else { - System.out.println("Stable doubles cannot be expected, skipping"); - } - - if (!expectStableFloats && !expectStableDoubles) { - throw new SkippedException("No tests were run."); - } + testFloat(); + testDouble(); } } diff --git a/test/hotspot/jtreg/compiler/jsr292/InvokerSignatureMismatch.java b/test/hotspot/jtreg/compiler/jsr292/InvokerSignatureMismatch.java index 188051025add..6e684ea104ca 100644 --- a/test/hotspot/jtreg/compiler/jsr292/InvokerSignatureMismatch.java +++ b/test/hotspot/jtreg/compiler/jsr292/InvokerSignatureMismatch.java @@ -1,3 +1,26 @@ +/* + * 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 + * 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.jsr292; import java.lang.invoke.MethodHandle; @@ -39,7 +62,7 @@ public static void main(String[] args) throws Throwable { static void mainLink(int i) throws Throwable { Object name = MethodHandleHelper.internalMemberName(INT_MH); - MethodHandleHelper.linkToStatic((float) i, name); + MethodHandleHelper.linkToStatic(name, (float) i); } static void mainInvoke(int i) throws Throwable { diff --git a/test/hotspot/jtreg/compiler/jsr292/patches/java.base/java/lang/invoke/MethodHandleHelper.java b/test/hotspot/jtreg/compiler/jsr292/patches/java.base/java/lang/invoke/MethodHandleHelper.java index a422e52986cc..64eb58c5a2dc 100644 --- a/test/hotspot/jtreg/compiler/jsr292/patches/java.base/java/lang/invoke/MethodHandleHelper.java +++ b/test/hotspot/jtreg/compiler/jsr292/patches/java.base/java/lang/invoke/MethodHandleHelper.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 @@ -49,7 +49,7 @@ public static Object internalMemberName(MethodHandle mh) throws Throwable { } @ForceInline - public static void linkToStatic(float arg, Object name) throws Throwable { + public static void linkToStatic(Object name, float arg) throws Throwable { MethodHandle.linkToStatic(arg, name); } diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java index 55d591acdb3a..4f7869f444a2 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java @@ -233,6 +233,11 @@ public class IRNode { beforeMatchingNameRegex(ADD_P, "AddP"); } + public static final String ADD_D = PREFIX + "ADD_D" + POSTFIX; + static { + beforeMatchingNameRegex(ADD_D, "AddD"); + } + public static final String ADD_VD = VECTOR_PREFIX + "ADD_VD" + POSTFIX; static { vectorNode(ADD_VD, "AddVD", TYPE_DOUBLE); @@ -763,11 +768,21 @@ public class IRNode { vectorNode(DIV_VHF, "DivVHF", TYPE_SHORT); } + public static final String DIV_F = PREFIX + "DIV_F" + POSTFIX; + static { + beforeMatchingNameRegex(DIV_F, "DivF"); + } + public static final String DIV_VF = VECTOR_PREFIX + "DIV_VF" + POSTFIX; static { vectorNode(DIV_VF, "DivVF", TYPE_FLOAT); } + public static final String DIV_D = PREFIX + "DIV_D" + POSTFIX; + static { + beforeMatchingNameRegex(DIV_D, "DivD"); + } + public static final String DIV_VD = VECTOR_PREFIX + "DIV_VD" + POSTFIX; static { vectorNode(DIV_VD, "DivVD", TYPE_DOUBLE); diff --git a/test/hotspot/jtreg/compiler/profiling/TestPrintMethodData.java b/test/hotspot/jtreg/compiler/profiling/TestPrintMethodData.java new file mode 100644 index 000000000000..9e14cd6ea4b1 --- /dev/null +++ b/test/hotspot/jtreg/compiler/profiling/TestPrintMethodData.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2026 IBM Corporation. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8382777 + * @summary Test that PrintMethodData output matches expectations + * @requires vm.flagless + * @modules java.base/jdk.internal.misc + * @library /test/lib / + * @run driver ${test.main.class} + */ + +package compiler.profiling; + +import compiler.lib.generators.Generator; +import compiler.lib.generators.Generators; +import jdk.test.lib.process.OutputAnalyzer; +import jdk.test.lib.process.ProcessTools; + +public class TestPrintMethodData { + public static void main(String[] args) throws Exception { + ProcessBuilder pb = ProcessTools.createLimitedTestJavaProcessBuilder( + "-XX:+UnlockDiagnosticVMOptions", "-XX:+PrintMethodData", + "-XX:CompileCommand=compileonly,compiler.profiling.TestPrintMethodData::test*", + Launcher.class.getName()); + + OutputAnalyzer analyzer = new OutputAnalyzer(pb.start()); + analyzer.shouldHaveExitValue(0); + analyzer.shouldContain("BranchData"); + analyzer.shouldMatch("taken\\(\\d+\\)"); + } + + static long testLongReductionSimpleMax(long[] array) { + long result = Long.MIN_VALUE; + for (int i = 0; i < array.length; i++) { + var v = array[i]; + result = Math.max(v, result); + } + return result; + } + + static class Launcher { + private static final Generator GEN_LONG = Generators.G.longs(); + private static final int SIZE = 1024; + + static void main(String[] args) throws Exception { + long[] longs = new long[SIZE]; + Generators.G.fill(GEN_LONG, longs); + + for (int i = 0; i < 20_000; i++) { + testLongReductionSimpleMax(longs); + } + } + } +} diff --git a/test/hotspot/jtreg/compiler/stable/LazyConstantsIrTest.java b/test/hotspot/jtreg/compiler/stable/LazyConstantsIrTest.java new file mode 100644 index 000000000000..b9f9343dd393 --- /dev/null +++ b/test/hotspot/jtreg/compiler/stable/LazyConstantsIrTest.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 Check LazyConstant and lazy collection constant folding + * @modules java.base/jdk.internal.lang + * @library /test/lib / + * @enablePreview + * @run main ${test.main.class} + */ + +package compiler.stable; + +import compiler.lib.ir_framework.*; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class LazyConstantsIrTest { + + public static void main(String[] args) { + new TestFramework() + .addTestClassesToBootClassPath() + .addFlags( + "--enable-preview", + "-XX:+UnlockExperimentalVMOptions") + .addCrossProductScenarios( + Set.of("-XX:+TieredCompilation", "-XX:-TieredCompilation")) + .setDefaultWarmup(5000) + .start(); + } + + static final int THE_VALUE = 42; + + static final LazyConstant LAZY_CONSTANT = LazyConstant.of(() -> THE_VALUE); + static final List LAZY_LIST = List.ofLazy(1, _ -> THE_VALUE); + static final Set LAZY_SET = Set.ofLazy(Set.of(THE_VALUE), _ -> true); + static final Map LAZY_MAP = Map.ofLazy(Set.of(0), _ -> THE_VALUE); + + + // For all tests: + // * Access should be folded. + // * No barriers expected for a folded access (as opposed to a non-folded). + + // Lazy constant + + @Test + @IR(failOn = { IRNode.LOAD, IRNode.MEMBAR }) + static int foldLazyConstantGet() { + return LAZY_CONSTANT.get(); + } + + // Lazy list + + @Test + @IR(failOn = { IRNode.LOAD, IRNode.MEMBAR}) + static int foldLazyListGet() { + return LAZY_LIST.get(0); + } + + @Test + @IR(failOn = { IRNode.LOAD, IRNode.MEMBAR}) + static int foldLazyListSize() { + return LAZY_LIST.size(); + } + + // Lazy map + + @Test + @IR(failOn = { IRNode.LOAD, IRNode.MEMBAR}) + static int foldLazyMapGet() { + return LAZY_MAP.get(0); + } + + @Test + @IR(failOn = { IRNode.LOAD, IRNode.MEMBAR}) + static int foldLazyMapSize() { + return LAZY_MAP.size(); + } + + @Test + @IR(failOn = { IRNode.LOAD, IRNode.MEMBAR}) + static int foldLazyMapHashCode() { + return LAZY_MAP.hashCode(); + } + + // Lazy set + + @Test + @IR(failOn = { IRNode.LOAD, IRNode.MEMBAR}) + static boolean foldLazySetContains() { + return LAZY_SET.contains(THE_VALUE); + } + + @Test + @IR(failOn = { IRNode.LOAD, IRNode.MEMBAR}) + static int foldLazySetHashCode() { + return LAZY_SET.hashCode(); + } + + @Test + @IR(failOn = { IRNode.LOAD, IRNode.MEMBAR}) + static int foldLazySetSize() { + return LAZY_SET.size(); + } + +} diff --git a/test/hotspot/jtreg/compiler/startup/SmallCodeCacheStartup.java b/test/hotspot/jtreg/compiler/startup/SmallCodeCacheStartup.java index b3b92afa0d15..8e0d039ea52e 100644 --- a/test/hotspot/jtreg/compiler/startup/SmallCodeCacheStartup.java +++ b/test/hotspot/jtreg/compiler/startup/SmallCodeCacheStartup.java @@ -52,8 +52,8 @@ public static void main(String[] args) throws Exception { try { analyzer.shouldHaveExitValue(0); } catch (RuntimeException e) { - // Error occurred during initialization, did we run out of adapter space? - assertTrue(analyzer.getOutput().contains("VirtualMachineError: Out of space in CodeCache"), + // Error occurred during initialization + assertTrue(analyzer.getOutput().contains("CICompilerCount is too large"), "Expected VirtualMachineError"); } diff --git a/test/hotspot/jtreg/compiler/types/TestCMovePMachType.java b/test/hotspot/jtreg/compiler/types/TestCMovePMachType.java deleted file mode 100644 index 48069fd900eb..000000000000 --- a/test/hotspot/jtreg/compiler/types/TestCMovePMachType.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ -package compiler.types; - -/* - * @test - * @bug 8379667 - * @summary C2 crashes due to deep recursion in cmovP_regNode::bottom_type - * @run main/othervm -Xbatch -XX:CompileCommand=memlimit,${test.main.class}::test,50M~crash ${test.main.class} - */ -public class TestCMovePMachType { - interface I0 {} - interface I1 {} - interface I2 {} - interface I3 {} - interface I4 {} - interface I5 {} - interface I6 {} - interface I7 {} - interface I8 {} - interface I9 {} - interface I10 {} - interface I11 {} - interface I12 {} - interface I13 {} - interface I14 {} - interface I15 {} - interface I16 {} - interface I17 {} - interface I18 {} - interface I19 {} - interface IA {} - interface IB {} - - static class P implements I0, I1, I2, I3, I4, I5, I6, I7, I8, I9, I10, I11, I12, I13, I14, I15, I16, I17, I18, I19 { - int v; - } - - static class A extends P implements IA {} - static class B extends P implements IB {} - - public static void main(String[] args) { - A a = new A(); - B b = new B(); - for (int i = 0; i < 20000; i++) { - test(a, b, false, false); - test(a, b, false, true); - test(a, b, true, false); - test(a, b, true, true); - } - } - - // This method is compiled into a giant chain of CMovePs, which leads to a deep recursion when - // invoking Node::bottom_type on the corresponding MachNodes - private static int test(A p1, B p2, boolean b1, boolean b2) { - P p = b1 ? p1 : p2; - p = b2 ? p : p2; - p = b1 ? p : p1; - p = b2 ? p : p2; - p = b1 ? p : p1; - p = b2 ? p : p2; - p = b1 ? p : p1; - p = b2 ? p : p2; - p = b1 ? p : p1; - p = b2 ? p : p2; - p = b1 ? p : p1; - p = b2 ? p : p2; - p = b1 ? p : p1; - p = b2 ? p : p2; - p = b1 ? p : p1; - p = b2 ? p : p2; - p = b1 ? p : p1; - p = b2 ? p : p2; - p = b1 ? p : p1; - p = b2 ? p : p2; - p = b1 ? p : p1; - p = b2 ? p : p2; - p = b1 ? p : p1; - p = b2 ? p : p2; - p = b1 ? p : p1; - p = b2 ? p : p2; - p = b1 ? p : p1; - p = b2 ? p : p2; - p = b1 ? p : p1; - p = b2 ? p : p2; - p = b1 ? p : p1; - p = b2 ? p : p2; - p = b1 ? p : p1; - p = b2 ? p : p2; - p = b1 ? p : p1; - p = b2 ? p : p2; - p = b1 ? p : p1; - p = b2 ? p : p2; - p = b1 ? p : p1; - p = b2 ? p : p2; - p = b1 ? p : p1; - p = b2 ? p : p2; - p = b1 ? p : p1; - p = b2 ? p : p2; - p = b1 ? p : p1; - p = b2 ? p : p2; - p = b1 ? p : p1; - p = b2 ? p : p2; - p = b1 ? p : p1; - p = b2 ? p : p2; - p = b1 ? p : p1; - p = b2 ? p : p2; - p = b1 ? p : p1; - p = b2 ? p : p2; - p = b1 ? p : p1; - p = b2 ? p : p2; - p = b1 ? p : p1; - p = b2 ? p : p2; - p = b1 ? p : p1; - p = b2 ? p : p2; - p = b1 ? p : p1; - p = b2 ? p : p2; - p = b1 ? p : p1; - p = b2 ? p : p2; - p = b1 ? p : p1; - p = b2 ? p : p2; - p = b1 ? p : p1; - p = b2 ? p : p2; - p = b1 ? p : p1; - p = b2 ? p : p2; - p = b1 ? p : p1; - p = b2 ? p : p2; - p = b1 ? p : p1; - p = b2 ? p : p2; - p = b1 ? p : p1; - p = b2 ? p : p2; - p = b1 ? p : p1; - p = b2 ? p : p2; - p = b1 ? p : p1; - p = b2 ? p : p2; - p = b1 ? p : p1; - int r = p.v; - p.v = 0; - return r; - } -} diff --git a/test/hotspot/jtreg/compiler/vectorapi/TestVectorBroadcastTransforms.java b/test/hotspot/jtreg/compiler/vectorapi/TestVectorBroadcastTransforms.java new file mode 100644 index 000000000000..c58a6710c868 --- /dev/null +++ b/test/hotspot/jtreg/compiler/vectorapi/TestVectorBroadcastTransforms.java @@ -0,0 +1,1100 @@ +/* + * 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 8358521 + * @summary Optimize vector operations by reassociating broadcasted inputs + * @modules jdk.incubator.vector + * @library /test/lib / + * @run driver compiler.vectorapi.TestVectorBroadcastTransforms + */ + +package compiler.vectorapi; + +import compiler.lib.ir_framework.*; +import compiler.lib.verify.Verify; +import jdk.incubator.vector.*; + +import jdk.test.lib.Utils; +import java.util.Random; + +public class TestVectorBroadcastTransforms { + + public static void main(String[] args) { + TestFramework.runWithFlags("--add-modules=jdk.incubator.vector"); + } + + private static final Random R = Utils.getRandomInstance(); + + /* ======================= + * INT + * ======================= */ + + static final VectorSpecies ISP = IntVector.SPECIES_PREFERRED; + + @Test + @IR(failOn = IRNode.ADD_VI, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.ADD_I, ">= 1", IRNode.REPLICATE_I, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static int int_add(int ia, int ib) { + return IntVector.broadcast(ISP, ia) + .add(IntVector.broadcast(ISP, ib)) + .lane(0); + } + + @Run(test = "int_add") + static void run_int_add() { + int ia = R.nextInt(); + int ib = R.nextInt(); + int ir = int_add(ia, ib); + Verify.checkEQ(ir, ia + ib); + } + + @Test + @IR(failOn = IRNode.SUB_VI, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.SUB_I, ">= 1", IRNode.REPLICATE_I, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static int int_sub(int ia, int ib) { + return IntVector.broadcast(ISP, ia) + .sub(IntVector.broadcast(ISP, ib)) + .lane(0); + } + + @Run(test = "int_sub") + static void run_int_sub() { + int ia = R.nextInt(); + int ib = R.nextInt(); + int ir = int_sub(ia, ib); + Verify.checkEQ(ir, ia - ib); + } + + @Test + @IR(failOn = IRNode.MUL_VI, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.MUL_I, ">= 1", IRNode.REPLICATE_I, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static int int_mul(int ia, int ib) { + return IntVector.broadcast(ISP, ia) + .mul(IntVector.broadcast(ISP, ib)) + .lane(0); + } + + @Run(test = "int_mul") + static void run_int_mul() { + int ia = R.nextInt(); + int ib = R.nextInt(); + int ir = int_mul(ia, ib); + Verify.checkEQ(ir, ia * ib); + } + + @Test + @IR(failOn = IRNode.AND_VI, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.AND_I, ">= 1", IRNode.REPLICATE_I, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static int int_and(int ia, int ib) { + return IntVector.broadcast(ISP, ia) + .and(IntVector.broadcast(ISP, ib)) + .lane(0); + } + + @Run(test = "int_and") + static void run_int_and() { + int ia = R.nextInt(); + int ib = R.nextInt(); + int ir = int_and(ia, ib); + Verify.checkEQ(ir, ia & ib); + } + + @Test + @IR(failOn = IRNode.OR_VI, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.OR_I, ">= 1", IRNode.REPLICATE_I, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static int int_or(int ia, int ib) { + return IntVector.broadcast(ISP, ia) + .or(IntVector.broadcast(ISP, ib)) + .lane(0); + } + + @Run(test = "int_or") + static void run_int_or() { + int ia = R.nextInt(); + int ib = R.nextInt(); + int ir = int_or(ia, ib); + Verify.checkEQ(ir, ia | ib); + } + + @Test + @IR(failOn = IRNode.XOR_VI, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.XOR_I, ">= 1", IRNode.REPLICATE_I, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static int int_xor(int ia, int ib) { + return IntVector.broadcast(ISP, ia) + .lanewise(VectorOperators.XOR, IntVector.broadcast(ISP, ib)) + .lane(0); + } + + @Run(test = "int_xor") + static void run_int_xor() { + int ia = R.nextInt(); + int ib = R.nextInt(); + int ir = int_xor(ia, ib); + Verify.checkEQ(ir, ia ^ ib); + } + + @Test + @IR(failOn = IRNode.MIN_VI, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.MIN_I, ">= 1", IRNode.REPLICATE_I, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static int int_min(int ia, int ib) { + return IntVector.broadcast(ISP, ia) + .min(IntVector.broadcast(ISP, ib)) + .lane(0); + } + + @Run(test = "int_min") + static void run_int_min() { + int ia = R.nextInt(); + int ib = R.nextInt(); + int ir = int_min(ia, ib); + Verify.checkEQ(ir, Math.min(ia, ib)); + } + + @Test + @IR(failOn = IRNode.MAX_VI, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.MAX_I, ">= 1", IRNode.REPLICATE_I, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static int int_max(int ia, int ib) { + return IntVector.broadcast(ISP, ia) + .max(IntVector.broadcast(ISP, ib)) + .lane(0); + } + + @Run(test = "int_max") + static void run_int_max() { + int ia = R.nextInt(); + int ib = R.nextInt(); + int ir = int_max(ia, ib); + Verify.checkEQ(ir, Math.max(ia, ib)); + } + + /* ======================= + * LONG + * ======================= */ + + static final VectorSpecies LSP = LongVector.SPECIES_PREFERRED; + + @Test + @IR(failOn = IRNode.ADD_VL, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.ADD_L, ">= 1", IRNode.REPLICATE_L, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static long long_add(long la, long lb) { + return LongVector.broadcast(LSP, la) + .add(LongVector.broadcast(LSP, lb)) + .lane(0); + } + + @Run(test = "long_add") + static void run_long_add() { + long la = R.nextLong(); + long lb = R.nextLong(); + long lr = long_add(la, lb); + Verify.checkEQ(lr, la + lb); + } + + @Test + @IR(failOn = IRNode.SUB_VL, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.SUB_L, ">= 1", IRNode.REPLICATE_L, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static long long_sub(long la, long lb) { + return LongVector.broadcast(LSP, la) + .sub(LongVector.broadcast(LSP, lb)) + .lane(0); + } + + @Run(test = "long_sub") + static void run_long_sub() { + long la = R.nextLong(); + long lb = R.nextLong(); + long lr = long_sub(la, lb); + Verify.checkEQ(lr, la - lb); + } + + @Test + @IR(failOn = IRNode.MUL_VL, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.MUL_L, ">= 1", IRNode.REPLICATE_L, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static long long_mul(long la, long lb) { + return LongVector.broadcast(LSP, la) + .mul(LongVector.broadcast(LSP, lb)) + .lane(0); + } + + @Run(test = "long_mul") + static void run_long_mul() { + long la = R.nextLong(); + long lb = R.nextLong(); + long lr = long_mul(la, lb); + Verify.checkEQ(lr, la * lb); + } + + @Test + @IR(failOn = IRNode.AND_VL, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.AND_L, ">= 1", IRNode.REPLICATE_L, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static long long_and(long la, long lb) { + return LongVector.broadcast(LSP, la) + .and(LongVector.broadcast(LSP, lb)) + .lane(0); + } + + @Run(test = "long_and") + static void run_long_and() { + long la = R.nextLong(); + long lb = R.nextLong(); + long lr = long_and(la, lb); + Verify.checkEQ(lr, la & lb); + } + + @Test + @IR(failOn = IRNode.OR_VL, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.OR_L, ">= 1", IRNode.REPLICATE_L, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static long long_or(long la, long lb) { + return LongVector.broadcast(LSP, la) + .or(LongVector.broadcast(LSP, lb)) + .lane(0); + } + + @Run(test = "long_or") + static void run_long_or() { + long la = R.nextLong(); + long lb = R.nextLong(); + long lr = long_or(la, lb); + Verify.checkEQ(lr, la | lb); + } + + @Test + @IR(failOn = IRNode.XOR_VL, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.XOR_L, ">= 1", IRNode.REPLICATE_L, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static long long_xor(long la, long lb) { + return LongVector.broadcast(LSP, la) + .lanewise(VectorOperators.XOR, LongVector.broadcast(LSP, lb)) + .lane(0); + } + + @Run(test = "long_xor") + static void run_long_xor() { + long la = R.nextLong(); + long lb = R.nextLong(); + long lr = long_xor(la, lb); + Verify.checkEQ(lr, la ^ lb); + } + + @Test + @IR(failOn = IRNode.MIN_VL, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = {IRNode.REPLICATE_L, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static long long_min(long la, long lb) { + return LongVector.broadcast(LSP, la) + .min(LongVector.broadcast(LSP, lb)) + .lane(0); + } + + @Run(test = "long_min") + static void run_long_min() { + long la = R.nextLong(); + long lb = R.nextLong(); + long lr = long_min(la, lb); + Verify.checkEQ(lr, Math.min(la, lb)); + } + + @Test + @IR(failOn = IRNode.MAX_VL, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = {IRNode.REPLICATE_L, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static long long_max(long la, long lb) { + return LongVector.broadcast(LSP, la) + .max(LongVector.broadcast(LSP, lb)) + .lane(0); + } + + @Run(test = "long_max") + static void run_long_max() { + long la = R.nextLong(); + long lb = R.nextLong(); + long lr = long_max(la, lb); + Verify.checkEQ(lr, Math.max(la, lb)); + } + + /* ======================= + * FLOAT + * ======================= */ + + static final VectorSpecies FSP = FloatVector.SPECIES_PREFERRED; + + @Test + @IR(failOn = IRNode.ADD_VF, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.ADD_F, ">= 1", IRNode.REPLICATE_F, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static float float_add(float fa, float fb) { + return FloatVector.broadcast(FSP, fa) + .add(FloatVector.broadcast(FSP, fb)) + .lane(0); + } + + @Run(test = "float_add") + static void run_float_add() { + float fa = R.nextFloat(); + float fb = R.nextFloat(); + float fr = float_add(fa, fb); + Verify.checkEQ(fr, fa + fb); + } + + @Test + @IR(failOn = IRNode.SUB_VF, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.SUB_F, ">= 1", IRNode.REPLICATE_F, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static float float_sub(float fa, float fb) { + return FloatVector.broadcast(FSP, fa) + .sub(FloatVector.broadcast(FSP, fb)) + .lane(0); + } + + @Run(test = "float_sub") + static void run_float_sub() { + float fa = R.nextFloat(); + float fb = R.nextFloat(); + float fr = float_sub(fa, fb); + Verify.checkEQ(fr, fa - fb); + } + + @Test + @IR(failOn = IRNode.MUL_VF, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.MUL_F, ">= 1", IRNode.REPLICATE_F, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static float float_mul(float fa, float fb) { + return FloatVector.broadcast(FSP, fa) + .mul(FloatVector.broadcast(FSP, fb)) + .lane(0); + } + + @Run(test = "float_mul") + static void run_float_mul() { + float fa = R.nextFloat(); + float fb = R.nextFloat(); + float fr = float_mul(fa, fb); + Verify.checkEQ(fr, fa * fb); + } + + @Test + @IR(failOn = IRNode.DIV_VF, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.DIV_F, ">= 1", IRNode.REPLICATE_F, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static float float_div(float fa, float fb) { + return FloatVector.broadcast(FSP, fa) + .div(FloatVector.broadcast(FSP, fb)) + .lane(0); + } + + @Run(test = "float_div") + static void run_float_div() { + float fa = R.nextFloat(); + float fb = R.nextFloat(); + if (fb == 0f) fb = 1f; + float fr = float_div(fa, fb); + Verify.checkEQ(fr, fa / fb); + } + + @Test + @IR(failOn = IRNode.MIN_VF, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.MIN_F, ">= 1", IRNode.REPLICATE_F, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static float float_min(float fa, float fb) { + return FloatVector.broadcast(FSP, fa) + .min(FloatVector.broadcast(FSP, fb)) + .lane(0); + } + + @Run(test = "float_min") + static void run_float_min() { + float fa = R.nextFloat(); + float fb = R.nextFloat(); + float fr = float_min(fa, fb); + Verify.checkEQ(fr, Math.min(fa, fb)); + } + + @Test + @IR(failOn = IRNode.MAX_VF, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.MAX_F, ">= 1", IRNode.REPLICATE_F, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static float float_max(float fa, float fb) { + return FloatVector.broadcast(FSP, fa) + .max(FloatVector.broadcast(FSP, fb)) + .lane(0); + } + + @Run(test = "float_max") + static void run_float_max() { + float fa = R.nextFloat(); + float fb = R.nextFloat(); + float fr = float_max(fa, fb); + Verify.checkEQ(fr, Math.max(fa, fb)); + } + + @Test + @IR(failOn = IRNode.SQRT_VF, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.SQRT_F, ">= 1", IRNode.REPLICATE_F, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static float float_sqrt(float fa) { + return FloatVector.broadcast(FSP, fa) + .sqrt() + .lane(0); + } + + @Run(test = "float_sqrt") + static void run_float_sqrt() { + float fa = Math.abs(R.nextFloat()) + Float.MIN_VALUE; + float fr = float_sqrt(fa); + Verify.checkEQ(fr, (float) Math.sqrt(fa)); + } + + @Test + @IR(failOn = IRNode.FMA_VF, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.FMA_F, ">= 1", IRNode.REPLICATE_F, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static float float_fma(float fa, float fb, float fc) { + return FloatVector.broadcast(FSP, fa) + .fma(FloatVector.broadcast(FSP, fb), + FloatVector.broadcast(FSP, fc)) + .lane(0); + } + + @Run(test = "float_fma") + static void run_float_fma() { + float fa = R.nextFloat(); + float fb = R.nextFloat(); + float fc = R.nextFloat(); + float fr = float_fma(fa, fb, fc); + Verify.checkEQ(fr, Math.fma(fa, fb, fc)); + } + + /* ======================= + * DOUBLE + * ======================= */ + + static final VectorSpecies DSP = DoubleVector.SPECIES_PREFERRED; + + @Test + @IR(failOn = IRNode.ADD_VD, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.ADD_D, ">= 1", IRNode.REPLICATE_D, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static double double_add(double da, double db) { + return DoubleVector.broadcast(DSP, da) + .add(DoubleVector.broadcast(DSP, db)) + .lane(0); + } + + @Run(test = "double_add") + static void run_double_add() { + double da = R.nextDouble(); + double db = R.nextDouble(); + double dr = double_add(da, db); + Verify.checkEQ(dr, da + db); + } + + @Test + @IR(failOn = IRNode.SUB_VD, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.SUB_D, ">= 1", IRNode.REPLICATE_D, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static double double_sub(double da, double db) { + return DoubleVector.broadcast(DSP, da) + .sub(DoubleVector.broadcast(DSP, db)) + .lane(0); + } + + @Run(test = "double_sub") + static void run_double_sub() { + double da = R.nextDouble(); + double db = R.nextDouble(); + double dr = double_sub(da, db); + Verify.checkEQ(dr, da - db); + } + + @Test + @IR(failOn = IRNode.MUL_VD, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.MUL_D, ">= 1", IRNode.REPLICATE_D, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static double double_mul(double da, double db) { + return DoubleVector.broadcast(DSP, da) + .mul(DoubleVector.broadcast(DSP, db)) + .lane(0); + } + + @Run(test = "double_mul") + static void run_double_mul() { + double da = R.nextDouble(); + double db = R.nextDouble(); + double dr = double_mul(da, db); + Verify.checkEQ(dr, da * db); + } + + @Test + @IR(failOn = IRNode.DIV_VD, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.DIV_D, ">= 1", IRNode.REPLICATE_D, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static double double_div(double da, double db) { + return DoubleVector.broadcast(DSP, da) + .div(DoubleVector.broadcast(DSP, db)) + .lane(0); + } + + @Run(test = "double_div") + static void run_double_div() { + double da = R.nextDouble(); + double db = R.nextDouble(); + if (db == 0d) db = 1d; + double dr = double_div(da, db); + Verify.checkEQ(dr, da / db); + } + + @Test + @IR(failOn = IRNode.MIN_VD, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.MIN_D, ">= 1", IRNode.REPLICATE_D, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static double double_min(double da, double db) { + return DoubleVector.broadcast(DSP, da) + .min(DoubleVector.broadcast(DSP, db)) + .lane(0); + } + + @Run(test = "double_min") + static void run_double_min() { + double da = R.nextDouble(); + double db = R.nextDouble(); + double dr = double_min(da, db); + Verify.checkEQ(dr, Math.min(da, db)); + } + + @Test + @IR(failOn = IRNode.MAX_VD, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.MAX_D, ">= 1", IRNode.REPLICATE_D, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static double double_max(double da, double db) { + return DoubleVector.broadcast(DSP, da) + .max(DoubleVector.broadcast(DSP, db)) + .lane(0); + } + + @Run(test = "double_max") + static void run_double_max() { + double da = R.nextDouble(); + double db = R.nextDouble(); + double dr = double_max(da, db); + Verify.checkEQ(dr, Math.max(da, db)); + } + + @Test + @IR(failOn = IRNode.SQRT_VD, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.SQRT_D, ">= 1", IRNode.REPLICATE_D, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static double double_sqrt(double da) { + return DoubleVector.broadcast(DSP, da) + .sqrt() + .lane(0); + } + + @Run(test = "double_sqrt") + static void run_double_sqrt() { + double da = Math.abs(R.nextDouble()) + Double.MIN_VALUE; + double dr = double_sqrt(da); + Verify.checkEQ(dr, Math.sqrt(da)); + } + + @Test + @IR(failOn = IRNode.FMA_VD, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.FMA_D, ">= 1", IRNode.REPLICATE_D, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static double double_fma(double da, double db, double dc) { + return DoubleVector.broadcast(DSP, da) + .fma(DoubleVector.broadcast(DSP, db), + DoubleVector.broadcast(DSP, dc)) + .lane(0); + } + + @Run(test = "double_fma") + static void run_double_fma() { + double da = R.nextDouble(); + double db = R.nextDouble(); + double dc = R.nextDouble(); + double dr = double_fma(da, db, dc); + Verify.checkEQ(dr, Math.fma(da, db, dc)); + } + + /* ======================= + * BYTE + * ======================= */ + + static final VectorSpecies BSP = ByteVector.SPECIES_PREFERRED; + static byte B_MAX = Byte.MAX_VALUE, B_MIN = Byte.MIN_VALUE; + static byte B_ONE = (byte) 1, B_NEG_ONE = (byte) -1; + + @Test + @IR(failOn = IRNode.ADD_VB, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.ADD_I, ">= 1", + IRNode.REPLICATE_B, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static byte byte_add(byte ba, byte bb) { + return ByteVector.broadcast(BSP, ba) + .add(ByteVector.broadcast(BSP, bb)) + .lane(0); + } + + @Run(test = "byte_add") + static void run_byte_add() { + byte ba = (byte) R.nextInt(); + byte bb = (byte) R.nextInt(); + byte br = byte_add(ba, bb); + Verify.checkEQ(br, (byte) (ba + bb)); + } + + @Test + @IR(failOn = IRNode.SUB_VB, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.SUB_I, ">= 1", + IRNode.REPLICATE_B, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static byte byte_sub(byte ba, byte bb) { + return ByteVector.broadcast(BSP, ba) + .sub(ByteVector.broadcast(BSP, bb)) + .lane(0); + } + + @Run(test = "byte_sub") + static void run_byte_sub() { + byte ba = (byte) R.nextInt(); + byte bb = (byte) R.nextInt(); + byte br = byte_sub(ba, bb); + Verify.checkEQ(br, (byte) (ba - bb)); + } + + @Test + @IR(failOn = IRNode.ADD_VB, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.ADD_I, ">= 1", + IRNode.REPLICATE_B, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static byte byte_add_overflow() { + return ByteVector.broadcast(BSP, B_MAX) + .add(ByteVector.broadcast(BSP, B_ONE)) + .lane(0); + } + + @Run(test = "byte_add_overflow") + static void run_byte_add_overflow() { + byte br = byte_add_overflow(); + Verify.checkEQ(br, (byte) (B_MAX + B_ONE)); + } + + @Test + @IR(failOn = IRNode.ADD_VB, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.ADD_I, ">= 1", + IRNode.REPLICATE_B, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static byte byte_add_underflow() { + return ByteVector.broadcast(BSP, B_MIN) + .add(ByteVector.broadcast(BSP, B_NEG_ONE)) + .lane(0); + } + + @Run(test = "byte_add_underflow") + static void run_byte_add_underflow() { + byte br = byte_add_underflow(); + Verify.checkEQ(br, (byte) (B_MIN + B_NEG_ONE)); + } + + @Test + @IR(failOn = IRNode.SUB_VB, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.SUB_I, ">= 1", + IRNode.REPLICATE_B, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static byte byte_sub_overflow() { + return ByteVector.broadcast(BSP, B_MAX) + .sub(ByteVector.broadcast(BSP, B_NEG_ONE)) + .lane(0); + } + + @Run(test = "byte_sub_overflow") + static void run_byte_sub_overflow() { + byte br = byte_sub_overflow(); + Verify.checkEQ(br, (byte) (B_MAX - B_NEG_ONE)); + } + + @Test + @IR(failOn = IRNode.SUB_VB, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.SUB_I, ">= 1", + IRNode.REPLICATE_B, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static byte byte_sub_underflow() { + return ByteVector.broadcast(BSP, B_MIN) + .sub(ByteVector.broadcast(BSP, B_ONE)) + .lane(0); + } + + @Run(test = "byte_sub_underflow") + static void run_byte_sub_underflow() { + byte br = byte_sub_underflow(); + Verify.checkEQ(br, (byte) (B_MIN - B_ONE)); + } + + @Test + @IR(failOn = IRNode.MUL_VB, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.MUL_I, ">= 1", + IRNode.REPLICATE_B, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static byte byte_mul(byte ba, byte bb) { + return ByteVector.broadcast(BSP, ba) + .mul(ByteVector.broadcast(BSP, bb)) + .lane(0); + } + + @Run(test = "byte_mul") + static void run_byte_mul() { + byte ba = (byte) R.nextInt(); + byte bb = (byte) R.nextInt(); + byte br = byte_mul(ba, bb); + Verify.checkEQ(br, (byte) (ba * bb)); + } + + @Test + @IR(failOn = IRNode.AND_VB, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.AND_I, ">= 1", IRNode.REPLICATE_B, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static byte byte_and(byte ba, byte bb) { + return ByteVector.broadcast(BSP, ba) + .and(ByteVector.broadcast(BSP, bb)) + .lane(0); + } + + @Run(test = "byte_and") + static void run_byte_and() { + byte ba = (byte) R.nextInt(); + byte bb = (byte) R.nextInt(); + byte br = byte_and(ba, bb); + Verify.checkEQ(br, (byte) (ba & bb)); + } + + @Test + @IR(failOn = IRNode.OR_VB, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.OR_I, ">= 1", IRNode.REPLICATE_B, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static byte byte_or(byte ba, byte bb) { + return ByteVector.broadcast(BSP, ba) + .or(ByteVector.broadcast(BSP, bb)) + .lane(0); + } + + @Run(test = "byte_or") + static void run_byte_or() { + byte ba = (byte) R.nextInt(); + byte bb = (byte) R.nextInt(); + byte br = byte_or(ba, bb); + Verify.checkEQ(br, (byte) (ba | bb)); + } + + @Test + @IR(failOn = IRNode.XOR_VB, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.XOR_I, ">= 1", IRNode.REPLICATE_B, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static byte byte_xor(byte ba, byte bb) { + return ByteVector.broadcast(BSP, ba) + .lanewise(VectorOperators.XOR, ByteVector.broadcast(BSP, bb)) + .lane(0); + } + + @Run(test = "byte_xor") + static void run_byte_xor() { + byte ba = (byte) R.nextInt(); + byte bb = (byte) R.nextInt(); + byte br = byte_xor(ba, bb); + Verify.checkEQ(br, (byte) (ba ^ bb)); + } + + @Test + @IR(failOn = IRNode.MIN_VB, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.MIN_I, ">= 1", IRNode.REPLICATE_B, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static byte byte_min(byte ba, byte bb) { + return ByteVector.broadcast(BSP, ba) + .min(ByteVector.broadcast(BSP, bb)) + .lane(0); + } + + @Run(test = "byte_min") + static void run_byte_min() { + byte ba = (byte) R.nextInt(); + byte bb = (byte) R.nextInt(); + byte br = byte_min(ba, bb); + Verify.checkEQ(br, (byte) Math.min(ba, bb)); + } + + @Test + @IR(failOn = IRNode.MAX_VB, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.MAX_I, ">= 1", IRNode.REPLICATE_B, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static byte byte_max(byte ba, byte bb) { + return ByteVector.broadcast(BSP, ba) + .max(ByteVector.broadcast(BSP, bb)) + .lane(0); + } + + @Run(test = "byte_max") + static void run_byte_max() { + byte ba = (byte) R.nextInt(); + byte bb = (byte) R.nextInt(); + byte br = byte_max(ba, bb); + Verify.checkEQ(br, (byte) Math.max(ba, bb)); + } + + /* ======================= + * SHORT + * ======================= */ + + static final VectorSpecies SSP = ShortVector.SPECIES_PREFERRED; + static short S_MAX = Short.MAX_VALUE, S_MIN = Short.MIN_VALUE; + static short S_ONE = (short) 1, S_NEG_ONE = (short) -1; + + @Test + @IR(failOn = IRNode.ADD_VS, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.ADD_I, ">= 1", + IRNode.REPLICATE_S, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static short short_add(short sa, short sb) { + return ShortVector.broadcast(SSP, sa) + .add(ShortVector.broadcast(SSP, sb)) + .lane(0); + } + + @Run(test = "short_add") + static void run_short_add() { + short sa = (short) R.nextInt(); + short sb = (short) R.nextInt(); + short sr = short_add(sa, sb); + Verify.checkEQ(sr, (short) (sa + sb)); + } + + @Test + @IR(failOn = IRNode.SUB_VS, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.SUB_I, ">= 1", + IRNode.REPLICATE_S, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static short short_sub(short sa, short sb) { + return ShortVector.broadcast(SSP, sa) + .sub(ShortVector.broadcast(SSP, sb)) + .lane(0); + } + + @Run(test = "short_sub") + static void run_short_sub() { + short sa = (short) R.nextInt(); + short sb = (short) R.nextInt(); + short sr = short_sub(sa, sb); + Verify.checkEQ(sr, (short) (sa - sb)); + } + + @Test + @IR(failOn = IRNode.ADD_VS, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.ADD_I, ">= 1", + IRNode.REPLICATE_S, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static short short_add_overflow() { + return ShortVector.broadcast(SSP, S_MAX) + .add(ShortVector.broadcast(SSP, S_ONE)) + .lane(0); + } + + @Run(test = "short_add_overflow") + static void run_short_add_overflow() { + short sr = short_add_overflow(); + Verify.checkEQ(sr, (short) (S_MAX + S_ONE)); + } + + @Test + @IR(failOn = IRNode.ADD_VS, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.ADD_I, ">= 1", + IRNode.REPLICATE_S, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static short short_add_underflow() { + return ShortVector.broadcast(SSP, S_MIN) + .add(ShortVector.broadcast(SSP, S_NEG_ONE)) + .lane(0); + } + + @Run(test = "short_add_underflow") + static void run_short_add_underflow() { + short sr = short_add_underflow(); + Verify.checkEQ(sr, (short) (S_MIN + S_NEG_ONE)); + } + + @Test + @IR(failOn = IRNode.SUB_VS, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.SUB_I, ">= 1", + IRNode.REPLICATE_S, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static short short_sub_overflow() { + return ShortVector.broadcast(SSP, S_MAX) + .sub(ShortVector.broadcast(SSP, S_NEG_ONE)) + .lane(0); + } + + @Run(test = "short_sub_overflow") + static void run_short_sub_overflow() { + short sr = short_sub_overflow(); + Verify.checkEQ(sr, (short) (S_MAX - S_NEG_ONE)); + } + + @Test + @IR(failOn = IRNode.SUB_VS, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.SUB_I, ">= 1", + IRNode.REPLICATE_S, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static short short_sub_underflow() { + return ShortVector.broadcast(SSP, S_MIN) + .sub(ShortVector.broadcast(SSP, S_ONE)) + .lane(0); + } + + @Run(test = "short_sub_underflow") + static void run_short_sub_underflow() { + short sr = short_sub_underflow(); + Verify.checkEQ(sr, (short) (S_MIN - S_ONE)); + } + + @Test + @IR(failOn = IRNode.MUL_VS, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.MUL_I, ">= 1", + IRNode.REPLICATE_S, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static short short_mul(short sa, short sb) { + return ShortVector.broadcast(SSP, sa) + .mul(ShortVector.broadcast(SSP, sb)) + .lane(0); + } + + @Run(test = "short_mul") + static void run_short_mul() { + short sa = (short) R.nextInt(); + short sb = (short) R.nextInt(); + short sr = short_mul(sa, sb); + Verify.checkEQ(sr, (short) (sa * sb)); + } + + @Test + @IR(failOn = IRNode.AND_VS, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.AND_I, ">= 1", IRNode.REPLICATE_S, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static short short_and(short sa, short sb) { + return ShortVector.broadcast(SSP, sa) + .and(ShortVector.broadcast(SSP, sb)) + .lane(0); + } + + @Run(test = "short_and") + static void run_short_and() { + short sa = (short) R.nextInt(); + short sb = (short) R.nextInt(); + short sr = short_and(sa, sb); + Verify.checkEQ(sr, (short) (sa & sb)); + } + + @Test + @IR(failOn = IRNode.OR_VS, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.OR_I, ">= 1", IRNode.REPLICATE_S, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static short short_or(short sa, short sb) { + return ShortVector.broadcast(SSP, sa) + .or(ShortVector.broadcast(SSP, sb)) + .lane(0); + } + + @Run(test = "short_or") + static void run_short_or() { + short sa = (short) R.nextInt(); + short sb = (short) R.nextInt(); + short sr = short_or(sa, sb); + Verify.checkEQ(sr, (short) (sa | sb)); + } + + @Test + @IR(failOn = IRNode.XOR_VS, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.XOR_I, ">= 1", IRNode.REPLICATE_S, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static short short_xor(short sa, short sb) { + return ShortVector.broadcast(SSP, sa) + .lanewise(VectorOperators.XOR, ShortVector.broadcast(SSP, sb)) + .lane(0); + } + + @Run(test = "short_xor") + static void run_short_xor() { + short sa = (short) R.nextInt(); + short sb = (short) R.nextInt(); + short sr = short_xor(sa, sb); + Verify.checkEQ(sr, (short) (sa ^ sb)); + } + + @Test + @IR(failOn = IRNode.MIN_VS, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.MIN_I, ">= 1", IRNode.REPLICATE_S, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static short short_min(short sa, short sb) { + return ShortVector.broadcast(SSP, sa) + .min(ShortVector.broadcast(SSP, sb)) + .lane(0); + } + + @Run(test = "short_min") + static void run_short_min() { + short sa = (short) R.nextInt(); + short sb = (short) R.nextInt(); + short sr = short_min(sa, sb); + Verify.checkEQ(sr, (short) Math.min(sa, sb)); + } + + @Test + @IR(failOn = IRNode.MAX_VS, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.MAX_I, ">= 1", IRNode.REPLICATE_S, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + static short short_max(short sa, short sb) { + return ShortVector.broadcast(SSP, sa) + .max(ShortVector.broadcast(SSP, sb)) + .lane(0); + } + + @Run(test = "short_max") + static void run_short_max() { + short sa = (short) R.nextInt(); + short sb = (short) R.nextInt(); + short sr = short_max(sa, sb); + Verify.checkEQ(sr, (short) Math.max(sa, sb)); + } + +} diff --git a/test/hotspot/jtreg/compiler/vectorapi/TestVectorReassociations.java b/test/hotspot/jtreg/compiler/vectorapi/TestVectorReassociations.java new file mode 100644 index 000000000000..c6a11627215b --- /dev/null +++ b/test/hotspot/jtreg/compiler/vectorapi/TestVectorReassociations.java @@ -0,0 +1,605 @@ +/* + * 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 8358521 + * @summary Test reassociation of broadcasted inputs across vector operations + * @modules jdk.incubator.vector + * @library /test/lib / + * @run driver compiler.vectorapi.TestVectorReassociations + */ + +package compiler.vectorapi; + +import compiler.lib.ir_framework.*; +import jdk.incubator.vector.*; +import java.util.stream.IntStream; + +/** + * Tests for the reassociation transform: + * VectorOp(broadcast(a), VectorOp(broadcast(b), array)) + * => VectorOp(broadcast(ScalarOp(a, b)), array) + */ +public class TestVectorReassociations { + + public static void main(String[] args) { + TestFramework.runWithFlags("--add-modules=jdk.incubator.vector"); + } + + /* ======================= + * INT + * ======================= */ + + static final VectorSpecies ISP = IntVector.SPECIES_PREFERRED; + static int[] intIn = IntStream.range(0, IntVector.SPECIES_PREFERRED.length()).toArray(); + static int[] intOut = new int[IntVector.SPECIES_PREFERRED.length()]; + static int ia = 17, ib = 9; + + // --- INT ADD --- + + // bcast(a) ADD (bcast(b) ADD array) + @Test + @IR(applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.ADD_VI, IRNode.VECTOR_SIZE_ANY, " 1 ", IRNode.ADD_I, ">= 1", + IRNode.REPLICATE_I, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + @Warmup(value = 10000) + static void test_int_add_reassociation_pattern1() { + IntVector.broadcast(ISP, ia) + .lanewise(VectorOperators.ADD, + IntVector.broadcast(ISP, ib) + .lanewise(VectorOperators.ADD, + IntVector.fromArray(ISP, intIn, 0))) + .intoArray(intOut, 0); + } + + // bcast(a) ADD (array ADD bcast(b)) + @Test + @IR(applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.ADD_VI, IRNode.VECTOR_SIZE_ANY, " 1 ", IRNode.ADD_I, ">= 1", + IRNode.REPLICATE_I, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + @Warmup(value = 10000) + static void test_int_add_reassociation_pattern2() { + IntVector.broadcast(ISP, ia) + .lanewise(VectorOperators.ADD, + IntVector.fromArray(ISP, intIn, 0) + .lanewise(VectorOperators.ADD, + IntVector.broadcast(ISP, ib))) + .intoArray(intOut, 0); + } + + // (bcast(a) ADD array) ADD bcast(b) + @Test + @IR(applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.ADD_VI, IRNode.VECTOR_SIZE_ANY, " 1 ", IRNode.ADD_I, ">= 1", + IRNode.REPLICATE_I, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + @Warmup(value = 10000) + static void test_int_add_reassociation_pattern3() { + IntVector.broadcast(ISP, ia) + .lanewise(VectorOperators.ADD, + IntVector.fromArray(ISP, intIn, 0)) + .lanewise(VectorOperators.ADD, + IntVector.broadcast(ISP, ib)) + .intoArray(intOut, 0); + } + + // (array ADD bcast(a)) ADD bcast(b) + @Test + @IR(applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.ADD_VI, IRNode.VECTOR_SIZE_ANY, " 1 ", IRNode.ADD_I, ">= 1", + IRNode.REPLICATE_I, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + @Warmup(value = 10000) + static void test_int_add_reassociation_pattern4() { + IntVector.fromArray(ISP, intIn, 0) + .lanewise(VectorOperators.ADD, + IntVector.broadcast(ISP, ia)) + .lanewise(VectorOperators.ADD, + IntVector.broadcast(ISP, ib)) + .intoArray(intOut, 0); + } + + // --- INT MUL --- + + // bcast(a) MUL (bcast(b) MUL array) + @Test + @IR(applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.MUL_VI, IRNode.VECTOR_SIZE_ANY, " 1 ", IRNode.MUL_I, ">= 1", + IRNode.REPLICATE_I, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + @Warmup(value = 10000) + static void test_int_mul_reassociation_pattern1() { + IntVector.broadcast(ISP, ia) + .lanewise(VectorOperators.MUL, + IntVector.broadcast(ISP, ib) + .lanewise(VectorOperators.MUL, + IntVector.fromArray(ISP, intIn, 0))) + .intoArray(intOut, 0); + } + + // bcast(a) MUL (array MUL bcast(b)) + @Test + @IR(applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.MUL_VI, IRNode.VECTOR_SIZE_ANY, " 1 ", IRNode.MUL_I, ">= 1", + IRNode.REPLICATE_I, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + @Warmup(value = 10000) + static void test_int_mul_reassociation_pattern2() { + IntVector.broadcast(ISP, ia) + .lanewise(VectorOperators.MUL, + IntVector.fromArray(ISP, intIn, 0) + .lanewise(VectorOperators.MUL, + IntVector.broadcast(ISP, ib))) + .intoArray(intOut, 0); + } + + // (bcast(a) MUL array) MUL bcast(b) + @Test + @IR(applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.MUL_VI, IRNode.VECTOR_SIZE_ANY, " 1 ", IRNode.MUL_I, ">= 1", + IRNode.REPLICATE_I, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + @Warmup(value = 10000) + static void test_int_mul_reassociation_pattern3() { + IntVector.broadcast(ISP, ia) + .lanewise(VectorOperators.MUL, + IntVector.fromArray(ISP, intIn, 0)) + .lanewise(VectorOperators.MUL, + IntVector.broadcast(ISP, ib)) + .intoArray(intOut, 0); + } + + // (array MUL bcast(a)) MUL bcast(b) + @Test + @IR(applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.MUL_VI, IRNode.VECTOR_SIZE_ANY, " 1 ", IRNode.MUL_I, ">= 1", + IRNode.REPLICATE_I, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + @Warmup(value = 10000) + static void test_int_mul_reassociation_pattern4() { + IntVector.fromArray(ISP, intIn, 0) + .lanewise(VectorOperators.MUL, + IntVector.broadcast(ISP, ia)) + .lanewise(VectorOperators.MUL, + IntVector.broadcast(ISP, ib)) + .intoArray(intOut, 0); + } + + /* ======================= + * LONG + * ======================= */ + + static final VectorSpecies LSP = LongVector.SPECIES_PREFERRED; + static long[] longIn; + static long[] longOut; + static long la = 17L, lb = 9L; + + static { + longIn = new long[LSP.length()]; + longOut = new long[LSP.length()]; + for (int i = 0; i < LSP.length(); i++) { + longIn[i] = (long) i; + } + } + + // --- LONG ADD --- + + // bcast(a) ADD (bcast(b) ADD array) + @Test + @IR(applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.ADD_VL, IRNode.VECTOR_SIZE_ANY, " 1 ", IRNode.ADD_L, ">= 1", + IRNode.REPLICATE_L, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + @Warmup(value = 10000) + static void test_long_add_reassociation_pattern1() { + LongVector.broadcast(LSP, la) + .lanewise(VectorOperators.ADD, + LongVector.broadcast(LSP, lb) + .lanewise(VectorOperators.ADD, + LongVector.fromArray(LSP, longIn, 0))) + .intoArray(longOut, 0); + } + + // bcast(a) ADD (array ADD bcast(b)) + @Test + @IR(applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.ADD_VL, IRNode.VECTOR_SIZE_ANY, " 1 ", IRNode.ADD_L, ">= 1", + IRNode.REPLICATE_L, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + @Warmup(value = 10000) + static void test_long_add_reassociation_pattern2() { + LongVector.broadcast(LSP, la) + .lanewise(VectorOperators.ADD, + LongVector.fromArray(LSP, longIn, 0) + .lanewise(VectorOperators.ADD, + LongVector.broadcast(LSP, lb))) + .intoArray(longOut, 0); + } + + // (bcast(a) ADD array) ADD bcast(b) + @Test + @IR(applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.ADD_VL, IRNode.VECTOR_SIZE_ANY, " 1 ", IRNode.ADD_L, ">= 1", + IRNode.REPLICATE_L, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + @Warmup(value = 10000) + static void test_long_add_reassociation_pattern3() { + LongVector.broadcast(LSP, la) + .lanewise(VectorOperators.ADD, + LongVector.fromArray(LSP, longIn, 0)) + .lanewise(VectorOperators.ADD, + LongVector.broadcast(LSP, lb)) + .intoArray(longOut, 0); + } + + // (array ADD bcast(a)) ADD bcast(b) + @Test + @IR(applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.ADD_VL, IRNode.VECTOR_SIZE_ANY, " 1 ", IRNode.ADD_L, ">= 1", + IRNode.REPLICATE_L, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + @Warmup(value = 10000) + static void test_long_add_reassociation_pattern4() { + LongVector.fromArray(LSP, longIn, 0) + .lanewise(VectorOperators.ADD, + LongVector.broadcast(LSP, la)) + .lanewise(VectorOperators.ADD, + LongVector.broadcast(LSP, lb)) + .intoArray(longOut, 0); + } + + // --- LONG MUL --- + + // bcast(a) MUL (bcast(b) MUL array) + @Test + @IR(applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.MUL_VL, IRNode.VECTOR_SIZE_ANY, " 1 ", IRNode.MUL_L, ">= 1", + IRNode.REPLICATE_L, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + @Warmup(value = 10000) + static void test_long_mul_reassociation_pattern1() { + LongVector.broadcast(LSP, la) + .lanewise(VectorOperators.MUL, + LongVector.broadcast(LSP, lb) + .lanewise(VectorOperators.MUL, + LongVector.fromArray(LSP, longIn, 0))) + .intoArray(longOut, 0); + } + + // bcast(a) MUL (array MUL bcast(b)) + @Test + @IR(applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.MUL_VL, IRNode.VECTOR_SIZE_ANY, " 1 ", IRNode.MUL_L, ">= 1", + IRNode.REPLICATE_L, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + @Warmup(value = 10000) + static void test_long_mul_reassociation_pattern2() { + LongVector.broadcast(LSP, la) + .lanewise(VectorOperators.MUL, + LongVector.fromArray(LSP, longIn, 0) + .lanewise(VectorOperators.MUL, + LongVector.broadcast(LSP, lb))) + .intoArray(longOut, 0); + } + + // (bcast(a) MUL array) MUL bcast(b) + @Test + @IR(applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.MUL_VL, IRNode.VECTOR_SIZE_ANY, " 1 ", IRNode.MUL_L, ">= 1", + IRNode.REPLICATE_L, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + @Warmup(value = 10000) + static void test_long_mul_reassociation_pattern3() { + LongVector.broadcast(LSP, la) + .lanewise(VectorOperators.MUL, + LongVector.fromArray(LSP, longIn, 0)) + .lanewise(VectorOperators.MUL, + LongVector.broadcast(LSP, lb)) + .intoArray(longOut, 0); + } + + // (array MUL bcast(a)) MUL bcast(b) + @Test + @IR(applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.MUL_VL, IRNode.VECTOR_SIZE_ANY, " 1 ", IRNode.MUL_L, ">= 1", + IRNode.REPLICATE_L, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + @Warmup(value = 10000) + static void test_long_mul_reassociation_pattern4() { + LongVector.fromArray(LSP, longIn, 0) + .lanewise(VectorOperators.MUL, + LongVector.broadcast(LSP, la)) + .lanewise(VectorOperators.MUL, + LongVector.broadcast(LSP, lb)) + .intoArray(longOut, 0); + } + + /* ======================= + * SHORT + * ======================= */ + + static final VectorSpecies SSP = ShortVector.SPECIES_PREFERRED; + static short[] shortIn; + static short[] shortOut; + static short sa = 17, sb = 9; + + static { + shortIn = new short[SSP.length()]; + shortOut = new short[SSP.length()]; + for (int i = 0; i < SSP.length(); i++) { + shortIn[i] = (short) i; + } + } + + // --- SHORT ADD --- + + // bcast(a) ADD (bcast(b) ADD array) + @Test + @IR(applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.ADD_VS, IRNode.VECTOR_SIZE_ANY, " 1 ", IRNode.ADD_I, ">= 1", + IRNode.REPLICATE_S, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + @Warmup(value = 10000) + static void test_short_add_reassociation_pattern1() { + ShortVector.broadcast(SSP, sa) + .lanewise(VectorOperators.ADD, + ShortVector.broadcast(SSP, sb) + .lanewise(VectorOperators.ADD, + ShortVector.fromArray(SSP, shortIn, 0))) + .intoArray(shortOut, 0); + } + + // bcast(a) ADD (array ADD bcast(b)) + @Test + @IR(applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.ADD_VS, IRNode.VECTOR_SIZE_ANY, " 1 ", IRNode.ADD_I, ">= 1", + IRNode.REPLICATE_S, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + @Warmup(value = 10000) + static void test_short_add_reassociation_pattern2() { + ShortVector.broadcast(SSP, sa) + .lanewise(VectorOperators.ADD, + ShortVector.fromArray(SSP, shortIn, 0) + .lanewise(VectorOperators.ADD, + ShortVector.broadcast(SSP, sb))) + .intoArray(shortOut, 0); + } + + // (bcast(a) ADD array) ADD bcast(b) + @Test + @IR(applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.ADD_VS, IRNode.VECTOR_SIZE_ANY, " 1 ", IRNode.ADD_I, ">= 1", + IRNode.REPLICATE_S, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + @Warmup(value = 10000) + static void test_short_add_reassociation_pattern3() { + ShortVector.broadcast(SSP, sa) + .lanewise(VectorOperators.ADD, + ShortVector.fromArray(SSP, shortIn, 0)) + .lanewise(VectorOperators.ADD, + ShortVector.broadcast(SSP, sb)) + .intoArray(shortOut, 0); + } + + // (array ADD bcast(a)) ADD bcast(b) + @Test + @IR(applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.ADD_VS, IRNode.VECTOR_SIZE_ANY, " 1 ", IRNode.ADD_I, ">= 1", + IRNode.REPLICATE_S, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + @Warmup(value = 10000) + static void test_short_add_reassociation_pattern4() { + ShortVector.fromArray(SSP, shortIn, 0) + .lanewise(VectorOperators.ADD, + ShortVector.broadcast(SSP, sa)) + .lanewise(VectorOperators.ADD, + ShortVector.broadcast(SSP, sb)) + .intoArray(shortOut, 0); + } + + // --- SHORT MUL --- + + // bcast(a) MUL (bcast(b) MUL array) + @Test + @IR(applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.MUL_VS, IRNode.VECTOR_SIZE_ANY, " 1 ", IRNode.MUL_I, ">= 1", + IRNode.REPLICATE_S, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + @Warmup(value = 10000) + static void test_short_mul_reassociation_pattern1() { + ShortVector.broadcast(SSP, sa) + .lanewise(VectorOperators.MUL, + ShortVector.broadcast(SSP, sb) + .lanewise(VectorOperators.MUL, + ShortVector.fromArray(SSP, shortIn, 0))) + .intoArray(shortOut, 0); + } + + // bcast(a) MUL (array MUL bcast(b)) + @Test + @IR(applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.MUL_VS, IRNode.VECTOR_SIZE_ANY, " 1 ", IRNode.MUL_I, ">= 1", + IRNode.REPLICATE_S, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + @Warmup(value = 10000) + static void test_short_mul_reassociation_pattern2() { + ShortVector.broadcast(SSP, sa) + .lanewise(VectorOperators.MUL, + ShortVector.fromArray(SSP, shortIn, 0) + .lanewise(VectorOperators.MUL, + ShortVector.broadcast(SSP, sb))) + .intoArray(shortOut, 0); + } + + // (bcast(a) MUL array) MUL bcast(b) + @Test + @IR(applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.MUL_VS, IRNode.VECTOR_SIZE_ANY, " 1 ", IRNode.MUL_I, ">= 1", + IRNode.REPLICATE_S, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + @Warmup(value = 10000) + static void test_short_mul_reassociation_pattern3() { + ShortVector.broadcast(SSP, sa) + .lanewise(VectorOperators.MUL, + ShortVector.fromArray(SSP, shortIn, 0)) + .lanewise(VectorOperators.MUL, + ShortVector.broadcast(SSP, sb)) + .intoArray(shortOut, 0); + } + + // (array MUL bcast(a)) MUL bcast(b) + @Test + @IR(applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.MUL_VS, IRNode.VECTOR_SIZE_ANY, " 1 ", IRNode.MUL_I, ">= 1", + IRNode.REPLICATE_S, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + @Warmup(value = 10000) + static void test_short_mul_reassociation_pattern4() { + ShortVector.fromArray(SSP, shortIn, 0) + .lanewise(VectorOperators.MUL, + ShortVector.broadcast(SSP, sa)) + .lanewise(VectorOperators.MUL, + ShortVector.broadcast(SSP, sb)) + .intoArray(shortOut, 0); + } + + /* ======================= + * BYTE + * ======================= */ + + static final VectorSpecies BSP = ByteVector.SPECIES_PREFERRED; + static byte[] byteIn; + static byte[] byteOut; + static byte ba = 17, bb = 9; + + static { + byteIn = new byte[BSP.length()]; + byteOut = new byte[BSP.length()]; + for (int i = 0; i < BSP.length(); i++) { + byteIn[i] = (byte) i; + } + } + + // --- BYTE ADD --- + + // bcast(a) ADD (bcast(b) ADD array) + @Test + @IR(applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.ADD_VB, IRNode.VECTOR_SIZE_ANY, " 1 ", IRNode.ADD_I, ">= 1", + IRNode.REPLICATE_B, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + @Warmup(value = 10000) + static void test_byte_add_reassociation_pattern1() { + ByteVector.broadcast(BSP, ba) + .lanewise(VectorOperators.ADD, + ByteVector.broadcast(BSP, bb) + .lanewise(VectorOperators.ADD, + ByteVector.fromArray(BSP, byteIn, 0))) + .intoArray(byteOut, 0); + } + + // bcast(a) ADD (array ADD bcast(b)) + @Test + @IR(applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.ADD_VB, IRNode.VECTOR_SIZE_ANY, " 1 ", IRNode.ADD_I, ">= 1", + IRNode.REPLICATE_B, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + @Warmup(value = 10000) + static void test_byte_add_reassociation_pattern2() { + ByteVector.broadcast(BSP, ba) + .lanewise(VectorOperators.ADD, + ByteVector.fromArray(BSP, byteIn, 0) + .lanewise(VectorOperators.ADD, + ByteVector.broadcast(BSP, bb))) + .intoArray(byteOut, 0); + } + + // (bcast(a) ADD array) ADD bcast(b) + @Test + @IR(applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.ADD_VB, IRNode.VECTOR_SIZE_ANY, " 1 ", IRNode.ADD_I, ">= 1", + IRNode.REPLICATE_B, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + @Warmup(value = 10000) + static void test_byte_add_reassociation_pattern3() { + ByteVector.broadcast(BSP, ba) + .lanewise(VectorOperators.ADD, + ByteVector.fromArray(BSP, byteIn, 0)) + .lanewise(VectorOperators.ADD, + ByteVector.broadcast(BSP, bb)) + .intoArray(byteOut, 0); + } + + // (array ADD bcast(a)) ADD bcast(b) + @Test + @IR(applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.ADD_VB, IRNode.VECTOR_SIZE_ANY, " 1 ", IRNode.ADD_I, ">= 1", + IRNode.REPLICATE_B, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + @Warmup(value = 10000) + static void test_byte_add_reassociation_pattern4() { + ByteVector.fromArray(BSP, byteIn, 0) + .lanewise(VectorOperators.ADD, + ByteVector.broadcast(BSP, ba)) + .lanewise(VectorOperators.ADD, + ByteVector.broadcast(BSP, bb)) + .intoArray(byteOut, 0); + } + + // --- BYTE MUL --- + + // bcast(a) MUL (bcast(b) MUL array) + @Test + @IR(applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.MUL_VB, IRNode.VECTOR_SIZE_ANY, " 1 ", IRNode.MUL_I, ">= 1", + IRNode.REPLICATE_B, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + @Warmup(value = 10000) + static void test_byte_mul_reassociation_pattern1() { + ByteVector.broadcast(BSP, ba) + .lanewise(VectorOperators.MUL, + ByteVector.broadcast(BSP, bb) + .lanewise(VectorOperators.MUL, + ByteVector.fromArray(BSP, byteIn, 0))) + .intoArray(byteOut, 0); + } + + // bcast(a) MUL (array MUL bcast(b)) + @Test + @IR(applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.MUL_VB, IRNode.VECTOR_SIZE_ANY, " 1 ", IRNode.MUL_I, ">= 1", + IRNode.REPLICATE_B, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + @Warmup(value = 10000) + static void test_byte_mul_reassociation_pattern2() { + ByteVector.broadcast(BSP, ba) + .lanewise(VectorOperators.MUL, + ByteVector.fromArray(BSP, byteIn, 0) + .lanewise(VectorOperators.MUL, + ByteVector.broadcast(BSP, bb))) + .intoArray(byteOut, 0); + } + + // (bcast(a) MUL array) MUL bcast(b) + @Test + @IR(applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.MUL_VB, IRNode.VECTOR_SIZE_ANY, " 1 ", IRNode.MUL_I, ">= 1", + IRNode.REPLICATE_B, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + @Warmup(value = 10000) + static void test_byte_mul_reassociation_pattern3() { + ByteVector.broadcast(BSP, ba) + .lanewise(VectorOperators.MUL, + ByteVector.fromArray(BSP, byteIn, 0)) + .lanewise(VectorOperators.MUL, + ByteVector.broadcast(BSP, bb)) + .intoArray(byteOut, 0); + } + + // (array MUL bcast(a)) MUL bcast(b) + @Test + @IR(applyIfCPUFeatureOr = {"avx", "true", "asimd", "true"}, + counts = { IRNode.MUL_VB, IRNode.VECTOR_SIZE_ANY, " 1 ", IRNode.MUL_I, ">= 1", + IRNode.REPLICATE_B, IRNode.VECTOR_SIZE_ANY, ">= 1" }) + @Warmup(value = 10000) + static void test_byte_mul_reassociation_pattern4() { + ByteVector.fromArray(BSP, byteIn, 0) + .lanewise(VectorOperators.MUL, + ByteVector.broadcast(BSP, ba)) + .lanewise(VectorOperators.MUL, + ByteVector.broadcast(BSP, bb)) + .intoArray(byteOut, 0); + } +} diff --git a/test/hotspot/jtreg/gc/g1/TestHumongousAllocConcurrentStart.java b/test/hotspot/jtreg/gc/g1/TestHumongousAllocConcurrentStart.java index 0d932ef50d4b..5ee027d36481 100644 --- a/test/hotspot/jtreg/gc/g1/TestHumongousAllocConcurrentStart.java +++ b/test/hotspot/jtreg/gc/g1/TestHumongousAllocConcurrentStart.java @@ -41,9 +41,9 @@ public class TestHumongousAllocConcurrentStart { // Heap sizes < 224 MB are increased to 224 MB if vm_page_size == 64K to // fulfill alignment constraints. - private static final int heapSize = 224; // MB - private static final int heapRegionSize = 1; // MB - private static final int initiatingHeapOccupancyPercent = 50; // % + private static final int heapSize = 224; // MB + private static final int heapRegionSize = 1; // MB + private static final int G1IHOP = 50; // % public static void main(String[] args) throws Exception { OutputAnalyzer output = ProcessTools.executeLimitedTestJava( @@ -51,7 +51,7 @@ public static void main(String[] args) throws Exception { "-Xms" + heapSize + "m", "-Xmx" + heapSize + "m", "-XX:G1HeapRegionSize=" + heapRegionSize + "m", - "-XX:InitiatingHeapOccupancyPercent=" + initiatingHeapOccupancyPercent, + "-XX:G1IHOP=" + G1IHOP, "-Xlog:gc", HumongousObjectAllocator.class.getName()); @@ -70,7 +70,7 @@ public static void main(String [] args) throws Exception { // Number of objects to allocate to go above IHOP final int humongousObjectAllocations = - (int)((heapSize * initiatingHeapOccupancyPercent / 100.0) / heapRegionSize) + 1; + (int)((heapSize * G1IHOP / 100.0) / heapRegionSize) + 1; // Allocate for (int i = 1; i <= humongousObjectAllocations; i++) { diff --git a/test/hotspot/jtreg/gc/g1/TestHumongousCodeCacheRoots.java b/test/hotspot/jtreg/gc/g1/TestHumongousCodeCacheRoots.java index b12fd50ae694..fb5cc553e62c 100644 --- a/test/hotspot/jtreg/gc/g1/TestHumongousCodeCacheRoots.java +++ b/test/hotspot/jtreg/gc/g1/TestHumongousCodeCacheRoots.java @@ -115,7 +115,7 @@ public static void main(String[] args) throws Exception { final String[] baseArguments = new String[] { "-XX:+UseG1GC", "-XX:G1HeapRegionSize=1M", "-Xmx100M", // make sure we get a humongous region "-XX:+UnlockDiagnosticVMOptions", - "-XX:InitiatingHeapOccupancyPercent=1", // strong code root marking + "-XX:G1IHOP=1", // strong code root marking "-XX:+G1VerifyHeapRegionCodeRoots", "-XX:+VerifyAfterGC", // make sure that verification is run }; diff --git a/test/hotspot/jtreg/gc/g1/TestHumongousConcurrentStartUndo.java b/test/hotspot/jtreg/gc/g1/TestHumongousConcurrentStartUndo.java index b4302111b8c8..e5c60f967328 100644 --- a/test/hotspot/jtreg/gc/g1/TestHumongousConcurrentStartUndo.java +++ b/test/hotspot/jtreg/gc/g1/TestHumongousConcurrentStartUndo.java @@ -49,10 +49,10 @@ public class TestHumongousConcurrentStartUndo { // Heap sizes < 224 MB are increased to 224 MB if vm_page_size == 64K to // fulfill alignment constraints. - private static final int HeapSize = 224; // MB - private static final int HeapRegionSize = 1; // MB - private static final int InitiatingHeapOccupancyPercent = 50; // % - private static final int YoungSize = HeapSize / 8; + private static final int HeapSize = 224; // MB + private static final int HeapRegionSize = 1; // MB + private static final int G1IHOP = 50; // % + private static final int YoungSize = HeapSize / 8; public static void main(String[] args) throws Exception { OutputAnalyzer output = ProcessTools.executeLimitedTestJava( @@ -62,7 +62,7 @@ public static void main(String[] args) throws Exception { "-Xmx" + HeapSize + "m", "-Xmn" + YoungSize + "m", "-XX:G1HeapRegionSize=" + HeapRegionSize + "m", - "-XX:InitiatingHeapOccupancyPercent=" + InitiatingHeapOccupancyPercent, + "-XX:G1IHOP=" + G1IHOP, "-XX:-G1UseAdaptiveIHOP", "-XX:+UnlockDiagnosticVMOptions", "-XX:+WhiteBoxAPI", diff --git a/test/hotspot/jtreg/gc/g1/TestPrintRegionRememberedSetInfo.java b/test/hotspot/jtreg/gc/g1/TestPrintRegionRememberedSetInfo.java index a13cf1fa0136..46e8c051832f 100644 --- a/test/hotspot/jtreg/gc/g1/TestPrintRegionRememberedSetInfo.java +++ b/test/hotspot/jtreg/gc/g1/TestPrintRegionRememberedSetInfo.java @@ -61,7 +61,7 @@ public static String runTest(String arg) throws Exception { "-XX:+ExplicitGCInvokesConcurrent", "-XX:+UnlockDiagnosticVMOptions", "-XX:G1HeapRegionSize=1M", - "-XX:InitiatingHeapOccupancyPercent=0", + "-XX:G1IHOP=0", }; finalargs.addAll(Arrays.asList(defaultArgs)); diff --git a/test/hotspot/jtreg/gc/g1/TestRemsetLoggingTools.java b/test/hotspot/jtreg/gc/g1/TestRemsetLoggingTools.java index 65b532e43d4a..4b7daa0df4df 100644 --- a/test/hotspot/jtreg/gc/g1/TestRemsetLoggingTools.java +++ b/test/hotspot/jtreg/gc/g1/TestRemsetLoggingTools.java @@ -63,7 +63,7 @@ public static String runTest(String[] additionalArgs, int numGCs) throws Excepti "-Xms20m", "-Xmx20m", "-XX:ParallelGCThreads=1", - "-XX:InitiatingHeapOccupancyPercent=100", // we don't want the additional GCs due to marking + "-XX:G1IHOP=100", // we don't want the additional GCs due to marking "-XX:+UnlockDiagnosticVMOptions", "-XX:G1HeapRegionSize=1M", }; diff --git a/test/hotspot/jtreg/gc/g1/humongousObjects/TestHeapCounters.java b/test/hotspot/jtreg/gc/g1/humongousObjects/TestHeapCounters.java index 7deacca85774..36f06685a728 100644 --- a/test/hotspot/jtreg/gc/g1/humongousObjects/TestHeapCounters.java +++ b/test/hotspot/jtreg/gc/g1/humongousObjects/TestHeapCounters.java @@ -45,13 +45,13 @@ * * @run main/othervm -XX:+UseG1GC -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. * -Xmx128m -Xms128m - * -XX:G1HeapRegionSize=1M -XX:InitiatingHeapOccupancyPercent=100 -XX:-G1UseAdaptiveIHOP + * -XX:G1HeapRegionSize=1M -XX:G1IHOP=100 -XX:-G1UseAdaptiveIHOP * -Xlog:gc -Xlog:gc:file=TestHeapCountersRuntime.gc.log * gc.g1.humongousObjects.TestHeapCounters RUNTIME_COUNTER * * @run main/othervm -XX:+UseG1GC -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. * -Xmx128m -Xms128m - * -XX:G1HeapRegionSize=1M -XX:InitiatingHeapOccupancyPercent=100 -XX:-G1UseAdaptiveIHOP + * -XX:G1HeapRegionSize=1M -XX:G1IHOP=100 -XX:-G1UseAdaptiveIHOP * -Xlog:gc -Xlog:gc:file=TestHeapCountersMXBean.gc.log * gc.g1.humongousObjects.TestHeapCounters MX_BEAN_COUNTER */ diff --git a/test/hotspot/jtreg/gc/g1/humongousObjects/TestHumongousMovement.java b/test/hotspot/jtreg/gc/g1/humongousObjects/TestHumongousMovement.java index 8acc8fce56bd..3e393d57fb9a 100644 --- a/test/hotspot/jtreg/gc/g1/humongousObjects/TestHumongousMovement.java +++ b/test/hotspot/jtreg/gc/g1/humongousObjects/TestHumongousMovement.java @@ -46,7 +46,7 @@ * @build jdk.test.whitebox.WhiteBox * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox * @run main/othervm -XX:+UseG1GC -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -Xbootclasspath/a:. - * -XX:G1HeapRegionSize=1M -Xms200m -Xmx200m -XX:InitiatingHeapOccupancyPercent=100 + * -XX:G1HeapRegionSize=1M -Xms200m -Xmx200m -XX:G1IHOP=100 * -Xlog:gc=info:file=TestHumongousMovement.log * gc.g1.humongousObjects.TestHumongousMovement */ diff --git a/test/hotspot/jtreg/gc/g1/humongousObjects/TestObjectCollected.java b/test/hotspot/jtreg/gc/g1/humongousObjects/TestObjectCollected.java index b3a30d8f2f4c..7e79d0139c28 100644 --- a/test/hotspot/jtreg/gc/g1/humongousObjects/TestObjectCollected.java +++ b/test/hotspot/jtreg/gc/g1/humongousObjects/TestObjectCollected.java @@ -45,7 +45,7 @@ * * @run main/othervm -XX:+UseG1GC -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions * -XX:+WhiteBoxAPI -Xbootclasspath/a:. -Xms200m -Xmx200m -Xlog:gc,gc+humongous=debug - * -XX:InitiatingHeapOccupancyPercent=100 -XX:G1HeapRegionSize=1M -Xlog:gc,gc+humongous=debug:file=TestObjectCollected.gc.log + * -XX:G1IHOP=100 -XX:G1HeapRegionSize=1M -Xlog:gc,gc+humongous=debug:file=TestObjectCollected.gc.log * gc.g1.humongousObjects.TestObjectCollected */ diff --git a/test/hotspot/jtreg/gc/g1/ihop/TestIHOPErgo.java b/test/hotspot/jtreg/gc/g1/ihop/TestIHOPErgo.java index 83080175a156..9deea1797afe 100644 --- a/test/hotspot/jtreg/gc/g1/ihop/TestIHOPErgo.java +++ b/test/hotspot/jtreg/gc/g1/ihop/TestIHOPErgo.java @@ -71,7 +71,7 @@ public class TestIHOPErgo { "-Xlog:gc+ihop=debug,gc+ihop+ergo=debug,gc+ergo=debug", "-XX:+AlwaysTenure", "-XX:G1AdaptiveIHOPNumInitialSamples=1", - "-XX:InitiatingHeapOccupancyPercent=30" + "-XX:G1IHOP=30" }; public static void main(String[] args) throws Throwable { diff --git a/test/hotspot/jtreg/gc/g1/ihop/TestIHOPStatic.java b/test/hotspot/jtreg/gc/g1/ihop/TestIHOPStatic.java index 2028e8751d7b..26dfe932f599 100644 --- a/test/hotspot/jtreg/gc/g1/ihop/TestIHOPStatic.java +++ b/test/hotspot/jtreg/gc/g1/ihop/TestIHOPStatic.java @@ -114,13 +114,13 @@ public static void main(String[] args) throws Throwable { private static void runTest(int ihop, long pctToFill, long heapSize, boolean expectInitiationMessage) throws Throwable { System.out.println(""); System.out.println("IHOP test:"); - System.out.println(" InitiatingHeapOccupancyPercent : " + ihop); + System.out.println(" G1IHOP : " + ihop); System.out.println(" Part of heap to fill (percentage) : " + pctToFill); System.out.println(" MaxHeapSize : " + heapSize); System.out.println(" Expect for concurrent cycle initiation message : " + expectInitiationMessage); List options = new ArrayList<>(); Collections.addAll(options, - "-XX:InitiatingHeapOccupancyPercent=" + ihop, + "-XX:G1IHOP=" + ihop, "-Dmemory.fill=" + (heapSize * 1024 * 1024 * pctToFill / 100), "-XX:MaxHeapSize=" + heapSize + "M", "-XX:InitialHeapSize=" + heapSize + "M" diff --git a/test/hotspot/jtreg/gc/g1/mixedgc/TestLogging.java b/test/hotspot/jtreg/gc/g1/mixedgc/TestLogging.java index 28da65981413..b1d4a4ed5fc0 100644 --- a/test/hotspot/jtreg/gc/g1/mixedgc/TestLogging.java +++ b/test/hotspot/jtreg/gc/g1/mixedgc/TestLogging.java @@ -56,7 +56,7 @@ public class TestLogging { "-XX:+WhiteBoxAPI", "-Xms10M", "-Xmx10M", "-XX:NewSize=2M", "-XX:MaxNewSize=2M", "-XX:+AlwaysTenure", // surviving promote objects immediately - "-XX:InitiatingHeapOccupancyPercent=100", // set initial CMC threshold and disable adaptive IHOP + "-XX:G1IHOP=100", // set initial CMC threshold and disable adaptive IHOP "-XX:-G1UseAdaptiveIHOP", // to avoid additional concurrent cycles caused by ergonomics "-XX:G1MixedGCCountTarget=4", "-XX:MaxGCPauseMillis=30000", // to have enough time diff --git a/test/hotspot/jtreg/gc/g1/mixedgc/TestOldGenCollectionUsage.java b/test/hotspot/jtreg/gc/g1/mixedgc/TestOldGenCollectionUsage.java index 1bc92ef7cc0b..3204cd38971f 100644 --- a/test/hotspot/jtreg/gc/g1/mixedgc/TestOldGenCollectionUsage.java +++ b/test/hotspot/jtreg/gc/g1/mixedgc/TestOldGenCollectionUsage.java @@ -41,7 +41,7 @@ * @modules java.management * @build jdk.test.whitebox.WhiteBox * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox - * @run main/othervm -Xbootclasspath/a:. -XX:+UseG1GC -XX:+UnlockExperimentalVMOptions -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -verbose:gc -XX:NewSize=2m -XX:MaxNewSize=2m -Xmx14m -Xms14m -XX:+AlwaysTenure -XX:InitiatingHeapOccupancyPercent=100 -XX:-G1UseAdaptiveIHOP -XX:G1MixedGCCountTarget=4 -XX:MaxGCPauseMillis=30000 -XX:G1HeapRegionSize=1m -XX:G1HeapWastePercent=0 -XX:G1MixedGCLiveThresholdPercent=100 gc.g1.mixedgc.TestOldGenCollectionUsage + * @run main/othervm -Xbootclasspath/a:. -XX:+UseG1GC -XX:+UnlockExperimentalVMOptions -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI -verbose:gc -XX:NewSize=2m -XX:MaxNewSize=2m -Xmx14m -Xms14m -XX:+AlwaysTenure -XX:G1IHOP=100 -XX:-G1UseAdaptiveIHOP -XX:G1MixedGCCountTarget=4 -XX:MaxGCPauseMillis=30000 -XX:G1HeapRegionSize=1m -XX:G1HeapWastePercent=0 -XX:G1MixedGCLiveThresholdPercent=100 gc.g1.mixedgc.TestOldGenCollectionUsage */ // 8195115 says that for the "G1 Old Gen" MemoryPool, CollectionUsage.used diff --git a/test/hotspot/jtreg/gc/g1/plab/TestPLABEvacuationFailure.java b/test/hotspot/jtreg/gc/g1/plab/TestPLABEvacuationFailure.java index bc70b9c193c2..acba7c5355c4 100644 --- a/test/hotspot/jtreg/gc/g1/plab/TestPLABEvacuationFailure.java +++ b/test/hotspot/jtreg/gc/g1/plab/TestPLABEvacuationFailure.java @@ -70,7 +70,7 @@ public class TestPLABEvacuationFailure { private static final String[] COMMON_OPTIONS = { "-Xlog:gc,gc+plab=debug", "-XX:+UseG1GC", - "-XX:InitiatingHeapOccupancyPercent=100", + "-XX:G1IHOP=100", "-XX:-G1UseAdaptiveIHOP", "-XX:G1HeapRegionSize=1m"}; diff --git a/test/hotspot/jtreg/gc/shenandoah/TestLargeArrayInit.java b/test/hotspot/jtreg/gc/shenandoah/TestLargeArrayInit.java index 9ce254ff845d..83c74866a955 100644 --- a/test/hotspot/jtreg/gc/shenandoah/TestLargeArrayInit.java +++ b/test/hotspot/jtreg/gc/shenandoah/TestLargeArrayInit.java @@ -68,6 +68,18 @@ * TestLargeArrayInit */ +/* + * @test id=compressed-oops-off + * @summary Verify zero-initialization completeness for large arrays with compressed oops disabled + * @requires vm.gc.Shenandoah + * @library /test/lib + * + * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -Xmx512m -Xms512m + * -XX:+UseShenandoahGC -XX:ShenandoahGCHeuristics=adaptive + * -XX:-UseCompressedOops + * TestLargeArrayInit + */ + /** * * Allocates large byte[], int[], long[], and Object[] arrays whose sizes span diff --git a/test/hotspot/jtreg/gc/shenandoah/TestLargeArrayInitGCStress.java b/test/hotspot/jtreg/gc/shenandoah/TestLargeArrayInitGCStress.java index 34f3b70197a5..6dfd250e3cc4 100644 --- a/test/hotspot/jtreg/gc/shenandoah/TestLargeArrayInitGCStress.java +++ b/test/hotspot/jtreg/gc/shenandoah/TestLargeArrayInitGCStress.java @@ -69,6 +69,18 @@ * TestLargeArrayInitGCStress */ +/* + * @test id=compressed-oops-off + * @summary Verify correct object metadata for large arrays with compressed oops disabled under GC stress + * @requires vm.gc.Shenandoah + * @library /test/lib + * + * @run main/othervm -XX:+UnlockDiagnosticVMOptions -XX:+UnlockExperimentalVMOptions -Xmx256m -Xms256m + * -XX:+UseShenandoahGC -XX:ShenandoahGCHeuristics=aggressive + * -XX:-UseCompressedOops + * TestLargeArrayInitGCStress + */ + /** * * Allocates large arrays of various types under GC stress (aggressive heuristics, diff --git a/test/hotspot/jtreg/runtime/CommandLine/OptionsValidation/TestOptionsWithRanges.java b/test/hotspot/jtreg/runtime/CommandLine/OptionsValidation/TestOptionsWithRanges.java index eb0fd5f8be56..81ab75a95143 100644 --- a/test/hotspot/jtreg/runtime/CommandLine/OptionsValidation/TestOptionsWithRanges.java +++ b/test/hotspot/jtreg/runtime/CommandLine/OptionsValidation/TestOptionsWithRanges.java @@ -267,6 +267,13 @@ public static void main(String[] args) throws Exception { */ excludeTestMaxRange("CompileThresholdScaling"); + /* + * Do not test InitiatingHeapOccupancyPercent as it is an + * alias that will answer with the string G1IHOP. Remove this + * when the alias is removed. + */ + excludeTestRange("InitiatingHeapOccupancyPercent"); + List testSubset = getTestSubset(args); Asserts.assertGT(testSubset.size(), 0, "Options with ranges not found!"); diff --git a/test/hotspot/jtreg/runtime/ErrorHandling/TestDwarf.java b/test/hotspot/jtreg/runtime/ErrorHandling/TestDwarf.java index 6fe00498797d..309a77ae7ef9 100644 --- a/test/hotspot/jtreg/runtime/ErrorHandling/TestDwarf.java +++ b/test/hotspot/jtreg/runtime/ErrorHandling/TestDwarf.java @@ -29,6 +29,7 @@ * in the same directory as the libjvm.so file, in a subdirectory called .debug, or in the path specified * by the environment variable _JVM_DWARF_PATH, then no verification of the hs_err_file is done for libjvm.so. * @requires vm.debug == true & vm.flagless & vm.compMode != "Xint" & os.family == "linux" & !vm.graal.enabled & vm.gc.G1 + * @requires !vm.ubsan * @modules java.base/jdk.internal.misc * @run main/native/othervm -Xbootclasspath/a:. -XX:-CreateCoredumpOnCrash TestDwarf */ diff --git a/test/hotspot/jtreg/runtime/ErrorHandling/VeryEarlyAssertTest.java b/test/hotspot/jtreg/runtime/ErrorHandling/VeryEarlyAssertTest.java index 2d26079fa0ea..4534a609bb3a 100644 --- a/test/hotspot/jtreg/runtime/ErrorHandling/VeryEarlyAssertTest.java +++ b/test/hotspot/jtreg/runtime/ErrorHandling/VeryEarlyAssertTest.java @@ -1,6 +1,6 @@ /* - * Copyright (c) 2013, 2023, Oracle and/or its affiliates. All rights reserved. - * Copyright (c) 2018, 2022 SAP. All rights reserved. + * Copyright (c) 2013, 2026, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2018, 2026 SAP. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -30,6 +30,7 @@ * @library /test/lib * @modules java.base/jdk.internal.misc * @requires vm.flagless + * @requires !vm.ubsan * @requires (vm.debug == true) * @requires os.family == "linux" * @run driver VeryEarlyAssertTest diff --git a/test/hotspot/jtreg/runtime/Metaspace/DefineClass.java b/test/hotspot/jtreg/runtime/Metaspace/DefineClass.java index 302bf7cdc73b..9c7d1b5532a4 100644 --- a/test/hotspot/jtreg/runtime/Metaspace/DefineClass.java +++ b/test/hotspot/jtreg/runtime/Metaspace/DefineClass.java @@ -193,7 +193,7 @@ private static int getStringIndex(String needle, byte[] buf) { private static int getStringIndex(String needle, byte[] buf, int offset) { outer: - for (int i = offset; i < buf.length - offset - needle.length(); i++) { + for (int i = offset; i <= buf.length - needle.length(); i++) { for (int j = 0; j < needle.length(); j++) { if (buf[i + j] != (byte)needle.charAt(j)) continue outer; } diff --git a/test/hotspot/jtreg/runtime/interpreter/PopFrameMethodNameInvariantTest.java b/test/hotspot/jtreg/runtime/interpreter/PopFrameMethodNameInvariantTest.java new file mode 100644 index 000000000000..bd02227c4647 --- /dev/null +++ b/test/hotspot/jtreg/runtime/interpreter/PopFrameMethodNameInvariantTest.java @@ -0,0 +1,119 @@ +/* + * 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 runtime.interpreter; + +import com.sun.jdi.Bootstrap; +import com.sun.jdi.VirtualMachine; +import com.sun.jdi.connect.Connector; +import com.sun.jdi.connect.IllegalConnectorArgumentsException; +import com.sun.jdi.connect.LaunchingConnector; +import com.sun.jdi.connect.VMStartException; +import com.sun.jdi.event.BreakpointEvent; +import com.sun.jdi.event.ClassPrepareEvent; +import com.sun.jdi.event.Event; +import com.sun.jdi.event.EventSet; +import com.sun.jdi.request.ClassPrepareRequest; +import com.sun.jdi.request.EventRequestManager; +import jdk.test.lib.Utils; + +import java.io.IOException; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandleHelper; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.nio.file.Path; +import java.util.Map; + +/* + * @test + * @bug 8380080 + * @summary MemberName in local0 must be preserved when PopFrame re-executes direct MethodHandles.linkToStatic + * @requires vm.jvmti + * @requires vm.flavor != "zero" + * @library ../../compiler/jsr292/patches /test/lib + * @modules java.base/jdk.internal.vm.annotation + * jdk.jdi + * @build java.base/java.lang.invoke.MethodHandleHelper + * @run driver runtime.interpreter.PopFrameMethodNameInvariantTest + */ + +public class PopFrameMethodNameInvariantTest { + public static class Target { + static void body(float x) {} + public static void main(String[] args) throws Throwable { + MethodHandle target = MethodHandles.lookup().findStatic( + Target.class, + "body", + MethodType.methodType(void.class, float.class)); + Object name = MethodHandleHelper.internalMemberName(target); + MethodHandleHelper.linkToStatic(name, (float)1.0); + } + } + + public static void main(String[] args) throws Exception { + VirtualMachine vm = getVm(); + EventRequestManager eventRequestManager = vm.eventRequestManager(); + ClassPrepareRequest classPrepareRequest = eventRequestManager.createClassPrepareRequest(); + classPrepareRequest.addClassFilter(Target.class.getName()); + classPrepareRequest.enable(); + outerLoop: + while (true) { + EventSet eventSet = vm.eventQueue().remove(); + for (Event event : eventSet) { + if (event instanceof ClassPrepareEvent prepareEvent) { + eventRequestManager.createBreakpointRequest( + prepareEvent.referenceType() + .methodsByName("body") + .get(0) + .location() + ).enable(); + } + if (event instanceof BreakpointEvent breakpointEvent) { + breakpointEvent.request().disable(); + breakpointEvent.thread().popFrames(breakpointEvent.thread().frame(0)); + eventSet.resume(); + break outerLoop; + } + } + eventSet.resume(); + } + int exitCode = vm.process().waitFor(); + if (exitCode != 0) { + throw new RuntimeException("Debugee exited with code " + exitCode); + } + } + + private static VirtualMachine getVm() throws IOException, IllegalConnectorArgumentsException, VMStartException { + LaunchingConnector connector = Bootstrap.virtualMachineManager().defaultConnector(); + Map argumentMap = connector.defaultArguments(); + argumentMap.get("main").setValue(Target.class.getName()); + String options = String.join(" ", Utils.getTestJavaOpts()); + String patchPath = System.getProperty("test.patch.path"); + if (patchPath != null) { + options += " --patch-module=java.base=\"" + Path.of(patchPath, "java.base") + "\""; + } + argumentMap.get("options").setValue((options + " -Xint").trim()); + return connector.launch(argumentMap); + } +} diff --git a/test/hotspot/jtreg/runtime/jni/critical/SuspendInCritical.java b/test/hotspot/jtreg/runtime/jni/critical/SuspendInCritical.java new file mode 100644 index 000000000000..47ece0f1857e --- /dev/null +++ b/test/hotspot/jtreg/runtime/jni/critical/SuspendInCritical.java @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/** + * @test + * @bug 8373839 + * @requires vm.jvmti + * @library /test/lib + * @run main/othervm/native -agentlib:SuspendInCritical SuspendInCritical 1 + * @run main/othervm/native -agentlib:SuspendInCritical SuspendInCritical 2 + * @run main/othervm/native -agentlib:SuspendInCritical SuspendInCritical 3 + */ +import jdk.test.lib.Asserts; + +import java.util.concurrent.CountDownLatch; + +public class SuspendInCritical { + + static { + System.loadLibrary("SuspendInCritical"); + } + + static native void suspendThread(Thread t); + static native void resumeThread(Thread t); + static native boolean isSuspended(Thread t); + + static native void doCritical(byte[] b, String str); + static native void leaveCriticalNative(); + static native long getNativeCounter(); + + static volatile boolean suspendStarted = false; + static volatile boolean suspendCompleted = false; + static volatile boolean canTerminate = false; + + /* + * Incoming arg sets the mode: + * - 1: GetPrimitiveArrayCritical only + * - 2: GetStringCritical only + * - 3: Do both so we have a nested critical region + */ + public static void main(String[] args) throws Throwable { + int mode = Integer.parseInt(args[0]); + final byte[] bytes = (mode == 1 || mode == 3) ? new byte[16] : null; + final String str = (mode == 2 || mode == 3) ? "A String" : null; + + final Thread t = new Thread() { + public void run() { + System.out.println("CriticalThread calling native ..."); + doCritical(bytes, str); + System.out.println("CriticalThread return from native ..."); + // delay termination so we can check the suspend state + // from another thread + while (!canTerminate) { + delay(10); + } + System.out.println("CriticalThread terminating"); + } + }; + t.setName("CriticalThread"); + + System.out.println("main thread starting CriticalThread"); + + // Start the target thread. It will enter a JNI critical region + // and stay executing native code until released. + t.start(); + + // Check that the counter is progressing + checkNativeCounter(); + + System.out.println("main thread saw CriticalThread executing and is starting SuspenderThread"); + + // Now start the suspender thread. + Thread s = new Thread() { + public void run() { + suspendStarted = true; + System.out.println("SuspenderThread calling suspend ..."); + // This will block until t is out of the critical region + suspendThread(t); + suspendCompleted = true; + + System.out.println("SuspenderThread checking suspend ..."); + // Verify t is suspended + Asserts.assertTrue(isSuspended(t), "not suspended"); + + System.out.println("SuspenderThread calling resume ..."); + // Resume t + resumeThread(t); + + System.out.println("SuspenderThread checking not suspended ..."); + Asserts.assertFalse(isSuspended(t), "suspended"); + + System.out.println("SuspenderThread allowing target to terminate ..."); + // Allow t to terminate + canTerminate = true; + System.out.println("SuspenderThread terminatng"); + } + }; + s.setName("SuspenderThread"); + s.start(); + + while (!suspendStarted) { + delay(2); + } + + // Check suspender is blocked + checkSuspenderIsBlocked(); + + System.out.println("main thread confirms SuspenderThread is blocked in suspend()"); + + // Check target is still progressing + checkNativeCounter(); + + System.out.println("main thread saw CriticalThread still executing and has enabled the upcall"); + + // Allow target to proceed to Java upcall + leaveCriticalNative(); + + // Wait till target is in Java upcall + waitForUpcall(); + + System.out.println("main thread saw CriticalThread in upcall"); + + // Check suspender is blocked + checkSuspenderIsBlocked(); + + System.out.println("main thread confirms SuspenderThread is still blocked in suspend()"); + + // Check target is still executing + checkUpcallCount(); + + System.out.println("main thread confirms CriticalThread is still executing and will let it return and be suspended"); + + // Check suspender is still blocked + checkSuspenderIsBlocked(); + + // Allow target to return from Java and exit critical + upcallDone = true; + + // The suspension can now proceed, then both threads will terminate + t.join(); + s.join(); + System.out.println("main thread terminating"); + } + + static void checkNativeCounter() { + long counter = getNativeCounter(); + // If the counter never progresses then the test will timeout + while (counter == getNativeCounter()) { + delay(5); + } + } + + static void checkSuspenderIsBlocked() { + delay(200); + Asserts.assertTrue(!suspendCompleted,"Unexpected suspend completion"); + } + + static volatile boolean upcallDone = false; + static volatile long upcallCounter = 0; + static CountDownLatch inUpcall = new CountDownLatch(1); + + static void waitForUpcall() throws InterruptedException { + inUpcall.await(); + } + + static void upcall() { + inUpcall.countDown(); + System.out.println("CriticalThread is executing upcall"); + while (!upcallDone) { + upcallCounter++; + delay(1); + } + } + + static void checkUpcallCount() { + long count = upcallCounter; + // If the counter never progresses then the test will timeout + while (count == upcallCounter) { + delay(5); + } + } + + static void delay(long ms) { + try { + Thread.sleep(ms); + } + catch (InterruptedException ex) { + throw new Error("interrupted!"); + } + } + +} diff --git a/test/hotspot/jtreg/runtime/jni/critical/libSuspendInCritical.cpp b/test/hotspot/jtreg/runtime/jni/critical/libSuspendInCritical.cpp new file mode 100644 index 000000000000..0c5e95de6114 --- /dev/null +++ b/test/hotspot/jtreg/runtime/jni/critical/libSuspendInCritical.cpp @@ -0,0 +1,146 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include +#include +#include +#include +#include + +#include "jvmti.h" +#include "jvmti_common.hpp" + +static jvmtiEnv* jvmti = nullptr; + +static std::atomic stay_in_critical_native{JNI_TRUE}; +static std::atomic native_counter{0}; + +extern "C" { + +JNIEXPORT void JNICALL +Java_SuspendInCritical_suspendThread(JNIEnv* jni, jclass cls, jthread t) { + suspend_thread(jvmti, jni, t); +} + +JNIEXPORT void JNICALL +Java_SuspendInCritical_resumeThread(JNIEnv* jni, jclass cls, jthread t) { + resume_thread(jvmti, jni, t); +} + +JNIEXPORT jboolean JNICALL +Java_SuspendInCritical_isSuspended(JNIEnv* jni, jclass cls, jthread t) { + jint state = get_thread_state(jvmti, jni, t); + return (state & JVMTI_THREAD_STATE_SUSPENDED) != 0; +} + +JNIEXPORT void JNICALL +Java_SuspendInCritical_leaveCriticalNative(JNIEnv* jni, jclass cls) { + stay_in_critical_native = JNI_FALSE; +} + +JNIEXPORT jlong JNICALL +Java_SuspendInCritical_getNativeCounter(JNIEnv* jni, jclass cls) { + return native_counter; +} + +JNIEXPORT void JNICALL + Java_SuspendInCritical_doCritical(JNIEnv* jni, jclass cls, jbyteArray bytes, jstring str) { + jboolean is_copy = JNI_FALSE; + jbyte* b; + if (bytes != nullptr) { + LOG("CriticalThread doing GetPrimitiveArrayCritical\n"); + b = (jbyte*) jni->GetPrimitiveArrayCritical(bytes, &is_copy); + if (b == nullptr) { + jni->FatalError("GetPrimitiveArrayCritical returned null!"); + } + } + + const jchar* c; + if (str != nullptr) { + LOG("CriticalThread doing GetStringCritical\n"); + c = jni->GetStringCritical(str, &is_copy); + if (c == nullptr) { + jni->FatalError("GetStringCritical returned null!"); + } + } + + LOG("CriticalThread should now be in deferred suspension\n"); + // Do some visible work + while (stay_in_critical_native) { + native_counter++; + sleep_ms(1); + } + + LOG("CriticalThread released for Java upcall\n"); + // Now perform the Java upcall + jmethodID upcall_mid = jni->GetStaticMethodID(cls, "upcall", "()V"); + if (upcall_mid == nullptr) { + // Unexpected exception - let it propagate + if (bytes != nullptr) { + jni->ReleasePrimitiveArrayCritical(bytes, b, 0); + } + if (str != nullptr) { + jni->ReleaseStringCritical(str, c); + } + return; + } + jni->CallStaticVoidMethod(cls, upcall_mid); + + if (bytes != nullptr) { + jni->ReleasePrimitiveArrayCritical(bytes, b, 0); + } + if (str != nullptr) { + jni->ReleaseStringCritical(str, c); + } + + // Now we should suspend as we return to Java + LOG("CriticalThread returning to Java\n"); +} + +/** Agent library initialization. */ + +JNIEXPORT jint JNICALL +Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) { + LOG("\nAgent_OnLoad started\n"); + + // create JVMTI environment + if (jvm->GetEnv((void **) (&jvmti), JVMTI_VERSION_1_1) != JNI_OK) { + LOG("Wrong result of a valid call to GetEnv!\n"); + return JNI_ERR; + } + + // add specific capabilities for suspending thread + jvmtiCapabilities suspendCaps; + memset(&suspendCaps, 0, sizeof(suspendCaps)); + suspendCaps.can_suspend = 1; + + jvmtiError err = jvmti->AddCapabilities(&suspendCaps); + if (err != JVMTI_ERROR_NONE) { + LOG("(AddCapabilities) unexpected error: %s (%d)\n", TranslateError(err), err); + return JNI_ERR; + } + LOG("Agent_OnLoad finished\n"); + return JNI_OK; +} + +} // extern C diff --git a/test/hotspot/jtreg/runtime/vthread/RedefineClass.java b/test/hotspot/jtreg/runtime/vthread/RedefineClass.java index 33a9d52a5232..b17bb55701bc 100644 --- a/test/hotspot/jtreg/runtime/vthread/RedefineClass.java +++ b/test/hotspot/jtreg/runtime/vthread/RedefineClass.java @@ -120,7 +120,7 @@ private static int getStringIndex(String needle, byte[] buf) { private static int getStringIndex(String needle, byte[] buf, int offset) { outer: - for (int i = offset; i < buf.length - offset - needle.length(); i++) { + for (int i = offset; i <= buf.length - needle.length(); i++) { for (int j = 0; j < needle.length(); j++) { if (buf[i + j] != (byte)needle.charAt(j)) continue outer; } diff --git a/test/hotspot/jtreg/serviceability/sa/ClhsdbCDSCore.java b/test/hotspot/jtreg/serviceability/sa/ClhsdbCDSCore.java index ab5b73bf720f..e6a6e55c7f74 100644 --- a/test/hotspot/jtreg/serviceability/sa/ClhsdbCDSCore.java +++ b/test/hotspot/jtreg/serviceability/sa/ClhsdbCDSCore.java @@ -26,6 +26,7 @@ * @bug 8174994 8200613 * @summary Test the clhsdb commands 'printall', 'jstack' on a CDS enabled corefile. * @requires vm.hasSA + * @requires !vm.ubsan * @requires vm.gc != "Z" * @requires vm.cds * @requires vm.flavor == "server" diff --git a/test/hotspot/jtreg/serviceability/sa/ClhsdbFindPC.java b/test/hotspot/jtreg/serviceability/sa/ClhsdbFindPC.java index 1997e497af82..821ce1356e5d 100644 --- a/test/hotspot/jtreg/serviceability/sa/ClhsdbFindPC.java +++ b/test/hotspot/jtreg/serviceability/sa/ClhsdbFindPC.java @@ -50,6 +50,7 @@ * @bug 8193124 * @summary Test the clhsdb 'findpc' command with Xcomp on core file * @requires vm.hasSA + * @requires !vm.ubsan * @requires vm.gc != "Z" * @requires vm.compMode != "Xcomp" * @requires vm.compiler1.enabled @@ -75,6 +76,7 @@ * @bug 8193124 * @summary Test the clhsdb 'findpc' command w/o Xcomp on core file * @requires vm.hasSA + * @requires !vm.ubsan * @requires vm.gc != "Z" * @requires vm.compiler1.enabled * @library /test/lib diff --git a/test/hotspot/jtreg/serviceability/sa/ClhsdbLongConstant.java b/test/hotspot/jtreg/serviceability/sa/ClhsdbLongConstant.java index 7fd3eb23d026..051dd397db1b 100644 --- a/test/hotspot/jtreg/serviceability/sa/ClhsdbLongConstant.java +++ b/test/hotspot/jtreg/serviceability/sa/ClhsdbLongConstant.java @@ -107,7 +107,7 @@ private static void checkForTruncation(String longConstantOutput) throws Excepti // Expected value obtained from the CPU_SHA definition in vm_version_x86.hpp checkLongValue("VM_Version::CPU_SHA ", longConstantOutput, - 34L); + 31L); } } diff --git a/test/hotspot/jtreg/serviceability/sa/ClhsdbPmap.java b/test/hotspot/jtreg/serviceability/sa/ClhsdbPmap.java index 885694f7abbf..4079ee6f2b23 100644 --- a/test/hotspot/jtreg/serviceability/sa/ClhsdbPmap.java +++ b/test/hotspot/jtreg/serviceability/sa/ClhsdbPmap.java @@ -46,6 +46,7 @@ * @bug 8190198 * @summary Test clhsdb pmap command on a core file * @requires vm.hasSA + * @requires !vm.ubsan * @requires vm.gc != "Z" * @library /test/lib * @run main/othervm/timeout=480 ClhsdbPmap true diff --git a/test/hotspot/jtreg/serviceability/sa/ClhsdbPstack.java b/test/hotspot/jtreg/serviceability/sa/ClhsdbPstack.java index 6921a7ec0ca3..8f06cf755188 100644 --- a/test/hotspot/jtreg/serviceability/sa/ClhsdbPstack.java +++ b/test/hotspot/jtreg/serviceability/sa/ClhsdbPstack.java @@ -46,6 +46,7 @@ * @bug 8190198 * @summary Test clhsdb pstack command on a core file * @requires vm.hasSA + * @requires !vm.ubsan * @requires vm.gc != "Z" * @library /test/lib * @run main/othervm/timeout=480 ClhsdbPstack true diff --git a/test/hotspot/jtreg/vmTestbase/gc/ArrayJuggle/Juggle2.java b/test/hotspot/jtreg/vmTestbase/gc/ArrayJuggle/Juggle2.java index eff35559626d..839d8200da0b 100644 --- a/test/hotspot/jtreg/vmTestbase/gc/ArrayJuggle/Juggle2.java +++ b/test/hotspot/jtreg/vmTestbase/gc/ArrayJuggle/Juggle2.java @@ -33,7 +33,7 @@ /* * The next test stresses the interaction between (mostly) full garbage collections and refinement. */ -/* @test @key stress randomness @library /vmTestbase /test/lib @run main/othervm -XX:-G1UseAdaptiveIHOP -XX:InitiatingHeapOccupancyPercent=0 -XX:G1HeapRegionSize=1m -XX:G1RSetUpdatingPauseTimePercent=0 -XX:+UnlockDiagnosticVMOptions -XX:G1PerThreadPendingCardThreshold=0 -XX:+VerifyAfterGC -Xlog:gc=debug,gc+refine=debug:gc.log gc.ArrayJuggle.Juggle2 -tg */ +/* @test @key stress randomness @library /vmTestbase /test/lib @run main/othervm -XX:-G1UseAdaptiveIHOP -XX:G1IHOP=0 -XX:G1HeapRegionSize=1m -XX:G1RSetUpdatingPauseTimePercent=0 -XX:+UnlockDiagnosticVMOptions -XX:G1PerThreadPendingCardThreshold=0 -XX:+VerifyAfterGC -Xlog:gc=debug,gc+refine=debug:gc.log gc.ArrayJuggle.Juggle2 -tg */ package gc.ArrayJuggle; diff --git a/test/hotspot/jtreg/vmTestbase/jit/Arrays/ArrayBounds/ArrayBounds.java b/test/hotspot/jtreg/vmTestbase/jit/Arrays/ArrayBounds/ArrayBounds.java index b0b7bcdccde4..ac9a442cfb20 100644 --- a/test/hotspot/jtreg/vmTestbase/jit/Arrays/ArrayBounds/ArrayBounds.java +++ b/test/hotspot/jtreg/vmTestbase/jit/Arrays/ArrayBounds/ArrayBounds.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -34,9 +34,6 @@ package jit.Arrays.ArrayBounds; -/* -SCCS ID : @(#)ArrayBounds.java 1.2 02/07/16 -*/ /* The intent of this Java program is to expose Virtual Machines that make illegal array bounds check removal optimizations. */ diff --git a/test/hotspot/jtreg/vmTestbase/jit/verifier/VerifyInitLocal/VerifyInitLocal.java b/test/hotspot/jtreg/vmTestbase/jit/verifier/VerifyInitLocal/VerifyInitLocal.java index d4ab2ee30bb0..76d6b59e542e 100644 --- a/test/hotspot/jtreg/vmTestbase/jit/verifier/VerifyInitLocal/VerifyInitLocal.java +++ b/test/hotspot/jtreg/vmTestbase/jit/verifier/VerifyInitLocal/VerifyInitLocal.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -40,7 +40,6 @@ import nsk.share.TestFailure; /** - * @(#)VerifyInitLocal.java 1.1 01/03/15 * @bug 4408261 * @summary Make sure verifier allows initialization of local fields. */ diff --git a/test/hotspot/jtreg/vmTestbase/jit/verifier/VerifyMergeStack/VerifyMergeStack.java b/test/hotspot/jtreg/vmTestbase/jit/verifier/VerifyMergeStack/VerifyMergeStack.java index fe2171e7ed99..5f50b5a73394 100644 --- a/test/hotspot/jtreg/vmTestbase/jit/verifier/VerifyMergeStack/VerifyMergeStack.java +++ b/test/hotspot/jtreg/vmTestbase/jit/verifier/VerifyMergeStack/VerifyMergeStack.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2008, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2008, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -38,7 +38,6 @@ import nsk.share.TestFailure; /** - * @(#)VerifyMergeStack.java 1.1 01/25/01 * @bug 4336916 * @summary Make sure verifier fails when two distinct types meet on operand stack */ diff --git a/test/hotspot/jtreg/vmTestbase/nsk/share/jdb/Launcher.java b/test/hotspot/jtreg/vmTestbase/nsk/share/jdb/Launcher.java index 32d4bd8b8651..01fe847ec337 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/share/jdb/Launcher.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/share/jdb/Launcher.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2002, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -47,13 +47,6 @@ public class Launcher extends DebugeeBinder { /** Pattern for message of jdb has started. */ protected static String JDB_STARTED = "Initializing jdb"; - /** - * Get version string. - */ - public static String getVersion () { - return "@(#)Launcher.java %I% %E%"; - } - // -------------------------------------------------- // /** diff --git a/test/hotspot/jtreg/vmTestbase/nsk/share/jdi/Binder.java b/test/hotspot/jtreg/vmTestbase/nsk/share/jdi/Binder.java index a30afed77d85..558ade41c8df 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/share/jdi/Binder.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/share/jdi/Binder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -63,13 +63,6 @@ public class Binder extends DebugeeBinder { */ public static final String LOG_PREFIX = "binder> "; - /** - * Get version string. - */ - public static String getVersion () { - return "@(#)Binder.java 1.14 03/10/08"; - } - // -------------------------------------------------- // /** diff --git a/test/hotspot/jtreg/vmTestbase/nsk/share/jdwp/Binder.java b/test/hotspot/jtreg/vmTestbase/nsk/share/jdwp/Binder.java index 0a15c23b004a..ef1cf2943c15 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/share/jdwp/Binder.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/share/jdwp/Binder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -55,13 +55,6 @@ final public class Binder extends DebugeeBinder { */ public static final String LOG_PREFIX = "binder> "; - /** - * Get version string. - */ - public static String getVersion () { - return "@(#)Binder.java %I% %E%"; - } - // -------------------------------------------------- // /** diff --git a/test/hotspot/jtreg/vmTestbase/nsk/share/jpda/DebugeeBinder.java b/test/hotspot/jtreg/vmTestbase/nsk/share/jpda/DebugeeBinder.java index d04fed1bd328..03142768b47c 100644 --- a/test/hotspot/jtreg/vmTestbase/nsk/share/jpda/DebugeeBinder.java +++ b/test/hotspot/jtreg/vmTestbase/nsk/share/jpda/DebugeeBinder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -88,13 +88,6 @@ public class DebugeeBinder extends Log.Logger { private DebugeeArgumentHandler argumentHandler = null; - /** - * Get version string. - */ - public static String getVersion () { - return "@(#)Binder.java %I% %E%"; - } - // -------------------------------------------------- // private ServerSocket pipeServerSocket = null; diff --git a/test/jdk/ProblemList.txt b/test/jdk/ProblemList.txt index b39a0d093d89..3e1437f01f5d 100644 --- a/test/jdk/ProblemList.txt +++ b/test/jdk/ProblemList.txt @@ -151,7 +151,6 @@ java/awt/grab/EmbeddedFrameTest1/EmbeddedFrameTest1.java 7080150 macosx-all java/awt/event/InputEvent/EventWhenTest/EventWhenTest.java 8168646 generic-all java/awt/List/KeyEventsTest/KeyEventsTest.java 8201307 linux-all java/awt/List/NoEvents/ProgrammaticChange.java 8201307 linux-all -java/awt/List/ListSelection/SelectInvalidTest.java 8369455 linux-all java/awt/Paint/ListRepaint.java 8201307 linux-all java/awt/Mixing/AWT_Mixing/OpaqueOverlappingChoice.java 8048171 generic-all java/awt/Mixing/AWT_Mixing/JInternalFrameMoveOverlapping.java 6986109 windows-all @@ -378,7 +377,6 @@ java/awt/GraphicsDevice/DisplayModes/UnknownRefrshRateTest.java 8286436 macosx-a java/awt/image/multiresolution/MultiresolutionIconTest.java 8291979 linux-x64,windows-all java/awt/event/SequencedEvent/MultipleContextsFunctionalTest.java 8305061 macosx-x64 sun/java2d/DirectX/OnScreenRenderingResizeTest/OnScreenRenderingResizeTest.java 8301177 linux-x64 -sun/awt/image/bug8038000.java 8373065 generic-all # Several tests which fail on some hidpi systems/macosx12-aarch64 system java/awt/Window/8159168/SetShapeTest.java 8274106 macosx-aarch64 diff --git a/test/jdk/java/awt/EventDispatchThread/LoopRobustness/LoopRobustness.java b/test/jdk/java/awt/EventDispatchThread/LoopRobustness/LoopRobustness.java index 46e5473a2f31..ab7dcff7179f 100644 --- a/test/jdk/java/awt/EventDispatchThread/LoopRobustness/LoopRobustness.java +++ b/test/jdk/java/awt/EventDispatchThread/LoopRobustness/LoopRobustness.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,19 +27,17 @@ * @key headful * @summary Checks that an Error which propogates up to the EventDispatch * loop does not crash AWT. - * @author Andrei Dmitriev: area=awt.event - * @library ../../regtesthelpers - * @modules java.desktop/sun.awt - * @build Util * @run main LoopRobustness */ -import java.awt.*; -import java.awt.event.*; - -import sun.awt.SunToolkit; - -import test.java.awt.regtesthelpers.Util; +import java.awt.AWTException; +import java.awt.Button; +import java.awt.EventQueue; +import java.awt.Frame; +import java.awt.Robot; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.InputEvent; public class LoopRobustness { @@ -50,15 +48,15 @@ public class LoopRobustness { public static volatile boolean notifyOccured = false; public static volatile boolean otherExceptionsCaught = false; - public static void main(String [] args) throws Exception { - SunToolkit.createNewAppContext(); + public static void main(String[] args) throws Exception { ThreadGroup mainThreadGroup = Thread.currentThread().getThreadGroup(); + Impl impl = new Impl(); long at; //wait for a TIMEOUT giving a chance to a new Thread above to accomplish its stuff. synchronized (LoopRobustness.LOCK) { - new Thread(new TestThreadGroup(mainThreadGroup, "TestGroup"), new Impl()).start(); + new Thread(new TestThreadGroup(mainThreadGroup, "TestGroup"), impl).start(); at = System.currentTimeMillis(); try { while (!notifyOccured && (System.currentTimeMillis() - at < TIMEOUT)) { @@ -76,7 +74,7 @@ public static void main(String [] args) throws Exception { //now wait for two clicks at = System.currentTimeMillis(); - while(System.currentTimeMillis() - at < TIMEOUT && clicks < 2) { + while (System.currentTimeMillis() - at < TIMEOUT && clicks < 2) { try { Thread.sleep(100); } catch(InterruptedException e) { @@ -89,16 +87,26 @@ public static void main(String [] args) throws Exception { if (otherExceptionsCaught) { throw new RuntimeException("Test FAILED: unexpected exceptions caught"); } + Frame f = impl.getFrame(); + if (f != null) { + EventQueue.invokeAndWait(() -> f.dispose()); + } } } -class Impl implements Runnable{ +class Impl implements Runnable { static Robot robot; - public void run() { - SunToolkit.createNewAppContext(); + volatile Button b; + volatile Frame lr; + + public Frame getFrame() { + return lr; + } + + void createUI() { - Button b = new Button("Press me to test the AWT-Event Queue thread"); - Frame lr = new Frame("ROBUST FRAME"); + b = new Button("Press me to test the AWT-Event Queue thread"); + lr = new Frame("ROBUST FRAME"); lr.setBounds(100, 100, 300, 100); b.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { @@ -109,13 +117,19 @@ public void actionPerformed(ActionEvent e) { }); lr.add(b); lr.setVisible(true); + } + + public void run() { try { + EventQueue.invokeAndWait(() -> createUI()); robot = new Robot(); } catch (AWTException e) { throw new RuntimeException("Test interrupted.", e); + } catch (Exception e) { + throw new RuntimeException("UI creation failed.", e); } - Util.waitForIdle(robot); + robot.waitForIdle(); synchronized (LoopRobustness.LOCK){ LoopRobustness.LOCK.notify(); @@ -126,11 +140,11 @@ public void actionPerformed(ActionEvent e) { while (i < 2) { robot.mouseMove(b.getLocationOnScreen().x + b.getWidth()/2, b.getLocationOnScreen().y + b.getHeight()/2); - Util.waitForIdle(robot); - robot.mousePress(InputEvent.BUTTON1_MASK); - Util.waitForIdle(robot); - robot.mouseRelease(InputEvent.BUTTON1_MASK); - Util.waitForIdle(robot); + robot.waitForIdle(); + robot.mousePress(InputEvent.BUTTON1_DOWN_MASK); + robot.waitForIdle(); + robot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK); + robot.waitForIdle(); i++; } } diff --git a/test/jdk/java/awt/List/ListSelection/SelectInvalidTest.java b/test/jdk/java/awt/List/ListSelection/SelectInvalidTest.java index ac29b270c81f..247599857257 100644 --- a/test/jdk/java/awt/List/ListSelection/SelectInvalidTest.java +++ b/test/jdk/java/awt/List/ListSelection/SelectInvalidTest.java @@ -32,7 +32,7 @@ /** * @test - * @bug 8369327 + * @bug 8369327 8369455 * @summary Test awt list selection of invalid indexes * @key headful * @library /test/lib diff --git a/test/jdk/java/lang/LazyConstant/DemoContainerInjectionTest.java b/test/jdk/java/lang/LazyConstant/DemoContainerInjectionTest.java index df51a5622107..0878ec037891 100644 --- a/test/jdk/java/lang/LazyConstant/DemoContainerInjectionTest.java +++ b/test/jdk/java/lang/LazyConstant/DemoContainerInjectionTest.java @@ -58,15 +58,6 @@ void ComputedComponentsViaLambda() { assertContainerPopulated(container); } - @Test - void SettableComponents() { - SettableContainer container = SettableScratchContainer.of(Set.of(Foo.class, Bar.class)); - container.set(Foo.class, new FooImpl()); - container.set(Bar.class, new BarImpl()); - assertContainerPopulated(container); - } - - @Test void ProviderComponents() { Container container = ProviderContainer.of(Map.of( @@ -93,10 +84,6 @@ interface Container { T get(Class type); } - interface SettableContainer extends Container { - void set(Class type, T implementation); - } - record ComputedContainer(Map, ?> components) implements Container { @Override @@ -110,26 +97,6 @@ static Container of(Set> components, Function, ?> mapper) { } - record SettableScratchContainer(Map, Object> scratch, Map, ?> components) implements SettableContainer { - - @Override - public void set(Class type, T implementation) { - if (scratch.putIfAbsent(type, type.cast(implementation)) != null) { - throw new IllegalStateException("Can only set once for " + type); - } - } - - @Override - public T get(Class type) { - return type.cast(components.get(type)); - } - - static SettableContainer of(Set> components) { - Map, Object> scratch = new ConcurrentHashMap<>(); - return new SettableScratchContainer(scratch, Map.ofLazy(components, scratch::get)); - } - - } record ProviderContainer(Map, ?> components) implements Container { diff --git a/test/jdk/java/lang/LazyConstant/DemoImperativeTest.java b/test/jdk/java/lang/LazyConstant/DemoImperativeTest.java deleted file mode 100644 index bc1208e67f88..000000000000 --- a/test/jdk/java/lang/LazyConstant/DemoImperativeTest.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. - * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. - * - * This code is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License version 2 only, as - * published by the Free Software Foundation. - * - * This code is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License - * version 2 for more details (a copy is included in the LICENSE file that - * accompanied this code). - * - * You should have received a copy of the GNU General Public License version - * 2 along with this work; if not, write to the Free Software Foundation, - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. - * - * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA - * or visit www.oracle.com if you need additional information or have any - * questions. - */ - -/* @test - * @summary Test of a demo of an imperative stable value based on a lazy constant - * @enablePreview - * @run junit DemoImperativeTest - */ - -import org.junit.jupiter.api.Test; - -import java.util.concurrent.atomic.AtomicReference; - -import static org.junit.jupiter.api.Assertions.*; - -final class DemoImperativeTest { - - interface ImperativeStableValue { - T orElse(T other); - boolean isSet(); - boolean trySet(T t); - T get(); - - static ImperativeStableValue of() { - var scratch = new AtomicReference(); - return new Impl<>(scratch, LazyConstant.of(scratch::get)); - } - - } - - - private record Impl(AtomicReference scratch, - LazyConstant underlying) implements ImperativeStableValue { - - @Override - public boolean trySet(T t) { - final boolean result = scratch.compareAndSet(null, t); - if (result) { - // Actually set the value - get(); - } - return result; - } - - @Override public T orElse(T other) { return underlying.orElse(other); } - @Override public boolean isSet() { return underlying.isInitialized(); } - @Override public T get() { return underlying.get(); } - - } - - @Test - void basic() { - var stableValue = ImperativeStableValue.of(); - assertFalse(stableValue.isSet()); - assertEquals(13, stableValue.orElse(13)); - assertTrue(stableValue.trySet(42)); - assertFalse(stableValue.trySet(13)); - assertTrue(stableValue.isSet()); - assertEquals(42, stableValue.get()); - assertEquals(42, stableValue.orElse(13)); - } - -} diff --git a/test/jdk/java/lang/LazyConstant/LazyConstantClassUnloading.java b/test/jdk/java/lang/LazyConstant/LazyConstantClassUnloading.java new file mode 100644 index 000000000000..fcc9920aa396 --- /dev/null +++ b/test/jdk/java/lang/LazyConstant/LazyConstantClassUnloading.java @@ -0,0 +1,184 @@ +/* + * 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 LazyConstant should not retain throwable classes after failed computation + * @enablePreview + * @modules java.base/java.lang.ref:open + * @library /test/lib + * @build jdk.test.whitebox.WhiteBox + * @requires vm.opt.final.ClassUnloading + * @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox + * @run main/othervm + * -Xbootclasspath/a:. + * -XX:+UnlockDiagnosticVMOptions + * -XX:+WhiteBoxAPI + * LazyConstantClassUnloading + */ + +import jdk.test.lib.Utils; +import jdk.test.whitebox.WhiteBox; + +import java.io.ByteArrayOutputStream; +import java.lang.LazyConstant; +import java.lang.ref.WeakReference; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.NoSuchElementException; +import java.util.function.BooleanSupplier; +import java.util.function.Supplier; +import javax.tools.JavaCompiler; +import javax.tools.ToolProvider; + +public class LazyConstantClassUnloading { + + private static final String THROWABLE_NAME = "test.lazyconstant.GeneratedProblem"; + private static final String SUPPLIER_NAME = "test.lazyconstant.ThrowingSupplier"; + + private static WhiteBox wb; + + public static void main(String[] args) throws Exception { + TestState state = createFailedLazyConstant(); + + if (!waitFor(() -> state.loaderRef().refersTo(null), Utils.adjustTimeout(4_000L))) { + throw new AssertionError("The throwing supplier class loader was not unloaded"); + } + + assertLazyAccessFails(state.lazyConstant(), THROWABLE_NAME); + } + + private static TestState createFailedLazyConstant() throws Exception { + Path sourceDir = Files.createTempDirectory("lazy-constant-throwables-src"); + Path classesDir = Files.createTempDirectory("lazy-constant-throwables-classes"); + + writeSource(sourceDir, "GeneratedProblem.java", """ + package test.lazyconstant; + + public class GeneratedProblem extends RuntimeException { + } + """); + writeSource(sourceDir, "ThrowingSupplier.java", """ + package test.lazyconstant; + + import java.util.function.Supplier; + + public class ThrowingSupplier implements Supplier { + @Override + public String get() { + throw new GeneratedProblem(); + } + } + """); + compile(sourceDir, classesDir); + + URLClassLoader loader = new URLClassLoader(new URL[] { classesDir.toUri().toURL() }, + LazyConstantClassUnloading.class.getClassLoader()); + WeakReference loaderRef = new WeakReference<>(loader); + + Class supplierClass = Class.forName(SUPPLIER_NAME, true, loader); + @SuppressWarnings("unchecked") + Supplier supplier = + (Supplier) supplierClass.getConstructor().newInstance(); + + LazyConstant lazyConstant = LazyConstant.of(supplier); + assertLazyAccessFails(lazyConstant, THROWABLE_NAME); + + supplier = null; + supplierClass = null; + loader.close(); + loader = null; + + return new TestState(lazyConstant, loaderRef); + } + + private static void writeSource(Path sourceDir, String fileName, String source) throws Exception { + Path file = sourceDir.resolve("test/lazyconstant").resolve(fileName); + Files.createDirectories(file.getParent()); + Files.writeString(file, source); + } + + private static void compile(Path sourceDir, Path classesDir) throws Exception { + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + if (compiler == null) { + throw new AssertionError("No system Java compiler available"); + } + + ByteArrayOutputStream output = new ByteArrayOutputStream(); + int exitCode = compiler.run(null, output, output, + "-d", classesDir.toString(), + sourceDir.resolve("test/lazyconstant/GeneratedProblem.java").toString(), + sourceDir.resolve("test/lazyconstant/ThrowingSupplier.java").toString()); + if (exitCode != 0) { + throw new AssertionError("Compilation failed: " + output); + } + } + + private static void assertLazyAccessFails(LazyConstant lazyConstant, String throwableName) { + var x = assertThrows(NoSuchElementException.class, () -> lazyConstant.get()); + var message = x.getMessage(); + assertTrue(message.contains(throwableName), "Missing throwable name in message: " + message); + } + + private static boolean waitFor(BooleanSupplier condition, long timeoutMillis) { + final long deadline = System.currentTimeMillis() + timeoutMillis; + wb = WhiteBox.getWhiteBox(); + wb.fullGC(); + boolean refProResult; + boolean conditionValue; + try { + do { + refProResult = wb.waitForReferenceProcessing(); + conditionValue = condition.getAsBoolean(); + if (System.currentTimeMillis() > deadline) { + throw new AssertionError("Timed out waiting for reference"); + } + } while (refProResult || !conditionValue); + return conditionValue; + } catch (InterruptedException e) { + throw new AssertionError(e); + } + } + + static X assertThrows(Class type, Runnable runnable) { + try { + runnable.run(); + } catch (Throwable t) { + if (t.getClass().equals(type)) { + return (X) t; + } + throw new AssertionError("Expected " + type + " to be thrown", t); + } + throw new AssertionError("Expected " + type + " to be thrown but nothing was thrown."); + } + + static void assertTrue(boolean value, String message) { + if (!value) { + throw new AssertionError(message); + } + } + + private record TestState(LazyConstant lazyConstant, WeakReference loaderRef) { } +} diff --git a/test/jdk/java/lang/LazyConstant/LazyConstantSafePublicationTest.java b/test/jdk/java/lang/LazyConstant/LazyConstantSafePublicationTest.java index 3c51d4afd90e..84e6ddd5444e 100644 --- a/test/jdk/java/lang/LazyConstant/LazyConstantSafePublicationTest.java +++ b/test/jdk/java/lang/LazyConstant/LazyConstantSafePublicationTest.java @@ -30,6 +30,7 @@ * @run junit LazyConstantSafePublicationTest */ +import jdk.internal.lang.LazyConstantImpl; import jdk.test.lib.Utils; import org.junit.jupiter.api.Test; @@ -64,18 +65,18 @@ static final class Holder { static final class Consumer implements Runnable { - final LazyConstant[] constants; + final LazyConstantImpl[] constants; final int[] observations = new int[SIZE]; int i = 0; - public Consumer(LazyConstant[] constants) { + public Consumer(LazyConstantImpl[] constants) { this.constants = constants; } @Override public void run() { for (; i < SIZE; i++) { - LazyConstant s = constants[i]; + LazyConstantImpl s = constants[i]; Holder h; // Wait until the ComputedConstant has a holder value while ((h = s.orElse(null)) == null) { Thread.onSpinWait();} @@ -91,15 +92,15 @@ public void run() { static final class Producer implements Runnable { - final LazyConstant[] constants; + final LazyConstantImpl[] constants; - public Producer(LazyConstant[] constants) { + public Producer(LazyConstantImpl[] constants) { this.constants = constants; } @Override public void run() { - LazyConstant s; + LazyConstantImpl s; long deadlineNs = System.nanoTime(); for (int i = 0; i < SIZE; i++) { s = constants[i]; @@ -114,7 +115,7 @@ public void run() { @Test void mainTest() { - final LazyConstant[] constants = constants(); + final LazyConstantImpl[] constants = constants(); List consumers = IntStream.range(0, THREADS) .mapToObj(_ -> new Consumer(constants)) .toList(); @@ -150,7 +151,7 @@ void mainTest() { assertEquals(THREADS * SIZE, histogram[63]); } - static void join(final LazyConstant[] constants, List consumers, Thread... threads) { + static void join(final LazyConstantImpl[] constants, List consumers, Thread... threads) { try { for (Thread t:threads) { long deadline = System.nanoTime() + Utils.adjustTimeout(TimeUnit.MINUTES.toNanos(4)); @@ -180,11 +181,11 @@ static void join(final LazyConstant[] constants, List consumer } } - static LazyConstant[] constants() { + static LazyConstantImpl[] constants() { @SuppressWarnings("unchecked") - LazyConstant[] constants = (LazyConstant[]) new LazyConstant[SIZE]; + LazyConstantImpl[] constants = (LazyConstantImpl[]) new LazyConstantImpl[SIZE]; for (int i = 0; i < SIZE; i++) { - constants[i] = LazyConstant.of(Holder::new); + constants[i] = LazyConstantImpl.ofLazy(Holder::new); } return constants; } diff --git a/test/jdk/java/lang/LazyConstant/LazyConstantTest.java b/test/jdk/java/lang/LazyConstant/LazyConstantTest.java index 485138bc492c..4faaf74a8112 100644 --- a/test/jdk/java/lang/LazyConstant/LazyConstantTest.java +++ b/test/jdk/java/lang/LazyConstant/LazyConstantTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2025, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -24,6 +24,7 @@ /* @test * @summary Basic tests for the LazyConstant implementation * @enablePreview + * @library /test/lib * @modules java.base/jdk.internal.lang * @run junit/othervm --add-opens java.base/jdk.internal.lang=ALL-UNNAMED LazyConstantTest */ @@ -32,12 +33,18 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; -import java.util.Objects; +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.lang.LazyConstant; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Stream; +import jdk.test.lib.Utils; import static org.junit.jupiter.api.Assertions.*; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -46,6 +53,8 @@ final class LazyConstantTest { private static final int VALUE = 42; private static final Supplier SUPPLIER = () -> VALUE; + private static final long TIME_OUT_S = Utils.adjustTimeout(5); + private static final long OVERLAP_TIME_MS = 100; @Test void factoryInvariants() { @@ -57,7 +66,6 @@ void factoryInvariants() { void basic(Function, LazyConstant> factory) { LazyConstantTestUtil.CountingSupplier cs = new LazyConstantTestUtil.CountingSupplier<>(SUPPLIER); var lazy = factory.apply(cs); - assertFalse(lazy.isInitialized()); assertEquals(SUPPLIER.get(), lazy.get()); assertEquals(1, cs.cnt()); assertEquals(SUPPLIER.get(), lazy.get()); @@ -67,24 +75,48 @@ void basic(Function, LazyConstant> factory) { @ParameterizedTest @MethodSource("factories") - void exception(Function, LazyConstant> factory) { + void exceptionInComputingFunction(Function, LazyConstant> factory) { + // Test different Throwable categories + for (LazyConstantTestUtil.Thrower thrower : LazyConstantTestUtil.throwers()) { + AtomicReference exceptionThrown = new AtomicReference<>(); + LazyConstantTestUtil.CountingSupplier cs = new LazyConstantTestUtil.CountingSupplier<>(() -> { + Throwable t = thrower.supplier().get(); + exceptionThrown.set(t); + LazyConstantTestUtil.sneakyThrow(t); + return 42; // Unreachable + }); + exceptionInComputingFunction(factory, cs, () -> exceptionThrown.get().getClass(), thrower.message()); + } + } + + @ParameterizedTest + @MethodSource("factories") + void nullInComputingFunction(Function, LazyConstant> factory) { LazyConstantTestUtil.CountingSupplier cs = new LazyConstantTestUtil.CountingSupplier<>(() -> { - throw new UnsupportedOperationException(); + return null; }); - var lazy = factory.apply(cs); - assertThrows(UnsupportedOperationException.class, lazy::get); - assertEquals(1, cs.cnt()); - assertThrows(UnsupportedOperationException.class, lazy::get); - assertEquals(2, cs.cnt()); - assertTrue(lazy.toString().contains("computing function")); + exceptionInComputingFunction(factory, cs, () -> NullPointerException.class, null); } - @ParameterizedTest - @MethodSource("lazyConstants") - void orElse(LazyConstant constant) { - assertNull(constant.orElse(null)); - constant.get(); - assertEquals(VALUE, constant.orElse(null)); + void exceptionInComputingFunction(Function, LazyConstant> factory, + LazyConstantTestUtil.CountingSupplier cs, + Supplier> causeTypeSupplier, + String message) { + var lazy = factory.apply(cs); + var ix = assertThrows(NoSuchElementException.class, lazy::get); + // Now we can look at the throwable + var causeType = causeTypeSupplier.get(); + assertEquals(causeType, ix.getCause().getClass()); + if (message != null) { + assertEquals(message, ix.getCause().getMessage()); + } + assertEquals(1, cs.cnt()); + var x = assertThrows(NoSuchElementException.class, lazy::get); + assertEquals("Unable to access the constant because "+causeType.getName()+" was thrown at initial computation", x.getMessage()); + assertEquals(1, cs.cnt()); + var toString = lazy.toString(); + assertTrue(toString.contains("failed with="+causeType.getName()), toString); + assertNull(x.getCause()); } @ParameterizedTest @@ -94,34 +126,35 @@ void get(LazyConstant constant) { } @ParameterizedTest - @MethodSource("lazyConstants") - void isInitialized(LazyConstant constant) { - assertFalse(constant.isInitialized()); - constant.get(); - assertTrue(constant.isInitialized()); - } - - @ParameterizedTest - @MethodSource("lazyConstants") - void testHashCode(LazyConstant constant) { - assertEquals(System.identityHashCode(constant), constant.hashCode()); + @MethodSource("factories") + void testHashCode(Function, LazyConstant> factory) { + LazyConstantTestUtil.CountingSupplier cs = new LazyConstantTestUtil.CountingSupplier<>(SUPPLIER); + var lazy = factory.apply(cs); + assertEquals(System.identityHashCode(lazy), lazy.hashCode()); + assertEquals(System.identityHashCode(lazy), lazy.hashCode()); + // The supplier should never be invoked + assertEquals(0, cs.cnt()); } @ParameterizedTest - @MethodSource("lazyConstants") - void testEquals(LazyConstant c0) { - assertNotEquals(null, c0); + @MethodSource("factories") + void testEquals(Function, LazyConstant> factory) { + LazyConstantTestUtil.CountingSupplier cs = new LazyConstantTestUtil.CountingSupplier<>(SUPPLIER); + var lazy = factory.apply(cs); + assertNotEquals(null, lazy); LazyConstant different = LazyConstant.of(SUPPLIER); - assertNotEquals(different, c0); - assertNotEquals(c0, different); - assertNotEquals("a", c0); + assertNotEquals(different, lazy); + assertNotEquals(lazy, different); + assertNotEquals("a", lazy); + // The supplier should never be invoked + assertEquals(0, cs.cnt()); } @ParameterizedTest @MethodSource("lazyConstants") void testLazyConstantAsComputingFunction(LazyConstant constant) { LazyConstant c1 = LazyConstant.of(constant); - assertSame(constant, c1); + assertNotSame(constant, c1); } @Test @@ -161,9 +194,156 @@ void toStringCircular() { void recursiveCall() { AtomicReference> ref = new AtomicReference<>(); LazyConstant constant = LazyConstant.of(() -> ref.get().get()); - LazyConstant constant1 = LazyConstant.of(constant); - ref.set(constant1); - assertThrows(IllegalStateException.class, constant::get); + ref.set(constant); + var x = assertThrows(NoSuchElementException.class, constant::get); + assertEquals(IllegalStateException.class, x.getCause().getClass()); + } + + @Test + void recursiveCallWithComputingFunctionsToStringThrowing() { + AtomicReference> ref = new AtomicReference<>(); + AtomicInteger cnt = new AtomicInteger(); + + final class NaughtySupplier implements Supplier { + @Override + public Integer get() { + return ref.get().get(); + } + + @Override + public String toString() { + cnt.incrementAndGet(); + throw new UnsupportedOperationException("I should never be seen"); + } + } + + LazyConstant constant = LazyConstant.of(new NaughtySupplier()); + + ref.set(constant); + var x = assertThrows(NoSuchElementException.class, constant::get); + assertEquals(IllegalStateException.class, x.getCause().getClass()); + assertEquals(1, cnt.get()); + assertEquals("Unable to access the constant because java.lang.IllegalStateException was thrown at initial computation", x.getMessage()); + assertTrue(x.getCause().getMessage().contains(NaughtySupplier.class.getName()), x.getCause().getMessage()); + } + + @Test + void atMostOnceComputationUnderContention() throws Exception { + // Mitigate thread starvation via a dedicated thread pool != FJP + try (var testExecutor = Executors.newFixedThreadPool(3)) { + AtomicInteger calls = new AtomicInteger(); + CountDownLatch entered = new CountDownLatch(1); + CountDownLatch release = new CountDownLatch(1); + CountDownLatch competing = new CountDownLatch(2); + + LazyConstant constant = LazyConstant.of(() -> { + calls.incrementAndGet(); + entered.countDown(); + try { + assertTrue(release.await(TIME_OUT_S, TimeUnit.SECONDS)); + } catch (InterruptedException e) { + throw new AssertionError(e); + } + return VALUE; + }); + + var f1 = CompletableFuture.supplyAsync(constant::get, testExecutor); + assertTrue(entered.await(TIME_OUT_S, TimeUnit.SECONDS)); + + var f2 = CompletableFuture.supplyAsync(() -> { + competing.countDown(); + return constant.get(); + }, testExecutor); + var f3 = CompletableFuture.supplyAsync(() -> { + competing.countDown(); + return constant.get(); + }, testExecutor); + + assertTrue(competing.await(TIME_OUT_S, TimeUnit.SECONDS)); + // While computation is blocked, only one thread should have entered supplier + assertEquals(1, calls.get()); + + release.countDown(); + + assertEquals(VALUE, f1.get(TIME_OUT_S, TimeUnit.SECONDS)); + assertEquals(VALUE, f2.get(TIME_OUT_S, TimeUnit.SECONDS)); + assertEquals(VALUE, f3.get(TIME_OUT_S, TimeUnit.SECONDS)); + assertEquals(1, calls.get()); + } + } + + @Test + void competingThreadsBlockUntilInitializationCompletes() throws Exception { + // Mitigate thread starvation via a dedicated thread pool != FJP + try (var testExecutor = Executors.newFixedThreadPool(2)) { + CountDownLatch entered = new CountDownLatch(1); + CountDownLatch release = new CountDownLatch(1); + CountDownLatch waiting = new CountDownLatch(1); + + LazyConstant constant = LazyConstant.of(() -> { + entered.countDown(); + try { + assertTrue(release.await(TIME_OUT_S, TimeUnit.SECONDS)); + } catch (InterruptedException e) { + throw new AssertionError(e); + } + return VALUE; + }); + + var computingThread = CompletableFuture.supplyAsync(constant::get, testExecutor); + assertTrue(entered.await(TIME_OUT_S, TimeUnit.SECONDS)); + + var waitingThread = CompletableFuture.supplyAsync(() -> { + waiting.countDown(); + return constant.get(); + }, testExecutor); + + assertTrue(waiting.await(TIME_OUT_S, TimeUnit.SECONDS)); + Thread.sleep(OVERLAP_TIME_MS); + assertFalse(waitingThread.isDone(), "contending thread should be be blocked"); + + release.countDown(); + + assertEquals(VALUE, computingThread.get(TIME_OUT_S, TimeUnit.SECONDS)); + assertEquals(VALUE, waitingThread.get(TIME_OUT_S, TimeUnit.SECONDS)); + } + } + + @Test + void interruptStatusIsPreservedForComputingThread() throws Exception { + int unset = -1; + int notInterrupted = 0; + int interrupted = 1; + AtomicInteger observedInterrupted = new AtomicInteger(unset); + CountDownLatch supplierRunning = new CountDownLatch(1); + CountDownLatch release = new CountDownLatch(1); + + LazyConstant constant = LazyConstant.of(() -> { + supplierRunning.countDown(); + try { + assertTrue(release.await(TIME_OUT_S, TimeUnit.SECONDS)); + } catch (InterruptedException e) { + observedInterrupted.set(Thread.currentThread().isInterrupted() ? interrupted : notInterrupted); + Thread.currentThread().interrupt(); // restore if await cleared it + } + return VALUE; + }); + + AtomicInteger interruptedAfterGet = new AtomicInteger(unset); + + Thread t = Thread.ofPlatform().start(() -> { + assertEquals(VALUE, constant.get()); + interruptedAfterGet.set(Thread.currentThread().isInterrupted() ? interrupted : notInterrupted); + }); + + assertTrue(supplierRunning.await(TIME_OUT_S, TimeUnit.SECONDS)); + Thread.sleep(OVERLAP_TIME_MS); + t.interrupt(); + release.countDown(); + t.join(); + + assertEquals(notInterrupted, observedInterrupted.get()); // Observed before restoration of the status + assertEquals(interrupted, interruptedAfterGet.get(), "get() cleared interrupt status"); } @ParameterizedTest @@ -172,10 +352,10 @@ void underlying(Function, LazyConstant> factory) { LazyConstantTestUtil.CountingSupplier cs = new LazyConstantTestUtil.CountingSupplier<>(SUPPLIER); var f1 = factory.apply(cs); - Supplier underlyingBefore = LazyConstantTestUtil.computingFunction(f1); + Object underlyingBefore = LazyConstantTestUtil.computingFunction(f1); assertSame(cs, underlyingBefore); int v = f1.get(); - Supplier underlyingAfter = LazyConstantTestUtil.computingFunction(f1); + Object underlyingAfter = LazyConstantTestUtil.computingFunction(f1); assertNull(underlyingAfter); } @@ -187,15 +367,14 @@ void functionHolderException(Function, LazyConstant> }); var f1 = factory.apply(cs); - Supplier underlyingBefore = LazyConstantTestUtil.computingFunction(f1); + Object underlyingBefore = LazyConstantTestUtil.computingFunction(f1); assertSame(cs, underlyingBefore); - try { - int v = f1.get(); - } catch (UnsupportedOperationException _) { - // Expected - } - Supplier underlyingAfter = LazyConstantTestUtil.computingFunction(f1); - assertSame(cs, underlyingAfter); + + var x = assertThrows(NoSuchElementException.class, f1::get); + assertEquals(UnsupportedOperationException.class, x.getCause().getClass()); + + Object underlyingAfter = LazyConstantTestUtil.computingFunction(f1); + assertEquals(UnsupportedOperationException.class.getName(), underlyingAfter); } private static Stream> lazyConstants() { diff --git a/test/jdk/java/lang/LazyConstant/LazyConstantTestUtil.java b/test/jdk/java/lang/LazyConstant/LazyConstantTestUtil.java index 502f46b726d9..31317c524904 100644 --- a/test/jdk/java/lang/LazyConstant/LazyConstantTestUtil.java +++ b/test/jdk/java/lang/LazyConstant/LazyConstantTestUtil.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 @@ -21,12 +21,12 @@ * questions. */ +import java.io.IOException; import java.lang.reflect.Field; +import java.lang.reflect.Type; +import java.util.List; import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.BiFunction; -import java.util.function.Function; -import java.util.function.IntFunction; -import java.util.function.Supplier; +import java.util.function.*; final class LazyConstantTestUtil { @@ -82,6 +82,22 @@ public R apply(T t) { } + public static final class CountingPredicate + extends AbstractCounting> + implements Predicate { + + public CountingPredicate(Predicate delegate) { + super(delegate); + } + + @Override + public boolean test(T t) { + incrementCounter(); + return delegate.test(t); + } + + } + public static final class CountingBiFunction extends AbstractCounting> implements BiFunction { @@ -150,11 +166,11 @@ static int functionHolderCounter(Object o) { } } - static Supplier computingFunction(LazyConstant o) { + static Object computingFunction(LazyConstant o) { try { - final Field field = field(o.getClass(), "computingFunction"); + final Field field = field(o.getClass(), "computingFunctionOrExceptionType"); field.setAccessible(true); - return (Supplier) field.get(o); + return field.get(o); } catch (ReflectiveOperationException e) { throw new RuntimeException(e); } @@ -171,4 +187,27 @@ static Field field(Class clazz, String name) { } } + static String expectedMessage(Class throwableClass, Object input) { + return "Unable to access the lazy collection because " + throwableClass.getName() + " was thrown at initial computation for input '" + input + "'"; + } + + @SuppressWarnings("unchecked") + static void sneakyThrow(Throwable e) throws E { + throw (E) e; + } + + record Thrower(String message, Function factory) { + public Supplier supplier() { + return () -> factory.apply(Thrower.this.message); + } + } + + static List throwers() { + return List.of( + new Thrower("Initial checked exception", IOException::new), + new Thrower("Initial runtime exception", UnsupportedOperationException::new), + new Thrower("Initial Error", InternalError::new) + ); + } + } diff --git a/test/jdk/java/lang/LazyConstant/LazyListTest.java b/test/jdk/java/lang/LazyConstant/LazyListTest.java index 4201fff3bb77..c4cc843646ee 100644 --- a/test/jdk/java/lang/LazyConstant/LazyListTest.java +++ b/test/jdk/java/lang/LazyConstant/LazyListTest.java @@ -33,18 +33,15 @@ import org.junit.jupiter.params.provider.MethodSource; import java.io.Serializable; -import java.util.Comparator; -import java.util.List; -import java.util.NoSuchElementException; -import java.util.RandomAccess; -import java.util.Set; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Consumer; +import java.util.function.*; import java.util.function.Function; -import java.util.function.IntFunction; -import java.util.function.UnaryOperator; -import java.util.stream.Collectors; import java.util.stream.IntStream; import java.util.stream.Stream; @@ -57,6 +54,9 @@ final class LazyListTest { private static final int SIZE = 31; private static final IntFunction IDENTITY = i -> i; + private static final long TIME_OUT_S = 5; + private static final long OVERLAP_TIME_MS = 100; + @Test void factoryInvariants() { assertThrows(NullPointerException.class, () -> List.ofLazy(SIZE, null)); @@ -88,15 +88,44 @@ void get() { } @Test - void getException() { + void exeptionInComputingFunction() { LazyConstantTestUtil.CountingIntFunction cif = new LazyConstantTestUtil.CountingIntFunction(_ -> { - throw new UnsupportedOperationException(); + throw new UnsupportedOperationException("Initial exception"); }); + exceptionInComputingFunction(cif, UnsupportedOperationException.class); + } + + @Test + void nullResultInComputingFunction() { + LazyConstantTestUtil.CountingIntFunction cif = new LazyConstantTestUtil.CountingIntFunction(_ -> { + return null; + }); + exceptionInComputingFunction(cif, NullPointerException.class); + } + + void exceptionInComputingFunction(LazyConstantTestUtil.CountingIntFunction cif, + Class causeType) { var lazy = List.ofLazy(SIZE, cif); - assertThrows(UnsupportedOperationException.class, () -> lazy.get(INDEX)); + var x = assertThrows(NoSuchElementException.class, () -> lazy.get(INDEX)); + assertEquals(LazyConstantTestUtil.expectedMessage(causeType, INDEX), x.getMessage()); + assertEquals(causeType, x.getCause().getClass()); + assertEquals(1, cif.cnt()); + + var x2 = assertThrows(NoSuchElementException.class, () -> lazy.get(INDEX)); assertEquals(1, cif.cnt()); - assertThrows(UnsupportedOperationException.class, () -> lazy.get(INDEX)); - assertEquals(2, cif.cnt()); + assertEquals(LazyConstantTestUtil.expectedMessage(causeType, INDEX), x2.getMessage()); + // The initial cause should only be present on the _first_ unchecked exception + assertNull(x2.getCause()); + + for (int i = 0; i < SIZE; i++) { + // Make sure all values are touched + final int finalI = i; + assertThrows(Exception.class, () -> lazy.get(finalI)); + } + + var xToString = assertThrows(NoSuchElementException.class, lazy::toString); + assertEquals(LazyConstantTestUtil.expectedMessage(causeType, 0), xToString.getMessage()); + assertEquals(SIZE, cif.cnt()); } @Test @@ -109,14 +138,14 @@ void toArray() { void toArrayWithArrayLarger() { Integer[] actual = new Integer[SIZE]; for (int i = 0; i < SIZE; i++) { - actual[INDEX] = 100 + i; + actual[i] = 100 + i; } var lazy = List.ofLazy(INDEX, IDENTITY); assertSame(actual, lazy.toArray(actual)); - Integer[] expected = IntStream.range(0, SIZE) - .mapToObj(i -> i < INDEX ? i : null) - .toArray(Integer[]::new); - assertArrayEquals(expected, actual); + for (int i = 0; i < INDEX; i++) { + assertEquals(i, actual[i]); + } + assertNull(actual[INDEX]); } @Test @@ -273,8 +302,148 @@ void recursiveCall() { AtomicReference> ref = new AtomicReference<>(); var lazy = List.ofLazy(SIZE, i -> ref.get().apply(i)); ref.set(lazy::get); - var x = assertThrows(IllegalStateException.class, () -> lazy.get(INDEX)); - assertEquals("Recursive initialization of a lazy collection is illegal", x.getMessage()); + var x = assertThrows(NoSuchElementException.class, () -> lazy.get(INDEX)); + assertEquals(LazyConstantTestUtil.expectedMessage(IllegalStateException.class, INDEX), x.getMessage()); + assertEquals("Recursive initialization of a lazy collection is illegal: " + INDEX, x.getCause().getMessage()); + assertEquals(IllegalStateException.class, x.getCause().getClass()); + } + + @ParameterizedTest + @MethodSource("viewOperations") + void atMostOnceComputationUnderContention(UnaryOperation viewOp) throws Exception { + final int index = SIZE / 2; + // Mitigate thread starvation via a dedicated thread pool != FJP + try (var testExecutor = Executors.newFixedThreadPool(3)) { + AtomicInteger calls = new AtomicInteger(); + CountDownLatch entered = new CountDownLatch(1); + CountDownLatch release = new CountDownLatch(1); + CountDownLatch competing = new CountDownLatch(2); + + List ref = viewOp.apply(newRegularList()); + List constant = viewOp.apply(List.ofLazy(SIZE, i -> { + calls.incrementAndGet(); + entered.countDown(); + try { + assertTrue(release.await(TIME_OUT_S, TimeUnit.SECONDS)); + } catch (InterruptedException e) { + throw new AssertionError(e); + } + return i; + })); + + var f1 = CompletableFuture.supplyAsync(() -> constant.get(index), testExecutor); + assertTrue(entered.await(5, TimeUnit.SECONDS)); + + var f2 = CompletableFuture.supplyAsync(() -> { + competing.countDown(); + return constant.get(index); + }, testExecutor); + var f3 = CompletableFuture.supplyAsync(() -> { + competing.countDown(); + return constant.get(index); + }, testExecutor); + + assertTrue(competing.await(TIME_OUT_S, TimeUnit.SECONDS)); + // While computation is blocked, only one thread should have entered supplier + Thread.sleep(OVERLAP_TIME_MS); + assertEquals(1, calls.get()); + + release.countDown(); + + assertEquals(ref.get(index), f1.get(TIME_OUT_S, TimeUnit.SECONDS)); + assertEquals(ref.get(index), f2.get(TIME_OUT_S, TimeUnit.SECONDS)); + assertEquals(ref.get(index), f3.get(TIME_OUT_S, TimeUnit.SECONDS)); + assertEquals(1, calls.get()); + } + } + + @ParameterizedTest + @MethodSource("viewOperations") + void competingThreadsBlockUntilInitializationCompletes(UnaryOperation viewOp) throws Exception { + final int index = SIZE / 2; + // Mitigate thread starvation via a dedicated thread pool != FJP + try (var testExecutor = Executors.newFixedThreadPool(2)) { + CountDownLatch entered = new CountDownLatch(1); + CountDownLatch release = new CountDownLatch(1); + CountDownLatch waiting = new CountDownLatch(1); + + List ref = viewOp.apply(newRegularList()); + List constant = viewOp.apply(List.ofLazy(SIZE, i -> { + entered.countDown(); + try { + assertTrue(release.await(TIME_OUT_S, TimeUnit.SECONDS)); + } catch (InterruptedException e) { + throw new AssertionError(e); + } + return i; + })); + + var computingThread = CompletableFuture.supplyAsync(() -> constant.get(index), testExecutor); + assertTrue(entered.await(TIME_OUT_S, TimeUnit.SECONDS)); + + var waitingThread = CompletableFuture.supplyAsync(() -> { + waiting.countDown(); + return constant.get(index); + }, testExecutor); + + assertTrue(waiting.await(TIME_OUT_S, TimeUnit.SECONDS)); + Thread.sleep(OVERLAP_TIME_MS); + assertFalse(waitingThread.isDone(), "contending thread should be be blocked"); + + release.countDown(); + + assertEquals(ref.get(index), computingThread.get(TIME_OUT_S, TimeUnit.SECONDS)); + assertEquals(ref.get(index), waitingThread.get(TIME_OUT_S, TimeUnit.SECONDS)); + } + } + + @ParameterizedTest + @MethodSource("viewOperations") + void interruptStatusIsPreservedForComputingThread(UnaryOperation viewOp) throws Exception { + final int index = SIZE / 2; + int unset = -1; + int notInterrupted = 0; + int interrupted = 1; + AtomicInteger observedInterrupted = new AtomicInteger(unset); + CountDownLatch supplierRunning = new CountDownLatch(1); + CountDownLatch release = new CountDownLatch(1); + + List constant = viewOp.apply(List.ofLazy(SIZE, i -> { + supplierRunning.countDown(); + try { + assertTrue(release.await(TIME_OUT_S, TimeUnit.SECONDS)); + } catch (InterruptedException e) { + observedInterrupted.set(Thread.currentThread().isInterrupted() ? interrupted : notInterrupted); + Thread.currentThread().interrupt(); // restore if await cleared it + } + return i; + })); + + AtomicInteger interruptedAfterGet = new AtomicInteger(unset); + + Thread t = Thread.ofPlatform().start(() -> { + assertEquals(index, constant.get(index)); + interruptedAfterGet.set(Thread.currentThread().isInterrupted() ? interrupted : notInterrupted); + }); + + assertTrue(supplierRunning.await(TIME_OUT_S, TimeUnit.SECONDS)); + Thread.sleep(OVERLAP_TIME_MS); + t.interrupt(); + release.countDown(); + t.join(); + + assertEquals(notInterrupted, observedInterrupted.get()); // Observed before restoration of the status + assertEquals(interrupted, interruptedAfterGet.get(), "get() cleared interrupt status"); + } + + @ParameterizedTest + @MethodSource("iteratorOperations") + void iterators(ListFunction viewOp) throws Exception { + List lazy = newLazyList(); + Iterator iter = (Iterator) viewOp.apply(lazy); + List actual = new ArrayList<>(); + iter.forEachRemaining(actual::add); + assertEquals(newRegularList(), actual); } // Immutability @@ -374,16 +543,16 @@ static Stream viewOperations() { // We need identity to capture all combinations new UnaryOperation("identity", l -> l), new UnaryOperation("reversed", List::reversed), - new UnaryOperation("subList", l -> l.subList(0, l.size())) + new UnaryOperation("subList", l -> l.subList(0, l.size() - 1)) ); } - static Stream childOperations() { + static Stream iteratorOperations() { return Stream.of( // We need identity to capture all combinations new ListFunction("iterator", List::iterator), new ListFunction("listIterator", List::listIterator), - new ListFunction("listIterator", List::stream) + new ListFunction("stream::iterator", l -> l.stream().iterator()) ); } @@ -391,6 +560,9 @@ static Stream nullAverseOperations() { return Stream.of( new Operation("forEach", l -> l.forEach(null)), new Operation("containsAll", l -> l.containsAll(null)), + new Operation("contains", l -> l.contains(null)), + new Operation("indexOf", l -> l.indexOf(null)), + new Operation("lastIndexOf", l -> l.lastIndexOf(null)), new Operation("toArray", l -> l.toArray((Integer[]) null)), new Operation("toArray", l -> l.toArray((IntFunction) null)) ); @@ -448,4 +620,5 @@ static List newEmptyLazyList() { static List newRegularList() { return IntStream.range(0, SIZE).boxed().toList(); } + } diff --git a/test/jdk/java/lang/LazyConstant/LazyMapTest.java b/test/jdk/java/lang/LazyConstant/LazyMapTest.java index 889ec43df6a7..56285fcd52c6 100644 --- a/test/jdk/java/lang/LazyConstant/LazyMapTest.java +++ b/test/jdk/java/lang/LazyConstant/LazyMapTest.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 @@ -32,21 +32,26 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; +import java.io.IOException; import java.io.Serializable; -import java.util.AbstractMap; +import java.util.*; import java.util.Arrays; import java.util.Comparator; -import java.util.EnumSet; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.Map; +import java.util.List; import java.util.Set; import java.util.TreeSet; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import java.util.function.Function; +import java.util.function.IntFunction; +import java.util.function.Supplier; import java.util.stream.Collectors; +import java.util.stream.IntStream; import java.util.stream.Stream; import static java.util.stream.Collectors.joining; @@ -90,6 +95,9 @@ int asInt() { private static final Value KEY = Value.FORTY_TWO; private static final Integer VALUE = MAPPER.apply(KEY); + private static final long TIME_OUT_S = 5; + private static final long OVERLAP_TIME_MS = 100; + @ParameterizedTest @MethodSource("allSets") void factoryInvariants(Set set) { @@ -132,19 +140,58 @@ void get(Set set) { assertNull(lazy.get(Value.ILLEGAL_BETWEEN)); } + @ParameterizedTest + @MethodSource("nonEmptySets") + void getOrDefault(Set set) { + LazyConstantTestUtil.CountingFunction cf = new LazyConstantTestUtil.CountingFunction<>(MAPPER); + var lazy = Map.ofLazy(set, cf); + int cnt = 1; + for (Value v : set) { + assertEquals(MAPPER.apply(v), lazy.getOrDefault(v, Integer.MIN_VALUE)); + assertEquals(cnt, cf.cnt()); + assertEquals(MAPPER.apply(v), lazy.getOrDefault(v, Integer.MIN_VALUE)); + assertEquals(cnt++, cf.cnt()); + } + assertEquals(Integer.MIN_VALUE, lazy.getOrDefault(Value.ILLEGAL_BETWEEN, Integer.MIN_VALUE)); + assertEquals(Integer.MIN_VALUE, lazy.getOrDefault("a", Integer.MIN_VALUE)); + assertThrows(NullPointerException.class, () -> lazy.getOrDefault(null, Integer.MIN_VALUE)); + } + @ParameterizedTest @MethodSource("nonEmptySets") void exception(Set set) { - LazyConstantTestUtil.CountingFunction cif = new LazyConstantTestUtil.CountingFunction<>(_ -> { - throw new UnsupportedOperationException(); - }); - var lazy = Map.ofLazy(set, cif); - assertThrows(UnsupportedOperationException.class, () -> lazy.get(KEY)); - assertEquals(1, cif.cnt()); - assertThrows(UnsupportedOperationException.class, () -> lazy.get(KEY)); - assertEquals(2, cif.cnt()); - assertThrows(UnsupportedOperationException.class, lazy::toString); - assertEquals(3, cif.cnt()); + // Test different Throwable categories + for (LazyConstantTestUtil.Thrower thrower : LazyConstantTestUtil.throwers()) { + AtomicReference exceptionThrown = new AtomicReference<>(); + LazyConstantTestUtil.CountingFunction cif = new LazyConstantTestUtil.CountingFunction<>(_ -> { + Throwable t = thrower.supplier().get(); + exceptionThrown.set(t); + LazyConstantTestUtil.sneakyThrow(t); + return 42; // Unreachable + }); + var lazy = Map.ofLazy(set, cif); + var x = assertThrows(NoSuchElementException.class, () -> lazy.get(KEY)); + assertEquals(LazyConstantTestUtil.expectedMessage(exceptionThrown.get().getClass(), KEY), x.getMessage()); + assertEquals(exceptionThrown.get().getClass(), x.getCause().getClass()); + assertEquals(thrower.message(), x.getCause().getMessage()); + assertEquals(1, cif.cnt()); + + var x2 = assertThrows(NoSuchElementException.class, () -> lazy.get(KEY)); + assertEquals(1, cif.cnt()); + assertEquals(LazyConstantTestUtil.expectedMessage(exceptionThrown.get().getClass(), KEY), x2.getMessage()); + // The initial cause should only be present on the _first_ unchecked exception + assertNull(x2.getCause()); + + for (Value v : set) { + // Make sure all values are touched + assertThrows(Exception.class, () -> lazy.get(v)); + } + + var xToString = assertThrows(NoSuchElementException.class, lazy::toString); + var xMessage = xToString.getMessage(); + assertTrue(xMessage.startsWith(LazyConstantTestUtil.expectedMessage(exceptionThrown.get().getClass(), 0).substring(0, xMessage.indexOf("'")))); + assertEquals(set.size(), cif.cnt()); + } } @ParameterizedTest @@ -155,6 +202,8 @@ void containsKey(Set set) { assertTrue(lazy.containsKey(v)); } assertFalse(lazy.containsKey(Value.ILLEGAL_BETWEEN)); + assertThrows(NullPointerException.class, () -> lazy.containsKey(null)); + assertFalse(lazy.containsKey("a")); } @ParameterizedTest @@ -240,8 +289,36 @@ void recursiveCall(Set set) { @SuppressWarnings("unchecked") Map> lazy = Map.ofLazy(set, k -> (Map) ref.get().get(k)); ref.set(lazy); - var x = assertThrows(IllegalStateException.class, () -> lazy.get(KEY)); - assertEquals("Recursive initialization of a lazy collection is illegal", x.getMessage()); + var x = assertThrows(NoSuchElementException.class, () -> lazy.get(KEY)); + assertEquals(LazyConstantTestUtil.expectedMessage(IllegalStateException.class, KEY), x.getMessage()); + assertEquals("Recursive initialization of a lazy collection is illegal: " + KEY, x.getCause().getMessage()); + assertEquals(IllegalStateException.class, x.getCause().getClass()); + } + + @Test + void recursiveCallWithKeysToStringThrowing() { + AtomicInteger cnt = new AtomicInteger(); + + final class NaughtyKey { + + @Override + public String toString() { + cnt.incrementAndGet(); + throw new UnsupportedOperationException("I should never be seen"); + } + } + + final NaughtyKey key = new NaughtyKey(); + final Set set = Set.of(key); + + final AtomicReference> ref = new AtomicReference<>(); + @SuppressWarnings("unchecked") + Map> lazy = Map.ofLazy(set, k -> (Map) ref.get().get(k)); + ref.set(lazy); + var x = assertThrows(NoSuchElementException.class, () -> lazy.get(key)); + // We recurse here so `NaughtyKey.toString` is called twice before reentry is prevented + assertEquals(2, cnt.get()); + assertTrue(x.getCause().getMessage().contains(NaughtyKey.class.getName())); } @ParameterizedTest @@ -252,6 +329,31 @@ void entrySet(Set set) { assertTrue(regular.equals(lazy)); assertTrue(lazy.equals(regular)); assertTrue(regular.equals(lazy)); + assertEquals(lazy.hashCode(), regular.hashCode()); + } + + @ParameterizedTest + @MethodSource("allSets") + void entrySetHashCode(Set set) { + assertEquals(newRegularMap(set).entrySet().hashCode(), + newLazyMap(set).entrySet().hashCode()); + } + + @ParameterizedTest + @MethodSource("nonEmptySets") + void keySet(Set set) { + var lazy = newLazyMap(set); + var keySet = lazy.keySet(); + assertEquals(set, keySet); + assertThrows(UnsupportedOperationException.class, () -> lazy.remove(KEY)); + } + + @ParameterizedTest + @MethodSource("nonEmptySets") + void entrySetValue(Set set) { + var entry = newLazyMap(set).entrySet().iterator().next(); + assertThrows(UnsupportedOperationException.class, () -> entry.setValue(null)); + assertThrows(UnsupportedOperationException.class, () -> entry.setValue(1)); } @ParameterizedTest @@ -267,6 +369,15 @@ void entrySetToString(Set set) { assertTrue(toString.endsWith("]")); } + @ParameterizedTest + @MethodSource("emptySets") + void emptyValues(Set set) { + var lazy = newLazyMap(set); + var lazyValues = lazy.values(); + assertEquals(0, lazyValues.size()); + assertTrue(lazyValues.isEmpty()); + } + @ParameterizedTest @MethodSource("nonEmptySets") void values(Set set) { @@ -276,6 +387,10 @@ void values(Set set) { var val = lazyValues.stream().iterator().next(); assertEquals(lazy.size() - 1, functionCounter(lazy)); + assertEquals(set.size(), lazyValues.size()); + assertFalse(lazyValues.isEmpty()); + assertTrue(lazyValues.contains(VALUE)); + // Mod ops assertThrows(UnsupportedOperationException.class, () -> lazyValues.remove(val)); assertThrows(UnsupportedOperationException.class, () -> lazyValues.add(val)); @@ -395,7 +510,9 @@ void serializable(Set set) { @Test void nullResult() { var lazy = Map.ofLazy(Set.of(0), _ -> null); - assertThrows(NullPointerException.class, () -> lazy.getOrDefault(0, 1));; + var x = assertThrows(NoSuchElementException.class, () -> lazy.getOrDefault(0, 1)); + assertEquals(LazyConstantTestUtil.expectedMessage(NullPointerException.class, 0), x.getMessage()); + assertEquals(NullPointerException.class, x.getCause().getClass()); assertTrue(lazy.containsKey(0)); } @@ -439,6 +556,129 @@ void functionHolderViaEntrySet(Set set) { assertNull(LazyConstantTestUtil.functionHolderFunction(holder)); } + @ParameterizedTest + @MethodSource("nonEmptySets") + void atMostOnceComputationUnderContention(Set set) throws Exception { + // Mitigate thread starvation via a dedicated thread pool != FJP + try (var testExecutor = Executors.newFixedThreadPool(3)) { + AtomicInteger calls = new AtomicInteger(); + CountDownLatch entered = new CountDownLatch(1); + CountDownLatch release = new CountDownLatch(1); + CountDownLatch competing = new CountDownLatch(2); + + Map constant = Map.ofLazy(set, i -> { + calls.incrementAndGet(); + entered.countDown(); + try { + assertTrue(release.await(TIME_OUT_S, TimeUnit.SECONDS)); + } catch (InterruptedException e) { + throw new AssertionError(e); + } + return MAPPER.apply(i); + }); + + var f1 = CompletableFuture.supplyAsync(() -> constant.get(KEY), testExecutor); + assertTrue(entered.await(5, TimeUnit.SECONDS)); + + var f2 = CompletableFuture.supplyAsync(() -> { + competing.countDown(); + return constant.get(KEY); + }, testExecutor); + var f3 = CompletableFuture.supplyAsync(() -> { + competing.countDown(); + return constant.get(KEY); + }, testExecutor); + + assertTrue(competing.await(TIME_OUT_S, TimeUnit.SECONDS)); + // While computation is blocked, only one thread should have entered supplier + Thread.sleep(OVERLAP_TIME_MS); + assertEquals(1, calls.get()); + + release.countDown(); + + assertEquals(VALUE, f1.get(TIME_OUT_S, TimeUnit.SECONDS)); + assertEquals(VALUE, f2.get(TIME_OUT_S, TimeUnit.SECONDS)); + assertEquals(VALUE, f3.get(TIME_OUT_S, TimeUnit.SECONDS)); + assertEquals(1, calls.get()); + } + } + + @ParameterizedTest + @MethodSource("nonEmptySets") + void competingThreadsBlockUntilInitializationCompletes(Set set) throws Exception { + // Mitigate thread starvation via a dedicated thread pool != FJP + try (var testExecutor = Executors.newFixedThreadPool(2)) { + CountDownLatch entered = new CountDownLatch(1); + CountDownLatch release = new CountDownLatch(1); + CountDownLatch waiting = new CountDownLatch(1); + + Map constant = Map.ofLazy(set, i -> { + entered.countDown(); + try { + assertTrue(release.await(TIME_OUT_S, TimeUnit.SECONDS)); + } catch (InterruptedException e) { + throw new AssertionError(e); + } + return MAPPER.apply(i); + }); + + var computingThread = CompletableFuture.supplyAsync(() -> constant.get(KEY), testExecutor); + assertTrue(entered.await(TIME_OUT_S, TimeUnit.SECONDS)); + + var waitingThread = CompletableFuture.supplyAsync(() -> { + waiting.countDown(); + return constant.get(KEY); + }, testExecutor); + + assertTrue(waiting.await(TIME_OUT_S, TimeUnit.SECONDS)); + Thread.sleep(OVERLAP_TIME_MS); + assertFalse(waitingThread.isDone(), "contending thread should be be blocked"); + + release.countDown(); + + assertEquals(VALUE, computingThread.get(TIME_OUT_S, TimeUnit.SECONDS)); + assertEquals(VALUE, waitingThread.get(TIME_OUT_S, TimeUnit.SECONDS)); + } + } + + @ParameterizedTest + @MethodSource("nonEmptySets") + void interruptStatusIsPreservedForComputingThread(Set set) throws Exception { + int unset = -1; + int notInterrupted = 0; + int interrupted = 1; + AtomicInteger observedInterrupted = new AtomicInteger(unset); + CountDownLatch supplierRunning = new CountDownLatch(1); + CountDownLatch release = new CountDownLatch(1); + + Map constant = Map.ofLazy(set, i -> { + supplierRunning.countDown(); + try { + assertTrue(release.await(TIME_OUT_S, TimeUnit.SECONDS)); + } catch (InterruptedException e) { + observedInterrupted.set(Thread.currentThread().isInterrupted() ? interrupted : notInterrupted); + Thread.currentThread().interrupt(); // restore if await cleared it + } + return MAPPER.apply(i); + }); + + AtomicInteger interruptedAfterGet = new AtomicInteger(unset); + + Thread t = Thread.ofPlatform().start(() -> { + assertEquals(VALUE, constant.get(KEY)); + interruptedAfterGet.set(Thread.currentThread().isInterrupted() ? interrupted : notInterrupted); + }); + + assertTrue(supplierRunning.await(TIME_OUT_S, TimeUnit.SECONDS)); + Thread.sleep(OVERLAP_TIME_MS); + t.interrupt(); + release.countDown(); + t.join(); + + assertEquals(notInterrupted, observedInterrupted.get()); // Observed before restoration of the status + assertEquals(interrupted, interruptedAfterGet.get(), "get() cleared interrupt status"); + } + @ParameterizedTest @MethodSource("allSets") void underlyingRefViaEntrySetForEach(Set set) { @@ -461,6 +701,7 @@ void underlyingRefViaEntrySetForEach(Set set) { @Test void usesOptimizedVersion() { + // This test is using name magic but we are in control of the naming. Map enumMap = Map.ofLazy(EnumSet.of(KEY), Value::asInt); assertTrue(enumMap.getClass().getName().contains("Enum"), enumMap.getClass().getName()); Map emptyMap = Map.ofLazy(EnumSet.noneOf(Value.class), Value::asInt); @@ -498,7 +739,8 @@ record Operation(String name, static Stream nullAverseOperations() { return Stream.of( - new Operation("forEach", m -> m.forEach(null)) + new Operation("forEach", m -> m.forEach(null)), + new Operation("containsValue", m -> m.containsValue(null)) ); } @@ -571,4 +813,30 @@ private static int functionCounter(Map lazy) { return LazyConstantTestUtil.functionHolderCounter(holder); } + // Javadoc equivalent + class LazyMap extends AbstractMap { + + private final Map> backingMap; + + public LazyMap(Set keys, Function computingFunction) { + this.backingMap = keys.stream() + .collect(Collectors.toUnmodifiableMap( + Function.identity(), + k -> LazyConstant.of(() -> computingFunction.apply(k)))); + } + + @Override + public V get(Object key) { + var lazyConstant = backingMap.get(key); + return lazyConstant == null + ? null + : lazyConstant.get(); + } + + @Override + public Set> entrySet() { + return Set.of(); + } + } + } diff --git a/test/jdk/java/lang/LazyConstant/LazySetTest.java b/test/jdk/java/lang/LazyConstant/LazySetTest.java new file mode 100644 index 000000000000..4e168c7bcadf --- /dev/null +++ b/test/jdk/java/lang/LazyConstant/LazySetTest.java @@ -0,0 +1,591 @@ +/* + * 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 Basic tests for lazy set methods + * @enablePreview + * @modules java.base/java.util:+open + * @run junit LazySetTest + */ + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import java.io.Serializable; +import java.lang.Class; +import java.lang.Override; +import java.util.*; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Set; +import java.util.TreeSet; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.*; + +final class LazySetTest { + + enum Value { + // Zero is here so that we have enums with ordinals before the first one + // actually used in input sets (i.e. ZERO is not in the input set) + ZERO(0), + ILLEGAL_BEFORE(-1), + // Valid values + THIRTEEN(13) { + @Override + public String toString() { + // getEnumConstants will be `null` for this enum as it is overridden + return super.toString()+" (Overridden)"; + } + }, + ILLEGAL_BETWEEN(-2), + FORTY_TWO(42), + // Illegal values (not in the input set) + ILLEGAL_AFTER(-3); + + final int intValue; + + Value(int intValue) { + this.intValue = intValue; + } + + int asInt() { + return intValue; + } + + } + + private static final Value MEMBER = Value.FORTY_TWO; + private static final Value NON_MEMBER = Value.THIRTEEN; + private static final Set SET = Set.of(NON_MEMBER, MEMBER); + private static final Predicate PREDICATE = c -> c == MEMBER; ; + + private static final long TIME_OUT_S = 5; + private static final long OVERLAP_TIME_MS = 100; + + @ParameterizedTest + @MethodSource("allSets") + void factoryInvariants(Set set) { + assertThrows(NullPointerException.class, () -> Set.ofLazy(set, null), set.getClass().getSimpleName()); + assertThrows(NullPointerException.class, () -> Set.ofLazy(null, PREDICATE)); + Set setWithNull = new HashSet<>(); + setWithNull.add(MEMBER); + setWithNull.add(null); + assertThrows(NullPointerException.class, () -> Set.ofLazy(setWithNull, PREDICATE)); + } + + @ParameterizedTest + @MethodSource("emptySets") + void empty(Set set) { + var lazy = newLazySet(set); + assertTrue(lazy.isEmpty()); + assertEquals("[]", lazy.toString()); + } + + @ParameterizedTest + @MethodSource("allSets") + void size(Set set) { + assertEquals(newRegularSet(set).size(), newLazySet(set).size()); + } + + @ParameterizedTest + @MethodSource("nonEmptySets") + void exception(Set set) { + LazyConstantTestUtil.CountingPredicate cif = new LazyConstantTestUtil.CountingPredicate<>(_ -> { + throw new UnsupportedOperationException("Initial exception"); + }); + var lazy = Set.ofLazy(set, cif); + var x = assertThrows(NoSuchElementException.class, () -> lazy.contains(MEMBER)); + assertEquals(LazyConstantTestUtil.expectedMessage(UnsupportedOperationException.class, MEMBER), x.getMessage()); + assertEquals(UnsupportedOperationException.class, x.getCause().getClass()); + assertEquals(1, cif.cnt()); + + var x2 = assertThrows(NoSuchElementException.class, () -> lazy.contains(MEMBER)); + assertEquals(LazyConstantTestUtil.expectedMessage(UnsupportedOperationException.class, MEMBER), x2.getMessage()); + // The initial cause should only be present on the _first_ unchecked exception + assertNull(x2.getCause()); + + for (Value v : set) { + // Make sure all values are touched + assertThrows(Exception.class, () -> lazy.contains(v)); + } + + var xToString = assertThrows(NoSuchElementException.class, lazy::toString); + var xMessage = xToString.getMessage(); + assertTrue(xMessage.startsWith(LazyConstantTestUtil.expectedMessage(UnsupportedOperationException.class, 0).substring(0, xMessage.indexOf("'")))); + assertEquals(set.size(), cif.cnt()); + } + + @ParameterizedTest + @MethodSource("allSets") + void contains(Set set) { + var lazy = newLazySet(set); + var expected = newRegularSet(set); + for (Value v : set) { + assertEquals(expected.contains(v), lazy.contains(v)); + } + assertFalse(lazy.contains(Value.ILLEGAL_BETWEEN)); + } + + @ParameterizedTest + @MethodSource("allSets") + void forEach(Set set) { + var lazy = newLazySet(set); + var expected = newRegularSet(set); + Set actual = new HashSet<>(); + lazy.forEach(actual::add); + assertEquals(expected, actual); + } + + @ParameterizedTest + @MethodSource("emptySets") + void toStringTestEmpty(Set set) { + var lazy = newLazySet(set); + assertEquals("[]", lazy.toString()); + } + + @ParameterizedTest + @MethodSource("nonEmptySets") + void toStringTest(Set set) { + var lazy = newLazySet(set); + var expected = newRegularSet(set); + var toString = lazy.toString(); + assertTrue(toString.startsWith("[")); + assertTrue(toString.endsWith("]")); + + // Key order is unspecified + for (Value key : expected) { + assertTrue(toString.contains(key.toString()), key + " is not in" + toString); + } + + // One between the values + assertEquals(expected.size() - 1, toString.chars().filter(ch -> ch == ',').count()); + } + + @ParameterizedTest + @MethodSource("allSets") + void hashCodeTest(Set set) { + var lazy = newLazySet(set); + var regular = newRegularSet(set); + assertEquals(regular.hashCode(), lazy.hashCode()); + } + + @ParameterizedTest + @MethodSource("allSets") + void zeroHashCodeTest() { + specificHashCodeTest(0); + } + + @ParameterizedTest + @MethodSource("allSets") + void negativeHashCodeTest() { + specificHashCodeTest(-1); + specificHashCodeTest(-42); + specificHashCodeTest(Integer.MIN_VALUE); + } + + @ParameterizedTest + @MethodSource("allSets") + void positiveHashCodeTest() { + specificHashCodeTest(1); + specificHashCodeTest(42); + specificHashCodeTest(Integer.MAX_VALUE); + } + + void specificHashCodeTest(int hc) { + final class ZeroHashCode { + @Override + public int hashCode() { + return hc; + } + } + + var lazy = Set.ofLazy(Set.of(new ZeroHashCode()), e -> true); + assertEquals(hc, lazy.hashCode()); + } + + @ParameterizedTest + @MethodSource("allSets") + void equality(Set set) { + var lazy = newLazySet(set); + var regular = newRegularSet(set); + assertEquals(regular, lazy); + assertEquals(lazy, regular); + assertNotEquals("A", lazy); + } + + @ParameterizedTest + @MethodSource("nonEmptySets") + void recursiveCall(Set set) { + final AtomicReference> ref = new AtomicReference<>(); + @SuppressWarnings("unchecked") + Set lazy = Set.ofLazy(set, k -> ref.get().contains(k)); + ref.set(lazy); + var x = assertThrows(NoSuchElementException.class, () -> lazy.contains(MEMBER)); + assertEquals(LazyConstantTestUtil.expectedMessage(java.lang.IllegalStateException.class, MEMBER), x.getMessage()); + assertEquals("Recursive initialization of a lazy collection is illegal: " + MEMBER, x.getCause().getMessage()); + assertEquals(IllegalStateException.class, x.getCause().getClass()); + } + + @ParameterizedTest + @MethodSource("allSets") + void iteratorNext(Set set) { + Set encountered = new HashSet<>(); + var expected = newRegularSet(set); + var iterator = newLazySet(set).iterator(); + while (iterator.hasNext()) { + var entry = iterator.next(); + encountered.add(entry); + } + assertEquals(expected, encountered); + } + + @ParameterizedTest + @MethodSource("nonEmptySets") + void iteratorForEachRemaining(Set set) { + Set encountered = new HashSet<>(); + var expected = newRegularSet(set); + var iterator = newLazySet(set).iterator(); + var value = iterator.next(); + encountered.add(value); + iterator.forEachRemaining(encountered::add); + assertEquals(expected, encountered); + } + + + @ParameterizedTest + @MethodSource("nonEmptySets") + void atMostOnceComputationUnderContention(Set set) throws Exception { + // Make sure to exercise both member and non-member statuses + for (Value candidate : set) { + // Mitigate thread starvation via a dedicated thread pool != FJP + try (var testExecutor = Executors.newFixedThreadPool(3)) { + AtomicInteger calls = new AtomicInteger(); + CountDownLatch entered = new CountDownLatch(1); + CountDownLatch release = new CountDownLatch(1); + CountDownLatch competing = new CountDownLatch(2); + + Set constant = Set.ofLazy(set, i -> { + calls.incrementAndGet(); + entered.countDown(); + try { + assertTrue(release.await(TIME_OUT_S, TimeUnit.SECONDS)); + } catch (InterruptedException e) { + throw new AssertionError(e); + } + return PREDICATE.test(i); + }); + + var f1 = CompletableFuture.supplyAsync(() -> constant.contains(candidate), testExecutor); + assertTrue(entered.await(5, TimeUnit.SECONDS)); + + var f2 = CompletableFuture.supplyAsync(() -> { + competing.countDown(); + return constant.contains(candidate); + }, testExecutor); + var f3 = CompletableFuture.supplyAsync(() -> { + competing.countDown(); + return constant.contains(candidate); + }, testExecutor); + + assertTrue(competing.await(TIME_OUT_S, TimeUnit.SECONDS)); + // While computation is blocked, only one thread should have entered supplier + Thread.sleep(OVERLAP_TIME_MS); + assertEquals(1, calls.get()); + + release.countDown(); + + assertEquals(PREDICATE.test(candidate), f1.get(TIME_OUT_S, TimeUnit.SECONDS)); + assertEquals(PREDICATE.test(candidate), f2.get(TIME_OUT_S, TimeUnit.SECONDS)); + assertEquals(PREDICATE.test(candidate), f3.get(TIME_OUT_S, TimeUnit.SECONDS)); + assertEquals(1, calls.get()); + } + } + } + + @ParameterizedTest + @MethodSource("nonEmptySets") + void competingThreadsBlockUntilInitializationCompletes(Set set) throws Exception { + // Make sure to exercise both member and non-member statuses + for (Value candidate : set) { + // Mitigate thread starvation via a dedicated thread pool != FJP + try (var testExecutor = Executors.newFixedThreadPool(2)) { + CountDownLatch entered = new CountDownLatch(1); + CountDownLatch release = new CountDownLatch(1); + CountDownLatch waiting = new CountDownLatch(1); + + Set constant = Set.ofLazy(set, i -> { + entered.countDown(); + try { + assertTrue(release.await(TIME_OUT_S, TimeUnit.SECONDS)); + } catch (InterruptedException e) { + throw new AssertionError(e); + } + return PREDICATE.test(i); + }); + + var computingThread = CompletableFuture.supplyAsync(() -> constant.contains(candidate), testExecutor); + assertTrue(entered.await(TIME_OUT_S, TimeUnit.SECONDS)); + + var waitingThread = CompletableFuture.supplyAsync(() -> { + waiting.countDown(); + return constant.contains(candidate); + }, testExecutor); + + assertTrue(waiting.await(TIME_OUT_S, TimeUnit.SECONDS)); + Thread.sleep(OVERLAP_TIME_MS); + assertFalse(waitingThread.isDone(), "contending thread should be be blocked"); + + release.countDown(); + + assertEquals(PREDICATE.test(candidate), computingThread.get(TIME_OUT_S, TimeUnit.SECONDS)); + assertEquals(PREDICATE.test(candidate), waitingThread.get(TIME_OUT_S, TimeUnit.SECONDS)); + } + } + } + + @ParameterizedTest + @MethodSource("nonEmptySets") + void interruptStatusIsPreservedForComputingThread(Set set) throws Exception { + // Make sure to exercise both member and non-member statuses + for (Value candidate : set) { + int unset = -1; + int notInterrupted = 0; + int interrupted = 1; + AtomicInteger observedInterrupted = new AtomicInteger(unset); + CountDownLatch supplierRunning = new CountDownLatch(1); + CountDownLatch release = new CountDownLatch(1); + + Set constant = Set.ofLazy(set, i -> { + supplierRunning.countDown(); + try { + assertTrue(release.await(TIME_OUT_S, TimeUnit.SECONDS)); + } catch (InterruptedException e) { + observedInterrupted.set(Thread.currentThread().isInterrupted() ? interrupted : notInterrupted); + Thread.currentThread().interrupt(); // restore if await cleared it + } + return PREDICATE.test(i); + }); + + AtomicInteger interruptedAfterGet = new AtomicInteger(unset); + + Thread t = Thread.ofPlatform().start(() -> { + assertEquals(PREDICATE.test(candidate), constant.contains(candidate)); + interruptedAfterGet.set(Thread.currentThread().isInterrupted() ? interrupted : notInterrupted); + }); + + assertTrue(supplierRunning.await(TIME_OUT_S, TimeUnit.SECONDS)); + Thread.sleep(OVERLAP_TIME_MS); + t.interrupt(); + release.countDown(); + t.join(); + + assertEquals(notInterrupted, observedInterrupted.get()); // Observed before restoration of the status + assertEquals(interrupted, interruptedAfterGet.get(), "get() cleared interrupt status"); + } + } + + // Immutability + @ParameterizedTest + @MethodSource("unsupportedOperations") + void unsupported(Operation operation) { + assertThrowsForOperation(UnsupportedOperationException.class, operation); + } + + // Method parameter invariant checking + + @ParameterizedTest + @MethodSource("nullAverseOperations") + void nullAverse(Operation operation) { + assertThrowsForOperation(NullPointerException.class, operation); + } + + static void assertThrowsForOperation(Class expectedType, Operation operation) { + for (Set set : allSets().toList()) { + var lazy = newLazySet(set); + assertThrows(expectedType, () -> operation.accept(lazy), set.getClass().getSimpleName() + " " + operation); + } + } + + // Implementing interfaces + + @ParameterizedTest + @MethodSource("allSets") + void serializable(Set set) { + var lazy = newLazySet(set); + assertFalse(lazy instanceof Serializable); + } + + @Test + void overriddenEnum() { + final var overridden = Value.THIRTEEN; + Set enumMap = Set.ofLazy(EnumSet.of(overridden), PREDICATE); + assertEquals(PREDICATE.test(overridden), enumMap.contains(overridden), enumMap.toString()); + } + + // Support constructs + + record Operation(String name, + Consumer> consumer) implements Consumer> { + @Override + public void accept(Set set) { consumer.accept(set); } + @Override + public String toString() { return name; } + } + + static Stream nullAverseOperations() { + return Stream.of( + new Operation("forEach", m -> m.forEach(null)), + new Operation("containsAll", m -> m.containsAll(null)), + new Operation("contains", m -> m.contains(null)) + ); + } + + static Stream unsupportedOperations() { + return Stream.of( + new Operation("clear", Set::clear), + new Operation("add", m -> m.add(MEMBER)), + new Operation("addAll", m -> m.addAll(Set.of(MEMBER))), + new Operation("remove", m -> m.remove(MEMBER)), + new Operation("removeAll",m -> m.removeAll(Set.of(MEMBER))), + new Operation("retainAll",m -> m.retainAll(Set.of(MEMBER))), + new Operation("iter.rm", m -> m.iterator().remove()) + ); + } + + static Set newLazySet(Set set) { + return Set.ofLazy(set, PREDICATE); + } + + static Set newRegularSet(Set set) { + return set.stream() + .filter(PREDICATE) + .collect(Collectors.toSet()); + } + + private static Stream> nonEmptySets() { + return Stream.of( + Set.of(MEMBER, NON_MEMBER), + linkedHashSet(NON_MEMBER, MEMBER), + treeSet(MEMBER, NON_MEMBER), + EnumSet.of(MEMBER, NON_MEMBER) + ); + } + + private static Stream> emptySets() { + return Stream.of( + Set.of(), + linkedHashSet(), + treeSet(), + EnumSet.noneOf(Value.class) + ); + } + + private static Stream> allSets() { + return Stream.concat( + nonEmptySets(), + emptySets() + ); + } + + static Set treeSet(Value... values) { + return populate(new TreeSet<>(Comparator.comparingInt(Value::asInt).reversed()),values); + } + + static Set linkedHashSet(Value... values) { + return populate(new LinkedHashSet<>(), values); + } + + static Set populate(Set set, Value... values) { + set.addAll(Arrays.asList(values)); + return set; + } + + // JEP Example + class Application { + + enum Option { VERBOSE, DRY_RUN, STRICT } + + // Return true when the given Option is enabled + private static boolean isEnabled(Option option) { + // Parse command line, read configuration file, load database + return true; + } + + // Lazily initialized Set of Options + static final Set