diff --git a/make/conf/github-actions.conf b/make/conf/github-actions.conf index ebfc9191535..6771e8923dc 100644 --- a/make/conf/github-actions.conf +++ b/make/conf/github-actions.conf @@ -29,21 +29,21 @@ GTEST_VERSION=1.14.0 JTREG_VERSION=8.2.1+1 LINUX_X64_BOOT_JDK_EXT=tar.gz -LINUX_X64_BOOT_JDK_URL=https://download.java.net/java/GA/jdk25/bd75d5f9689641da8e1daabeccb5528b/36/GPL/openjdk-25_linux-x64_bin.tar.gz -LINUX_X64_BOOT_JDK_SHA256=59cdcaf255add4721de38eb411d4ecfe779356b61fb671aee63c7dec78054c2b +LINUX_X64_BOOT_JDK_URL=https://download.java.net/java/GA/jdk26/c3cc523845074aa0af4f5e1e1ed4151d/35/GPL/openjdk-26_linux-x64_bin.tar.gz +LINUX_X64_BOOT_JDK_SHA256=83c78367f8c81257beef72aca4bbbf8e6dac8ca2b3a4546a85879a09e6e4e128 ALPINE_LINUX_X64_BOOT_JDK_EXT=tar.gz -ALPINE_LINUX_X64_BOOT_JDK_URL=https://github.com/adoptium/temurin25-binaries/releases/download/jdk-25%2B36/OpenJDK25U-jdk_x64_alpine-linux_hotspot_25_36.tar.gz -ALPINE_LINUX_X64_BOOT_JDK_SHA256=637e47474d411ed86134f413af7d5fef4180ddb0bf556347b7e74a88cf8904c8 +ALPINE_LINUX_X64_BOOT_JDK_URL=https://github.com/adoptium/temurin26-binaries/releases/download/jdk-26%2B35/OpenJDK26U-jdk_x64_alpine-linux_hotspot_26_35.tar.gz +ALPINE_LINUX_X64_BOOT_JDK_SHA256=c105e581fdccb4e7120d889235d1ad8d5b2bed0af4972bc881e0a8ba687c94a4 MACOS_AARCH64_BOOT_JDK_EXT=tar.gz -MACOS_AARCH64_BOOT_JDK_URL=https://download.java.net/java/GA/jdk25/bd75d5f9689641da8e1daabeccb5528b/36/GPL/openjdk-25_macos-aarch64_bin.tar.gz -MACOS_AARCH64_BOOT_JDK_SHA256=2006337bf326fdfdf6117081751ba38c1c8706d63419ecac7ff102ff7c776876 +MACOS_AARCH64_BOOT_JDK_URL=https://download.java.net/java/GA/jdk26/c3cc523845074aa0af4f5e1e1ed4151d/35/GPL/openjdk-26_macos-aarch64_bin.tar.gz +MACOS_AARCH64_BOOT_JDK_SHA256=254586bcd1bf6dcd125ad667ac32562cb1e2ab1abf3a61fb117b6fabb571e765 MACOS_X64_BOOT_JDK_EXT=tar.gz -MACOS_X64_BOOT_JDK_URL=https://download.java.net/java/GA/jdk25/bd75d5f9689641da8e1daabeccb5528b/36/GPL/openjdk-25_macos-x64_bin.tar.gz -MACOS_X64_BOOT_JDK_SHA256=47482ad9888991ecac9b2bcc131e2b53ff78aff275104cef85f66252308e8a09 +MACOS_X64_BOOT_JDK_URL=https://download.java.net/java/GA/jdk26/c3cc523845074aa0af4f5e1e1ed4151d/35/GPL/openjdk-26_macos-x64_bin.tar.gz +MACOS_X64_BOOT_JDK_SHA256=8642b89d889c14ede2c446fd5bbe3621c8a3082e3df02013fd1658e39f52929a WINDOWS_X64_BOOT_JDK_EXT=zip -WINDOWS_X64_BOOT_JDK_URL=https://download.java.net/java/GA/jdk25/bd75d5f9689641da8e1daabeccb5528b/36/GPL/openjdk-25_windows-x64_bin.zip -WINDOWS_X64_BOOT_JDK_SHA256=85bcc178461e2cb3c549ab9ca9dfa73afd54c09a175d6510d0884071867137d3 +WINDOWS_X64_BOOT_JDK_URL=https://download.java.net/java/GA/jdk26/c3cc523845074aa0af4f5e1e1ed4151d/35/GPL/openjdk-26_windows-x64_bin.zip +WINDOWS_X64_BOOT_JDK_SHA256=2dd2d92c9374cd49a120fe9d916732840bf6bb9f0e0cc29794917a3c08b99c5f diff --git a/make/conf/jib-profiles.js b/make/conf/jib-profiles.js index 76a94b7789e..4c1d2835054 100644 --- a/make/conf/jib-profiles.js +++ b/make/conf/jib-profiles.js @@ -387,8 +387,8 @@ var getJibProfilesCommon = function (input, data) { }; }; - common.boot_jdk_version = "25"; - common.boot_jdk_build_number = "37"; + common.boot_jdk_version = "26"; + common.boot_jdk_build_number = "35"; common.boot_jdk_home = input.get("boot_jdk", "install_path") + "/jdk-" + common.boot_jdk_version + (input.build_os == "macosx" ? ".jdk/Contents/Home" : ""); diff --git a/make/conf/version-numbers.conf b/make/conf/version-numbers.conf index 4392d86ac33..4f63179ae05 100644 --- a/make/conf/version-numbers.conf +++ b/make/conf/version-numbers.conf @@ -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 @@ -37,6 +37,6 @@ DEFAULT_VERSION_DATE=2026-09-15 DEFAULT_VERSION_CLASSFILE_MAJOR=71 # "`$EXPR $DEFAULT_VERSION_FEATURE + 44`" DEFAULT_VERSION_CLASSFILE_MINOR=0 DEFAULT_VERSION_DOCS_API_SINCE=11 -DEFAULT_ACCEPTABLE_BOOT_VERSIONS="25 26 27" +DEFAULT_ACCEPTABLE_BOOT_VERSIONS="26 27" DEFAULT_JDK_SOURCE_TARGET_VERSION=27 DEFAULT_PROMOTED_VERSION_PRE=ea diff --git a/src/hotspot/os_cpu/windows_aarch64/sve_windows_aarch64.S b/src/hotspot/os_cpu/windows_aarch64/sve_windows_aarch64.S new file mode 100644 index 00000000000..e0c85830bd4 --- /dev/null +++ b/src/hotspot/os_cpu/windows_aarch64/sve_windows_aarch64.S @@ -0,0 +1,42 @@ +; +; 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. +; + + ; Support for int get_sve_vector_length(); + ; + ; Returns the current SVE vector length in bytes. + ; This function uses the INCB instruction which increments a register + ; by the number of bytes in an SVE vector register. + ; + ; Note: This function will fault if SVE is not available or enabled. + ; The caller must ensure SVE support is detected before calling. + + ALIGN 4 + EXPORT get_sve_vector_length + AREA sve_text, CODE + +get_sve_vector_length + mov x0, #0 + incb x0 + ret + + END diff --git a/src/hotspot/os_cpu/windows_aarch64/vm_version_windows_aarch64.cpp b/src/hotspot/os_cpu/windows_aarch64/vm_version_windows_aarch64.cpp index 93beb549366..e78a37b4178 100644 --- a/src/hotspot/os_cpu/windows_aarch64/vm_version_windows_aarch64.cpp +++ b/src/hotspot/os_cpu/windows_aarch64/vm_version_windows_aarch64.cpp @@ -26,16 +26,19 @@ #include "runtime/os.hpp" #include "runtime/vm_version.hpp" +// Assembly function to get SVE vector length using INCB instruction +extern "C" int get_sve_vector_length(); + int VM_Version::get_current_sve_vector_length() { assert(VM_Version::supports_sve(), "should not call this"); - ShouldNotReachHere(); - return 0; + // Use assembly instruction to get the actual SVE vector length + return VM_Version::supports_sve() ? get_sve_vector_length() : 0; // This value is in bytes } int VM_Version::set_and_get_current_sve_vector_length(int length) { assert(VM_Version::supports_sve(), "should not call this"); - ShouldNotReachHere(); - return 0; + // Use assembly instruction to get the SVE vector length + return VM_Version::supports_sve() ? get_sve_vector_length() : 0; // This value is in bytes } void VM_Version::get_os_cpu_info() { @@ -47,11 +50,29 @@ void VM_Version::get_os_cpu_info() { set_feature(CPU_AES); set_feature(CPU_SHA1); set_feature(CPU_SHA2); + set_feature(CPU_PMULL); } if (IsProcessorFeaturePresent(PF_ARM_VFP_32_REGISTERS_AVAILABLE)) { set_feature(CPU_ASIMD); } - // No check for CPU_PMULL, CPU_SVE, CPU_SVE2 + if (IsProcessorFeaturePresent(PF_ARM_V81_ATOMIC_INSTRUCTIONS_AVAILABLE)) { + set_feature(CPU_LSE); + } + if (IsProcessorFeaturePresent(PF_ARM_SVE_INSTRUCTIONS_AVAILABLE)) { + set_feature(CPU_SVE); + } + if (IsProcessorFeaturePresent(PF_ARM_SVE2_INSTRUCTIONS_AVAILABLE)) { + set_feature(CPU_SVE2); + } + if (IsProcessorFeaturePresent(PF_ARM_SVE_BITPERM_INSTRUCTIONS_AVAILABLE)) { + set_feature(CPU_SVEBITPERM); + } + if (IsProcessorFeaturePresent(PF_ARM_SHA3_INSTRUCTIONS_AVAILABLE)) { + set_feature(CPU_SHA3); + } + if (IsProcessorFeaturePresent(PF_ARM_SHA512_INSTRUCTIONS_AVAILABLE)) { + set_feature(CPU_SHA512); + } __int64 dczid_el0 = _ReadStatusReg(0x5807 /* ARM64_DCZID_EL0 */); @@ -102,8 +123,8 @@ void VM_Version::get_os_cpu_info() { SYSTEM_INFO si; GetSystemInfo(&si); _model = si.wProcessorLevel; - _variant = si.wProcessorRevision / 0xFF; - _revision = si.wProcessorRevision & 0xFF; + _variant = (si.wProcessorRevision >> 8) & 0xFF; // Variant is the upper byte of wProcessorRevision + _revision = si.wProcessorRevision & 0xFF; // Revision is the lower byte of wProcessorRevision } } } diff --git a/src/hotspot/share/code/aotCodeCache.cpp b/src/hotspot/share/code/aotCodeCache.cpp index 030e2684bfc..938cf4eaa41 100644 --- a/src/hotspot/share/code/aotCodeCache.cpp +++ b/src/hotspot/share/code/aotCodeCache.cpp @@ -380,37 +380,110 @@ void AOTCodeCache::init_early_c1_table() { } } +// macro to record which flags are set -- flag_type selects the +// relevant accessor e.g. set_flag, set_x86_flag, set_x86_use_flag. +// n.b. flag_enum_name and global_flag_name are both needed because we +// don't have consistent conventions for naming global flags e.g. +// EnableContended vs UseMulAddIntrinsic vs UseCRC32Intrinsics + +#define RECORD_FLAG(flag_type, flag_enum_name, global_flag_name) \ + if (global_flag_name) { \ + set_ ## flag_type ## flag(flag_enum_name); \ + } + void AOTCodeCache::Config::record(uint cpu_features_offset) { _flags = 0; #ifdef ASSERT - _flags |= debugVM; + set_flag(debugVM); #endif - if (UseCompressedOops) { - _flags |= compressedOops; - } - if (UseTLAB) { - _flags |= useTLAB; - } + RECORD_FLAG(, compressedOops, UseCompressedOops); + RECORD_FLAG(, useTLAB, UseTLAB); if (JavaAssertions::systemClassDefault()) { - _flags |= systemClassAssertions; + set_flag(systemClassAssertions); } if (JavaAssertions::userClassDefault()) { - _flags |= userClassAssertions; - } - if (EnableContended) { - _flags |= enableContendedPadding; - } - if (RestrictContended) { - _flags |= restrictContendedPadding; + set_flag(userClassAssertions); } + RECORD_FLAG(, enableContendedPadding, EnableContended); + RECORD_FLAG(, restrictContendedPadding, RestrictContended); + _compressedOopShift = CompressedOops::shift(); _compressedOopBase = CompressedOops::base(); _compressedKlassShift = CompressedKlassPointers::shift(); _contendedPaddingWidth = ContendedPaddingWidth; _gc = (uint)Universe::heap()->kind(); + _optoLoopAlignment = (uint)OptoLoopAlignment; + _codeEntryAlignment = (uint)CodeEntryAlignment; + _allocatePrefetchLines = (uint)AllocatePrefetchLines; + _allocateInstancePrefetchLines = (uint)AllocateInstancePrefetchLines; + _allocatePrefetchDistance = (uint)AllocatePrefetchDistance; + _allocatePrefetchStepSize = (uint)AllocatePrefetchStepSize; + _use_intrinsics_flags = 0; + RECORD_FLAG(use_, useCRC32, UseCRC32Intrinsics); + RECORD_FLAG(use_, useCRC32C, UseCRC32CIntrinsics); +#ifdef COMPILER2 + _maxVectorSize = (uint)MaxVectorSize; + _arrayOperationPartialInlineSize = (uint)ArrayOperationPartialInlineSize; + RECORD_FLAG(use_, useMultiplyToLen, UseMultiplyToLenIntrinsic); + RECORD_FLAG(use_, useSquareToLen, UseSquareToLenIntrinsic); + RECORD_FLAG(use_, useMulAdd, UseMulAddIntrinsic); + RECORD_FLAG(use_, useMontgomeryMultiply, UseMontgomeryMultiplyIntrinsic); + RECORD_FLAG(use_, useMontgomerySquare, UseMontgomerySquareIntrinsic); +#endif // COMPILER2 + RECORD_FLAG(use_, useChaCha20, UseChaCha20Intrinsics); + RECORD_FLAG(use_, useDilithium, UseDilithiumIntrinsics); + RECORD_FLAG(use_, useKyber, UseKyberIntrinsics); + RECORD_FLAG(use_, useBASE64, UseBASE64Intrinsics); + RECORD_FLAG(use_, useAdler32, UseAdler32Intrinsics); + RECORD_FLAG(use_, useAES, UseAESIntrinsics); + RECORD_FLAG(use_, useAESCTR, UseAESCTRIntrinsics); + RECORD_FLAG(use_, useGHASH, UseGHASHIntrinsics); + RECORD_FLAG(use_, useMD5, UseMD5Intrinsics); + RECORD_FLAG(use_, useSHA1, UseSHA1Intrinsics); + RECORD_FLAG(use_, useSHA256, UseSHA256Intrinsics); + RECORD_FLAG(use_, useSHA512, UseSHA512Intrinsics); + RECORD_FLAG(use_, useSHA3, UseSHA3Intrinsics); + RECORD_FLAG(use_, usePoly1305, UsePoly1305Intrinsics); + RECORD_FLAG(use_, useVectorizedMismatch,UseVectorizedMismatchIntrinsic ); + RECORD_FLAG(use_, useSecondarySupersTable, UseSecondarySupersTable); +#if defined(X86) && !defined(ZERO) + _avx3threshold = (uint)AVX3Threshold; + _useAVX = (uint)UseAVX; + _x86_flags = 0; + RECORD_FLAG(x86_, x86_enableX86ECoreOpts, EnableX86ECoreOpts); + RECORD_FLAG(x86_, x86_useUnalignedLoadStores, UseUnalignedLoadStores); + RECORD_FLAG(x86_, x86_useAPX, UseAPX); + + _x86_use_intrinsics_flags = 0; + RECORD_FLAG(x86_use_, x86_useLibm, UseLibmIntrinsic); + RECORD_FLAG(x86_use_, x86_useIntPoly, UseIntPolyIntrinsics); +#endif // defined(X86) && !defined(ZERO) +#if defined(AARCH64) && !defined(ZERO) + _prefetchCopyIntervalInBytes = (uint)PrefetchCopyIntervalInBytes; + _blockZeroingLowLimit = (uint)BlockZeroingLowLimit; + _softwarePrefetchHintDistance = (uint)SoftwarePrefetchHintDistance; + _useSVE = (uint)UseSVE; + _aarch64_flags = 0; + RECORD_FLAG(aarch64_, aarch64_avoidUnalignedAccesses, AvoidUnalignedAccesses); + RECORD_FLAG(aarch64_, aarch64_useSIMDForMemoryOps, UseSIMDForMemoryOps); + RECORD_FLAG(aarch64_, aarch64_useSIMDForArrayEquals, UseSIMDForArrayEquals); + RECORD_FLAG(aarch64_, aarch64_useSIMDForSHA3, UseSIMDForSHA3Intrinsic); + RECORD_FLAG(aarch64_, aarch64_useLSE, UseLSE); + + _aarch64_use_intrinsics_flags = 0; + RECORD_FLAG(aarch64_use_, aarch64_useBlockZeroing, UseBlockZeroing); + RECORD_FLAG(aarch64_use_, aarch64_useSIMDForBigIntegerShift, UseSIMDForBigIntegerShiftIntrinsics); + RECORD_FLAG(aarch64_use_, aarch64_useSimpleArrayEquals, UseSimpleArrayEquals); + RECORD_FLAG(aarch64_use_, aarch64_useSecondarySupersCache, UseSecondarySupersCache); +#endif // defined(AARCH64) && !defined(ZERO) +#if INCLUDE_JVMCI + _enableJVMCI = (uint)EnableJVMCI; +#endif _cpu_features_offset = cpu_features_offset; } +#undef RECORD_FLAG + bool AOTCodeCache::Config::verify_cpu_features(AOTCodeCache* cache) const { LogStreamHandle(Debug, aot, codecache, init) log; uint offset = _cpu_features_offset; @@ -451,15 +524,27 @@ bool AOTCodeCache::Config::verify_cpu_features(AOTCodeCache* cache) const { return true; } +// macro to do *standard* flag eq checks -- flag_type selects the +// relevant accessor e.g. test_flag, test_x86_flag, test_x86_use_flag. +// n.b. flag_enum_name and global_flag_name are both needed because we +// don't have consistent conventions for naming global flags e.g. +// EnableContended vs UseMulAddIntrinsic vs UseCRC32Intrinsics + +#define CHECK_FLAG(flag_type, flag_enum_name, global_flag_name) \ + if (test_ ## flag_type ## flag(flag_enum_name) != global_flag_name) { \ + log_debug(aot, codecache, init)("AOT Code Cache disabled: it was created with " # global_flag_name " = %s vs current %s" , (global_flag_name ? "false" : "true"), (global_flag_name ? "true" : "false")); \ + return false; \ + } + bool AOTCodeCache::Config::verify(AOTCodeCache* cache) const { // First checks affect all cached AOT code #ifdef ASSERT - if ((_flags & debugVM) == 0) { + if (!test_flag(debugVM)) { log_debug(aot, codecache, init)("AOT Code Cache disabled: it was created by product VM, it can't be used by debug VM"); return false; } #else - if ((_flags & debugVM) != 0) { + if (test_flag(debugVM)) { log_debug(aot, codecache, init)("AOT Code Cache disabled: it was created by debug VM, it can't be used by product VM"); return false; } @@ -476,9 +561,195 @@ bool AOTCodeCache::Config::verify(AOTCodeCache* cache) const { return false; } + // check CPU features before checking flags that may be + // auto-configured in response to them + if (!verify_cpu_features(cache)) { + return false; + } + + // change to EnableContended can affect validity of nmethods + CHECK_FLAG(, enableContendedPadding, EnableContended); + // change to RestrictContended can affect validity of nmethods + CHECK_FLAG(, restrictContendedPadding, RestrictContended); + + // Tests for config options which might affect validity of adapters, + // stubs or nmethods. Currently we take a pessemistic stand and + // drop the whole cache if any of these are changed. + + // change to opto alignment can affect performance of array copy + // stubs and nmethods + if (_optoLoopAlignment != (uint)OptoLoopAlignment) { + log_debug(aot, codecache, init)("AOT Code Cache disabled: it was created with OptoLoopAlignment = %d vs current %d", (int)_optoLoopAlignment, (int)OptoLoopAlignment); + return false; + } + + // change to CodeEntryAlignment can affect performance of array + // copy stubs and nmethods + if (_codeEntryAlignment != CodeEntryAlignment) { + log_debug(aot, codecache, init)("AOT Code Cache disabled: it was created with CodeEntryAlignment = %d vs current %d", _codeEntryAlignment, CodeEntryAlignment); + return false; + } + + // changing Prefetch configuration can affect validity of nmethods + // and stubs + if (_allocatePrefetchLines != (uint)AllocatePrefetchLines) { + log_debug(aot, codecache, init)("AOT Code Cache disabled: it was created with = %d vs current %d", (int)_allocatePrefetchLines, (int)AllocatePrefetchLines); + return false; + } + if (_allocateInstancePrefetchLines != (uint)AllocateInstancePrefetchLines) { + log_debug(aot, codecache, init)("AOT Code Cache disabled: it was created with = %d vs current %d", (int)_allocateInstancePrefetchLines, (int)AllocateInstancePrefetchLines); + return false; + } + if (_allocatePrefetchDistance != (uint)AllocatePrefetchDistance) { + log_debug(aot, codecache, init)("AOT Code Cache disabled: it was created with = %d vs current %d", (int)_allocatePrefetchDistance, (int)AllocatePrefetchDistance); + return false; + } + if (_allocatePrefetchStepSize != (uint)AllocatePrefetchStepSize) { + log_debug(aot, codecache, init)("AOT Code Cache disabled: it was created with = %d vs current %d", (int)_allocatePrefetchStepSize, (int)AllocatePrefetchStepSize); + return false; + } + + // check intrinsic use settings are compatible + + CHECK_FLAG(use_, useCRC32, UseCRC32Intrinsics); + CHECK_FLAG(use_, useCRC32C, UseCRC32CIntrinsics); + +#ifdef COMPILER2 + // change to MaxVectorSize can affect validity of array copy/fill + // stubs + if (_maxVectorSize != (uint)MaxVectorSize) { + log_debug(aot, codecache, init)("AOT Code Cache disabled: it was created with MaxVectorSize = %d vs current %d", (int)_maxVectorSize, (int)MaxVectorSize); + return false; + } + + // changing ArrayOperationPartialInlineSize can affect validity of + // nmethods and stubs + if (_arrayOperationPartialInlineSize != (uint)ArrayOperationPartialInlineSize) { + log_debug(aot, codecache, init)("AOT Code Cache disabled: it was created with ArrayOperationPartialInlineSize = %d vs current %d", (int)_arrayOperationPartialInlineSize, (int)ArrayOperationPartialInlineSize); + return false; + } + CHECK_FLAG(use_, useMultiplyToLen, UseMultiplyToLenIntrinsic); + CHECK_FLAG(use_, useSquareToLen, UseSquareToLenIntrinsic); + CHECK_FLAG(use_, useMulAdd, UseMulAddIntrinsic); + CHECK_FLAG(use_, useMontgomeryMultiply,UseMontgomeryMultiplyIntrinsic); + CHECK_FLAG(use_, useMontgomerySquare, UseMontgomerySquareIntrinsic); +#endif // COMPILER2 + CHECK_FLAG(use_, useChaCha20, UseChaCha20Intrinsics); + CHECK_FLAG(use_, useDilithium, UseDilithiumIntrinsics); + CHECK_FLAG(use_, useKyber, UseKyberIntrinsics); + CHECK_FLAG(use_, useBASE64, UseBASE64Intrinsics); + CHECK_FLAG(use_, useAES, UseAESIntrinsics); + CHECK_FLAG(use_, useAESCTR, UseAESCTRIntrinsics); + CHECK_FLAG(use_, useGHASH, UseGHASHIntrinsics); + CHECK_FLAG(use_, useMD5, UseMD5Intrinsics); + CHECK_FLAG(use_, useSHA1, UseSHA1Intrinsics); + CHECK_FLAG(use_, useSHA256, UseSHA256Intrinsics); + CHECK_FLAG(use_, useSHA512, UseSHA512Intrinsics); + CHECK_FLAG(use_, useSHA3, UseSHA3Intrinsics); + CHECK_FLAG(use_, usePoly1305, UsePoly1305Intrinsics); + CHECK_FLAG(use_, useVectorizedMismatch, UseVectorizedMismatchIntrinsic); + CHECK_FLAG(use_, useSecondarySupersTable, UseSecondarySupersTable); + +#if defined(X86) && !defined(ZERO) + // change to AVX3Threshold may affect validity of array copy stubs + // and nmethods + if (_avx3threshold != (uint)AVX3Threshold) { + log_debug(aot, codecache, init)("AOT Code Cache disabled: it was created with AVX3Threshold = %d vs current %d", (int)_avx3threshold, AVX3Threshold); + return false; + } + + // change to UseAVX may affect validity of array copy stubs and + // nmethods + if (_useAVX != (uint)UseAVX) { + log_debug(aot, codecache, init)("AOT Code Cache disabled: it was created with useAVX = %d vs current %d", (int)_useAVX, UseAVX); + return false; + } + + // change to EnableX86ECoreOpts may affect validity of nmethods + CHECK_FLAG(x86_, x86_enableX86ECoreOpts, EnableX86ECoreOpts); + + // switching off UseUnalignedLoadStores can affect validity of fill + // stubs + if (test_x86_flag(x86_useUnalignedLoadStores) && !UseUnalignedLoadStores) { + log_debug(aot, codecache, init)("AOT Code Cache disabled: it was created with UseUnalignedLoadStores = true vs current = false"); + return false; + } + + // change to UseAPX can affect validity of nmethods and stubs + CHECK_FLAG(x86_, x86_useAPX, UseAPX); + + // check x86-specific intrinsic use settings are compatible + + CHECK_FLAG(x86_use_, x86_useLibm, UseLibmIntrinsic); + CHECK_FLAG(x86_use_, x86_useIntPoly, UseIntPolyIntrinsics); +#endif // defined(X86) && !defined(ZERO) + +#if defined(AARCH64) && !defined(ZERO) + // change to PrefetchCopyIntervalInBytes may affect validity of + // array copy stubs + if (_prefetchCopyIntervalInBytes != (uint)PrefetchCopyIntervalInBytes) { + log_debug(aot, codecache, init)("AOT Code Cache disabled: it was created with PrefetchCopyIntervalInBytes = %d vs current %d", (int)_prefetchCopyIntervalInBytes, (int)PrefetchCopyIntervalInBytes); + return false; + } + + // change to BlockZeroingLowLimit may affect validity of array fill + // stubs + if (_blockZeroingLowLimit != (uint)BlockZeroingLowLimit) { + log_debug(aot, codecache, init)("AOT Code Cache disabled: it was created with BlockZeroingLowLimit = %d vs current %d", (int)_blockZeroingLowLimit, (int)BlockZeroingLowLimit); + return false; + } + + // change to SoftwarePrefetchHintDistance may affect validity of array fill + // stubs + if (_softwarePrefetchHintDistance != (uint)SoftwarePrefetchHintDistance) { + log_debug(aot, codecache, init)("AOT Code Cache disabled: it was created with SoftwarePrefetchHintDistance = %d vs current %d", (int)_softwarePrefetchHintDistance, (int)SoftwarePrefetchHintDistance); + return false; + } + + // change to UseSVE may affect validity of stubs and nmethods + if (_useSVE != (uint)UseSVE) { + log_debug(aot, codecache, init)("AOT Code Cache disabled: it was created with UseSVE = %d vs current %d",(int)_useSVE, UseSVE); + return false; + } + + // switching on AvoidUnalignedAccesses may affect validity of array + // copy stubs and nmethods + if (!test_aarch64_flag(aarch64_avoidUnalignedAccesses) && AvoidUnalignedAccesses) { + log_debug(aot, codecache, init)("AOT Code Cache disabled: it was created with AvoidUnalignedAccesses = false vs current = true"); + return false; + } + + // change to UseSIMDForMemoryOps may affect validity of array + // copy stubs and nmethods + CHECK_FLAG(aarch64_, aarch64_useSIMDForMemoryOps, UseSIMDForMemoryOps); + // change to UseSIMDForArrayEquals may affect validity of array + // copy stubs and nmethods + CHECK_FLAG(aarch64_, aarch64_useSIMDForArrayEquals, UseSIMDForArrayEquals); + // change to useSIMDForSHA3 may affect validity of SHA3 stubs + CHECK_FLAG(aarch64_, aarch64_useSIMDForSHA3, UseSIMDForSHA3Intrinsic); + // change to UseLSE may affect validity of stubs and nmethods + CHECK_FLAG(aarch64_, aarch64_useLSE, UseLSE); + + // check aarch64-specific intrinsic use settings are compatible + + CHECK_FLAG(aarch64_use_, aarch64_useBlockZeroing, UseBlockZeroing); + CHECK_FLAG(aarch64_use_, aarch64_useSIMDForBigIntegerShift, UseSIMDForBigIntegerShiftIntrinsics); + CHECK_FLAG(aarch64_use_, aarch64_useSimpleArrayEquals, UseSimpleArrayEquals); + CHECK_FLAG(aarch64_use_, aarch64_useSecondarySupersCache, UseSecondarySupersCache); +#endif // defined(AARCH64) && !defined(ZERO) + +#if INCLUDE_JVMCI + // change to EnableJVMCI will affect validity of adapters and + // nmethods + if (_enableJVMCI != (uint)EnableJVMCI) { + log_debug(aot, codecache, init)("AOT Code Cache disabled: it was created with EnableJVMCI = %s vs current %s", (_enableJVMCI ? "true" : "false"), (EnableJVMCI ? "true" : "false")); + return false; + } +#endif // INCLUDE_JVMCI + // The following checks do not affect AOT adapters caching - if (((_flags & compressedOops) != 0) != UseCompressedOops) { + if (test_flag(compressedOops) != UseCompressedOops) { log_debug(aot, codecache, init)("AOT Code Cache disabled: it was created with UseCompressedOops = %s", UseCompressedOops ? "false" : "true"); AOTStubCaching = false; } @@ -493,12 +764,11 @@ bool AOTCodeCache::Config::verify(AOTCodeCache* cache) const { AOTStubCaching = false; } - if (!verify_cpu_features(cache)) { - return false; - } return true; } +#undef TEST_FLAG + bool AOTCodeCache::Header::verify(uint load_size) const { if (_version != AOT_CODE_VERSION) { log_debug(aot, codecache, init)("AOT Code Cache disabled: different AOT Code version %d vs %d recorded in AOT Code header", AOT_CODE_VERSION, _version); diff --git a/src/hotspot/share/code/aotCodeCache.hpp b/src/hotspot/share/code/aotCodeCache.hpp index b0d39ff3e08..52b0adfba48 100644 --- a/src/hotspot/share/code/aotCodeCache.hpp +++ b/src/hotspot/share/code/aotCodeCache.hpp @@ -173,6 +173,16 @@ protected: uint _compressedKlassShift; uint _contendedPaddingWidth; uint _gc; + uint _optoLoopAlignment; + uint _codeEntryAlignment; + uint _allocatePrefetchLines; + uint _allocateInstancePrefetchLines; + uint _allocatePrefetchDistance; + uint _allocatePrefetchStepSize; +#ifdef COMPILER2 + uint _maxVectorSize; + uint _arrayOperationPartialInlineSize; +#endif // COMPILER2 enum Flags { none = 0, debugVM = 1, @@ -184,8 +194,90 @@ protected: restrictContendedPadding = 64 }; uint _flags; + enum IntrinsicsUseFlags { + use_none = 0, + useCRC32 = 1 << 0, + useCRC32C = 1 << 1, + useMultiplyToLen = 1 << 2, + useSquareToLen = 1 << 3, + useMulAdd = 1 << 4, + useMontgomeryMultiply = 1 << 5, + useMontgomerySquare = 1 << 6, + useChaCha20 = 1 << 7, + useDilithium = 1 << 8, + useKyber = 1 << 9, + useBASE64 = 1 << 10, + useAdler32 = 1 << 11, + useAES = 1 << 12, + useAESCTR = 1 << 13, + useGHASH = 1 << 14, + useMD5 = 1 << 15, + useSHA1 = 1 << 16, + useSHA256 = 1 << 17, + useSHA512 = 1 << 18, + useSHA3 = 1 << 19, + usePoly1305 = 1 << 20, + useVectorizedMismatch = 1 << 21, + useSecondarySupersTable = 1 << 22, + }; + uint _use_intrinsics_flags; + bool test_flag(enum Flags flag) const { return (_flags & flag) != 0; } + bool test_use_flag(enum IntrinsicsUseFlags flag) const { return (_use_intrinsics_flags & flag) != 0; } + void set_flag(enum Flags flag) { _flags |= flag; } + void set_use_flag(enum IntrinsicsUseFlags flag) { _use_intrinsics_flags |= flag; } +#if defined(X86) && !defined(ZERO) + uint _avx3threshold; + uint _useAVX; + enum X86Flags { + x86_none = 0, + x86_enableX86ECoreOpts = 1, + x86_useUnalignedLoadStores = 2, + x86_useAPX = 4 + }; + uint _x86_flags; + enum X86IntrinsicsUseFlags { + x86_use_none = 0, + x86_useLibm = 1 << 1, + x86_useIntPoly = 1 << 2, + }; + uint _x86_use_intrinsics_flags; + bool test_x86_flag(enum X86Flags flag) const { return (_x86_flags & flag) != 0; } + bool test_x86_use_flag(enum X86IntrinsicsUseFlags flag) const { return (_x86_use_intrinsics_flags & flag) != 0; } + void set_x86_flag(enum X86Flags flag) { _x86_flags |= flag; } + void set_x86_use_flag(enum X86IntrinsicsUseFlags flag) { _x86_use_intrinsics_flags |= flag; } +#endif // defined(X86) && !defined(ZERO) +#if defined(AARCH64) && !defined(ZERO) + // this is global but x86 does not use it and aarch64 does + uint _prefetchCopyIntervalInBytes; + uint _blockZeroingLowLimit; + uint _softwarePrefetchHintDistance; + uint _useSVE; + enum AArch64Flags { + aarch64_none = 0, + aarch64_avoidUnalignedAccesses = 1, + aarch64_useSIMDForMemoryOps = 2, + aarch64_useSIMDForArrayEquals = 4, + aarch64_useSIMDForSHA3 = 8, + aarch64_useLSE = 16, + }; + uint _aarch64_flags; + enum AArch64IntrinsicsUseFlags { + aarch64_use_none = 0, + aarch64_useBlockZeroing = 1 << 0, + aarch64_useSIMDForBigIntegerShift = 1 << 1, + aarch64_useSimpleArrayEquals = 1 << 2, + aarch64_useSecondarySupersCache = 1 << 3, + }; + uint _aarch64_use_intrinsics_flags; + bool test_aarch64_flag(enum AArch64Flags flag) const { return (_aarch64_flags & flag) != 0; } + bool test_aarch64_use_flag(enum AArch64IntrinsicsUseFlags flag) const { return (_aarch64_use_intrinsics_flags & flag) != 0; } + void set_aarch64_flag(enum AArch64Flags flag) { _aarch64_flags |= flag; } + void set_aarch64_use_flag(enum AArch64IntrinsicsUseFlags flag) { _aarch64_use_intrinsics_flags |= flag; } +#endif // defined(AARCH64) && !defined(ZERO) +#if INCLUDE_JVMCI + uint _enableJVMCI; +#endif // INCLUDE_JVMCI uint _cpu_features_offset; // offset in the cache where cpu features are stored - public: void record(uint cpu_features_offset); bool verify_cpu_features(AOTCodeCache* cache) const; diff --git a/src/hotspot/share/gc/g1/g1Arguments.cpp b/src/hotspot/share/gc/g1/g1Arguments.cpp index 8bec6e7e86f..c3bbd5a3b52 100644 --- a/src/hotspot/share/gc/g1/g1Arguments.cpp +++ b/src/hotspot/share/gc/g1/g1Arguments.cpp @@ -148,8 +148,9 @@ void G1Arguments::initialize_card_set_configuration() { if (FLAG_IS_DEFAULT(G1RemSetArrayOfCardsEntries)) { uint max_cards_in_inline_ptr = G1CardSetConfiguration::max_cards_in_inline_ptr(G1HeapRegion::LogCardsPerRegion); + const JVMTypedFlagLimit* limit = JVMFlagLimit::get_range_at(FLAG_MEMBER_ENUM(G1RemSetArrayOfCardsEntries))->cast(); FLAG_SET_ERGO(G1RemSetArrayOfCardsEntries, MAX2(max_cards_in_inline_ptr * 2, - G1RemSetArrayOfCardsEntriesBase << region_size_log_mb)); + MIN2(G1RemSetArrayOfCardsEntriesBase << region_size_log_mb, limit->max()))); } // Howl card set container globals. diff --git a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp index e6dd91df84b..fe286793ae7 100644 --- a/src/hotspot/share/gc/g1/g1CollectedHeap.cpp +++ b/src/hotspot/share/gc/g1/g1CollectedHeap.cpp @@ -1652,6 +1652,7 @@ jint G1CollectedHeap::initialize() { } void G1CollectedHeap::stop() { + assert_not_at_safepoint(); // Stop all concurrent threads. We do this to make sure these threads // do not continue to execute and access resources (e.g. logging) // that are destroyed during shutdown. diff --git a/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp b/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp index 1caa8dbdd06..84a0b7588ef 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentMark.cpp @@ -372,63 +372,56 @@ void G1CMMarkStack::set_empty() { G1CMRootMemRegions::G1CMRootMemRegions(uint const max_regions) : _root_regions(MemRegion::create_array(max_regions, mtGC)), _max_regions(max_regions), - _num_root_regions(0), - _claimed_root_regions(0), - _scan_in_progress(false), - _should_abort(false) { } + _num_regions(0), + _num_claimed_regions(0) { } G1CMRootMemRegions::~G1CMRootMemRegions() { MemRegion::destroy_array(_root_regions, _max_regions); } void G1CMRootMemRegions::reset() { - _num_root_regions.store_relaxed(0); + assert_at_safepoint(); + assert(G1CollectedHeap::heap()->collector_state()->is_in_concurrent_start_gc(), "must be"); + + _num_regions.store_relaxed(0); + _num_claimed_regions.store_relaxed(0); } void G1CMRootMemRegions::add(HeapWord* start, HeapWord* end) { assert_at_safepoint(); - size_t idx = _num_root_regions.fetch_then_add(1u); - assert(idx < _max_regions, "Trying to add more root MemRegions than there is space %zu", _max_regions); + uint idx = _num_regions.fetch_then_add(1u); + assert(idx < _max_regions, "Trying to add more root MemRegions than there is space %u", _max_regions); assert(start != nullptr && end != nullptr && start <= end, "Start (" PTR_FORMAT ") should be less or equal to " "end (" PTR_FORMAT ")", p2i(start), p2i(end)); _root_regions[idx].set_start(start); _root_regions[idx].set_end(end); } -void G1CMRootMemRegions::prepare_for_scan() { - assert(!scan_in_progress(), "pre-condition"); - - _scan_in_progress.store_relaxed(num_root_regions() > 0); - - _claimed_root_regions.store_relaxed(0); - _should_abort.store_relaxed(false); -} - const MemRegion* G1CMRootMemRegions::claim_next() { - if (_should_abort.load_relaxed()) { - // If someone has set the should_abort flag, we return null to - // force the caller to bail out of their loop. + uint local_num_regions = num_regions(); + if (num_claimed_regions() >= local_num_regions) { return nullptr; } - uint local_num_root_regions = num_root_regions(); - if (_claimed_root_regions.load_relaxed() >= local_num_root_regions) { - return nullptr; - } - - size_t claimed_index = _claimed_root_regions.fetch_then_add(1u); - if (claimed_index < local_num_root_regions) { + uint claimed_index = _num_claimed_regions.fetch_then_add(1u); + if (claimed_index < local_num_regions) { return &_root_regions[claimed_index]; } return nullptr; } -uint G1CMRootMemRegions::num_root_regions() const { - return (uint)_num_root_regions.load_relaxed(); +bool G1CMRootMemRegions::work_completed() const { + return num_remaining_regions() == 0; +} + +uint G1CMRootMemRegions::num_remaining_regions() const { + uint total = num_regions(); + uint claimed = num_claimed_regions(); + return (total > claimed) ? total - claimed : 0; } bool G1CMRootMemRegions::contains(const MemRegion mr) const { - uint local_num_root_regions = num_root_regions(); + uint local_num_root_regions = num_regions(); for (uint i = 0; i < local_num_root_regions; i++) { if (_root_regions[i].equals(mr)) { return true; @@ -437,42 +430,6 @@ bool G1CMRootMemRegions::contains(const MemRegion mr) const { return false; } -void G1CMRootMemRegions::notify_scan_done() { - MutexLocker x(G1RootRegionScan_lock, Mutex::_no_safepoint_check_flag); - _scan_in_progress.store_relaxed(false); - G1RootRegionScan_lock->notify_all(); -} - -void G1CMRootMemRegions::cancel_scan() { - notify_scan_done(); -} - -void G1CMRootMemRegions::scan_finished() { - assert(scan_in_progress(), "pre-condition"); - - if (!_should_abort.load_relaxed()) { - assert(_claimed_root_regions.load_relaxed() >= num_root_regions(), - "we should have claimed all root regions, claimed %zu, length = %u", - _claimed_root_regions.load_relaxed(), num_root_regions()); - } - - notify_scan_done(); -} - -bool G1CMRootMemRegions::wait_until_scan_finished() { - if (!scan_in_progress()) { - return false; - } - - { - MonitorLocker ml(G1RootRegionScan_lock, Mutex::_no_safepoint_check_flag); - while (scan_in_progress()) { - ml.wait(); - } - } - return true; -} - G1ConcurrentMark::G1ConcurrentMark(G1CollectedHeap* g1h, G1RegionToSpaceMapper* bitmap_storage) : _cm_thread(nullptr), @@ -483,6 +440,7 @@ G1ConcurrentMark::G1ConcurrentMark(G1CollectedHeap* g1h, _heap(_g1h->reserved()), _root_regions(_g1h->max_num_regions()), + _root_region_scan_aborted(false), _global_mark_stack(), @@ -614,6 +572,7 @@ void G1ConcurrentMark::reset() { _region_mark_stats[i].clear(); } + _root_region_scan_aborted.store_relaxed(false); _root_regions.reset(); } @@ -970,8 +929,6 @@ void G1ConcurrentMark::start_full_concurrent_cycle() { satb_mq_set.set_active_all_threads(true, /* new active value */ false /* expected_active */); - _root_regions.prepare_for_scan(); - // update_g1_committed() will be called at the end of an evac pause // when marking is on. So, it's also called at the end of the // concurrent start pause to update the heap end, if the heap expands @@ -982,7 +939,11 @@ void G1ConcurrentMark::start_full_concurrent_cycle() { } void G1ConcurrentMark::start_undo_concurrent_cycle() { - root_regions()->cancel_scan(); + assert_at_safepoint_on_vm_thread(); + // At this time this GC is not a concurrent start gc any more, can only check for young only gc/phase. + assert(_g1h->collector_state()->is_in_young_only_phase(), "must be"); + + abort_root_region_scan_at_safepoint(); // Signal the thread to start work. cm_thread()->start_undo_cycle(); @@ -1094,6 +1055,16 @@ uint G1ConcurrentMark::calc_active_marking_workers() { return result; } +bool G1ConcurrentMark::has_root_region_scan_aborted() const { + return _root_region_scan_aborted.load_relaxed(); +} + +#ifndef PRODUCT +void G1ConcurrentMark::assert_root_region_scan_completed_or_aborted() { + assert(root_regions()->work_completed() || has_root_region_scan_aborted(), "must be"); +} +#endif + void G1ConcurrentMark::scan_root_region(const MemRegion* region, uint worker_id) { #ifdef ASSERT HeapWord* last = region->last(); @@ -1120,45 +1091,76 @@ void G1ConcurrentMark::scan_root_region(const MemRegion* region, uint worker_id) class G1CMRootRegionScanTask : public WorkerTask { G1ConcurrentMark* _cm; + bool _should_yield; + public: - G1CMRootRegionScanTask(G1ConcurrentMark* cm) : - WorkerTask("G1 Root Region Scan"), _cm(cm) { } + G1CMRootRegionScanTask(G1ConcurrentMark* cm, bool should_yield) : + WorkerTask("G1 Root Region Scan"), _cm(cm), _should_yield(should_yield) { } void work(uint worker_id) { - G1CMRootMemRegions* root_regions = _cm->root_regions(); - const MemRegion* region = root_regions->claim_next(); - while (region != nullptr) { + SuspendibleThreadSetJoiner sts_join(_should_yield); + + while (true) { + if (_cm->has_root_region_scan_aborted()) { + return; + } + G1CMRootMemRegions* root_regions = _cm->root_regions(); + const MemRegion* region = root_regions->claim_next(); + if (region == nullptr) { + return; + } _cm->scan_root_region(region, worker_id); - region = root_regions->claim_next(); + if (_should_yield) { + SuspendibleThreadSet::yield(); + // If we yielded, a GC may have processed all root regions, + // so this loop will naturally exit on the next claim_next() call. + // Same if a Full GC signalled abort of the concurrent mark. + } } } }; -void G1ConcurrentMark::scan_root_regions() { - // scan_in_progress() will have been set to true only if there was - // at least one root region to scan. So, if it's false, we - // should not attempt to do any further work. - if (root_regions()->scan_in_progress()) { - assert(!has_aborted(), "Aborting before root region scanning is finished not supported."); - +bool G1ConcurrentMark::scan_root_regions(WorkerThreads* workers, bool concurrent) { + // We first check whether there is any work to do as we might have already aborted + // the concurrent cycle, or ran into a GC that did the actual work when we reach here. + // We want to avoid spinning up the worker threads if that happened. + // (Note that due to races reading the abort-flag, we might spin up the threads anyway). + // + // Abort happens if a Full GC occurs right after starting the concurrent cycle or + // a young gc doing the work. + // + // Concurrent gc threads enter an STS when starting the task, so they stop, then + // continue after that safepoint. + bool do_scan = !root_regions()->work_completed() && !has_root_region_scan_aborted(); + if (do_scan) { // Assign one worker to each root-region but subject to the max constraint. - const uint num_workers = MIN2(root_regions()->num_root_regions(), + // The constraint is also important to avoid accesses beyond the allocated per-worker + // marking helper data structures. We might get passed different WorkerThreads with + // different number of threads (potential worker ids) than helper data structures when + // completing this work during GC. + const uint num_workers = MIN2(root_regions()->num_remaining_regions(), _max_concurrent_workers); + assert(num_workers > 0, "no more remaining root regions to process"); - G1CMRootRegionScanTask task(this); + G1CMRootRegionScanTask task(this, concurrent); log_debug(gc, ergo)("Running %s using %u workers for %u work units.", - task.name(), num_workers, root_regions()->num_root_regions()); - _concurrent_workers->run_task(&task, num_workers); - - // It's possible that has_aborted() is true here without actually - // aborting the survivor scan earlier. This is OK as it's - // mainly used for sanity checking. - root_regions()->scan_finished(); + task.name(), num_workers, root_regions()->num_remaining_regions()); + workers->run_task(&task, num_workers); } + + assert_root_region_scan_completed_or_aborted(); + + return do_scan; } -bool G1ConcurrentMark::wait_until_root_region_scan_finished() { - return root_regions()->wait_until_scan_finished(); +void G1ConcurrentMark::scan_root_regions_concurrently() { + assert(Thread::current() == cm_thread(), "must be on Concurrent Mark Thread"); + scan_root_regions(_concurrent_workers, true /* concurrent */); +} + +bool G1ConcurrentMark::complete_root_regions_scan_in_safepoint() { + assert_at_safepoint_on_vm_thread(); + return scan_root_regions(_g1h->workers(), false /* concurrent */); } void G1ConcurrentMark::add_root_region(G1HeapRegion* r) { @@ -1169,9 +1171,16 @@ bool G1ConcurrentMark::is_root_region(G1HeapRegion* r) { return root_regions()->contains(MemRegion(top_at_mark_start(r), r->top())); } -void G1ConcurrentMark::root_region_scan_abort_and_wait() { - root_regions()->abort(); - root_regions()->wait_until_scan_finished(); +void G1ConcurrentMark::abort_root_region_scan() { + assert_not_at_safepoint(); + + _root_region_scan_aborted.store_relaxed(true); +} + +void G1ConcurrentMark::abort_root_region_scan_at_safepoint() { + assert_at_safepoint_on_vm_thread(); + + _root_region_scan_aborted.store_relaxed(true); } void G1ConcurrentMark::concurrent_cycle_start() { @@ -1948,12 +1957,15 @@ void G1ConcurrentMark::print_stats() { } bool G1ConcurrentMark::concurrent_cycle_abort() { + assert_at_safepoint_on_vm_thread(); + assert(_g1h->collector_state()->is_in_full_gc(), "must be"); + // If we start the compaction before the CM threads finish // scanning the root regions we might trip them over as we'll - // be moving objects / updating references. So let's wait until - // they are done. By telling them to abort, they should complete - // early. - root_region_scan_abort_and_wait(); + // be moving objects / updating references. Since the root region + // scan synchronized with the safepoint, just tell it to abort. + // It will notice when the threads start up again later. + abort_root_region_scan_at_safepoint(); // We haven't started a concurrent cycle no need to do anything; we might have // aborted the marking because of shutting down though. In this case the marking @@ -1983,7 +1995,7 @@ bool G1ConcurrentMark::concurrent_cycle_abort() { } void G1ConcurrentMark::abort_marking_threads() { - assert(!_root_regions.scan_in_progress(), "still doing root region scan"); + assert_root_region_scan_completed_or_aborted(); _has_aborted.store_relaxed(true); _first_overflow_barrier_sync.abort(); _second_overflow_barrier_sync.abort(); diff --git a/src/hotspot/share/gc/g1/g1ConcurrentMark.hpp b/src/hotspot/share/gc/g1/g1ConcurrentMark.hpp index de97179d210..7aa93947204 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentMark.hpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentMark.hpp @@ -288,57 +288,36 @@ private: class G1CMRootMemRegions { // The set of root MemRegions. MemRegion* _root_regions; - size_t const _max_regions; + uint const _max_regions; - Atomic _num_root_regions; // Actual number of root regions. + Atomic _num_regions; // Actual number of root regions. + Atomic _num_claimed_regions; // Number of root regions currently claimed. - Atomic _claimed_root_regions; // Number of root regions currently claimed. - - Atomic _scan_in_progress; - Atomic _should_abort; - - void notify_scan_done(); + uint num_regions() const { return _num_regions.load_relaxed(); } + uint num_claimed_regions() const { return _num_claimed_regions.load_relaxed(); } public: G1CMRootMemRegions(uint const max_regions); ~G1CMRootMemRegions(); - // Reset the data structure to allow addition of new root regions. - void reset(); - void add(HeapWord* start, HeapWord* end); - // Reset the claiming / scanning of the root regions. - void prepare_for_scan(); - - // Forces get_next() to return null so that the iteration aborts early. - void abort() { _should_abort.store_relaxed(true); } - - // Return true if the CM thread are actively scanning root regions, - // false otherwise. - bool scan_in_progress() { return _scan_in_progress.load_relaxed(); } + // Reset data structure to initial state. + void reset(); // Claim the next root MemRegion to scan atomically, or return null if // all have been claimed. const MemRegion* claim_next(); - // The number of root regions to scan. - uint num_root_regions() const; + // Number of root regions to still process. + uint num_remaining_regions() const; + + // Returns whether all root regions have been processed or the processing been aborted. + bool work_completed() const; // Is the given memregion contained in the root regions; the MemRegion must // match exactly. bool contains(const MemRegion mr) const; - - void cancel_scan(); - - // Flag that we're done with root region scanning and notify anyone - // who's waiting on it. If aborted is false, assume that all regions - // have been claimed. - void scan_finished(); - - // If CM threads are still scanning root regions, wait until they - // are done. Return true if we had to wait, false otherwise. - bool wait_until_scan_finished(); }; // This class manages data structures and methods for doing liveness analysis in @@ -367,6 +346,7 @@ class G1ConcurrentMark : public CHeapObj { // Root region tracking and claiming G1CMRootMemRegions _root_regions; + Atomic _root_region_scan_aborted; // For grey objects G1CMMarkStack _global_mark_stack; // Grey objects behind global finger @@ -600,7 +580,7 @@ public: // Notifies marking threads to abort. This is a best-effort notification. Does not // guarantee or update any state after the call. Root region scan must not be - // running. + // running or being aborted. void abort_marking_threads(); // Total cpu time spent in mark worker threads in seconds. @@ -651,17 +631,30 @@ public: // Stop active components/the concurrent mark thread. void stop(); - // Scan all the root regions and mark everything reachable from - // them. - void scan_root_regions(); - bool wait_until_root_region_scan_finished(); void add_root_region(G1HeapRegion* r); bool is_root_region(G1HeapRegion* r); - void root_region_scan_abort_and_wait(); + + // Scan all the root regions concurrently and mark everything reachable from + // them. + void scan_root_regions_concurrently(); + // Complete root region scan work in the safepoint, return if we did some work. + bool complete_root_regions_scan_in_safepoint(); + + // Abort an active concurrent root region scan outside safepoint. + void abort_root_region_scan(); + + bool has_root_region_scan_aborted() const; private: + // Abort an active concurrent root region scan during safepoint. + void abort_root_region_scan_at_safepoint(); + + void assert_root_region_scan_completed_or_aborted() PRODUCT_RETURN; G1CMRootMemRegions* root_regions() { return &_root_regions; } + // Perform root region scan until all root regions have been processed, or + // the process has been aborted. Returns true if we did some work. + bool scan_root_regions(WorkerThreads* workers, bool concurrent); // Scan a single root MemRegion to mark everything reachable from it. void scan_root_region(const MemRegion* region, uint worker_id); diff --git a/src/hotspot/share/gc/g1/g1ConcurrentMarkThread.cpp b/src/hotspot/share/gc/g1/g1ConcurrentMarkThread.cpp index 31d61b8b388..b8c97acd1b0 100644 --- a/src/hotspot/share/gc/g1/g1ConcurrentMarkThread.cpp +++ b/src/hotspot/share/gc/g1/g1ConcurrentMarkThread.cpp @@ -131,14 +131,11 @@ void G1ConcurrentMarkThread::run_service() { update_perf_counter_cpu_time(); } - _cm->root_regions()->cancel_scan(); } void G1ConcurrentMarkThread::stop_service() { if (is_in_progress()) { - // We are not allowed to abort the marking threads during root region scan. - // Needs to be done separately. - _cm->root_region_scan_abort_and_wait(); + _cm->abort_root_region_scan(); _cm->abort_marking_threads(); } @@ -164,7 +161,7 @@ bool G1ConcurrentMarkThread::phase_clear_cld_claimed_marks() { bool G1ConcurrentMarkThread::phase_scan_root_regions() { G1ConcPhaseTimer p(_cm, "Concurrent Scan Root Regions"); - _cm->scan_root_regions(); + _cm->scan_root_regions_concurrently(); update_perf_counter_cpu_time(); return _cm->has_aborted(); } diff --git a/src/hotspot/share/gc/g1/g1GCPhaseTimes.cpp b/src/hotspot/share/gc/g1/g1GCPhaseTimes.cpp index a5013ddbb40..023790a2422 100644 --- a/src/hotspot/share/gc/g1/g1GCPhaseTimes.cpp +++ b/src/hotspot/share/gc/g1/g1GCPhaseTimes.cpp @@ -180,7 +180,7 @@ void G1GCPhaseTimes::reset() { _cur_post_evacuate_cleanup_2_time_ms = 0.0; _cur_resize_heap_time_ms = 0.0; _cur_ref_proc_time_ms = 0.0; - _root_region_scan_wait_time_ms = 0.0; + _root_region_scan_time_ms = 0.0; _external_accounted_time_ms = 0.0; _recorded_prepare_heap_roots_time_ms = 0.0; _recorded_young_cset_choice_time_ms = 0.0; @@ -549,8 +549,8 @@ void G1GCPhaseTimes::print_other(double accounted_ms) const { // In addition, these are not included in G1GCPhaseTimes::_gc_pause_time_ms. // See G1YoungCollector::collect(). void G1GCPhaseTimes::print(bool evacuation_failed) { - if (_root_region_scan_wait_time_ms > 0.0) { - debug_time("Root Region Scan Waiting", _root_region_scan_wait_time_ms); + if (_root_region_scan_time_ms > 0.0) { + debug_time("Root Region Scan", _root_region_scan_time_ms); } // Check if some time has been recorded for verification and only then print diff --git a/src/hotspot/share/gc/g1/g1GCPhaseTimes.hpp b/src/hotspot/share/gc/g1/g1GCPhaseTimes.hpp index 8223148b791..b57bf0d617e 100644 --- a/src/hotspot/share/gc/g1/g1GCPhaseTimes.hpp +++ b/src/hotspot/share/gc/g1/g1GCPhaseTimes.hpp @@ -191,7 +191,7 @@ class G1GCPhaseTimes : public CHeapObj { double _cur_ref_proc_time_ms; // Not included in _gc_pause_time_ms - double _root_region_scan_wait_time_ms; + double _root_region_scan_time_ms; double _external_accounted_time_ms; @@ -325,8 +325,8 @@ class G1GCPhaseTimes : public CHeapObj { _cur_prepare_concurrent_task_time_ms = ms; } - void record_root_region_scan_wait_time(double time_ms) { - _root_region_scan_wait_time_ms = time_ms; + void record_root_region_scan_time(double time_ms) { + _root_region_scan_time_ms = time_ms; } void record_serial_free_cset_time_ms(double time_ms) { @@ -399,8 +399,8 @@ class G1GCPhaseTimes : public CHeapObj { return _cur_resize_heap_time_ms; } - double root_region_scan_wait_time_ms() { - return _root_region_scan_wait_time_ms; + double root_region_scan_time_ms() { + return _root_region_scan_time_ms; } double young_cset_choice_time_ms() { diff --git a/src/hotspot/share/gc/g1/g1Policy.cpp b/src/hotspot/share/gc/g1/g1Policy.cpp index 24a82113d20..5744bbc2f03 100644 --- a/src/hotspot/share/gc/g1/g1Policy.cpp +++ b/src/hotspot/share/gc/g1/g1Policy.cpp @@ -690,11 +690,6 @@ void G1Policy::record_young_collection_start() { assert(_g1h->collection_set()->verify_young_ages(), "region age verification failed"); } -void G1Policy::record_concurrent_mark_init_end() { - assert(!collector_state()->initiate_conc_mark_if_possible(), "we should have cleared it by now"); - collector_state()->set_in_normal_young_gc(); -} - void G1Policy::record_concurrent_mark_remark_end() { double end_time_sec = os::elapsedTime(); double start_time_sec = cur_pause_start_sec(); @@ -795,7 +790,8 @@ void G1Policy::record_young_collection_end(bool concurrent_operation_is_full_mar bool is_young_only_pause = G1CollectorState::is_young_only_pause(this_pause); if (G1CollectorState::is_concurrent_start_pause(this_pause)) { - record_concurrent_mark_init_end(); + assert(!collector_state()->initiate_conc_mark_if_possible(), "we should have cleared it by now"); + collector_state()->set_in_normal_young_gc(); } else { maybe_start_marking(allocation_word_size); } @@ -1245,10 +1241,6 @@ bool G1Policy::force_concurrent_start_if_outside_cycle(GCCause::Cause gc_cause) } } -void G1Policy::initiate_conc_mark() { - collector_state()->set_in_concurrent_start_gc(); -} - static const char* requester_for_mixed_abort(GCCause::Cause cause) { if (cause == GCCause::_wb_breakpoint) { return "run_to breakpoint"; @@ -1264,7 +1256,7 @@ void G1Policy::decide_on_concurrent_start_pause() { // We are about to decide on whether this pause will be a // concurrent start pause. - // First, collector_state()->in_concurrent_start_gc() should not be already set. We + // First, collector_state()->is_in_concurrent_start_gc() should not already be set. We // will set it here if we have to. However, it should be cleared by // the end of the pause (it's only set for the duration of a // concurrent start pause). @@ -1283,22 +1275,19 @@ void G1Policy::decide_on_concurrent_start_pause() { log_debug(gc, ergo)("Do not initiate concurrent cycle (whitebox controlled)"); } else if (!about_to_start_mixed_phase() && collector_state()->is_in_young_only_phase()) { // Initiate a new concurrent start if there is no marking or reclamation going on. - initiate_conc_mark(); + collector_state()->set_in_concurrent_start_gc(); log_debug(gc, ergo)("Initiate concurrent cycle (concurrent cycle initiation requested)"); } else if (_g1h->is_user_requested_concurrent_full_gc(cause) || GCCause::is_codecache_requested_gc(cause) || (cause == GCCause::_wb_breakpoint)) { - // Initiate a concurrent start. A concurrent start must be a young only - // GC, so the collector state must be updated to reflect this. - collector_state()->set_in_normal_young_gc(); - + // Force concurrent start. + collector_state()->set_in_concurrent_start_gc(); // We might have ended up coming here about to start a mixed phase with a collection set // active. The following remark might change the change the "evacuation efficiency" of // the regions in this set, leading to failing asserts later. // Since the concurrent cycle will recreate the collection set anyway, simply drop it here. abandon_collection_set_candidates(); abort_time_to_mixed_tracking(); - initiate_conc_mark(); log_debug(gc, ergo)("Initiate concurrent cycle (%s requested concurrent cycle)", requester_for_mixed_abort(cause)); } else { diff --git a/src/hotspot/share/gc/g1/g1Policy.hpp b/src/hotspot/share/gc/g1/g1Policy.hpp index bcc3bceda49..5c5c2bc3572 100644 --- a/src/hotspot/share/gc/g1/g1Policy.hpp +++ b/src/hotspot/share/gc/g1/g1Policy.hpp @@ -313,9 +313,6 @@ public: void record_full_collection_start(); void record_full_collection_end(size_t allocation_word_size); - // Must currently be called while the world is stopped. - void record_concurrent_mark_init_end(); - void record_concurrent_mark_remark_end(); // Record start, end, and completion of cleanup. @@ -332,11 +329,6 @@ private: // regions and update the associated members. void update_survival_estimates_for_next_collection(); - // Set the state to start a concurrent marking cycle and clear - // _initiate_conc_mark_if_possible because it has now been - // acted on. - void initiate_conc_mark(); - public: // This sets the initiate_conc_mark_if_possible() flag to start a // new cycle, as long as we are not already in one. It's best if it diff --git a/src/hotspot/share/gc/g1/g1VMOperations.cpp b/src/hotspot/share/gc/g1/g1VMOperations.cpp index b0c6b680b78..891432e20a7 100644 --- a/src/hotspot/share/gc/g1/g1VMOperations.cpp +++ b/src/hotspot/share/gc/g1/g1VMOperations.cpp @@ -152,8 +152,9 @@ bool VM_G1PauseConcurrent::doit_prologue() { G1CollectedHeap* g1h = G1CollectedHeap::heap(); if (g1h->is_shutting_down()) { Heap_lock->unlock(); - // JVM shutdown has started. This ensures that any further operations will be properly aborted - // and will not interfere with the shutdown process. + // JVM shutdown has started. Abort concurrent marking to ensure that any further + // concurrent VM operations will not try to start and interfere with the shutdown + // process. g1h->concurrent_mark()->abort_marking_threads(); return false; } diff --git a/src/hotspot/share/gc/g1/g1YoungCollector.cpp b/src/hotspot/share/gc/g1/g1YoungCollector.cpp index a4938416f25..9c12127c864 100644 --- a/src/hotspot/share/gc/g1/g1YoungCollector.cpp +++ b/src/hotspot/share/gc/g1/g1YoungCollector.cpp @@ -244,19 +244,13 @@ G1YoungGCAllocationFailureInjector* G1YoungCollector::allocation_failure_injecto return _g1h->allocation_failure_injector(); } - -void G1YoungCollector::wait_for_root_region_scanning() { +void G1YoungCollector::complete_root_region_scan() { Ticks start = Ticks::now(); - // We have to wait until the CM threads finish scanning the - // root regions as it's the only way to ensure that all the - // objects on them have been correctly scanned before we start - // moving them during the GC. - bool waited = concurrent_mark()->wait_until_root_region_scan_finished(); - Tickspan wait_time; - if (waited) { - wait_time = (Ticks::now() - start); + // We have to complete root region scan as it's the only way to ensure that all the + // objects on them have been correctly scanned before we start moving them during the GC. + if (concurrent_mark()->complete_root_regions_scan_in_safepoint()) { + phase_times()->record_root_region_scan_time((Ticks::now() - start).seconds() * MILLIUNITS); } - phase_times()->record_root_region_scan_wait_time(wait_time.seconds() * MILLIUNITS); } class G1PrintCollectionSetClosure : public G1HeapRegionClosure { @@ -1147,7 +1141,7 @@ void G1YoungCollector::collect() { // Wait for root region scan here to make sure that it is done before any // use of the STW workers to maximize cpu use (i.e. all cores are available // just to do that). - wait_for_root_region_scanning(); + complete_root_region_scan(); G1YoungGCVerifierMark vm(this); { diff --git a/src/hotspot/share/gc/g1/g1YoungCollector.hpp b/src/hotspot/share/gc/g1/g1YoungCollector.hpp index 76d443b1a9f..ab32ca770a4 100644 --- a/src/hotspot/share/gc/g1/g1YoungCollector.hpp +++ b/src/hotspot/share/gc/g1/g1YoungCollector.hpp @@ -89,7 +89,7 @@ class G1YoungCollector { // returning the total time taken. Tickspan run_task_timed(WorkerTask* task); - void wait_for_root_region_scanning(); + void complete_root_region_scan(); void calculate_collection_set(G1EvacInfo* evacuation_info, double target_pause_time_ms); diff --git a/src/hotspot/share/opto/c2_globals.hpp b/src/hotspot/share/opto/c2_globals.hpp index a4f6d04f6a3..983ac8a32c6 100644 --- a/src/hotspot/share/opto/c2_globals.hpp +++ b/src/hotspot/share/opto/c2_globals.hpp @@ -705,6 +705,10 @@ develop(bool, TraceIterativeGVN, false, \ "Print progress during Iterative Global Value Numbering") \ \ + develop(bool, UseDeepIGVNRevisit, true, \ + "Re-process nodes that could benefit from a deep revisit after " \ + "the IGVN worklist drains") \ + \ develop(uint, VerifyIterativeGVN, 0, \ "Verify Iterative Global Value Numbering =FEDCBA, with:" \ " F: verify Node::Ideal does not return nullptr if the node" \ diff --git a/src/hotspot/share/opto/cfgnode.cpp b/src/hotspot/share/opto/cfgnode.cpp index 0915f79a503..828e5bf299f 100644 --- a/src/hotspot/share/opto/cfgnode.cpp +++ b/src/hotspot/share/opto/cfgnode.cpp @@ -735,7 +735,7 @@ Node *RegionNode::Ideal(PhaseGVN *phase, bool can_reshape) { #endif } // Remove the RegionNode itself from DefUse info - igvn->remove_dead_node(this); + igvn->remove_dead_node(this, PhaseIterGVN::NodeOrigin::Graph); return nullptr; } return this; // Record progress @@ -1007,7 +1007,7 @@ bool RegionNode::optimize_trichotomy(PhaseIterGVN* igvn) { BoolNode* new_bol = new BoolNode(bol2->in(1), res); igvn->replace_input_of(iff2, 1, igvn->transform((proj2->_con == 1) ? new_bol : new_bol->negate(igvn))); if (new_bol->outcnt() == 0) { - igvn->remove_dead_node(new_bol); + igvn->remove_dead_node(new_bol, PhaseIterGVN::NodeOrigin::Speculative); } } return false; diff --git a/src/hotspot/share/opto/compile.cpp b/src/hotspot/share/opto/compile.cpp index 7ab384a29c7..7b5f78a8ad3 100644 --- a/src/hotspot/share/opto/compile.cpp +++ b/src/hotspot/share/opto/compile.cpp @@ -2277,7 +2277,7 @@ void Compile::remove_root_to_sfpts_edges(PhaseIterGVN& igvn) { if (n != nullptr && n->is_SafePoint()) { r->rm_prec(i); if (n->outcnt() == 0) { - igvn.remove_dead_node(n); + igvn.remove_dead_node(n, PhaseIterGVN::NodeOrigin::Graph); } --i; } @@ -2321,7 +2321,7 @@ void Compile::Optimize() { #endif { TracePhase tp(_t_iterGVN); - igvn.optimize(); + igvn.optimize(true); } if (failing()) return; @@ -2385,7 +2385,7 @@ void Compile::Optimize() { PhaseRenumberLive prl(initial_gvn(), *igvn_worklist()); } igvn.reset(); - igvn.optimize(); + igvn.optimize(true); if (failing()) return; } @@ -2418,7 +2418,7 @@ void Compile::Optimize() { int mcount = macro_count(); // Record number of allocations and locks before IGVN // Optimize out fields loads from scalar replaceable allocations. - igvn.optimize(); + igvn.optimize(true); print_method(PHASE_ITER_GVN_AFTER_EA, 2); if (failing()) return; @@ -2498,7 +2498,7 @@ void Compile::Optimize() { { TracePhase tp(_t_iterGVN2); igvn.reset_from_igvn(&ccp); - igvn.optimize(); + igvn.optimize(true); } print_method(PHASE_ITER_GVN2, 2); diff --git a/src/hotspot/share/opto/escape.cpp b/src/hotspot/share/opto/escape.cpp index 7041eb0b810..64ee60037d6 100644 --- a/src/hotspot/share/opto/escape.cpp +++ b/src/hotspot/share/opto/escape.cpp @@ -933,7 +933,7 @@ void ConnectionGraph::reduce_phi_on_castpp_field_load(Node* curr_castpp, Growabl j = MIN2(j, (int)use->outcnt()-1); } - _igvn->remove_dead_node(use); + _igvn->remove_dead_node(use, PhaseIterGVN::NodeOrigin::Graph); } --i; i = MIN2(i, (int)curr_castpp->outcnt()-1); diff --git a/src/hotspot/share/opto/graphKit.cpp b/src/hotspot/share/opto/graphKit.cpp index e297292770e..8d32694e9a5 100644 --- a/src/hotspot/share/opto/graphKit.cpp +++ b/src/hotspot/share/opto/graphKit.cpp @@ -2911,8 +2911,8 @@ Node* Phase::gen_subtype_check(Node* subklass, Node* superklass, Node** ctrl, No *ctrl = iftrue1; // We need exactly the 1 test above PhaseIterGVN* igvn = gvn.is_IterGVN(); if (igvn != nullptr) { - igvn->remove_globally_dead_node(r_ok_subtype); - igvn->remove_globally_dead_node(r_not_subtype); + igvn->remove_globally_dead_node(r_ok_subtype, PhaseIterGVN::NodeOrigin::Speculative); + igvn->remove_globally_dead_node(r_not_subtype, PhaseIterGVN::NodeOrigin::Speculative); } return not_subtype_ctrl; } diff --git a/src/hotspot/share/opto/ifnode.cpp b/src/hotspot/share/opto/ifnode.cpp index 762791d467d..ad8f0ced6ea 100644 --- a/src/hotspot/share/opto/ifnode.cpp +++ b/src/hotspot/share/opto/ifnode.cpp @@ -132,7 +132,7 @@ static Node* split_if(IfNode *iff, PhaseIterGVN *igvn) { cmp2->set_req(2,con2); const Type *t = cmp2->Value(igvn); // This compare is dead, so whack it! - igvn->remove_dead_node(cmp2); + igvn->remove_dead_node(cmp2, PhaseIterGVN::NodeOrigin::Speculative); if( !t->singleton() ) return nullptr; // No intervening control, like a simple Call @@ -443,7 +443,7 @@ static Node* split_if(IfNode *iff, PhaseIterGVN *igvn) { } l -= uses_found; // we deleted 1 or more copies of this edge } - igvn->remove_dead_node(p); + igvn->remove_dead_node(p, PhaseIterGVN::NodeOrigin::Graph); } // Force the original merge dead @@ -455,14 +455,14 @@ static Node* split_if(IfNode *iff, PhaseIterGVN *igvn) { r->set_req(0, nullptr); } else { assert(u->outcnt() == 0, "only dead users"); - igvn->remove_dead_node(u); + igvn->remove_dead_node(u, PhaseIterGVN::NodeOrigin::Graph); } l -= 1; } - igvn->remove_dead_node(r); + igvn->remove_dead_node(r, PhaseIterGVN::NodeOrigin::Graph); // Now remove the bogus extra edges used to keep things alive - igvn->remove_dead_node( hook ); + igvn->remove_dead_node(hook, PhaseIterGVN::NodeOrigin::Speculative); // Must return either the original node (now dead) or a new node // (Do not return a top here, since that would break the uniqueness of top.) @@ -905,6 +905,7 @@ bool IfNode::fold_compares_helper(IfProjNode* proj, IfProjNode* success, IfProjN IfNode* dom_iff = proj->in(0)->as_If(); BoolNode* dom_bool = dom_iff->in(1)->as_Bool(); Node* lo = dom_iff->in(1)->in(1)->in(2); + Node* orig_lo = lo; Node* hi = this_cmp->in(2); Node* n = this_cmp->in(1); IfProjNode* otherproj = proj->other_if_proj(); @@ -916,6 +917,7 @@ bool IfNode::fold_compares_helper(IfProjNode* proj, IfProjNode* success, IfProjN BoolTest::mask hi_test = this_bool->_test._test; BoolTest::mask cond = hi_test; + PhaseTransform::SpeculativeProgressGuard progress_guard(igvn); // convert: // // dom_bool = x {<,<=,>,>=} a @@ -1053,6 +1055,7 @@ bool IfNode::fold_compares_helper(IfProjNode* proj, IfProjNode* success, IfProjN // previous if determines the result of this if so // replace Bool with constant igvn->replace_input_of(this, 1, igvn->intcon(success->_con)); + progress_guard.commit(); return true; } } @@ -1087,11 +1090,14 @@ bool IfNode::fold_compares_helper(IfProjNode* proj, IfProjNode* success, IfProjN // min(limit, max(-2 + min_jint + 1, min_jint)) // = min(limit, min_jint) // = min_jint + if (lo != orig_lo && lo->outcnt() == 0) { + igvn->remove_dead_node(lo, PhaseIterGVN::NodeOrigin::Speculative); + } if (adjusted_val->outcnt() == 0) { - igvn->remove_dead_node(adjusted_val); + igvn->remove_dead_node(adjusted_val, PhaseIterGVN::NodeOrigin::Speculative); } if (adjusted_lim->outcnt() == 0) { - igvn->remove_dead_node(adjusted_lim); + igvn->remove_dead_node(adjusted_lim, PhaseIterGVN::NodeOrigin::Speculative); } igvn->C->record_for_post_loop_opts_igvn(this); return false; @@ -1103,6 +1109,7 @@ bool IfNode::fold_compares_helper(IfProjNode* proj, IfProjNode* success, IfProjN igvn->replace_input_of(dom_iff, 1, igvn->intcon(proj->_con)); igvn->replace_input_of(this, 1, newbool); + progress_guard.commit(); return true; } @@ -1592,11 +1599,11 @@ Node* IfNode::dominated_by(Node* prev_dom, PhaseIterGVN* igvn, bool prev_dom_not } } // End for each child of a projection - igvn->remove_dead_node(ifp); + igvn->remove_dead_node(ifp, PhaseIterGVN::NodeOrigin::Graph); } // End for each IfTrue/IfFalse child of If // Kill the IfNode - igvn->remove_dead_node(this); + igvn->remove_dead_node(this, PhaseIterGVN::NodeOrigin::Graph); // Must return either the original node (now dead) or a new node // (Do not return a top here, since that would break the uniqueness of top.) @@ -1758,7 +1765,7 @@ Node* IfNode::simple_subsuming(PhaseIterGVN* igvn) { } if (bol->outcnt() == 0) { - igvn->remove_dead_node(bol); // Kill the BoolNode. + igvn->remove_dead_node(bol, PhaseIterGVN::NodeOrigin::Graph); // Kill the BoolNode. } return this; } @@ -1903,7 +1910,7 @@ static IfNode* idealize_test(PhaseGVN* phase, IfNode* iff) { Node *prior = igvn->hash_find_insert(iff); if( prior ) { - igvn->remove_dead_node(iff); + igvn->remove_dead_node(iff, PhaseIterGVN::NodeOrigin::Graph); iff = (IfNode*)prior; } else { // Cannot call transform on it just yet diff --git a/src/hotspot/share/opto/loopnode.cpp b/src/hotspot/share/opto/loopnode.cpp index 61efccc5719..fc12639b50d 100644 --- a/src/hotspot/share/opto/loopnode.cpp +++ b/src/hotspot/share/opto/loopnode.cpp @@ -698,7 +698,7 @@ SafePointNode* PhaseIdealLoop::find_safepoint(Node* back_control, const Node* he } #ifdef ASSERT if (mm != nullptr) { - _igvn.remove_dead_node(mm); + _igvn.remove_dead_node(mm, PhaseIterGVN::NodeOrigin::Speculative); } #endif } @@ -2003,7 +2003,7 @@ bool CountedLoopConverter::stress_long_counted_loop() { Node* n = iv_nodes.at(i); Node* clone = old_new[n->_idx]; if (clone != nullptr) { - igvn->remove_dead_node(clone); + igvn->remove_dead_node(clone, PhaseIterGVN::NodeOrigin::Speculative); } } return false; @@ -4742,7 +4742,7 @@ void PhaseIdealLoop::replace_parallel_iv(IdealLoopTree *loop) { _igvn.replace_node(phi2, result); // Sometimes an induction variable is unused if (result->outcnt() == 0) { - _igvn.remove_dead_node(result); + _igvn.remove_dead_node(result, PhaseIterGVN::NodeOrigin::Graph); } --i; // deleted this phi; rescan starting with next position } @@ -5420,7 +5420,7 @@ void PhaseIdealLoop::build_and_optimize() { // clear out the dead code after build_loop_late while (_deadlist.size()) { - _igvn.remove_globally_dead_node(_deadlist.pop()); + _igvn.remove_globally_dead_node(_deadlist.pop(), PhaseIterGVN::NodeOrigin::Graph); } eliminate_useless_zero_trip_guard(); diff --git a/src/hotspot/share/opto/loopopts.cpp b/src/hotspot/share/opto/loopopts.cpp index 4e447edee8d..11b23d7c365 100644 --- a/src/hotspot/share/opto/loopopts.cpp +++ b/src/hotspot/share/opto/loopopts.cpp @@ -161,7 +161,7 @@ Node* PhaseIdealLoop::split_thru_phi(Node* n, Node* region, int policy) { } if (the_clone != x) { - _igvn.remove_dead_node(the_clone); + _igvn.remove_dead_node(the_clone, PhaseIterGVN::NodeOrigin::Speculative); } else if (region->is_Loop() && i == LoopNode::LoopBackControl && n->is_Load() && can_move_to_inner_loop(n, region->as_Loop(), x)) { // it is not a win if 'x' moved from an outer to an inner loop @@ -172,7 +172,7 @@ Node* PhaseIdealLoop::split_thru_phi(Node* n, Node* region, int policy) { } // Too few wins? if (!wins.profitable(policy)) { - _igvn.remove_dead_node(phi); + _igvn.remove_dead_node(phi, PhaseIterGVN::NodeOrigin::Speculative); return nullptr; } @@ -1866,7 +1866,7 @@ void PhaseIdealLoop::try_sink_out_of_loop(Node* n) { assert(cast != nullptr, "must have added a cast to pin the node"); } } - _igvn.remove_dead_node(n); + _igvn.remove_dead_node(n, PhaseIterGVN::NodeOrigin::Graph); } _dom_lca_tags_round = 0; } @@ -2082,7 +2082,7 @@ Node* PhaseIdealLoop::clone_iff(PhiNode* phi) { // Register with optimizer Node *hit1 = _igvn.hash_find_insert(phi1); if (hit1) { // Hit, toss just made Phi - _igvn.remove_dead_node(phi1); // Remove new phi + _igvn.remove_dead_node(phi1, PhaseIterGVN::NodeOrigin::Speculative); // Remove new phi assert(hit1->is_Phi(), "" ); phi1 = (PhiNode*)hit1; // Use existing phi } else { // Miss @@ -2090,7 +2090,7 @@ Node* PhaseIdealLoop::clone_iff(PhiNode* phi) { } Node *hit2 = _igvn.hash_find_insert(phi2); if (hit2) { // Hit, toss just made Phi - _igvn.remove_dead_node(phi2); // Remove new phi + _igvn.remove_dead_node(phi2, PhaseIterGVN::NodeOrigin::Speculative); // Remove new phi assert(hit2->is_Phi(), "" ); phi2 = (PhiNode*)hit2; // Use existing phi } else { // Miss @@ -2165,7 +2165,7 @@ CmpNode*PhaseIdealLoop::clone_bool(PhiNode* phi) { // Register with optimizer Node *hit1 = _igvn.hash_find_insert(phi1); if( hit1 ) { // Hit, toss just made Phi - _igvn.remove_dead_node(phi1); // Remove new phi + _igvn.remove_dead_node(phi1, PhaseIterGVN::NodeOrigin::Speculative); // Remove new phi assert( hit1->is_Phi(), "" ); phi1 = (PhiNode*)hit1; // Use existing phi } else { // Miss @@ -2173,7 +2173,7 @@ CmpNode*PhaseIdealLoop::clone_bool(PhiNode* phi) { } Node *hit2 = _igvn.hash_find_insert(phi2); if( hit2 ) { // Hit, toss just made Phi - _igvn.remove_dead_node(phi2); // Remove new phi + _igvn.remove_dead_node(phi2, PhaseIterGVN::NodeOrigin::Speculative); // Remove new phi assert( hit2->is_Phi(), "" ); phi2 = (PhiNode*)hit2; // Use existing phi } else { // Miss @@ -2324,7 +2324,7 @@ void PhaseIdealLoop::clone_loop_handle_data_uses(Node* old, Node_List &old_new, _igvn.register_new_node_with_optimizer(phi); // Register new phi } else { // or // Remove the new phi from the graph and use the hit - _igvn.remove_dead_node(phi); + _igvn.remove_dead_node(phi, phi == prev ? PhaseIterGVN::NodeOrigin::Graph : PhaseIterGVN::NodeOrigin::Speculative); phi = hit; // Use existing phi } set_ctrl(phi, prev); @@ -3472,7 +3472,7 @@ void PhaseIdealLoop::insert_phi_for_loop( Node* use, uint idx, Node* lp_entry_va set_ctrl(phi, lp); } else { // Remove the new phi from the graph and use the hit - _igvn.remove_dead_node(phi); + _igvn.remove_dead_node(phi, PhaseIterGVN::NodeOrigin::Speculative); phi = hit; } _igvn.replace_input_of(use, idx, phi); diff --git a/src/hotspot/share/opto/macro.cpp b/src/hotspot/share/opto/macro.cpp index c78f6533840..1c54a11e2a4 100644 --- a/src/hotspot/share/opto/macro.cpp +++ b/src/hotspot/share/opto/macro.cpp @@ -973,7 +973,7 @@ void PhaseMacroExpand::process_users_of_allocation(CallNode *alloc) { } k -= (oc2 - use->outcnt()); } - _igvn.remove_dead_node(use); + _igvn.remove_dead_node(use, PhaseIterGVN::NodeOrigin::Graph); } else if (use->is_ArrayCopy()) { // Disconnect ArrayCopy node ArrayCopyNode* ac = use->as_ArrayCopy(); @@ -1008,7 +1008,7 @@ void PhaseMacroExpand::process_users_of_allocation(CallNode *alloc) { // src can be top at this point if src and dest of the // arraycopy were the same if (src->outcnt() == 0 && !src->is_top()) { - _igvn.remove_dead_node(src); + _igvn.remove_dead_node(src, PhaseIterGVN::NodeOrigin::Graph); } } _igvn._worklist.push(ac); @@ -1018,7 +1018,7 @@ void PhaseMacroExpand::process_users_of_allocation(CallNode *alloc) { j -= (oc1 - res->outcnt()); } assert(res->outcnt() == 0, "all uses of allocated objects must be deleted"); - _igvn.remove_dead_node(res); + _igvn.remove_dead_node(res, PhaseIterGVN::NodeOrigin::Graph); } // @@ -1502,7 +1502,7 @@ void PhaseMacroExpand::expand_allocate_common( transform_later(_callprojs.fallthrough_memproj); } migrate_outs(_callprojs.catchall_memproj, _callprojs.fallthrough_memproj); - _igvn.remove_dead_node(_callprojs.catchall_memproj); + _igvn.remove_dead_node(_callprojs.catchall_memproj, PhaseIterGVN::NodeOrigin::Graph); } // An allocate node has separate i_o projections for the uses on the control @@ -1521,7 +1521,7 @@ void PhaseMacroExpand::expand_allocate_common( transform_later(_callprojs.fallthrough_ioproj); } migrate_outs(_callprojs.catchall_ioproj, _callprojs.fallthrough_ioproj); - _igvn.remove_dead_node(_callprojs.catchall_ioproj); + _igvn.remove_dead_node(_callprojs.catchall_ioproj, PhaseIterGVN::NodeOrigin::Graph); } // if we generated only a slow call, we are done @@ -1585,11 +1585,11 @@ void PhaseMacroExpand::yank_alloc_node(AllocateNode* alloc) { --i; // back up iterator } assert(_callprojs.resproj->outcnt() == 0, "all uses must be deleted"); - _igvn.remove_dead_node(_callprojs.resproj); + _igvn.remove_dead_node(_callprojs.resproj, PhaseIterGVN::NodeOrigin::Graph); } if (_callprojs.fallthrough_catchproj != nullptr) { migrate_outs(_callprojs.fallthrough_catchproj, ctrl); - _igvn.remove_dead_node(_callprojs.fallthrough_catchproj); + _igvn.remove_dead_node(_callprojs.fallthrough_catchproj, PhaseIterGVN::NodeOrigin::Graph); } if (_callprojs.catchall_catchproj != nullptr) { _igvn.rehash_node_delayed(_callprojs.catchall_catchproj); @@ -1597,16 +1597,16 @@ void PhaseMacroExpand::yank_alloc_node(AllocateNode* alloc) { } if (_callprojs.fallthrough_proj != nullptr) { Node* catchnode = _callprojs.fallthrough_proj->unique_ctrl_out(); - _igvn.remove_dead_node(catchnode); - _igvn.remove_dead_node(_callprojs.fallthrough_proj); + _igvn.remove_dead_node(catchnode, PhaseIterGVN::NodeOrigin::Graph); + _igvn.remove_dead_node(_callprojs.fallthrough_proj, PhaseIterGVN::NodeOrigin::Graph); } if (_callprojs.fallthrough_memproj != nullptr) { migrate_outs(_callprojs.fallthrough_memproj, mem); - _igvn.remove_dead_node(_callprojs.fallthrough_memproj); + _igvn.remove_dead_node(_callprojs.fallthrough_memproj, PhaseIterGVN::NodeOrigin::Graph); } if (_callprojs.fallthrough_ioproj != nullptr) { migrate_outs(_callprojs.fallthrough_ioproj, i_o); - _igvn.remove_dead_node(_callprojs.fallthrough_ioproj); + _igvn.remove_dead_node(_callprojs.fallthrough_ioproj, PhaseIterGVN::NodeOrigin::Graph); } if (_callprojs.catchall_memproj != nullptr) { _igvn.rehash_node_delayed(_callprojs.catchall_memproj); @@ -1625,7 +1625,7 @@ void PhaseMacroExpand::yank_alloc_node(AllocateNode* alloc) { } } #endif - _igvn.remove_dead_node(alloc); + _igvn.remove_dead_node(alloc, PhaseIterGVN::NodeOrigin::Graph); } void PhaseMacroExpand::expand_initialize_membar(AllocateNode* alloc, InitializeNode* init, diff --git a/src/hotspot/share/opto/memnode.cpp b/src/hotspot/share/opto/memnode.cpp index 46f6729c82f..9ec3402c701 100644 --- a/src/hotspot/share/opto/memnode.cpp +++ b/src/hotspot/share/opto/memnode.cpp @@ -1156,40 +1156,38 @@ Node* LoadNode::can_see_arraycopy_value(Node* st, PhaseGVN* phase) const { return nullptr; } - -//---------------------------can_see_stored_value------------------------------ // This routine exists to make sure this set of tests is done the same // everywhere. We need to make a coordinated change: first LoadNode::Ideal // will change the graph shape in a way which makes memory alive twice at the // same time (uses the Oracle model of aliasing), then some // LoadXNode::Identity will fold things back to the equivalence-class model // of aliasing. -Node* MemNode::can_see_stored_value(Node* st, PhaseValues* phase) const { +Node* LoadNode::can_see_stored_value_through_membars(Node* st, PhaseValues* phase) const { Node* ld_adr = in(MemNode::Address); - intptr_t ld_off = 0; - Node* ld_base = AddPNode::Ideal_base_and_offset(ld_adr, phase, ld_off); - Node* ld_alloc = AllocateNode::Ideal_allocation(ld_base); const TypeInstPtr* tp = phase->type(ld_adr)->isa_instptr(); Compile::AliasType* atp = (tp != nullptr) ? phase->C->alias_type(tp) : nullptr; - // This is more general than load from boxing objects. + if (skip_through_membars(atp, tp, phase->C->eliminate_boxing())) { uint alias_idx = atp->index(); Node* result = nullptr; Node* current = st; - // Skip through chains of MemBarNodes checking the MergeMems for - // new states for the slice of this load. Stop once any other - // kind of node is encountered. Loads from final memory can skip - // through any kind of MemBar but normal loads shouldn't skip - // through MemBarAcquire since the could allow them to move out of - // a synchronized region. It is not safe to step over MemBarCPUOrder, - // because alias info above them may be inaccurate (e.g., due to - // mixed/mismatched unsafe accesses). + // Skip through chains of MemBarNodes checking the MergeMems for new states for the slice of + // this load. Stop once any other kind of node is encountered. + // + // In principle, folding a load is moving it up until it meets a matching store. + // + // store(ptr, v); store(ptr, v); store(ptr, v); + // membar1; -> membar1; -> load(ptr); + // membar2; load(ptr); membar1; + // load(ptr); membar2; membar2; + // + // So, we can decide which kinds of barriers we can walk past. It is not safe to step over + // MemBarCPUOrder, even if the memory is not rewritable, because alias info above them may be + // inaccurate (e.g., due to mixed/mismatched unsafe accesses). bool is_final_mem = !atp->is_rewritable(); while (current->is_Proj()) { int opc = current->in(0)->Opcode(); - if ((is_final_mem && (opc == Op_MemBarAcquire || - opc == Op_MemBarAcquireLock || - opc == Op_LoadFence)) || + if ((is_final_mem && (opc == Op_MemBarAcquire || opc == Op_MemBarAcquireLock || opc == Op_LoadFence)) || opc == Op_MemBarRelease || opc == Op_StoreFence || opc == Op_MemBarReleaseLock || @@ -1216,6 +1214,17 @@ Node* MemNode::can_see_stored_value(Node* st, PhaseValues* phase) const { } } + return can_see_stored_value(st, phase); +} + +// If st is a store to the same location as this, return the stored value +Node* MemNode::can_see_stored_value(Node* st, PhaseValues* phase) const { + Node* ld_adr = in(MemNode::Address); + intptr_t ld_off = 0; + Node* ld_base = AddPNode::Ideal_base_and_offset(ld_adr, phase, ld_off); + Node* ld_alloc = AllocateNode::Ideal_allocation(ld_base); + const TypeInstPtr* tp = phase->type(ld_adr)->isa_instptr(); + // Loop around twice in the case Load -> Initialize -> Store. // (See PhaseIterGVN::add_users_to_worklist, which knows about this case.) for (int trip = 0; trip <= 1; trip++) { @@ -1344,7 +1353,7 @@ Node* LoadNode::Identity(PhaseGVN* phase) { // If the previous store-maker is the right kind of Store, and the store is // to the same address, then we are equal to the value stored. Node* mem = in(Memory); - Node* value = can_see_stored_value(mem, phase); + Node* value = can_see_stored_value_through_membars(mem, phase); if( value ) { // byte, short & char stores truncate naturally. // A load has to load the truncated value which requires @@ -1889,7 +1898,7 @@ Node* LoadNode::split_through_phi(PhaseGVN* phase, bool ignore_missing_instance_ } } if (x != the_clone && the_clone != nullptr) { - igvn->remove_dead_node(the_clone); + igvn->remove_dead_node(the_clone, PhaseIterGVN::NodeOrigin::Speculative); } phi->set_req(i, x); } @@ -2042,7 +2051,7 @@ Node *LoadNode::Ideal(PhaseGVN *phase, bool can_reshape) { // (c) See if we can fold up on the spot, but don't fold up here. // Fold-up might require truncation (for LoadB/LoadS/LoadUS) or // just return a prior value, which is done by Identity calls. - if (can_see_stored_value(prev_mem, phase)) { + if (can_see_stored_value_through_membars(prev_mem, phase)) { // Make ready for step (d): set_req_X(MemNode::Memory, prev_mem, phase); return this; @@ -2099,7 +2108,7 @@ const Type* LoadNode::Value(PhaseGVN* phase) const { Compile* C = phase->C; // If load can see a previous constant store, use that. - Node* value = can_see_stored_value(mem, phase); + Node* value = can_see_stored_value_through_membars(mem, phase); if (value != nullptr && value->is_Con()) { assert(value->bottom_type()->higher_equal(_type), "sanity"); return value->bottom_type(); @@ -2350,7 +2359,7 @@ uint LoadNode::match_edge(uint idx) const { // Node* LoadBNode::Ideal(PhaseGVN* phase, bool can_reshape) { Node* mem = in(MemNode::Memory); - Node* value = can_see_stored_value(mem,phase); + Node* value = can_see_stored_value_through_membars(mem, phase); if (value != nullptr) { Node* narrow = Compile::narrow_value(T_BYTE, value, _type, phase, false); if (narrow != value) { @@ -2363,7 +2372,7 @@ Node* LoadBNode::Ideal(PhaseGVN* phase, bool can_reshape) { const Type* LoadBNode::Value(PhaseGVN* phase) const { Node* mem = in(MemNode::Memory); - Node* value = can_see_stored_value(mem,phase); + Node* value = can_see_stored_value_through_membars(mem, phase); if (value != nullptr && value->is_Con() && !value->bottom_type()->higher_equal(_type)) { // If the input to the store does not fit with the load's result type, @@ -2384,7 +2393,7 @@ const Type* LoadBNode::Value(PhaseGVN* phase) const { // Node* LoadUBNode::Ideal(PhaseGVN* phase, bool can_reshape) { Node* mem = in(MemNode::Memory); - Node* value = can_see_stored_value(mem, phase); + Node* value = can_see_stored_value_through_membars(mem, phase); if (value != nullptr) { Node* narrow = Compile::narrow_value(T_BOOLEAN, value, _type, phase, false); if (narrow != value) { @@ -2397,7 +2406,7 @@ Node* LoadUBNode::Ideal(PhaseGVN* phase, bool can_reshape) { const Type* LoadUBNode::Value(PhaseGVN* phase) const { Node* mem = in(MemNode::Memory); - Node* value = can_see_stored_value(mem,phase); + Node* value = can_see_stored_value_through_membars(mem, phase); if (value != nullptr && value->is_Con() && !value->bottom_type()->higher_equal(_type)) { // If the input to the store does not fit with the load's result type, @@ -2418,7 +2427,7 @@ const Type* LoadUBNode::Value(PhaseGVN* phase) const { // Node* LoadUSNode::Ideal(PhaseGVN* phase, bool can_reshape) { Node* mem = in(MemNode::Memory); - Node* value = can_see_stored_value(mem,phase); + Node* value = can_see_stored_value_through_membars(mem, phase); if (value != nullptr) { Node* narrow = Compile::narrow_value(T_CHAR, value, _type, phase, false); if (narrow != value) { @@ -2431,7 +2440,7 @@ Node* LoadUSNode::Ideal(PhaseGVN* phase, bool can_reshape) { const Type* LoadUSNode::Value(PhaseGVN* phase) const { Node* mem = in(MemNode::Memory); - Node* value = can_see_stored_value(mem,phase); + Node* value = can_see_stored_value_through_membars(mem, phase); if (value != nullptr && value->is_Con() && !value->bottom_type()->higher_equal(_type)) { // If the input to the store does not fit with the load's result type, @@ -2452,7 +2461,7 @@ const Type* LoadUSNode::Value(PhaseGVN* phase) const { // Node* LoadSNode::Ideal(PhaseGVN* phase, bool can_reshape) { Node* mem = in(MemNode::Memory); - Node* value = can_see_stored_value(mem,phase); + Node* value = can_see_stored_value_through_membars(mem, phase); if (value != nullptr) { Node* narrow = Compile::narrow_value(T_SHORT, value, _type, phase, false); if (narrow != value) { @@ -2465,7 +2474,7 @@ Node* LoadSNode::Ideal(PhaseGVN* phase, bool can_reshape) { const Type* LoadSNode::Value(PhaseGVN* phase) const { Node* mem = in(MemNode::Memory); - Node* value = can_see_stored_value(mem,phase); + Node* value = can_see_stored_value_through_membars(mem, phase); if (value != nullptr && value->is_Con() && !value->bottom_type()->higher_equal(_type)) { // If the input to the store does not fit with the load's result type, diff --git a/src/hotspot/share/opto/memnode.hpp b/src/hotspot/share/opto/memnode.hpp index 7fa238f574d..8efb5521e7c 100644 --- a/src/hotspot/share/opto/memnode.hpp +++ b/src/hotspot/share/opto/memnode.hpp @@ -266,6 +266,7 @@ protected: const Type* const _type; // What kind of value is loaded? virtual Node* find_previous_arraycopy(PhaseValues* phase, Node* ld_alloc, Node*& mem, bool can_see_stored_value) const; + Node* can_see_stored_value_through_membars(Node* st, PhaseValues* phase) const; public: LoadNode(Node *c, Node *mem, Node *adr, const TypePtr* at, const Type *rt, MemOrd mo, ControlDependency control_dependency) diff --git a/src/hotspot/share/opto/parse2.cpp b/src/hotspot/share/opto/parse2.cpp index 7f41870ccea..a7c5398171b 100644 --- a/src/hotspot/share/opto/parse2.cpp +++ b/src/hotspot/share/opto/parse2.cpp @@ -1757,6 +1757,12 @@ static bool match_type_check(PhaseGVN& gvn, // Bool(CmpP(LoadKlass(obj._klass), ConP(Foo.klass)), [eq]) // or the narrowOop equivalent. (*obj) = extract_obj_from_klass_load(&gvn, val); + // Some klass comparisons are not directly in the form + // Bool(CmpP(LoadKlass(obj._klass), ConP(Foo.klass)), [eq]), + // e.g. Bool(CmpP(CastPP(LoadKlass(...)), ConP(klass)), [eq]). + // These patterns with nullable klasses arise from example from + // load_array_klass_from_mirror. + if (*obj == nullptr) { return false; } (*cast_type) = tcon->isa_klassptr()->as_instance_type(); return true; // found } diff --git a/src/hotspot/share/opto/phaseX.cpp b/src/hotspot/share/opto/phaseX.cpp index 29961e152b3..06963eff4cf 100644 --- a/src/hotspot/share/opto/phaseX.cpp +++ b/src/hotspot/share/opto/phaseX.cpp @@ -575,7 +575,7 @@ PhaseValues::~PhaseValues() { _table.dump(); // Statistics for value progress and efficiency if( PrintCompilation && Verbose && WizardMode ) { - tty->print("\n%sValues: %d nodes ---> %d/%d (%d)", + tty->print("\n%sValues: %d nodes ---> " UINT64_FORMAT "/%d (%d)", is_IterGVN() ? "Iter" : " ", C->unique(), made_progress(), made_transforms(), made_new_values()); if( made_transforms() != 0 ) { tty->print_cr(" ratio %f", made_progress()/(float)made_transforms() ); @@ -731,14 +731,14 @@ Node* PhaseGVN::transform(Node* n) { } if (t->singleton() && !k->is_Con()) { - NOT_PRODUCT(set_progress();) + set_progress(); return makecon(t); // Turn into a constant } // Now check for Identities i = k->Identity(this); // Look for a nearby replacement if (i != k) { // Found? Return replacement! - NOT_PRODUCT(set_progress();) + set_progress(); return i; } @@ -746,7 +746,7 @@ Node* PhaseGVN::transform(Node* n) { i = hash_find_insert(k); // Insert if new if (i && (i != k)) { // Return the pre-existing node - NOT_PRODUCT(set_progress();) + set_progress(); return i; } @@ -977,7 +977,7 @@ void PhaseIterGVN::init_verifyPhaseIterGVN() { #endif } -void PhaseIterGVN::verify_PhaseIterGVN() { +void PhaseIterGVN::verify_PhaseIterGVN(bool deep_revisit_converged) { #ifdef ASSERT // Verify nodes with changed inputs. Unique_Node_List* modified_list = C->modified_nodes(); @@ -1010,7 +1010,7 @@ void PhaseIterGVN::verify_PhaseIterGVN() { } } - verify_optimize(); + verify_optimize(deep_revisit_converged); #endif } #endif /* PRODUCT */ @@ -1040,38 +1040,54 @@ void PhaseIterGVN::trace_PhaseIterGVN_verbose(Node* n, int num_processed) { } #endif /* ASSERT */ -void PhaseIterGVN::optimize() { - DEBUG_ONLY(uint num_processed = 0;) - NOT_PRODUCT(init_verifyPhaseIterGVN();) - NOT_PRODUCT(C->reset_igv_phase_iter(PHASE_AFTER_ITER_GVN_STEP);) - C->print_method(PHASE_BEFORE_ITER_GVN, 3); - if (StressIGVN) { - shuffle_worklist(); +bool PhaseIterGVN::needs_deep_revisit(const Node* n) const { + // LoadNode::Value() -> can_see_stored_value() walks up through many memory + // nodes. LoadNode::Ideal() -> find_previous_store() also walks up to 50 + // nodes through stores and arraycopy nodes. + if (n->is_Load()) { + return true; } + // CmpPNode::sub() -> detect_ptr_independence() -> all_controls_dominate() + // walks CFG dominator relationships extensively. This only triggers when + // both inputs are oop pointers (subnode.cpp:984). + if (n->Opcode() == Op_CmpP) { + const Type* t1 = type_or_null(n->in(1)); + const Type* t2 = type_or_null(n->in(2)); + return t1 != nullptr && t1->isa_oopptr() && + t2 != nullptr && t2->isa_oopptr(); + } + // IfNode::Ideal() -> search_identical() walks up the CFG dominator tree. + // RangeCheckNode::Ideal() scans up to ~999 nodes up the chain. + // CountedLoopEndNode/LongCountedLoopEndNode::Ideal() via simple_subsuming + // looks for dominating test that subsumes the current test. + switch (n->Opcode()) { + case Op_If: + case Op_RangeCheck: + case Op_CountedLoopEnd: + case Op_LongCountedLoopEnd: + return true; + default: + break; + } + return false; +} - // The node count check in the loop below (check_node_count) assumes that we - // increase the live node count with at most - // max_live_nodes_increase_per_iteration in between checks. If this - // assumption does not hold, there is a risk that we exceed the max node - // limit in between checks and trigger an assert during node creation. +bool PhaseIterGVN::drain_worklist() { + uint loop_count = 1; const int max_live_nodes_increase_per_iteration = NodeLimitFudgeFactor * 3; - - uint loop_count = 0; - // Pull from worklist and transform the node. If the node has changed, - // update edge info and put uses on worklist. - while (_worklist.size() > 0) { + while (_worklist.size() != 0) { if (C->check_node_count(max_live_nodes_increase_per_iteration, "Out of nodes")) { C->print_method(PHASE_AFTER_ITER_GVN, 3); - return; + return true; } Node* n = _worklist.pop(); if (loop_count >= K * C->live_nodes()) { - DEBUG_ONLY(dump_infinite_loop_info(n, "PhaseIterGVN::optimize");) - C->record_method_not_compilable("infinite loop in PhaseIterGVN::optimize"); + DEBUG_ONLY(dump_infinite_loop_info(n, "PhaseIterGVN::drain_worklist");) + C->record_method_not_compilable("infinite loop in PhaseIterGVN::drain_worklist"); C->print_method(PHASE_AFTER_ITER_GVN, 3); - return; + return true; } - DEBUG_ONLY(trace_PhaseIterGVN_verbose(n, num_processed++);) + DEBUG_ONLY(trace_PhaseIterGVN_verbose(n, _num_processed++);) if (n->outcnt() != 0) { NOT_PRODUCT(const Type* oldtype = type_or_null(n)); // Do the transformation @@ -1079,7 +1095,7 @@ void PhaseIterGVN::optimize() { Node* nn = transform_old(n); DEBUG_ONLY(int live_nodes_after = C->live_nodes();) // Ensure we did not increase the live node count with more than - // max_live_nodes_increase_per_iteration during the call to transform_old + // max_live_nodes_increase_per_iteration during the call to transform_old. DEBUG_ONLY(int increase = live_nodes_after - live_nodes_before;) assert(increase < max_live_nodes_increase_per_iteration, "excessive live node increase in single iteration of IGVN: %d " @@ -1087,16 +1103,115 @@ void PhaseIterGVN::optimize() { increase, max_live_nodes_increase_per_iteration); NOT_PRODUCT(trace_PhaseIterGVN(n, nn, oldtype);) } else if (!n->is_top()) { - remove_dead_node(n); + remove_dead_node(n, NodeOrigin::Graph); } loop_count++; } - NOT_PRODUCT(verify_PhaseIterGVN();) + return false; +} + +void PhaseIterGVN::push_deep_revisit_candidates() { + ResourceMark rm; + Unique_Node_List all_nodes; + all_nodes.push(C->root()); + for (uint j = 0; j < all_nodes.size(); j++) { + Node* n = all_nodes.at(j); + if (needs_deep_revisit(n)) { + _worklist.push(n); + } + for (DUIterator_Fast imax, i = n->fast_outs(imax); i < imax; i++) { + all_nodes.push(n->fast_out(i)); + } + } +} + +bool PhaseIterGVN::deep_revisit() { + // Re-process nodes that inspect the graph deeply. After the main worklist drains, walk + // the graph to find all live deep-inspection nodes and push them to the worklist + // for re-evaluation. If any produce changes, drain the worklist again. + // Repeat until stable. This mirrors PhaseCCP::analyze()'s revisit loop. + const uint max_deep_revisit_rounds = 10; // typically converges in <2 rounds + uint round = 0; + for (; round < max_deep_revisit_rounds; round++) { + push_deep_revisit_candidates(); + if (_worklist.size() == 0) { + break; // No deep-inspection nodes to revisit, done. + } + +#ifndef PRODUCT + uint candidates = _worklist.size(); + uint n_if = 0; uint n_rc = 0; uint n_load = 0; uint n_cmpp = 0; uint n_cle = 0; uint n_lcle = 0; + if (TraceIterativeGVN) { + for (uint i = 0; i < _worklist.size(); i++) { + Node* n = _worklist.at(i); + switch (n->Opcode()) { + case Op_If: n_if++; break; + case Op_RangeCheck: n_rc++; break; + case Op_CountedLoopEnd: n_cle++; break; + case Op_LongCountedLoopEnd: n_lcle++; break; + case Op_CmpP: n_cmpp++; break; + default: if (n->is_Load()) n_load++; break; + } + } + } +#endif + + // Convergence: if the drain does not make progress (no Ideal, Value, Identity or GVN changes), + // we are at a fixed point. We use made_progress() rather than live_nodes because live_nodes + // misses non-structural changes like a LoadNode dropping its control input. + uint progress_before = made_progress(); + if (drain_worklist()) { + return false; + } + uint progress = made_progress() - progress_before; + +#ifndef PRODUCT + if (TraceIterativeGVN) { + tty->print("deep_revisit round %u: %u candidates (If=%u RC=%u Load=%u CmpP=%u CLE=%u LCLE=%u), progress=%u (%s)", + round, candidates, n_if, n_rc, n_load, n_cmpp, n_cle, n_lcle, progress, progress != 0 ? "changed" : "converged"); + if (C->method() != nullptr) { + tty->print(", "); + C->method()->print_short_name(tty); + } + tty->cr(); + } +#endif + + if (progress == 0) { + break; + } + } + return round < max_deep_revisit_rounds; +} + +void PhaseIterGVN::optimize(bool deep) { + bool deep_revisit_converged = false; + DEBUG_ONLY(_num_processed = 0;) + NOT_PRODUCT(init_verifyPhaseIterGVN();) + NOT_PRODUCT(C->reset_igv_phase_iter(PHASE_AFTER_ITER_GVN_STEP);) + C->print_method(PHASE_BEFORE_ITER_GVN, 3); + if (StressIGVN) { + shuffle_worklist(); + } + + // Pull from worklist and transform the node. + if (drain_worklist()) { + return; + } + + if (deep && UseDeepIGVNRevisit) { + deep_revisit_converged = deep_revisit(); + if (C->failing()) { + return; + } + } + + NOT_PRODUCT(verify_PhaseIterGVN(deep_revisit_converged);) C->print_method(PHASE_AFTER_ITER_GVN, 3); } #ifdef ASSERT -void PhaseIterGVN::verify_optimize() { +void PhaseIterGVN::verify_optimize(bool deep_revisit_converged) { assert(_worklist.size() == 0, "igvn worklist must be empty before verify"); if (is_verify_Value() || @@ -1114,11 +1229,11 @@ void PhaseIterGVN::verify_optimize() { // in PhaseIterGVN::add_users_to_worklist to update it again or add an exception // in the verification methods below if that is not possible for some reason (like Load nodes). if (is_verify_Value()) { - verify_Value_for(n); + verify_Value_for(n, deep_revisit_converged /* strict */); } if (is_verify_Ideal()) { - verify_Ideal_for(n, false); - verify_Ideal_for(n, true); + verify_Ideal_for(n, false /* can_reshape */, deep_revisit_converged); + verify_Ideal_for(n, true /* can_reshape */, deep_revisit_converged); } if (is_verify_Identity()) { verify_Identity_for(n); @@ -1240,52 +1355,15 @@ void PhaseIterGVN::verify_Value_for(const Node* n, bool strict) { // Check that all Ideal optimizations that could be done were done. // Asserts if it found missed optimization opportunities or encountered unexpected changes, and // returns normally otherwise (no missed optimization, or skipped verification). -void PhaseIterGVN::verify_Ideal_for(Node* n, bool can_reshape) { +void PhaseIterGVN::verify_Ideal_for(Node* n, bool can_reshape, bool deep_revisit_converged) { + if (!deep_revisit_converged && needs_deep_revisit(n)) { + return; + } + // First, we check a list of exceptions, where we skip verification, // because there are known cases where Ideal can optimize after IGVN. // Some may be expected and cannot be fixed, and others should be fixed. switch (n->Opcode()) { - // RangeCheckNode::Ideal looks up the chain for about 999 nodes - // (see "Range-Check scan limit"). So, it is possible that something - // is optimized in that input subgraph, and the RangeCheck was not - // added to the worklist because it would be too expensive to walk - // down the graph for 1000 nodes and put all on the worklist. - // - // Found with: - // java -XX:VerifyIterativeGVN=0100 -Xbatch --version - case Op_RangeCheck: - return; - - // IfNode::Ideal does: - // Node* prev_dom = search_identical(dist, igvn); - // which means we seach up the CFG, traversing at most up to a distance. - // If anything happens rather far away from the If, we may not put the If - // back on the worklist. - // - // Found with: - // java -XX:VerifyIterativeGVN=0100 -Xcomp --version - case Op_If: - return; - - // IfNode::simple_subsuming - // Looks for dominating test that subsumes the current test. - // Notification could be difficult because of larger distance. - // - // Found with: - // runtime/exceptionMsgs/ArrayIndexOutOfBoundsException/ArrayIndexOutOfBoundsExceptionTest.java#id1 - // -XX:VerifyIterativeGVN=1110 - case Op_CountedLoopEnd: - return; - - // LongCountedLoopEndNode::Ideal - // Probably same issue as above. - // - // Found with: - // compiler/predicates/assertion/TestAssertionPredicates.java#NoLoopPredicationXbatch - // -XX:StressLongCountedLoop=2000000 -XX:+IgnoreUnrecognizedVMOptions -XX:VerifyIterativeGVN=1110 - case Op_LongCountedLoopEnd: - return; - // RegionNode::Ideal does "Skip around the useless IF diamond". // 245 IfTrue === 244 // 258 If === 245 257 @@ -1757,22 +1835,6 @@ void PhaseIterGVN::verify_Ideal_for(Node* n, bool can_reshape) { return; } - if (n->is_Load()) { - // LoadNode::Ideal uses tries to find an earlier memory state, and - // checks can_see_stored_value for it. - // - // Investigate why this was not already done during IGVN. - // A similar issue happens with Identity. - // - // There seem to be other cases where loads go up some steps, like - // LoadNode::Ideal going up 10x steps to find dominating load. - // - // Found with: - // test/hotspot/jtreg/compiler/arraycopy/TestCloneAccess.java - // -XX:VerifyIterativeGVN=1110 - return; - } - if (n->is_Store()) { // StoreNode::Ideal can do this: // // Capture an unaliased, unconditional, simple store into an initializer. @@ -1857,8 +1919,16 @@ void PhaseIterGVN::verify_Ideal_for(Node* n, bool can_reshape) { return; } - // The number of nodes shoud not increase. - uint old_unique = C->unique(); + // Ideal should not make progress if it returns nullptr. + // We use made_progress() rather than unique() or live_nodes() because some + // Ideal implementations speculatively create nodes and kill them before + // returning nullptr (e.g. split_if clones a Cmp to check is_canonical). + // unique() is a high-water mark that is not decremented by remove_dead_node, + // so it would cause false-positives. live_nodes() accounts for dead nodes but can + // decrease when Ideal removes existing nodes as side effects. + // made_progress() precisely tracks meaningful transforms, and speculative + // work killed via NodeOrigin::Speculative does not increment it. + uint old_progress = made_progress(); // The hash of a node should not change, this would indicate different inputs uint old_hash = n->hash(); // Remove 'n' from hash table in case it gets modified. We want to avoid @@ -1870,14 +1940,15 @@ void PhaseIterGVN::verify_Ideal_for(Node* n, bool can_reshape) { Node* i = n->Ideal(this, can_reshape); // If there was no new Idealization, we are probably happy. if (i == nullptr) { - if (old_unique < C->unique()) { + uint progress = made_progress() - old_progress; + if (progress != 0) { stringStream ss; // Print as a block without tty lock. ss.cr(); - ss.print_cr("Ideal optimization did not make progress but created new unused nodes."); - ss.print_cr(" old_unique = %d, unique = %d", old_unique, C->unique()); + ss.print_cr("Ideal optimization did not make progress but had side effects."); + ss.print_cr(" %u transforms made progress", progress); n->dump_bfs(1, nullptr, "", &ss); tty->print_cr("%s", ss.as_string()); - assert(false, "Unexpected new unused nodes from applying Ideal optimization on %s", n->Name()); + assert(false, "Unexpected side effects from applying Ideal optimization on %s", n->Name()); } if (old_hash != n->hash()) { @@ -2152,6 +2223,9 @@ Node *PhaseIterGVN::transform_old(Node* n) { #endif DEBUG_ONLY(uint loop_count = 1;) + if (i != nullptr) { + set_progress(); + } while (i != nullptr) { #ifdef ASSERT if (loop_count >= K + C->live_nodes()) { @@ -2197,10 +2271,8 @@ Node *PhaseIterGVN::transform_old(Node* n) { // cache Value. Later requests for the local phase->type of this Node can // use the cached Value instead of suffering with 'bottom_type'. if (type_or_null(k) != t) { -#ifndef PRODUCT - inc_new_values(); + NOT_PRODUCT(inc_new_values();) set_progress(); -#endif set_type(k, t); // If k is a TypeNode, capture any more-precise type permanently into Node k->raise_bottom_type(t); @@ -2209,7 +2281,7 @@ Node *PhaseIterGVN::transform_old(Node* n) { } // If 'k' computes a constant, replace it with a constant if (t->singleton() && !k->is_Con()) { - NOT_PRODUCT(set_progress();) + set_progress(); Node* con = makecon(t); // Make a constant add_users_to_worklist(k); subsume_node(k, con); // Everybody using k now uses con @@ -2219,7 +2291,7 @@ Node *PhaseIterGVN::transform_old(Node* n) { // Now check for Identities i = k->Identity(this); // Look for a nearby replacement if (i != k) { // Found? Return replacement! - NOT_PRODUCT(set_progress();) + set_progress(); add_users_to_worklist(k); subsume_node(k, i); // Everybody using k now uses i return i; @@ -2229,7 +2301,7 @@ Node *PhaseIterGVN::transform_old(Node* n) { i = hash_find_insert(k); // Check for pre-existing node if (i && (i != k)) { // Return the pre-existing node if it isn't dead - NOT_PRODUCT(set_progress();) + set_progress(); add_users_to_worklist(k); subsume_node(k, i); // Everybody using k now uses i return i; @@ -2248,7 +2320,7 @@ const Type* PhaseIterGVN::saturate(const Type* new_type, const Type* old_type, //------------------------------remove_globally_dead_node---------------------- // Kill a globally dead Node. All uses are also globally dead and are // aggressively trimmed. -void PhaseIterGVN::remove_globally_dead_node( Node *dead ) { +void PhaseIterGVN::remove_globally_dead_node(Node* dead, NodeOrigin origin) { enum DeleteProgress { PROCESS_INPUTS, PROCESS_OUTPUTS @@ -2265,11 +2337,13 @@ void PhaseIterGVN::remove_globally_dead_node( Node *dead ) { uint progress_state = stack.index(); assert(dead != C->root(), "killing root, eh?"); assert(!dead->is_top(), "add check for top when pushing"); - NOT_PRODUCT( set_progress(); ) if (progress_state == PROCESS_INPUTS) { // After following inputs, continue to outputs stack.set_index(PROCESS_OUTPUTS); if (!dead->is_Con()) { // Don't kill cons but uses + if (origin != NodeOrigin::Speculative) { + set_progress(); + } bool recurse = false; // Remove from hash table _table.hash_delete( dead ); @@ -2379,7 +2453,7 @@ void PhaseIterGVN::subsume_node( Node *old, Node *nn ) { // Smash all inputs to 'old', isolating him completely Node *temp = new Node(1); temp->init_req(0,nn); // Add a use to nn to prevent him from dying - remove_dead_node( old ); + remove_dead_node(old, NodeOrigin::Graph); temp->del_req(0); // Yank bogus edge if (nn != nullptr && nn->outcnt() == 0) { _worklist.push(nn); diff --git a/src/hotspot/share/opto/phaseX.hpp b/src/hotspot/share/opto/phaseX.hpp index 94890c250c4..cef273ff3df 100644 --- a/src/hotspot/share/opto/phaseX.hpp +++ b/src/hotspot/share/opto/phaseX.hpp @@ -187,8 +187,8 @@ public: class PhaseTransform : public Phase { public: PhaseTransform(PhaseNumber pnum) : Phase(pnum) { -#ifndef PRODUCT clear_progress(); +#ifndef PRODUCT clear_transforms(); set_allow_progress(true); #endif @@ -201,12 +201,31 @@ public: // true if CFG node d dominates CFG node n virtual bool is_dominator(Node *d, Node *n) { fatal("unimplemented for this pass"); return false; }; -#ifndef PRODUCT - uint _count_progress; // For profiling, count transforms that make progress - void set_progress() { ++_count_progress; assert( allow_progress(),"No progress allowed during verification"); } - void clear_progress() { _count_progress = 0; } - uint made_progress() const { return _count_progress; } + uint64_t _count_progress; // Count transforms that make progress + void set_progress() { ++_count_progress; assert(allow_progress(), "No progress allowed during verification"); } + void clear_progress() { _count_progress = 0; } + uint64_t made_progress() const { return _count_progress; } + // RAII guard for speculative transforms. Restores _count_progress in the destructor + // unless commit() is called, so that abandoned speculative work does not count as progress. + // In case multiple nodes are created and only some are speculative, commit() should still be called. + class SpeculativeProgressGuard { + PhaseTransform* _phase; + uint64_t _saved_progress; + bool _committed; + public: + SpeculativeProgressGuard(PhaseTransform* phase) : + _phase(phase), _saved_progress(phase->made_progress()), _committed(false) {} + ~SpeculativeProgressGuard() { + if (!_committed) { + _phase->_count_progress = _saved_progress; + } + } + + void commit() { _committed = true; } + }; + +#ifndef PRODUCT uint _count_transforms; // For profiling, count transforms performed void set_transforms() { ++_count_transforms; } void clear_transforms() { _count_transforms = 0; } @@ -446,10 +465,30 @@ class PhaseIterGVN : public PhaseGVN { private: bool _delay_transform; // When true simply register the node when calling transform // instead of actually optimizing it + DEBUG_ONLY(uint _num_processed;) // Running count for trace_PhaseIterGVN_verbose // Idealize old Node 'n' with respect to its inputs and its value virtual Node *transform_old( Node *a_node ); + // Drain the IGVN worklist: process nodes until the worklist is empty. + // Returns true if compilation was aborted (node limit or infinite loop), + // false on normal completion. + bool drain_worklist(); + + // Walk all live nodes and push deep-inspection candidates to _worklist. + void push_deep_revisit_candidates(); + + // After the main worklist drains, re-process deep-inspection nodes to + // catch optimization opportunities from far-away changes. Repeats until + // convergence (no progress made) or max rounds reached. + // Returns true if converged. + bool deep_revisit(); + + // Returns true for nodes that inspect the graph beyond their direct + // inputs, and therefore may miss optimization opportunities when + // changes happen far away. + bool needs_deep_revisit(const Node* n) const; + // Subsume users of node 'old' into node 'nn' void subsume_node( Node *old, Node *nn ); @@ -493,11 +532,16 @@ public: // Given def-use info and an initial worklist, apply Node::Ideal, // Node::Value, Node::Identity, hash-based value numbering, Node::Ideal_DU // and dominator info to a fixed point. - void optimize(); + // When deep is true, after the main worklist drains, re-process + // nodes that inspect the graph deeply (Load, CmpP, If, RangeCheck, + // CountedLoopEnd, LongCountedLoopEnd) to catch optimization opportunities + // from changes far away that the normal notification mechanism misses. + void optimize(bool deep = false); + #ifdef ASSERT - void verify_optimize(); + void verify_optimize(bool deep_revisit_converged); void verify_Value_for(const Node* n, bool strict = false); - void verify_Ideal_for(Node* n, bool can_reshape); + void verify_Ideal_for(Node* n, bool can_reshape, bool deep_revisit_converged); void verify_Identity_for(Node* n); void verify_node_invariants_for(const Node* n); void verify_empty_worklist(Node* n); @@ -506,7 +550,7 @@ public: #ifndef PRODUCT void trace_PhaseIterGVN(Node* n, Node* nn, const Type* old_type); void init_verifyPhaseIterGVN(); - void verify_PhaseIterGVN(); + void verify_PhaseIterGVN(bool deep_revisit_converged); #endif #ifdef ASSERT @@ -522,15 +566,21 @@ public: // It is significant only for debugging and profiling. Node* register_new_node_with_optimizer(Node* n, Node* orig = nullptr); - // Kill a globally dead Node. All uses are also globally dead and are + // Origin of a dead node, describing why it is dying. + // Speculative: a temporarily created node that was never part of the graph + // (e.g., a speculative clone in split_if to test constant foldability). + // Its death does not count as progress for convergence tracking. + enum class NodeOrigin { Graph, Speculative }; + + // Kill a globally dead Node. All uses are also globally dead and are // aggressively trimmed. - void remove_globally_dead_node( Node *dead ); + void remove_globally_dead_node(Node* dead, NodeOrigin origin); // Kill all inputs to a dead node, recursively making more dead nodes. // The Node must be dead locally, i.e., have no uses. - void remove_dead_node( Node *dead ) { + void remove_dead_node(Node* dead, NodeOrigin origin) { assert(dead->outcnt() == 0 && !dead->is_top(), "node must be dead"); - remove_globally_dead_node(dead); + remove_globally_dead_node(dead, origin); } // Add users of 'n' to worklist diff --git a/src/hotspot/share/opto/split_if.cpp b/src/hotspot/share/opto/split_if.cpp index 91595c57a10..c1303f0d0db 100644 --- a/src/hotspot/share/opto/split_if.cpp +++ b/src/hotspot/share/opto/split_if.cpp @@ -85,7 +85,7 @@ bool PhaseIdealLoop::split_up( Node *n, Node *blk1, Node *blk2 ) { if( split_up( n->in(i), blk1, blk2 ) ) { // Got split recursively and self went dead? if (n->outcnt() == 0) - _igvn.remove_dead_node(n); + _igvn.remove_dead_node(n, PhaseIterGVN::NodeOrigin::Graph); return true; } } @@ -273,7 +273,7 @@ void PhaseIdealLoop::clone_loadklass_nodes_at_cmp_index(const Node* n, Node* cmp _igvn.replace_input_of(decode_clone, 1, loadklass_clone); _igvn.replace_input_of(loadklass_clone, MemNode::Address, addp_clone); if (decode->outcnt() == 0) { - _igvn.remove_dead_node(decode); + _igvn.remove_dead_node(decode, PhaseIterGVN::NodeOrigin::Graph); } } } @@ -290,7 +290,7 @@ void PhaseIdealLoop::clone_loadklass_nodes_at_cmp_index(const Node* n, Node* cmp _igvn.replace_input_of(cmp, i, loadklass_clone); _igvn.replace_input_of(loadklass_clone, MemNode::Address, addp_clone); if (loadklass->outcnt() == 0) { - _igvn.remove_dead_node(loadklass); + _igvn.remove_dead_node(loadklass, PhaseIterGVN::NodeOrigin::Graph); } } } @@ -369,7 +369,7 @@ bool PhaseIdealLoop::clone_cmp_down(Node* n, const Node* blk1, const Node* blk2) _igvn.replace_input_of(x2, 1, x1); _igvn.replace_input_of(iff, 1, x2); } - _igvn.remove_dead_node(u); + _igvn.remove_dead_node(u, PhaseIterGVN::NodeOrigin::Graph); --j; } else { // We might see an Opaque1 from a loop limit check here @@ -385,7 +385,7 @@ bool PhaseIdealLoop::clone_cmp_down(Node* n, const Node* blk1, const Node* blk2) --j; } } - _igvn.remove_dead_node(bol); + _igvn.remove_dead_node(bol, PhaseIterGVN::NodeOrigin::Graph); --i; } } @@ -403,7 +403,7 @@ bool PhaseIdealLoop::clone_cmp_down(Node* n, const Node* blk1, const Node* blk2) register_new_node(x, ctrl_or_self(use)); _igvn.replace_input_of(use, pos, x); } - _igvn.remove_dead_node(n); + _igvn.remove_dead_node(n, PhaseIterGVN::NodeOrigin::Graph); return true; } @@ -517,7 +517,7 @@ Node *PhaseIdealLoop::spinup( Node *iff_dom, Node *new_false, Node *new_true, No Node *t = _igvn.hash_find_insert(phi_post); if( t ) { // See if we already have this one // phi_post will not be used, so kill it - _igvn.remove_dead_node(phi_post); + _igvn.remove_dead_node(phi_post, PhaseIterGVN::NodeOrigin::Speculative); phi_post->destruct(&_igvn); phi_post = t; } else { @@ -647,7 +647,7 @@ void PhaseIdealLoop::do_split_if(Node* iff, RegionNode** new_false_region, Regio Node* m = n->out(j); // If m is dead, throw it away, and declare progress if (_loop_or_ctrl[m->_idx] == nullptr) { - _igvn.remove_dead_node(m); + _igvn.remove_dead_node(m, PhaseIterGVN::NodeOrigin::Graph); // fall through } else if (m != iff && split_up(m, region, iff)) { @@ -704,7 +704,7 @@ void PhaseIdealLoop::do_split_if(Node* iff, RegionNode** new_false_region, Regio new_true = ifpx; } } - _igvn.remove_dead_node(new_iff); + _igvn.remove_dead_node(new_iff, PhaseIterGVN::NodeOrigin::Speculative); // Lazy replace IDOM info with the region's dominator replace_node_and_forward_ctrl(iff, region_dom); // Break the self-cycle. Required for forward_ctrl to work on region. @@ -720,7 +720,7 @@ void PhaseIdealLoop::do_split_if(Node* iff, RegionNode** new_false_region, Regio for (DUIterator k = region->outs(); region->has_out(k); k++) { Node* phi = region->out(k); if (!phi->in(0)) { // Dead phi? Remove it - _igvn.remove_dead_node(phi); + _igvn.remove_dead_node(phi, PhaseIterGVN::NodeOrigin::Graph); } else if (phi == region) { // Found the self-reference continue; // No roll-back of DUIterator } else if (phi->is_Phi()) { // Expected common case: Phi hanging off of Region @@ -739,7 +739,7 @@ void PhaseIdealLoop::do_split_if(Node* iff, RegionNode** new_false_region, Regio handle_use(use, phi, &phi_cache, region_dom, new_false, new_true, old_false, old_true); } // End of while phi has uses // Remove the dead Phi - _igvn.remove_dead_node( phi ); + _igvn.remove_dead_node(phi, PhaseIterGVN::NodeOrigin::Graph); } else { assert(phi->in(0) == region, "Inconsistent graph"); // Random memory op guarded by Region. Compute new DEF for USE. @@ -752,7 +752,7 @@ void PhaseIdealLoop::do_split_if(Node* iff, RegionNode** new_false_region, Regio --k; } // End of while merge point has phis - _igvn.remove_dead_node(region); + _igvn.remove_dead_node(region, PhaseIterGVN::NodeOrigin::Graph); // Control is updated here to a region, which is not a test, so any node that // depends_only_on_test must be pinned diff --git a/src/hotspot/share/runtime/mutexLocker.cpp b/src/hotspot/share/runtime/mutexLocker.cpp index b102e6424f1..3dd61374d03 100644 --- a/src/hotspot/share/runtime/mutexLocker.cpp +++ b/src/hotspot/share/runtime/mutexLocker.cpp @@ -105,7 +105,6 @@ Mutex* G1MarkStackFreeList_lock = nullptr; Monitor* G1OldGCCount_lock = nullptr; Mutex* G1OldSets_lock = nullptr; Mutex* G1ReviseYoungLength_lock = nullptr; -Monitor* G1RootRegionScan_lock = nullptr; Mutex* G1RareEvent_lock = nullptr; Mutex* G1Uncommit_lock = nullptr; #endif @@ -216,7 +215,6 @@ void mutex_init() { MUTEX_DEFN(G1MarkStackChunkList_lock , PaddedMutex , nosafepoint); MUTEX_DEFN(G1MarkStackFreeList_lock , PaddedMutex , nosafepoint); MUTEX_DEFN(G1OldSets_lock , PaddedMutex , nosafepoint); - MUTEX_DEFN(G1RootRegionScan_lock , PaddedMonitor, nosafepoint-1); MUTEX_DEFN(G1Uncommit_lock , PaddedMutex , service-2); } #endif diff --git a/src/hotspot/share/runtime/mutexLocker.hpp b/src/hotspot/share/runtime/mutexLocker.hpp index f6c0a967718..682383de401 100644 --- a/src/hotspot/share/runtime/mutexLocker.hpp +++ b/src/hotspot/share/runtime/mutexLocker.hpp @@ -101,7 +101,6 @@ extern Monitor* G1OldGCCount_lock; // in support of "concurrent" f extern Mutex* G1OldSets_lock; // protects the G1 old region sets extern Mutex* G1RareEvent_lock; // Synchronizes (rare) parallel GC operations. extern Mutex* G1ReviseYoungLength_lock; // Protects access to young gen length revising operations. -extern Monitor* G1RootRegionScan_lock; // used to notify that the G1 CM threads have finished scanning the root regions extern Mutex* G1Uncommit_lock; // protects the G1 uncommit list when not at safepoints #endif diff --git a/src/hotspot/share/runtime/objectMonitor.cpp b/src/hotspot/share/runtime/objectMonitor.cpp index 94399088464..9fc834f4b6b 100644 --- a/src/hotspot/share/runtime/objectMonitor.cpp +++ b/src/hotspot/share/runtime/objectMonitor.cpp @@ -155,7 +155,7 @@ static const jlong MAX_RECHECK_INTERVAL = 1000; // // Succession is provided for by a policy of competitive handoff. // The exiting thread does _not_ grant or pass ownership to the -// successor thread. (This is also referred to as "handoff succession"). +// successor thread. (This is also referred to as "handoff succession"). // Instead the exiting thread releases ownership and possibly wakes // a successor, so the successor can (re)compete for ownership of the lock. // @@ -189,7 +189,7 @@ static const jlong MAX_RECHECK_INTERVAL = 1000; // // Once we have formed a doubly linked list it's easy to find the // successor (A), wake it up, have it remove itself, and update the -// tail pointer, as seen in and 3) below. +// tail pointer, see 3) below. // // 3) entry_list ->F<=>E<=>D<=>C<=>B->null // entry_list_tail ------------------^ @@ -223,7 +223,7 @@ static const jlong MAX_RECHECK_INTERVAL = 1000; // remove itself) or update the tail. // // * The monitor entry list operations avoid locks, but strictly speaking -// they're not lock-free. Enter is lock-free, exit is not. +// they're not lock-free. Enter is lock-free, exit is not. // For a description of 'Methods and apparatus providing non-blocking access // to a resource,' see U.S. Pat. No. 7844973. // @@ -387,7 +387,7 @@ bool ObjectMonitor::try_lock_with_contention_mark(JavaThread* locking_thread, Ob prev_owner = try_set_owner_from(DEFLATER_MARKER, locking_thread); if (prev_owner == DEFLATER_MARKER) { // We successfully cancelled the in-progress async deflation by - // changing owner from DEFLATER_MARKER to current. We now extend + // changing owner from DEFLATER_MARKER to current. We now extend // the lifetime of the contention_mark (e.g. contentions++) here // to prevent the deflater thread from winning the last part of // the 2-part async deflation protocol after the regular @@ -633,14 +633,14 @@ void ObjectMonitor::enter_with_contention_mark(JavaThread* current, ObjectMonito // The thread -- now the owner -- is back in vm mode. // Report the glorious news via TI,DTrace and jvmstat. - // The probe effect is non-trivial. All the reportage occurs + // The probe effect is non-trivial. All the reportage occurs // while we hold the monitor, increasing the length of the critical - // section. Amdahl's parallel speedup law comes vividly into play. + // section. Amdahl's parallel speedup law comes vividly into play. // // Another option might be to aggregate the events (thread local or // per-monitor aggregation) and defer reporting until a more opportune // time -- such as next time some thread encounters contention but has - // yet to acquire the lock. While spinning that thread could + // yet to acquire the lock. While spinning that thread could // spinning we could increment JVMStat counters, etc. DTRACE_MONITOR_PROBE(contended__entered, this, object(), current); @@ -739,11 +739,11 @@ bool ObjectMonitor::try_lock_or_add_to_entry_list(JavaThread* current, ObjectWai return false; } - // Interference - the CAS failed because _entry_list changed. Before + // Interference - the CAS failed because _entry_list changed. Before // retrying the CAS retry taking the lock as it may now be free. if (try_lock(current) == TryLockResult::Success) { - assert(!has_successor(current), "invariant"); assert(has_owner(current), "invariant"); + assert(!has_successor(current), "invariant"); node->TState = ObjectWaiter::TS_RUN; return true; } @@ -953,8 +953,8 @@ bool ObjectMonitor::try_enter_fast(JavaThread* current, ObjectWaiter* current_no // Try the lock - TATAS if (try_lock(current) == TryLockResult::Success) { - assert(!has_successor(current), "invariant"); assert(has_owner(current), "invariant"); + assert(!has_successor(current), "invariant"); return true; } @@ -964,7 +964,7 @@ bool ObjectMonitor::try_enter_fast(JavaThread* current, ObjectWaiter* current_no // // If the _owner is ready but OFFPROC we could use a YieldTo() // operation to donate the remainder of this thread's quantum - // to the owner. This has subtle but beneficial affinity + // to the owner. This has subtle but beneficial affinity // effects. if (try_spin(current)) { @@ -974,15 +974,15 @@ bool ObjectMonitor::try_enter_fast(JavaThread* current, ObjectWaiter* current_no } // The Spin failed -- Enqueue and park the thread ... - assert(!has_successor(current), "invariant"); assert(!has_owner(current), "invariant"); + assert(!has_successor(current), "invariant"); // Enqueue "current" on ObjectMonitor's _entry_list. // // current_node acts as a proxy for current. // As an aside, if were to ever rewrite the synchronization code mostly // in Java, WaitNodes, ObjectMonitors, and Events would become 1st-class - // Java objects. This would avoid awkward lifecycle and liveness issues, + // Java objects. This would avoid awkward lifecycle and liveness issues, // as well as eliminate a subset of ABA issues. // TODO: eliminate ObjectWaiter and enqueue either Threads or Events. @@ -995,7 +995,7 @@ bool ObjectMonitor::try_enter_fast(JavaThread* current, ObjectWaiter* current_no // This thread is now added to the _entry_list. // The lock might have been released while this thread was occupied queueing - // itself onto _entry_list. To close the race and avoid "stranding" and + // itself onto _entry_list. To close the race and avoid "stranding" and // progress-liveness failure the caller must resample-retry _owner before parking. // Note the Dekker/Lamport duality: ST _entry_list; MEMBAR; LD Owner. // In this case the ST-MEMBAR is accomplished with CAS() in try_lock_or_add_to_entry_list. @@ -1051,13 +1051,13 @@ void ObjectMonitor::enter_internal(JavaThread* current, ObjectWaiter* current_no } // Try again, but just so we distinguish between futile wakeups and - // successful wakeups. The following test isn't algorithmically + // successful wakeups. The following test isn't algorithmically // necessary, but it helps us maintain sensible statistics. if (try_lock(current) == TryLockResult::Success) { break; } - // The lock is still contested. + // The lock is still contended. if (!reenter_path) { // Assuming this is not a spurious wakeup we'll normally find _succ == current. @@ -1070,9 +1070,9 @@ void ObjectMonitor::enter_internal(JavaThread* current, ObjectWaiter* current_no } // We can find that we were unpark()ed and redesignated _succ while - // we were spinning. That's harmless. If we iterate and call park(), + // we were spinning. That's harmless. If we iterate and call park(), // park() will consume the event and return immediately and we'll - // just spin again. This pattern can repeat, leaving _succ to simply + // just spin again. This pattern can repeat, leaving _succ to simply // spin on a CPU. if (has_successor(current)) { @@ -1092,9 +1092,9 @@ void ObjectMonitor::enter_internal(JavaThread* current, ObjectWaiter* current_no // Current has acquired the lock -- Unlink current from the _entry_list. unlink_after_acquire(current, current_node); if (has_successor(current)) { - clear_successor(); // Note that we don't need to do OrderAccess::fence() after clearing // _succ here, since we own the lock. + clear_successor(); } // We've acquired ownership with CAS(). @@ -1146,7 +1146,9 @@ bool ObjectMonitor::vthread_monitor_enter(JavaThread* current, ObjectWaiter* wai if (try_lock_or_add_to_entry_list(current, node)) { // We got the lock. - if (waiter == nullptr) delete node; // for Object.wait() don't delete yet + if (waiter == nullptr) { + delete node; // for Object.wait() don't delete yet + } dec_unmounted_vthreads(); return true; } @@ -1157,8 +1159,14 @@ bool ObjectMonitor::vthread_monitor_enter(JavaThread* current, ObjectWaiter* wai if (try_lock(current) == TryLockResult::Success) { assert(has_owner(current), "invariant"); unlink_after_acquire(current, node); - if (has_successor(current)) clear_successor(); - if (waiter == nullptr) delete node; // for Object.wait() don't delete yet + if (has_successor(current)) { + // Note that we don't need to do OrderAccess::fence() after clearing + // _succ here, since we own the lock. + clear_successor(); + } + if (waiter == nullptr) { + delete node; // for Object.wait() don't delete yet + } dec_unmounted_vthreads(); return true; } @@ -1182,7 +1190,9 @@ bool ObjectMonitor::resume_operation(JavaThread* current, ObjectWaiter* node, Co if (node->is_wait() && !node->at_reenter()) { bool acquired_monitor = vthread_wait_reenter(current, node, cont); - if (acquired_monitor) return true; + if (acquired_monitor) { + return true; + } } // Retry acquiring monitor... @@ -1196,7 +1206,9 @@ bool ObjectMonitor::resume_operation(JavaThread* current, ObjectWaiter* node, Co } oop vthread = current->vthread(); - if (has_successor(current)) clear_successor(); + if (has_successor(current)) { + clear_successor(); + } // Invariant: after clearing _succ a thread *must* retry acquiring the monitor. OrderAccess::fence(); @@ -1217,7 +1229,11 @@ void ObjectMonitor::vthread_epilog(JavaThread* current, ObjectWaiter* node) { add_to_contentions(-1); dec_unmounted_vthreads(); - if (has_successor(current)) clear_successor(); + if (has_successor(current)) { + // Note that we don't need to do OrderAccess::fence() after clearing + // _succ here, since we own the lock. + clear_successor(); + } guarantee(_recursions == 0, "invariant"); @@ -1419,7 +1435,7 @@ void ObjectMonitor::unlink_after_acquire(JavaThread* current, ObjectWaiter* curr // inopportune) reclamation of "this". // // We'd like to assert that: (THREAD->thread_state() != _thread_blocked) ; -// There's one exception to the claim above, however. enter_internal() can call +// There's one exception to the claim above, however. enter_internal() can call // exit() to drop a lock if the acquirer has been externally suspended. // In that case exit() is called with _thread_state == _thread_blocked, // but the monitor's _contentions field is > 0, which inhibits reclamation. @@ -1507,12 +1523,12 @@ void ObjectMonitor::exit(JavaThread* current, bool not_suspended) { w = entry_list_tail(current); // I'd like to write: guarantee (w->_thread != current). // But in practice an exiting thread may find itself on the entry_list. - // Let's say thread T1 calls O.wait(). Wait() enqueues T1 on O's waitset and - // then calls exit(). Exit release the lock by setting O._owner to null. - // Let's say T1 then stalls. T2 acquires O and calls O.notify(). The + // Let's say thread T1 calls O.wait(). Wait() enqueues T1 on O's waitset and + // then calls exit(). Exit releases the lock by setting O._owner to null. + // Let's say T1 then stalls. T2 acquires O and calls O.notify(). The // notify() operation moves T1 from O's waitset to O's entry_list. T2 then - // release the lock "O". T1 resumes immediately after the ST of null into - // _owner, above. T1 notices that the entry_list is populated, so it + // releases the lock "O". T1 resumes immediately after the ST of null into + // _owner, above. T1 notices that the entry_list is populated, so it // reacquires the lock and then finds itself on the entry_list. // Given all that, we have to tolerate the circumstance where "w" is // associated with current. @@ -1534,26 +1550,26 @@ void ObjectMonitor::exit(JavaThread* current, bool not_suspended) { // Normally the exiting thread is responsible for ensuring succession, // but if this thread observes other successors are ready or other // entering threads are spinning after it has stored null into _owner - // then it can exit without waking a successor. The existence of + // then it can exit without waking a successor. The existence of // spinners or ready successors guarantees proper succession (liveness). - // Responsibility passes to the ready or running successors. The exiting - // thread delegates the duty. More precisely, if a successor already + // Responsibility passes to the ready or running successors. The exiting + // thread delegates the duty. More precisely, if a successor already // exists this thread is absolved of the responsibility of waking // (unparking) one. // The _succ variable is critical to reducing futile wakeup frequency. // _succ identifies the "heir presumptive" thread that has been made - // ready (unparked) but that has not yet run. We need only one such + // ready (unparked) but that has not yet run. We need only one such // successor thread to guarantee progress. // See http://www.usenix.org/events/jvm01/full_papers/dice/dice.pdf // section 3.3 "Futile Wakeup Throttling" for details. // - // Note that spinners in Enter() also set _succ non-null. - // In the current implementation spinners opportunistically set + // Note that spinners in enter(), try_enter_fast() and enter_internal() also + // set _succ non-null. In the current implementation spinners opportunistically set // _succ so that exiting threads might avoid waking a successor. // Which means that the exiting thread could exit immediately without // waking a successor, if it observes a successor after it has dropped - // the lock. Note that the dropped lock needs to become visible to the + // the lock. Note that the dropped lock needs to become visible to the // spinner. if (_entry_list == nullptr || has_successor()) { @@ -1730,7 +1746,7 @@ void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS) { if (JvmtiExport::should_post_monitor_wait()) { JvmtiExport::post_monitor_wait(current, object(), millis); } - // post monitor waited event. Note that this is past-tense, we are done waiting. + // post monitor waited event. Note that this is past-tense, we are done waiting. if (JvmtiExport::should_post_monitor_waited()) { // Note: 'false' parameter is passed here because the // wait was not timed out due to thread interrupt. @@ -1791,9 +1807,9 @@ void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS) { // Enter the waiting queue, which is a circular doubly linked list in this case // but it could be a priority queue or any data structure. - // _wait_set_lock protects the wait queue. Normally the wait queue is accessed only + // _wait_set_lock protects the wait queue. Normally the wait queue is accessed only // by the owner of the monitor *except* in the case where park() - // returns because of a timeout of interrupt. Contention is exceptionally rare + // returns because of a timeout of interrupt. Contention is exceptionally rare // so we use a simple spin-lock instead of a heavier-weight blocking lock. { @@ -1850,7 +1866,7 @@ void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS) { // written by the is thread. (perhaps the fetch might even be satisfied // by a look-aside into the processor's own store buffer, although given // the length of the code path between the prior ST and this load that's - // highly unlikely). If the following LD fetches a stale TS_WAIT value + // highly unlikely). If the following LD fetches a stale TS_WAIT value // then we'll acquire the lock and then re-fetch a fresh TState value. // That is, we fail toward safety. @@ -1868,7 +1884,12 @@ void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS) { // No other threads will asynchronously modify TState. guarantee(node.TState != ObjectWaiter::TS_WAIT, "invariant"); OrderAccess::loadload(); - if (has_successor(current)) clear_successor(); + if (has_successor(current)) { + clear_successor(); + // Note that we do not need a fence here, as, regardless of the path taken, + // there is a fence either in ThreadBlockInVM's destructor or + // right after a call to post_monitor_wait_event(). + } // Reentry phase -- reacquire the monitor. // re-enter contended monitor after object.wait(). @@ -2046,11 +2067,11 @@ bool ObjectMonitor::notify_internal(JavaThread* current) { } } - // _wait_set_lock protects the wait queue, not the entry_list. We could + // _wait_set_lock protects the wait queue, not the entry_list. We could // move the add-to-entry_list operation, above, outside the critical section - // protected by _wait_set_lock. In practice that's not useful. With the + // protected by _wait_set_lock. In practice that's not useful. With the // exception of wait() timeouts and interrupts the monitor owner - // is the only thread that grabs _wait_set_lock. There's almost no contention + // is the only thread that grabs _wait_set_lock. There's almost no contention // on _wait_set_lock so it's not profitable to reduce the length of the // critical section. } @@ -2151,9 +2172,9 @@ void ObjectMonitor::vthread_wait(JavaThread* current, jlong millis, bool interru // Enter the waiting queue, which is a circular doubly linked list in this case // but it could be a priority queue or any data structure. - // _wait_set_lock protects the wait queue. Normally the wait queue is accessed only + // _wait_set_lock protects the wait queue. Normally the wait queue is accessed only // by the owner of the monitor *except* in the case where park() - // returns because of a timeout or interrupt. Contention is exceptionally rare + // returns because of a timeout or interrupt. Contention is exceptionally rare // so we use a simple spin-lock instead of a heavier-weight blocking lock. { @@ -2243,25 +2264,25 @@ bool ObjectMonitor::vthread_wait_reenter(JavaThread* current, ObjectWaiter* node // algorithm. // // Broadly, we can fix the spin frequency -- that is, the % of contended lock -// acquisition attempts where we opt to spin -- at 100% and vary the spin count +// acquisition attempts where we opt to spin -- at 100% and vary the spin count // (duration) or we can fix the count at approximately the duration of -// a context switch and vary the frequency. Of course we could also +// a context switch and vary the frequency. Of course we could also // vary both satisfying K == Frequency * Duration, where K is adaptive by monitor. // For a description of 'Adaptive spin-then-block mutual exclusion in // multi-threaded processing,' see U.S. Pat. No. 8046758. // // This implementation varies the duration "D", where D varies with // the success rate of recent spin attempts. (D is capped at approximately -// length of a round-trip context switch). The success rate for recent +// length of a round-trip context switch). The success rate for recent // spin attempts is a good predictor of the success rate of future spin -// attempts. The mechanism adapts automatically to varying critical +// attempts. The mechanism adapts automatically to varying critical // section length (lock modality), system load and degree of parallelism. // D is maintained per-monitor in _SpinDuration and is initialized -// optimistically. Spin frequency is fixed at 100%. +// optimistically. Spin frequency is fixed at 100%. // // Note that _SpinDuration is volatile, but we update it without locks -// or atomics. The code is designed so that _SpinDuration stays within -// a reasonable range even in the presence of races. The arithmetic +// or atomics. The code is designed so that _SpinDuration stays within +// a reasonable range even in the presence of races. The arithmetic // operations on _SpinDuration are closed over the domain of legal values, // so at worst a race will install and older but still legal value. // At the very worst this introduces some apparent non-determinism. @@ -2269,28 +2290,28 @@ bool ObjectMonitor::vthread_wait_reenter(JavaThread* current, ObjectWaiter* node // count are relatively short, even in the worst case, the effect is harmless. // // Care must be taken that a low "D" value does not become an -// an absorbing state. Transient spinning failures -- when spinning +// an absorbing state. Transient spinning failures -- when spinning // is overall profitable -- should not cause the system to converge -// on low "D" values. We want spinning to be stable and predictable +// on low "D" values. We want spinning to be stable and predictable // and fairly responsive to change and at the same time we don't want // it to oscillate, become metastable, be "too" non-deterministic, // or converge on or enter undesirable stable absorbing states. // // We implement a feedback-based control system -- using past behavior -// to predict future behavior. We face two issues: (a) if the +// to predict future behavior. We face two issues: (a) if the // input signal is random then the spin predictor won't provide optimal // results, and (b) if the signal frequency is too high then the control // system, which has some natural response lag, will "chase" the signal. -// (b) can arise from multimodal lock hold times. Transient preemption +// (b) can arise from multimodal lock hold times. Transient preemption // can also result in apparent bimodal lock hold times. // Although sub-optimal, neither condition is particularly harmful, as // in the worst-case we'll spin when we shouldn't or vice-versa. // The maximum spin duration is rather short so the failure modes aren't bad. // To be conservative, I've tuned the gain in system to bias toward -// _not spinning. Relatedly, the system can sometimes enter a mode where it -// "rings" or oscillates between spinning and not spinning. This happens +// _not spinning. Relatedly, the system can sometimes enter a mode where it +// "rings" or oscillates between spinning and not spinning. This happens // when spinning is just on the cusp of profitability, however, so the -// situation is not dire. The state is benign -- there's no need to add +// situation is not dire. The state is benign -- there's no need to add // hysteresis control to damp the transition rate between spinning and // not spinning. @@ -2322,7 +2343,9 @@ inline static int adjust_down(int spin_duration) { // Consider an AIMD scheme like: x -= (x >> 3) + 100 // This is globally sample and tends to damp the response. x -= Knob_Penalty; - if (x < 0) { x = 0; } + if (x < 0) { + x = 0; + } return x; } else { return spin_duration; @@ -2348,7 +2371,7 @@ bool ObjectMonitor::short_fixed_spin(JavaThread* current, int spin_count, bool a // Spinning: Fixed frequency (100%), vary duration bool ObjectMonitor::try_spin(JavaThread* current) { - // Dumb, brutal spin. Good for comparative measurements against adaptive spinning. + // Dumb, brutal spin. Good for comparative measurements against adaptive spinning. int knob_fixed_spin = Knob_FixedSpin; // 0 (don't spin: default), 2000 good test if (knob_fixed_spin > 0) { return short_fixed_spin(current, knob_fixed_spin, false); @@ -2357,7 +2380,7 @@ bool ObjectMonitor::try_spin(JavaThread* current) { // Admission control - verify preconditions for spinning // // We always spin a little bit, just to prevent _SpinDuration == 0 from - // becoming an absorbing state. Put another way, we spin briefly to + // becoming an absorbing state. Put another way, we spin briefly to // sample, just in case the system load, parallelism, contention, or lock // modality changed. @@ -2369,7 +2392,7 @@ bool ObjectMonitor::try_spin(JavaThread* current) { // // Consider the following alternative: // Periodically set _SpinDuration = _SpinLimit and try a long/full - // spin attempt. "Periodically" might mean after a tally of + // spin attempt. "Periodically" might mean after a tally of // the # of failed spin attempts (or iterations) reaches some threshold. // This takes us into the realm of 1-out-of-N spinning, where we // hold the duration constant but vary the frequency. @@ -2416,9 +2439,9 @@ bool ObjectMonitor::try_spin(JavaThread* current) { // If this thread observes the monitor transition or flicker // from locked to unlocked to locked, then the odds that this // thread will acquire the lock in this spin attempt go down - // considerably. The same argument applies if the CAS fails + // considerably. The same argument applies if the CAS fails // or if we observe _owner change from one non-null value to - // another non-null value. In such cases we might abort + // another non-null value. In such cases we might abort // the spin without prejudice or apply a "penalty" to the // spin count-down variable "ctr", reducing it by 100, say. @@ -2429,6 +2452,8 @@ bool ObjectMonitor::try_spin(JavaThread* current) { // The CAS succeeded -- this thread acquired ownership // Take care of some bookkeeping to exit spin state. if (has_successor(current)) { + // Note that we don't need to do OrderAccess::fence() after clearing + // _succ here, since we own the lock. clear_successor(); } @@ -2470,7 +2495,7 @@ bool ObjectMonitor::try_spin(JavaThread* current) { if (has_successor(current)) { clear_successor(); // Invariant: after setting succ=null a contending thread - // must recheck-retry _owner before parking. This usually happens + // must recheck-retry _owner before parking. This usually happens // in the normal usage of try_spin(), but it's safest // to make try_spin() as foolproof as possible. OrderAccess::fence(); diff --git a/src/java.base/share/classes/java/lang/foreign/Linker.java b/src/java.base/share/classes/java/lang/foreign/Linker.java index 1a7d33266aa..f597e4ee52e 100644 --- a/src/java.base/share/classes/java/lang/foreign/Linker.java +++ b/src/java.base/share/classes/java/lang/foreign/Linker.java @@ -833,7 +833,7 @@ public sealed interface Linker permits AbstractLinker { *

* Captured state can be stored in, or retrieved from the capture state segment by * constructing var handles from the {@linkplain #captureStateLayout capture state layout}. - * Some functions require this state the be initialized to a particular value before + * Some functions require this state to be initialized to a particular value before * invoking the downcall. *

* The following example demonstrates the use of this linker option: diff --git a/src/java.base/share/classes/java/util/zip/ZipOutputStream.java b/src/java.base/share/classes/java/util/zip/ZipOutputStream.java index 47499858a37..4ea4a103fef 100644 --- a/src/java.base/share/classes/java/util/zip/ZipOutputStream.java +++ b/src/java.base/share/classes/java/util/zip/ZipOutputStream.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -257,6 +257,11 @@ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstant default: throw new ZipException("unsupported compression method"); } + // Verify that entry name and comment can be encoded + byte[] nameBytes = checkEncodable(e.name, "unmappable character in ZIP entry name"); + if (e.comment != null) { + checkEncodable(e.comment, "unmappable character in ZIP entry comment"); + } if (! names.add(e.name)) { throw new ZipException("duplicate entry: " + e.name); } @@ -270,7 +275,16 @@ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstant } current = new XEntry(e, written); xentries.add(current); - writeLOC(current); + writeLOC(current, nameBytes); + } + + // Throws ZipException if the given string cannot be encoded + private byte[] checkEncodable(String str, String msg) throws ZipException { + try { + return zc.getBytes(str); + } catch (IllegalArgumentException ex) { + throw (ZipException) new ZipException(msg).initCause(ex); + } } /** @@ -424,7 +438,7 @@ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstant /* * Writes local file (LOC) header for specified entry. */ - private void writeLOC(XEntry xentry) throws IOException { + private void writeLOC(XEntry xentry, byte[] nameBytes) throws IOException { ZipEntry e = xentry.entry; int flag = e.flag; boolean hasZip64 = false; @@ -461,7 +475,6 @@ public class ZipOutputStream extends DeflaterOutputStream implements ZipConstant writeInt(e.size); // uncompressed size } } - byte[] nameBytes = zc.getBytes(e.name); writeShort(nameBytes.length); int elenEXTT = 0; // info-zip extended timestamp diff --git a/src/java.base/share/classes/sun/nio/cs/UTF_32Coder.java b/src/java.base/share/classes/sun/nio/cs/UTF_32Coder.java index c6f38ec9bfc..72e59d22e2c 100644 --- a/src/java.base/share/classes/sun/nio/cs/UTF_32Coder.java +++ b/src/java.base/share/classes/sun/nio/cs/UTF_32Coder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -185,5 +185,12 @@ class UTF_32Coder { doneBOM = !doBOM; } + public boolean canEncode(char c) { + return !Character.isSurrogate(c); + } + + public boolean canEncode(CharSequence cs) { + return Unicode.isValidUnicode(cs); + } } } diff --git a/src/java.base/share/classes/sun/nio/cs/UTF_8.java b/src/java.base/share/classes/sun/nio/cs/UTF_8.java index 2928ae6d509..fda8e5eec1f 100644 --- a/src/java.base/share/classes/sun/nio/cs/UTF_8.java +++ b/src/java.base/share/classes/sun/nio/cs/UTF_8.java @@ -424,6 +424,10 @@ public final class UTF_8 extends Unicode { return !Character.isSurrogate(c); } + public boolean canEncode(CharSequence cs) { + return Unicode.isValidUnicode(cs); + } + public boolean isLegalReplacement(byte[] repl) { return ((repl.length == 1 && repl[0] >= 0) || super.isLegalReplacement(repl)); diff --git a/src/java.base/share/classes/sun/nio/cs/Unicode.java b/src/java.base/share/classes/sun/nio/cs/Unicode.java index aac77a13ffb..06a50f125c5 100644 --- a/src/java.base/share/classes/sun/nio/cs/Unicode.java +++ b/src/java.base/share/classes/sun/nio/cs/Unicode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -95,4 +95,23 @@ abstract class Unicode extends Charset || (cs.name().equals("x-Johab")) || (cs.name().equals("Shift_JIS"))); } + + static boolean isValidUnicode(CharSequence cs) { + int length = cs.length(); + for (int i = 0; i < length;) { + char c = cs.charAt(i++); + if (Character.isHighSurrogate(c)) { + if (i == length) { + return false; + } + char low = cs.charAt(i++); + if (!Character.isLowSurrogate(low)) { + return false; + } + } else if (Character.isLowSurrogate(c)) { + return false; + } + } + return true; + } } diff --git a/src/java.base/share/classes/sun/nio/cs/UnicodeEncoder.java b/src/java.base/share/classes/sun/nio/cs/UnicodeEncoder.java index 7b34fb2d512..6f7413dcbf8 100644 --- a/src/java.base/share/classes/sun/nio/cs/UnicodeEncoder.java +++ b/src/java.base/share/classes/sun/nio/cs/UnicodeEncoder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2000, 2010, 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 @@ -108,4 +108,8 @@ public abstract class UnicodeEncoder extends CharsetEncoder { public boolean canEncode(char c) { return ! Character.isSurrogate(c); } + + public boolean canEncode(CharSequence cs) { + return Unicode.isValidUnicode(cs); + } } diff --git a/src/java.desktop/share/classes/java/awt/Dialog.java b/src/java.desktop/share/classes/java/awt/Dialog.java index 83aa89b9bf7..038aa5b65e3 100644 --- a/src/java.desktop/share/classes/java/awt/Dialog.java +++ b/src/java.desktop/share/classes/java/awt/Dialog.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 @@ -41,7 +41,6 @@ import javax.accessibility.AccessibleRole; import javax.accessibility.AccessibleState; import javax.accessibility.AccessibleStateSet; -import sun.awt.AppContext; import sun.awt.SunToolkit; import sun.awt.util.IdentityArrayList; @@ -1013,30 +1012,12 @@ public class Dialog extends Window { if (!isModal()) { conditionalShow(null, null); } else { - AppContext showAppContext = AppContext.getAppContext(); - AtomicLong time = new AtomicLong(); Component predictedFocusOwner = null; try { predictedFocusOwner = getMostRecentFocusOwner(); if (conditionalShow(predictedFocusOwner, time)) { modalFilter = ModalEventFilter.createFilterForDialog(this); - // if this dialog is toolkit-modal, the filter should be added - // to all EDTs (for all AppContexts) - if (modalityType == ModalityType.TOOLKIT_MODAL) { - for (AppContext appContext : AppContext.getAppContexts()) { - if (appContext == showAppContext) { - continue; - } - EventQueue eventQueue = (EventQueue)appContext.get(AppContext.EVENT_QUEUE_KEY); - // it may occur that EDT for appContext hasn't been started yet, so - // we post an empty invocation event to trigger EDT initialization - eventQueue.postEvent(new InvocationEvent(this, () -> {})); - EventDispatchThread edt = eventQueue.getDispatchThread(); - edt.addEventFilter(modalFilter); - } - } - modalityPushed(); try { EventQueue eventQueue = Toolkit.getDefaultToolkit().getSystemEventQueue(); @@ -1047,19 +1028,6 @@ public class Dialog extends Window { } finally { modalityPopped(); } - - // if this dialog is toolkit-modal, its filter must be removed - // from all EDTs (for all AppContexts) - if (modalityType == ModalityType.TOOLKIT_MODAL) { - for (AppContext appContext : AppContext.getAppContexts()) { - if (appContext == showAppContext) { - continue; - } - EventQueue eventQueue = (EventQueue)appContext.get(AppContext.EVENT_QUEUE_KEY); - EventDispatchThread edt = eventQueue.getDispatchThread(); - edt.removeEventFilter(modalFilter); - } - } } } finally { if (predictedFocusOwner != null) { @@ -1482,8 +1450,7 @@ public class Dialog extends Window { return getDocumentRoot() == w.getDocumentRoot(); } case APPLICATION_MODAL: - return !w.isModalExcluded(ModalExclusionType.APPLICATION_EXCLUDE) && - (appContext == w.appContext); + return !w.isModalExcluded(ModalExclusionType.APPLICATION_EXCLUDE); case TOOLKIT_MODAL: return !w.isModalExcluded(ModalExclusionType.TOOLKIT_EXCLUDE); } diff --git a/src/java.desktop/share/classes/sun/awt/util/PerformanceLogger.java b/src/java.desktop/share/classes/sun/awt/util/PerformanceLogger.java index 4a1dde6538d..cf0ff1d9958 100644 --- a/src/java.desktop/share/classes/sun/awt/util/PerformanceLogger.java +++ b/src/java.desktop/share/classes/sun/awt/util/PerformanceLogger.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 @@ -43,11 +43,6 @@ import java.io.Writer; * for setting and getting all times and for doing whatever * analysis is interesting; this class is merely a central container * for those timing values. - * Note that, due to the variables in this class being static, - * use of particular time values by multiple AppContexts will cause - * confusing results. For example, if two contexts run - * simultaneously, the initTime for those will collide - * and the results may be undefined. *

* To automatically track startup performance in an app * use the command-line parameter sun.perflog as follows:
diff --git a/src/java.desktop/share/classes/sun/font/ScriptRunData.java b/src/java.desktop/share/classes/sun/font/ScriptRunData.java index 1f1f6b44d59..4e13fb5bbd0 100644 --- a/src/java.desktop/share/classes/sun/font/ScriptRunData.java +++ b/src/java.desktop/share/classes/sun/font/ScriptRunData.java @@ -38,11 +38,12 @@ public final class ScriptRunData { private static final int CHAR_START = 0; private static final int CHAR_LIMIT = 0x110000; - private static int cache = 0; + private static volatile int cache = 0; public static int getScript(int cp) { + int lcache = cache; // optimize for runs of characters in the same script - if (cp >= data[cache] && cp < data[cache+2]) { - return data[cache+1]; + if (cp >= data[lcache] && cp < data[lcache+2]) { + return data[lcache+1]; } if ((cp >= CHAR_START) && (cp < CHAR_LIMIT)) { int probe = dataPower; diff --git a/src/java.desktop/unix/classes/sun/awt/X11/InfoWindow.java b/src/java.desktop/unix/classes/sun/awt/X11/InfoWindow.java index bab0f34f90f..c03eae62225 100644 --- a/src/java.desktop/unix/classes/sun/awt/X11/InfoWindow.java +++ b/src/java.desktop/unix/classes/sun/awt/X11/InfoWindow.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2009, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -31,6 +31,7 @@ import java.awt.Color; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; +import java.awt.EventQueue; import java.awt.Font; import java.awt.Frame; import java.awt.GridLayout; @@ -91,7 +92,7 @@ public abstract class InfoWindow extends Window { // Must be executed on EDT. @SuppressWarnings("deprecation") protected void show(Point corner, int indent) { - assert SunToolkit.isDispatchThreadForAppContext(this); + assert EventQueue.isDispatchThread(); pack(); @@ -464,7 +465,7 @@ public abstract class InfoWindow extends Window { ActionEvent aev = new ActionEvent(target, ActionEvent.ACTION_PERFORMED, liveArguments.getActionCommand(), e.getWhen(), e.getModifiers()); - XToolkit.postEvent(XToolkit.targetToAppContext(aev.getSource()), aev); + XToolkit.postEvent(aev); } } } diff --git a/src/java.desktop/unix/classes/sun/awt/X11/XBaseMenuWindow.java b/src/java.desktop/unix/classes/sun/awt/X11/XBaseMenuWindow.java index f8fcb30d8d8..9512d1d0351 100644 --- a/src/java.desktop/unix/classes/sun/awt/X11/XBaseMenuWindow.java +++ b/src/java.desktop/unix/classes/sun/awt/X11/XBaseMenuWindow.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -114,8 +114,6 @@ public abstract class XBaseMenuWindow extends XWindow { protected Point grabInputPoint = null; protected boolean hasPointerMoved = false; - private AppContext disposeAppContext; - /************************************************ * * Mapping data @@ -175,8 +173,6 @@ public abstract class XBaseMenuWindow extends XWindow { XBaseMenuWindow() { super(new XCreateWindowParams(new Object[] { DELAYED, Boolean.TRUE})); - - disposeAppContext = AppContext.getAppContext(); } /************************************************ @@ -920,7 +916,7 @@ public abstract class XBaseMenuWindow extends XWindow { public void dispose() { setDisposed(true); - SunToolkit.invokeLaterOnAppContext(disposeAppContext, new Runnable() { + SunToolkit.invokeLater(new Runnable() { public void run() { doDispose(); } diff --git a/src/java.desktop/unix/classes/sun/awt/X11/XEmbedChildProxyPeer.java b/src/java.desktop/unix/classes/sun/awt/X11/XEmbedChildProxyPeer.java index efae47d6f23..03593761111 100644 --- a/src/java.desktop/unix/classes/sun/awt/X11/XEmbedChildProxyPeer.java +++ b/src/java.desktop/unix/classes/sun/awt/X11/XEmbedChildProxyPeer.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 @@ -201,7 +201,7 @@ public final class XEmbedChildProxyPeer implements ComponentPeer, XEventDispatch public void updateCursorImmediately() {} void postEvent(AWTEvent event) { - XToolkit.postEvent(XToolkit.targetToAppContext(proxy), event); + XToolkit.postEvent(event); } boolean simulateMotifRequestFocus(Component lightweightChild, boolean temporary, @@ -323,9 +323,9 @@ public final class XEmbedChildProxyPeer implements ComponentPeer, XEventDispatch } void childResized() { - XToolkit.postEvent(XToolkit.targetToAppContext(proxy), new ComponentEvent(proxy, ComponentEvent.COMPONENT_RESIZED)); + XToolkit.postEvent(new ComponentEvent(proxy, ComponentEvent.COMPONENT_RESIZED)); container.childResized(proxy); -// XToolkit.postEvent(XToolkit.targetToAppContext(proxy), new InvocationEvent(proxy, new Runnable() { +// XToolkit.postEvent(new InvocationEvent(proxy, new Runnable() { // public void run() { // getTopLevel(proxy).invalidate(); // getTopLevel(proxy).pack(); diff --git a/src/java.desktop/unix/classes/sun/awt/X11/XTaskbarPeer.java b/src/java.desktop/unix/classes/sun/awt/X11/XTaskbarPeer.java index 7f0629e101e..cb80f0abd0e 100644 --- a/src/java.desktop/unix/classes/sun/awt/X11/XTaskbarPeer.java +++ b/src/java.desktop/unix/classes/sun/awt/X11/XTaskbarPeer.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 @@ -152,7 +152,7 @@ final class XTaskbarPeer implements TaskbarPeer { mi.getActionCommand()); try { XToolkit.awtLock(); - XToolkit.postEvent(XToolkit.targetToAppContext(ae.getSource()), ae); + XToolkit.postEvent(ae); } finally { XToolkit.awtUnlock(); } 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 5dcd1b763e1..1ec0039febd 100644 --- a/src/java.desktop/unix/classes/sun/awt/X11/XToolkit.java +++ b/src/java.desktop/unix/classes/sun/awt/X11/XToolkit.java @@ -625,14 +625,8 @@ public final class XToolkit extends UNIXToolkit implements Runnable { while(true) { // Fix for 6829923: we should gracefully handle toolkit thread interruption if (Thread.currentThread().isInterrupted()) { - // We expect interruption from the AppContext.dispose() method only. // If the thread is interrupted from another place, let's skip it - // for compatibility reasons. Probably some time later we'll remove - // the check for AppContext.isDisposed() and will unconditionally - // break the loop here. - if (AppContext.getAppContext().isDisposed()) { - break; - } + // for compatibility reasons. } awtLock(); try { @@ -2054,14 +2048,6 @@ public final class XToolkit extends UNIXToolkit implements Runnable { (exclusionType == Dialog.ModalExclusionType.TOOLKIT_EXCLUDE); } - static EventQueue getEventQueue(Object target) { - AppContext appContext = targetToAppContext(target); - if (appContext != null) { - return (EventQueue)appContext.get(AppContext.EVENT_QUEUE_KEY); - } - return null; - } - static void removeSourceEvents(EventQueue queue, Object source, boolean removeAllEvents) { diff --git a/src/java.desktop/unix/classes/sun/awt/X11/XTrayIconPeer.java b/src/java.desktop/unix/classes/sun/awt/X11/XTrayIconPeer.java index 9f0ac241f5b..5ab97125991 100644 --- a/src/java.desktop/unix/classes/sun/awt/X11/XTrayIconPeer.java +++ b/src/java.desktop/unix/classes/sun/awt/X11/XTrayIconPeer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2005, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2005, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -269,7 +269,7 @@ public final class XTrayIconPeer implements TrayIconPeer, @Override public void dispose() { - if (SunToolkit.isDispatchThreadForAppContext(target)) { + if (EventQueue.isDispatchThread()) { disposeOnEDT(); } else { try { @@ -329,7 +329,7 @@ public final class XTrayIconPeer implements TrayIconPeer, } }; - if (!SunToolkit.isDispatchThreadForAppContext(target)) { + if (!EventQueue.isDispatchThread()) { SunToolkit.executeOnEventHandlerThread(target, r); } else { r.run(); @@ -355,7 +355,7 @@ public final class XTrayIconPeer implements TrayIconPeer, if (isDisposed()) return; - assert SunToolkit.isDispatchThreadForAppContext(target); + assert EventQueue.isDispatchThread(); PopupMenu newPopup = target.getPopupMenu(); if (popup != newPopup) { @@ -476,7 +476,7 @@ public final class XTrayIconPeer implements TrayIconPeer, // other class tries to cast source field to Component). // We already filter DRAG events out (CR 6565779). e.setSource(xtiPeer.target); - XToolkit.postEvent(XToolkit.targetToAppContext(e.getSource()), e); + XToolkit.postEvent(e); } @Override @SuppressWarnings("deprecation") @@ -487,7 +487,7 @@ public final class XTrayIconPeer implements TrayIconPeer, ActionEvent aev = new ActionEvent(xtiPeer.target, ActionEvent.ACTION_PERFORMED, xtiPeer.target.getActionCommand(), e.getWhen(), e.getModifiers()); - XToolkit.postEvent(XToolkit.targetToAppContext(aev.getSource()), aev); + XToolkit.postEvent(aev); } if (xtiPeer.balloon.isVisible()) { xtiPeer.balloon.hide(); diff --git a/src/java.desktop/unix/classes/sun/awt/X11InputMethodBase.java b/src/java.desktop/unix/classes/sun/awt/X11InputMethodBase.java index d9eaa9629d1..3aa1624a1dd 100644 --- a/src/java.desktop/unix/classes/sun/awt/X11InputMethodBase.java +++ b/src/java.desktop/unix/classes/sun/awt/X11InputMethodBase.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2025, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -434,7 +434,7 @@ public abstract class X11InputMethodBase extends InputMethodAdapter { if (source != null) { InputMethodEvent event = new InputMethodEvent(source, id, when, text, committedCharacterCount, caret, visiblePosition); - SunToolkit.postEvent(SunToolkit.targetToAppContext(source), (AWTEvent)event); + SunToolkit.postEvent((AWTEvent)event); } } diff --git a/src/java.desktop/unix/native/common/awt/awt_Component.h b/src/java.desktop/unix/native/common/awt/awt_Component.h index 089691a8a4d..7ef0a0de94b 100644 --- a/src/java.desktop/unix/native/common/awt/awt_Component.h +++ b/src/java.desktop/unix/native/common/awt/awt_Component.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2011, 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 @@ -38,7 +38,6 @@ struct ComponentIDs { jfieldID graphicsConfig; jfieldID name; jfieldID isProxyActive; - jfieldID appContext; jmethodID getParent; jmethodID getLocationOnScreen; }; diff --git a/src/java.desktop/unix/native/libawt_xawt/xawt/XToolkit.c b/src/java.desktop/unix/native/libawt_xawt/xawt/XToolkit.c index d5f3130386d..c84aa4ff8f9 100644 --- a/src/java.desktop/unix/native/libawt_xawt/xawt/XToolkit.c +++ b/src/java.desktop/unix/native/libawt_xawt/xawt/XToolkit.c @@ -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 @@ -195,10 +195,6 @@ Java_java_awt_Component_initIDs "Z"); CHECK_NULL(componentIDs.isProxyActive); - componentIDs.appContext = - (*env)->GetFieldID(env, cls, "appContext", - "Lsun/awt/AppContext;"); - (*env)->DeleteLocalRef(env, keyclass); } diff --git a/src/java.desktop/windows/native/libawt/windows/awt_Component.cpp b/src/java.desktop/windows/native/libawt/windows/awt_Component.cpp index b67c5dfcf8d..eca8290a1aa 100644 --- a/src/java.desktop/windows/native/libawt/windows/awt_Component.cpp +++ b/src/java.desktop/windows/native/libawt/windows/awt_Component.cpp @@ -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 @@ -178,7 +178,6 @@ jfieldID AwtComponent::parentID; jfieldID AwtComponent::graphicsConfigID; jfieldID AwtComponent::peerGCID; jfieldID AwtComponent::focusableID; -jfieldID AwtComponent::appContextID; jfieldID AwtComponent::cursorID; jfieldID AwtComponent::hwndID; @@ -6573,11 +6572,6 @@ Java_java_awt_Component_initIDs(JNIEnv *env, jclass cls) DASSERT(AwtComponent::focusableID); CHECK_NULL(AwtComponent::focusableID); - AwtComponent::appContextID = env->GetFieldID(cls, "appContext", - "Lsun/awt/AppContext;"); - DASSERT(AwtComponent::appContextID); - CHECK_NULL(AwtComponent::appContextID); - AwtComponent::peerGCID = env->GetFieldID(peerCls, "winGraphicsConfig", "Lsun/awt/Win32GraphicsConfig;"); DASSERT(AwtComponent::peerGCID); diff --git a/src/java.desktop/windows/native/libawt/windows/awt_Component.h b/src/java.desktop/windows/native/libawt/windows/awt_Component.h index 740eb8c72f9..1246f6cb06e 100644 --- a/src/java.desktop/windows/native/libawt/windows/awt_Component.h +++ b/src/java.desktop/windows/native/libawt/windows/awt_Component.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2026, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -112,7 +112,6 @@ public: static jfieldID graphicsConfigID; static jfieldID peerGCID; static jfieldID focusableID; - static jfieldID appContextID; static jfieldID hwndID; static jmethodID getFontMID; diff --git a/src/jdk.compiler/share/classes/com/sun/source/util/DocSourcePositions.java b/src/jdk.compiler/share/classes/com/sun/source/util/DocSourcePositions.java index 520943c464d..d0afd126bf6 100644 --- a/src/jdk.compiler/share/classes/com/sun/source/util/DocSourcePositions.java +++ b/src/jdk.compiler/share/classes/com/sun/source/util/DocSourcePositions.java @@ -28,6 +28,7 @@ package com.sun.source.util; import com.sun.source.doctree.DocCommentTree; import com.sun.source.doctree.DocTree; import com.sun.source.tree.CompilationUnitTree; +import com.sun.source.tree.Tree; /** * Provides methods to obtain the position of a DocTree within a javadoc comment. @@ -59,8 +60,31 @@ public interface DocSourcePositions extends SourcePositions { * position is being sought * @param tree tree for which a position is sought * @return the start position of tree + * @deprecated use {@link #getStartPosition(DocCommentTree, DocTree)} instead */ - long getStartPosition(CompilationUnitTree file, DocCommentTree comment, DocTree tree); + @Deprecated(since = "27", forRemoval = true) + default long getStartPosition(CompilationUnitTree file, DocCommentTree comment, DocTree tree) { + return getStartPosition(comment, tree); + } + + /** + * {@return the starting position of the given {@link Tree}. If the starting position is not available, returns + * {@link javax.tools.Diagnostic#NOPOS}} + * + *

The given tree should be under the given comment tree. The returned position must be at the start of the + * yield of this tree, that is for any sub-tree of this tree, the following must hold: + * + *

+ * {@code getStartPosition(comment, tree) <= getStartPosition(comment, subtree)} or
+ * {@code getStartPosition(comment, tree) == NOPOS} or
+ * {@code getStartPosition(comment, subtree) == NOPOS} + *

+ * + * @param comment the comment tree that encloses the tree for which the + * position is being sought + * @param tree tree for which a position is sought + */ + long getStartPosition(DocCommentTree comment, DocTree tree); /** * Returns the ending position of the tree within the comment within the file. If tree is not found within @@ -91,7 +115,38 @@ public interface DocSourcePositions extends SourcePositions { * position is being sought * @param tree tree for which a position is sought * @return the end position of tree + * @deprecated use {@link #getEndPosition(DocCommentTree, DocTree)} instead */ - long getEndPosition(CompilationUnitTree file, DocCommentTree comment, DocTree tree); + @Deprecated(since = "27", forRemoval = true) + default long getEndPosition(CompilationUnitTree file, DocCommentTree comment, DocTree tree) { + return getEndPosition(comment, tree); + } + + /** + * {@return the ending position of the given {@link Tree}. If the ending position is not available, returns + * {@link javax.tools.Diagnostic#NOPOS}} + * + *

The given tree should be under the given comment tree. The returned position must be at the end of the yield + * of this tree, that is for any sub-tree of this tree, the following must hold: + * + *

+ * {@code getEndPosition(comment, tree) >= getEndPosition(comment, subtree)} or
+ * {@code getEndPosition(comment, tree) == NOPOS} or
+ * {@code getEndPosition(comment, subtree) == NOPOS} + *

+ * + * In addition, the following must hold: + * + *

+ * {@code getStartPosition(comment, tree) <= getEndPosition(comment, tree)} or
+ * {@code getStartPosition(comment, tree) == NOPOS} or
+ * {@code getEndPosition(comment, tree) == NOPOS} + *

+ * + * @param comment the comment tree that encloses the tree for which the + * position is being sought + * @param tree tree for which a position is sought + */ + long getEndPosition(DocCommentTree comment, DocTree tree); } diff --git a/src/jdk.compiler/share/classes/com/sun/source/util/SourcePositions.java b/src/jdk.compiler/share/classes/com/sun/source/util/SourcePositions.java index b6112fd32e6..460f4f2a1ce 100644 --- a/src/jdk.compiler/share/classes/com/sun/source/util/SourcePositions.java +++ b/src/jdk.compiler/share/classes/com/sun/source/util/SourcePositions.java @@ -53,8 +53,29 @@ public interface SourcePositions { * @param file CompilationUnit in which to find tree * @param tree tree for which a position is sought * @return the start position of tree + * @deprecated use {@link #getStartPosition(Tree)} instead */ - long getStartPosition(CompilationUnitTree file, Tree tree); + @Deprecated(since = "27", forRemoval = true) + default long getStartPosition(CompilationUnitTree file, Tree tree) { + return getStartPosition(tree); + } + + /** + * {@return the starting position of the given {@link Tree}, or if the starting position is not available, returns + * {@link javax.tools.Diagnostic#NOPOS}} + * + *

The returned position must be at the start of the yield of this tree, that is for any sub-tree of this tree, + * the following must hold: + * + *

+ * {@code getStartPosition(tree) <= getStartPosition(subtree)} or
+ * {@code getStartPosition(tree) == NOPOS} or
+ * {@code getStartPosition(subtree) == NOPOS} + *

+ * + * @param tree tree for which a position is sought + */ + long getStartPosition(Tree tree); /** * Returns the ending position of tree within file. If tree is not found within @@ -80,7 +101,35 @@ public interface SourcePositions { * @param file CompilationUnit in which to find tree * @param tree tree for which a position is sought * @return the end position of tree + * @deprecated use {@link #getEndPosition(Tree)} instead */ - long getEndPosition(CompilationUnitTree file, Tree tree); + @Deprecated(since = "27", forRemoval = true) + default long getEndPosition(CompilationUnitTree file, Tree tree) { + return getEndPosition(tree); + } + /** + * {@return the ending position of the given {@link Tree}. If the ending position is not available, + * returns {@link javax.tools.Diagnostic#NOPOS}} + * + *

The returned position must be at the end of the yield of this tree, that is for any sub-tree of this tree, + * the following must hold: + * + *

+ * {@code getEndPosition(tree) >= getEndPosition(subtree)} or
+ * {@code getEndPosition(tree) == NOPOS} or
+ * {@code getEndPosition(subtree) == NOPOS} + *

+ * + * In addition, the following must hold: + * + *

+ * {@code getStartPosition(tree) <= getEndPosition(tree)} or
+ * {@code getStartPosition(tree) == NOPOS} or
+ * {@code getEndPosition(tree) == NOPOS} + *

+ * + * @param tree tree for which a position is sought + */ + long getEndPosition(Tree tree); } diff --git a/src/jdk.compiler/share/classes/com/sun/tools/javac/api/JavacTrees.java b/src/jdk.compiler/share/classes/com/sun/tools/javac/api/JavacTrees.java index ecd7f5b101a..41dd904bc8a 100644 --- a/src/jdk.compiler/share/classes/com/sun/tools/javac/api/JavacTrees.java +++ b/src/jdk.compiler/share/classes/com/sun/tools/javac/api/JavacTrees.java @@ -233,24 +233,24 @@ public class JavacTrees extends DocTrees { public DocSourcePositions getSourcePositions() { return new DocSourcePositions() { @Override @DefinedBy(Api.COMPILER_TREE) - public long getStartPosition(CompilationUnitTree file, Tree tree) { + public long getStartPosition(Tree tree) { return TreeInfo.getStartPos((JCTree) tree); } @Override @DefinedBy(Api.COMPILER_TREE) - public long getEndPosition(CompilationUnitTree file, Tree tree) { + public long getEndPosition(Tree tree) { return TreeInfo.getEndPos((JCTree) tree); } @Override @DefinedBy(Api.COMPILER_TREE) - public long getStartPosition(CompilationUnitTree file, DocCommentTree comment, DocTree tree) { + public long getStartPosition(DocCommentTree comment, DocTree tree) { DCDocComment dcComment = (DCDocComment) comment; DCTree dcTree = (DCTree) tree; return dcComment.getSourcePosition(dcTree.getStartPosition()); } @Override @DefinedBy(Api.COMPILER_TREE) - public long getEndPosition(CompilationUnitTree file, DocCommentTree comment, DocTree tree) { + public long getEndPosition(DocCommentTree comment, DocTree tree) { DCDocComment dcComment = (DCDocComment) comment; DCTree dcTree = (DCTree) tree; return dcComment.getSourcePosition(dcTree.getEndPosition()); diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/Utils.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/Utils.java index ddadd33a18b..0b3b767ea39 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/Utils.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclets/toolkit/util/Utils.java @@ -1618,7 +1618,7 @@ public class Utils { CompilationUnitTree cu = path.getCompilationUnit(); LineMap lineMap = cu.getLineMap(); DocSourcePositions spos = docTrees.getSourcePositions(); - long pos = spos.getStartPosition(cu, path.getLeaf()); + long pos = spos.getStartPosition(path.getLeaf()); return lineMap.getLineNumber(pos); } diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclint/Env.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclint/Env.java index f2940963f17..ba7f68cccf6 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclint/Env.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/doclint/Env.java @@ -211,7 +211,7 @@ public class Env { long getStartPos(TreePath p) { SourcePositions sp = trees.getSourcePositions(); - return sp.getStartPosition(p.getCompilationUnit(), p.getLeaf()); + return sp.getStartPosition(p.getLeaf()); } boolean shouldCheck(CompilationUnitTree unit) { diff --git a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/JavadocLog.java b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/JavadocLog.java index b3bf1c14cb1..39bc58dff59 100644 --- a/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/JavadocLog.java +++ b/src/jdk.javadoc/share/classes/jdk/javadoc/internal/tool/JavadocLog.java @@ -582,9 +582,9 @@ public class JavadocLog extends Log implements Reporter { } CompilationUnitTree compUnit = tp.getCompilationUnit(); JCTree tree = (JCTree) tp.getLeaf(); - int start = (int) posns.getStartPosition(compUnit, tree); + int start = (int) posns.getStartPosition(tree); int pos = tree.getPreferredPosition(); - int end = (int) posns.getEndPosition(compUnit, tree); + int end = (int) posns.getEndPosition(tree); return createDiagnosticPosition(tree, start, pos, end); } diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/EnvironmentProvider.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/EnvironmentProvider.java new file mode 100644 index 00000000000..de364129256 --- /dev/null +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/EnvironmentProvider.java @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package jdk.jpackage.internal; + +public interface EnvironmentProvider { + + String getProperty(String propertyName); + + String getenv(String envVarName); + + public static EnvironmentProvider DEFAULT = new EnvironmentProvider() { + + @Override + public String getenv(String envVarName) { + return System.getenv(envVarName); + } + + @Override + public String getProperty(String propertyName) { + return System.getProperty(propertyName); + } + }; +} diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Globals.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Globals.java index 0128d050c25..2fc0046fad5 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Globals.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/Globals.java @@ -65,6 +65,14 @@ public final class Globals { return this; } + public EnvironmentProvider system() { + return this.findProperty(EnvironmentProvider.class).orElse(EnvironmentProvider.DEFAULT); + } + + public Globals system(EnvironmentProvider v) { + return setProperty(EnvironmentProvider.class, v); + } + Log.Logger logger() { return logger; } diff --git a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/PathUtils.java b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/PathUtils.java index a8944a67ae0..707dd2784ce 100644 --- a/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/PathUtils.java +++ b/src/jdk.jpackage/share/classes/jdk/jpackage/internal/util/PathUtils.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 @@ -24,6 +24,7 @@ */ package jdk.jpackage.internal.util; +import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.util.Objects; import java.util.Optional; @@ -31,18 +32,26 @@ import java.util.function.UnaryOperator; public final class PathUtils { + private PathUtils() { + } + public static String getSuffix(Path path) { String filename = replaceSuffix(path.getFileName(), null).toString(); return path.getFileName().toString().substring(filename.length()); } public static Path addSuffix(Path path, String suffix) { + Objects.requireNonNull(path); + Objects.requireNonNull(suffix); + Path parent = path.getParent(); String filename = path.getFileName().toString() + suffix; return parent != null ? parent.resolve(filename) : Path.of(filename); } public static Path replaceSuffix(Path path, String suffix) { + Objects.requireNonNull(path); + Path parent = path.getParent(); String filename = path.getFileName().toString().replaceAll("\\.[^.]*$", "") + Optional.ofNullable(suffix).orElse(""); @@ -59,18 +68,22 @@ public final class PathUtils { } public static Path normalizedAbsolutePath(Path path) { - if (path != null) { + return mapNullablePath(_ -> { return path.normalize().toAbsolutePath(); - } else { - return null; - } + }, path); } public static String normalizedAbsolutePathString(Path path) { - if (path != null) { - return normalizedAbsolutePath(path).toString(); - } else { - return null; - } + return Optional.ofNullable(normalizedAbsolutePath(path)).map(Path::toString).orElse(null); + } + + public static Optional asPath(String value) { + return Optional.ofNullable(value).map(v -> { + try { + return Path.of(v); + } catch (InvalidPathException ex) { + return null; + } + }); } } diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixTool.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixTool.java index c4f8610312a..d0c5e6ca3b0 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixTool.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixTool.java @@ -23,14 +23,14 @@ * questions. */ package jdk.jpackage.internal; +import static java.util.stream.Collectors.groupingBy; +import static java.util.stream.Collectors.toMap; +import static java.util.stream.Collectors.toSet; import java.io.IOException; import java.nio.file.FileSystems; import java.nio.file.Files; -import java.nio.file.InvalidPathException; import java.nio.file.Path; -import java.nio.file.PathMatcher; -import java.text.MessageFormat; import java.util.Comparator; import java.util.List; import java.util.Map; @@ -38,12 +38,13 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.function.Function; -import java.util.stream.Collectors; +import java.util.function.Supplier; import java.util.stream.Stream; import jdk.jpackage.internal.WixToolset.WixToolsetType; import jdk.jpackage.internal.model.ConfigException; import jdk.jpackage.internal.model.DottedVersion; import jdk.jpackage.internal.util.PathUtils; +import jdk.jpackage.internal.util.Slot; /** * WiX tool. @@ -58,16 +59,20 @@ public enum WixTool { this.minimalVersion = minimalVersion; } - interface ToolInfo { + Path fileName() { + return toolFileName; + } + + sealed interface ToolInfo { Path path(); DottedVersion version(); } - interface CandleInfo extends ToolInfo { + sealed interface CandleInfo extends ToolInfo { boolean fips(); } - private record DefaultToolInfo(Path path, DottedVersion version) implements ToolInfo { + record DefaultToolInfo(Path path, DottedVersion version) implements ToolInfo { DefaultToolInfo { Objects.requireNonNull(path); Objects.requireNonNull(version); @@ -76,9 +81,14 @@ public enum WixTool { DefaultToolInfo(Path path, String version) { this(path, DottedVersion.lazy(version)); } + + @Override + public String toString() { + return String.format("%s|ver=%s", path, version); + } } - private record DefaultCandleInfo(Path path, DottedVersion version, boolean fips) implements CandleInfo { + record DefaultCandleInfo(Path path, DottedVersion version, boolean fips) implements CandleInfo { DefaultCandleInfo { Objects.requireNonNull(path); Objects.requireNonNull(version); @@ -87,25 +97,42 @@ public enum WixTool { DefaultCandleInfo(ToolInfo info, boolean fips) { this(info.path(), info.version(), fips); } + + @Override + public String toString() { + var sb = new StringBuffer(); + sb.append(path); + if (fips) { + sb.append("|fips"); + } + sb.append("|ver=").append(version); + return sb.toString(); + } } static WixToolset createToolset() { + return createToolset(WixTool::findWixInstallDirs, true); + } + + static WixToolset createToolset(Supplier> wixInstallDirs, boolean searchInPath) { + Function, Map> conv = lookupResults -> { - return lookupResults.stream().filter(ToolLookupResult::isValid).collect(Collectors. - groupingBy(lookupResult -> { + return lookupResults.stream().filter(ToolLookupResult::isValid).collect(groupingBy(lookupResult -> { return lookupResult.info().version().toString(); })).values().stream().filter(sameVersionLookupResults -> { - Set sameVersionTools = sameVersionLookupResults.stream().map( - ToolLookupResult::tool).collect(Collectors.toSet()); - if (sameVersionTools.equals(Set.of(Candle3)) || sameVersionTools.equals(Set.of( - Light3))) { + var sameVersionTools = sameVersionLookupResults.stream() + .map(ToolLookupResult::tool) + .collect(toSet()); + if (sameVersionTools.equals(Set.of(Candle3)) || sameVersionTools.equals(Set.of(Light3))) { // There is only one tool from WiX v3 toolset of some version available. Discard it. return false; } else { return true; } - }).flatMap(List::stream).collect(Collectors.toMap(ToolLookupResult::tool, - ToolLookupResult::info, (ToolInfo x, ToolInfo y) -> { + }).flatMap(List::stream).collect(toMap( + ToolLookupResult::tool, + ToolLookupResult::info, + (ToolInfo x, ToolInfo y) -> { return Stream.of(x, y).sorted(Comparator.comparing((ToolInfo toolInfo) -> { return toolInfo.version().toComponentsString(); }).reversed()).findFirst().get(); @@ -115,58 +142,53 @@ public enum WixTool { Function, Optional> createToolset = lookupResults -> { var tools = conv.apply(lookupResults); // Try to build a toolset found in the PATH and in known locations. - return Stream.of(WixToolsetType.values()).map(toolsetType -> { - return WixToolset.create(toolsetType.getTools(), tools); - }).filter(Objects::nonNull).findFirst(); + return Stream.of(WixToolsetType.values()).flatMap(toolsetType -> { + return WixToolset.create(toolsetType, tools).stream(); + }).findFirst(); }; - var toolsInPath = Stream.of(values()).map(tool -> { - return ToolLookupResult.lookup(tool, Optional.empty()); - }).filter(Optional::isPresent).map(Optional::get).toList(); + final List toolsInPath; + if (searchInPath) { + toolsInPath = Stream.of(values()).flatMap(tool -> { + return ToolLookupResult.lookup(tool, Optional.empty()).stream(); + }).toList(); + } else { + toolsInPath = List.of(); + } // Try to build a toolset from tools in the PATH first. - var toolset = createToolset.apply(toolsInPath); - if (toolset.isPresent()) { - return toolset.get(); - } + var toolset = createToolset.apply(toolsInPath).orElseGet(() -> { + // Look up for WiX tools in known locations. + var toolsInKnownWiXDirs = wixInstallDirs.get().stream().flatMap(dir -> { + return Stream.of(values()).flatMap(tool -> { + return ToolLookupResult.lookup(tool, Optional.of(dir)).stream(); + }); + }).toList(); - // Look up for WiX tools in known locations. - var toolsInKnownWiXDirs = findWixInstallDirs().stream().map(dir -> { - return Stream.of(values()).map(tool -> { - return ToolLookupResult.lookup(tool, Optional.of(dir)); + // Build a toolset found in the PATH and in known locations. + var allValidFoundTools = Stream.of(toolsInPath, toolsInKnownWiXDirs) + .flatMap(List::stream) + .filter(ToolLookupResult::isValid) + .toList(); + + return createToolset.apply(allValidFoundTools).orElseThrow(() -> { + return new ConfigException( + I18N.getString("error.no-wix-tools"), + I18N.getString("error.no-wix-tools.advice")); }); - }).flatMap(Function.identity()).filter(Optional::isPresent).map(Optional::get).toList(); + }); - // Build a toolset found in the PATH and in known locations. - var allFoundTools = Stream.of(toolsInPath, toolsInKnownWiXDirs).flatMap(List::stream).filter( - ToolLookupResult::isValid).toList(); - toolset = createToolset.apply(allFoundTools); - if (toolset.isPresent()) { - return toolset.get(); - } else if (allFoundTools.isEmpty()) { - throw new ConfigException(I18N.getString("error.no-wix-tools"), I18N.getString( - "error.no-wix-tools.advice")); - } else { - var toolOldVerErr = allFoundTools.stream().map(lookupResult -> { - if (lookupResult.versionTooOld) { - return new ConfigException(MessageFormat.format(I18N.getString( - "message.wrong-tool-version"), lookupResult.info().path(), - lookupResult.info().version(), lookupResult.tool().minimalVersion), - I18N.getString("error.no-wix-tools.advice")); - } else { - return null; - } - }).filter(Objects::nonNull).findAny(); - if (toolOldVerErr.isPresent()) { - throw toolOldVerErr.get(); - } else { - throw new ConfigException(I18N.getString("error.no-wix-tools"), I18N.getString( - "error.no-wix-tools.advice")); - } - } + return toolset; } - private record ToolLookupResult(WixTool tool, ToolInfo info, boolean versionTooOld) { + static List findWixInstallDirs() { + return Stream.of( + findWixCurrentInstallDirs(), + findWix3InstallDirs() + ).flatMap(List::stream).toList(); + } + + private record ToolLookupResult(WixTool tool, ToolInfo info) { ToolLookupResult { Objects.requireNonNull(tool); @@ -177,58 +199,59 @@ public enum WixTool { Objects.requireNonNull(tool); Objects.requireNonNull(lookupDir); - final Path toolPath = lookupDir.map(p -> p.resolve( - tool.toolFileName)).orElse(tool.toolFileName); + final Path toolPath = lookupDir.map(p -> { + return p.resolve(tool.toolFileName); + }).orElse(tool.toolFileName); - final boolean[] tooOld = new boolean[1]; - final String[] parsedVersion = new String[1]; + final var validator = new ToolValidator(toolPath).setMinimalVersion(tool.minimalVersion); - final var validator = new ToolValidator(toolPath) - .setMinimalVersion(tool.minimalVersion) - .setToolOldVersionErrorHandler((name, version) -> { - tooOld[0] = true; - return null; - }); - - final Function, String> versionParser; - - if (Set.of(Candle3, Light3).contains(tool)) { - final String printVersionArg; - if (tool == Candle3) { + final var printVersionArg = switch (tool) { + case Candle3 -> { // Add '-fips' to make "candle.exe" print help message and return // 0 exit code instead of returning error exit code and printing // "error CNDL0308 : The Federal Information Processing Standard (FIPS) appears to be enabled on the machine..." // error message if FIPS is enabled. // If FIPS is disabled, passing '-fips' parameter still makes // "candle.exe" print help message and return 0 exit code. - printVersionArg = "-fips"; - } else { - printVersionArg = "-?"; + yield "-fips"; } - validator.setCommandLine(printVersionArg); - versionParser = output -> { - String firstLineOfOutput = output.findFirst().orElse(""); - int separatorIdx = firstLineOfOutput.lastIndexOf(' '); - if (separatorIdx == -1) { - return null; - } - return firstLineOfOutput.substring(separatorIdx + 1); - }; - } else { - validator.setCommandLine("--version"); - versionParser = output -> { - return output.findFirst().orElse(""); - }; - } + case Light3 -> { + yield "-?"; + } + default -> { + yield "--version"; + } + }; + validator.setCommandLine(printVersionArg); + final Function, Optional> versionParser = switch (tool) { + case Candle3, Light3 -> { + yield output -> { + return output.findFirst().map(firstLineOfOutput -> { + int separatorIdx = firstLineOfOutput.lastIndexOf(' '); + if (separatorIdx == -1) { + return null; + } + return firstLineOfOutput.substring(separatorIdx + 1); + }); + }; + } + default -> { + yield output -> { + return output.findFirst(); + }; + } + }; + + final var parsedVersion = Slot.createEmpty(); validator.setVersionParser(output -> { - parsedVersion[0] = versionParser.apply(output); - return parsedVersion[0]; + versionParser.apply(output).ifPresent(parsedVersion::set); + return parsedVersion.find().orElse(null); }); if (validator.validate() == null) { // Tool found - ToolInfo info = new DefaultToolInfo(toolPath, parsedVersion[0]); + ToolInfo info = new DefaultToolInfo(toolPath, parsedVersion.get()); if (tool == Candle3) { // Detect FIPS mode var fips = false; @@ -242,63 +265,52 @@ public enum WixTool { } } } catch (IOException ex) { - Log.verbose(ex); } info = new DefaultCandleInfo(info, fips); } - return Optional.of(new ToolLookupResult(tool, info, tooOld[0])); + + return Optional.of(new ToolLookupResult(tool, info)); } else { return Optional.empty(); } } + boolean versionTooOld() { + return DottedVersion.compareComponents(info.version(), tool.minimalVersion) < 0; + } + boolean isValid() { - return !versionTooOld; + return !versionTooOld(); } } - private static Path getSystemDir(String envVar, String knownDir) { - return Optional - .ofNullable(getEnvVariableAsPath(envVar)) - .orElseGet(() -> Optional - .ofNullable(getEnvVariableAsPath("SystemDrive")) - .orElseGet(() -> Path.of("C:")).resolve(knownDir)); + private static Path getSystemDir(String envVar, Path knownDir) { + return getEnvVariableAsPath(envVar).orElseGet(() -> { + return getEnvVariableAsPath("SystemDrive").orElseGet(() -> { + return Path.of("C:"); + }).resolve(knownDir); + }); } - private static Path getEnvVariableAsPath(String envVar) { - String path = System.getenv(envVar); - if (path != null) { - try { - return Path.of(path); - } catch (InvalidPathException ex) { - Log.error(MessageFormat.format(I18N.getString( - "error.invalid-envvar"), envVar)); - } - } - return null; - } - - private static List findWixInstallDirs() { - return Stream.of(findWixCurrentInstallDirs(), findWix3InstallDirs()). - flatMap(List::stream).toList(); + private static Optional getEnvVariableAsPath(String envVar) { + Objects.requireNonNull(envVar); + return Optional.ofNullable(Globals.instance().system().getenv(envVar)).flatMap(PathUtils::asPath); } private static List findWixCurrentInstallDirs() { - return Stream.of(getEnvVariableAsPath("USERPROFILE"), Optional.ofNullable(System. - getProperty("user.home")).map(Path::of).orElse(null)).filter(Objects::nonNull).map( - path -> { - return path.resolve(".dotnet/tools"); - }).filter(Files::isDirectory).distinct().toList(); + return Stream.of( + getEnvVariableAsPath("USERPROFILE"), + Optional.ofNullable(Globals.instance().system().getProperty("user.home")).flatMap(PathUtils::asPath) + ).flatMap(Optional::stream).map(path -> { + return path.resolve(".dotnet/tools"); + }).filter(Files::isDirectory).distinct().toList(); } private static List findWix3InstallDirs() { - PathMatcher wixInstallDirMatcher = FileSystems.getDefault(). - getPathMatcher( - "glob:WiX Toolset v*"); + var wixInstallDirMatcher = FileSystems.getDefault().getPathMatcher("glob:WiX Toolset v*"); - Path programFiles = getSystemDir("ProgramFiles", "\\Program Files"); - Path programFilesX86 = getSystemDir("ProgramFiles(x86)", - "\\Program Files (x86)"); + var programFiles = getSystemDir("ProgramFiles", Path.of("Program Files")); + var programFilesX86 = getSystemDir("ProgramFiles(x86)", Path.of("Program Files (x86)")); // Returns list of WiX install directories ordered by WiX version number. // Newer versions go first. @@ -306,13 +318,11 @@ public enum WixTool { try (var paths = Files.walk(path, 1)) { return paths.toList(); } catch (IOException ex) { - Log.verbose(ex); - List empty = List.of(); - return empty; + return List.of(); } }).flatMap(List::stream) - .filter(path -> wixInstallDirMatcher.matches(path.getFileName())). - sorted(Comparator.comparing(Path::getFileName).reversed()) + .filter(path -> wixInstallDirMatcher.matches(path.getFileName())) + .sorted(Comparator.comparing(Path::getFileName).reversed()) .map(path -> path.resolve("bin")) .toList(); } diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixToolset.java b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixToolset.java index e7bdbede368..1694503a8c8 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixToolset.java +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/WixToolset.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 @@ -26,14 +26,20 @@ package jdk.jpackage.internal; import java.nio.file.Path; import java.util.Map; +import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.Stream; import jdk.jpackage.internal.model.DottedVersion; -final class WixToolset { +record WixToolset(Map tools) { - static enum WixToolsetType { + WixToolset { + tools = Map.copyOf(tools); + } + + enum WixToolsetType { // Wix v4+ Wix4(WixTool.Wix4), // Wix v3+ @@ -50,10 +56,6 @@ final class WixToolset { private final Set tools; } - private WixToolset(Map tools) { - this.tools = tools; - } - WixToolsetType getType() { return Stream.of(WixToolsetType.values()).filter(toolsetType -> { return toolsetType.getTools().equals(tools.keySet()); @@ -75,16 +77,19 @@ final class WixToolset { .anyMatch(WixTool.CandleInfo::fips); } - static WixToolset create(Set requiredTools, Map allTools) { + static Optional create(WixToolsetType type, Map allTools) { + Objects.requireNonNull(type); + Objects.requireNonNull(allTools); + + var requiredTools = type.getTools(); + var filteredTools = allTools.entrySet().stream().filter(e -> { return requiredTools.contains(e.getKey()); }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); if (filteredTools.keySet().equals(requiredTools)) { - return new WixToolset(filteredTools); + return Optional.of(new WixToolset(filteredTools)); } else { - return null; + return Optional.empty(); } } - - private final Map tools; } diff --git a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources.properties b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources.properties index 0f3dcab8260..a11a8a6b41e 100644 --- a/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources.properties +++ b/src/jdk.jpackage/windows/classes/jdk/jpackage/internal/resources/WinResources.properties @@ -36,8 +36,8 @@ resource.launcher-as-service-wix-file=Service installer WiX project file resource.wix-src-conv=XSLT stylesheet converting WiX sources from WiX v3 to WiX v4 format resource.installer-exe=installer executable -error.no-wix-tools=Can not find WiX tools. Was looking for WiX v3 light.exe and candle.exe or WiX v4/v5 wix.exe and none was found -error.no-wix-tools.advice=Download WiX 3.0 or later from https://wixtoolset.org and add it to the PATH. +error.no-wix-tools=No usable WiX Toolset installation found +error.no-wix-tools.advice=Install the latest WiX v3 from https://github.com/wixtoolset/wix3/releases or WiX v4+ from https://github.com/wixtoolset/wix/releases error.version-string-wrong-format.advice=Set value of --app-version parameter to a valid Windows Installer ProductVersion. error.msi-product-version-components=Version string [{0}] must have between 2 and 4 components. error.msi-product-version-major-out-of-range=Major version must be in the range [0, 255] @@ -56,7 +56,6 @@ error.missing-service-installer.advice=Add 'service-installer.exe' service insta message.icon-not-ico=The specified icon "{0}" is not an ICO file and will not be used. The default icon will be used in it's place. message.tool-version=Detected [{0}] version [{1}]. -message.wrong-tool-version=Detected [{0}] version {1} but version {2} is required. message.product-code=MSI ProductCode: {0}. message.upgrade-code=MSI UpgradeCode: {0}. message.preparing-msi-config=Preparing MSI config: {0}. diff --git a/src/jdk.jshell/share/classes/jdk/internal/shellsupport/doc/JavadocHelper.java b/src/jdk.jshell/share/classes/jdk/internal/shellsupport/doc/JavadocHelper.java index 7a1dd6c9877..112808420bc 100644 --- a/src/jdk.jshell/share/classes/jdk/internal/shellsupport/doc/JavadocHelper.java +++ b/src/jdk.jshell/share/classes/jdk/internal/shellsupport/doc/JavadocHelper.java @@ -543,9 +543,9 @@ public abstract class JavadocHelper implements AutoCloseable { for (DocTree t : inheritedText.get(0)) { start = Math.min(start, - sp.getStartPosition(null, inheritedDocTree, t) - offset); + sp.getStartPosition(inheritedDocTree, t) - offset); end = Math.max(end, - sp.getEndPosition(null, inheritedDocTree, t) - offset); + sp.getEndPosition(inheritedDocTree, t) - offset); } String text = end >= 0 ? inherited.substring((int) start, (int) end) : ""; @@ -559,8 +559,8 @@ public abstract class JavadocHelper implements AutoCloseable { } else { //replace the {@inheritDoc} with the full text from //the overridden method: - long inheritedStart = sp.getStartPosition(null, dcTree, node); - long inheritedEnd = sp.getEndPosition(null, dcTree, node); + long inheritedStart = sp.getStartPosition(dcTree, node); + long inheritedEnd = sp.getEndPosition(dcTree, node); int[] span = new int[] {(int) inheritedStart, (int) inheritedEnd}; replace.computeIfAbsent(span, s -> new ArrayList<>()) @@ -571,11 +571,11 @@ public abstract class JavadocHelper implements AutoCloseable { } @Override public Void visitLink(LinkTree node, Void p) { - if (sp.isRewrittenTree(null, dcTree, node)) { + if (sp.isRewrittenTree(dcTree, node)) { //this link is a synthetic rewritten link, replace //the original span with the new link: - int start = (int) sp.getStartPosition(null, dcTree, node); - int end = (int) sp.getEndPosition(null, dcTree, node); + int start = (int) sp.getStartPosition(dcTree, node); + int end = (int) sp.getEndPosition(dcTree, node); replace.computeIfAbsent(new int[] {start, end}, _ -> new ArrayList<>()) .add(node.toString()); @@ -601,7 +601,7 @@ public abstract class JavadocHelper implements AutoCloseable { //this tree) //if there is a newline immediately behind this tree, insert behind //the newline: - long endPos = sp.getEndPosition(null, dcTree, tree); + long endPos = sp.getEndPosition(dcTree, tree); if (endPos >= offset) { if (endPos - offset + 1 < docComment.length() && docComment.charAt((int) (endPos - offset + 1)) == '\n') { @@ -744,7 +744,7 @@ public abstract class JavadocHelper implements AutoCloseable { } }; DocCommentTree tree = trees.getDocCommentTree(fo); - offset += (int) trees.getSourcePositions().getStartPosition(null, tree, tree); + offset += (int) trees.getSourcePositions().getStartPosition(tree, tree); return Pair.of(tree, offset); } catch (URISyntaxException ex) { throw new IllegalStateException(ex); @@ -939,7 +939,7 @@ public abstract class JavadocHelper implements AutoCloseable { Iterable trees) { StringBuilder sourceBuilder = new StringBuilder(); List replaceSpans = new ArrayList<>(); - int currentSpanStart = (int) sp.getStartPosition(null, comment, trees.iterator().next()); + int currentSpanStart = (int) sp.getStartPosition(comment, trees.iterator().next()); DocTree lastTree = null; for (DocTree tree : trees) { @@ -958,8 +958,8 @@ public abstract class JavadocHelper implements AutoCloseable { } sourceBuilder.append(code); } else { - int treeStart = (int) sp.getStartPosition(null, comment, tree); - int treeEnd = (int) sp.getEndPosition(null, comment, tree); + int treeStart = (int) sp.getStartPosition(comment, tree); + int treeEnd = (int) sp.getEndPosition(comment, tree); replaceSpans.add(new int[] {currentSpanStart, treeStart}); currentSpanStart = treeEnd; sourceBuilder.append(PLACEHOLDER); @@ -967,7 +967,7 @@ public abstract class JavadocHelper implements AutoCloseable { lastTree = tree; } - int end = (int) sp.getEndPosition(null, comment, lastTree); + int end = (int) sp.getEndPosition(comment, lastTree); replaceSpans.add(new int[] {currentSpanStart, end}); @@ -1006,8 +1006,8 @@ public abstract class JavadocHelper implements AutoCloseable { } @Override - public long getStartPosition(CompilationUnitTree file, DocCommentTree comment, DocTree tree) { - ensureAdjustedSpansFilled(file, comment, tree); + public long getStartPosition(DocCommentTree comment, DocTree tree) { + ensureAdjustedSpansFilled(comment, tree); long[] adjusted = adjustedSpan.get(tree); @@ -1015,12 +1015,12 @@ public abstract class JavadocHelper implements AutoCloseable { return adjusted[0]; } - return delegate.getStartPosition(file, comment, tree); + return delegate.getStartPosition(comment, tree); } @Override - public long getEndPosition(CompilationUnitTree file, DocCommentTree comment, DocTree tree) { - ensureAdjustedSpansFilled(file, comment, tree); + public long getEndPosition(DocCommentTree comment, DocTree tree) { + ensureAdjustedSpansFilled(comment, tree); long[] adjusted = adjustedSpan.get(tree); @@ -1028,28 +1028,26 @@ public abstract class JavadocHelper implements AutoCloseable { return adjusted[1]; } - return delegate.getEndPosition(file, comment, tree); + return delegate.getEndPosition(comment, tree); } @Override - public long getStartPosition(CompilationUnitTree file, Tree tree) { - return delegate.getStartPosition(file, tree); + public long getStartPosition(Tree tree) { + return delegate.getStartPosition(tree); } @Override - public long getEndPosition(CompilationUnitTree file, Tree tree) { - return delegate.getEndPosition(file, tree); + public long getEndPosition(Tree tree) { + return delegate.getEndPosition(tree); } - boolean isRewrittenTree(CompilationUnitTree file, - DocCommentTree comment, + boolean isRewrittenTree(DocCommentTree comment, DocTree tree) { - ensureAdjustedSpansFilled(file, comment, tree); + ensureAdjustedSpansFilled(comment, tree); return rewrittenTrees.contains(tree); } - private void ensureAdjustedSpansFilled(CompilationUnitTree file, - DocCommentTree comment, + private void ensureAdjustedSpansFilled(DocCommentTree comment, DocTree tree) { if (tree.getKind() != DocTree.Kind.LINK && tree.getKind() != DocTree.Kind.LINK_PLAIN) { @@ -1057,7 +1055,7 @@ public abstract class JavadocHelper implements AutoCloseable { } long[] span; - long treeStart = delegate.getStartPosition(file, comment, tree); + long treeStart = delegate.getStartPosition(comment, tree); if (treeStart == (-1)) { LinkTree link = (LinkTree) tree; @@ -1069,15 +1067,15 @@ public abstract class JavadocHelper implements AutoCloseable { for (DocTree t : nested) { start = Math.min(start, - delegate.getStartPosition(file, comment, t)); + delegate.getStartPosition(comment, t)); end = Math.max(end, - delegate.getEndPosition(file, comment, t)); + delegate.getEndPosition(comment, t)); } span = new long[] {(int) start - 1, (int) end + 1}; rewrittenTrees.add(tree); } else { - long treeEnd = delegate.getEndPosition(file, comment, tree); + long treeEnd = delegate.getEndPosition(comment, tree); span = new long[] {treeStart, treeEnd}; } diff --git a/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysisImpl.java b/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysisImpl.java index 35faab231af..215b412003d 100644 --- a/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysisImpl.java +++ b/src/jdk.jshell/share/classes/jdk/jshell/SourceCodeAnalysisImpl.java @@ -464,7 +464,7 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis { ImportTree it = findImport(tp); if (it != null && it.isModule()) { - int selectStart = (int) sp.getStartPosition(topLevel, tp.getLeaf()); + int selectStart = (int) sp.getStartPosition(tp.getLeaf()); String qualifiedPrefix = it.getQualifiedIdentifier().getKind() == Kind.MEMBER_SELECT ? ((MemberSelectTree) it.getQualifiedIdentifier()).getExpression().toString() + "." : ""; @@ -634,7 +634,7 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis { Element annotationType = tp.getParentPath().getParentPath().getLeaf().getKind() == Kind.ANNOTATION ? at.trees().getElement(tp.getParentPath().getParentPath()) : at.trees().getElement(tp.getParentPath().getParentPath().getParentPath()); - if (sp.getEndPosition(topLevel, tp.getParentPath().getLeaf()) == (-1)) { + if (sp.getEndPosition(tp.getParentPath().getLeaf()) == (-1)) { //synthetic 'value': //TODO: filter out existing: addElements(javadoc, ElementFilter.methodsIn(annotationType.getEnclosedElements()), TRUE, TRUE, cursor, prefix, result); @@ -846,14 +846,14 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis { new TreePathScanner() { @Override public Void visitIdentifier(IdentifierTree node, Void p) { - long start = sp.getStartPosition(cut, node); - long end = sp.getEndPosition(cut, node); + long start = sp.getStartPosition(node); + long end = sp.getEndPosition(node); handleElement(false, start, end); return super.visitIdentifier(node, p); } @Override public Void visitMemberSelect(MemberSelectTree node, Void p) { - long exprEnd = sp.getEndPosition(cut, node.getExpression()); + long exprEnd = sp.getEndPosition(node.getExpression()); Token ident = findTokensFrom(exprEnd, TokenKind.DOT, TokenKind.IDENTIFIER); if (ident != null) { handleElement(false, ident.pos, ident.endPos); @@ -866,16 +866,16 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis { if (mods.getFlags().contains(Modifier.SEALED) || mods.getFlags().contains(Modifier.NON_SEALED)) { List modifierTokens = new ArrayList<>(); - long modsStart = sp.getStartPosition(cut, mods); - long modsEnd = sp.getEndPosition(cut, mods); + long modsStart = sp.getStartPosition(mods); + long modsEnd = sp.getEndPosition(mods); for (Token t : tokens) { if (t.pos >= modsStart && t.endPos <= modsEnd) { modifierTokens.add(t); } } for (AnnotationTree at : mods.getAnnotations()) { - long annStart = sp.getStartPosition(cut, at); - long annEnd = sp.getEndPosition(cut, at); + long annStart = sp.getStartPosition(at); + long annEnd = sp.getEndPosition(at); modifierTokens.removeIf(t -> t.pos >= annStart && t.endPos <= annEnd); } OUTER: for (int i = 0; i < modifierTokens.size(); i++) { @@ -912,7 +912,7 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis { handleElement(true, ident.pos, ident.endPos); } if (!node.getPermitsClause().isEmpty()) { - long start = sp.getStartPosition(cut, node.getPermitsClause().get(0)); + long start = sp.getStartPosition(node.getPermitsClause().get(0)); Token permitsCandidate = findTokensBefore(start, TokenKind.IDENTIFIER); if (permitsCandidate != null && permitsCandidate.name().contentEquals("permits")) { addKeyword.accept(permitsCandidate); @@ -946,7 +946,7 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis { } @Override public Void visitYield(YieldTree node, Void p) { - long start = sp.getStartPosition(cut, node); + long start = sp.getStartPosition(node); Token yield = findTokensFrom(start, TokenKind.IDENTIFIER); addKeyword.accept(yield); return super.visitYield(node, p); @@ -961,7 +961,7 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis { @Override public Void scan(Tree tree, Void p) { if (tree != null) { - long end = sp.getEndPosition(cut, tree); + long end = sp.getEndPosition(tree); if (end == (-1)) { //synthetic return null; @@ -1072,14 +1072,14 @@ class SourceCodeAnalysisImpl extends SourceCodeAnalysis { if (tree == null) return null; - long start = sp.getStartPosition(topLevel, tree); - long end = sp.getEndPosition(topLevel, tree); + long start = sp.getStartPosition(tree); + long end = sp.getEndPosition(tree); if (end == (-1) && tree.getKind() == Kind.ASSIGNMENT && getCurrentPath() != null && getCurrentPath().getLeaf().getKind() == Kind.ANNOTATION) { //the assignment is synthetically generated, take the end pos of the nested tree: - end = sp.getEndPosition(topLevel, ((AssignmentTree) tree).getExpression()); + end = sp.getEndPosition(((AssignmentTree) tree).getExpression()); } if (start <= wrapEndPos && wrapEndPos <= end && (deepest[0] == null || deepest[0].getLeaf() == getCurrentPath().getLeaf())) { diff --git a/src/jdk.jshell/share/classes/jdk/jshell/TreeDissector.java b/src/jdk.jshell/share/classes/jdk/jshell/TreeDissector.java index ef50f8ce71c..e5817f84206 100644 --- a/src/jdk.jshell/share/classes/jdk/jshell/TreeDissector.java +++ b/src/jdk.jshell/share/classes/jdk/jshell/TreeDissector.java @@ -119,11 +119,11 @@ class TreeDissector { } int getStartPosition(Tree tree) { - return (int) getSourcePositions().getStartPosition(targetCompilationUnit, tree); + return (int) getSourcePositions().getStartPosition(tree); } int getEndPosition(Tree tree) { - return (int) getSourcePositions().getEndPosition(targetCompilationUnit, tree); + return (int) getSourcePositions().getEndPosition(tree); } Range treeToRange(Tree tree) { diff --git a/test/hotspot/jtreg/ProblemList.txt b/test/hotspot/jtreg/ProblemList.txt index 3b871d9f4b6..d4efaa7e631 100644 --- a/test/hotspot/jtreg/ProblemList.txt +++ b/test/hotspot/jtreg/ProblemList.txt @@ -56,7 +56,7 @@ compiler/c2/irTests/TestDuplicateBackedge.java 8318904 generic-all compiler/codecache/jmx/PoolsIndependenceTest.java 8264632 macosx-all -compiler/vectorapi/reshape/TestVectorReinterpret.java 8320897,8348519 aix-ppc64,linux-ppc64le,linux-s390x +compiler/vectorapi/reshape/TestVectorReinterpret.java 8348519 linux-s390x compiler/vectorapi/VectorRebracket128Test.java 8330538 generic-all compiler/vectorization/TestVectorAlgorithms.java#noSuperWord 8376803 aix-ppc64,linux-s390x diff --git a/test/hotspot/jtreg/compiler/arraycopy/TestCloneAccess.java b/test/hotspot/jtreg/compiler/arraycopy/TestCloneAccess.java index a0da0741cad..22bf1f88ae7 100644 --- a/test/hotspot/jtreg/compiler/arraycopy/TestCloneAccess.java +++ b/test/hotspot/jtreg/compiler/arraycopy/TestCloneAccess.java @@ -23,16 +23,18 @@ /* * @test - * @bug 8248791 + * @bug 8248791 8375442 * @summary Test cloning with more than 8 (=ArrayCopyLoadStoreMaxElem) where loads are wrongly replaced by zero. * @requires vm.compiler2.enabled | vm.graal.enabled * * @run main/othervm -XX:-ReduceBulkZeroing - * -XX:CompileCommand=dontinline,compiler.arraycopy.TestCloneAccess::* - * compiler.arraycopy.TestCloneAccess + * -XX:CompileCommand=dontinline,${test.main.class}::* + * ${test.main.class} * @run main/othervm -XX:-ReduceBulkZeroing -XX:-ReduceInitialCardMarks - * -XX:CompileCommand=dontinline,compiler.arraycopy.TestCloneAccess::* - * compiler.arraycopy.TestCloneAccess + * -XX:CompileCommand=dontinline,${test.main.class}::* + * ${test.main.class} + * @run main/othervm -XX:-ReduceBulkZeroing -XX:+IgnoreUnrecognizedVMOptions -XX:VerifyIterativeGVN=1110 + * ${test.main.class} */ package compiler.arraycopy; diff --git a/test/hotspot/jtreg/compiler/c2/igvn/TestDeepIGVNRevisit.java b/test/hotspot/jtreg/compiler/c2/igvn/TestDeepIGVNRevisit.java new file mode 100644 index 00000000000..228357f953d --- /dev/null +++ b/test/hotspot/jtreg/compiler/c2/igvn/TestDeepIGVNRevisit.java @@ -0,0 +1,83 @@ +/* + * 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. + */ +package compiler.c2.igvn; + +import compiler.lib.ir_framework.*; + +/* + * @test + * @bug 8375442 + * @summary Test deep IGVN revisit for RangeCheck elimination. Other deep-revisit node types + * (If, Load, CmpP, CountedLoopEnd, LongCountedLoopEnd) benefit in large methods + * but require graph complexity beyond this test. + * @library /test/lib / + * @run driver ${test.main.class} + */ +public class TestDeepIGVNRevisit { + static boolean c1, c2, c3, c4; + static volatile int volatileField; + + public static void main(String[] args) { + TestFramework tf = new TestFramework(); + tf.setDefaultWarmup(0); + tf.addFlags("-XX:+IgnoreUnrecognizedVMOptions", + "-XX:+AlwaysIncrementalInline", + "-XX:-PartialPeelLoop", + "-XX:-LoopUnswitching"); + tf.addScenarios( + new Scenario(1, "-XX:-StressIGVN", "-XX:+UseDeepIGVNRevisit"), + new Scenario(2, "-XX:+StressIGVN", "-XX:+UseDeepIGVNRevisit"), + new Scenario(3, "-XX:-StressIGVN", "-XX:-UseDeepIGVNRevisit"), + new Scenario(4, "-XX:+StressIGVN", "-XX:-UseDeepIGVNRevisit")); + tf.start(); + } + + static void lateInline() {} + + // Deferred calls create separate LoadRange nodes for the two arr[idx] + // accesses. After inlining, LoadRanges CSE but RangeCheck#2 is already + // processed. Deep revisit re-processes it with matching range pointers. + @Setup + static Object[] setupRangeCheck() { + return new Object[] { new int[100], 42 }; + } + + @Test + @Arguments(setup = "setupRangeCheck") + @IR(phase = CompilePhase.ITER_GVN2, + applyIf = {"UseDeepIGVNRevisit", "true"}, + counts = {IRNode.RANGE_CHECK, "1"}) + @IR(phase = CompilePhase.ITER_GVN2, + applyIf = {"UseDeepIGVNRevisit", "false"}, + counts = {IRNode.RANGE_CHECK, "2"}) + static int testRangeCheck(int[] arr, int idx) { + int r = arr[idx]; // RangeCheck #1 + if (c1) { lateInline(); } + if (c2) { lateInline(); } + if (c3) { lateInline(); } + if (c4) { lateInline(); } + volatileField = r; + r += arr[idx]; // RangeCheck #2 + return r; + } +} diff --git a/test/hotspot/jtreg/compiler/igvn/TestFoldComparesCleanup.java b/test/hotspot/jtreg/compiler/igvn/TestFoldComparesCleanup.java new file mode 100644 index 00000000000..bcc3b9541ba --- /dev/null +++ b/test/hotspot/jtreg/compiler/igvn/TestFoldComparesCleanup.java @@ -0,0 +1,56 @@ +/* + * 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 8375442 + * @summary fold_compares_helper must clean up speculative lo node when bailing out with deep revisit + * @library /test/lib / + * @run main/othervm -XX:-TieredCompilation -Xbatch -XX:+IgnoreUnrecognizedVMOptions -XX:VerifyIterativeGVN=1110 + * -XX:CompileCommand=compileonly,${test.main.class}::test + * ${test.main.class} + * + * @run main ${test.main.class} + */ +package compiler.igvn; + +import jdk.test.lib.Asserts; + +public class TestFoldComparesCleanup { + // Constants chosen so that fold_compares_helper computes adjusted_lim which overflows negative. + static final int A = -2_000_000_000; + static final int B = 2_000_000_000; + + static int test(int z) { + int sum = 0; + if (z > A) sum += 1; + if (z < B) sum += 2; + return sum; + } + + public static void main(String[] args) { + for (int i = 0; i < 50_000; i++) { + Asserts.assertEquals(3, test(i)); + } + } +} diff --git a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java index 0753a0b04bc..0e1accf8a50 100644 --- a/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java +++ b/test/hotspot/jtreg/compiler/lib/ir_framework/IRNode.java @@ -1776,6 +1776,11 @@ public class IRNode { trapNodes(PREDICATE_TRAP, "predicate"); } + public static final String RANGE_CHECK = PREFIX + "RANGE_CHECK" + POSTFIX; + static { + beforeMatchingNameRegex(RANGE_CHECK, "RangeCheck"); + } + public static final String RANGE_CHECK_TRAP = PREFIX + "RANGE_CHECK_TRAP" + POSTFIX; static { trapNodes(RANGE_CHECK_TRAP, "range_check"); diff --git a/test/hotspot/jtreg/compiler/reflection/TestSharpenTypeAfterIfMissingTypeCheckInfo.java b/test/hotspot/jtreg/compiler/reflection/TestSharpenTypeAfterIfMissingTypeCheckInfo.java new file mode 100644 index 00000000000..7199ad74717 --- /dev/null +++ b/test/hotspot/jtreg/compiler/reflection/TestSharpenTypeAfterIfMissingTypeCheckInfo.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8380579 + * @summary Test that C2 match_type_check handles Bool(CmpP(CastPP(LoadKlass(...)), ConP(klass)), eq) + * @run main/othervm + * -XX:-TieredCompilation + * -Xcomp + * -XX:CompileCommand=compileonly,${test.main.class}::test + * ${test.main.class} + */ + +package compiler.reflection; + +import java.lang.reflect.Array; + +public class TestSharpenTypeAfterIfMissingTypeCheckInfo { + + public static void main(String[] args) { + for (int i = 0; i < 20_000; i++) { + test(i); + } + } + + static boolean test(int i) { + Class componentType; + if (i % 2 == 0) { + componentType = Object.class; + } else { + componentType = Integer.class; + } + Object array = Array.newInstance(componentType, 1); + return array.getClass() == Object[].class; + } +} \ No newline at end of file diff --git a/test/hotspot/jtreg/compiler/vectorization/TestVectorAlgorithms.java b/test/hotspot/jtreg/compiler/vectorization/TestVectorAlgorithms.java index 2667ac59471..ec1b43dd3f2 100644 --- a/test/hotspot/jtreg/compiler/vectorization/TestVectorAlgorithms.java +++ b/test/hotspot/jtreg/compiler/vectorization/TestVectorAlgorithms.java @@ -122,6 +122,7 @@ public class TestVectorAlgorithms { testGroups.get("dotProductF").put("dotProductF_loop", i -> { return dotProductF_loop(d.aF, d.bF); }); testGroups.get("dotProductF").put("dotProductF_VectorAPI_naive", i -> { return dotProductF_VectorAPI_naive(d.aF, d.bF); }); testGroups.get("dotProductF").put("dotProductF_VectorAPI_reduction_after_loop", i -> { return dotProductF_VectorAPI_reduction_after_loop(d.aF, d.bF); }); + testGroups.get("dotProductF").put("dotProductF_VectorAPI_fma", i -> { return dotProductF_VectorAPI_fma(d.aF, d.bF); }); testGroups.put("hashCodeB", new HashMap()); testGroups.get("hashCodeB").put("hashCodeB_loop", i -> { return hashCodeB_loop(d.aB); }); @@ -142,6 +143,12 @@ public class TestVectorAlgorithms { testGroups.get("findI").put("findI_loop", i -> { return findI_loop(d.aI, d.eI_findI[i]); }); testGroups.get("findI").put("findI_VectorAPI", i -> { return findI_VectorAPI(d.aI, d.eI_findI[i]); }); + testGroups.put("mismatchB", new HashMap()); + testGroups.get("mismatchB").put("mismatchB_loop", i -> { return d.wrap_mismatchB(i, TestVectorAlgorithms::mismatchB_loop); }); + testGroups.get("mismatchB").put("mismatchB_Arrays", i -> { return d.wrap_mismatchB(i, TestVectorAlgorithms::mismatchB_Arrays); }); + testGroups.get("mismatchB").put("mismatchB_MemorySegment", i -> { return d.wrap_mismatchB(i, TestVectorAlgorithms::mismatchB_MemorySegment); }); + testGroups.get("mismatchB").put("mismatchB_VectorAPI", i -> { return d.wrap_mismatchB(i, TestVectorAlgorithms::mismatchB_VectorAPI); }); + testGroups.put("reverseI", new HashMap()); testGroups.get("reverseI").put("reverseI_loop", i -> { return reverseI_loop(d.aI, d.rI1); }); testGroups.get("reverseI").put("reverseI_VectorAPI", i -> { return reverseI_VectorAPI(d.aI, d.rI2); }); @@ -192,6 +199,7 @@ public class TestVectorAlgorithms { "dotProductF_loop", "dotProductF_VectorAPI_naive", "dotProductF_VectorAPI_reduction_after_loop", + "dotProductF_VectorAPI_fma", "hashCodeB_loop", "hashCodeB_Arrays", "hashCodeB_VectorAPI_v1", @@ -203,6 +211,10 @@ public class TestVectorAlgorithms { "findMinIndexI_VectorAPI", "findI_loop", "findI_VectorAPI", + "mismatchB_loop", + "mismatchB_Arrays", + "mismatchB_MemorySegment", + "mismatchB_VectorAPI", "reverseI_loop", "reverseI_VectorAPI", "filterI_loop", @@ -409,6 +421,16 @@ public class TestVectorAlgorithms { return VectorAlgorithmsImpl.dotProductF_VectorAPI_reduction_after_loop(a, b); } + @Test + @IR(counts = {IRNode.LOAD_VECTOR_F, "> 0", + IRNode.ADD_REDUCTION_V, "> 0", + IRNode.FMA_VF, "> 0"}, + applyIfCPUFeatureOr = {"avx2", "true", "asimd", "true", "rvv", "true"}, + applyIf = {"UseSuperWord", "true"}) + public float dotProductF_VectorAPI_fma(float[] a, float[] b) { + return VectorAlgorithmsImpl.dotProductF_VectorAPI_fma(a, b); + } + @Test public int hashCodeB_loop(byte[] a) { return VectorAlgorithmsImpl.hashCodeB_loop(a); @@ -509,6 +531,34 @@ public class TestVectorAlgorithms { return VectorAlgorithmsImpl.findI_VectorAPI(a, e); } + @Test + @IR(counts = {IRNode.LOAD_VECTOR_B, "= 0"}) + // Currently does not vectorize, but might in the future. + public static int mismatchB_loop(byte[] a, byte[] b) { + return VectorAlgorithmsImpl.mismatchB_loop(a, b); + } + + @Test + // Inlining makes IR rules difficult. Just keep this as a correctness test. + public static int mismatchB_Arrays(byte[] a, byte[] b) { + return VectorAlgorithmsImpl.mismatchB_Arrays(a, b); + } + + @Test + // Inlining makes IR rules difficult. Just keep this as a correctness test. + public static int mismatchB_MemorySegment(byte[] a, byte[] b) { + return VectorAlgorithmsImpl.mismatchB_MemorySegment(a, b); + } + + @Test + @IR(counts = {IRNode.LOAD_VECTOR_B, "> 0", + IRNode.VECTOR_MASK_CMP, "> 0", + IRNode.VECTOR_TEST, "> 0"}, + applyIfCPUFeatureOr = {"avx", "true", "asimd", "true", "rvv", "true"}) + public static int mismatchB_VectorAPI(byte[] a, byte[] b) { + return VectorAlgorithmsImpl.mismatchB_VectorAPI(a, b); + } + @Test @IR(counts = {IRNode.LOAD_VECTOR_I, "= 0", IRNode.STORE_VECTOR, "= 0"}) diff --git a/test/hotspot/jtreg/compiler/vectorization/VectorAlgorithmsImpl.java b/test/hotspot/jtreg/compiler/vectorization/VectorAlgorithmsImpl.java index 8276d90509f..c06473d26c5 100644 --- a/test/hotspot/jtreg/compiler/vectorization/VectorAlgorithmsImpl.java +++ b/test/hotspot/jtreg/compiler/vectorization/VectorAlgorithmsImpl.java @@ -26,6 +26,7 @@ package compiler.vectorization; import java.util.Arrays; import java.util.Random; +import java.lang.foreign.MemorySegment; import jdk.incubator.vector.*; /** @@ -94,6 +95,15 @@ public class VectorAlgorithmsImpl { public int[] oopsX4; public int[] memX4; + // Input for mismatchB + // We set m1B and m2B to have identical data, temporarily edit m2B at one position, + // run the mismatch implementation, and then reset that position. This means we + // perform as little mutation while randomizing the input data. + public byte[] m1B; + public byte[] m2B; + public int[] mismatchB_idx; + public int mismatchB_idx_idx = 0; + public Data(int size, int seed, int numX4Objects, float branchProbability) { Random random = new Random(seed); @@ -165,6 +175,30 @@ public class VectorAlgorithmsImpl { ? (byte)(random.nextInt(16) + 'A') : (byte)(random.nextInt(16) + 'a'); } + + // Input data for mismatchB + m1B = new byte[size]; + m2B = new byte[size]; + random.nextBytes(m1B); + System.arraycopy(m1B, 0, m2B, 0, size); + + mismatchB_idx = new int[0x10000]; + for (int i = 0; i < mismatchB_idx.length; i++) { + // Sometimes make no mutation (-1), sometimes pick index for mutation. + mismatchB_idx[i] = (random.nextInt(10) == 0) ? -1 : random.nextInt(m1B.length); + } + } + + public interface MismatchBImpl { + int run(byte[] a, byte[] b); + } + + public int wrap_mismatchB(int idx, MismatchBImpl impl) { + int i = mismatchB_idx[idx & 0xffff]; + if (i != -1) { m2B[i]++; } + int res = impl.run(m1B, m2B); + if (i != -1) { m2B[i]--; } + return res; } } @@ -348,6 +382,21 @@ public class VectorAlgorithmsImpl { return sum; } + public static float dotProductF_VectorAPI_fma(float[] a, float[] b) { + var sums = FloatVector.broadcast(SPECIES_F, 0.0f); + int i; + for (i = 0; i < SPECIES_F.loopBound(a.length); i += SPECIES_F.length()) { + var va = FloatVector.fromArray(SPECIES_F, a, i); + var vb = FloatVector.fromArray(SPECIES_F, b, i); + sums = va.fma(vb, sums); + } + float sum = sums.reduceLanes(VectorOperators.ADD); + for (; i < a.length; i++) { + sum = Math.fma(a[i], b[i], sum); + } + return sum; + } + public static int hashCodeB_loop(byte[] a) { int h = 1; for (int i = 0; i < a.length; i++) { @@ -656,6 +705,44 @@ public class VectorAlgorithmsImpl { return -1; } + public static int mismatchB_loop(byte[] a, byte[] b) { + for (int i = 0; i < a.length; i++) { + if (a[i] != b[i]) { + return i; + } + } + return -1; + } + + public static int mismatchB_Arrays(byte[] a, byte[] b) { + return Arrays.mismatch(a, b); + } + + public static int mismatchB_MemorySegment(byte[] a, byte[] b) { + var aMS = MemorySegment.ofArray(a); + var bMS = MemorySegment.ofArray(b); + return (int) aMS.mismatch(bMS); + } + + public static int mismatchB_VectorAPI(byte[] a, byte[] b) { + int i = 0; + for (; i < SPECIES_B.loopBound(a.length); i += SPECIES_B.length()) { + ByteVector va = ByteVector.fromArray(SPECIES_B, a, i); + ByteVector vb = ByteVector.fromArray(SPECIES_B, b, i); + var mask = va.compare(VectorOperators.NE, vb); + if (mask.anyTrue()) { + return i + mask.firstTrue(); + } + } + for (; i < a.length; i++) { + if (a[i] != b[i]) { + return i; + } + } + return -1; + } + + public static Object reverseI_loop(int[] a, int[] r) { for (int i = 0; i < a.length; i++) { r[a.length - i - 1] = a[i]; diff --git a/test/hotspot/jtreg/gc/g1/TestG1RemSetArrayOfCardsEntriesErgoLimit.java b/test/hotspot/jtreg/gc/g1/TestG1RemSetArrayOfCardsEntriesErgoLimit.java new file mode 100644 index 00000000000..4af09066def --- /dev/null +++ b/test/hotspot/jtreg/gc/g1/TestG1RemSetArrayOfCardsEntriesErgoLimit.java @@ -0,0 +1,43 @@ +/* + * 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 8379174 + * @summary Test for G1 ergonomics deriving an out-of-range + * G1RemSetArrayOfCardsEntries value from G1RemSetArrayOfCardsEntriesBase + * @requires vm.gc.G1 + * @run main/othervm -XX:+IgnoreUnrecognizedVMOptions + * -XX:+UseG1GC + * -XX:G1RemSetArrayOfCardsEntriesBase=62117 + * -XX:G1HeapRegionSize=4m + * ${test.main.class} + */ + +package gc.g1; + +public class TestG1RemSetArrayOfCardsEntriesErgoLimit { + public static void main(String[] args) { + System.out.println("passed"); + } +} diff --git a/test/jdk/java/nio/charset/CharsetEncoder/CanEncode.java b/test/jdk/java/nio/charset/CharsetEncoder/CanEncode.java index 8545ef61be9..d4dabf70910 100644 --- a/test/jdk/java/nio/charset/CharsetEncoder/CanEncode.java +++ b/test/jdk/java/nio/charset/CharsetEncoder/CanEncode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 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 @@ -66,14 +66,21 @@ public class CanEncode { Charset cs = Charset.forName(csn); CharsetEncoder ce = cs.newEncoder(); - if (cs.name().equals("US-ASCII")) { - ck(ce, 'x', true); - ck(ce, '\u00B6', false); - ck(ce, "x", true); - ck(ce, "\u00B6", false); - ck(ce, "xyzzy", true); - ck(ce, "xy\u00B6", false); - } + // Basic multilingual plane + boolean utf = csn.startsWith("UTF-"); + ck(ce, 'x', true); + ck(ce, '\u00B6', utf); + ck(ce, "", true); + ck(ce, "x", true); + ck(ce, "\u00B6", utf); + ck(ce, "xyzzy", true); + ck(ce, "xy\u00B6", utf); + + // Paired surrogates + ck(ce, "\uD83D\uDE00", utf); + ck(ce, "XX\uD83D\uDE00", utf); + ck(ce, "\uD83D\uDE00XX", utf); + ck(ce, "X\uD83D\uDE00X", utf); // Unpaired surrogates should never be encodable ck(ce, '\ud800', false); @@ -81,15 +88,36 @@ public class CanEncode { ck(ce, '\udffe', false); ck(ce, '\udfff', false); ck(ce, "\ud800", false); + ck(ce, "XX\ud800", false); + ck(ce, "\ud800XX", false); + ck(ce, "X\ud800X", false); ck(ce, "\ud801", false); + ck(ce, "XX\ud801", false); + ck(ce, "\ud801XX", false); + ck(ce, "X\ud801X", false); ck(ce, "\udffe", false); + ck(ce, "XX\udffe", false); + ck(ce, "\udffeXX", false); + ck(ce, "X\udffeX", false); ck(ce, "\udfff", false); + ck(ce, "XX\udfff", false); + ck(ce, "\udfffXX", false); + ck(ce, "X\udfffX", false); + if (errors > 0) { + throw new RuntimeException(errors + " errors for Charset " + csn); + } } public static void main(String[] args) throws Exception { test("US-ASCII"); test("UTF-8"); + test("UTF-16"); + test("UTF-16LE"); + test("UTF-16BE"); + test("UTF-32"); + test("UTF-32LE"); + test("UTF-32BE"); } } diff --git a/test/jdk/java/util/zip/ZipOutputStream/UnmappableZipEntryNameOrComment.java b/test/jdk/java/util/zip/ZipOutputStream/UnmappableZipEntryNameOrComment.java new file mode 100644 index 00000000000..af08de2adcf --- /dev/null +++ b/test/jdk/java/util/zip/ZipOutputStream/UnmappableZipEntryNameOrComment.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.zip.ZipEntry; +import java.util.zip.ZipException; +import java.util.zip.ZipOutputStream; + +import static java.io.OutputStream.nullOutputStream; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/* @test + * @bug 8380452 + * @summary Unmappable characters in ZipEntry name or comment should be rejected with ZipException + * @run junit ${test.main.class} + */ +public class UnmappableZipEntryNameOrComment { + + // Charset used by any ZipOutputStream in this test + static final Charset CHARSET = StandardCharsets.US_ASCII; + // 'ΓΈ' is an unmappable character in US_ASCII + static final String UNMAPPABLE = "\u00f8"; + + /** + * Verify that calling ZipOutputStream.putNextEntry with an unmappable ZipEntry + * name is rejected with a ZipException. + * + * @throws IOException if an unexpected IO error occurs + */ + @Test + void rejectUnmappableZipEntryName() throws IOException { + ZipEntry e = new ZipEntry(UNMAPPABLE); + try (var out = new ZipOutputStream(nullOutputStream(), CHARSET)) { + assertThrows(ZipException.class, () -> out.putNextEntry(e)); + } + } + + /** + * Verify that calling ZipOutputStream.putNextEntry with an unmappable ZipEntry + * comment is rejected with a ZipException. + * + * @throws IOException if an unexpected IO error occurs + */ + @Test + void rejectUnmappableZipEntryComment() throws IOException { + ZipEntry e = new ZipEntry("file.txt"); + e.setComment(UNMAPPABLE); + try (var out = new ZipOutputStream(nullOutputStream(), CHARSET)) { + assertThrows(ZipException.class, () -> out.putNextEntry(e)); + } + } +} diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/mock/MockingToolProvider.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/mock/MockingToolProvider.java index f8c04cc3927..2c46d02e7ce 100644 --- a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/mock/MockingToolProvider.java +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/mock/MockingToolProvider.java @@ -160,5 +160,5 @@ abstract sealed class MockingToolProvider implements ToolProviderCommandMock { private final String name; private final Iterator actionIter; - static ToolProviderCommandMock UNREACHABLE = new MockingToolProvider.NonCompletable("", List.of()); + static final ToolProviderCommandMock UNREACHABLE = new MockingToolProvider.NonCompletable("", List.of()); } diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/stdmock/EnvironmentProviderMock.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/stdmock/EnvironmentProviderMock.java new file mode 100644 index 00000000000..1e12078ec66 --- /dev/null +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/stdmock/EnvironmentProviderMock.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.jpackage.test.stdmock; + +import jdk.jpackage.internal.EnvironmentProvider; +import java.util.ArrayList; +import java.util.Map; +import java.util.Objects; + +public record EnvironmentProviderMock( + Map envVariables, + Map systemProperties) implements EnvironmentProvider { + + public EnvironmentProviderMock { + envVariables.keySet().forEach(Objects::requireNonNull); + envVariables.values().forEach(Objects::requireNonNull); + + systemProperties.keySet().forEach(Objects::requireNonNull); + systemProperties.values().forEach(Objects::requireNonNull); + } + + @Override + public String getenv(String envVarName) { + return envVariables.get(Objects.requireNonNull(envVarName)); + } + + @Override + public String getProperty(String propertyName) { + return systemProperties.get(Objects.requireNonNull(propertyName)); + } + + @Override + public String toString() { + var tokens = new ArrayList(); + if (!envVariables.isEmpty()) { + tokens.add(String.format("env=%s", envVariables)); + } + if (!systemProperties.isEmpty()) { + tokens.add(String.format("props=%s", systemProperties)); + } + return String.join(", ", tokens); + } +} diff --git a/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/stdmock/WixToolMock.java b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/stdmock/WixToolMock.java new file mode 100644 index 00000000000..9cd00f61082 --- /dev/null +++ b/test/jdk/tools/jpackage/helpers/jdk/jpackage/test/stdmock/WixToolMock.java @@ -0,0 +1,173 @@ +/* + * 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 jdk.jpackage.test.stdmock; + +import java.nio.file.Path; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import jdk.jpackage.internal.util.PathUtils; +import jdk.jpackage.test.mock.CommandActionSpec; +import jdk.jpackage.test.mock.CommandActionSpecs; +import jdk.jpackage.test.mock.CommandMockSpec; + +public final class WixToolMock { + + public CommandMockSpec create() { + Objects.requireNonNull(type); + Objects.requireNonNull(version); + + CommandActionSpec action = switch (type) { + case CANDLE3 -> { + yield candleAction(fips, version); + } + case LIGHT3 -> { + yield lightAction(version); + } + case WIX4 -> { + yield wixAction(version); + } + }; + + var toolPath = Optional.ofNullable(dir).map(d -> { + return d.resolve(type.fileName); + }).orElse(type.fileName); + var mockName = PathUtils.replaceSuffix(toolPath, ""); + + return new CommandMockSpec(toolPath, mockName, CommandActionSpecs.build().action(action).create()); + } + + public WixToolMock fips(Boolean v) { + fips = v; + return this; + } + + public WixToolMock fips() { + return fips(true); + } + + public WixToolMock dir(Path v) { + dir = v; + return this; + } + + public WixToolMock version(String v) { + version = v; + return this; + } + + public WixToolMock candle(String version) { + return type(WixTool.CANDLE3).version(version); + } + + public WixToolMock light(String version) { + return type(WixTool.LIGHT3).version(version); + } + + public WixToolMock wix(String version) { + return type(WixTool.WIX4).version(version); + } + + private WixToolMock type(WixTool v) { + type = v; + return this; + } + + private static CommandActionSpec candleAction(boolean fips, String version) { + Objects.requireNonNull(version); + var sb = new StringBuilder(); + sb.append(version); + if (fips) { + sb.append("; fips"); + } + return CommandActionSpec.create(sb.toString(), context -> { + if (List.of("-?").equals(context.args())) { + if (fips) { + context.err().println("error CNDL0308 : The Federal Information Processing Standard (FIPS) appears to be enabled on the machine"); + return Optional.of(308); + } + } else if (!List.of("-fips").equals(context.args())) { + throw context.unexpectedArguments(); + } + + var out = context.out(); + List.of( + "Windows Installer XML Toolset Compiler version " + version, + "Copyright (c) .NET Foundation and contributors. All rights reserved.", + "", + " usage: candle.exe [-?] [-nologo] [-out outputFile] sourceFile [sourceFile ...] [@responseFile]" + ).forEach(out::println); + + return Optional.of(0); + }); + } + + private static CommandActionSpec lightAction(String version) { + Objects.requireNonNull(version); + return CommandActionSpec.create(version, context -> { + if (List.of("-?").equals(context.args())) { + var out = context.out(); + List.of( + "Windows Installer XML Toolset Linker version " + version, + "Copyright (c) .NET Foundation and contributors. All rights reserved.", + "", + " usage: light.exe [-?] [-b bindPath] [-nologo] [-out outputFile] objectFile [objectFile ...] [@responseFile]" + ).forEach(out::println); + return Optional.of(0); + } else { + throw context.unexpectedArguments(); + } + }); + } + + private static CommandActionSpec wixAction(String version) { + Objects.requireNonNull(version); + return CommandActionSpec.create(version, context -> { + if (List.of("--version").equals(context.args())) { + context.out().println(version); + return Optional.of(0); + } else { + throw context.unexpectedArguments(); + } + }); + } + + private enum WixTool { + CANDLE3("candle"), + LIGHT3("light"), + WIX4("wix"), + ; + + WixTool(String name) { + this.fileName = Path.of(Objects.requireNonNull(name) + ".exe"); + } + + final Path fileName; + } + + private Path dir; + private WixTool type; + private String version; + private boolean fips; +} diff --git a/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/util/PathUtilsTest.java b/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/util/PathUtilsTest.java new file mode 100644 index 00000000000..7ed679ea3b5 --- /dev/null +++ b/test/jdk/tools/jpackage/junit/share/jdk.jpackage/jdk/jpackage/internal/util/PathUtilsTest.java @@ -0,0 +1,213 @@ +/* + * 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 jdk.jpackage.internal.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrowsExactly; + +import java.nio.file.Path; +import java.util.Optional; +import java.util.function.UnaryOperator; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + + +class PathUtilsTest { + + @ParameterizedTest + @CsvSource({ + "foo,''", + "foo.bar,.bar", + "foo..bar,.bar", + ".bar,.bar", + "foo.bar.buz,.buz", + ".,.", + "...,.", + "..,.", + }) + void test_getSuffix(Path path, String expected) { + + var suffix = PathUtils.getSuffix(path); + + assertEquals(expected, suffix); + } + + @Test + void test_getSuffix_null() { + assertThrowsExactly(NullPointerException.class, () -> { + PathUtils.getSuffix(null); + }); + } + + @ParameterizedTest + @CsvSource({ + "foo,'',foo", + "a/b/foo.exe,.ico,a/b/foo.exe.ico", + "foo,bar,foobar", + "'',bar,bar", + ".,bar,.bar", + }) + void test_addSuffix(Path path, String suffix, Path expected) { + + var newPath = PathUtils.addSuffix(path, suffix); + + assertEquals(expected, newPath); + } + + @Test + void test_addSuffix_null() { + assertThrowsExactly(NullPointerException.class, () -> { + PathUtils.addSuffix(null, "foo"); + }); + assertThrowsExactly(NullPointerException.class, () -> { + PathUtils.addSuffix(Path.of("foo"), null); + }); + } + + @ParameterizedTest + @CsvSource({ + "foo.exe,.ico,foo.ico", + "foo.exe,,foo", + "foo.exe,'',foo", + "a/b/foo.exe,.ico,a/b/foo.ico", + "foo,'',foo", + "foo,bar,foobar", + "'',bar,bar", + ".,bar,bar", + ".,.bar,.bar", + }) + void test_replaceSuffix(Path path, String newSuffix, Path expected) { + + var newPath = PathUtils.replaceSuffix(path, newSuffix); + + assertEquals(expected, newPath); + } + + @Test + void test_replaceSuffix_null() { + assertThrowsExactly(NullPointerException.class, () -> { + PathUtils.replaceSuffix(null, "foo"); + }); + + assertEquals(Path.of("foo"), PathUtils.replaceSuffix(Path.of("foo.a"), null)); + } + + @ParameterizedTest + @CsvSource({ + "IDENTITY,a,a", + "IDENTITY,,", + "RETURN_NULL,a,", + "RETURN_NULL,,", + "FOO,a,foo", + "FOO,,", + }) + void test_mapNullablePath(PathMapper mapper, Path path, Path expected) { + + var newPath = PathUtils.mapNullablePath(mapper, path); + + assertEquals(expected, newPath); + } + + @Test + void test_mapNullablePath_null() { + assertThrowsExactly(NullPointerException.class, () -> { + PathUtils.mapNullablePath(null, Path.of("")); + }); + } + + @ParameterizedTest + @CsvSource(nullValues = {"N/A"}, value = { + "foo.exe", + "N/A", + }) + void test_normalizedAbsolutePath(Path path) { + + var newPath = PathUtils.normalizedAbsolutePath(path); + + var expected = Optional.ofNullable(path).map(v -> { + return v.normalize().toAbsolutePath(); + }).orElse(null); + + assertEquals(expected, newPath); + } + + @ParameterizedTest + @CsvSource(nullValues = {"N/A"}, value = { + "foo.exe", + "N/A", + }) + void test_normalizedAbsolutePathString(Path path) { + + var newPath = PathUtils.normalizedAbsolutePathString(path); + + var expected = Optional.ofNullable(path).map(v -> { + return v.normalize().toAbsolutePath().toString(); + }).orElse(null); + + assertEquals(expected, newPath); + } + + @ParameterizedTest + @CsvSource(nullValues = {"N/A"}, value = { + "N/A", + "foo", + "*", + ":", + }) + void test_asPath(String str) { + + var path = PathUtils.asPath(str); + + var expected = Optional.ofNullable(str).flatMap(v -> { + return Result.of(() -> { + return Path.of(v); + }).value(); + }); + + assertEquals(expected, path); + } + + enum PathMapper implements UnaryOperator { + IDENTITY { + @Override + public Path apply(Path path) { + return path; + } + }, + RETURN_NULL { + @Override + public Path apply(Path path) { + return null; + } + }, + FOO { + @Override + public Path apply(Path path) { + return Path.of("foo"); + } + }, + ; + } +} diff --git a/test/jdk/tools/jpackage/junit/windows/jdk.jpackage/jdk/jpackage/internal/WixToolTest.java b/test/jdk/tools/jpackage/junit/windows/jdk.jpackage/jdk/jpackage/internal/WixToolTest.java new file mode 100644 index 00000000000..d4881069844 --- /dev/null +++ b/test/jdk/tools/jpackage/junit/windows/jdk.jpackage/jdk/jpackage/internal/WixToolTest.java @@ -0,0 +1,754 @@ +/* + * 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 jdk.jpackage.internal; + +import static java.util.stream.Collectors.toMap; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import jdk.jpackage.internal.WixTool.ToolInfo; +import jdk.jpackage.internal.WixToolset.WixToolsetType; +import jdk.jpackage.internal.model.ConfigException; +import jdk.jpackage.internal.util.TokenReplace; +import jdk.jpackage.test.CannedFormattedString; +import jdk.jpackage.test.JPackageStringBundle; +import jdk.jpackage.test.mock.CommandActionSpecs; +import jdk.jpackage.test.mock.CommandMock; +import jdk.jpackage.test.mock.CommandMockSpec; +import jdk.jpackage.test.mock.Script; +import jdk.jpackage.test.stdmock.EnvironmentProviderMock; +import jdk.jpackage.test.stdmock.JPackageMockUtils; +import jdk.jpackage.test.stdmock.WixToolMock; +import org.junit.jupiter.api.io.TempDir; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + + +class WixToolTest { + + @ParameterizedTest + @MethodSource + void testLookup(TestSpec spec, @TempDir Path workDir) throws IOException { + spec.run(workDir); + } + + @ParameterizedTest + @MethodSource + void testLookupDirs(EnvironmentTestSpec spec, @TempDir Path workDir) throws IOException { + spec.run(workDir); + } + + private static Collection testLookup() { + + List testCases = new ArrayList<>(); + + Consumer appendTestCases = builder -> { + testCases.add(builder.create()); + }; + + Stream.of( + // Simple WiX3 of a minimal acceptable version + TestSpec.build() + .expect(toolset().version("3.0").put(WixToolsetType.Wix3, "foo")) + .tool(tool("foo").candle("3.0")) + .tool(tool("foo").light("3.0")), + // Simple WiX3 with FIPS + TestSpec.build() + .expect(toolset().version("3.14.1.8722").put(WixToolsetType.Wix3, "foo").fips()) + .tool(tool("foo").candle("3.14.1.8722").fips()) + .tool(tool("foo").light("3.14.1.8722")), + // Simple WiX4+ of a minimal acceptable version + TestSpec.build() + .expect(toolset().version("4.0.4").put(WixToolsetType.Wix4, "foo")) + .tool(tool("foo").wix("4.0.4")), + // WiX3 with light and candle from different directories and non-existent directory + TestSpec.build() + .expect(toolset().version("3.11.2").put(WixTool.Candle3, "foo").put(WixTool.Light3, "bar")) + .lookupDir("buz") + .tool(tool("foo").candle("3.11.2")) + .tool(tool("bar").light("3.11.2")) + .tool(tool("bar").candle("3.11.1")) + .tool(tool("foo").light("3.11.1")), + // WiX3, WiX4+ same directory + TestSpec.build() + .expect(toolset().version("5.0.2+aa65968c").put(WixToolsetType.Wix4, "foo")) + .tool(tool("foo").candle("3.14.1.8722")) + .tool(tool("foo").light("3.14.1.8722")) + .tool(tool("foo").wix("5.0.2+aa65968c")), + // WiX3 (good), WiX4+ (bad version) + TestSpec.build() + .expect(toolset().version("3.14.1.8722").put(WixToolsetType.Wix3, "foo")) + .tool(tool("foo").candle("3.14.1.8722")) + .tool(tool("foo").light("3.14.1.8722")) + .tool(tool("foo").wix("Blah-blah-blah")), + // WiX3 (incomplete), WiX4+ (good) + TestSpec.build() + .expect(toolset().version("5.0").put(WixToolsetType.Wix4, "foo")) + .tool(tool("foo").candle("3.14.1.8722")) + .tool(tool("foo").wix("5.0")), + // WiX5 in the PATH and in the directory, same version; PATH always wins + TestSpec.build() + .expect(toolset().version("5.0").put(WixToolsetType.Wix4)) + .tool(tool().wix("5.0")) + .tool(tool("foo").wix("5.0")), + // WiX5 in the PATH and in the directory; the one in the directory is newer; PATH always wins + TestSpec.build() + .expect(toolset().version("5.0").put(WixToolsetType.Wix4)) + .tool(tool().wix("5.0")) + .tool(tool("foo").wix("5.1")), + // WiX5 in the PATH and in the directory; the one in the PATH is newer; PATH always wins + TestSpec.build() + .expect(toolset().version("5.1").put(WixToolsetType.Wix4)) + .tool(tool().wix("5.1")) + .tool(tool("foo").wix("5.0")), + // WiX3 in the PATH, WiX3 in the directory; PATH always wins + TestSpec.build() + .expect(toolset().version("3.20").put(WixToolsetType.Wix3)) + .tool(tool().candle("3.20")) + .tool(tool().light("3.20")) + .tool(tool("foo").wix("5.0")), + // Old WiX3 in the PATH, WiX3 in the directory + TestSpec.build() + .expect(toolset().version("3.20").put(WixToolsetType.Wix3, "foo")) + .tool(tool().candle("2.9")) + .tool(tool().light("2.9")) + .tool(tool("foo").candle("3.20")) + .tool(tool("foo").light("3.20")) + ).forEach(appendTestCases); + + for (var oldLightStatus : ToolStatus.values()) { + for (var oldCandleStatus : ToolStatus.values()) { + for (var newLightStatus : ToolStatus.values()) { + for (var newCandleStatus : ToolStatus.values()) { + boolean newGood = ToolStatus.isAllGood(newLightStatus, newCandleStatus); + if (!ToolStatus.isAllGood(oldLightStatus, oldCandleStatus) && !newGood) { + continue; + } + + var builder = TestSpec.build(); + if (newGood) { + builder.expect(toolset().version("3.14").put(WixToolsetType.Wix3, "new")); + } else { + builder.expect(toolset().version("3.11").put(WixToolsetType.Wix3, "old")); + } + + oldCandleStatus.map(tool("old").candle("3.11")).ifPresent(builder::tool); + oldLightStatus.map(tool("old").light("3.11")).ifPresent(builder::tool); + + newCandleStatus.map(tool("new").candle("3.14")).ifPresent(builder::tool); + newLightStatus.map(tool("new").light("3.14")).ifPresent(builder::tool); + + appendTestCases.accept(builder); + } + } + } + } + + Stream.of( + // No WiX tools + TestSpec.build(), + TestSpec.build() + .lookupDir("foo"), + TestSpec.build() + .lookupDir(LOOKUP_IN_PATH), + // Incomplete WiX3: missing candle.exe + TestSpec.build() + .tool(tool("foo").light("3.14.1.8722")), + // Incomplete WiX3: missing light.exe + TestSpec.build() + .tool(tool("foo").candle("3.14.1.8722")), + // Incomplete WiX3: version mismatch of light.exe and candle.exe + TestSpec.build() + .tool(tool("foo").candle("3.14")) + .tool(tool("foo").light("3.15")), + // WiX3 too old + TestSpec.build() + .tool(tool("foo").candle("2.9")) + .tool(tool("foo").light("2.9")), + // WiX4+ too old + TestSpec.build() + .tool(tool("foo").wix("4.0.3")) + ).forEach(appendTestCases); + + return testCases; + } + + private static Collection testLookupDirs() { + + List testCases = new ArrayList<>(); + + Stream.of( + EnvironmentTestSpec.build() + .env(EnvironmentVariable.USERPROFILE, "@@/foo") + .expect("@USERPROFILE@/.dotnet/tools"), + EnvironmentTestSpec.build() + .env(SystemProperty.USER_HOME, "@@/bar") + .expect("@user.home@/.dotnet/tools"), + // "USERPROFILE" environment variable and "user.home" system property set to different values, + // the order should be "USERPROFILE" followed by "user.home". + EnvironmentTestSpec.build() + .env(EnvironmentVariable.USERPROFILE, "@@/foo") + .env(SystemProperty.USER_HOME, "@@/bar") + .expect("@USERPROFILE@/.dotnet/tools") + .expect("@user.home@/.dotnet/tools"), + // "USERPROFILE" environment variable and "user.home" system property set to the same value. + EnvironmentTestSpec.build() + .env(EnvironmentVariable.USERPROFILE, "@@/buz") + .env(SystemProperty.USER_HOME, "@@/buz") + .expect("@USERPROFILE@/.dotnet/tools"), + // WiX3: newer versions first; 32bit after 64bit + EnvironmentTestSpec.build() + .standardEnv(EnvironmentVariable.PROGRAM_FILES_X86) + .standardEnv(EnvironmentVariable.PROGRAM_FILES) + .expect(String.format("@%s@/WiX Toolset v3.11/bin", EnvironmentVariable.PROGRAM_FILES_X86.variableName())) + .expect(String.format("@%s@/WiX Toolset v3.10/bin", EnvironmentVariable.PROGRAM_FILES.variableName())) + .expect(String.format("@%s@/WiX Toolset v3.10/bin", EnvironmentVariable.PROGRAM_FILES_X86.variableName())), + // Malformed installation directory should be accepted + EnvironmentTestSpec.build() + .standardEnv(EnvironmentVariable.PROGRAM_FILES_X86) + .expect(String.format("@%s@/WiX Toolset vb/bin", EnvironmentVariable.PROGRAM_FILES_X86.variableName())) + .expect(String.format("@%s@/WiX Toolset va/bin", EnvironmentVariable.PROGRAM_FILES_X86.variableName())) + .expect(String.format("@%s@/WiX Toolset v/bin", EnvironmentVariable.PROGRAM_FILES_X86.variableName())), + // No directories + EnvironmentTestSpec.build() + ).map(EnvironmentTestSpec.Builder::create).forEach(testCases::add); + + return testCases; + } + + private enum ToolStatus { + GOOD, + MISSING, + UNEXPECTED_STDOUT, + ; + + static boolean isAllGood(ToolStatus... status) { + return Stream.of(status).allMatch(Predicate.isEqual(GOOD)); + } + + Optional map(WixToolMock builder) { + return switch (this) { + case MISSING -> { + yield Optional.empty(); + } + case UNEXPECTED_STDOUT -> { + var mock = builder.create(); + yield Optional.of(new CommandMockSpec( + mock.name(), + mock.mockName(), + CommandActionSpecs.build().stdout("Blah-Blah-Blah").exit().create())); + } + case GOOD -> { + yield Optional.of(builder.create()); + } + }; + } + } + + record TestSpec( + Optional expected, + List lookupDirs, + boolean lookupInPATH, + Collection mocks, + List expectedErrors) { + + TestSpec { + Objects.requireNonNull(expected); + lookupDirs.forEach(Objects::requireNonNull); + mocks.forEach(Objects::requireNonNull); + expectedErrors.forEach(Objects::requireNonNull); + + if (expected.isEmpty() == expectedErrors.isEmpty()) { + // It should be either toolset or errors, not both or non both. + throw new IllegalArgumentException(); + } + + lookupDirs.forEach(WixToolTest::assertIsRelative); + + lookupDirs.forEach(path -> { + assertNotEquals(LOOKUP_IN_PATH, path); + }); + + // Ensure tool paths are unique. + mocks.stream().map(CommandMockSpec::name).collect(toMap(x -> x, x -> x)); + } + + @Override + public String toString() { + var tokens = new ArrayList(); + expected.map(Object::toString).ifPresent(tokens::add); + if (!expectedErrors.isEmpty()) { + tokens.add(String.format("errors=%s", expectedErrors)); + } + + List lookupPaths; + if (lookupInPATH) { + lookupPaths = new ArrayList<>(); + lookupPaths.add(Path.of("${PATH}")); + lookupPaths.addAll(lookupDirs); + } else { + lookupPaths = lookupDirs; + } + + if (!lookupPaths.isEmpty()) { + tokens.add(String.format("lookup-dirs=%s", lookupPaths)); + } + if (!mocks.isEmpty()) { + tokens.add(mocks.toString()); + } + return String.join(", ", tokens); + } + + void run(Path workDir) { + var scriptBuilder = Script.build().commandMockBuilderMutator(CommandMock.Builder::repeatInfinitely); + mocks.stream().map(mockSpec -> { + Path toolPath = mockSpec.name(); + if (toolPath.getNameCount() > 1) { + toolPath = workDir.resolve(toolPath); + } + return new CommandMockSpec(toolPath, mockSpec.mockName(), mockSpec.actions()); + }).forEach(scriptBuilder::map); + + scriptBuilder.map(_ -> true, CommandMock.ioerror("non-existent")); + + var script = scriptBuilder.createLoop(); + + Supplier createToolset = () -> { + return WixTool.createToolset(() -> { + return lookupDirs.stream().map(workDir::resolve).toList(); + }, lookupInPATH()); + }; + + Globals.main(() -> { + JPackageMockUtils.buildJPackage() + .script(script) + .listener(System.out::println) + .applyToGlobals(); + + expected.ifPresentOrElse(expectedToolset -> { + var toolset = createToolset.get(); + assertEquals(resolveAt(expectedToolset, workDir), toolset); + }, () -> { + var ex = assertThrows(RuntimeException.class, createToolset::get); + assertEquals(expectedErrors.getFirst().getValue(), ex.getMessage()); + if (ex instanceof ConfigException cfgEx) { + assertEquals(expectedErrors.getLast().getValue(), cfgEx.getAdvice()); + assertEquals(2, expectedErrors.size()); + } else { + assertEquals(1, expectedErrors.size()); + } + }); + + return 0; + }); + } + + static Builder build() { + return new Builder(); + } + + static final class Builder { + + TestSpec create() { + if (expected == null && expectedErrors.isEmpty()) { + return copy() + .expect("error.no-wix-tools") + .expect("error.no-wix-tools.advice") + .create(); + } else { + var allLookupDirs = Stream.concat( + lookupDirs.stream(), + tools.stream().map(CommandMockSpec::name).map(toolPath -> { + if (toolPath.getNameCount() == 1) { + return LOOKUP_IN_PATH; + } else { + return toolPath.getParent(); + } + }) + ).distinct().collect(Collectors.toCollection(ArrayList::new)); + + var lookupInPATH = allLookupDirs.contains(LOOKUP_IN_PATH); + if (lookupInPATH) { + allLookupDirs.remove(LOOKUP_IN_PATH); + } + + return new TestSpec( + Optional.ofNullable(expected), + Collections.unmodifiableList(allLookupDirs), + lookupInPATH, + List.copyOf(tools), + List.copyOf(expectedErrors)); + } + } + + Builder copy() { + return new Builder(this); + } + + private Builder() { + expectedErrors = new ArrayList<>(); + lookupDirs = new ArrayList<>(); + tools = new ArrayList<>(); + } + + private Builder(Builder other) { + expected = other.expected; + expectedErrors = new ArrayList<>(other.expectedErrors); + lookupDirs = new ArrayList<>(other.lookupDirs); + tools = new ArrayList<>(other.tools); + } + + Builder expect(WixToolset v) { + expected = v; + return this; + } + + Builder expect(String formatKey, Object ... args) { + expectedErrors.add(JPackageStringBundle.MAIN.cannedFormattedString(formatKey, args)); + return this; + } + + Builder expect(WixToolsetBuilder builder) { + return expect(builder.create()); + } + + Builder lookupDir(String v) { + return lookupDir(Path.of(v)); + } + + Builder lookupDir(Path v) { + lookupDirs.add(Objects.requireNonNull(v)); + return this; + } + + Builder tool(CommandMockSpec v) { + tools.add(Objects.requireNonNull(v)); + return this; + } + + Builder tool(WixToolMock v) { + return tool(v.create()); + } + + private WixToolset expected; + private final List expectedErrors; + private final List lookupDirs; + private final List tools; + } + } + + private static final class WixToolsetBuilder { + + WixToolset create() { + return new WixToolset(tools.entrySet().stream().collect(toMap(Map.Entry::getKey, e -> { + ToolInfo toolInfo = new WixTool.DefaultToolInfo(e.getValue(), version); + if (e.getKey() == WixTool.Candle3) { + toolInfo = new WixTool.DefaultCandleInfo(toolInfo, fips); + } + return toolInfo; + }))); + } + + WixToolsetBuilder version(String v) { + version = v; + return this; + } + + WixToolsetBuilder put(WixTool tool, String path) { + return put(tool, Path.of(path)); + } + + WixToolsetBuilder put(WixTool tool, Path path) { + tools.put(Objects.requireNonNull(tool), path.resolve(tool.fileName())); + return this; + } + + WixToolsetBuilder put(WixTool tool) { + return put(tool, LOOKUP_IN_PATH); + } + + WixToolsetBuilder put(WixToolsetType type, Path path) { + type.getTools().forEach(tool -> { + put(tool, path); + }); + return this; + } + + WixToolsetBuilder put(WixToolsetType type, String path) { + return put(type, Path.of(path)); + } + + WixToolsetBuilder put(WixToolsetType type) { + return put(type, LOOKUP_IN_PATH); + } + + WixToolsetBuilder fips(boolean v) { + fips = true; + return this; + } + + WixToolsetBuilder fips() { + return fips(true); + } + + private Map tools = new HashMap<>(); + private boolean fips; + private String version; + } + + enum EnvironmentVariable { + USERPROFILE("USERPROFILE"), + PROGRAM_FILES("ProgramFiles"), + PROGRAM_FILES_X86("ProgramFiles(x86)"), + SYSTEM_DRIVE("SystemDrive"), + ; + + EnvironmentVariable(String variableName) { + this.variableName = Objects.requireNonNull(variableName); + } + + String variableName() { + return variableName; + } + + private final String variableName; + } + + enum SystemProperty { + USER_HOME("user.home"), + ; + + SystemProperty(String propertyName) { + this.propertyName = Objects.requireNonNull(propertyName); + } + + String propertyName() { + return propertyName; + } + + private final String propertyName; + } + + record EnvironmentTestSpec(EnvironmentProviderMock env, List expectedDirs) { + + EnvironmentTestSpec { + Objects.requireNonNull(env); + expectedDirs.forEach(dir -> { + if (dir.isAbsolute()) { + throw new IllegalArgumentException(); + } + }); + } + + @Override + public String toString() { + var tokens = new ArrayList(); + tokens.add(String.format("expect=%s", expectedDirs)); + tokens.add(env.toString()); + return String.join(", ", tokens); + } + + void run(Path workDir) throws IOException { + var allResolved = resolve(workDir, Stream.of( + env.envVariables().entrySet().stream(), + env.systemProperties().entrySet().stream(), + expectedDirs.stream().map(Path::toString).map(dir -> { + return Map.entry(dir, dir); + }) + ).flatMap(x -> x).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue))); + + Function>, Map> filterAllResolved = filterSupplier -> { + var filter = filterSupplier.get(); + return allResolved.entrySet().stream().filter(e -> { + return filter.containsKey(e.getKey()); + }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + }; + + var resolvedEnv = new EnvironmentProviderMock( + filterAllResolved.apply(env::envVariables), + filterAllResolved.apply(env::systemProperties)); + + var resolvedDirs = expectedDirs.stream().map(Path::toString).map(allResolved::get).map(Path::of).toList(); + + for (var dir : resolvedDirs) { + Files.createDirectories(dir); + } + + Globals.main(() -> { + Globals.instance().system(resolvedEnv); + assertEquals(resolvedDirs, WixTool.findWixInstallDirs()); + return 0; + }); + } + + static Builder build() { + return new Builder(); + } + + static final class Builder { + + EnvironmentTestSpec create() { + var env = envVariables.entrySet().stream().collect(Collectors.toMap(e -> { + return e.getKey().variableName(); + }, Map.Entry::getValue)); + + var props = systemProperties.entrySet().stream().collect(Collectors.toMap(e -> { + return e.getKey().propertyName(); + }, Map.Entry::getValue)); + + return new EnvironmentTestSpec(new EnvironmentProviderMock(env, props), List.copyOf(expectedDirs)); + } + + Builder expect(List dirs) { + expectedDirs.addAll(dirs); + return this; + } + + Builder expect(Path... dirs) { + return expect(List.of(dirs)); + } + + Builder expect(String... dirs) { + return expect(List.of(dirs).stream().map(Path::of).toList()); + } + + Builder env(SystemProperty k, String v) { + systemProperties.put(Objects.requireNonNull(k), Objects.requireNonNull(v)); + return this; + } + + Builder env(EnvironmentVariable k, String v) { + envVariables.put(Objects.requireNonNull(k), Objects.requireNonNull(v)); + return this; + } + + Builder standardEnv(EnvironmentVariable k) { + var value = switch (k) { + case PROGRAM_FILES -> "Program Files"; + case PROGRAM_FILES_X86 -> "Program Files(x86)"; + default -> { + throw new IllegalArgumentException(); + } + }; + return env(k, "@@/" + value); + } + + private final Map envVariables = new HashMap<>(); + private final Map systemProperties = new HashMap<>(); + private final List expectedDirs = new ArrayList<>(); + } + + private static Map resolve(Path workDir, Map props) { + + var tokens = new ArrayList(); + + Stream.of( + Stream.of(EnvironmentVariable.values()).map(EnvironmentVariable::variableName), + Stream.of(SystemProperty.values()).map(SystemProperty::propertyName) + ).flatMap(x -> x).map(str -> { + return String.format("@%s@", str); + }).forEach(tokens::add); + + tokens.add(TOKEN_WORKDIR); + + var tokenReplace = new TokenReplace(tokens.toArray(String[]::new)); + + return props.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> { + return tokenReplace.recursiveApplyTo(e.getValue(), token -> { + if (token.equals(TOKEN_WORKDIR)) { + return workDir; + } else { + return Objects.requireNonNull(props.get(token.substring(1, token.length() - 1)), () -> { + return String.format("Unrecognized token: [%s]", token); + }); + } + }); + })); + } + + static final String TOKEN_WORKDIR = "@@"; + } + + private static WixToolsetBuilder toolset() { + return new WixToolsetBuilder(); + } + + private static WixToolMock tool() { + return new WixToolMock(); + } + + private static WixToolMock tool(Path dir) { + return tool().dir(dir); + } + + private static WixToolMock tool(String dir) { + return tool(Path.of(dir)); + } + + private static WixToolset resolveAt(WixToolset toolset, Path root) { + return new WixToolset(toolset.tools().entrySet().stream().collect(toMap(Map.Entry::getKey, e -> { + var toolInfo = e.getValue(); + + assertIsRelative(toolInfo.path()); + + if (toolInfo.path().getNameCount() == 1) { + // The tool is picked from the PATH. + return toolInfo; + } + + ToolInfo newToolInfo = new WixTool.DefaultToolInfo(root.resolve(toolInfo.path()), toolInfo.version()); + if (toolInfo instanceof WixTool.CandleInfo candleInfo) { + newToolInfo = new WixTool.DefaultCandleInfo(newToolInfo, candleInfo.fips()); + } + return newToolInfo; + }))); + } + + private static void assertIsRelative(Path path) { + if (path.isAbsolute()) { + throw new IllegalArgumentException(); + } + } + + static final Path LOOKUP_IN_PATH = Path.of(""); +} + diff --git a/test/jdk/tools/jpackage/junit/windows/junit.java b/test/jdk/tools/jpackage/junit/windows/junit.java index 1a1d0d58f7e..8c290c2c87f 100644 --- a/test/jdk/tools/jpackage/junit/windows/junit.java +++ b/test/jdk/tools/jpackage/junit/windows/junit.java @@ -45,3 +45,15 @@ * jdk/jpackage/internal/wixui/UISpecTest.java * @run junit jdk.jpackage/jdk.jpackage.internal.wixui.UISpecTest */ + +/* @test + * @summary Test WiX Toolset lookup algorithm + * @requires (os.family == "windows") + * @library /test/jdk/tools/jpackage/helpers + * @build jdk.jpackage.test.* + * @build jdk.jpackage.test.mock.* + * @build jdk.jpackage.test.stdmock.* + * @compile/module=jdk.jpackage -Xlint:all -Werror + * jdk/jpackage/internal/WixToolTest.java + * @run junit jdk.jpackage/jdk.jpackage.internal.WixToolTest + */ diff --git a/test/micro/org/openjdk/bench/java/nio/CharsetCanEncode.java b/test/micro/org/openjdk/bench/java/nio/CharsetCanEncode.java index ebfbc217a95..8c08a876696 100644 --- a/test/micro/org/openjdk/bench/java/nio/CharsetCanEncode.java +++ b/test/micro/org/openjdk/bench/java/nio/CharsetCanEncode.java @@ -65,6 +65,9 @@ public class CharsetCanEncode { // sun.nio.cs.UTF_16LE private CharsetEncoder utf16le = Charset.forName("UTF-16LE").newEncoder(); + // sun.nio.cs.UTF_32LE + private CharsetEncoder utf32le = Charset.forName("UTF-32LE").newEncoder(); + @Benchmark public boolean asciiCanEncodeCharYes() { return ascii.canEncode('D'); @@ -184,4 +187,24 @@ public class CharsetCanEncode { public boolean utf16leCanEncodeStringNo() { return utf16le.canEncode(String.valueOf(Character.MIN_SURROGATE)); } + + @Benchmark + public boolean utf32leCanEncodeCharYes() { + return utf32le.canEncode('D'); + } + + @Benchmark + public boolean utf32leCanEncodeStringYes() { + return utf32le.canEncode("D"); + } + + @Benchmark + public boolean utf32leCanEncodeCharNo() { + return utf32le.canEncode(Character.MIN_SURROGATE); + } + + @Benchmark + public boolean utf32leCanEncodeStringNo() { + return utf32le.canEncode(String.valueOf(Character.MIN_SURROGATE)); + } } diff --git a/test/micro/org/openjdk/bench/vm/compiler/VectorAlgorithms.java b/test/micro/org/openjdk/bench/vm/compiler/VectorAlgorithms.java index f60dfcb2d7c..8f5d83c4ae3 100644 --- a/test/micro/org/openjdk/bench/vm/compiler/VectorAlgorithms.java +++ b/test/micro/org/openjdk/bench/vm/compiler/VectorAlgorithms.java @@ -165,6 +165,11 @@ public class VectorAlgorithms { return VectorAlgorithmsImpl.dotProductF_VectorAPI_reduction_after_loop(d.aF, d.bF); } + @Benchmark + public float dotProductF_VectorAPI_fma() { + return VectorAlgorithmsImpl.dotProductF_VectorAPI_fma(d.aF, d.bF); + } + @Benchmark public int hashCodeB_loop() { return VectorAlgorithmsImpl.hashCodeB_loop(d.aB); @@ -227,6 +232,26 @@ public class VectorAlgorithms { return VectorAlgorithmsImpl.findI_VectorAPI(d.aI, e); } + @Benchmark + public int mismatchB_loop() { + return d.wrap_mismatchB(d.mismatchB_idx_idx++, VectorAlgorithmsImpl::mismatchB_loop); + } + + @Benchmark + public int mismatchB_Arrays() { + return d.wrap_mismatchB(d.mismatchB_idx_idx++, VectorAlgorithmsImpl::mismatchB_Arrays); + } + + @Benchmark + public int mismatchB_MemorySegment() { + return d.wrap_mismatchB(d.mismatchB_idx_idx++, VectorAlgorithmsImpl::mismatchB_MemorySegment); + } + + @Benchmark + public int mismatchB_VectorAPI() { + return d.wrap_mismatchB(d.mismatchB_idx_idx++, VectorAlgorithmsImpl::mismatchB_VectorAPI); + } + @Benchmark public Object reverseI_loop() { return VectorAlgorithmsImpl.reverseI_loop(d.aI, d.rI1); diff --git a/test/micro/org/openjdk/bench/vm/compiler/VectorAlgorithmsImpl.java b/test/micro/org/openjdk/bench/vm/compiler/VectorAlgorithmsImpl.java index 3ae4ed81634..a60ecc0f41a 100644 --- a/test/micro/org/openjdk/bench/vm/compiler/VectorAlgorithmsImpl.java +++ b/test/micro/org/openjdk/bench/vm/compiler/VectorAlgorithmsImpl.java @@ -26,6 +26,7 @@ package org.openjdk.bench.vm.compiler; import java.util.Arrays; import java.util.Random; +import java.lang.foreign.MemorySegment; import jdk.incubator.vector.*; /** @@ -94,6 +95,15 @@ public class VectorAlgorithmsImpl { public int[] oopsX4; public int[] memX4; + // Input for mismatchB + // We set m1B and m2B to have identical data, temporarily edit m2B at one position, + // run the mismatch implementation, and then reset that position. This means we + // perform as little mutation while randomizing the input data. + public byte[] m1B; + public byte[] m2B; + public int[] mismatchB_idx; + public int mismatchB_idx_idx = 0; + public Data(int size, int seed, int numX4Objects, float branchProbability) { Random random = new Random(seed); @@ -165,6 +175,30 @@ public class VectorAlgorithmsImpl { ? (byte)(random.nextInt(16) + 'A') : (byte)(random.nextInt(16) + 'a'); } + + // Input data for mismatchB + m1B = new byte[size]; + m2B = new byte[size]; + random.nextBytes(m1B); + System.arraycopy(m1B, 0, m2B, 0, size); + + mismatchB_idx = new int[0x10000]; + for (int i = 0; i < mismatchB_idx.length; i++) { + // Sometimes make no mutation (-1), sometimes pick index for mutation. + mismatchB_idx[i] = (random.nextInt(10) == 0) ? -1 : random.nextInt(m1B.length); + } + } + + public interface MismatchBImpl { + int run(byte[] a, byte[] b); + } + + public int wrap_mismatchB(int idx, MismatchBImpl impl) { + int i = mismatchB_idx[idx & 0xffff]; + if (i != -1) { m2B[i]++; } + int res = impl.run(m1B, m2B); + if (i != -1) { m2B[i]--; } + return res; } } @@ -348,6 +382,21 @@ public class VectorAlgorithmsImpl { return sum; } + public static float dotProductF_VectorAPI_fma(float[] a, float[] b) { + var sums = FloatVector.broadcast(SPECIES_F, 0.0f); + int i; + for (i = 0; i < SPECIES_F.loopBound(a.length); i += SPECIES_F.length()) { + var va = FloatVector.fromArray(SPECIES_F, a, i); + var vb = FloatVector.fromArray(SPECIES_F, b, i); + sums = va.fma(vb, sums); + } + float sum = sums.reduceLanes(VectorOperators.ADD); + for (; i < a.length; i++) { + sum = Math.fma(a[i], b[i], sum); + } + return sum; + } + public static int hashCodeB_loop(byte[] a) { int h = 1; for (int i = 0; i < a.length; i++) { @@ -656,6 +705,44 @@ public class VectorAlgorithmsImpl { return -1; } + public static int mismatchB_loop(byte[] a, byte[] b) { + for (int i = 0; i < a.length; i++) { + if (a[i] != b[i]) { + return i; + } + } + return -1; + } + + public static int mismatchB_Arrays(byte[] a, byte[] b) { + return Arrays.mismatch(a, b); + } + + public static int mismatchB_MemorySegment(byte[] a, byte[] b) { + var aMS = MemorySegment.ofArray(a); + var bMS = MemorySegment.ofArray(b); + return (int) aMS.mismatch(bMS); + } + + public static int mismatchB_VectorAPI(byte[] a, byte[] b) { + int i = 0; + for (; i < SPECIES_B.loopBound(a.length); i += SPECIES_B.length()) { + ByteVector va = ByteVector.fromArray(SPECIES_B, a, i); + ByteVector vb = ByteVector.fromArray(SPECIES_B, b, i); + var mask = va.compare(VectorOperators.NE, vb); + if (mask.anyTrue()) { + return i + mask.firstTrue(); + } + } + for (; i < a.length; i++) { + if (a[i] != b[i]) { + return i; + } + } + return -1; + } + + public static Object reverseI_loop(int[] a, int[] r) { for (int i = 0; i < a.length; i++) { r[a.length - i - 1] = a[i];